feat: ensure-agent + resolve npm-global CLIs without PATH

Add guided multi-select ensure-agent, resolve backend binaries via npm
prefix when not on PATH, and optionally persist npm bin in shell rc.
This commit is contained in:
Zhengshou Lai
2026-08-09 16:53:06 +08:00
parent 9344b376da
commit 3f79301eb3
5 changed files with 487 additions and 77 deletions
+18 -7
View File
@@ -2,6 +2,7 @@
from __future__ import annotations
import os
from pathlib import Path
from unittest.mock import patch
@@ -63,21 +64,31 @@ class TestEnsureBackend:
assert launcher_mod.ensure_backend("hermes") is False
class TestResolveInstalledBinary:
def test_falls_back_to_npm_global_bin(self, tmp_path: Path) -> None:
class TestResolveBackendBinary:
def test_falls_back_to_npm_global_bin(self, tmp_path: Path, monkeypatch) -> None:
bin_dir = tmp_path / "bin"
bin_dir.mkdir()
claude = bin_dir / "claude"
claude.write_text("#!/bin/sh\n")
claude.chmod(0o755)
cfg = launcher_mod._BACKENDS["claude"]
monkeypatch.delenv("CLAUDE_BIN", raising=False)
with (
patch.object(
launcher_mod, "_resolve_backend_binary", return_value=None
),
patch.object(launcher_mod.shutil, "which", return_value=None),
patch.object(launcher_mod, "_npm_global_bin", return_value=bin_dir),
):
assert launcher_mod._resolve_installed_binary(cfg) == str(claude)
assert launcher_mod.resolve_backend_binary("claude") == str(claude)
def test_ensure_npm_bin_prepends_path(
self, tmp_path: Path, monkeypatch
) -> None:
bin_dir = tmp_path / "npm-bin"
bin_dir.mkdir()
monkeypatch.setenv("PATH", "/usr/bin")
with patch.object(launcher_mod, "_npm_global_bin", return_value=bin_dir):
got = launcher_mod.ensure_npm_bin_on_path(persist=False)
assert got == bin_dir
assert str(bin_dir) in os.environ["PATH"].split(os.pathsep)
assert os.environ["PATH"].split(os.pathsep)[0] == str(bin_dir)
class TestOfferInstallGuided: