diff --git a/app/web/setup_instructions.py b/app/web/setup_instructions.py index 6d04fb9..0cb58f4 100644 --- a/app/web/setup_instructions.py +++ b/app/web/setup_instructions.py @@ -430,34 +430,48 @@ def _finale_lines(*, confirm_step_num: str, has_ca: bool, has_marketplace: bool) ] -def _git_check_block(step_num: str) -> list[str]: - """Git pre-flight check — runs before the marketplace clone. +def _preflight_block(step_num: str) -> list[str]: + """Pre-flight check — runs before the marketplace clone. `claude plugin marketplace add` (and our git-clone fallback) shells out - to `git`, so a missing git binary fails the marketplace step with a - confusing error. Cross-platform install commands cover the three - supported workstation OSes: + to `git`, AND the marketplace step calls `claude` itself, so a missing + binary on either side fails the step with a confusing error. We check + both here so the user gets a single clear "install X" message instead + of debugging a downstream error. + + Cross-platform install commands cover the three supported workstation + OSes: - macOS: Homebrew (`brew install git`). The Xcode CLT bundle also ships git; we prefer brew because it's non-interactive. - Windows: winget (`winget install --id Git.Git -e ...`). Bundled with Windows 10 1809+ and Windows 11; non-interactive with --silent. - Linux: apt or dnf, depending on distro family. + For `claude` we point at the official platform installer docs rather + than vendoring an install one-liner — Anthropic ships per-platform + installers (npm on Linux, native binary on macOS/Windows) and the + canonical instructions live at https://docs.claude.com/claude-code. + `step_num` is parameterized because step ordering shifted between layouts (the marketplace block now runs before diagnose/skills, so - git-check + marketplace are steps 4-5 instead of 6-7). + preflight + marketplace are steps 4-5 instead of 6-7). """ return [ "", - f"{step_num}) Make sure git is installed (required for the marketplace clone):", + f"{step_num}) Make sure git and claude are installed (required for the marketplace clone):", " git --version", + " claude --version", "", - " If that fails (\"command not found\" or similar), install git:", + " If `git --version` fails (\"command not found\" or similar), install git:", " - macOS: brew install git", " - Windows: winget install --id Git.Git -e --source winget --silent", " - Linux: sudo apt-get install git OR sudo dnf install git", "", - " Then re-run `git --version` to confirm before continuing.", + " If `claude --version` fails, install Claude Code:", + " - npm (Linux / WSL): npm i -g @anthropic-ai/claude-code", + " - macOS / Windows native installer: see https://docs.claude.com/claude-code", + "", + " Then re-run both `--version` checks to confirm before continuing.", ] @@ -733,7 +747,7 @@ def resolve_lines( lines.extend(_install_cli_lines(has_ca=has_ca)) # 1 lines.extend(_init_lines()) # 2, 3 if has_marketplace: - lines.extend(_git_check_block(steps["preflight"])) # 4 + lines.extend(_preflight_block(steps["preflight"])) # 4 lines.extend(_marketplace_block( # 5 names, effective_self_signed, has_ca=has_ca, step_num=steps["marketplace"], )) diff --git a/tests/test_setup_instructions.py b/tests/test_setup_instructions.py index 70ef21c..8509f12 100644 --- a/tests/test_setup_instructions.py +++ b/tests/test_setup_instructions.py @@ -66,7 +66,7 @@ def test_resolve_lines_no_plugins_unified_six_step_layout(): assert "claude plugin marketplace add" not in joined assert "claude plugin install" not in joined # No preflight step when there's no marketplace block to gate. - assert "Make sure git is installed" not in joined + assert "Make sure git and claude are installed" not in joined # Legacy `git config sslVerify=false` downgrade must NOT be emitted. # Match the specific config line, not the bare substring (which appears # in the preamble as a "don't do this" example). @@ -292,9 +292,10 @@ def test_resolve_lines_with_plugins_uses_install_first_diagnose_last_layout(): server_host="agnes.example.com", ) joined = "\n".join(lines) - # Step 4 — git pre-flight, with all three platforms' install commands. - assert "4) Make sure git is installed" in joined + # Step 4 — pre-flight, with all three platforms' install commands. + assert "4) Make sure git and claude are installed" in joined assert "git --version" in joined + assert "claude --version" in joined assert "brew install git" in joined assert "winget install --id Git.Git -e --source winget --silent" in joined assert "sudo apt-get install git" in joined or "sudo dnf install git" in joined @@ -318,7 +319,7 @@ def test_resolve_lines_with_plugins_uses_install_first_diagnose_last_layout(): install_idx = joined.index("1) Install the CLI") init_idx = joined.index("2) Bootstrap your Agnes workspace") catalog_idx = joined.index("3) Verify the data is queryable:") - git_idx = joined.index("4) Make sure git is installed") + git_idx = joined.index("4) Make sure git and claude are installed") market_idx = joined.index("5) Register the Agnes Claude Code marketplace") diag_idx = joined.index("6) Run diagnostics:") skills_idx = joined.index("7) Skills") @@ -333,6 +334,41 @@ def test_resolve_lines_with_plugins_uses_install_first_diagnose_last_layout(): assert "{token}" in joined +def test_preflight_checks_both_git_and_claude(): + """Pre-flight (step 4 when marketplace is gated on) checks BOTH binaries + before the marketplace clone — `git --version` is needed for the clone + itself, `claude --version` is needed for the `claude plugin + marketplace add` / `claude plugin install` calls. Either missing + breaks the marketplace step in a confusing way, so we surface the + failure before we get there. + """ + from app.web.setup_instructions import resolve_lines + + joined = "\n".join( + resolve_lines( + "agnes.whl", + plugin_install_names=["foo"], + server_host="agnes.example.com", + ) + ) + # Both version checks present. + assert "git --version" in joined + assert "claude --version" in joined + # Header mentions both tools. + assert "Make sure git and claude are installed" in joined + # Install hints for claude — npm one-liner for Linux/WSL plus a doc URL + # for native installers on macOS / Windows. We don't try to one-line a + # native installer; the canonical instructions live upstream. + assert "npm i -g @anthropic-ai/claude-code" in joined + assert "https://docs.claude.com/claude-code" in joined + # Both checks come BEFORE the marketplace add line. + git_check_idx = joined.index("git --version") + claude_check_idx = joined.index("claude --version") + market_idx = joined.index("claude plugin marketplace add") + assert git_check_idx < market_idx + assert claude_check_idx < market_idx + + def test_resolve_lines_self_signed_legacy_path_adds_git_config_line(): """Legacy fallback (no ca_pem on disk + self_signed_tls=True): the host-scoped `git config sslVerify=false` downgrade is still emitted so existing @@ -365,8 +401,8 @@ def test_resolve_lines_self_signed_no_op_without_plugins(): # Legacy downgrade line not present. assert "git config --global" not in joined assert "claude plugin" not in joined - # No git pre-flight either when there's no marketplace step. - assert "Make sure git is installed" not in joined + # No pre-flight either when there's no marketplace step. + assert "Make sure git and claude are installed" not in joined assert "6) Confirm:" in joined # original layout intact