feat(setup-instructions): preflight checks both git and claude

Renames `_git_check_block` to `_preflight_block` and adds a
`claude --version` check beside `git --version`. Both binaries are
required by the marketplace step — git for the clone fallback,
claude for `claude plugin marketplace add` / `claude plugin install` —
so checking them together gives one clear failure instead of two
confusing downstream errors.

Install hints: `npm i -g @anthropic-ai/claude-code` for Linux / WSL
plus a doc URL (https://docs.claude.com/claude-code) for the native
macOS / Windows installers. We don't try to one-line a native
installer; the canonical instructions live upstream.

Plan: docs/superpowers/plans/2026-05-04-unified-setup-prompt.md task 3.
This commit is contained in:
ZdenekSrotyr 2026-05-04 22:11:38 +02:00
parent e16698c3cc
commit 74b7f6e254
2 changed files with 66 additions and 16 deletions

View file

@ -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"],
))

View file

@ -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