fix(web): setup script step 2 checks pwd, no auto-mkdir (#344)
The /home onboarding page already has a visible manual "Step 3 — create your workspace folder" instructing the user to `mkdir -p ~/<dir> && cd ~/<dir>` BEFORE pasting the install script into Claude Code. The pasted script's step 2 then re-ran the same mkdir+cd, which silently overrode an intentional alternate install path (e.g. user cd'd to ~/work/agnes-prod on purpose) and was redundant on the default path. Step 2 now verifies the user is in `$HOME/<workspace_dir>` via `pwd`. On mismatch it stops and asks the user to either re-paste from the correct folder or reply `install here` to accept the current cwd. Never auto-creates a folder. Step 9 (restart Claude Code) references the install directory confirmed in step 2 instead of a hardcoded `~/<workspace_dir>`, so users on a custom path see accurate guidance.
This commit is contained in:
parent
c6c72b9c00
commit
bd90485dbd
4 changed files with 152 additions and 17 deletions
|
|
@ -11,6 +11,15 @@ CalVer image tags (`stable-YYYY.MM.N`, `dev-YYYY.MM.N`) are produced for every C
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
- Setup script no longer auto-creates the workspace folder. Step 2 of
|
||||||
|
the pasted prompt now runs `pwd`, compares it to `$HOME/<workspace_dir>`
|
||||||
|
(the folder the /home page's visible Step 3 told the user to create
|
||||||
|
manually), and on mismatch warns + asks the user to either re-paste
|
||||||
|
from the right folder or reply `install here` to accept the current
|
||||||
|
cwd. Respects an intentional alternate install path instead of
|
||||||
|
silently switching the user back to the default. Step 9 (restart
|
||||||
|
Claude Code) now references the install directory confirmed in step 2
|
||||||
|
rather than a hardcoded `~/<workspace_dir>`.
|
||||||
- **BREAKING (marketplace identifier)**: synthetic plugin bundling flea
|
- **BREAKING (marketplace identifier)**: synthetic plugin bundling flea
|
||||||
skills + agents renamed from `agnes-store-bundle` to `flea`. The
|
skills + agents renamed from `agnes-store-bundle` to `flea`. The
|
||||||
served `marketplace.json` now lists `flea` (previously
|
served `marketplace.json` now lists `flea` (previously
|
||||||
|
|
|
||||||
|
|
@ -324,19 +324,29 @@ def _install_cli_lines(*, has_ca: bool, server_url_placeholder: str = "{server_u
|
||||||
|
|
||||||
|
|
||||||
def _init_lines(server_url_placeholder: str = "{server_url}") -> list[str]:
|
def _init_lines(server_url_placeholder: str = "{server_url}") -> list[str]:
|
||||||
"""Steps 2-4 — workspace folder bootstrap, then `agnes init` + smoke verify.
|
"""Steps 2-4 — workspace folder check, then `agnes init` + smoke verify.
|
||||||
|
|
||||||
Step 2 (new) explicitly creates the workspace folder and cd's into it.
|
Step 2 verifies the user is already cd'd into the workspace folder
|
||||||
Previously the script assumed Claude Code was already cd'd to a sensible
|
that the /home onboarding page's visible "Step 3 — create your
|
||||||
location and ran `agnes init --workspace .` against whatever that
|
workspace folder" told them to create manually (`mkdir -p
|
||||||
happened to be. With the new explicit mkdir+cd the workspace path is
|
~/{workspace_dir} && cd ~/{workspace_dir}`). The pasted script
|
||||||
deterministic — `~/{workspace_dir}` — and the visible /home step block
|
DOES NOT auto-create the folder — that would silently override an
|
||||||
+ this scripted step stay in lockstep.
|
intentional choice to install at a different path (e.g. the user
|
||||||
|
cd'd to `~/work/agnes-prod` on purpose). Instead we `pwd`, compare
|
||||||
|
to `$HOME/{workspace_dir}`, and on mismatch warn loudly and ask the
|
||||||
|
user to either re-paste from the right folder or explicitly confirm
|
||||||
|
"install here" in the current cwd.
|
||||||
|
|
||||||
`{workspace_dir}` and `{instance_brand}` are placeholders pre-substituted
|
`{workspace_dir}` and `{instance_brand}` are placeholders pre-substituted
|
||||||
by :func:`resolve_lines` from the operator-configured brand. Defaults
|
by :func:`resolve_lines` from the operator-configured brand. Defaults
|
||||||
keep `~/Agnes` behavior for instances that don't set the brand knob.
|
keep `~/Agnes` behavior for instances that don't set the brand knob.
|
||||||
|
|
||||||
|
`agnes init --workspace .` continues to use the current cwd, so once
|
||||||
|
step 2 has confirmed (or the user has explicitly accepted) the
|
||||||
|
install dir, step 3 lands in the right place. Step 9's restart-claude
|
||||||
|
cue references "this same directory" so users on a custom path see
|
||||||
|
accurate guidance.
|
||||||
|
|
||||||
`agnes init` is the workspace-rails delivery mechanism for everyone:
|
`agnes init` is the workspace-rails delivery mechanism for everyone:
|
||||||
it authenticates with the PAT, fetches CLAUDE.md (RBAC-filtered),
|
it authenticates with the PAT, fetches CLAUDE.md (RBAC-filtered),
|
||||||
writes AGNES_WORKSPACE.md (human-facing docs), installs Claude Code
|
writes AGNES_WORKSPACE.md (human-facing docs), installs Claude Code
|
||||||
|
|
@ -352,14 +362,36 @@ def _init_lines(server_url_placeholder: str = "{server_url}") -> list[str]:
|
||||||
"""
|
"""
|
||||||
return [
|
return [
|
||||||
"",
|
"",
|
||||||
"2) Create the workspace folder in the user's home directory and cd into it:",
|
"2) Verify the user is already in the workspace folder.",
|
||||||
" # POSIX (macOS / Linux / WSL):",
|
" The /home page's visible \"Step 3 — create your workspace folder\"",
|
||||||
" mkdir -p \"$HOME/{workspace_dir}\" && cd \"$HOME/{workspace_dir}\"",
|
" already asked the user to run",
|
||||||
" # Windows (PowerShell) — only run if the user is on Windows:",
|
" mkdir -p ~/{workspace_dir} && cd ~/{workspace_dir}",
|
||||||
" # New-Item -ItemType Directory -Force -Path \"$HOME\\{workspace_dir}\" | Out-Null",
|
" in their terminal BEFORE pasting this script. Do NOT silently",
|
||||||
" # Set-Location \"$HOME\\{workspace_dir}\"",
|
" re-create the folder here — the user may have intentionally cd'd",
|
||||||
|
" to a different location to install {instance_brand} there instead.",
|
||||||
"",
|
"",
|
||||||
" The remaining steps run inside this directory.",
|
" Run:",
|
||||||
|
" pwd",
|
||||||
|
" Expected: $HOME/{workspace_dir} (i.e. ~/{workspace_dir})",
|
||||||
|
"",
|
||||||
|
" If `pwd` matches the expected path: continue silently to step 3.",
|
||||||
|
"",
|
||||||
|
" If `pwd` does NOT match, STOP and tell the user verbatim:",
|
||||||
|
"",
|
||||||
|
" \"You are in <current-pwd>, but {instance_brand} is normally",
|
||||||
|
" installed in ~/{workspace_dir} (see /home Step 3). Either run",
|
||||||
|
" mkdir -p ~/{workspace_dir} && cd ~/{workspace_dir}",
|
||||||
|
" in your terminal now and re-paste this setup script, OR reply",
|
||||||
|
" 'install here' to install {instance_brand} in <current-pwd>",
|
||||||
|
" instead. Reply 'abort' to stop.\"",
|
||||||
|
"",
|
||||||
|
" Wait for the user's reply.",
|
||||||
|
" - 'install here' → continue to step 3 in the current cwd.",
|
||||||
|
" The cwd you saw from `pwd` is the install",
|
||||||
|
" directory; remember it for step 9.",
|
||||||
|
" - 'abort' / anything else → stop without making any changes.",
|
||||||
|
" Do NOT run `mkdir`, do NOT `cd`, do NOT",
|
||||||
|
" continue to step 3.",
|
||||||
"",
|
"",
|
||||||
"3) Bootstrap your {instance_brand} workspace in this directory:",
|
"3) Bootstrap your {instance_brand} workspace in this directory:",
|
||||||
f" agnes init --server-url \"{server_url_placeholder}\" --token \"{{token}}\" --workspace .",
|
f" agnes init --server-url \"{server_url_placeholder}\" --token \"{{token}}\" --workspace .",
|
||||||
|
|
@ -490,7 +522,7 @@ def _restart_claude_lines(step_num: str) -> list[str]:
|
||||||
return [
|
return [
|
||||||
"",
|
"",
|
||||||
f"{step_num}) Restart Claude Code so every plugin, MCP server, and SessionStart hook installed above actually loads:",
|
f"{step_num}) Restart Claude Code so every plugin, MCP server, and SessionStart hook installed above actually loads:",
|
||||||
" Tell me to type `/exit` (or close the Claude Code session entirely), then run `claude` again from this same `~/{workspace_dir}` directory.",
|
" Tell me to type `/exit` (or close the Claude Code session entirely), then run `claude` again from this same directory — the install dir confirmed in step 2 (`~/{workspace_dir}` on the default path, or whatever cwd the user explicitly accepted with 'install here').",
|
||||||
" The next session boots with all marketplace plugins, every connector's keychain entries / OAuth grants, and the agnes-welcome + refresh-marketplace SessionStart hooks active. This is the last action before the Confirm summary — once I'm back in Claude Code, setup is complete.",
|
" The next session boots with all marketplace plugins, every connector's keychain entries / OAuth grants, and the agnes-welcome + refresh-marketplace SessionStart hooks active. This is the last action before the Confirm summary — once I'm back in Claude Code, setup is complete.",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,10 @@ class TestInstanceBrand:
|
||||||
workspace_dir="FoundryAI",
|
workspace_dir="FoundryAI",
|
||||||
))
|
))
|
||||||
assert "Set up the Foundry AI CLI on this machine." in joined
|
assert "Set up the Foundry AI CLI on this machine." in joined
|
||||||
assert "mkdir -p \"$HOME/FoundryAI\"" in joined
|
# Step 2 is a pwd-check now (no auto-mkdir); brand + workspace_dir
|
||||||
|
# thread through the warning copy + expected-path string.
|
||||||
|
assert "$HOME/FoundryAI" in joined
|
||||||
|
assert "mkdir -p ~/FoundryAI && cd ~/FoundryAI" in joined
|
||||||
assert "Bootstrap your Foundry AI workspace" in joined
|
assert "Bootstrap your Foundry AI workspace" in joined
|
||||||
assert "Foundry AI workspace is ready" in joined
|
assert "Foundry AI workspace is ready" in joined
|
||||||
# No raw placeholders survive substitution.
|
# No raw placeholders survive substitution.
|
||||||
|
|
@ -150,7 +153,10 @@ class TestInstanceBrand:
|
||||||
from app.web.setup_instructions import resolve_lines
|
from app.web.setup_instructions import resolve_lines
|
||||||
joined = "\n".join(resolve_lines("agnes.whl"))
|
joined = "\n".join(resolve_lines("agnes.whl"))
|
||||||
assert "Set up the Agnes CLI on this machine." in joined
|
assert "Set up the Agnes CLI on this machine." in joined
|
||||||
assert "mkdir -p \"$HOME/Agnes\"" in joined
|
# Step 2 is a pwd-check (no auto-mkdir); default path threads
|
||||||
|
# through as `$HOME/Agnes` + the warning's manual-mkdir example.
|
||||||
|
assert "$HOME/Agnes" in joined
|
||||||
|
assert "mkdir -p ~/Agnes && cd ~/Agnes" in joined
|
||||||
assert "Bootstrap your Agnes workspace" in joined
|
assert "Bootstrap your Agnes workspace" in joined
|
||||||
assert "Agnes workspace is ready" in joined
|
assert "Agnes workspace is ready" in joined
|
||||||
mod._instance_config = None
|
mod._instance_config = None
|
||||||
|
|
|
||||||
|
|
@ -1000,3 +1000,91 @@ def test_gws_prompt_emits_pass_fail_contract():
|
||||||
body = gws_prompt(gws_oauth_configured=False)
|
body = gws_prompt(gws_oauth_configured=False)
|
||||||
assert "✅ Google Workspace ready" in body
|
assert "✅ Google Workspace ready" in body
|
||||||
assert "❌ Google Workspace setup failed" in body
|
assert "❌ Google Workspace setup failed" in body
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Step 2 — workspace folder check (no auto-mkdir).
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def test_step_2_checks_pwd_does_not_auto_mkdir():
|
||||||
|
"""Step 2 must verify the user is already in the workspace folder
|
||||||
|
(the /home page Step 3 manual mkdir+cd) instead of silently re-running
|
||||||
|
`mkdir -p "$HOME/<dir>" && cd …` which would override an intentional
|
||||||
|
alternate install path.
|
||||||
|
|
||||||
|
Contract:
|
||||||
|
- Step 2 runs `pwd` and compares against `$HOME/<workspace_dir>`.
|
||||||
|
- Emits a warning message naming both the expected path and the
|
||||||
|
manual mkdir line the user already saw on /home.
|
||||||
|
- Does NOT emit `mkdir -p "$HOME/<workspace_dir>"` or the equivalent
|
||||||
|
PowerShell `New-Item -ItemType Directory -Force` line.
|
||||||
|
- Asks the user to reply 'install here' to proceed in a non-default
|
||||||
|
cwd, or 'abort' to stop. No automatic folder creation.
|
||||||
|
"""
|
||||||
|
from app.web.setup_instructions import resolve_lines
|
||||||
|
|
||||||
|
joined = "\n".join(resolve_lines("agnes.whl"))
|
||||||
|
|
||||||
|
# The new check-only step header.
|
||||||
|
assert "2) Verify the user is already in the workspace folder." in joined
|
||||||
|
|
||||||
|
# `pwd` check + expected-path comparison must be present.
|
||||||
|
assert "pwd" in joined
|
||||||
|
assert "$HOME/Agnes" in joined # default workspace_dir
|
||||||
|
assert "~/Agnes" in joined
|
||||||
|
|
||||||
|
# Warning copy that references the /home Step 3 manual mkdir line.
|
||||||
|
assert "/home Step 3" in joined
|
||||||
|
assert "mkdir -p ~/Agnes && cd ~/Agnes" in joined
|
||||||
|
|
||||||
|
# Explicit "install here" / "abort" decision tree.
|
||||||
|
assert "'install here'" in joined
|
||||||
|
assert "'abort'" in joined
|
||||||
|
|
||||||
|
# The auto-mkdir lines from the previous step 2 must be GONE.
|
||||||
|
assert 'mkdir -p "$HOME/Agnes"' not in joined
|
||||||
|
assert 'mkdir -p "$HOME/{workspace_dir}"' not in joined
|
||||||
|
assert 'New-Item -ItemType Directory -Force -Path "$HOME\\Agnes"' not in joined
|
||||||
|
assert "Set-Location \"$HOME\\Agnes\"" not in joined
|
||||||
|
|
||||||
|
|
||||||
|
def test_step_2_warning_substitutes_custom_brand():
|
||||||
|
"""Custom brand + workspace_dir must thread through the new step 2
|
||||||
|
warning copy — no leftover placeholders, expected path matches the
|
||||||
|
operator's configured workspace dir."""
|
||||||
|
from app.web.setup_instructions import resolve_lines
|
||||||
|
|
||||||
|
joined = "\n".join(resolve_lines(
|
||||||
|
"agnes.whl",
|
||||||
|
instance_brand="Foundry AI",
|
||||||
|
workspace_dir="FoundryAI",
|
||||||
|
))
|
||||||
|
assert "2) Verify the user is already in the workspace folder." in joined
|
||||||
|
assert "$HOME/FoundryAI" in joined
|
||||||
|
assert "~/FoundryAI" in joined
|
||||||
|
assert "mkdir -p ~/FoundryAI && cd ~/FoundryAI" in joined
|
||||||
|
# Brand + workspace_dir thread through the warning copy (the text
|
||||||
|
# wraps across two lines so we check the substrings separately).
|
||||||
|
assert "but Foundry AI is normally" in joined
|
||||||
|
assert "installed in ~/FoundryAI" in joined
|
||||||
|
# No placeholders survive into the rendered text.
|
||||||
|
assert "{workspace_dir}" not in joined
|
||||||
|
assert "{instance_brand}" not in joined
|
||||||
|
|
||||||
|
|
||||||
|
def test_step_9_restart_references_install_dir_not_hardcoded():
|
||||||
|
"""Step 9 must describe the restart cwd as the directory confirmed in
|
||||||
|
step 2 (mentioning the default path) rather than a bare hardcoded
|
||||||
|
`~/{workspace_dir}`. This keeps the wording accurate when the user
|
||||||
|
chose an alternate install path via 'install here' in step 2."""
|
||||||
|
from app.web.setup_instructions import resolve_lines
|
||||||
|
|
||||||
|
joined = "\n".join(resolve_lines("agnes.whl"))
|
||||||
|
assert "9) Restart Claude Code" in joined
|
||||||
|
# Wording references the step-2 confirmation.
|
||||||
|
assert "install dir confirmed in step 2" in joined
|
||||||
|
# Default path still mentioned as the expected baseline.
|
||||||
|
assert "~/Agnes" in joined
|
||||||
|
# The "install here" callout in the restart step keeps the user-flow
|
||||||
|
# connection visible.
|
||||||
|
assert "'install here'" in joined
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue