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
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/dshinstalls 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 doctorrows. A suffix map would just be the hand-maintained list again. - On a
wget-only host, only the menu entries that actually needcurlare held back (still shown as copy-paste hints); thenpm install -gentries, which never needed it, are unaffected. Rewritingcurltowgetinside a string about to be executed is the wrong instinct either way. CODEMAN_NONINTERACTIVE=1still 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.