From bd90485dbd91285e83da88082f8210c618fad6e8 Mon Sep 17 00:00:00 2001 From: Vojtech <119944107+cvrysanek@users.noreply.github.com> Date: Tue, 19 May 2026 13:20:33 +0400 Subject: [PATCH] fix(web): setup script step 2 checks pwd, no auto-mkdir (#344) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The /home onboarding page already has a visible manual "Step 3 — create your workspace folder" instructing the user to `mkdir -p ~/ && cd ~/` 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/` 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 `~/`, so users on a custom path see accurate guidance. --- CHANGELOG.md | 9 ++++ app/web/setup_instructions.py | 62 ++++++++++++++++------ tests/test_instance_config.py | 10 +++- tests/test_setup_instructions.py | 88 ++++++++++++++++++++++++++++++++ 4 files changed, 152 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d79e708..468870f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,15 @@ CalVer image tags (`stable-YYYY.MM.N`, `dev-YYYY.MM.N`) are produced for every C ## [Unreleased] ### Changed +- Setup script no longer auto-creates the workspace folder. Step 2 of + the pasted prompt now runs `pwd`, compares it to `$HOME/` + (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 `~/`. - **BREAKING (marketplace identifier)**: synthetic plugin bundling flea skills + agents renamed from `agnes-store-bundle` to `flea`. The served `marketplace.json` now lists `flea` (previously diff --git a/app/web/setup_instructions.py b/app/web/setup_instructions.py index 6862bbf..1e50b8e 100644 --- a/app/web/setup_instructions.py +++ b/app/web/setup_instructions.py @@ -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]: - """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. - Previously the script assumed Claude Code was already cd'd to a sensible - location and ran `agnes init --workspace .` against whatever that - happened to be. With the new explicit mkdir+cd the workspace path is - deterministic — `~/{workspace_dir}` — and the visible /home step block - + this scripted step stay in lockstep. + Step 2 verifies the user is already cd'd into the workspace folder + that the /home onboarding page's visible "Step 3 — create your + workspace folder" told them to create manually (`mkdir -p + ~/{workspace_dir} && cd ~/{workspace_dir}`). The pasted script + DOES NOT auto-create the folder — that would silently override an + 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 by :func:`resolve_lines` from the operator-configured brand. Defaults 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: it authenticates with the PAT, fetches CLAUDE.md (RBAC-filtered), 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 [ "", - "2) Create the workspace folder in the user's home directory and cd into it:", - " # POSIX (macOS / Linux / WSL):", - " mkdir -p \"$HOME/{workspace_dir}\" && cd \"$HOME/{workspace_dir}\"", - " # Windows (PowerShell) — only run if the user is on Windows:", - " # New-Item -ItemType Directory -Force -Path \"$HOME\\{workspace_dir}\" | Out-Null", - " # Set-Location \"$HOME\\{workspace_dir}\"", + "2) Verify the user is already in the workspace folder.", + " The /home page's visible \"Step 3 — create your workspace folder\"", + " already asked the user to run", + " mkdir -p ~/{workspace_dir} && cd ~/{workspace_dir}", + " in their terminal BEFORE pasting this script. Do NOT silently", + " 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 , 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 ", + " 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:", 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 [ "", 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.", ] diff --git a/tests/test_instance_config.py b/tests/test_instance_config.py index 3851c47..513a3ce 100644 --- a/tests/test_instance_config.py +++ b/tests/test_instance_config.py @@ -135,7 +135,10 @@ class TestInstanceBrand: workspace_dir="FoundryAI", )) 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 "Foundry AI workspace is ready" in joined # No raw placeholders survive substitution. @@ -150,7 +153,10 @@ class TestInstanceBrand: from app.web.setup_instructions import resolve_lines joined = "\n".join(resolve_lines("agnes.whl")) 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 "Agnes workspace is ready" in joined mod._instance_config = None diff --git a/tests/test_setup_instructions.py b/tests/test_setup_instructions.py index ef25f9e..c80779d 100644 --- a/tests/test_setup_instructions.py +++ b/tests/test_setup_instructions.py @@ -1000,3 +1000,91 @@ def test_gws_prompt_emits_pass_fail_contract(): body = gws_prompt(gws_oauth_configured=False) assert "✅ Google Workspace ready" 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/" && cd …` which would override an intentional + alternate install path. + + Contract: + - Step 2 runs `pwd` and compares against `$HOME/`. + - 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/"` 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