mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-04 06:29:42 +02:00
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
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
c5c015d648
commit
a0628a40e8
@@ -187,6 +187,13 @@ const discoverySchema = z
|
||||
.strict(),
|
||||
npmPackage: z.string().max(200).optional(),
|
||||
docsUrl: z.url().optional(),
|
||||
// Requires a `reason` on purpose — see the field's own doc comment in types.ts. A
|
||||
// dedicated agent-image layer with no stated reason is a silent id-keyed special case
|
||||
// rebuilding itself inside the data this change moved it out of.
|
||||
agentImageLayer: z
|
||||
.object({ kind: z.literal('dedicated'), reason: z.string().min(1).max(300) })
|
||||
.strict()
|
||||
.optional(),
|
||||
})
|
||||
.strict(),
|
||||
})
|
||||
|
||||
@@ -621,6 +621,10 @@ const PI: CliEntry = {
|
||||
},
|
||||
npmPackage: '@earendil-works/pi-coding-agent',
|
||||
docsUrl: 'https://pi.dev',
|
||||
agentImageLayer: {
|
||||
kind: 'dedicated',
|
||||
reason: 'installed with --ignore-scripts in its own layer, so the flag cannot leak to the shared block',
|
||||
},
|
||||
},
|
||||
},
|
||||
launch: {
|
||||
@@ -835,6 +839,10 @@ const DEEPSEEK: CliEntry = {
|
||||
},
|
||||
npmPackage: '@deepseek-ai/dsh',
|
||||
docsUrl: 'https://github.com/deepseek-ai/deepseek-harness',
|
||||
agentImageLayer: {
|
||||
kind: 'dedicated',
|
||||
reason: 'needs pnpm alongside it (dsh plugin, issue #352) and a dsh-tui profile install',
|
||||
},
|
||||
},
|
||||
},
|
||||
launch: {
|
||||
|
||||
@@ -230,6 +230,22 @@ export interface CliDiscovery {
|
||||
/** Package name for an npm-installable CLI. Display/tooling metadata only. */
|
||||
npmPackage?: string;
|
||||
docsUrl?: string;
|
||||
/**
|
||||
* Present when the agent Docker image (`docker/agent.Dockerfile`) cannot install this
|
||||
* CLI in the shared `npm install -g` layer with the rest and needs its own hand-written
|
||||
* layer instead — a flag that would leak into the shared install (pi's `--ignore-scripts`),
|
||||
* a companion package (deepseek's `pnpm`), or not being on npm at all (antigravity, grok,
|
||||
* omp ship standalone installers). `reason` is REQUIRED, not decorative: it is what
|
||||
* `test/docker-agent-image-coverage.test.ts` prints when a layer for this id goes missing
|
||||
* from the Dockerfile, and it is what keeps this a data field rather than the id-keyed
|
||||
* table it replaced (`AGENT_IMAGE_SPECIAL_CASE_IDS` in `docker-hosts.ts`,
|
||||
* `AGENT_IMAGE_SPECIAL_CASES` in `scripts/lib/cli-catalog.mjs` — two copies kept in step by
|
||||
* hand, outside stock.ts, which is exactly what this registry exists to prevent).
|
||||
* `agentImageNpmPackages()` (docker-hosts.ts) and its `.mjs` mirror both filter on its
|
||||
* presence rather than an id, so the shared npm layer and the special-case layers can never
|
||||
* silently disagree about which CLI belongs in which.
|
||||
*/
|
||||
agentImageLayer?: { kind: 'dedicated'; reason: string };
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+27
-11
@@ -508,6 +508,9 @@ export function agentImageBuildArgs(
|
||||
];
|
||||
}
|
||||
|
||||
/** Tokens allowed in an npm package name reaching a Dockerfile build arg unquoted. */
|
||||
const SAFE_PACKAGE = /^[@A-Za-z0-9][@A-Za-z0-9/._-]*$/;
|
||||
|
||||
/**
|
||||
* npm packages the agent image installs in its shared layer, from the STOCK catalogue.
|
||||
*
|
||||
@@ -515,23 +518,36 @@ export function agentImageBuildArgs(
|
||||
* change what lands inside an image tagged `codeman/agent:base`, or two machines holding that
|
||||
* same tag hold different images and every cache-hit decision downstream is a lie.
|
||||
*
|
||||
* ⚠️ An entry carrying `discovery.install.agentImageLayer` is excluded here — see that field's
|
||||
* doc comment in `types.ts` for why some CLIs need their own hand-written Dockerfile layer
|
||||
* instead of the shared one, and `test/docker-agent-image-coverage.test.ts` for the guard that
|
||||
* an exclusion here still lands in the Dockerfile somewhere.
|
||||
*
|
||||
* ⚠️ This mirrors `agentImageNpmPackages()` in `scripts/lib/cli-catalog.mjs`, which the CLI
|
||||
* build path uses because a `.mjs` cannot import TypeScript. Two producers of one command
|
||||
* line drift; `test/agent-image-build-args-parity.test.ts` is what stops them.
|
||||
* line drift; `test/agent-image-build-args-parity.test.ts` is what stops them — including the
|
||||
* SAFE_PACKAGE regex below, which is duplicated (not imported) in that file for the same
|
||||
* reason and must stay byte-identical to it.
|
||||
*/
|
||||
export function agentImageNpmPackages(): string[] {
|
||||
return STOCK_CLIS.filter((entry) => entry.enabled && !AGENT_IMAGE_SPECIAL_CASE_IDS.has(entry.id as string))
|
||||
.map((entry) => entry.discovery.install.npmPackage)
|
||||
.filter((pkg): pkg is string => Boolean(pkg));
|
||||
const packages: string[] = [];
|
||||
for (const entry of STOCK_CLIS) {
|
||||
if (!entry.enabled || entry.discovery.install.agentImageLayer) continue;
|
||||
const pkg = entry.discovery.install.npmPackage;
|
||||
if (!pkg) continue;
|
||||
// The value is interpolated into a Dockerfile ARG expanded UNQUOTED (word splitting is
|
||||
// how the list becomes several arguments), so a token with whitespace or shell
|
||||
// metacharacters would change what the RUN line means. The source is `stock.ts`, so the
|
||||
// practical risk is nil, but this is the in-app auto-build path and the only one of the
|
||||
// two producers where that had gone unchecked.
|
||||
if (!SAFE_PACKAGE.test(pkg)) {
|
||||
throw new Error(`Refusing unsafe npm package name for "${String(entry.id)}": ${JSON.stringify(pkg)}`);
|
||||
}
|
||||
packages.push(pkg);
|
||||
}
|
||||
return packages;
|
||||
}
|
||||
|
||||
/**
|
||||
* CLIs the agent image installs in their OWN Dockerfile layer rather than the shared npm one.
|
||||
* The registry cannot express what makes each special, so the layers stay hand-written and
|
||||
* `test/docker-agent-image-coverage.test.ts` requires each to carry a reason and still exist.
|
||||
*/
|
||||
const AGENT_IMAGE_SPECIAL_CASE_IDS = new Set(['pi', 'deepseek']);
|
||||
|
||||
/** The `--build-arg` pairs the agent image takes. */
|
||||
export function agentImageBuildArgPairs(): Array<[string, string]> {
|
||||
return [['CLI_NPM_PACKAGES', agentImageNpmPackages().join(' ')]];
|
||||
|
||||
Reference in New Issue
Block a user