mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-02 21:49: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
@@ -52,7 +52,12 @@ interface CatalogEntry {
|
||||
binaries: string[];
|
||||
searchDirs: string[];
|
||||
identity?: { arg: string; regex: string };
|
||||
install: { command: Record<string, string>; npmPackage?: string; docsUrl?: string };
|
||||
install: {
|
||||
command: Record<string, string>;
|
||||
npmPackage?: string;
|
||||
docsUrl?: string;
|
||||
agentImageLayer?: { kind: 'dedicated'; reason: string };
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -75,6 +80,7 @@ function toCatalogEntry(entry: CliEntry): CatalogEntry {
|
||||
command: { ...install.command } as Record<string, string>,
|
||||
...(install.npmPackage ? { npmPackage: install.npmPackage } : {}),
|
||||
...(install.docsUrl ? { docsUrl: install.docsUrl } : {}),
|
||||
...(install.agentImageLayer ? { agentImageLayer: { ...install.agentImageLayer } } : {}),
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -108,8 +114,19 @@ function shPath(dir: string, binary: string): string {
|
||||
* the exact platform, else linux, else whatever is declared. Resolved HERE, at generation
|
||||
* time, so that fallback logic stays in tested TypeScript instead of being reimplemented in
|
||||
* bash against an array the script would have to index by platform anyway.
|
||||
*
|
||||
* ⚠️ EMPTY for a `launcherProfile` entry (DeepSeek today), deliberately: `npm install -g
|
||||
* @deepseek-ai/dsh` installs the LAUNCHER, not something that can drive a pane on its own — it
|
||||
* ships only the `web`/`headless` profiles, neither of which is a terminal TUI. Emitting the
|
||||
* command made the installer offer DeepSeek as a normal menu choice: picking it printed
|
||||
* "DeepSeek installed at ...", counted as a found AI CLI, and left the user with a `dsh` that
|
||||
* cannot actually run anything, with no mention of the Run dropdown's profile installer that
|
||||
* fixes that. An empty command here means install.sh's menu-building loop (which requires a
|
||||
* non-empty CLI_INSTALL_CMD_TRUSTED entry) skips it and the hint printer falls through to the
|
||||
* docs URL instead — see cli_catalog_print_install_hints in install.sh.
|
||||
*/
|
||||
function installCommandFor(entry: CliEntry, platform: InstallPlatform): string {
|
||||
if (entry.discovery.launcherProfile) return '';
|
||||
const { command } = entry.discovery.install;
|
||||
return command[platform] ?? command.linux ?? Object.values(command)[0] ?? '';
|
||||
}
|
||||
@@ -173,8 +190,8 @@ export function renderInstallShBlock(entries: CliEntry[] = STOCK_CLIS): string {
|
||||
'#',
|
||||
'# ⚠️ TRUST BOUNDARY: CLI_CMD_LINUX/CLI_CMD_DARWIN are the ONLY source of a command this',
|
||||
'# script will ever execute, and they arrive embedded in this file — same TLS fetch, same',
|
||||
'# commit as the script itself. Nothing fetched at install time may write them; the',
|
||||
'# refresh may only touch the *_DISPLAY copy. See cli_catalog_select_platform below.',
|
||||
'# commit as the script itself. Nothing fetched at install time is ever executed; there is',
|
||||
'# no network refresh of these arrays. See cli_catalog_select_platform below.',
|
||||
arr('CLI_IDS', ids),
|
||||
arr('CLI_LABELS', labels),
|
||||
arr('CLI_ENABLED', enabled),
|
||||
|
||||
+14
-11
@@ -20,17 +20,16 @@ const CATALOG_PATH = fileURLToPath(new URL('../../config/clis.stock.json', impor
|
||||
* ⚠️ Filters on `enabled`. That is the field the earlier attempt's export omitted, which is
|
||||
* how a CLI that ships disabled still had its package baked into every image.
|
||||
*
|
||||
* ⚠️ SPECIAL_CASES are excluded here and installed by their own hand-written Dockerfile
|
||||
* layers, because the registry cannot express what makes them special — a flag, a companion
|
||||
* package, or not being on npm at all. `test/docker-agent-image-coverage.test.ts` requires
|
||||
* every one of them to carry a reason and to still be present in the Dockerfile, so an
|
||||
* exclusion cannot quietly become an omission.
|
||||
* ⚠️ An entry carrying `discovery.install.agentImageLayer` is excluded here and installed by
|
||||
* its own hand-written Dockerfile layer instead, because the registry cannot express what
|
||||
* makes it special — a flag, a companion package, or not being on npm at all. This used to be
|
||||
* an id-keyed table duplicated between this file and `src/docker-hosts.ts` (exactly the shape
|
||||
* `test/cli-registry-no-id-branching.test.ts` exists to forbid inside `src/`, which is why it
|
||||
* was a blind spot rather than a pass — that test scans `src/` only). It is data now: both
|
||||
* producers filter on the SAME field from the SAME catalogue entry, `reason` is required by
|
||||
* `schema.ts`, and `test/docker-agent-image-coverage.test.ts` requires every one of them to
|
||||
* still be present in the Dockerfile, so an exclusion cannot quietly become an omission.
|
||||
*/
|
||||
export const AGENT_IMAGE_SPECIAL_CASES = {
|
||||
pi: 'installed with --ignore-scripts in its own layer, so the flag cannot leak to the shared block',
|
||||
deepseek: 'needs pnpm alongside it (dsh plugin, issue #352) and a dsh-tui profile install',
|
||||
};
|
||||
|
||||
/** Tokens allowed in an npm package name reaching a Dockerfile build arg unquoted. */
|
||||
const SAFE_PACKAGE = /^[@A-Za-z0-9][@A-Za-z0-9/._-]*$/;
|
||||
|
||||
@@ -38,13 +37,17 @@ export function agentImageNpmPackages(catalog) {
|
||||
const packages = [];
|
||||
for (const entry of catalog) {
|
||||
if (!entry.enabled) continue;
|
||||
if (entry.id in AGENT_IMAGE_SPECIAL_CASES) continue;
|
||||
if (entry.discovery?.install?.agentImageLayer) continue;
|
||||
const pkg = entry.discovery?.install?.npmPackage;
|
||||
if (!pkg) continue; // antigravity/grok/omp ship standalone installers, not npm
|
||||
if (!SAFE_PACKAGE.test(pkg)) {
|
||||
// The value is interpolated into a Dockerfile ARG that is 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.
|
||||
// ⚠️ This exact regex is duplicated in `agentImageNpmPackages()` in
|
||||
// `src/docker-hosts.ts` (that file cannot import this one — it is the TypeScript side of
|
||||
// the same two-producers split this whole module exists for). Keep both literal patterns
|
||||
// identical; `test/agent-image-build-args-parity.test.ts` pins that they are.
|
||||
throw new Error(`Refusing unsafe npm package name for "${entry.id}": ${JSON.stringify(pkg)}`);
|
||||
}
|
||||
packages.push(pkg);
|
||||
|
||||
Reference in New Issue
Block a user