aicodeman 1.24.4 → 1.24.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +3 -4
- package/dist/cli.js.map +1 -1
- package/dist/config/cli-registry/argv.d.ts +44 -0
- package/dist/config/cli-registry/argv.d.ts.map +1 -0
- package/dist/config/cli-registry/argv.js +157 -0
- package/dist/config/cli-registry/argv.js.map +1 -0
- package/dist/config/cli-registry/index.d.ts +17 -0
- package/dist/config/cli-registry/index.d.ts.map +1 -0
- package/dist/config/cli-registry/index.js +11 -0
- package/dist/config/cli-registry/index.js.map +1 -0
- package/dist/config/cli-registry/patterns.d.ts +42 -0
- package/dist/config/cli-registry/patterns.d.ts.map +1 -0
- package/dist/config/cli-registry/patterns.js +104 -0
- package/dist/config/cli-registry/patterns.js.map +1 -0
- package/dist/config/cli-registry/profiles.d.ts +74 -0
- package/dist/config/cli-registry/profiles.d.ts.map +1 -0
- package/dist/config/cli-registry/profiles.js +90 -0
- package/dist/config/cli-registry/profiles.js.map +1 -0
- package/dist/config/cli-registry/registry.d.ts +63 -0
- package/dist/config/cli-registry/registry.d.ts.map +1 -0
- package/dist/config/cli-registry/registry.js +216 -0
- package/dist/config/cli-registry/registry.js.map +1 -0
- package/dist/config/cli-registry/schema.d.ts +259 -0
- package/dist/config/cli-registry/schema.d.ts.map +1 -0
- package/dist/config/cli-registry/schema.js +398 -0
- package/dist/config/cli-registry/schema.js.map +1 -0
- package/dist/config/cli-registry/stock.d.ts +15 -0
- package/dist/config/cli-registry/stock.d.ts.map +1 -0
- package/dist/config/cli-registry/stock.js +996 -0
- package/dist/config/cli-registry/stock.js.map +1 -0
- package/dist/config/cli-registry/types.d.ts +565 -0
- package/dist/config/cli-registry/types.d.ts.map +1 -0
- package/dist/config/cli-registry/types.js +13 -0
- package/dist/config/cli-registry/types.js.map +1 -0
- package/dist/config/dependency-registry.d.ts +12 -1
- package/dist/config/dependency-registry.d.ts.map +1 -1
- package/dist/config/dependency-registry.js +155 -203
- package/dist/config/dependency-registry.js.map +1 -1
- package/dist/cron/cron-service.d.ts.map +1 -1
- package/dist/cron/cron-service.js +51 -17
- package/dist/cron/cron-service.js.map +1 -1
- package/dist/docker-hosts.d.ts +9 -1
- package/dist/docker-hosts.d.ts.map +1 -1
- package/dist/docker-hosts.js +25 -17
- package/dist/docker-hosts.js.map +1 -1
- package/dist/remote-hosts.d.ts +73 -0
- package/dist/remote-hosts.d.ts.map +1 -1
- package/dist/remote-hosts.js +139 -45
- package/dist/remote-hosts.js.map +1 -1
- package/dist/remote-reconnect.d.ts +10 -1
- package/dist/remote-reconnect.d.ts.map +1 -1
- package/dist/remote-reconnect.js +9 -0
- package/dist/remote-reconnect.js.map +1 -1
- package/dist/session-cli-registry-bridge.d.ts +66 -0
- package/dist/session-cli-registry-bridge.d.ts.map +1 -0
- package/dist/session-cli-registry-bridge.js +165 -0
- package/dist/session-cli-registry-bridge.js.map +1 -0
- package/dist/session.d.ts +13 -1
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +69 -76
- package/dist/session.js.map +1 -1
- package/dist/tmux-manager.d.ts +50 -35
- package/dist/tmux-manager.d.ts.map +1 -1
- package/dist/tmux-manager.js +258 -623
- package/dist/tmux-manager.js.map +1 -1
- package/dist/utils/antigravity-cli-resolver.d.ts.map +1 -1
- package/dist/utils/antigravity-cli-resolver.js +8 -10
- package/dist/utils/antigravity-cli-resolver.js.map +1 -1
- package/dist/utils/cli-executable-resolver.d.ts +9 -1
- package/dist/utils/cli-executable-resolver.d.ts.map +1 -1
- package/dist/utils/cli-executable-resolver.js +3 -2
- package/dist/utils/cli-executable-resolver.js.map +1 -1
- package/dist/utils/cli-launcher.d.ts +46 -0
- package/dist/utils/cli-launcher.d.ts.map +1 -0
- package/dist/utils/cli-launcher.js +101 -0
- package/dist/utils/cli-launcher.js.map +1 -0
- package/dist/utils/cli-resolver.d.ts +101 -0
- package/dist/utils/cli-resolver.d.ts.map +1 -0
- package/dist/utils/cli-resolver.js +220 -0
- package/dist/utils/cli-resolver.js.map +1 -0
- package/dist/utils/codex-cli-resolver.d.ts.map +1 -1
- package/dist/utils/codex-cli-resolver.js +8 -11
- package/dist/utils/codex-cli-resolver.js.map +1 -1
- package/dist/utils/deepseek-cli-resolver.d.ts.map +1 -1
- package/dist/utils/deepseek-cli-resolver.js +8 -6
- package/dist/utils/deepseek-cli-resolver.js.map +1 -1
- package/dist/utils/gemini-cli-resolver.d.ts.map +1 -1
- package/dist/utils/gemini-cli-resolver.js +8 -10
- package/dist/utils/gemini-cli-resolver.js.map +1 -1
- package/dist/utils/grok-cli-resolver.js +8 -8
- package/dist/utils/grok-cli-resolver.js.map +1 -1
- package/dist/utils/omp-cli-resolver.d.ts.map +1 -1
- package/dist/utils/omp-cli-resolver.js +10 -15
- package/dist/utils/omp-cli-resolver.js.map +1 -1
- package/dist/utils/opencode-cli-resolver.d.ts.map +1 -1
- package/dist/utils/opencode-cli-resolver.js +8 -11
- package/dist/utils/opencode-cli-resolver.js.map +1 -1
- package/dist/utils/pi-cli-resolver.d.ts.map +1 -1
- package/dist/utils/pi-cli-resolver.js +8 -9
- package/dist/utils/pi-cli-resolver.js.map +1 -1
- package/dist/web/public/admin-ui.js.gz +0 -0
- package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
- package/dist/web/public/app.a5d2fc08.js.gz +0 -0
- package/dist/web/public/approvals-ui.js.gz +0 -0
- package/dist/web/public/constants.7bd482dc.js.gz +0 -0
- package/dist/web/public/cron-ui.js.gz +0 -0
- package/dist/web/public/entrance-animations.js.gz +0 -0
- package/dist/web/public/home-sessions.js.gz +0 -0
- package/dist/web/public/i18n.9ae15b51.js.gz +0 -0
- package/dist/web/public/image-input.ee16ad88.js.gz +0 -0
- package/dist/web/public/index.html +9 -2
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/public/input-cjk.87f14251.js.gz +0 -0
- package/dist/web/public/keyboard-accessory.0454a3e9.js.gz +0 -0
- package/dist/web/public/mobile-handlers.0f83a608.js.gz +0 -0
- package/dist/web/public/mobile-overview.js.gz +0 -0
- package/dist/web/public/mobile.87674154.css.gz +0 -0
- package/dist/web/public/notification-manager.5d229063.js.gz +0 -0
- package/dist/web/public/orchestrator-panel.js.gz +0 -0
- package/dist/web/public/panels-ui.e6e06034.js.gz +0 -0
- package/dist/web/public/ralph-panel.6de2d0f8.js.gz +0 -0
- package/dist/web/public/ralph-wizard.13a1831e.js.gz +0 -0
- package/dist/web/public/readmymind-ui.js.gz +0 -0
- package/dist/web/public/respawn-ui.ff0dae4c.js.gz +0 -0
- package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
- package/dist/web/public/session-lineage.js.gz +0 -0
- package/dist/web/public/session-ui.c558f981.js.gz +0 -0
- package/dist/web/public/{settings-ui.2a814a17.js → settings-ui.95b333c6.js} +1 -1
- package/dist/web/public/settings-ui.95b333c6.js.br +0 -0
- package/dist/web/public/settings-ui.95b333c6.js.gz +0 -0
- package/dist/web/public/styles.421b36d4.css.gz +0 -0
- package/dist/web/public/subagent-windows.e6ca799f.js.gz +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/public/tab-rail-resize.42c24949.js.gz +0 -0
- package/dist/web/public/terminal-ui.70445f81.js.gz +0 -0
- package/dist/web/public/ultracode-panel.js.gz +0 -0
- package/dist/web/public/ultracode-windows.js.gz +0 -0
- package/dist/web/public/upload.html.gz +0 -0
- package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
- package/dist/web/public/vendor/marked.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-predictive-echo.bd6882b8.js.gz +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.6fee72f2.js.gz +0 -0
- package/dist/web/public/vendor/xterm.css.gz +0 -0
- package/dist/web/public/vendor/xterm.min.js.gz +0 -0
- package/dist/web/public/voice-input.4ec670b0.js.gz +0 -0
- package/dist/web/public/voice-pcm-worklet.js.gz +0 -0
- package/dist/web/public/webview-tabs.js.gz +0 -0
- package/dist/web/routes/cron-routes.d.ts.map +1 -1
- package/dist/web/routes/cron-routes.js +3 -2
- package/dist/web/routes/cron-routes.js.map +1 -1
- package/dist/web/routes/session-routes.d.ts +8 -34
- package/dist/web/routes/session-routes.d.ts.map +1 -1
- package/dist/web/routes/session-routes.js +164 -251
- package/dist/web/routes/session-routes.js.map +1 -1
- package/dist/web/routes/system-routes.d.ts.map +1 -1
- package/dist/web/routes/system-routes.js +2 -1
- package/dist/web/routes/system-routes.js.map +1 -1
- package/dist/web/schemas.d.ts +12 -48
- package/dist/web/schemas.d.ts.map +1 -1
- package/dist/web/schemas.js +82 -37
- package/dist/web/schemas.js.map +1 -1
- package/dist/web/session-wait-registry.d.ts +1 -1
- package/dist/web/session-wait-registry.d.ts.map +1 -1
- package/dist/web/session-wait-registry.js +20 -12
- package/dist/web/session-wait-registry.js.map +1 -1
- package/package.json +1 -1
- package/dist/web/public/settings-ui.2a814a17.js.br +0 -0
- package/dist/web/public/settings-ui.2a814a17.js.gz +0 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Named value patterns for the CLI registry's argv engine.
|
|
3
|
+
*
|
|
4
|
+
* Config entries select a pattern BY NAME; the regexes themselves live here, in code.
|
|
5
|
+
* That is deliberate and is the reason a user-editable `clis.json` cannot widen its own
|
|
6
|
+
* validation: there is no field anywhere in the schema that accepts a raw regex for a
|
|
7
|
+
* shell token, so no entry can supply `.*` (nor a catastrophically backtracking one).
|
|
8
|
+
*
|
|
9
|
+
* The sole user-supplied regex in the whole registry is `discovery.version.regex`, which
|
|
10
|
+
* is applied to `--version` OUTPUT rather than to a shell token, and goes through
|
|
11
|
+
* `compileVersionRegex()` below.
|
|
12
|
+
*
|
|
13
|
+
* Every pattern here is transcribed from the builder it replaces in tmux-manager.ts, so
|
|
14
|
+
* the argv engine accepts and rejects exactly the values the hand-written builders did.
|
|
15
|
+
*
|
|
16
|
+
* @module config/cli-registry/patterns
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* The patterns, each traced to the builder it came from.
|
|
20
|
+
*
|
|
21
|
+
* ⚠️ These are ALLOWLISTS (`^...$` over a safe character class), never blocklists — with
|
|
22
|
+
* one deliberate exception, `tool-list`, which mirrors the existing `--allowedTools`
|
|
23
|
+
* sanitizer. That one is a metacharacter REJECTION because tool specs legitimately contain
|
|
24
|
+
* `(`, `)`, `*`, `:` and spaces (`Bash(git:*), Read`), so an allowlist of safe words cannot
|
|
25
|
+
* express it. Keeping it byte-identical to the original matters more than making it uniform.
|
|
26
|
+
*/
|
|
27
|
+
const PATTERNS = {
|
|
28
|
+
// buildOpenCodeCommand / buildCodexCommand / buildGeminiCommand / buildAntigravityCommand
|
|
29
|
+
model: /^[a-zA-Z0-9._\-/]+$/,
|
|
30
|
+
// buildSpawnCommand's claude branch — `[` and `]` for bracketed model aliases
|
|
31
|
+
'model-claude': /^[a-zA-Z0-9._\-[\]]+$/,
|
|
32
|
+
// buildPiCommand — `:` for a thinking suffix (`sonnet:high`), `/` for `provider/id`
|
|
33
|
+
'model-pi': /^[a-zA-Z0-9._\-/:]+$/,
|
|
34
|
+
// opencode --session, codex resume
|
|
35
|
+
id: /^[a-zA-Z0-9_-]+$/,
|
|
36
|
+
// gemini --resume, antigravity --conversation, pi --session
|
|
37
|
+
'id-dotted': /^[a-zA-Z0-9._-]+$/,
|
|
38
|
+
// claude --resume / --session-id
|
|
39
|
+
uuid: /^[a-f0-9-]+$/,
|
|
40
|
+
// pi --provider
|
|
41
|
+
slug: /^[a-z0-9-]+$/,
|
|
42
|
+
// dsh --profile. Deliberately STRICTER than `id-dotted`: a profile name is both
|
|
43
|
+
// interpolated into the shell line AND joined into a filesystem path, so it must be a
|
|
44
|
+
// single path segment. Requiring a leading alphanumeric is what rules out `.`, `..` and
|
|
45
|
+
// dotfile names, which `id-dotted` would happily accept.
|
|
46
|
+
'path-segment': /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/,
|
|
47
|
+
// codex --config tui.animations=false
|
|
48
|
+
'config-kv': /^[A-Za-z0-9._-]+=[A-Za-z0-9._-]+$/,
|
|
49
|
+
// Placeholder; `tool-list` is handled by isSafeToolList() below, not by a match.
|
|
50
|
+
'tool-list': /^$/,
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Shell metacharacters rejected in an `--allowedTools` value. Transcribed verbatim from
|
|
54
|
+
* buildClaudePermissionFlags so the accepted set does not move.
|
|
55
|
+
*/
|
|
56
|
+
const TOOL_LIST_DANGEROUS = /[;&|$`\\{}<>'"[\]\n\r]/;
|
|
57
|
+
/** Does `value` satisfy the named pattern? */
|
|
58
|
+
export function matchesPattern(pattern, value) {
|
|
59
|
+
if (pattern === 'tool-list')
|
|
60
|
+
return value.length > 0 && !TOOL_LIST_DANGEROUS.test(value);
|
|
61
|
+
return PATTERNS[pattern].test(value);
|
|
62
|
+
}
|
|
63
|
+
/** Every pattern name, for schema validation and error messages. */
|
|
64
|
+
export const TOKEN_PATTERNS = Object.keys(PATTERNS);
|
|
65
|
+
/**
|
|
66
|
+
* Characters a token may contain and still be emitted UNQUOTED into the `bash -c "..."`
|
|
67
|
+
* command string. Intentionally narrower than "what bash tolerates": anything outside it
|
|
68
|
+
* gets single-quoted, so the classification can only ever err toward more quoting.
|
|
69
|
+
*/
|
|
70
|
+
export const SAFE_BARE_TOKEN = /^[A-Za-z0-9._:@=+/,-]+$/;
|
|
71
|
+
/**
|
|
72
|
+
* Longest `--version` output we will run a user-supplied regex over. A version banner is a
|
|
73
|
+
* line or two; anything larger is a misconfiguration, and capping the input is what keeps a
|
|
74
|
+
* sloppy (not necessarily malicious) regex from becoming a stall.
|
|
75
|
+
*/
|
|
76
|
+
export const MAX_VERSION_OUTPUT = 200;
|
|
77
|
+
/** Longest permitted `discovery.version.regex` source. */
|
|
78
|
+
const MAX_VERSION_REGEX_SOURCE = 200;
|
|
79
|
+
/**
|
|
80
|
+
* Nested quantifiers — `(a+)+`, `(a*)*`, `(a+)*` and friends — the classic catastrophic
|
|
81
|
+
* backtracking shape. Rejected outright rather than analysed: this field exists to pull a
|
|
82
|
+
* semver out of a banner, and nothing legitimate for that job needs a nested quantifier.
|
|
83
|
+
*/
|
|
84
|
+
const NESTED_QUANTIFIER = /\([^)]*[+*][^)]*\)\s*[+*{]/;
|
|
85
|
+
/**
|
|
86
|
+
* Compile a user-supplied version regex, or return null if it is not one we are willing to
|
|
87
|
+
* run. Returning null (rather than throwing) lets the caller degrade to "version unknown",
|
|
88
|
+
* which every consumer already handles.
|
|
89
|
+
*/
|
|
90
|
+
export function compileVersionRegex(source) {
|
|
91
|
+
if (source.length > MAX_VERSION_REGEX_SOURCE)
|
|
92
|
+
return null;
|
|
93
|
+
if (NESTED_QUANTIFIER.test(source))
|
|
94
|
+
return null;
|
|
95
|
+
try {
|
|
96
|
+
// No `g`: a global regex carries lastIndex state across calls, which is a documented
|
|
97
|
+
// footgun in this codebase (see utils/regex-patterns.ts).
|
|
98
|
+
return new RegExp(source);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=patterns.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"patterns.js","sourceRoot":"","sources":["../../../src/config/cli-registry/patterns.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAeH;;;;;;;;GAQG;AACH,MAAM,QAAQ,GAAiC;IAC7C,0FAA0F;IAC1F,KAAK,EAAE,qBAAqB;IAC5B,8EAA8E;IAC9E,cAAc,EAAE,uBAAuB;IACvC,oFAAoF;IACpF,UAAU,EAAE,sBAAsB;IAClC,mCAAmC;IACnC,EAAE,EAAE,kBAAkB;IACtB,4DAA4D;IAC5D,WAAW,EAAE,mBAAmB;IAChC,iCAAiC;IACjC,IAAI,EAAE,cAAc;IACpB,gBAAgB;IAChB,IAAI,EAAE,cAAc;IACpB,gFAAgF;IAChF,sFAAsF;IACtF,wFAAwF;IACxF,yDAAyD;IACzD,cAAc,EAAE,8BAA8B;IAC9C,sCAAsC;IACtC,WAAW,EAAE,mCAAmC;IAChD,iFAAiF;IACjF,WAAW,EAAE,IAAI;CAClB,CAAC;AAEF;;;GAGG;AACH,MAAM,mBAAmB,GAAG,wBAAwB,CAAC;AAErD,8CAA8C;AAC9C,MAAM,UAAU,cAAc,CAAC,OAAqB,EAAE,KAAa;IACjE,IAAI,OAAO,KAAK,WAAW;QAAE,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACzF,OAAO,QAAQ,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AACvC,CAAC;AAED,oEAAoE;AACpE,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAmB,CAAC;AAEtE;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,yBAAyB,CAAC;AAEzD;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,GAAG,CAAC;AAEtC,0DAA0D;AAC1D,MAAM,wBAAwB,GAAG,GAAG,CAAC;AAErC;;;;GAIG;AACH,MAAM,iBAAiB,GAAG,4BAA4B,CAAC;AAEvD;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAAc;IAChD,IAAI,MAAM,CAAC,MAAM,GAAG,wBAAwB;QAAE,OAAO,IAAI,CAAC;IAC1D,IAAI,iBAAiB,CAAC,IAAI,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAC;IAChD,IAAI,CAAC;QACH,qFAAqF;QACrF,0DAA0D;QAC1D,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview The NAMES of code profiles a `CliEntry` field may select, and the helpers
|
|
3
|
+
* that validate them.
|
|
4
|
+
*
|
|
5
|
+
* A profile is the escape hatch for behaviour that is genuinely code-shaped and cannot be
|
|
6
|
+
* expressed as data — codex's predictive write-through echo, deepseek's profile-launcher
|
|
7
|
+
* runnability check, deepseek's status bridge — without letting any of that code branch on
|
|
8
|
+
* a CLI's id. A registry field names a profile; the implementation lives beside whatever it
|
|
9
|
+
* needs, and looks its name up here.
|
|
10
|
+
*
|
|
11
|
+
* ⚠️ This module is PURE and must stay that way: names, types and predicates only, no
|
|
12
|
+
* imports outside this directory. The implementations pull in resolvers and the status
|
|
13
|
+
* shim, which in turn reach back into the registry, so holding them here would close an
|
|
14
|
+
* import cycle (profiles → deepseek-cli-resolver → cli-resolver → registry → schema →
|
|
15
|
+
* profiles). Keeping the names here and the implementations at their call sites is what
|
|
16
|
+
* lets `schema.ts` validate a profile name at LOAD time — a custom entry naming a profile
|
|
17
|
+
* this build does not implement fails loudly instead of silently failing closed later.
|
|
18
|
+
*
|
|
19
|
+
* The rule all of this enforces: `test/cli-registry-no-id-branching.test.ts` fails on any
|
|
20
|
+
* `mode === '<stock id>'` comparison outside `stock.ts`, so a NEW behavioural special case
|
|
21
|
+
* must be added here, named, and referenced from a registry field — never inlined as an id
|
|
22
|
+
* check at the call site.
|
|
23
|
+
*
|
|
24
|
+
* ⚠️ A profile is a LAST resort, not a convenience. Reach for one only when the behaviour
|
|
25
|
+
* needs to run code (a side effect, a computed value, a probe); anything that is a list, a
|
|
26
|
+
* flag, or a string belongs in the entry as data, where a custom CLI can also use it.
|
|
27
|
+
*
|
|
28
|
+
* @module config/cli-registry/profiles
|
|
29
|
+
*/
|
|
30
|
+
/**
|
|
31
|
+
* Predictive local-echo profiles, selected via `capabilities.echo.predictProfile`.
|
|
32
|
+
*
|
|
33
|
+
* Implementation: packages/xterm-zerolag-input/src/predictive-echo-addon.ts.
|
|
34
|
+
*
|
|
35
|
+
* ⚠️ Unlike the other two registries, an unknown name here degrades to the 'buffer' policy
|
|
36
|
+
* rather than failing. Echo is a comfort feature — a worse-but-working overlay beats a
|
|
37
|
+
* refused session — which is why `predictProfile` alone is not schema-validated below.
|
|
38
|
+
*/
|
|
39
|
+
export declare const PREDICT_PROFILES: Record<string, true>;
|
|
40
|
+
/**
|
|
41
|
+
* Launcher profiles, selected via `discovery.launcherProfile`.
|
|
42
|
+
*
|
|
43
|
+
* For a CLI whose binary launches some further target, and so cannot answer two questions
|
|
44
|
+
* from the binary alone: is it RUNNABLE (stricter than "is the binary on disk?"), and what
|
|
45
|
+
* is the DEFAULT target when the caller names none? A CLI naming no profile is runnable
|
|
46
|
+
* exactly when its binary resolves, and has no default target.
|
|
47
|
+
*
|
|
48
|
+
* Implementation: `src/utils/cli-launcher.ts`.
|
|
49
|
+
*/
|
|
50
|
+
export declare const LAUNCHER_PROFILE_NAMES: readonly ["deepseek-profile"];
|
|
51
|
+
/**
|
|
52
|
+
* Extra `tmux setenv` work, selected via `env.setenvProfile`.
|
|
53
|
+
*
|
|
54
|
+
* Implementation: `src/tmux-manager.ts`, which already owns every setenv call.
|
|
55
|
+
*
|
|
56
|
+
* ⚠️ Anything that is merely "forward this name from the server's own env" belongs in
|
|
57
|
+
* `env.tmuxSetenvKeys` as data and must NOT be given a profile.
|
|
58
|
+
*/
|
|
59
|
+
export declare const SETENV_PROFILE_NAMES: readonly ["deepseek-status-bridge"];
|
|
60
|
+
export type LauncherProfileName = (typeof LAUNCHER_PROFILE_NAMES)[number];
|
|
61
|
+
export type SetenvProfileName = (typeof SETENV_PROFILE_NAMES)[number];
|
|
62
|
+
/**
|
|
63
|
+
* Transcript readers, selected via `capabilities.transcript`. Unlike the profile registries
|
|
64
|
+
* above this one is closed over the schema enum itself rather than an open string, since
|
|
65
|
+
* transcript format is a small, genuinely fixed set — see CliCapabilities['transcript'].
|
|
66
|
+
*/
|
|
67
|
+
export declare const TRANSCRIPT_READER_NAMES: readonly ["claude-jsonl", "codex-rollout", "deepseek-zstd", "none"];
|
|
68
|
+
/** Composer-row finders, selected via `capabilities.echo.anchor.kind`. Also schema-closed. */
|
|
69
|
+
export declare const COMPOSER_ANCHOR_KINDS: readonly ["glyph", "cursor", "none"];
|
|
70
|
+
/** True when `name` is a predictive-echo profile this build actually implements. */
|
|
71
|
+
export declare function isKnownPredictProfile(name: string | undefined): boolean;
|
|
72
|
+
export declare function isKnownLauncherProfile(name: string): name is LauncherProfileName;
|
|
73
|
+
export declare function isKnownSetenvProfile(name: string): name is SetenvProfileName;
|
|
74
|
+
//# sourceMappingURL=profiles.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"profiles.d.ts","sourceRoot":"","sources":["../../../src/config/cli-registry/profiles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAEjD,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,sBAAsB,+BAIzB,CAAC;AAEX;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,qCAMvB,CAAC;AAEX,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,sBAAsB,CAAC,CAAC,MAAM,CAAC,CAAC;AAC1E,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEtE;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,qEAAsE,CAAC;AAE3G,8FAA8F;AAC9F,eAAO,MAAM,qBAAqB,sCAAuC,CAAC;AAE1E,oFAAoF;AACpF,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAEvE;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,IAAI,mBAAmB,CAEhF;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,IAAI,iBAAiB,CAE5E"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview The NAMES of code profiles a `CliEntry` field may select, and the helpers
|
|
3
|
+
* that validate them.
|
|
4
|
+
*
|
|
5
|
+
* A profile is the escape hatch for behaviour that is genuinely code-shaped and cannot be
|
|
6
|
+
* expressed as data — codex's predictive write-through echo, deepseek's profile-launcher
|
|
7
|
+
* runnability check, deepseek's status bridge — without letting any of that code branch on
|
|
8
|
+
* a CLI's id. A registry field names a profile; the implementation lives beside whatever it
|
|
9
|
+
* needs, and looks its name up here.
|
|
10
|
+
*
|
|
11
|
+
* ⚠️ This module is PURE and must stay that way: names, types and predicates only, no
|
|
12
|
+
* imports outside this directory. The implementations pull in resolvers and the status
|
|
13
|
+
* shim, which in turn reach back into the registry, so holding them here would close an
|
|
14
|
+
* import cycle (profiles → deepseek-cli-resolver → cli-resolver → registry → schema →
|
|
15
|
+
* profiles). Keeping the names here and the implementations at their call sites is what
|
|
16
|
+
* lets `schema.ts` validate a profile name at LOAD time — a custom entry naming a profile
|
|
17
|
+
* this build does not implement fails loudly instead of silently failing closed later.
|
|
18
|
+
*
|
|
19
|
+
* The rule all of this enforces: `test/cli-registry-no-id-branching.test.ts` fails on any
|
|
20
|
+
* `mode === '<stock id>'` comparison outside `stock.ts`, so a NEW behavioural special case
|
|
21
|
+
* must be added here, named, and referenced from a registry field — never inlined as an id
|
|
22
|
+
* check at the call site.
|
|
23
|
+
*
|
|
24
|
+
* ⚠️ A profile is a LAST resort, not a convenience. Reach for one only when the behaviour
|
|
25
|
+
* needs to run code (a side effect, a computed value, a probe); anything that is a list, a
|
|
26
|
+
* flag, or a string belongs in the entry as data, where a custom CLI can also use it.
|
|
27
|
+
*
|
|
28
|
+
* @module config/cli-registry/profiles
|
|
29
|
+
*/
|
|
30
|
+
/**
|
|
31
|
+
* Predictive local-echo profiles, selected via `capabilities.echo.predictProfile`.
|
|
32
|
+
*
|
|
33
|
+
* Implementation: packages/xterm-zerolag-input/src/predictive-echo-addon.ts.
|
|
34
|
+
*
|
|
35
|
+
* ⚠️ Unlike the other two registries, an unknown name here degrades to the 'buffer' policy
|
|
36
|
+
* rather than failing. Echo is a comfort feature — a worse-but-working overlay beats a
|
|
37
|
+
* refused session — which is why `predictProfile` alone is not schema-validated below.
|
|
38
|
+
*/
|
|
39
|
+
export const PREDICT_PROFILES = {
|
|
40
|
+
codex: true,
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Launcher profiles, selected via `discovery.launcherProfile`.
|
|
44
|
+
*
|
|
45
|
+
* For a CLI whose binary launches some further target, and so cannot answer two questions
|
|
46
|
+
* from the binary alone: is it RUNNABLE (stricter than "is the binary on disk?"), and what
|
|
47
|
+
* is the DEFAULT target when the caller names none? A CLI naming no profile is runnable
|
|
48
|
+
* exactly when its binary resolves, and has no default target.
|
|
49
|
+
*
|
|
50
|
+
* Implementation: `src/utils/cli-launcher.ts`.
|
|
51
|
+
*/
|
|
52
|
+
export const LAUNCHER_PROFILE_NAMES = [
|
|
53
|
+
// `dsh` is a launcher over $DSH_HOME/profiles/<name>, and the profiles DeepSeek itself
|
|
54
|
+
// ships (web, headless) cannot drive a terminal pane. Binary AND a pane-capable profile.
|
|
55
|
+
'deepseek-profile',
|
|
56
|
+
];
|
|
57
|
+
/**
|
|
58
|
+
* Extra `tmux setenv` work, selected via `env.setenvProfile`.
|
|
59
|
+
*
|
|
60
|
+
* Implementation: `src/tmux-manager.ts`, which already owns every setenv call.
|
|
61
|
+
*
|
|
62
|
+
* ⚠️ Anything that is merely "forward this name from the server's own env" belongs in
|
|
63
|
+
* `env.tmuxSetenvKeys` as data and must NOT be given a profile.
|
|
64
|
+
*/
|
|
65
|
+
export const SETENV_PROFILE_NAMES = [
|
|
66
|
+
// DeepSeek's terminal front door reports idle/working/blocked to a supervisor over the
|
|
67
|
+
// generic env-gated Herdr contract; this makes Codeman that supervisor. It needs a
|
|
68
|
+
// profile rather than key names because it writes an executable shim to disk and then
|
|
69
|
+
// exports that shim's path along with the session's own pane id.
|
|
70
|
+
'deepseek-status-bridge',
|
|
71
|
+
];
|
|
72
|
+
/**
|
|
73
|
+
* Transcript readers, selected via `capabilities.transcript`. Unlike the profile registries
|
|
74
|
+
* above this one is closed over the schema enum itself rather than an open string, since
|
|
75
|
+
* transcript format is a small, genuinely fixed set — see CliCapabilities['transcript'].
|
|
76
|
+
*/
|
|
77
|
+
export const TRANSCRIPT_READER_NAMES = ['claude-jsonl', 'codex-rollout', 'deepseek-zstd', 'none'];
|
|
78
|
+
/** Composer-row finders, selected via `capabilities.echo.anchor.kind`. Also schema-closed. */
|
|
79
|
+
export const COMPOSER_ANCHOR_KINDS = ['glyph', 'cursor', 'none'];
|
|
80
|
+
/** True when `name` is a predictive-echo profile this build actually implements. */
|
|
81
|
+
export function isKnownPredictProfile(name) {
|
|
82
|
+
return name !== undefined && Object.prototype.hasOwnProperty.call(PREDICT_PROFILES, name);
|
|
83
|
+
}
|
|
84
|
+
export function isKnownLauncherProfile(name) {
|
|
85
|
+
return LAUNCHER_PROFILE_NAMES.includes(name);
|
|
86
|
+
}
|
|
87
|
+
export function isKnownSetenvProfile(name) {
|
|
88
|
+
return SETENV_PROFILE_NAMES.includes(name);
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=profiles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"profiles.js","sourceRoot":"","sources":["../../../src/config/cli-registry/profiles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAyB;IACpD,KAAK,EAAE,IAAI;CACZ,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACpC,uFAAuF;IACvF,yFAAyF;IACzF,kBAAkB;CACV,CAAC;AAEX;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,uFAAuF;IACvF,mFAAmF;IACnF,sFAAsF;IACtF,iEAAiE;IACjE,wBAAwB;CAChB,CAAC;AAKX;;;;GAIG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,cAAc,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,CAAU,CAAC;AAE3G,8FAA8F;AAC9F,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAU,CAAC;AAE1E,oFAAoF;AACpF,MAAM,UAAU,qBAAqB,CAAC,IAAwB;IAC5D,OAAO,IAAI,KAAK,SAAS,IAAI,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,gBAAgB,EAAE,IAAI,CAAC,CAAC;AAC5F,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,IAAY;IACjD,OAAQ,sBAA4C,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AACtE,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,IAAY;IAC/C,OAAQ,oBAA0C,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AACpE,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Loads, merges and re-validates the CLI registry.
|
|
3
|
+
*
|
|
4
|
+
* `~/.codeman/clis.json` holds OVERRIDES and CUSTOM entries only — never a full copy of the
|
|
5
|
+
* stock catalog — so a shipped fix to a stock definition actually reaches an existing
|
|
6
|
+
* install, and the file stays small enough to hand-edit.
|
|
7
|
+
*
|
|
8
|
+
* Resolution: start from `STOCK_CLIS` → deep-merge each override by id (objects merge
|
|
9
|
+
* key-wise, arrays replace wholesale) → validate every resulting entry. A stock entry that
|
|
10
|
+
* fails validation after merge falls back to its pristine stock definition (a fat-fingered
|
|
11
|
+
* override cannot brick a shipped CLI); a custom entry that fails is dropped with a warning
|
|
12
|
+
* rather than failing the whole load. Stock entries are always emitted, so `shell` and
|
|
13
|
+
* `claude` can be disabled but can never go missing — large parts of the app assume at
|
|
14
|
+
* minimum that a shell fallback exists.
|
|
15
|
+
*
|
|
16
|
+
* ⚠️ READ-ONLY. Nothing in this module writes, creates or migrates the file. That is a
|
|
17
|
+
* deliberate property, not a missing feature: there is no settings UI and no write API yet,
|
|
18
|
+
* so there is nothing to persist, and it means importing the registry — which
|
|
19
|
+
* `src/web/schemas.ts` does, transitively, just to validate a request — performs no
|
|
20
|
+
* filesystem writes. A `seededStockIds` ratchet belongs with the write API that needs it.
|
|
21
|
+
* The one exception is the quarantine RENAME of a file that fails to parse (see
|
|
22
|
+
* `readRegistryFile`), which happens on first use rather than at import.
|
|
23
|
+
*
|
|
24
|
+
* ⚠️ The file must be mode 0600. `isUnsafePermissions` refuses ANY group/world bit, read
|
|
25
|
+
* bits included, so a file created with a normal umask (0644) is ignored. Every reason the
|
|
26
|
+
* file was ignored, or an entry in it dropped, is logged ONCE on first load: the warnings
|
|
27
|
+
* used to be returned to a caller that nobody wired up, so a normally-created file was
|
|
28
|
+
* ignored with no feedback anywhere (found reviewing #347).
|
|
29
|
+
*
|
|
30
|
+
* @module config/cli-registry/registry
|
|
31
|
+
*/
|
|
32
|
+
import type { CliEntry, CliId, CliRegistryFile } from './types.js';
|
|
33
|
+
/** Construct a validated CliId. Throws if `raw` is not a well-formed id — call at API boundaries. */
|
|
34
|
+
export declare function asCliId(raw: string): CliId;
|
|
35
|
+
export interface LoadResult {
|
|
36
|
+
entries: CliEntry[];
|
|
37
|
+
warnings: string[];
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Merge the stock catalog with a (possibly absent) registry file. PURE — no IO, which is
|
|
41
|
+
* what lets the load tests drive every merge case directly.
|
|
42
|
+
*/
|
|
43
|
+
export declare function resolveRegistry(stock: CliEntry[], file: CliRegistryFile | null, warnings: string[]): LoadResult;
|
|
44
|
+
/**
|
|
45
|
+
* Load the effective registry (stock + user overrides). Memoized for the process lifetime;
|
|
46
|
+
* `reloadCliRegistry()` invalidates.
|
|
47
|
+
*/
|
|
48
|
+
export declare function loadCliRegistry(): LoadResult;
|
|
49
|
+
/** Drop the memoized registry so the next `loadCliRegistry()` re-reads the file. */
|
|
50
|
+
export declare function reloadCliRegistry(): void;
|
|
51
|
+
export declare function listClis(): CliEntry[];
|
|
52
|
+
export declare function enabledClis(): CliEntry[];
|
|
53
|
+
export declare function getCli(id: string): CliEntry | undefined;
|
|
54
|
+
export declare function cliIds(): string[];
|
|
55
|
+
/** Every enabled entry's id, in registry order. */
|
|
56
|
+
export declare function enabledCliIds(): string[];
|
|
57
|
+
/**
|
|
58
|
+
* Resolve the install command for the current platform, falling back to the linux one (the
|
|
59
|
+
* common case for a `curl | bash` or `npm install -g` line) and then to whatever is
|
|
60
|
+
* declared. Display text only — never executed. See CliDiscovery.install.command.
|
|
61
|
+
*/
|
|
62
|
+
export declare function resolveInstallCommandForPlatform(entry: CliEntry): string | undefined;
|
|
63
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/config/cli-registry/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAIH,OAAO,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAInE,qGAAqG;AACrG,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,CAK1C;AAkCD,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,QAAQ,EAAE,CAAC;IACpB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AA2DD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,eAAe,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,UAAU,CAoC/G;AAID;;;GAGG;AACH,wBAAgB,eAAe,IAAI,UAAU,CAS5C;AAED,oFAAoF;AACpF,wBAAgB,iBAAiB,IAAI,IAAI,CAExC;AAED,wBAAgB,QAAQ,IAAI,QAAQ,EAAE,CAErC;AAED,wBAAgB,WAAW,IAAI,QAAQ,EAAE,CAExC;AAED,wBAAgB,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAEvD;AAED,wBAAgB,MAAM,IAAI,MAAM,EAAE,CAEjC;AAED,mDAAmD;AACnD,wBAAgB,aAAa,IAAI,MAAM,EAAE,CAExC;AAED;;;;GAIG;AACH,wBAAgB,gCAAgC,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,GAAG,SAAS,CAIpF"}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Loads, merges and re-validates the CLI registry.
|
|
3
|
+
*
|
|
4
|
+
* `~/.codeman/clis.json` holds OVERRIDES and CUSTOM entries only — never a full copy of the
|
|
5
|
+
* stock catalog — so a shipped fix to a stock definition actually reaches an existing
|
|
6
|
+
* install, and the file stays small enough to hand-edit.
|
|
7
|
+
*
|
|
8
|
+
* Resolution: start from `STOCK_CLIS` → deep-merge each override by id (objects merge
|
|
9
|
+
* key-wise, arrays replace wholesale) → validate every resulting entry. A stock entry that
|
|
10
|
+
* fails validation after merge falls back to its pristine stock definition (a fat-fingered
|
|
11
|
+
* override cannot brick a shipped CLI); a custom entry that fails is dropped with a warning
|
|
12
|
+
* rather than failing the whole load. Stock entries are always emitted, so `shell` and
|
|
13
|
+
* `claude` can be disabled but can never go missing — large parts of the app assume at
|
|
14
|
+
* minimum that a shell fallback exists.
|
|
15
|
+
*
|
|
16
|
+
* ⚠️ READ-ONLY. Nothing in this module writes, creates or migrates the file. That is a
|
|
17
|
+
* deliberate property, not a missing feature: there is no settings UI and no write API yet,
|
|
18
|
+
* so there is nothing to persist, and it means importing the registry — which
|
|
19
|
+
* `src/web/schemas.ts` does, transitively, just to validate a request — performs no
|
|
20
|
+
* filesystem writes. A `seededStockIds` ratchet belongs with the write API that needs it.
|
|
21
|
+
* The one exception is the quarantine RENAME of a file that fails to parse (see
|
|
22
|
+
* `readRegistryFile`), which happens on first use rather than at import.
|
|
23
|
+
*
|
|
24
|
+
* ⚠️ The file must be mode 0600. `isUnsafePermissions` refuses ANY group/world bit, read
|
|
25
|
+
* bits included, so a file created with a normal umask (0644) is ignored. Every reason the
|
|
26
|
+
* file was ignored, or an entry in it dropped, is logged ONCE on first load: the warnings
|
|
27
|
+
* used to be returned to a caller that nobody wired up, so a normally-created file was
|
|
28
|
+
* ignored with no feedback anywhere (found reviewing #347).
|
|
29
|
+
*
|
|
30
|
+
* @module config/cli-registry/registry
|
|
31
|
+
*/
|
|
32
|
+
import { existsSync, readFileSync, renameSync, statSync } from 'node:fs';
|
|
33
|
+
import { dataPath } from '../instance.js';
|
|
34
|
+
import { CliEntrySchema } from './schema.js';
|
|
35
|
+
import { STOCK_CLIS } from './stock.js';
|
|
36
|
+
/** Construct a validated CliId. Throws if `raw` is not a well-formed id — call at API boundaries. */
|
|
37
|
+
export function asCliId(raw) {
|
|
38
|
+
if (!/^[a-z][a-z0-9-]{0,23}$/.test(raw)) {
|
|
39
|
+
throw new Error(`invalid CLI id: ${JSON.stringify(raw)}`);
|
|
40
|
+
}
|
|
41
|
+
return raw;
|
|
42
|
+
}
|
|
43
|
+
function filePath() {
|
|
44
|
+
return dataPath('clis.json');
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Keys that must never be merged out of a hand-editable JSON file.
|
|
48
|
+
*
|
|
49
|
+
* `JSON.parse` produces `__proto__` as an ORDINARY own property, but `result[key] = …` on a
|
|
50
|
+
* plain object walks the setter chain and would set the merged object's PROTOTYPE instead.
|
|
51
|
+
* Not exploitable today — every merged entry is spread into `{ ...merged, id, stock }` and
|
|
52
|
+
* then Zod-parsed before anything reads it, which drops the effect — but "not exploitable
|
|
53
|
+
* because of what a caller happens to do afterwards" is a property that quietly stops
|
|
54
|
+
* holding. A `continue` in the loop that reads the file is the cheap end of that trade.
|
|
55
|
+
*/
|
|
56
|
+
const UNMERGEABLE_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
57
|
+
/** Plain-object deep merge: nested objects merge key-wise, arrays and primitives replace. */
|
|
58
|
+
function deepMerge(base, override) {
|
|
59
|
+
if (override === null || typeof override !== 'object' || Array.isArray(override)) {
|
|
60
|
+
return (override === undefined ? base : override) ?? base;
|
|
61
|
+
}
|
|
62
|
+
if (base === null || typeof base !== 'object' || Array.isArray(base)) {
|
|
63
|
+
return override;
|
|
64
|
+
}
|
|
65
|
+
const result = { ...base };
|
|
66
|
+
for (const [key, value] of Object.entries(override)) {
|
|
67
|
+
if (UNMERGEABLE_KEYS.has(key))
|
|
68
|
+
continue;
|
|
69
|
+
result[key] = deepMerge(base[key], value);
|
|
70
|
+
}
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Refuse a registry file with any group/world permission bit — same posture as the ssh-key
|
|
75
|
+
* discipline, so 0600 is the only accepted mode. This file selects the binaries Codeman
|
|
76
|
+
* spawns, so a writable one is a way to redirect every session.
|
|
77
|
+
*
|
|
78
|
+
* POSIX only: Windows has no meaningful group/world bits on NTFS (Node reports every file
|
|
79
|
+
* as mode 0o666 there regardless of its actual ACL), so this check would flag every file on
|
|
80
|
+
* Windows and silently ignore all user config. `win32` relies on NTFS ACLs instead, which
|
|
81
|
+
* this check cannot see and does not attempt to.
|
|
82
|
+
*/
|
|
83
|
+
function isUnsafePermissions(path) {
|
|
84
|
+
if (process.platform === 'win32')
|
|
85
|
+
return false;
|
|
86
|
+
try {
|
|
87
|
+
const mode = statSync(path).mode & 0o777;
|
|
88
|
+
return (mode & 0o077) !== 0;
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function readRegistryFile(path, warnings) {
|
|
95
|
+
if (!existsSync(path))
|
|
96
|
+
return null;
|
|
97
|
+
if (isUnsafePermissions(path)) {
|
|
98
|
+
warnings.push(`${path} must be mode 0600 (no group/world permission bits; run \`chmod 600 ${path}\`); ignoring it and falling back to stock CLIs.`);
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
let raw;
|
|
102
|
+
try {
|
|
103
|
+
raw = readFileSync(path, 'utf-8');
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
warnings.push(`Failed to read ${path}: ${err.message}. Falling back to stock CLIs.`);
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
const parsed = JSON.parse(raw);
|
|
111
|
+
if (typeof parsed !== 'object' || parsed === null || typeof parsed.clis !== 'object') {
|
|
112
|
+
throw new Error('missing "clis" object');
|
|
113
|
+
}
|
|
114
|
+
return parsed;
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
// QUARANTINE, never overwrite: the file is hand-editable, so a syntax error is far more
|
|
118
|
+
// likely to be a half-finished edit than junk. Renaming keeps the user's work.
|
|
119
|
+
const quarantined = `${path}.invalid-${Date.now()}`;
|
|
120
|
+
try {
|
|
121
|
+
renameSync(path, quarantined);
|
|
122
|
+
warnings.push(`${path} was not valid JSON (${err.message}); moved to ${quarantined}.`);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
warnings.push(`${path} was not valid JSON (${err.message}); left in place, falling back to stock CLIs.`);
|
|
126
|
+
}
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Merge the stock catalog with a (possibly absent) registry file. PURE — no IO, which is
|
|
132
|
+
* what lets the load tests drive every merge case directly.
|
|
133
|
+
*/
|
|
134
|
+
export function resolveRegistry(stock, file, warnings) {
|
|
135
|
+
const stockById = new Map(stock.map((e) => [e.id, e]));
|
|
136
|
+
const overrides = file?.clis ?? {};
|
|
137
|
+
const entries = [];
|
|
138
|
+
for (const stockEntry of stock) {
|
|
139
|
+
const id = stockEntry.id;
|
|
140
|
+
const override = overrides[id];
|
|
141
|
+
const merged = override ? deepMerge(stockEntry, override) : stockEntry;
|
|
142
|
+
// `stock: true` is forced here rather than read from the merged object, so an override
|
|
143
|
+
// can never flip a custom entry's provenance or vice versa.
|
|
144
|
+
const parsed = CliEntrySchema.safeParse({ ...merged, id, stock: true });
|
|
145
|
+
if (parsed.success) {
|
|
146
|
+
entries.push(parsed.data);
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
warnings.push(`Override for stock CLI "${id}" failed validation; using the shipped definition. ${parsed.error.message}`);
|
|
150
|
+
entries.push(stockEntry);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
for (const [id, raw] of Object.entries(overrides)) {
|
|
154
|
+
if (stockById.has(id))
|
|
155
|
+
continue; // already merged above
|
|
156
|
+
// Same forcing in the other direction: a custom entry claiming `stock: true` cannot
|
|
157
|
+
// shadow or impersonate a shipped one.
|
|
158
|
+
const parsed = CliEntrySchema.safeParse({ ...raw, id, stock: false });
|
|
159
|
+
if (parsed.success) {
|
|
160
|
+
entries.push(parsed.data);
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
warnings.push(`Custom CLI "${id}" failed validation and was dropped. ${parsed.error.message}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
entries.sort((a, b) => a.order - b.order);
|
|
167
|
+
return { entries, warnings };
|
|
168
|
+
}
|
|
169
|
+
let cache = null;
|
|
170
|
+
/**
|
|
171
|
+
* Load the effective registry (stock + user overrides). Memoized for the process lifetime;
|
|
172
|
+
* `reloadCliRegistry()` invalidates.
|
|
173
|
+
*/
|
|
174
|
+
export function loadCliRegistry() {
|
|
175
|
+
if (cache)
|
|
176
|
+
return cache;
|
|
177
|
+
const warnings = [];
|
|
178
|
+
const existing = readRegistryFile(filePath(), warnings);
|
|
179
|
+
cache = resolveRegistry(STOCK_CLIS, existing, warnings);
|
|
180
|
+
// Once per process (the result is memoized): silence here is what made a 0644 file look
|
|
181
|
+
// like "the override feature does nothing".
|
|
182
|
+
for (const warning of warnings)
|
|
183
|
+
console.warn(`[cli-registry] ${warning}`);
|
|
184
|
+
return cache;
|
|
185
|
+
}
|
|
186
|
+
/** Drop the memoized registry so the next `loadCliRegistry()` re-reads the file. */
|
|
187
|
+
export function reloadCliRegistry() {
|
|
188
|
+
cache = null;
|
|
189
|
+
}
|
|
190
|
+
export function listClis() {
|
|
191
|
+
return loadCliRegistry().entries;
|
|
192
|
+
}
|
|
193
|
+
export function enabledClis() {
|
|
194
|
+
return listClis().filter((e) => e.enabled);
|
|
195
|
+
}
|
|
196
|
+
export function getCli(id) {
|
|
197
|
+
return listClis().find((e) => e.id === id);
|
|
198
|
+
}
|
|
199
|
+
export function cliIds() {
|
|
200
|
+
return listClis().map((e) => e.id);
|
|
201
|
+
}
|
|
202
|
+
/** Every enabled entry's id, in registry order. */
|
|
203
|
+
export function enabledCliIds() {
|
|
204
|
+
return enabledClis().map((e) => e.id);
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Resolve the install command for the current platform, falling back to the linux one (the
|
|
208
|
+
* common case for a `curl | bash` or `npm install -g` line) and then to whatever is
|
|
209
|
+
* declared. Display text only — never executed. See CliDiscovery.install.command.
|
|
210
|
+
*/
|
|
211
|
+
export function resolveInstallCommandForPlatform(entry) {
|
|
212
|
+
const { command } = entry.discovery.install;
|
|
213
|
+
const platform = process.platform;
|
|
214
|
+
return command[platform] ?? command.linux ?? Object.values(command)[0];
|
|
215
|
+
}
|
|
216
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../src/config/cli-registry/registry.ts"],"names":[],"mappings":"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"}
|