Two consumers of the registry cannot import TypeScript: `install.sh`, which runs
via `curl | bash` before any checkout exists, and `scripts/build-agent-image.mjs`.
Both currently hand-maintain their own CLI lists, and both have already drifted.
`scripts/generate-cli-catalog.mts` (`npm run generate:cli-catalog`, plus a
`--check` mode) emits from `STOCK_CLIS`:
- `config/clis.stock.json` for the `.mjs` and the tests. It carries `enabled` —
the field the earlier attempt omitted, which is how a disabled CLI's npm
package still got baked into every agent image.
- a marker-delimited block inside `install.sh`, embedded rather than fetched.
The embedded copy is the FULL catalogue on purpose: the earlier design fetched
it and fell back to a hardcoded two-CLI list, degrading silently on an empty
response. There is no degraded mode to fall into now.
The block is bash 3.2 safe: parallel indexed arrays, no associative arrays, no
namerefs, no mapfile. Variable-length lists use OFFSET/LENGTH windows into one
flat array rather than a delimiter, so a $HOME containing a space needs no IFS
handling and `shell` (no binaries) gets length 0 and is never iterated. Search
paths are emitted dir-major, matching the probe order the hand-written arrays
use and `test/install-sh-detection-parity.test.ts` pins.
Only fields the two consumers need are exported. `launch`/`env`/`capabilities`/
`overlays` are spawn-time concerns the server alone interprets, and a test
asserts they never leak into the artifact.
`main()` sits behind an `isMainModule()` guard so the sync test can import the
renderers. Without it, importing the module would rewrite the artifacts as a
side effect of checking them — passing always, guarding never.
This commit adds the block; it does not yet delete the hand-written arrays, so
the detection pin keeps measuring both against each other.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015EMxQreQUZX5ZyybxAGh12