Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 84 additions & 41 deletions .trellis/spec/backend/claude-code-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@

Read before changing Claude CLI detection, installation, updates, Agent
surface policy or the ordinary-user Windows helper. Main owners are
`services/tooling/claude.rs`, shared `tooling/grok_npm.rs`, macOS
`tooling/npm_runtime.rs`, and `user-helper/src/{claude,grok_npm,cli,windows}.rs`
with the Windows Claude adapter at `user-helper/src/windows/claude.rs`.
`services/tooling/claude.rs`, macOS `tooling/npm_runtime.rs`,
`user-helper/src/{claude,cli,windows}.rs`, and the Windows Claude adapter at
`user-helper/src/windows/claude.rs`.

[External Agent Lifecycle](./external-agent-lifecycle.md) owns inventory,
jobs and IPC. [External Agent Auth](./external-agent-auth.md) owns official
login/logout observation. No new OAuth implementation or package dependency is
needed for this lifecycle.
login/logout observation. [External Agent Product Sources and Desktop
Identity](./external-agent-sources.md) owns shared Claude/Grok live npm
metadata, mirror/integrity admission and compact exact-version plans. No new
OAuth implementation or package dependency is needed for this lifecycle.

## 2. Signatures

Expand All @@ -22,8 +24,14 @@ get_tool_versions(["claude"]) -> ToolVersion

OfficialNpmTool = Grok | Claude
GrokNpmInstallPlan::npm_argv_for(tool) -> closed exact-version argv
grok_npm::claude_manifest() -> validated bundled manifest
grok_npm::registries_matching_manifest(manifest) -> reviewed registry list
external source owner:
grok_npm::resolve_published_manifest(Claude)
-> live root/current-platform manifest
grok_npm::registries_matching_manifest(manifest)
-> admitted registries
fetch_npm_latest_for_tool(package, tool, local) -> dist-tags.latest or /latest version
build_tool_search_paths(tool) -> login PATH + process PATH + product env
default_install(installs) -> PATH default, else the sole entry

Windows: claude-tool --action observe|install|update --job-id <uuid> --pipe <nonce>
UserHelperAction::ClaudeTool { action } -> independent wire identities 15–17
Expand Down Expand Up @@ -52,34 +60,47 @@ generic command execution capability is added.
- Fresh install requires absence. Update requires one confirmed official npm
installation and its actual global prefix. Native, Homebrew and other
package-manager ownership is not silently converted to npm. A same/newer
installed version is not downgraded to the bundled version.

### Reviewed package and shared mirror policy

- Exact version and SHA-512 authority is
`tooling/claude_npm_manifest.json`, compiled into the product. It contains
`@anthropic-ai/claude-code` plus the reviewed Darwin/Windows x64/arm64 native
optional packages. Do not repeat version/hash literals in generic specs.
- The existing shared registry chain is Tencent, Huawei, npmmirror, npmjs.
Each candidate must return matching root and current-platform package name,
exact version and SHA-512. HTTPS-only, no redirects, per-request timeout and
a streaming 1 MiB metadata limit apply. Missing/mismatching sources fail
closed; runtime does not install `latest`.
- npm receives exact package/version, general registry and the matching
`@anthropic-ai:registry` option for that invocation. Scope config must not
silently redirect the request to a different registry. Global/user npmrc and
shell profiles are not rewritten. Grok uses the same scoped-registry owner.
- Claude retains optional dependencies. Newer npm requires a narrow
`--allow-scripts=@anthropic-ai/claude-code` allowance; never allow arbitrary
scripts. The reviewed root installer links/copies its matching native
optional package; a stub or process exit alone is not install success.
installed version is not downgraded to an older published version.

### CLI discovery

- macOS search directories are the login-shell PATH, the current process
PATH, `~/.local/bin`, and product env (`GROK_BIN_DIR` / `~/.grok/bin` for
Grok). Do not walk mise, nvm, fnm or Volta internal install trees, and do
not add per-manager adapters to make those trees visible. If the CLI is on
the user's PATH, that is enough.
- `is_mise_dispatcher` stays in the health filesystem probe. Enumerate must
not use it to drop PATH hits.
- `default_install` prefers the PATH-default entry. Several copies may exist;
that is not `tool_owner_unsupported` when one of them is PATH default. No
PATH default and more than one copy remains unsupported.
- Windows still uses the existing manager search plus the ordinary-user
helper; it does not copy the macOS env-only rule onto Alice's PATH.

### Claude projection of the shared npm source

- Source resolution consumes the exact manifest and admitted registries from
[External Agent Product Sources and Desktop Identity](./external-agent-sources.md).
Claude lifecycle code must not reimplement mirror ordering, metadata bounds,
current-platform package selection or root/platform integrity comparison.
- Directory `latest_version` uses `fetch_npm_latest_for_tool`: npmjs packument
`dist-tags` first, then the shared bounded `/latest` fallback. Claude may
consider `next` only when the local version is already newer than `latest`.
Display and install may race; install resolves a fresh exact manifest.
- Claude npm argv contains `@anthropic-ai/claude-code@<exact-version>`, the
admitted general registry and the matching `@anthropic-ai:registry` option
for that invocation. Global/user npmrc and shell profiles are not rewritten.
- Claude retains optional dependencies. npm versions requiring script policy
receive only `--allow-scripts=@anthropic-ai/claude-code`; arbitrary script
allowance is forbidden. The root installer must produce the matching native
optional package; process exit or a stub alone is not success.
- Require the reviewed Node major floor and Node architecture matching the
product. Do not automatically install Node, use sudo, switch architecture,
or edit PATH. macOS checks an already-discoverable global bin directory;
update checks that npm's prefix matches the selected installation.
- Root/optional metadata comparison and npm's package integrity checks are
the inherited distribution contract, not a claim of registry-independent
signed artifact verification or of login/inference availability.
product. Do not install Node, use sudo, switch architecture or edit PATH.
macOS checks an already-discoverable global bin directory; update checks that
npm's prefix matches the selected installation.
- Shared root/optional metadata comparison and npm package-integrity checks are
distribution admission, not proof of registry-independent artifact signing,
successful login or inference availability.

### Native execution boundaries

Expand Down Expand Up @@ -115,7 +136,10 @@ generic command execution capability is added.
| Condition | Required result |
| --------------------------------------------------------------- | ---------------------------------------------------------------------- |
| Claude Desktop surface or generic launch requested | `surface_not_supported` / `action_not_supported`, no source or process |
| Root/platform metadata differs from manifest | Skip source; source failure if none match |
| Root/platform metadata differs from the resolved latest | Skip source; source failure if none match |
| `/latest` document version is the tag `latest` | Reject; resolve a concrete semver first |
| Multiple PATH-visible copies, one is PATH default | Use PATH default; not `tool_owner_unsupported` |
| macOS discovery walks mise/nvm/fnm/Volta install trees | Contract regression; env/PATH only |
| Node/npm missing, too old or wrong architecture | `tool_host_missing`; no installer |
| Multiple installs, unrecognized owner or prefix mismatch | `tool_owner_unsupported`; no conversion/write |
| Already same/newer npm install on update | No-op; never downgrade |
Expand All @@ -131,9 +155,11 @@ generic command execution capability is added.
Good: a verified mirror installs the exact official optional package and the
actual CLI reports the expected version. Base: a native installation remains
usable/readable but must update through its original owner. Bad: `npm @latest`,
global mirror changes, treating an npm success exit as runnable proof,
executing a user-writable npm from the elevated Windows parent, or a renderer
parser that still expects Claude Desktop/`managed_desktop`.
a compiled reviewed version JSON, walking mise/nvm trees, treating
`len>1` as unsupported when PATH default exists, global mirror changes,
treating an npm success exit as runnable proof, executing a user-writable
npm from the elevated Windows parent, or a renderer parser that still expects
Claude Desktop/`managed_desktop`.

## 6. Tests Required

Expand All @@ -145,21 +171,38 @@ owner/prefix rejection, CLI-only policy and post-install observation. Renderer
tests must parse compact `claude-code` readiness as `cli` / `cli_tooling` and
reject Desktop/`managed_desktop`. Mirror smoke uses an isolated temporary
home/prefix/cache and no login or inference.
The shared source-owner tests must parse a `/latest` document version, reject
`version=latest`, keep executable argv free of `@latest`, match root and
current-platform integrity and prove no compiled version/hash JSON exists.
Claude tests additionally require its exact scoped-registry option and narrow
script allowance. `default_install` tests prefer the PATH default over a
second copy.
Windows native helper execution and real vendor login require their own
matching-host evidence; macOS and portable tests do not establish it.
Helper contract tests must require `npm.cmd` discovery plus
`.raw_arg(&command_line)` / `call {quoted_program}` and must not accept
`Command::new(npm.cmd)`.

Host-side npm command-string adapters used only by Windows are compiled under
`#[cfg(any(target_os = "windows", test))]`, with matching conditional imports.
Pure tests still run on the development host; the unused Windows adapters do
not enter a normal macOS build. Do not add `allow(dead_code)` to silence this
ownership mismatch. Check both the normal library and all test targets with
`mise run check:backend`; portable tests do not prove Windows execution.

## 7. Wrong vs Correct

```text
wrong: install latest from any mirror; npm exit 0 -> installed
wrong: install `@latest` from any mirror; npm exit 0 -> installed
wrong: compile a reviewed version/hash JSON and treat it as latest
wrong: walk ~/.mise / nvm / volta trees; treat any second copy as unsupported
wrong: run user npm from the elevated desktop process
wrong: Command::new("npm.cmd") as the helper application name
wrong: renderer surfacesForAgent(claude-code)=desktop; sourceKind=managed_desktop
correct: compiled manifest -> matching registry/root/platform -> closed plan
-> ordinary-user execution -> actual CLI version/owner readback
correct: login/process PATH + product env -> PATH-default owner
correct: registry /latest -> exact version + integrity -> matching registry
-> closed plan -> ordinary-user execution -> actual CLI version/owner
correct: Windows .cmd shim -> cmd /D /S /C call "{quoted}" via raw_arg
correct: renderer admits compact CLI readiness (cli_tooling, no surfaces array)
correct: Claude npm script policy allows only @anthropic-ai/claude-code
```
Loading