Files
Codeman/.changeset/cli-catalog-consumers.md
T
DevvynandClaude Sonnet 5 a0628a40e8 fix(cli-registry): address maintainer review on #380
Rebased onto current master (the one real conflict was the import line
in docker-hosts.ts Ark0N flagged; kept both), then addressed every
point from the review:

**1. Rebase.** Done — this branch now sits on current upstream/master.

**2. Agent-image special cases are data now, not an id-keyed table
outside stock.ts.** `AGENT_IMAGE_SPECIAL_CASE_IDS`/`AGENT_IMAGE_SPECIAL_CASES`
are gone. `CliDiscovery.install.agentImageLayer?: { kind: 'dedicated';
reason: string }` is a field on the registry entry itself (pi,
deepseek), `reason` is required by schema.ts, both producers
(docker-hosts.ts and cli-catalog.mjs) filter on its presence instead
of an id, and the coverage test reads it from the generated catalogue.
Also added the npm-package-name validation to the TS producer, which
only the .mjs one had — same SAFE_PACKAGE regex, duplicated
(necessarily, one side can't import the other) and now pinned
byte-identical by a new parity test.

**3. Changeset said five, it's eight.** (Not nine — see the DeepSeek
point below, which changes the true count.) Reworded to state it
structurally rather than pin a number that will go stale again.

Then the four behavior-changing findings:

- **DeepSeek was offered as a normal install option but can't actually
  drive a pane.** `npm install -g @deepseek-ai/dsh` installs the
  launcher only; DeepSeek ships no profile that can run standalone.
  The generator now emits an empty install command for any
  `launcherProfile` entry, so install.sh's menu (which requires a
  non-empty command) skips it and falls through to its docs URL hint
  instead — matching what the old hand-written code did before this
  PR replaced it.
- **wget-only hosts lost every automatic install, including the npm
  ones that never needed curl.** The menu-building loop now filters
  PER ENTRY (only a command starting with `curl ` is held back) rather
  than wiping the whole menu when DOWNLOADER != curl.
- **The DISPLAY/TRUSTED split and the catalogue refresh didn't hold up
  under review** (refresh's only real write was the label; it ran
  before the Node existence check; its own eval-detection test was
  tripped by the word "eval'd" in a comment). Dropped entirely per
  your own recommendation — embedded catalogue only, no network
  fetch, no second array. install-sh-invariants.test.ts now asserts
  the refresh/DISPLAY machinery does not exist rather than testing its
  internals.

The three take-or-leave items, applied:

- `dsh_banner_probe`'s bash 3.2 empty-array bug: `${runner[@]}` →
  `${runner[@]+"${runner[@]}"}`. Verified live in a real `bash:3.2.57`
  container with `timeout` removed from PATH — crashed before, clean
  now, full `detect_all_clis` path exercised end to end.
- `docker-agent-image-coverage.test.ts` now anchors on each layer's
  `<binary> --version` proof line instead of `Dockerfile.includes(binary)`,
  which stayed true if a layer were deleted but its comment survived.
- Doc drift: docs/docker-cases.md (four → five, and now describes the
  data field), docker/agent.Dockerfile's "other four CLIs" comment (no
  longer a magic number — CLI_NPM_PACKAGES is generated and can grow),
  CLAUDE.md's install.sh size (104KB → ~112KB) and its stale mention of
  the now-dropped refresh.

Verified: tsc clean, prettier clean, the full targeted suite (142
tests across the 8 affected files) green, and the full `npm test` gate
diffed BY TEST NAME against a clean upstream/master baseline run on
this same machine — identical 201-name failure set both sides (168
tests / 67 files, all pre-existing Windows-environment noise: symlinks,
PTY spawning, POSIX permission bits — none of it touching anything
this PR changes), zero new failures either side of the diff.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R9ZSTEenc8soSu9bTi8Xru
2026-09-13 17:43:14 +08:00

3.7 KiB

aicodeman
aicodeman
minor

install.sh and the Docker agent image now read the shipped CLI catalogue instead of hand-maintaining their own lists.

Adding a CLI to src/config/cli-registry/stock.ts and running npm run generate:cli-catalog wires it into the installer's detection, its install menu and its closing reminder, and into the agent image's npm layer. Previously each of those was a separate hand-written list that had to be kept in step and was not: upstream b6d0f1fa is "wire OMP into install.sh's CLI detection (it had none)", where a user with only omp installed was told no AI CLI was found and offered Claude Code, and the section comment above that code named six of the nine CLIs.

The generator emits two committed artifacts, because neither consumer can import TypeScript: config/clis.stock.json for the Docker build, and a marked block inside install.sh itself, which runs via curl | bash before any checkout exists. The embedded copy is the FULL catalogue: an earlier attempt fetched it and fell back to a hardcoded two-CLI list, degrading silently on an empty response, and there is no degraded mode to fall into now — nor a network fetch at all, since a curl | bash from master already carries a catalogue exactly as fresh as the script itself.

Trust model is unchanged and now mechanical. The server still never executes an entry's install command. install.sh executes only commands embedded in itself — same file, same TLS fetch, same commit as the curl | bash line that fetched it — and nothing pulled from the network at install time is ever run, because nothing is fetched at install time at all.

The agent image respects enabled. The generated catalogue carries that flag, so a CLI shipping disabled is no longer baked into every image. It reads the stock catalogue rather than the merged registry, so a user's ~/.codeman/clis.json cannot change what is inside an image tagged codeman/agent:base.

User-visible changes, all in the installer:

  • The install menu is built from the catalogue, so it offers every enabled CLI with an install command that can drive a pane on its own — eight today, rather than the previous fixed two. Gemini had a command in the registry and appeared in no list in the script at all. DeepSeek is the one enabled CLI with a registry command that is deliberately NOT offered: npm install -g @deepseek-ai/dsh installs only the launcher, which ships no profile that can drive a terminal on its own, so choosing it used to leave the user with an AI CLI the installer considered "found" but that could not actually run anything. It still gets a hint pointing at its docs.
  • Its entries use the registry's labels ("Claude" rather than "Claude Code"), the same trade already made for codeman doctor rows. A suffix map would just be the hand-maintained list again.
  • On a wget-only host, only the menu entries that actually need curl are held back (still shown as copy-paste hints); the npm install -g entries, which never needed it, are unaffected. Rewriting curl to wget inside a string about to be executed is the wrong instinct either way.
  • CODEMAN_NONINTERACTIVE=1 still defaults to Claude Code, unchanged.

install.sh remains bash 3.2 compatible (macOS ships it): parallel indexed arrays with offset/length windows instead of delimiters, no associative arrays, namerefs, mapfile or here-strings. CI now runs bash -n, executes the script inside a real bash:3.2 container — which is what catches expanding an empty array under set -u, a runtime abort bash -n cannot see — and checks the generated artifacts are in sync.

docker/server.Dockerfile is deliberately untouched; its narrower CLI list is now asserted as a declared omission list so the divergence is visible rather than accidental.