@euqns/nudge-mcp 1.14.0 → 1.19.0
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/README.md +52 -0
- package/dist/credentials.js +29 -0
- package/dist/credentials.js.map +1 -1
- package/dist/engines/claude.js +106 -6
- package/dist/engines/claude.js.map +1 -1
- package/dist/engines/claudeModels.js +122 -0
- package/dist/engines/claudeModels.js.map +1 -0
- package/dist/engines/claudeModels.test.js +67 -0
- package/dist/engines/claudeModels.test.js.map +1 -0
- package/dist/engines/codex.js +38 -2
- package/dist/engines/codex.js.map +1 -1
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -1
- package/dist/runner.js +170 -47
- package/dist/runner.js.map +1 -1
- package/dist/runner.test.js +27 -1
- package/dist/runner.test.js.map +1 -1
- package/dist/service.js +423 -0
- package/dist/service.js.map +1 -0
- package/dist/service.test.js +111 -0
- package/dist/service.test.js.map +1 -0
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -75,6 +75,11 @@ cursor-agent login
|
|
|
75
75
|
npx -y @euqns/nudge-mcp@latest agent
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
+
To have the companion come back on its own after a reboot, install it as a login
|
|
79
|
+
service from the same directory: `nudge-mcp service install agent --no-open`
|
|
80
|
+
(see *Survive reboots* below; `--no-open` keeps it from opening a browser tab at
|
|
81
|
+
every login — the pairing URL is in `~/.nudge/logs/com.nudge.agent.log`).
|
|
82
|
+
|
|
78
83
|
The companion checks Codex, Claude Code, and Cursor independently, starts when at
|
|
79
84
|
least one has a valid subscription login, and reports which CLIs are ready. One
|
|
80
85
|
pairing exposes every ready provider, so each chat can choose its own agent and
|
|
@@ -150,12 +155,59 @@ like opening several chats. All of them share the runner's checkout: parallel
|
|
|
150
155
|
coding turns can mix uncommitted changes across tasks, so prefer tasks that
|
|
151
156
|
touch different areas, or run one clone per runner (`--instance`) instead.
|
|
152
157
|
|
|
158
|
+
### One machine, one entry
|
|
159
|
+
|
|
160
|
+
The runner mints a machine id on first launch and keeps it in
|
|
161
|
+
`~/.nudge/machine.json`, independent of `--save`. Restarting, rebooting, or
|
|
162
|
+
re-running the setup command therefore re-registers the SAME machine rather
|
|
163
|
+
than adding another row to the board's runner list — the board just shows it
|
|
164
|
+
going online and offline. `--instance <name>` is the deliberate exception: it
|
|
165
|
+
registers as a second machine, for running two processes from one box.
|
|
166
|
+
|
|
167
|
+
While it runs the process heartbeats once a minute so the board can show it as
|
|
168
|
+
online, and reports offline on Ctrl-C. Startup probes each agent CLI (`codex`,
|
|
169
|
+
`claude`, `cursor-agent`) for version, sign-in state, and available models, and
|
|
170
|
+
sends the result with the registration — that is what the board's runner
|
|
171
|
+
settings render. Installed or signed in to an agent after startup? Press
|
|
172
|
+
**Reload** there and the running process re-probes and re-reports without a
|
|
173
|
+
restart.
|
|
174
|
+
|
|
153
175
|
A running runner picks up new builds by itself. The process supervises a child
|
|
154
176
|
that watches its own installed code; when `dist/` changes (a rebuild, or an npm
|
|
155
177
|
upgrade in place) it stops claiming jobs, lets in-flight jobs finish on the code
|
|
156
178
|
they started with, then restarts and re-registers under the same machine id.
|
|
157
179
|
Opt out with `--no-auto-restart`.
|
|
158
180
|
|
|
181
|
+
### Survive reboots: run it as a login service
|
|
182
|
+
|
|
183
|
+
A runner started from a terminal dies with that terminal, so a reboot takes the
|
|
184
|
+
machine off the board until someone relaunches it. Install it as a per-user
|
|
185
|
+
service instead — the OS starts it at login and brings it back after a crash:
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
npm install -g @euqns/nudge-mcp # a stable install path; npx's cache may be pruned
|
|
189
|
+
cd /path/to/checkout
|
|
190
|
+
nudge-mcp service install runner --convex-url https://<deployment>.convex.cloud \
|
|
191
|
+
--token nrun_... --concurrency 8 --cwd . --save
|
|
192
|
+
nudge-mcp service status # installed / running (pid) per service
|
|
193
|
+
nudge-mcp service uninstall runner # stop it and remove the service
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Everything after `runner` is passed to the runner unchanged; a relative `--cwd`
|
|
197
|
+
is pinned to the directory you ran the command in. On macOS this writes
|
|
198
|
+
`~/Library/LaunchAgents/com.nudge.runner.plist` (mode 0600, since it can carry
|
|
199
|
+
the token) and loads it with `launchctl`; logs go to `~/.nudge/logs/`. On Linux
|
|
200
|
+
it writes `~/.config/systemd/user/nudge-runner.service` and enables it with
|
|
201
|
+
`systemctl --user`; run `loginctl enable-linger $USER` if it should start at
|
|
202
|
+
boot before anyone logs in. The service runs the same node binary and installed
|
|
203
|
+
copy you invoked, with your current `PATH` baked in so `git`, `gh`, `claude`,
|
|
204
|
+
`codex`, and `cursor-agent` resolve exactly as they do in your shell. Once a
|
|
205
|
+
runner is a service, the auto-restart above still handles rebuilds; the service
|
|
206
|
+
manager handles reboots. `--instance <name>` installs a second, separately
|
|
207
|
+
managed service. Add `--dry-run` to print the service file without installing.
|
|
208
|
+
Windows has no equivalent here yet — the command prints the Task Scheduler
|
|
209
|
+
action to use instead.
|
|
210
|
+
|
|
159
211
|
During the work turn, `get_card`, `update_card`, `add_comment`, and subtask
|
|
160
212
|
completion use the job's board-scoped runner token. They are limited to the
|
|
161
213
|
active task and its direct subtasks, so task management does not depend on a
|
package/dist/credentials.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Local credentials file persistence. Tokens never sit in shell env or MCP
|
|
2
2
|
// client JSON — they're held in `~/.nudge/credentials` (mode 0600) so only
|
|
3
3
|
// the user can read them.
|
|
4
|
+
import crypto from "node:crypto";
|
|
4
5
|
import fs from "node:fs/promises";
|
|
5
6
|
import os from "node:os";
|
|
6
7
|
import path from "node:path";
|
|
@@ -42,6 +43,34 @@ export async function deleteCredentials() {
|
|
|
42
43
|
}
|
|
43
44
|
}
|
|
44
45
|
export const RUNNER_CONFIG_PATH = path.join(CREDENTIALS_DIR, "runner.json");
|
|
46
|
+
export const MACHINE_ID_PATH = path.join(CREDENTIALS_DIR, "machine.json");
|
|
47
|
+
// This machine's stable identity, minted once and kept forever.
|
|
48
|
+
//
|
|
49
|
+
// It used to live only inside runner.json, which is written only when the user
|
|
50
|
+
// passes `--save` — so the ordinary "paste the command and run it" path minted
|
|
51
|
+
// a fresh uuid on every launch and registered a brand new machine each time,
|
|
52
|
+
// leaving the board's Machines list full of ghosts. Persisting it here,
|
|
53
|
+
// unconditionally and separately from any credential, is what makes "my
|
|
54
|
+
// machine" one row that goes online and offline.
|
|
55
|
+
export async function ensureMachineId() {
|
|
56
|
+
try {
|
|
57
|
+
const parsed = JSON.parse(await fs.readFile(MACHINE_ID_PATH, "utf8"));
|
|
58
|
+
if (typeof parsed.machineId === "string" && parsed.machineId) {
|
|
59
|
+
return parsed.machineId;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
if (err.code !== "ENOENT")
|
|
64
|
+
throw err;
|
|
65
|
+
}
|
|
66
|
+
// Adopt the id an earlier `--save` wrote rather than minting a second one,
|
|
67
|
+
// so upgrading doesn't itself create one last ghost machine.
|
|
68
|
+
const machineId = (await readRunnerConfig())?.machineId ?? crypto.randomUUID();
|
|
69
|
+
await fs.mkdir(CREDENTIALS_DIR, { recursive: true, mode: 0o700 });
|
|
70
|
+
await fs.writeFile(MACHINE_ID_PATH, JSON.stringify({ machineId }, null, 2), { mode: 0o600 });
|
|
71
|
+
await fs.chmod(MACHINE_ID_PATH, 0o600);
|
|
72
|
+
return machineId;
|
|
73
|
+
}
|
|
45
74
|
export async function readRunnerConfig() {
|
|
46
75
|
try {
|
|
47
76
|
const parsed = JSON.parse(await fs.readFile(RUNNER_CONFIG_PATH, "utf8"));
|
package/dist/credentials.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"credentials.js","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,2EAA2E;AAC3E,0BAA0B;AAE1B,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAO7B,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,QAAQ,CAAC,CAAC;AACjE,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,aAAa,CAAC,CAAC;AAE1E,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC;QACxD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAyB,CAAC;QACvD,IACE,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ;YAChC,OAAO,MAAM,CAAC,SAAS,KAAK,QAAQ,EACpC,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC;IAC9D,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QAClE,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,KAAkB;IACvD,MAAM,EAAE,CAAC,KAAK,CAAC,eAAe,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAClE,wEAAwE;IACxE,yEAAyE;IACzE,2BAA2B;IAC3B,MAAM,EAAE,CAAC,SAAS,CAAC,gBAAgB,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;QACnE,IAAI,EAAE,KAAK;KACZ,CAAC,CAAC;IACH,MAAM,EAAE,CAAC,KAAK,CAAC,gBAAgB,EAAE,KAAK,CAAC,CAAC;AAC1C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB;IACrC,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;IACpC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;YAAE,MAAM,GAAG,CAAC;IAClE,CAAC;AACH,CAAC;AAoBD,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,aAAa,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"credentials.js","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,2EAA2E;AAC3E,0BAA0B;AAE1B,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAO7B,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,QAAQ,CAAC,CAAC;AACjE,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,aAAa,CAAC,CAAC;AAE1E,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC;QACxD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAyB,CAAC;QACvD,IACE,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ;YAChC,OAAO,MAAM,CAAC,SAAS,KAAK,QAAQ,EACpC,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC;IAC9D,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QAClE,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,KAAkB;IACvD,MAAM,EAAE,CAAC,KAAK,CAAC,eAAe,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAClE,wEAAwE;IACxE,yEAAyE;IACzE,2BAA2B;IAC3B,MAAM,EAAE,CAAC,SAAS,CAAC,gBAAgB,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;QACnE,IAAI,EAAE,KAAK;KACZ,CAAC,CAAC;IACH,MAAM,EAAE,CAAC,KAAK,CAAC,gBAAgB,EAAE,KAAK,CAAC,CAAC;AAC1C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB;IACrC,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC;IACpC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;YAAE,MAAM,GAAG,CAAC;IAClE,CAAC;AACH,CAAC;AAoBD,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,aAAa,CAAC,CAAC;AAC5E,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,cAAc,CAAC,CAAC;AAE1E,gEAAgE;AAChE,EAAE;AACF,+EAA+E;AAC/E,+EAA+E;AAC/E,6EAA6E;AAC7E,wEAAwE;AACxE,wEAAwE;AACxE,iDAAiD;AACjD,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC,CAEnE,CAAC;QACF,IAAI,OAAO,MAAM,CAAC,SAAS,KAAK,QAAQ,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;YAC7D,OAAO,MAAM,CAAC,SAAS,CAAC;QAC1B,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;YAAE,MAAM,GAAG,CAAC;IAClE,CAAC;IACD,2EAA2E;IAC3E,6DAA6D;IAC7D,MAAM,SAAS,GAAG,CAAC,MAAM,gBAAgB,EAAE,CAAC,EAAE,SAAS,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;IAC/E,MAAM,EAAE,CAAC,KAAK,CAAC,eAAe,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAClE,MAAM,EAAE,CAAC,SAAS,CAChB,eAAe,EACf,IAAI,CAAC,SAAS,CAAC,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,EACtC,EAAE,IAAI,EAAE,KAAK,EAAE,CAChB,CAAC;IACF,MAAM,EAAE,CAAC,KAAK,CAAC,eAAe,EAAE,KAAK,CAAC,CAAC;IACvC,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACpC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CACvB,MAAM,EAAE,CAAC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC,CACrB,CAAC;QAC3B,IACE,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ;YAChC,OAAO,MAAM,CAAC,SAAS,KAAK,QAAQ;YACpC,OAAO,MAAM,CAAC,SAAS,KAAK,QAAQ,EACpC,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,MAAsB,CAAC;IAChC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QAClE,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,MAAoB;IAC1D,MAAM,EAAE,CAAC,KAAK,CAAC,eAAe,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAClE,MAAM,EAAE,CAAC,SAAS,CAAC,kBAAkB,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;QACtE,IAAI,EAAE,KAAK;KACZ,CAAC,CAAC;IACH,MAAM,EAAE,CAAC,KAAK,CAAC,kBAAkB,EAAE,KAAK,CAAC,CAAC;AAC5C,CAAC"}
|
package/dist/engines/claude.js
CHANGED
|
@@ -6,9 +6,65 @@
|
|
|
6
6
|
// terminal `result` event, so jobs on this engine carry a real measured cost
|
|
7
7
|
// rather than just token counts.
|
|
8
8
|
import { spawn, spawnSync } from "node:child_process";
|
|
9
|
+
import { readFileSync } from "node:fs";
|
|
10
|
+
import os from "node:os";
|
|
11
|
+
import path from "node:path";
|
|
9
12
|
import readline from "node:readline";
|
|
13
|
+
import { listClaudeModels, resolveClaudeCliPath } from "./claudeModels.js";
|
|
10
14
|
import { claudeMcpConfig } from "./isolation.js";
|
|
11
15
|
const CLI = process.platform === "win32" ? "claude.cmd" : "claude";
|
|
16
|
+
// A wedged CLI must never hang runner startup — every startup probe is bounded.
|
|
17
|
+
const PROBE_TIMEOUT_MS = 10_000;
|
|
18
|
+
// Is this machine signed in to Claude Code?
|
|
19
|
+
//
|
|
20
|
+
// `claude --version` answers "installed", not "usable" — an unauthenticated CLI
|
|
21
|
+
// exits 0 and then fails the first real turn, which is a whole queued job
|
|
22
|
+
// wasted. There is no offline `auth status` subcommand, so this checks the two
|
|
23
|
+
// places a credential can live: the API key in the environment, and the OAuth
|
|
24
|
+
// account the CLI records in ~/.claude.json after a browser login. On macOS the
|
|
25
|
+
// token itself lives in the Keychain, which is exactly why we look for the
|
|
26
|
+
// recorded ACCOUNT rather than the secret.
|
|
27
|
+
function isClaudeSignedIn() {
|
|
28
|
+
// Every way a credential can arrive from the environment, including the
|
|
29
|
+
// enterprise gateways — a false negative here would take a working machine
|
|
30
|
+
// out of the pool, so this errs towards "yes".
|
|
31
|
+
for (const key of [
|
|
32
|
+
"ANTHROPIC_API_KEY",
|
|
33
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
34
|
+
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
35
|
+
"CLAUDE_CODE_USE_BEDROCK",
|
|
36
|
+
"CLAUDE_CODE_USE_VERTEX",
|
|
37
|
+
]) {
|
|
38
|
+
if (process.env[key])
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
const markers = [
|
|
42
|
+
// Written after a browser login. On macOS the token itself is in the
|
|
43
|
+
// Keychain, which is why this looks for the recorded account, not a secret.
|
|
44
|
+
[path.join(os.homedir(), ".claude.json"), ["oauthAccount", "primaryApiKey"]],
|
|
45
|
+
// Linux/WSL keep the token on disk instead.
|
|
46
|
+
[
|
|
47
|
+
path.join(os.homedir(), ".claude", ".credentials.json"),
|
|
48
|
+
["claudeAiOauth", "accessToken"],
|
|
49
|
+
],
|
|
50
|
+
// Enterprise setups mint a key per call through a helper command.
|
|
51
|
+
[
|
|
52
|
+
path.join(os.homedir(), ".claude", "settings.json"),
|
|
53
|
+
["apiKeyHelper", "awsAuthRefresh", "awsCredentialExport"],
|
|
54
|
+
],
|
|
55
|
+
];
|
|
56
|
+
for (const [file, keys] of markers) {
|
|
57
|
+
try {
|
|
58
|
+
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
59
|
+
if (keys.some((key) => parsed[key]))
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// Missing or unreadable — try the next location.
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
12
68
|
function readUsage(usage) {
|
|
13
69
|
return {
|
|
14
70
|
inputTokens: usage?.input_tokens ?? 0,
|
|
@@ -68,7 +124,10 @@ function describeEvent(event) {
|
|
|
68
124
|
const type = event.type;
|
|
69
125
|
if (type === "system" && event.subtype === "init") {
|
|
70
126
|
const cwd = typeof event.cwd === "string" ? event.cwd : "";
|
|
71
|
-
|
|
127
|
+
// The CLI resolves the alias before the first turn, so this is the one
|
|
128
|
+
// place the log can name the model that is actually about to run.
|
|
129
|
+
const model = typeof event.model === "string" ? ` on ${event.model}` : "";
|
|
130
|
+
return `Claude Code session started${model} in ${cwd}`;
|
|
72
131
|
}
|
|
73
132
|
if (type === "assistant" || type === "user") {
|
|
74
133
|
const message = event.message;
|
|
@@ -113,15 +172,47 @@ function textOf(content) {
|
|
|
113
172
|
export class ClaudeEngine {
|
|
114
173
|
id = "claude";
|
|
115
174
|
displayName = "Claude Code";
|
|
175
|
+
// Version, sign-in state, and the model aliases this build accepts — all
|
|
176
|
+
// probed locally and offline, so startup can't hang on the network. The
|
|
177
|
+
// board renders whatever comes back here, which is what makes "why is Claude
|
|
178
|
+
// Code unavailable?" answerable without opening the runner's terminal.
|
|
116
179
|
async detect() {
|
|
117
180
|
const result = spawnSync(CLI, ["--version"], {
|
|
118
181
|
encoding: "utf8",
|
|
119
182
|
stdio: ["ignore", "pipe", "pipe"],
|
|
183
|
+
timeout: PROBE_TIMEOUT_MS,
|
|
120
184
|
});
|
|
121
185
|
if (result.error || result.status !== 0) {
|
|
122
186
|
return { available: false, detail: "claude CLI not found" };
|
|
123
187
|
}
|
|
124
|
-
|
|
188
|
+
const version = (result.stdout ?? "").trim();
|
|
189
|
+
const help = spawnSync(CLI, ["--help"], {
|
|
190
|
+
encoding: "utf8",
|
|
191
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
192
|
+
timeout: PROBE_TIMEOUT_MS,
|
|
193
|
+
});
|
|
194
|
+
const helpText = `${help.stdout ?? ""}\n${help.stderr ?? ""}`;
|
|
195
|
+
const signedIn = isClaudeSignedIn();
|
|
196
|
+
if (!signedIn) {
|
|
197
|
+
return {
|
|
198
|
+
available: false,
|
|
199
|
+
detail: `${version} — installed but not signed in; run \`claude\` once`,
|
|
200
|
+
capabilities: { version: version || undefined, authenticated: false },
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
const cliPath = resolveClaudeCliPath(CLI);
|
|
204
|
+
const models = listClaudeModels(cliPath);
|
|
205
|
+
return {
|
|
206
|
+
available: true,
|
|
207
|
+
detail: version,
|
|
208
|
+
capabilities: {
|
|
209
|
+
version: version || undefined,
|
|
210
|
+
authenticated: true,
|
|
211
|
+
models,
|
|
212
|
+
modes: ["default", "plan", "bypassPermissions"],
|
|
213
|
+
resume: helpText.includes("--resume"),
|
|
214
|
+
},
|
|
215
|
+
};
|
|
125
216
|
}
|
|
126
217
|
async execute(options) {
|
|
127
218
|
const args = [
|
|
@@ -151,6 +242,11 @@ export class ClaudeEngine {
|
|
|
151
242
|
if (options.threadId)
|
|
152
243
|
args.push("--resume", options.threadId);
|
|
153
244
|
let result = null;
|
|
245
|
+
// What the CLI resolved `--model` to. An alias ("fable") and the empty
|
|
246
|
+
// "agent default" both only become a real model name here, so this is what
|
|
247
|
+
// the card has to show — reporting the request back would just echo the
|
|
248
|
+
// question.
|
|
249
|
+
let resolvedModel;
|
|
154
250
|
const { stderr, code } = await runClaude(args, {
|
|
155
251
|
prompt: options.prompt,
|
|
156
252
|
cwd: options.cwd,
|
|
@@ -171,10 +267,13 @@ export class ClaudeEngine {
|
|
|
171
267
|
// The init event carries the session id at the START of the turn —
|
|
172
268
|
// surface it immediately so an aborted turn (steer) stays resumable.
|
|
173
269
|
// The terminal result event never arrives on an abort.
|
|
174
|
-
if (event.type === "system" &&
|
|
175
|
-
event.
|
|
176
|
-
|
|
177
|
-
|
|
270
|
+
if (event.type === "system" && event.subtype === "init") {
|
|
271
|
+
if (typeof event.session_id === "string") {
|
|
272
|
+
options.onThreadId?.(event.session_id);
|
|
273
|
+
}
|
|
274
|
+
if (typeof event.model === "string" && event.model) {
|
|
275
|
+
resolvedModel = event.model;
|
|
276
|
+
}
|
|
178
277
|
}
|
|
179
278
|
const text = describeEvent(event);
|
|
180
279
|
if (text)
|
|
@@ -193,6 +292,7 @@ export class ClaudeEngine {
|
|
|
193
292
|
return {
|
|
194
293
|
summary: typeof final.result === "string" ? final.result : "",
|
|
195
294
|
threadId: final.session_id,
|
|
295
|
+
resolvedModel,
|
|
196
296
|
usage,
|
|
197
297
|
costUsd: final.total_cost_usd,
|
|
198
298
|
// Claude Code computes the figure itself, so this is measured, not modelled.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"claude.js","sourceRoot":"","sources":["../../src/engines/claude.ts"],"names":[],"mappings":"AAAA,iEAAiE;AACjE,EAAE;AACF,uEAAuE;AACvE,EAAE;AACF,8EAA8E;AAC9E,6EAA6E;AAC7E,iCAAiC;AAEjC,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,QAAQ,MAAM,eAAe,CAAC;AAErC,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"claude.js","sourceRoot":"","sources":["../../src/engines/claude.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
export const CLAUDE_ALIASES = ["fable", "opus", "sonnet", "haiku"];
|
|
6
|
+
const PINNED_ID = /^claude-(?:fable|opus|sonnet|haiku)-[a-z0-9][a-z0-9.-]*$/i;
|
|
7
|
+
export function isClaudePinnedModelId(id) {
|
|
8
|
+
if (!PINNED_ID.test(id))
|
|
9
|
+
return false;
|
|
10
|
+
if (/\d{6,}/.test(id))
|
|
11
|
+
return false;
|
|
12
|
+
if (/-v\d+$/i.test(id))
|
|
13
|
+
return false;
|
|
14
|
+
if (id.length > 40)
|
|
15
|
+
return false;
|
|
16
|
+
for (const part of id.split("-").slice(2)) {
|
|
17
|
+
if (/^\d+$/.test(part) && Number(part) >= 20)
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
export function parseClaudeModelIds(text) {
|
|
23
|
+
const found = [];
|
|
24
|
+
const re = /\bclaude-(?:fable|opus|sonnet|haiku)(?:-[a-z0-9][a-z0-9.-]*)?\b/gi;
|
|
25
|
+
for (const match of text.matchAll(re)) {
|
|
26
|
+
const id = match[0].toLowerCase();
|
|
27
|
+
if (!isClaudePinnedModelId(id))
|
|
28
|
+
continue;
|
|
29
|
+
if (!found.includes(id))
|
|
30
|
+
found.push(id);
|
|
31
|
+
}
|
|
32
|
+
return found.sort();
|
|
33
|
+
}
|
|
34
|
+
function extractModelIdsFromCatalog(value) {
|
|
35
|
+
const ids = [];
|
|
36
|
+
const visit = (node) => {
|
|
37
|
+
if (typeof node === "string") {
|
|
38
|
+
const id = node.replace(/\[1m\]$/i, "").trim().toLowerCase();
|
|
39
|
+
if (isClaudePinnedModelId(id) && !ids.includes(id))
|
|
40
|
+
ids.push(id);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (!node || typeof node !== "object")
|
|
44
|
+
return;
|
|
45
|
+
if (Array.isArray(node)) {
|
|
46
|
+
for (const item of node)
|
|
47
|
+
visit(item);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const record = node;
|
|
51
|
+
for (const key of ["model", "id", "value", "modelId"]) {
|
|
52
|
+
if (key in record)
|
|
53
|
+
visit(record[key]);
|
|
54
|
+
}
|
|
55
|
+
for (const child of Object.values(record))
|
|
56
|
+
visit(child);
|
|
57
|
+
};
|
|
58
|
+
visit(value);
|
|
59
|
+
return ids;
|
|
60
|
+
}
|
|
61
|
+
function catalogCacheDir(homeDir) {
|
|
62
|
+
const home = homeDir ?? process.env.HOME ?? process.env.USERPROFILE ?? os.homedir();
|
|
63
|
+
return path.join(home, ".claude", "cache", "model-catalog");
|
|
64
|
+
}
|
|
65
|
+
function readCatalogCache(homeDir) {
|
|
66
|
+
const base = catalogCacheDir(homeDir);
|
|
67
|
+
for (const file of ["published-floor.json", "masked-ids.json"]) {
|
|
68
|
+
try {
|
|
69
|
+
const parsed = JSON.parse(fs.readFileSync(path.join(base, file), "utf8"));
|
|
70
|
+
const ids = extractModelIdsFromCatalog(parsed);
|
|
71
|
+
if (ids.length)
|
|
72
|
+
return ids;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// Missing or unreadable — try the next file.
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
function discoverFromBinary(cliPath) {
|
|
81
|
+
const probe = spawnSync("strings", [cliPath], {
|
|
82
|
+
encoding: "utf8",
|
|
83
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
84
|
+
timeout: 10_000,
|
|
85
|
+
});
|
|
86
|
+
if (!probe.error && probe.status === 0) {
|
|
87
|
+
return parseClaudeModelIds(probe.stdout ?? "");
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
return parseClaudeModelIds(fs.readFileSync(cliPath).toString("latin1"));
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
export function resolveClaudeCliPath(cli) {
|
|
97
|
+
try {
|
|
98
|
+
const which = spawnSync(process.platform === "win32" ? "where" : "which", [cli], {
|
|
99
|
+
encoding: "utf8",
|
|
100
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
101
|
+
timeout: 5_000,
|
|
102
|
+
});
|
|
103
|
+
const candidate = (which.stdout ?? "").trim().split(/\r?\n/)[0]?.trim();
|
|
104
|
+
if (!candidate)
|
|
105
|
+
return undefined;
|
|
106
|
+
return fs.realpathSync(candidate);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
export function listClaudeModels(cliPath, homeDir) {
|
|
113
|
+
const pinned = new Set();
|
|
114
|
+
for (const id of readCatalogCache(homeDir))
|
|
115
|
+
pinned.add(id);
|
|
116
|
+
if (cliPath) {
|
|
117
|
+
for (const id of discoverFromBinary(cliPath))
|
|
118
|
+
pinned.add(id);
|
|
119
|
+
}
|
|
120
|
+
return [...CLAUDE_ALIASES, ...[...pinned].sort()];
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=claudeModels.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claudeModels.js","sourceRoot":"","sources":["../../src/engines/claudeModels.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
import { isClaudePinnedModelId, listClaudeModels, parseClaudeModelIds, resolveClaudeCliPath, } from "./claudeModels.js";
|
|
7
|
+
const FIXTURE = `
|
|
8
|
+
fable opus sonnet haiku
|
|
9
|
+
claude-fable-5-1 claude-fable-5 claude-fable-5-mythos-5
|
|
10
|
+
claude-opus-5 claude-sonnet-5 claude-haiku-4-5
|
|
11
|
+
claude-sonnet-4-5-20250929 claude-opus-4-5-20251101-v1
|
|
12
|
+
`;
|
|
13
|
+
test("parseClaudeModelIds keeps short pinned names and drops dated API ids", () => {
|
|
14
|
+
assert.deepEqual(parseClaudeModelIds(FIXTURE), [
|
|
15
|
+
"claude-fable-5",
|
|
16
|
+
"claude-fable-5-1",
|
|
17
|
+
"claude-fable-5-mythos-5",
|
|
18
|
+
"claude-haiku-4-5",
|
|
19
|
+
"claude-opus-5",
|
|
20
|
+
"claude-sonnet-5",
|
|
21
|
+
]);
|
|
22
|
+
});
|
|
23
|
+
test("isClaudePinnedModelId rejects bare family stems and aliases", () => {
|
|
24
|
+
assert.equal(isClaudePinnedModelId("fable"), false);
|
|
25
|
+
assert.equal(isClaudePinnedModelId("claude-fable"), false);
|
|
26
|
+
assert.equal(isClaudePinnedModelId("claude-fable-5-1"), true);
|
|
27
|
+
});
|
|
28
|
+
test("listClaudeModels always leads with family aliases", () => {
|
|
29
|
+
const models = listClaudeModels();
|
|
30
|
+
assert.deepEqual(models.slice(0, 4), ["fable", "opus", "sonnet", "haiku"]);
|
|
31
|
+
});
|
|
32
|
+
test("listClaudeModels discovers pinned ids from the installed CLI binary", () => {
|
|
33
|
+
const cliPath = resolveClaudeCliPath(process.platform === "win32" ? "claude.cmd" : "claude");
|
|
34
|
+
if (!cliPath)
|
|
35
|
+
return;
|
|
36
|
+
const models = listClaudeModels(cliPath);
|
|
37
|
+
assert.ok(models.includes("fable"));
|
|
38
|
+
assert.ok(models.some((id) => id.startsWith("claude-")), "installed Claude should report at least one pinned model id");
|
|
39
|
+
});
|
|
40
|
+
test("listClaudeModels reads pinned ids from a catalog cache file", () => {
|
|
41
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "claude-catalog-"));
|
|
42
|
+
const catalogDir = path.join(dir, ".claude", "cache", "model-catalog");
|
|
43
|
+
fs.mkdirSync(catalogDir, { recursive: true });
|
|
44
|
+
fs.writeFileSync(path.join(catalogDir, "published-floor.json"), JSON.stringify({
|
|
45
|
+
models: [{ model: "claude-fable-9" }, { model: "claude-opus-6" }],
|
|
46
|
+
}));
|
|
47
|
+
const home = process.env.HOME;
|
|
48
|
+
process.env.HOME = dir;
|
|
49
|
+
try {
|
|
50
|
+
assert.deepEqual(listClaudeModels(undefined, dir), [
|
|
51
|
+
"fable",
|
|
52
|
+
"opus",
|
|
53
|
+
"sonnet",
|
|
54
|
+
"haiku",
|
|
55
|
+
"claude-fable-9",
|
|
56
|
+
"claude-opus-6",
|
|
57
|
+
]);
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
if (home === undefined)
|
|
61
|
+
delete process.env.HOME;
|
|
62
|
+
else
|
|
63
|
+
process.env.HOME = home;
|
|
64
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
//# sourceMappingURL=claudeModels.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claudeModels.test.js","sourceRoot":"","sources":["../../src/engines/claudeModels.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EACL,qBAAqB,EACrB,gBAAgB,EAChB,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,mBAAmB,CAAC;AAE3B,MAAM,OAAO,GAAG;;;;;CAKf,CAAC;AAEF,IAAI,CAAC,sEAAsE,EAAE,GAAG,EAAE;IAChF,MAAM,CAAC,SAAS,CAAC,mBAAmB,CAAC,OAAO,CAAC,EAAE;QAC7C,gBAAgB;QAChB,kBAAkB;QAClB,yBAAyB;QACzB,kBAAkB;QAClB,eAAe;QACf,iBAAiB;KAClB,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,6DAA6D,EAAE,GAAG,EAAE;IACvE,MAAM,CAAC,KAAK,CAAC,qBAAqB,CAAC,OAAO,CAAC,EAAE,KAAK,CAAC,CAAC;IACpD,MAAM,CAAC,KAAK,CAAC,qBAAqB,CAAC,cAAc,CAAC,EAAE,KAAK,CAAC,CAAC;IAC3D,MAAM,CAAC,KAAK,CAAC,qBAAqB,CAAC,kBAAkB,CAAC,EAAE,IAAI,CAAC,CAAC;AAChE,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,mDAAmD,EAAE,GAAG,EAAE;IAC7D,MAAM,MAAM,GAAG,gBAAgB,EAAE,CAAC;IAClC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;AAC7E,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,qEAAqE,EAAE,GAAG,EAAE;IAC/E,MAAM,OAAO,GAAG,oBAAoB,CAAC,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;IAC7F,IAAI,CAAC,OAAO;QAAE,OAAO;IACrB,MAAM,MAAM,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;IACzC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;IACpC,MAAM,CAAC,EAAE,CACP,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,EAC7C,6DAA6D,CAC9D,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,6DAA6D,EAAE,GAAG,EAAE;IACvE,MAAM,GAAG,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,iBAAiB,CAAC,CAAC,CAAC;IACtE,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,OAAO,EAAE,eAAe,CAAC,CAAC;IACvE,EAAE,CAAC,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,EAAE,CAAC,aAAa,CACd,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,sBAAsB,CAAC,EAC7C,IAAI,CAAC,SAAS,CAAC;QACb,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,EAAE,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC;KAClE,CAAC,CACH,CAAC;IACF,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;IAC9B,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC;IACvB,IAAI,CAAC;QACH,MAAM,CAAC,SAAS,CAAC,gBAAgB,CAAC,SAAS,EAAE,GAAG,CAAC,EAAE;YACjD,OAAO;YACP,MAAM;YACN,QAAQ;YACR,OAAO;YACP,gBAAgB;YAChB,eAAe;SAChB,CAAC,CAAC;IACL,CAAC;YAAS,CAAC;QACT,IAAI,IAAI,KAAK,SAAS;YAAE,OAAO,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;;YAC3C,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC;QAC7B,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACnD,CAAC;AACH,CAAC,CAAC,CAAC"}
|
package/dist/engines/codex.js
CHANGED
|
@@ -8,6 +8,11 @@ import { classifyEngineFailure, collapseRepeatedLines } from "./failures.js";
|
|
|
8
8
|
import { ensureCodexShadowHome } from "./isolation.js";
|
|
9
9
|
import { estimateCostUsd } from "./pricing.js";
|
|
10
10
|
import { emptyUsage, } from "./types.js";
|
|
11
|
+
// A wedged CLI must never hang runner startup — every startup probe is bounded.
|
|
12
|
+
const PROBE_TIMEOUT_MS = 10_000;
|
|
13
|
+
// Models `codex -m` accepts, newest first. Reported at startup so the board's
|
|
14
|
+
// model picker reflects what this machine can actually run.
|
|
15
|
+
const CODEX_MODELS = ["gpt-5.6-sol", "gpt-5.6-luna"];
|
|
11
16
|
function threadOptions(options, mode, fallback) {
|
|
12
17
|
return {
|
|
13
18
|
sandboxMode: mode,
|
|
@@ -80,11 +85,26 @@ export class CodexEngine {
|
|
|
80
85
|
}
|
|
81
86
|
// Available only on a ChatGPT login. An API-key login would silently bill
|
|
82
87
|
// per token, which is exactly what the subscription-only design avoids.
|
|
88
|
+
//
|
|
89
|
+
// Reports the CLI version and the models this build offers alongside the
|
|
90
|
+
// verdict: the board's runner settings render exactly what the machine sent,
|
|
91
|
+
// so a stale or unauthenticated CLI is visible there instead of only in this
|
|
92
|
+
// terminal.
|
|
83
93
|
async detect() {
|
|
84
94
|
const command = process.platform === "win32" ? "codex.cmd" : "codex";
|
|
95
|
+
const version = spawnSync(command, ["--version"], {
|
|
96
|
+
encoding: "utf8",
|
|
97
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
98
|
+
timeout: PROBE_TIMEOUT_MS,
|
|
99
|
+
});
|
|
100
|
+
if (version.error) {
|
|
101
|
+
return { available: false, detail: "codex CLI not found" };
|
|
102
|
+
}
|
|
103
|
+
const versionText = (version.stdout ?? version.stderr ?? "").trim();
|
|
85
104
|
const result = spawnSync(command, ["login", "status"], {
|
|
86
105
|
encoding: "utf8",
|
|
87
106
|
stdio: ["ignore", "pipe", "pipe"],
|
|
107
|
+
timeout: PROBE_TIMEOUT_MS,
|
|
88
108
|
});
|
|
89
109
|
if (result.error) {
|
|
90
110
|
return { available: false, detail: "codex CLI not found" };
|
|
@@ -93,10 +113,26 @@ export class CodexEngine {
|
|
|
93
113
|
if (!status.includes("Logged in using ChatGPT")) {
|
|
94
114
|
return {
|
|
95
115
|
available: false,
|
|
96
|
-
detail:
|
|
116
|
+
detail: versionText
|
|
117
|
+
? `${versionText} — installed but not logged in with ChatGPT`
|
|
118
|
+
: "codex is installed but not logged in with ChatGPT",
|
|
119
|
+
capabilities: { version: versionText || undefined, authenticated: false },
|
|
97
120
|
};
|
|
98
121
|
}
|
|
99
|
-
return {
|
|
122
|
+
return {
|
|
123
|
+
available: true,
|
|
124
|
+
detail: versionText ? `${versionText} · ChatGPT login` : "ChatGPT login",
|
|
125
|
+
capabilities: {
|
|
126
|
+
version: versionText || undefined,
|
|
127
|
+
authenticated: true,
|
|
128
|
+
// Codex has no `--list-models`; these are the ids the CLI accepts for
|
|
129
|
+
// `-m`, reported so the board can offer them without hard-coding a
|
|
130
|
+
// model list in the frontend.
|
|
131
|
+
models: [...CODEX_MODELS],
|
|
132
|
+
modes: ["read-only", "workspace-write"],
|
|
133
|
+
resume: true,
|
|
134
|
+
},
|
|
135
|
+
};
|
|
100
136
|
}
|
|
101
137
|
async execute(options) {
|
|
102
138
|
await this.prepare(options);
|