codexteer 0.18.2 → 0.20.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.
Files changed (45) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +13 -5
  3. package/bin/codexteer.mjs +1 -1
  4. package/docs/distribution.md +23 -6
  5. package/docs/orchestration.md +4 -4
  6. package/package.json +1 -1
  7. package/src/app-server.mjs +4 -4
  8. package/src/checkpoint.mjs +10 -10
  9. package/src/cli.mjs +90 -79
  10. package/src/cloud-cli.mjs +29 -33
  11. package/src/connection-error.mjs +7 -0
  12. package/src/cursor.mjs +370 -0
  13. package/src/desktop-cloud-id.mjs +9 -1
  14. package/src/desktop-cloud-project.mjs +37 -4
  15. package/src/desktop-cloud.mjs +10 -2
  16. package/src/desktop.mjs +9 -9
  17. package/src/directive.mjs +7 -6
  18. package/src/distribution.mjs +58 -33
  19. package/src/evidence.mjs +3 -3
  20. package/src/findings.mjs +13 -8
  21. package/src/help-cloud-remote.mjs +399 -0
  22. package/src/help-errors.mjs +10 -0
  23. package/src/help-intervention.mjs +492 -0
  24. package/src/help-local.mjs +510 -0
  25. package/src/help-observation.mjs +200 -0
  26. package/src/help-supervision.mjs +257 -0
  27. package/src/help.mjs +126 -304
  28. package/src/journal.mjs +33 -12
  29. package/src/launcher.mjs +6 -6
  30. package/src/monitor.mjs +5 -4
  31. package/src/notify.mjs +3 -3
  32. package/src/observe.mjs +11 -40
  33. package/src/orchestrate-prompt.mjs +14 -12
  34. package/src/orchestrate.mjs +4 -7
  35. package/src/paged-read.mjs +10 -17
  36. package/src/prompt-command.mjs +45 -7
  37. package/src/prompt.mjs +15 -12
  38. package/src/resource.mjs +6 -6
  39. package/src/runner.mjs +5 -5
  40. package/src/store.mjs +2 -2
  41. package/src/supervise.mjs +5 -7
  42. package/src/task-id.mjs +29 -4
  43. package/src/thread-id.mjs +5 -5
  44. package/src/unknown-options.mjs +17 -0
  45. package/src/wrapper.mjs +2 -2
package/src/cli.mjs CHANGED
@@ -6,7 +6,7 @@ import { listLocalThreads } from "./thread-store.mjs";
6
6
  import { sendTrackedMessage, listMessages, getMessage, messageSummary, reconcileMessages, markMessage, listInstructions } from "./journal.mjs";
7
7
  import { launchDesktopUpdate, desktopUpdateStatus } from "./update.mjs";
8
8
  import { appServerDoctor, startDesktop } from "./launcher.mjs";
9
- import { VERSION, helpData, renderHelp } from "./help.mjs";
9
+ import { VERSION, helpData, renderHelp, renderErrors, errorRemedy, topicFor } from "./help.mjs";
10
10
  import { observeThread, printObservation } from "./observe.mjs";
11
11
  import { monitorCommand } from "./monitor.mjs";
12
12
  import { notificationOptions } from "./notify.mjs";
@@ -15,6 +15,8 @@ import { acquireResource, resourceStatus, renewResource, releaseResource, listRe
15
15
  import { playSendSound } from "./sound.mjs";
16
16
  import { prepareSupervisorPrompt, superviseAgent } from "./supervise.mjs";
17
17
  import { prepareOrchestrationPrompt, orchestrateAgent } from "./orchestrate.mjs";
18
+ import { rejectRepeatedBinding } from "./prompt-command.mjs";
19
+ import { rejectUnknownOptions } from "./unknown-options.mjs";
18
20
  import { supervisorId, registerSupervisor, getSupervisor, assertSupervisor, assertSupervisorRegistration, controlSupervisor, supervisorStatus, listSupervisors } from "./supervision.mjs";
19
21
  import { createFinding, findingStatus, listFindings, resolveFinding, dismissFinding, reopenFinding, evaluateFinding, findingStats } from "./findings.mjs";
20
22
  import { connectionMode, observationConnection, desktopReadOnly } from "./connection.mjs";
@@ -34,11 +36,13 @@ function success(command, data, json) {
34
36
  return data;
35
37
  }
36
38
 
37
- export function fail(error, json) {
39
+ export function fail(error, json, argv = []) {
38
40
  const message = error instanceof Error ? error.message : String(error);
41
+ const remedy = errorRemedy(argv, error.code);
39
42
  if (json) console.log(JSON.stringify({ ok: false, ...(error.watch ? { command: "watch", data: error.watch } : {}), error: {
40
- message, code: error.code, ...hostFields(), delivery_status: error.delivery_status,
43
+ message, code: error.code, ...remedy, ...hostFields(), delivery_status: error.delivery_status,
41
44
  operation: error.operation, capability: error.capability,
45
+ expected_connection: error.expected_connection, connection: error.connection,
42
46
  ...((error.execution_environment === "cloud" || (typeof error.code === "string" && error.code.startsWith("CLOUD_"))) ? {
43
47
  execution_environment: error.execution_environment, task_id: error.task_id, url: error.url,
44
48
  submission_status: error.submission_status, uncertain: error.uncertain,
@@ -47,7 +51,7 @@ export function fail(error, json) {
47
51
  } : {}),
48
52
  sent: error.sent, thread_id: error.thread_id, rpc_code: error.rpc_code ?? error.rpcCode,
49
53
  message_id: error.message_id, client_message_id: error.client_message_id, journal_update_required: error.journal_update_required,
50
- existing_message_id: error.existing_message_id,
54
+ existing_message_id: error.existing_message_id, unknown_options: error.unknown_options,
51
55
  existing_attempt_id: error.existing_attempt_id, existing_receipt_path: error.existing_receipt_path,
52
56
  supervision_update_required: error.supervision_update_required,
53
57
  history_changed: error.history_changed, backup_path: error.backup_path, receipt_path: error.receipt_path,
@@ -62,10 +66,12 @@ export function fail(error, json) {
62
66
  } }));
63
67
  else {
64
68
  console.error(`codexteer: ${message}`);
69
+ for (const line of remedy?.usage ?? []) console.error(`使い方: codexteer ${line}`);
70
+ if (remedy?.action) console.error(`対処: ${remedy.action}`);
65
71
  if (error.execution_environment === "cloud" || (typeof error.code === "string" && error.code.startsWith("DESKTOP_CLOUD_"))) {
66
72
  // Recovery metadata must survive an uncertain submission or a stale
67
73
  // on-disk receipt. Never print the child error, prompt, or diagnostics.
68
- for (const key of ["creation_status", "submission_status", "delivery_status", "attempt_id", "receipt_path", "existing_attempt_id", "existing_receipt_path", "task_id", "thread_id", "url"]) {
74
+ for (const key of ["creation_status", "submission_status", "delivery_status", "attempt_id", "receipt_path", "existing_message_id", "existing_attempt_id", "existing_receipt_path", "task_id", "thread_id", "url"]) {
69
75
  if (typeof error[key] === "string") console.error(`${key}: ${JSON.stringify(error[key])}`);
70
76
  }
71
77
  if (error.receipt_update_required) console.error("receipt_update_required: true");
@@ -76,11 +82,13 @@ export function fail(error, json) {
76
82
  process.exitCode = 1;
77
83
  }
78
84
 
85
+ const usage = message => Object.assign(new Error(message), { code: "OPTIONS_INVALID" });
86
+
79
87
  function takeOption(args, name, defaultValue) {
80
88
  const index = args.indexOf(name);
81
89
  if (index === -1 || (args.includes("--") && index > args.indexOf("--"))) return defaultValue;
82
90
  const value = args[index + 1];
83
- if (value == null || value.startsWith("--")) throw new Error(`${name} requires a value.`);
91
+ if (value == null || value.startsWith("--")) throw usage(`${name} requires a value.`);
84
92
  args.splice(index, 2);
85
93
  return value;
86
94
  }
@@ -123,7 +131,7 @@ function readMessage(parts) {
123
131
 
124
132
  function backendOption(args, fallback) {
125
133
  const backend = takeOption(args, "--backend", fallback);
126
- if (backend != null && !["app-server", "ui"].includes(backend)) throw new Error("--backend must be app-server or ui.");
134
+ if (backend != null && !["app-server", "ui"].includes(backend)) throw usage("--backend must be app-server or ui.");
127
135
  return backend;
128
136
  }
129
137
 
@@ -159,9 +167,9 @@ function printSupervisor(data) {
159
167
  export async function main(argv) {
160
168
  const args = [...argv];
161
169
  try {
170
+ rejectRepeatedBinding(args);
162
171
  const transport = validateRemoteTransport(takeOption(args, "--remote-transport", "native"));
163
172
  const explicitHost = takeOption(args, "--host", undefined);
164
- if (args.slice(0, args.includes("--") ? args.indexOf("--") : args.length).includes("--host")) throw new Error("Specify --host only once.");
165
173
  if (explicitHost !== undefined) validateHostId(explicitHost);
166
174
  const probe = [...args];
167
175
  takeFlag(probe, "--json");
@@ -182,7 +190,7 @@ export async function main(argv) {
182
190
  const ownArguments = probe.slice(0, probe.includes("--") ? probe.indexOf("--") : probe.length);
183
191
  if (isControlTransport(transport) && host === "local" && command !== "remote" && !["help", "--help", "--version"].some(value => ownArguments.includes(value))) throw Object.assign(new Error("--remote-transport control requires a remote hostId link or --host."), { code: "REMOTE_HOST_REQUIRED" });
184
192
  return await withTarget(host, contextThread, () => mainOnTarget(args), { transport });
185
- } catch (error) { fail(error, argv.includes("--json")); }
193
+ } catch (error) { fail(error, argv.includes("--json"), argv); }
186
194
  }
187
195
 
188
196
  async function mainOnTarget(argv) {
@@ -196,7 +204,7 @@ async function mainOnTarget(argv) {
196
204
  const connectionDependencies = observationConnection(connection);
197
205
  const requiredNode = takeOption(args, "--require-node-version", undefined);
198
206
  if (requiredNode !== undefined) {
199
- if (!/^v\d+\.\d+\.\d+(?:-[\w.-]+)?(?:\+[\w.-]+)?$/.test(requiredNode)) throw new Error("--require-node-version requires a Node version such as v25.1.0.");
207
+ if (!/^v\d+\.\d+\.\d+(?:-[\w.-]+)?(?:\+[\w.-]+)?$/.test(requiredNode)) throw usage("--require-node-version requires a Node version such as v25.1.0.");
200
208
  if (requiredNode !== process.version) throw Object.assign(new Error(`This supervision command requires Node ${requiredNode}, but is running ${process.version}. Regenerate the supervisor prompt with the intended Node runtime.`), { code: "NODE_VERSION_MISMATCH" });
201
209
  }
202
210
  if (takeFlag(args, "--version")) {
@@ -206,16 +214,15 @@ async function mainOnTarget(argv) {
206
214
  }
207
215
  if (args.length === 0 || takeFlag(args, "--help") || args[0] === "help") {
208
216
  if (args[0] === "help") args.shift();
209
- const candidate = args[0] ?? "overview";
210
- const topic = ["supervise", "orchestrate"].includes(candidate) && args[1] === "prompt" ? `${candidate} prompt`
211
- : /^(codex:\/\/|[0-9a-f]{8}-)/i.test(candidate) ? "send" : candidate;
212
- const data = helpData(topic);
217
+ const errors = takeFlag(args, "--errors");
218
+ const data = helpData(topicFor(args) ?? args[0] ?? "overview", { errors });
213
219
  success("help", data, json);
214
- if (!json) console.log(renderHelp(data));
220
+ if (!json) console.log(errors ? renderErrors(data) : renderHelp(data));
215
221
  return;
216
222
  }
217
223
 
218
224
  let command = args.shift();
225
+ if (command === "diff") throw usage("diff reads a Web Cloud task's recorded patch and is not available for local or remote Desktop tasks. Use read --include-output to inspect file changes.");
219
226
  const control = isControlTransport();
220
227
  if (targetHost() !== "local" && ["desktop", "debug-ui"].includes(command)) throw Object.assign(new Error("Desktop process/update and Accessibility diagnostics cannot target the remote host. No local operation was run."), { code: "REMOTE_HOST_OPERATION_UNSUPPORTED" });
221
228
  if (targetHost() !== "local" && !["remote", "projects", "read", "status", "watch", "doctor", "supervise", "findings", "history", "instructions", "thread", "open", ...(control ? ["send"] : []), ...(remoteTransport() === "paired" ? ["threads", "resource", "checkpoint", "orchestrate"] : [])].includes(command)
@@ -229,14 +236,14 @@ async function mainOnTarget(argv) {
229
236
  if (command === "remote") {
230
237
  if (supervisor) throw Object.assign(new Error("A bound supervisor cannot enroll, pair or manage account credentials."), { code: "SUPERVISOR_MISMATCH" });
231
238
  const action = args.shift(), codeStdin = takeFlag(args, "--code-stdin"), provider = takeOption(args, "--key-provider", undefined);
232
- if (provider !== undefined && action !== "prepare") throw new Error("--key-provider is only available for remote prepare.");
233
- if (!["prepare", "recover-key", "enroll", "status", "identity", "pair", "verify"].includes(action) || args.length || (codeStdin && action !== "pair") || (action === "pair" && !codeStdin)) throw new Error("Expected: remote prepare|recover-key|enroll|status|identity|verify, or --host HOST remote pair --code-stdin.");
234
- if (["prepare", "enroll"].includes(action) && targetHost() !== "local") throw new Error("remote prepare/enroll registers this local controller; omit --host.");
239
+ if (provider !== undefined && action !== "prepare") throw usage("--key-provider is only available for remote prepare.");
240
+ if (!["prepare", "recover-key", "enroll", "status", "identity", "pair", "verify"].includes(action) || args.length || (codeStdin && action !== "pair") || (action === "pair" && !codeStdin)) throw usage("Expected: remote prepare|recover-key|enroll|status|identity|verify, or --host HOST remote pair --code-stdin.");
241
+ if (["prepare", "enroll"].includes(action) && targetHost() !== "local") throw usage("remote prepare/enroll registers this local controller; omit --host.");
235
242
  // Code input is never an argv option or a chat message.
236
- if (action === "pair" && !/^remote-control:env_[A-Za-z0-9_-]+$/.test(targetHost())) throw new Error("remote pair requires --host remote-control:env_...");
243
+ if (action === "pair" && !/^remote-control:env_[A-Za-z0-9_-]+$/.test(targetHost())) throw Object.assign(new Error("remote pair requires --host remote-control:env_..."), { code: "REMOTE_PAIR_INPUT_INVALID" });
237
244
  if (action === "pair" && process.stdin.isTTY) console.error("codexteer: Enter the selected host's fresh pairing code (hidden), then press Enter.");
238
245
  const code = action === "pair" ? await readPairingCode() : undefined;
239
- if (code && code.length > 128) throw new Error("Invalid pairing code input.");
246
+ if (code && code.length > 128) throw Object.assign(new Error("Invalid pairing code input."), { code: "REMOTE_PAIR_INPUT_INVALID" });
240
247
  const data = await withDeviceAccount(context => action === "prepare" ? prepareDevice(context, { ...(provider ? { provider } : {}) }) : action === "recover-key" ? recoverDeviceKey(context) : action === "enroll" ? enrollDevice(context, { progress: text => console.error(`codexteer: ${text}`) })
241
248
  : action === "pair" ? pairDevice(context, targetHost(), code)
242
249
  : action === "verify" ? verifyDevice(context) : action === "identity" ? deviceIdentityInfo(context) : readDevice(context.home, context.identity).then(deviceStatus));
@@ -244,12 +251,13 @@ async function mainOnTarget(argv) {
244
251
  }
245
252
  if (command === "supervise" && ["register", "status", "list", "pause", "resume", "stop"].includes(args[0])) {
246
253
  const action = args.shift(), owner = action === "register" ? takeOption(args, "--owner", "external") : undefined;
247
- if (args.length !== (action === "list" ? 0 : 1)) throw new Error(`Expected: supervise ${action}${action === "list" ? "" : " <THREAD>"}.`);
254
+ if (args.length !== (action === "list" ? 0 : 1)) throw usage(`Expected: supervise ${action}${action === "list" ? "" : " <THREAD>"}.`);
248
255
  let data;
249
256
  if (action === "register") {
250
- if (!supervisor) throw new Error("supervise register requires the --supervisor ID from a generated prompt.");
257
+ if (!supervisor) throw usage("supervise register requires the --supervisor ID from a generated prompt.");
251
258
  data = await registerSupervisor(args[0], supervisor, { owner, connection });
252
259
  } else {
260
+ if (supervisor && action === "list") throw Object.assign(new Error("A bound supervisor cannot list other supervisor sessions. Use supervise status <THREAD> for its own task, or run supervise list from the user's terminal without --supervisor."), { code: "SUPERVISOR_CONTROL_FORBIDDEN" });
253
261
  if (supervisor && action !== "status") throw Object.assign(new Error("Supervisors cannot change their own control state. Run this control command from the user's terminal without --supervisor."), { code: "SUPERVISOR_CONTROL_FORBIDDEN" });
254
262
  if (supervisor) {
255
263
  const record = await getSupervisor(args[0]);
@@ -280,7 +288,7 @@ async function mainOnTarget(argv) {
280
288
  else if (action === "list") options = { all: takeFlag(args, "--all"), limit: Number(takeOption(args, "--limit", "50")) };
281
289
  const handlers = { create: createFinding, show: findingStatus, list: listFindings, resolve: resolveFinding, dismiss: dismissFinding, reopen: reopenFinding, evaluate: evaluateFinding, stats: findingStats };
282
290
  const single = ["create", "list", "stats"].includes(action);
283
- if (!Object.hasOwn(handlers, action) || args.length !== (single ? 1 : 2) || args.some(arg => arg.startsWith("--"))) throw new Error("See codexteer help findings.");
291
+ if (!Object.hasOwn(handlers, action) || args.length !== (single ? 1 : 2) || args.some(arg => arg.startsWith("--"))) throw usage("See codexteer help findings.");
284
292
  if (supervisor) await assertSupervisor(args[0], supervisor, { connection });
285
293
  data = single ? await handlers[action](args[0], options) : await handlers[action](args[0], args[1], options);
286
294
  success(`findings.${action}`, data, json);
@@ -291,7 +299,7 @@ async function mainOnTarget(argv) {
291
299
  if (supervisor) throw Object.assign(new Error("Managed supervision is bound to its existing task and cannot orchestrate a project."), { code: "SUPERVISOR_CONTROL_FORBIDDEN" });
292
300
  const promptOnly = args[0] === "prompt";
293
301
  if (promptOnly) args.shift();
294
- else if (json) throw new Error("orchestrate inherits agent output and does not support --json. Use help orchestrate --json or orchestrate prompt --project ID|NAME <GOAL> --json.");
302
+ else if (json) throw usage("orchestrate inherits agent output and does not support --json. Use help orchestrate --json or orchestrate prompt --project ID|NAME <GOAL> --json.");
295
303
  const separator = promptOnly ? -1 : args.indexOf("--");
296
304
  const ownArgs = separator < 0 ? args : args.slice(0, separator);
297
305
  const agentArgs = separator < 0 ? [] : args.slice(separator + 1);
@@ -299,8 +307,8 @@ async function mainOnTarget(argv) {
299
307
  const agent = takeOption(ownArgs, "--agent", promptOnly ? undefined : "claude");
300
308
  const usePathCli = takeFlag(ownArgs, "--use-path-cli");
301
309
  if (promptOnly && ownArgs[0] === "--") ownArgs.shift();
302
- else if (ownArgs.some(arg => arg.startsWith("--"))) throw new Error("Unknown orchestrate option. See help orchestrate; agent arguments belong after --.");
303
- if (ownArgs.length !== 1) throw new Error("Expected: orchestrate [prompt] --project <ID-or-exact-name> <GOAL>. Quote the goal as one argument, or use - for stdin.");
310
+ else rejectUnknownOptions(ownArgs, "orchestrate", "See help orchestrate; agent arguments belong after --.", usage);
311
+ if (ownArgs.length !== 1) throw usage("Expected: orchestrate [prompt] --project <ID-or-exact-name> <GOAL>. Quote the goal as one argument, or use - for stdin.");
304
312
  const goal = ownArgs[0] === "-" ? readFileSync(0, "utf8") : ownArgs[0];
305
313
  if (promptOnly) {
306
314
  const data = await prepareOrchestrationPrompt(project, goal, { agent, connection, usePathCli });
@@ -313,47 +321,49 @@ async function mainOnTarget(argv) {
313
321
  args.shift();
314
322
  const agent = takeOption(args, "--agent", "claude");
315
323
  const usePathCli = takeFlag(args, "--use-path-cli");
316
- if (args.length < 1 || args.length > 2 || args.some(arg => arg.startsWith("--"))) throw new Error("Expected: supervise prompt <THREAD> [MESSAGE]. See codexteer help supervise prompt.");
317
- if (supervisor) throw new Error("A supervisor cannot generate a replacement session.");
324
+ if (args.length < 1 || args.length > 2 || args.some(arg => arg.startsWith("--"))) throw usage("Expected: supervise prompt <THREAD> [MESSAGE]. See codexteer help supervise prompt.");
325
+ if (supervisor) throw Object.assign(new Error("A supervisor cannot generate a replacement session."), { code: "SUPERVISOR_CONTROL_FORBIDDEN" });
318
326
  const data = await prepareSupervisorPrompt(args[0], args[1], { agent, connection, usePathCli });
319
327
  success("supervise.prompt", data, json);
320
328
  if (!json) console.log(data.prompt);
321
329
  return;
322
330
  }
323
331
  if (command === "supervise") {
324
- if (supervisor) throw new Error("A supervisor cannot start a replacement session.");
325
- if (json) throw new Error("supervise inherits agent output and does not support --json. Use help supervise --json or supervise prompt <THREAD> --json.");
332
+ if (supervisor) throw Object.assign(new Error("A supervisor cannot start a replacement session."), { code: "SUPERVISOR_CONTROL_FORBIDDEN" });
333
+ if (json) throw usage("supervise inherits agent output and does not support --json. Use help supervise --json or supervise prompt <THREAD> --json.");
326
334
  const separator = args.indexOf("--");
327
335
  const ownArgs = separator < 0 ? args : args.slice(0, separator);
328
336
  const agentArgs = separator < 0 ? [] : args.slice(separator + 1);
329
337
  const agent = takeOption(ownArgs, "--agent", "claude");
330
338
  const usePathCli = takeFlag(ownArgs, "--use-path-cli");
331
- if (ownArgs.length < 1 || ownArgs.length > 2 || ownArgs.some(arg => arg.startsWith("--"))) throw new Error("Expected: supervise <THREAD> [MESSAGE] [--agent claude|codex] [--use-path-cli] [-- <AGENT-ARGS...>].");
339
+ if (ownArgs.length < 1 || ownArgs.length > 2 || ownArgs.some(arg => arg.startsWith("--"))) throw usage("Expected: supervise <THREAD> [MESSAGE] [--agent claude|codex] [--use-path-cli] [-- <AGENT-ARGS...>].");
332
340
  process.exitCode = await superviseAgent(ownArgs[0], { agent, agentArgs, policy: ownArgs[1], connection, usePathCli });
333
341
  return;
334
342
  }
335
343
  if (command === "resource") {
336
- const action = args.shift(); let data;
344
+ const threadId = takeOption(args, "--thread", undefined), action = args.shift(); let data;
345
+ if (supervisor && threadId === undefined) throw Object.assign(new Error("A supervisor resource command requires its --thread target."), { code: "SUPERVISOR_CONTROL_FORBIDDEN" });
346
+ // Like read/watch, inspecting leases and giving one up stay available while intervention is paused.
347
+ if (supervisor) await assertSupervisor(threadId, supervisor, { connection, sending: ["acquire", "renew", "run"].includes(action) });
337
348
  if (["acquire", "run"].includes(action)) {
338
- const options = { owner: takeOption(args, "--owner", undefined), ttlMs: Number(takeOption(args, "--ttl-ms", "600000")), reason: takeOption(args, "--reason", ""), condition: takeOption(args, "--condition", ""), threadId: takeOption(args, "--thread", undefined), cwd: takeOption(args, "--cwd", undefined) };
349
+ const options = { owner: takeOption(args, "--owner", undefined), ttlMs: Number(takeOption(args, "--ttl-ms", "600000")), reason: takeOption(args, "--reason", ""), condition: takeOption(args, "--condition", ""), threadId, cwd: takeOption(args, "--cwd", undefined) };
339
350
  if (action === "run") {
340
351
  const timeoutMs = Number(takeOption(args, "--timeout-ms", "0")), includeOutput = takeFlag(args, "--include-output"), execBackend = takeOption(args, "--exec-backend", "app-server"), remoteShell = takeOption(args, "--shell", undefined);
341
- if (supervisor) await assertSupervisor(options.threadId, supervisor, { connection, sending: execBackend === "thread-shell" });
342
- if (args[1] !== "--" || args.length < 3) throw new Error("Expected: resource run <NAME> --owner NAME -- <COMMAND> [ARGS...]");
352
+ if (args[1] !== "--" || args.length < 3) throw usage("Expected: resource run <NAME> --owner NAME -- <COMMAND> [ARGS...]");
343
353
  data = await runWithResource(args[0], args.slice(2), { ...options, timeoutMs, execBackend, remoteShell, supervisor, connection });
344
354
  if (!includeOutput) delete data.output_tail;
345
355
  if (!data.valid) process.exitCode = 1;
346
356
  } else {
347
- if (args.length !== 1) throw new Error("Expected: resource acquire <NAME> --owner NAME.");
357
+ if (args.length !== 1) throw usage("Expected: resource acquire <NAME> --owner NAME.");
348
358
  data = await acquireResource(args[0], options);
349
359
  }
350
360
  } else if (["renew", "release"].includes(action)) {
351
361
  const token = takeOption(args, "--token", undefined), ttlMs = action === "renew" ? Number(takeOption(args, "--ttl-ms", "600000")) : undefined;
352
- if (args.length !== 1) throw new Error(`Expected: resource ${action} <NAME> --token TOKEN.`);
362
+ if (args.length !== 1) throw usage(`Expected: resource ${action} <NAME> --token TOKEN.`);
353
363
  data = action === "renew" ? await renewResource(args[0], token, { ttlMs }) : await releaseResource(args[0], token);
354
364
  } else if (action === "status" && args.length === 1) data = await resourceStatus(args[0]);
355
365
  else if (action === "list" && args.length === 0) data = await listResources();
356
- else throw new Error("See codexteer help resource.");
366
+ else throw usage("See codexteer help resource.");
357
367
  success(`resource.${action}`, data, json);
358
368
  if (!json) console.log(JSON.stringify(data, null, 2));
359
369
  return;
@@ -364,27 +374,27 @@ async function mainOnTarget(argv) {
364
374
  let data;
365
375
  if (action === "capture") {
366
376
  const paths = takeOptions(args, "--path"), excludes = takeOptions(args, "--exclude");
367
- if (args.length !== 2) throw new Error("Expected: checkpoint capture <THREAD> <NAME> --path PATH ...");
377
+ if (args.length !== 2) throw usage("Expected: checkpoint capture <THREAD> <NAME> --path PATH ...");
368
378
  data = await createCheckpoint(args[0], args[1], { paths, excludes });
369
379
  } else if (action === "run") {
370
380
  const timeoutMs = Number(takeOption(args, "--timeout-ms", "0")), execBackend = takeOption(args, "--exec-backend", "app-server"), remoteShell = takeOption(args, "--shell", undefined);
371
381
  if (supervisor && execBackend === "thread-shell") await assertSupervisor(args[0], supervisor, { connection, sending: true });
372
- if (args[2] !== "--" || args.length < 4) throw new Error("Expected: checkpoint run <THREAD> <ID> -- <COMMAND> [ARGS...]");
382
+ if (args[2] !== "--" || args.length < 4) throw usage("Expected: checkpoint run <THREAD> <ID> -- <COMMAND> [ARGS...]");
373
383
  data = await runCheckpoint(args[0], args[1], args.slice(3), { timeoutMs, execBackend, remoteShell, supervisor, connection });
374
384
  if (!includeOutput) delete data.output_tail;
375
385
  if (!data.valid) process.exitCode = 1;
376
386
  } else if (action === "attach") {
377
387
  const refs = takeOptions(args, "--artifact");
378
- if (args.length !== 2) throw new Error("Expected: checkpoint attach <THREAD> <ID> --artifact FILE ...");
388
+ if (args.length !== 2) throw usage("Expected: checkpoint attach <THREAD> <ID> --artifact FILE ...");
379
389
  data = await attachArtifacts(args[0], args[1], refs);
380
390
  } else if (action === "list") {
381
- if (args.length !== 1) throw new Error("Expected: checkpoint list <THREAD>.");
391
+ if (args.length !== 1) throw usage("Expected: checkpoint list <THREAD>.");
382
392
  data = await listCheckpoints(args[0]);
383
393
  } else if (["show", "verify"].includes(action)) {
384
- if (args.length !== 2) throw new Error(`Expected: checkpoint ${action} <THREAD> <ID>.`);
394
+ if (args.length !== 2) throw usage(`Expected: checkpoint ${action} <THREAD> <ID>.`);
385
395
  data = action === "verify" ? await verifyCheckpoint(args[0], args[1]) : checkpointSummary(await getCheckpoint(args[0], args[1]), includeOutput);
386
396
  if (action === "verify" && !data.valid) process.exitCode = 1;
387
- } else throw new Error("See codexteer help checkpoint.");
397
+ } else throw usage("See codexteer help checkpoint.");
388
398
  success(`checkpoint.${action}`, data, json);
389
399
  if (!json) console.log(JSON.stringify(data, null, 2));
390
400
  return;
@@ -393,18 +403,18 @@ async function mainOnTarget(argv) {
393
403
  const action = args.shift();
394
404
  if (action === "list") {
395
405
  const all = takeFlag(args, "--all");
396
- if (args.length !== 1) throw new Error("Expected: instructions list <THREAD> [--all].");
406
+ if (args.length !== 1) throw usage("Expected: instructions list <THREAD> [--all].");
397
407
  if (supervisor) await assertSupervisor(args[0], supervisor, { connection });
398
408
  const data = await listInstructions(args[0], { all });
399
409
  success("instructions.list", data, json);
400
410
  if (!json) { for (const entry of data.instructions) console.log(`${entry.id} ${entry.instruction_status} ${entry.metadata?.source ?? "unspecified"}\n${entry.body}`); if (data.pending_changes.length) console.log(`配送未確認: ${data.pending_changes.map(e => e.id).join(", ")}`); }
401
411
  return;
402
412
  }
403
- if (action !== "retract") throw new Error("See codexteer help instructions.");
413
+ if (action !== "retract") throw usage("See codexteer help instructions.");
404
414
  if (connection === "desktop") throw desktopReadOnly();
405
415
  const reason = takeOption(args, "--reason", undefined), source = takeOption(args, "--source", undefined), basedOn = takeOption(args, "--based-on", undefined);
406
416
  const dryRun = takeFlag(args, "--dry-run"), newTurn = takeFlag(args, "--new-turn");
407
- if (args.length !== 2 || !reason?.trim()) throw new Error("Expected: instructions retract <THREAD> <MESSAGE-ID> --reason TEXT.");
417
+ if (args.length !== 2 || !reason?.trim()) throw usage("Expected: instructions retract <THREAD> <MESSAGE-ID> --reason TEXT.");
408
418
  const data = await sendTrackedMessage(args[0], reason, { retracts: args[1], source, basedOn, dryRun, newTurn, supervisor, connection });
409
419
  success("instructions.retract", data, json);
410
420
  if (!json) console.log(dryRun ? "撤回の送信プレビューです。" : `撤回を受け付けました。message_id: ${data.message_id}`);
@@ -416,7 +426,7 @@ async function mainOnTarget(argv) {
416
426
  const pending = takeFlag(args, "--pending");
417
427
  const report = action === "mark" ? { status: takeOption(args, "--status", undefined), note: takeOption(args, "--note", undefined), evidence: takeOptions(args, "--evidence"), by: takeOption(args, "--by", "local") } : {};
418
428
  const [threadId, id] = args;
419
- if (!threadId || args.length > 2 || !["list", "show", "check", "mark"].includes(action) || (["show", "mark"].includes(action) && !id) || (action === "list" && id)) throw new Error("See codexteer help history.");
429
+ if (!threadId || args.length > 2 || !["list", "show", "check", "mark"].includes(action) || (["show", "mark"].includes(action) && !id) || (action === "list" && id)) throw usage("See codexteer help history.");
420
430
  if (supervisor) await assertSupervisor(threadId, supervisor, { connection });
421
431
  const data = action === "list" ? await listMessages(threadId, { includeText, pending }) : action === "show" ? messageSummary(await getMessage(threadId, id), includeText) : action === "check" ? await reconcileMessages(threadId, id, { includeText }, connectionDependencies) : await markMessage(threadId, id, report);
422
432
  success(`history.${action}`, data, json);
@@ -437,14 +447,14 @@ async function mainOnTarget(argv) {
437
447
  if (stream) json = true;
438
448
  if (command === "watch") {
439
449
  const until = takeOption(args, "--until", undefined), timeout = takeOption(args, "--timeout-ms", undefined);
440
- if (stream && (until !== undefined || timeout !== undefined)) throw new Error("--stream runs until cancelled; do not combine it with --until or --timeout-ms. See codexteer help monitor.");
450
+ if (stream && (until !== undefined || timeout !== undefined)) throw usage("--stream runs until cancelled; do not combine it with --until or --timeout-ms. See codexteer help monitor.");
441
451
  const notify = takeOption(args, "--notify", stream ? "digest" : "all"), settle = takeOption(args, "--settle-ms", undefined), maxHold = takeOption(args, "--max-hold-ms", undefined);
442
- if (!stream && (notify === "digest" || settle !== undefined || maxHold !== undefined)) throw new Error("--notify digest, --settle-ms and --max-hold-ms require --stream.");
443
- if (notify !== "digest" && (settle !== undefined || maxHold !== undefined)) throw new Error("--settle-ms and --max-hold-ms require --notify digest.");
452
+ if (!stream && (notify === "digest" || settle !== undefined || maxHold !== undefined)) throw usage("--notify digest, --settle-ms and --max-hold-ms require --stream.");
453
+ if (notify !== "digest" && (settle !== undefined || maxHold !== undefined)) throw usage("--settle-ms and --max-hold-ms require --notify digest.");
444
454
  Object.assign(options, notificationOptions({ notify, ...(settle === undefined ? {} : { settleMs: Number(settle) }), ...(maxHold === undefined ? {} : { maxHoldMs: Number(maxHold) }) }));
445
455
  Object.assign(options, { watch: true, until: until ?? "change", timeoutMs: Number(timeout ?? "30000"), pollMs: Number(takeOption(args, "--poll-ms", "1000")) });
446
456
  }
447
- if (args.length !== 1) throw new Error(`Expected: ${command} <THREAD>. See codexteer help ${command}.`);
457
+ if (args.length !== 1) throw usage(`Expected: ${command} <THREAD>. See codexteer help ${command}.`);
448
458
  if (stream) { await monitorCommand(args[0], options, connectionDependencies); return; }
449
459
  const data = await observeThread(args[0], options, connectionDependencies);
450
460
  data.connection_mode = connection;
@@ -457,10 +467,10 @@ async function mainOnTarget(argv) {
457
467
  const backend = backendOption(args, "app-server");
458
468
  if (connection === "desktop" && backend === "ui") throw desktopReadOnly();
459
469
  const target = takeOption(args, "--thread", undefined);
460
- if (target !== undefined && backend !== "app-server") throw new Error("doctor --thread requires --backend app-server.");
470
+ if (target !== undefined && backend !== "app-server") throw usage("doctor --thread requires --backend app-server.");
461
471
  const threadId = target === undefined ? undefined : normalizeThreadId(target);
462
472
  if (supervisor && threadId) await assertSupervisorRegistration(threadId, supervisor, { connection });
463
- if (args.length) throw new Error(`Unexpected argument: ${args[0]}`);
473
+ if (args.length) throw usage(`Unexpected argument: ${args[0]}`);
464
474
  if (targetHost() !== "local" && backend === "ui") throw remoteReadOnly();
465
475
  const report = backend === "ui" ? desktopDoctor() : await appServerDoctor({ threadId, ...connectionDependencies });
466
476
  if (report.connection) Object.assign(report.connection, { mode: connection, observation_only: connection === "desktop" || (targetHost() !== "local" && !control), ...hostFields() });
@@ -492,7 +502,7 @@ async function mainOnTarget(argv) {
492
502
  if (command === "desktop") {
493
503
  const action = args.shift();
494
504
  if (action === "update-status") {
495
- if (args.length) throw new Error(`Unexpected argument: ${args[0]}`);
505
+ if (args.length) throw usage(`Unexpected argument: ${args[0]}`);
496
506
  const data = await desktopUpdateStatus();
497
507
  success("desktop.update-status", data, json);
498
508
  if (!json) console.log(JSON.stringify(data, null, 2));
@@ -502,15 +512,15 @@ async function mainOnTarget(argv) {
502
512
  if (supervisor) throw Object.assign(new Error("A supervisor cannot update or restart Desktop. Run this command from the user's terminal without --supervisor."), { code: "SUPERVISOR_CONTROL_FORBIDDEN" });
503
513
  const dryRun = takeFlag(args, "--dry-run");
504
514
  const timeoutMs = Number(takeOption(args, "--timeout-ms", "600000"));
505
- if (args.length) throw new Error(`Unexpected argument: ${args[0]}`);
515
+ if (args.length) throw usage(`Unexpected argument: ${args[0]}`);
506
516
  const data = await launchDesktopUpdate({ dryRun, timeoutMs });
507
517
  success("desktop.update", data, json);
508
518
  if (!json) console.log(dryRun ? "Dry run: official update, relaunch, normal quit, wrapper startup, and verification." : `Update worker started (${data.worker_pid}). Check: ${data.status_command}`);
509
519
  return;
510
520
  }
511
- if (action !== "start") throw new Error("Expected: codexteer desktop start|update|update-status");
521
+ if (action !== "start") throw usage("Expected: codexteer desktop start|update|update-status");
512
522
  const dryRun = takeFlag(args, "--dry-run");
513
- if (args.length) throw new Error(`Unexpected argument: ${args[0]}`);
523
+ if (args.length) throw usage(`Unexpected argument: ${args[0]}`);
514
524
  const data = await startDesktop({ dryRun });
515
525
  success("desktop.start", data, json);
516
526
  if (!json) console.log(dryRun ? "Dry run: would launch Desktop with the shared App Server wrapper." : "Desktop shared App Server is ready.");
@@ -519,9 +529,9 @@ async function mainOnTarget(argv) {
519
529
 
520
530
  if (command === "projects") {
521
531
  if (connection === "desktop") throw desktopReadOnly();
522
- if (args.shift() !== "list") throw new Error("Expected: codexteer projects list [--remote | --all] [--host HOST-ID]");
532
+ if (args.shift() !== "list") throw usage("Expected: codexteer projects list [--remote | --all] [--host HOST-ID]");
523
533
  const remote = takeFlag(args, "--remote"), all = takeFlag(args, "--all");
524
- if (args.length || (remote && all) || (all && targetHost() !== "local")) throw new Error("Use projects list [--remote | --all] or projects list --host HOST-ID.");
534
+ if (args.length || (remote && all) || (all && targetHost() !== "local")) throw usage("Use projects list [--remote | --all] or projects list --host HOST-ID.");
525
535
  const saved = targetHost() !== "local" && remoteTransport() === "paired" ? await listRemoteProjects()
526
536
  : remote || all || targetHost() !== "local" ? await registeredRemoteProjects() : [];
527
537
  const projects = [...(!remote && (all || targetHost() === "local") ? await listProjects() : []),
@@ -532,15 +542,15 @@ async function mainOnTarget(argv) {
532
542
  }
533
543
 
534
544
  if (command === "threads") {
535
- if (args.shift() !== "list") throw new Error("Expected: codexteer threads list");
545
+ if (args.shift() !== "list") throw usage("Expected: codexteer threads list");
536
546
  const limit = Number.parseInt(takeOption(args, "--limit", "20"), 10);
537
547
  const desktopOnly = takeFlag(args, "--desktop-only");
538
548
  const cursor = takeOption(args, "--cursor", undefined), projectId = takeOption(args, "--project", undefined), search = takeOption(args, "--search", undefined);
539
- if (args.length > 0) throw new Error(`Unexpected argument: ${args[0]}`);
549
+ if (args.length > 0) throw usage(`Unexpected argument: ${args[0]}`);
540
550
  if (!Number.isInteger(limit) || limit < 1 || limit > 500) {
541
- throw new Error("--limit must be an integer between 1 and 500.");
551
+ throw usage("--limit must be an integer between 1 and 500.");
542
552
  }
543
- if (targetHost() === "local" && [cursor, projectId, search].some(value => value !== undefined)) throw new Error("--cursor/--project/--search currently require a paired remote host.");
553
+ if (targetHost() === "local" && [cursor, projectId, search].some(value => value !== undefined)) throw usage("--cursor/--project/--search currently require a paired remote host.");
544
554
  const data = targetHost() === "local" ? { threads: await listLocalThreads({ limit, desktopOnly }) } : await listRemoteThreads({ limit, desktopOnly, cursor, projectId, search });
545
555
  const threads = data.threads;
546
556
  success("threads.list", { ...data, count: threads.length }, json);
@@ -554,7 +564,7 @@ async function mainOnTarget(argv) {
554
564
  if (connection === "desktop") throw desktopReadOnly();
555
565
  if (supervisor) throw Object.assign(new Error("Managed supervision is bound to its existing task. Verify a project creation receipt without --supervisor."), { code: "SUPERVISOR_CONTROL_FORBIDDEN" });
556
566
  const timeoutMs = Number(takeOption(args, "--timeout-ms", "120000"));
557
- if (args.length !== 1 || args[0].startsWith("--")) throw new Error("Expected: thread verify <ATTEMPT-ID> [--timeout-ms N].");
567
+ if (args.length !== 1 || args[0].startsWith("--")) throw usage("Expected: thread verify <ATTEMPT-ID> [--timeout-ms N].");
558
568
  const data = await (targetHost() === "local" ? verifyProjectThread : verifyRemoteProjectThread)(args[0], { timeoutMs });
559
569
  success("thread.verify", data, json);
560
570
  if (!json) console.log(`Verified existing ${data.deep_link}; creation and initial instruction were not repeated.`);
@@ -572,7 +582,7 @@ async function mainOnTarget(argv) {
572
582
  options.timeoutMs = Number(options.timeoutMs);
573
583
  }
574
584
  const literal = args.indexOf("--");
575
- if (args.slice(0, literal < 0 ? args.length : literal).some(arg => arg.startsWith("--"))) throw new Error("Unknown thread create option. Use -- before an instruction starting with --.");
585
+ rejectUnknownOptions(args.slice(0, literal < 0 ? args.length : literal), "thread create", "Put an instruction that starts with -- after --.", usage);
576
586
  const message = readMessage(args);
577
587
  const data = await (targetHost() === "local" ? createProjectThread : createRemoteProjectThread)(selector, message, options);
578
588
  success("thread.create", data, json);
@@ -581,7 +591,7 @@ async function mainOnTarget(argv) {
581
591
  }
582
592
  if (action === "repair") {
583
593
  const apply = takeFlag(args, "--apply"), dryRun = takeFlag(args, "--dry-run");
584
- if (args.length !== 1 || args[0].startsWith("--") || (apply && dryRun)) throw new Error("Expected: thread repair <THREAD> [--dry-run | --apply].");
594
+ if (args.length !== 1 || args[0].startsWith("--") || (apply && dryRun)) throw usage("Expected: thread repair <THREAD> [--dry-run | --apply].");
585
595
  if (connection === "desktop") throw desktopReadOnly();
586
596
  if (supervisor) throw Object.assign(new Error("A supervisor cannot repair task history. Run this command from the user's terminal without --supervisor."), { code: "SUPERVISOR_CONTROL_FORBIDDEN" });
587
597
  const data = await repairTurns(args[0], { apply });
@@ -589,8 +599,8 @@ async function mainOnTarget(argv) {
589
599
  if (!json) console.log(JSON.stringify(data, null, 2));
590
600
  return;
591
601
  }
592
- if (action !== "resolve") throw new Error("Expected: codexteer thread create|verify|resolve|repair. See help thread.");
593
- if (args.length !== 1) throw new Error("thread resolve requires exactly one thread ID or URL.");
602
+ if (action !== "resolve") throw usage("Expected: codexteer thread create|verify|resolve|repair. See help thread.");
603
+ if (args.length !== 1) throw usage("thread resolve requires exactly one thread ID or URL.");
594
604
  const threadId = normalizeThreadId(args[0]);
595
605
  const data = { thread_id: threadId, deep_link: threadDeepLink(threadId) };
596
606
  success("thread.resolve", data, json);
@@ -599,7 +609,7 @@ async function mainOnTarget(argv) {
599
609
  }
600
610
 
601
611
  if (command === "open") {
602
- if (args.length !== 1) throw new Error("open requires exactly one thread ID or URL.");
612
+ if (args.length !== 1) throw usage("open requires exactly one thread ID or URL.");
603
613
  const threadId = normalizeThreadId(args[0]);
604
614
  const data = targetHost() === "local" ? openDesktopThread(threadId) : await ensureRemoteDesktopSubscription(threadId);
605
615
  success("open", data, json);
@@ -609,7 +619,7 @@ async function mainOnTarget(argv) {
609
619
 
610
620
  if (command === "debug-ui") {
611
621
  const waitMs = Number.parseInt(takeOption(args, "--wait-ms", "1500"), 10);
612
- if (args.length !== 1) throw new Error("debug-ui requires exactly one thread ID or URL.");
622
+ if (args.length !== 1) throw usage("debug-ui requires exactly one thread ID or URL.");
613
623
  const threadId = normalizeThreadId(args[0]);
614
624
  const data = inspectDesktopUi(threadId, { waitMs });
615
625
  success("debug-ui", data, json);
@@ -628,22 +638,23 @@ async function mainOnTarget(argv) {
628
638
  if (remoteTransport() !== "paired") throw remoteReadOnly();
629
639
  throw Object.assign(new Error("UI sending on the remote host is unsupported. No UI or other delivery route was used."), { code: "REMOTE_UI_UNSUPPORTED", sent: false, delivery_status: "not_sent" });
630
640
  }
631
- if (supervisor && backend !== "app-server") throw new Error("Managed supervision requires the app-server backend.");
641
+ if (supervisor && backend !== "app-server") throw usage("Managed supervision requires the app-server backend.");
632
642
  const explicitSound = takeFlag(args, "--sound");
633
643
  const noSound = takeFlag(args, "--no-sound");
634
- if (explicitSound && noSound) throw new Error("--sound and --no-sound cannot be combined.");
635
- if (explicitSound && backend !== "app-server") throw new Error("--sound requires --backend app-server so receipt can be confirmed.");
644
+ if (explicitSound && noSound) throw usage("--sound and --no-sound cannot be combined.");
645
+ if (explicitSound && backend !== "app-server") throw usage("--sound requires --backend app-server so receipt can be confirmed.");
636
646
  const newTurn = takeFlag(args, "--new-turn");
637
647
  const directive = { source: takeOption(args, "--source", undefined), kind: takeOption(args, "--kind", undefined), evidence: takeOptions(args, "--evidence"), basedOn: takeOption(args, "--based-on", undefined), supersedes: takeOption(args, "--supersedes", undefined), expiresAt: takeOption(args, "--expires-at", undefined), checkpoint: takeOption(args, "--checkpoint", undefined), finding: takeOption(args, "--finding", undefined) };
638
- if (backend === "ui" && (directive.source || directive.kind || directive.evidence.length || directive.basedOn || directive.supersedes || directive.expiresAt || directive.checkpoint || directive.finding)) throw new Error("Directive metadata requires --backend app-server.");
648
+ if (backend === "ui" && (directive.source || directive.kind || directive.evidence.length || directive.basedOn || directive.supersedes || directive.expiresAt || directive.checkpoint || directive.finding)) throw usage("Directive metadata requires --backend app-server.");
639
649
  const keepFocus = takeFlag(args, "--keep-focus");
640
650
  const waitInput = takeOption(args, "--wait-ms", undefined);
641
- if (backend !== "ui" && (keepFocus || waitInput != null)) throw new Error("--keep-focus and --wait-ms require --backend ui.");
651
+ if (backend !== "ui" && (keepFocus || waitInput != null)) throw usage("--keep-focus and --wait-ms require --backend ui.");
642
652
  const waitMs = Number(waitInput ?? "1500");
653
+ rejectUnknownOptions(args.slice(0, args.includes("--") ? args.indexOf("--") : args.length), "send", "Put instruction text that starts with -- after --.", usage);
643
654
  const threadInput = args.shift();
644
- if (!threadInput) throw new Error("send requires a thread ID or codex://threads URL.");
655
+ if (!threadInput) throw usage("send requires a thread ID or codex://threads URL.");
645
656
  const message = readMessage(args);
646
- if (message.trim() === "") throw new Error("send requires a non-empty message.");
657
+ if (message.trim() === "") throw usage("send requires a non-empty message.");
647
658
  const threadId = normalizeThreadId(threadInput);
648
659
  const data = backend === "app-server"
649
660
  ? await sendTrackedMessage(threadId, message, { dryRun, newTurn, ...directive, supervisor, connection })
@@ -660,6 +671,6 @@ async function mainOnTarget(argv) {
660
671
  if (!noSound && !dryRun && data.sent === true && !data.sound.played) console.error(`codexteer: Input was accepted, but the sound could not be played (${data.sound.reason}).`);
661
672
  }
662
673
  } catch (error) {
663
- fail(error, json);
674
+ fail(error, json, argv);
664
675
  }
665
676
  }