fix(onboarding): /home install flow + agnes init UX hardening (#350)

* fix(web): /home Step 2 recommends --dangerously-skip-permissions for setup

The Step 4 paste runs ~20 shell commands (CLI install, workspace
bootstrap, marketplace clone, MCP register, connector logins). Previous
Step 2 recommended auto-accept-edits via Shift + Tab, which covers file
edits but not Bash — users still clicked ~20 Yes prompts during setup.

Step 2 now leads with `claude --dangerously-skip-permissions` as the
recommended session flag (Bash + edits both skip). Session-scoped, drops
on next plain `claude` — safe here because the pasted script is
generated by this server and ends after a fixed sequence; the flag does
not weaken future Claude sessions.

Auto-accept-edits via Shift + Tab kept as the strict-review fallback;
persistent YOLO allowlist link to /setup-advanced#yolo unchanged.

* fix(web): swap /home Steps 2↔3, claude --yolo as copy-button command

Folder creation moves to Step 2; Step 3 launches Claude from that
directory with `claude --dangerously-skip-permissions`. The YOLO flag
is rendered through the standard .install-cmd + copy-button affordance
(matching Step 1 + Step 2), not inline prose. Step 4 paste runs ~20
shell commands that auto-accept-edits would not cover (Bash still
prompts), so the YOLO flag is the default recommendation; session-
scoped, drops on next plain `claude`.

Setup script's pwd-check warning copy refreshed to reference "/home
Step 2" (the new folder-creation step number).

# Conflicts:
#	CHANGELOG.md

* fix(web): open YOLO setup-advanced link in new tab

Step 3 install-hero's persistent-YOLO link now opens /setup-advanced#yolo
in a new window so users don't lose their /home install context mid-
setup. target="_blank" + rel="noopener" (no reverse-tabnabbing).

* fix(web): merge /home Step 3 fallback prose into prior paragraph

Drop the <br><br> between the 'Session-scoped' line and the 'Prefer
reviewing each command' line so the strict-review fallback flows on
the same paragraph — less vertical space in the install-hero block.

* docs(web): add "What leaves your machine" privacy callout on /home

Install-hero lead now includes a short privacy paragraph: explains that
session telemetry (prompts / tool-calls / tool-responses) flows back to
the central catalog for failure-pattern analysis while raw data rows
the user queries locally stay on their machine. Points at /agnes-private
as the per-session opt-out.

Also collapses leftover cherry-pick conflict markers in CHANGELOG.md
into one clean [Unreleased] section.

* fix(init): harden agnes init UX — 5 issues from David's report

1. chmod +x hooks. agnes init + agnes refresh-marketplace --bootstrap
   now set the execute bit on every .sh they land on disk
   (`<workspace>/.claude/hooks/*.sh` after init; every `.sh` under the
   `~/.agnes/marketplace` clone after a bootstrap/pull). Git checkout
   doesn't always preserve filemode (filemode=false repos, ZIP
   extractions), so hooks were firing with "Permission denied" — silent
   SessionStart / PreToolUse breakage. Best-effort, no-op on Windows.

2. --token-file + AGNES_TOKEN. agnes init now accepts `--token-file
   <path>` and an `AGNES_TOKEN` env fallback alongside `--token`.
   Precedence: --token > --token-file > AGNES_TOKEN. The file / env-var
   paths dodge Claude Code's auto-classifier, which sometimes flags a
   long bearer token in `--token "eyJ..."` command line as a credential-
   exfil pattern. The pasted setup script now uses `--token-file
   ~/.agnes/token` (token written via single-quoted heredoc, umask 077)
   for the same reason.

3. Bash(agnes *) in allow. Default `.claude/settings.json` permissions.
   allow seeded by agnes init now includes `Bash(agnes *)` alongside the
   bare `Bash` entry, so Claude Code's classifier sees an explicit allow
   for subsequent `agnes <verb>` calls inside the workspace it just
   bootstrapped.

4. .zshrc PATH dedup. Setup-script step 1's PATH-persist snippet
   (no-CA install path) replaced with a `grep -qF + ||` idiom so a
   re-run doesn't append a duplicate `export PATH=...` line. Fixed-
   string match (not regex) per the dedup-bug report.

5. `!` prefix doc note. Setup-script step 3 now explicitly tells the
   user: if Claude Code blocks an `agnes` command, prefix it with `!`
   (e.g. `! agnes init …`) to run the command directly in the shell,
   bypassing the auto-classifier.

* release: 0.55.1 — /home onboarding install-hero rework + agnes init UX hardening

---------

Co-authored-by: ZdenekSrotyr <zdenek.srotyr@keboola.com>
This commit is contained in:
Vojtech 2026-05-19 17:26:35 +04:00 committed by GitHub
parent 64cf78860d
commit ae67c40a81
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 239 additions and 38 deletions

View file

@ -10,6 +10,58 @@ CalVer image tags (`stable-YYYY.MM.N`, `dev-YYYY.MM.N`) are produced for every C
## [Unreleased] ## [Unreleased]
## [0.55.1] — 2026-05-19
### Added
- `/home` install-hero lead now includes a short "What leaves your
machine" privacy callout: explains that prompts / tool-calls /
tool-responses travel back to the central catalog while raw data
rows stay local, and points at `/agnes-private` as the per-session
opt-out.
- `agnes init` now accepts `--token-file <path>` and `AGNES_TOKEN`
env-var fallback alongside `--token`. Precedence: `--token` >
`--token-file` > `AGNES_TOKEN`. The file-/env-var paths dodge
Claude Code's auto-classifier, which sometimes flags a long bearer
token in an `--token "eyJ..."` command line as a credential-exfil
pattern. The pasted setup script now uses `--token-file
~/.agnes/token` (token written via single-quoted heredoc, umask 077)
for the same reason.
### Changed
- `/home` onboarding install-hero reordered: folder creation is now
Step 2 (was Step 3) and starting Claude with
`claude --dangerously-skip-permissions` is the new Step 3, rendered
with the same `.install-cmd` + copy-button affordance as the other
steps. Step 4 paste runs ~20 shell commands that auto-accept-edits
would not cover (Bash still prompts), so the YOLO flag is the
default recommendation (session-scoped, drops on next plain
`claude`). Shift + Tab → auto-accept-edits kept as the strict-
review fallback; persistent YOLO allowlist link to
`/setup-advanced#yolo` opens in a new tab so users don't lose
their `/home` install context. Setup script's "Verify cwd" warning
copy refreshed to reference "/home Step 2".
- `agnes init` adds `Bash(agnes *)` to the default `permissions.allow`
list in the seeded `.claude/settings.json`. Without it, Claude Code
was blocking subsequent `agnes <verb>` invocations (`agnes catalog`,
`agnes pull`, …) inside the workspace it had just bootstrapped.
- `agnes init` and `agnes refresh-marketplace --bootstrap` now
`chmod +x` every `.sh` they land on disk
(`<workspace>/.claude/hooks/*.sh` after init; every `.sh` under
`~/.agnes/marketplace` after a clone/pull). Git checkout doesn't
always preserve the file-mode bit (filemode=false repos, ZIP
extractions), so hooks were firing with "Permission denied" —
silent `SessionStart` / `PreToolUse` breakage. Best-effort: no-op
on Windows NTFS.
- Setup script step 3 now uses `--token-file ~/.agnes/token` plus a
single-quoted heredoc for the token write, and includes an explicit
note about the `!` prefix fallback when Claude Code's classifier
blocks an `agnes <verb>` invocation (e.g. `! agnes init …`).
- Setup script step 1 (no-CA install path) now emits a robust
`grep -qF + ||` snippet for the optional `~/.local/bin` PATH
persistence so re-runs don't append a duplicate entry to the
user's rc file (fixed-string match + short-circuit per the dedup
bug report).
## [0.55.0] — 2026-05-19 ## [0.55.0] — 2026-05-19
### Added ### Added
@ -133,6 +185,7 @@ CalVer image tags (`stable-YYYY.MM.N`, `dev-YYYY.MM.N`) are produced for every C
save) mirroring the Memory Domain pattern. save) mirroring the Memory Domain pattern.
### Changed ### Changed
<<<<<<< HEAD
- **Bulk-assign tables → package** modal — package dropdown options - **Bulk-assign tables → package** modal — package dropdown options
now carry a `(N of M tables already in)` suffix so admins see the now carry a `(N of M tables already in)` suffix so admins see the
existing distribution before picking a target. Counts surface existing distribution before picking a target. Counts surface
@ -442,6 +495,16 @@ CalVer image tags (`stable-YYYY.MM.N`, `dev-YYYY.MM.N`) are produced for every C
- Single PR cutover (no two-phase rollout). Legacy - Single PR cutover (no two-phase rollout). Legacy
`marketplace_plugins.is_system` + `user_plugin_optouts` retained `marketplace_plugins.is_system` + `user_plugin_optouts` retained
per spec D1 — Marketplace was deliberately not touched. per spec D1 — Marketplace was deliberately not touched.
=======
<<<<<<< HEAD
- /home onboarding Step 2 retitled "turn on permission-skip for setup"
and now leads with `claude --dangerously-skip-permissions` as the
recommended session flag, because the Step 4 paste runs ~20 shell
commands that auto-accept-edits does not cover (Bash still prompts).
The flag is session-scoped, drops on next plain `claude`. Auto-accept
via Shift + Tab kept as the strict-review fallback for users who want
to approve each command; persistent YOLO setup link unchanged.
>>>>>>> 4c4e9e42 (fix(web): swap /home Steps 2↔3, claude --yolo as copy-button command)
## [0.54.29] — 2026-05-19 ## [0.54.29] — 2026-05-19
@ -496,6 +559,18 @@ CalVer image tags (`stable-YYYY.MM.N`, `dev-YYYY.MM.N`) are produced for every C
UI shows the corrected project). The orchestrator now compares the UI shows the corrected project). The orchestrator now compares the
two at every rebuild and, if they differ, calls two at every rebuild and, if they differ, calls
`rebuild_from_registry()` to regenerate the extract. `rebuild_from_registry()` to regenerate the extract.
=======
- /home onboarding reordered: folder creation is now Step 2 (was
Step 3) and starting Claude with `claude --dangerously-skip-permissions`
is the new Step 3 (was the auto-mode step), rendered with the same
`.install-cmd` + copy-button affordance the other steps use. Step 4
paste runs ~20 shell commands that auto-accept-edits would not cover
(Bash still prompts), so the YOLO flag is the default recommendation;
session-scoped, drops on next plain `claude`. Shift + Tab → auto-
accept-edits kept as the strict-review fallback; persistent YOLO
allowlist link to /setup-advanced#yolo unchanged. Setup script's
"Verify cwd" warning copy refreshed to reference "/home Step 2".
>>>>>>> c195e0fa (fix(web): swap /home Steps 2↔3, claude --yolo as copy-button command)
- Setup script no longer auto-creates the workspace folder. Step 2 of - 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 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 (the folder the /home page's visible Step 3 told the user to create

View file

@ -307,8 +307,13 @@ def _install_cli_lines(*, has_ca: bool, server_url_placeholder: str = "{server_u
"", "",
" If `agnes --version` fails after install because ~/.local/bin is not on PATH:", " If `agnes --version` fails after install because ~/.local/bin is not on PATH:",
" export PATH=\"$HOME/.local/bin:$PATH\"", " export PATH=\"$HOME/.local/bin:$PATH\"",
" # persist: append the same line to your ~/.zshrc or ~/.bashrc", " # Persist for future shells. Use `grep -qF` (fixed-string,",
" # (the trust block in step 0 already does this for you on first run).", " # not regex) + `||` short-circuit so a re-run doesn't append",
" # a duplicate. Pick the rc file your login shell reads:",
" RC=\"$HOME/.zshrc\" # or ~/.bashrc / ~/.bash_profile",
" grep -qF '$HOME/.local/bin' \"$RC\" 2>/dev/null \\",
" || echo 'export PATH=\"$HOME/.local/bin:$PATH\"' >> \"$RC\"",
" # (The trust block in step 0 already does this for you on first run.)",
] ]
return [ return [
"1) Install the CLI:", "1) Install the CLI:",
@ -319,7 +324,12 @@ def _install_cli_lines(*, has_ca: bool, server_url_placeholder: str = "{server_u
"", "",
" If `agnes --version` fails after install because ~/.local/bin is not on PATH:", " If `agnes --version` fails after install because ~/.local/bin is not on PATH:",
" export PATH=\"$HOME/.local/bin:$PATH\"", " export PATH=\"$HOME/.local/bin:$PATH\"",
" # persist: append the same line to your ~/.zshrc or ~/.bashrc", " # Persist for future shells. Use `grep -qF` (fixed-string, not",
" # regex) + `||` short-circuit so a re-run doesn't append a",
" # duplicate. Pick the rc file your login shell reads:",
" RC=\"$HOME/.zshrc\" # or ~/.bashrc / ~/.bash_profile",
" grep -qF '$HOME/.local/bin' \"$RC\" 2>/dev/null \\",
" || echo 'export PATH=\"$HOME/.local/bin:$PATH\"' >> \"$RC\"",
] ]
@ -327,7 +337,7 @@ def _init_lines(server_url_placeholder: str = "{server_url}") -> list[str]:
"""Steps 2-4 — workspace folder check, then `agnes init` + smoke verify. """Steps 2-4 — workspace folder check, then `agnes init` + smoke verify.
Step 2 verifies the user is already cd'd into the workspace folder Step 2 verifies the user is already cd'd into the workspace folder
that the /home onboarding page's visible "Step 3 — create your that the /home onboarding page's visible "Step 2 — create your
workspace folder" told them to create manually (`mkdir -p workspace folder" told them to create manually (`mkdir -p
~/{workspace_dir} && cd ~/{workspace_dir}`). The pasted script ~/{workspace_dir} && cd ~/{workspace_dir}`). The pasted script
DOES NOT auto-create the folder that would silently override an DOES NOT auto-create the folder that would silently override an
@ -363,7 +373,7 @@ def _init_lines(server_url_placeholder: str = "{server_url}") -> list[str]:
return [ return [
"", "",
"2) Verify the user is already in the workspace folder.", "2) Verify the user is already in the workspace folder.",
" The /home page's visible \"Step 3 — create your workspace folder\"", " The /home page's visible \"Step 2 — create your workspace folder\"",
" already asked the user to run", " already asked the user to run",
" mkdir -p ~/{workspace_dir} && cd ~/{workspace_dir}", " mkdir -p ~/{workspace_dir} && cd ~/{workspace_dir}",
" in their terminal BEFORE pasting this script. Do NOT silently", " in their terminal BEFORE pasting this script. Do NOT silently",
@ -379,7 +389,7 @@ def _init_lines(server_url_placeholder: str = "{server_url}") -> list[str]:
" If `pwd` does NOT match, STOP and tell the user verbatim:", " If `pwd` does NOT match, STOP and tell the user verbatim:",
"", "",
" \"You are in <current-pwd>, but {instance_brand} is normally", " \"You are in <current-pwd>, but {instance_brand} is normally",
" installed in ~/{workspace_dir} (see /home Step 3). Either run", " installed in ~/{workspace_dir} (see /home Step 2). Either run",
" mkdir -p ~/{workspace_dir} && cd ~/{workspace_dir}", " mkdir -p ~/{workspace_dir} && cd ~/{workspace_dir}",
" in your terminal now and re-paste this setup script, OR reply", " in your terminal now and re-paste this setup script, OR reply",
" 'install here' to install {instance_brand} in <current-pwd>", " 'install here' to install {instance_brand} in <current-pwd>",
@ -393,8 +403,25 @@ def _init_lines(server_url_placeholder: str = "{server_url}") -> list[str]:
" Do NOT run `mkdir`, do NOT `cd`, do NOT", " Do NOT run `mkdir`, do NOT `cd`, do NOT",
" continue to step 3.", " 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 .", " Write the PAT to a file FIRST, then run `agnes init` with",
" `--token-file`. Passing the JWT inline via `--token \"eyJ...\"`",
" sometimes trips Claude Code's auto-classifier (long bearer",
" token in a command line looks like a credential-exfil pattern);",
" piping the token through a file keeps it out of the command-",
" line argv entirely.",
"",
" mkdir -p ~/.agnes && umask 077 && cat > ~/.agnes/token <<'AGNES_PAT'",
"{token}",
"AGNES_PAT",
f" agnes init --server-url \"{server_url_placeholder}\" --token-file ~/.agnes/token --workspace .",
"",
" If Claude Code still blocks an `agnes` command (e.g. the",
" `agnes init` line above or `agnes catalog` in step 4), prefix",
" it with `!` to run the command directly in your shell,",
" bypassing the auto-classifier — e.g. `! agnes init …`. The",
" `!` prefix is Claude Code's escape hatch for commands you",
" explicitly trust.",
"", "",
" This authenticates with the PAT, fetches your CLAUDE.md (RBAC-filtered),", " This authenticates with the PAT, fetches your CLAUDE.md (RBAC-filtered),",
" writes AGNES_WORKSPACE.md (human-facing docs), installs Claude Code", " writes AGNES_WORKSPACE.md (human-facing docs), installs Claude Code",

View file

@ -1057,6 +1057,9 @@
<p class="lead"> <p class="lead">
{{ instance_brand }} gives <strong>Claude Code</strong> on your computer access to your team's <strong>curated data, plugins, third-party tools (Asana, Google Workspace, Atlassian), and shared knowledge</strong> — so you can ask questions and get answers in plain language, right from your terminal. This page walks you through the <strong>one-time setup (~10 minutes)</strong>; the install script also connects your tools for you, so there's no extra page to visit. Everything it installs lives in your home folder (<code style="background: rgba(255,255,255,0.12); padding: 1px 6px; border-radius: 4px; font-family: var(--hp-font-mono); font-size: 12.5px;">~/{{ workspace_dir }}</code>) and can be removed in one command. {{ instance_brand }} gives <strong>Claude Code</strong> on your computer access to your team's <strong>curated data, plugins, third-party tools (Asana, Google Workspace, Atlassian), and shared knowledge</strong> — so you can ask questions and get answers in plain language, right from your terminal. This page walks you through the <strong>one-time setup (~10 minutes)</strong>; the install script also connects your tools for you, so there's no extra page to visit. Everything it installs lives in your home folder (<code style="background: rgba(255,255,255,0.12); padding: 1px 6px; border-radius: 4px; font-family: var(--hp-font-mono); font-size: 12.5px;">~/{{ workspace_dir }}</code>) and can be removed in one command.
</p> </p>
<p class="lead lead-privacy">
<strong>What leaves your machine.</strong> Session telemetry — prompts, tool calls, and tool responses — flows back to the central catalog so the team can analyse failure patterns. Raw data rows you query locally stay on your machine; only the prompt/response transcript travels. Need a session off the record? Toggle Private in Claude Code (by typing <code style="background: rgba(255,255,255,0.12); padding: 1px 6px; border-radius: 4px; font-family: var(--hp-font-mono); font-size: 12.5px;">/agnes-private</code>) to disable telemetry for that chat — nothing from that session reaches the catalog.
</p>
<div class="install-block"> <div class="install-block">
<div class="label">Step 1 — install Claude Code</div> <div class="label">Step 1 — install Claude Code</div>
@ -1097,19 +1100,8 @@
</div> </div>
</div> </div>
{% if home_automode.show %}
<div class="install-block"> <div class="install-block">
<div class="label">Step 2 — turn on auto-mode (recommended before Step 4)</div> <div class="label">Step 2 — create your workspace folder</div>
<div class="install-note">
In the Claude Code session you just signed into, press <strong>Shift + Tab</strong>. Claude cycles modes: default → <strong>auto-accept edits</strong> → plan mode → default; the footer shows <code>⏵⏵</code> when auto-accept is on. On the first cycle to auto-accept, Claude asks whether to make it the default — say <strong>yes</strong>. Closed the session already? Run <code>claude</code> again, then press <strong>Shift + Tab</strong>.
<br><br>
Want full auto-approve including Bash? See <a href="/setup-advanced#yolo">YOLO mode</a> on /setup-advanced — pairs <code>--dangerously-skip-permissions</code> with a reviewed <code>~/.claude/settings.local.json</code> allowlist. Skip if you're not sure.
</div>
</div>
{% endif %}
<div class="install-block">
<div class="label">Step 3 — create your workspace folder</div>
<div class="os-tabs" role="tablist" aria-label="Operating system"> <div class="os-tabs" role="tablist" aria-label="Operating system">
<button type="button" role="tab" class="os-tab is-active" <button type="button" role="tab" class="os-tab is-active"
data-os-tab="unix" aria-selected="true">macOS / Linux / WSL</button> data-os-tab="unix" aria-selected="true">macOS / Linux / WSL</button>
@ -1126,10 +1118,28 @@ Set-Location "$HOME\{{ workspace_dir }}"</span>
<button class="copy-btn" data-copy-target="install-cmd-mkdir-windows">Copy</button> <button class="copy-btn" data-copy-target="install-cmd-mkdir-windows">Copy</button>
</div> </div>
<div class="install-note"> <div class="install-note">
This is where {{ instance_brand }} will live. Run the command in the terminal you opened for Step 1, then keep that terminal handy — the next step pastes the setup script into Claude Code from this same directory. This is where {{ instance_brand }} will live. Run the command in the terminal you opened for Step 1, then keep that terminal handy — Step 3 launches Claude Code from this same directory.
</div> </div>
</div> </div>
{% if home_automode.show %}
<div class="install-block">
<div class="label">Step 3 — start Claude Code with permission-skip (recommended before Step 4)</div>
<div class="install-note">
The Step 4 paste runs many shell commands (CLI install, workspace bootstrap, marketplace clone, MCP register, connector logins). Launch Claude Code from your Step 2 directory with this flag so the setup completes without ~20 Yes/No prompt approvals:
</div>
<div class="install-cmd">
<span class="multiline" id="install-cmd-claude-yolo">claude --dangerously-skip-permissions</span>
<button class="copy-btn" data-copy-target="install-cmd-claude-yolo">Copy</button>
</div>
<div class="install-note">
Session-scoped — drops on next plain <code>claude</code>. Safe here because the script you paste in Step 4 is generated by this server and ends after a fixed sequence; the flag does not weaken future Claude sessions. Prefer reviewing each command? Run plain <code>claude</code> and press <strong>Shift + Tab</strong> to cycle on <strong>auto-accept edits</strong> (default → auto-accept edits → plan mode; footer shows <code>⏵⏵</code>). Covers file edits, not Bash — expect ~20 prompt clicks during Step 4.
<br><br>
Persistent YOLO (allowlisted): see <a href="/setup-advanced#yolo" target="_blank" rel="noopener">YOLO mode</a> on /setup-advanced — pairs <code>--dangerously-skip-permissions</code> with a reviewed <code>~/.claude/settings.local.json</code> allowlist.
</div>
</div>
{% endif %}
<div class="install-block"> <div class="install-block">
<div class="label">Step 4 — install {{ instance_brand }} from inside Claude Code</div> <div class="label">Step 4 — install {{ instance_brand }} from inside Claude Code</div>
<p class="setup-cta-lead"> <p class="setup-cta-lead">

View file

@ -87,6 +87,32 @@ _INIT_COMPLETE_FILE = ".claude/init-complete"
_CA_ENV_VARS = ("SSL_CERT_FILE", "REQUESTS_CA_BUNDLE", "GIT_SSL_CAINFO") _CA_ENV_VARS = ("SSL_CERT_FILE", "REQUESTS_CA_BUNDLE", "GIT_SSL_CAINFO")
def _chmod_workspace_hooks(workspace: Path) -> None:
"""Set execute bit on every `.sh` under `<workspace>/.claude/hooks/`.
Claude Code's plugin install path doesn't always preserve the execute
bit on shell hook files depending on the archive format the plugin
ships in (zip, no-bit-preserving git checkout config, etc.), hooks
can land on disk as `rw-r--r--` and every fire returns Permission
denied. The user-visible symptom is a silent SessionStart / PreToolUse
failure that looks like the hooks just aren't installed.
Best-effort. No-op on Windows NTFS via Git Bash (chmod is meaningless
on NTFS without ACLs). Failures are swallowed a hook the user can
still read is no worse than the pre-fix baseline.
"""
hooks_dir = workspace / ".claude" / "hooks"
if not hooks_dir.is_dir():
return
for path in hooks_dir.rglob("*.sh"):
try:
current = path.stat().st_mode
# Add user/group/other execute. Same effect as `chmod +x`.
path.chmod(current | 0o111)
except OSError:
pass
def _is_windows_host() -> bool: def _is_windows_host() -> bool:
"""True when the Python interpreter sees Windows underneath. """True when the Python interpreter sees Windows underneath.
@ -182,7 +208,25 @@ init_app = typer.Typer(help="Bootstrap an analyst workspace in this directory")
@init_app.callback(invoke_without_command=True) @init_app.callback(invoke_without_command=True)
def init( def init(
server_url: str = typer.Option(..., "--server-url", help="Agnes server URL"), server_url: str = typer.Option(..., "--server-url", help="Agnes server URL"),
token: str = typer.Option(..., "--token", help="Personal access token"), token: Optional[str] = typer.Option(
None, "--token",
help=(
"Personal access token. Can also be supplied via the "
"AGNES_TOKEN env var or --token-file (see also). Inline "
"--token sometimes trips Claude Code's auto-classifier "
"(long bearer-token string in a command line); prefer "
"--token-file or AGNES_TOKEN to dodge that."
),
),
token_file: Optional[str] = typer.Option(
None, "--token-file",
help=(
"Path to a file whose first non-blank line is the PAT. Wins "
"over AGNES_TOKEN env when both are set; loses to an explicit "
"--token flag. The token never appears in the command string "
"this way, which dodges Claude Code's bearer-token classifier."
),
),
force: bool = typer.Option(False, "--force", help="Re-initialize an existing workspace"), force: bool = typer.Option(False, "--force", help="Re-initialize an existing workspace"),
workspace_str: Optional[str] = typer.Option(None, "--workspace", help="Target dir (default: cwd)"), workspace_str: Optional[str] = typer.Option(None, "--workspace", help="Target dir (default: cwd)"),
skip_materialize: bool = typer.Option( skip_materialize: bool = typer.Option(
@ -200,6 +244,33 @@ def init(
workspace = Path(workspace_str).resolve() if workspace_str else Path.cwd() workspace = Path(workspace_str).resolve() if workspace_str else Path.cwd()
server_url = server_url.rstrip("/") server_url = server_url.rstrip("/")
# Resolve the token. Precedence: explicit --token > --token-file >
# AGNES_TOKEN env var > error. --token-file and AGNES_TOKEN exist so
# the analyst can paste an `agnes init --server-url … --token-file
# ~/.agnes/token` (or simply set the env) without Claude Code's
# auto-classifier flagging the long JWT in the command line.
if token is None and token_file:
try:
for line in Path(token_file).expanduser().read_text(encoding="utf-8").splitlines():
line = line.strip()
if line:
token = line
break
except OSError as exc:
typer.echo(render_error(0, {"detail": {
"kind": "partial_state",
"hint": f"--token-file {token_file!r} could not be read: {exc}",
}}), err=True)
raise typer.Exit(1)
if token is None:
token = os.environ.get("AGNES_TOKEN", "").strip() or None
if not token:
typer.echo(render_error(0, {"detail": {
"kind": "partial_state",
"hint": "Supply a token via --token, --token-file, or AGNES_TOKEN env var.",
}}), err=True)
raise typer.Exit(1)
# Best-effort cleanup before ANY TLS handshake fires below — stale # Best-effort cleanup before ANY TLS handshake fires below — stale
# SSL_CERT_FILE / REQUESTS_CA_BUNDLE / GIT_SSL_CAINFO pointers from a # SSL_CERT_FILE / REQUESTS_CA_BUNDLE / GIT_SSL_CAINFO pointers from a
# previous Agnes install on this host (or its Windows User-scope # previous Agnes install on this host (or its Windows User-scope
@ -417,11 +488,12 @@ def init(
if not settings_path.exists(): if not settings_path.exists():
settings_path.parent.mkdir(parents=True, exist_ok=True) settings_path.parent.mkdir(parents=True, exist_ok=True)
settings_path.write_text(json.dumps( settings_path.write_text(json.dumps(
{"model": "sonnet", "permissions": {"allow": ["Read", "Bash", "Grep", "Glob"]}}, {"model": "sonnet", "permissions": {"allow": ["Read", "Bash", "Bash(agnes *)", "Grep", "Glob"]}},
indent=2, indent=2,
), encoding="utf-8") ), encoding="utf-8")
install_claude_hooks(workspace) install_claude_hooks(workspace)
install_claude_commands(workspace) install_claude_commands(workspace)
_chmod_workspace_hooks(workspace)
# ------------------------------------------------------------------ # ------------------------------------------------------------------
# Step 6: CLAUDE.local.md stub — only when absent. `--force` does NOT # Step 6: CLAUDE.local.md stub — only when absent. `--force` does NOT

View file

@ -242,6 +242,19 @@ def _bootstrap_clone(token: str) -> bool:
except OSError: except OSError:
pass pass
# Add execute bit to every `.sh` under the clone — git's checkout doesn't
# always preserve the file-mode bit (filemode=false repos, archive
# extractions), and Claude Code's later `plugin install` copies the
# files into the workspace `.claude/hooks/` AS-IS, so hooks that lost
# the +x bit here would fire with Permission denied. Fixing at the
# source (marketplace clone) means every downstream plugin install
# gets executable hooks for free. Best-effort: no-op on Windows NTFS.
for sh in CLONE_DIR.rglob("*.sh"):
try:
sh.chmod(sh.stat().st_mode | 0o111)
except OSError:
pass
if not _register_clone_with_claude(CLONE_DIR): if not _register_clone_with_claude(CLONE_DIR):
return False return False

View file

@ -1,6 +1,6 @@
[project] [project]
name = "agnes-the-ai-analyst" name = "agnes-the-ai-analyst"
version = "0.55.0" version = "0.55.1"
description = "Agnes — AI Data Analyst platform for AI analytical systems" description = "Agnes — AI Data Analyst platform for AI analytical systems"
requires-python = ">=3.11,<3.14" requires-python = ">=3.11,<3.14"
license = "MIT" license = "MIT"

View file

@ -260,10 +260,14 @@ def test_home_automode_env_can_hide(fresh_db, monkeypatch):
def test_home_renders_automode_block_by_default(fresh_db, monkeypatch): def test_home_renders_automode_block_by_default(fresh_db, monkeypatch):
"""The auto-mode step renders by default for the not-onboarded /home """The permission-mode step renders by default for the not-onboarded
view. The block is now Step 2 (the install-flow reorder put auto-mode /home view. The block is Step 3 (folder creation moved up to Step 2
BEFORE the Agnes install so users have auto-accept on for Step 3's so the user mkdir+cd's first, then this step launches Claude in that
~20 commands), so its label is "Step 2 — turn on auto-mode".""" directory with the right flag for Step 4's ~20 shell commands).
Label primarily recommends `claude --dangerously-skip-permissions`
via the standard `.install-cmd` + copy-button affordance; auto-
accept-edits via Shift + Tab kept as the strict fallback for users
who want to review each command."""
monkeypatch.delenv("AGNES_HOME_SHOW_AUTOMODE", raising=False) monkeypatch.delenv("AGNES_HOME_SHOW_AUTOMODE", raising=False)
from src.db import get_system_db, close_system_db from src.db import get_system_db, close_system_db
@ -277,10 +281,10 @@ def test_home_renders_automode_block_by_default(fresh_db, monkeypatch):
c = _client() c = _client()
body = c.get("/home", cookies={"access_token": sess}).text body = c.get("/home", cookies={"access_token": sess}).text
assert "Step 2 — turn on auto-mode" in body assert "Step 3 — start Claude Code with permission-skip" in body
# The auto-mode step now lives inside the install-hero as an # Recommended path: `claude --dangerously-skip-permissions`.
# install-block (peer with Step 1 + Step 3), not as a separate assert "claude --dangerously-skip-permissions" in body
# automode-card. Look for the label + the keystroke prompt. # Strict fallback: Shift + Tab → auto-accept-edits.
assert "Shift + Tab" in body assert "Shift + Tab" in body
@ -298,7 +302,7 @@ def test_home_hides_automode_block_when_env_off(fresh_db, monkeypatch):
c = _client() c = _client()
body = c.get("/home", cookies={"access_token": sess}).text body = c.get("/home", cookies={"access_token": sess}).text
assert "Step 2 — turn on auto-mode" not in body assert "Step 3 — start Claude Code with permission-skip" not in body
def test_navbar_home_link_uses_home_route(fresh_db, monkeypatch): def test_navbar_home_link_uses_home_route(fresh_db, monkeypatch):

View file

@ -1033,8 +1033,8 @@ def test_step_2_checks_pwd_does_not_auto_mkdir():
assert "$HOME/Agnes" in joined # default workspace_dir assert "$HOME/Agnes" in joined # default workspace_dir
assert "~/Agnes" in joined assert "~/Agnes" in joined
# Warning copy that references the /home Step 3 manual mkdir line. # Warning copy that references the /home Step 2 manual mkdir line.
assert "/home Step 3" in joined assert "/home Step 2" in joined
assert "mkdir -p ~/Agnes && cd ~/Agnes" in joined assert "mkdir -p ~/Agnes && cd ~/Agnes" in joined
# Explicit "install here" / "abort" decision tree. # Explicit "install here" / "abort" decision tree.

View file

@ -104,8 +104,8 @@ def test_home_onboarded_user_sees_nav_hub(fresh_db):
# All four inline install-blocks are hidden post-onboarding — the # All four inline install-blocks are hidden post-onboarding — the
# labels rendered inside the install-block divs go away. # labels rendered inside the install-block divs go away.
assert "Step 1 — install Claude Code" not in body assert "Step 1 — install Claude Code" not in body
assert "Step 2 — turn on auto-mode" not in body assert "Step 2 — create your workspace folder" not in body
assert "Step 3 — create your workspace folder" not in body assert "Step 3 — start Claude Code with permission-skip" not in body
assert "Step 4 — install" not in body assert "Step 4 — install" not in body
@ -139,7 +139,7 @@ def test_connectors_section_removed_from_home(fresh_db):
# Auto-mode peer section still gone (legacy guard, not regressed). # Auto-mode peer section still gone (legacy guard, not regressed).
assert 'class="automode-card"' not in body assert 'class="automode-card"' not in body
assert 'data-section="step3"' not in body assert 'data-section="step3"' not in body
assert "Step 2 — turn on auto-mode" not in body assert "Step 3 — start Claude Code with permission-skip" not in body
# Dedicated connectors block is gone from /home in BOTH states. # Dedicated connectors block is gone from /home in BOTH states.
assert 'class="connector-tiles"' not in body assert 'class="connector-tiles"' not in body
assert 'data-section="connectors"' not in body assert 'data-section="connectors"' not in body