@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 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
@@ -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"));
@@ -1 +1 @@
1
- {"version":3,"file":"credentials.js","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"credentials.js","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"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"}
@@ -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
- return `Claude Code session started in ${cwd}`;
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
- return { available: true, detail: (result.stdout ?? "").trim() };
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.subtype === "init" &&
176
- typeof event.session_id === "string") {
177
- options.onThreadId?.(event.session_id);
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":"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"}
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":"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"}
@@ -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: "codex is installed but not logged in with ChatGPT",
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 { available: true, detail: "ChatGPT login" };
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);