@claudexor/cli 3.1.1 → 3.2.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 (261) hide show
  1. package/dist/about-command.d.ts.map +1 -1
  2. package/dist/accounts-projection.d.ts +8 -1
  3. package/dist/accounts-projection.d.ts.map +1 -1
  4. package/dist/accounts-projection.js +33 -20
  5. package/dist/accounts-projection.js.map +1 -1
  6. package/dist/accounts-services.d.ts +206 -0
  7. package/dist/accounts-services.d.ts.map +1 -0
  8. package/dist/accounts-services.js +71 -0
  9. package/dist/accounts-services.js.map +1 -0
  10. package/dist/acp-surface-runner.d.ts +39 -0
  11. package/dist/acp-surface-runner.d.ts.map +1 -1
  12. package/dist/acp-surface-runner.js +104 -30
  13. package/dist/acp-surface-runner.js.map +1 -1
  14. package/dist/attachment-upload.d.ts +5 -0
  15. package/dist/attachment-upload.d.ts.map +1 -0
  16. package/dist/attachment-upload.js +64 -0
  17. package/dist/attachment-upload.js.map +1 -0
  18. package/dist/belt-bridge.d.ts +2 -0
  19. package/dist/belt-bridge.d.ts.map +1 -0
  20. package/dist/belt-bridge.js +31 -0
  21. package/dist/belt-bridge.js.map +1 -0
  22. package/dist/bridge-serve.d.ts +1 -9
  23. package/dist/bridge-serve.d.ts.map +1 -1
  24. package/dist/bridge-serve.js +5 -22
  25. package/dist/bridge-serve.js.map +1 -1
  26. package/dist/capabilities.d.ts.map +1 -1
  27. package/dist/capabilities.js +8 -1
  28. package/dist/capabilities.js.map +1 -1
  29. package/dist/choice-input.d.ts +7 -0
  30. package/dist/choice-input.d.ts.map +1 -0
  31. package/dist/choice-input.js +17 -0
  32. package/dist/choice-input.js.map +1 -0
  33. package/dist/claude-oauth-usage.js.map +1 -1
  34. package/dist/claudexord-entry.d.ts +11 -0
  35. package/dist/claudexord-entry.d.ts.map +1 -0
  36. package/dist/claudexord-entry.js +42 -0
  37. package/dist/claudexord-entry.js.map +1 -0
  38. package/dist/claudexord.d.ts +2 -17
  39. package/dist/claudexord.d.ts.map +1 -1
  40. package/dist/claudexord.js +60 -93
  41. package/dist/claudexord.js.map +1 -1
  42. package/dist/cli-error.d.ts.map +1 -1
  43. package/dist/cli-error.js +21 -11
  44. package/dist/cli-error.js.map +1 -1
  45. package/dist/cli.js +149 -170
  46. package/dist/cli.js.map +1 -1
  47. package/dist/codex-device-login.js.map +1 -1
  48. package/dist/codex-quota-source.js.map +1 -1
  49. package/dist/command-flags.d.ts +16 -1
  50. package/dist/command-flags.d.ts.map +1 -1
  51. package/dist/command-flags.js +41 -19
  52. package/dist/command-flags.js.map +1 -1
  53. package/dist/command-registry.d.ts +21 -8
  54. package/dist/command-registry.d.ts.map +1 -1
  55. package/dist/command-registry.js +59 -116
  56. package/dist/command-registry.js.map +1 -1
  57. package/dist/command-scope.d.ts +13 -0
  58. package/dist/command-scope.d.ts.map +1 -0
  59. package/dist/command-scope.js +61 -0
  60. package/dist/command-scope.js.map +1 -0
  61. package/dist/control-services.d.ts +1622 -982
  62. package/dist/control-services.d.ts.map +1 -1
  63. package/dist/control-services.js +39 -68
  64. package/dist/control-services.js.map +1 -1
  65. package/dist/credential-commands.d.ts.map +1 -1
  66. package/dist/credential-commands.js +13 -3
  67. package/dist/credential-commands.js.map +1 -1
  68. package/dist/daemon-lifecycle.d.ts.map +1 -1
  69. package/dist/daemon-outcome.d.ts +3 -5
  70. package/dist/daemon-outcome.d.ts.map +1 -1
  71. package/dist/daemon-outcome.js +7 -19
  72. package/dist/daemon-outcome.js.map +1 -1
  73. package/dist/daemon-run.d.ts +26 -29
  74. package/dist/daemon-run.d.ts.map +1 -1
  75. package/dist/daemon-run.js +89 -97
  76. package/dist/daemon-run.js.map +1 -1
  77. package/dist/daemon-runtime-shutdown.d.ts +7 -0
  78. package/dist/daemon-runtime-shutdown.d.ts.map +1 -1
  79. package/dist/daemon-runtime-shutdown.js +29 -0
  80. package/dist/daemon-runtime-shutdown.js.map +1 -1
  81. package/dist/delegation-belt-descriptor.d.ts +20 -36
  82. package/dist/delegation-belt-descriptor.d.ts.map +1 -1
  83. package/dist/delegation-belt-descriptor.js +48 -51
  84. package/dist/delegation-belt-descriptor.js.map +1 -1
  85. package/dist/delegation-capability.d.ts +4 -0
  86. package/dist/delegation-capability.d.ts.map +1 -0
  87. package/dist/delegation-capability.js +29 -0
  88. package/dist/delegation-capability.js.map +1 -0
  89. package/dist/delegation-daemon-binding.d.ts +8 -0
  90. package/dist/delegation-daemon-binding.d.ts.map +1 -0
  91. package/dist/delegation-daemon-binding.js +18 -0
  92. package/dist/delegation-daemon-binding.js.map +1 -0
  93. package/dist/delegation-output.d.ts +18 -0
  94. package/dist/delegation-output.d.ts.map +1 -0
  95. package/dist/delegation-output.js +47 -0
  96. package/dist/delegation-output.js.map +1 -0
  97. package/dist/harness-command.d.ts +13 -0
  98. package/dist/harness-command.d.ts.map +1 -0
  99. package/dist/harness-command.js +35 -0
  100. package/dist/harness-command.js.map +1 -0
  101. package/dist/harness-installer.d.ts +79 -0
  102. package/dist/harness-installer.d.ts.map +1 -0
  103. package/dist/harness-installer.js +256 -0
  104. package/dist/harness-installer.js.map +1 -0
  105. package/dist/interaction-prompt.d.ts +19 -0
  106. package/dist/interaction-prompt.d.ts.map +1 -0
  107. package/dist/interaction-prompt.js +114 -0
  108. package/dist/interaction-prompt.js.map +1 -0
  109. package/dist/live.d.ts +2 -6
  110. package/dist/live.d.ts.map +1 -1
  111. package/dist/live.js +92 -75
  112. package/dist/live.js.map +1 -1
  113. package/dist/mcp-daemon-unavailable.d.ts +6 -0
  114. package/dist/mcp-daemon-unavailable.d.ts.map +1 -0
  115. package/dist/mcp-daemon-unavailable.js +16 -0
  116. package/dist/mcp-daemon-unavailable.js.map +1 -0
  117. package/dist/mcp-run-projections.d.ts +42 -0
  118. package/dist/mcp-run-projections.d.ts.map +1 -0
  119. package/dist/mcp-run-projections.js +74 -0
  120. package/dist/mcp-run-projections.js.map +1 -0
  121. package/dist/mcp-runner.d.ts +19 -1
  122. package/dist/mcp-runner.d.ts.map +1 -1
  123. package/dist/mcp-runner.js +121 -115
  124. package/dist/mcp-runner.js.map +1 -1
  125. package/dist/native-login.js.map +1 -1
  126. package/dist/ops-command-specs.d.ts +64 -0
  127. package/dist/ops-command-specs.d.ts.map +1 -0
  128. package/dist/ops-command-specs.js +146 -0
  129. package/dist/ops-command-specs.js.map +1 -0
  130. package/dist/ops-commands.d.ts.map +1 -1
  131. package/dist/ops-commands.js +23 -5
  132. package/dist/ops-commands.js.map +1 -1
  133. package/dist/orphan-reaper.js.map +1 -1
  134. package/dist/output-mode.d.ts +11 -0
  135. package/dist/output-mode.d.ts.map +1 -0
  136. package/dist/output-mode.js +28 -0
  137. package/dist/output-mode.js.map +1 -0
  138. package/dist/plan-question-loop.d.ts.map +1 -1
  139. package/dist/plan-question-loop.js +16 -13
  140. package/dist/plan-question-loop.js.map +1 -1
  141. package/dist/plugins.js +1 -1
  142. package/dist/plugins.js.map +1 -1
  143. package/dist/primary-output.d.ts +9 -4
  144. package/dist/primary-output.d.ts.map +1 -1
  145. package/dist/primary-output.js +50 -2
  146. package/dist/primary-output.js.map +1 -1
  147. package/dist/quota-subject-universe.d.ts +6 -0
  148. package/dist/quota-subject-universe.d.ts.map +1 -0
  149. package/dist/quota-subject-universe.js +31 -0
  150. package/dist/quota-subject-universe.js.map +1 -0
  151. package/dist/registry.js.map +1 -1
  152. package/dist/release-command.d.ts +1 -1
  153. package/dist/release-command.js +2 -2
  154. package/dist/remote-command-specs.d.ts +45 -0
  155. package/dist/remote-command-specs.d.ts.map +1 -0
  156. package/dist/remote-command-specs.js +29 -0
  157. package/dist/remote-command-specs.js.map +1 -0
  158. package/dist/remote-command.d.ts +11 -0
  159. package/dist/remote-command.d.ts.map +1 -0
  160. package/dist/remote-command.js +262 -0
  161. package/dist/remote-command.js.map +1 -0
  162. package/dist/remote-filesystem.d.ts +33 -0
  163. package/dist/remote-filesystem.d.ts.map +1 -0
  164. package/dist/remote-filesystem.js +280 -0
  165. package/dist/remote-filesystem.js.map +1 -0
  166. package/dist/repo-asset-authority.js.map +1 -1
  167. package/dist/request-preflight.d.ts +11 -2
  168. package/dist/request-preflight.d.ts.map +1 -1
  169. package/dist/request-preflight.js +36 -13
  170. package/dist/request-preflight.js.map +1 -1
  171. package/dist/retry-command-specs.d.ts.map +1 -1
  172. package/dist/retry-command-specs.js +2 -0
  173. package/dist/retry-command-specs.js.map +1 -1
  174. package/dist/run-applicability.d.ts +74 -0
  175. package/dist/run-applicability.d.ts.map +1 -0
  176. package/dist/run-applicability.js +72 -0
  177. package/dist/run-applicability.js.map +1 -0
  178. package/dist/run-detail-projections.d.ts +39 -0
  179. package/dist/run-detail-projections.d.ts.map +1 -0
  180. package/dist/run-detail-projections.js +64 -0
  181. package/dist/run-detail-projections.js.map +1 -0
  182. package/dist/run-detail-response.d.ts +7 -0
  183. package/dist/run-detail-response.d.ts.map +1 -0
  184. package/dist/run-detail-response.js +23 -0
  185. package/dist/run-detail-response.js.map +1 -0
  186. package/dist/run-facts-projection.d.ts +22 -0
  187. package/dist/run-facts-projection.d.ts.map +1 -0
  188. package/dist/run-facts-projection.js +86 -0
  189. package/dist/run-facts-projection.js.map +1 -0
  190. package/dist/runtime-replacement-stop.d.ts +25 -0
  191. package/dist/runtime-replacement-stop.d.ts.map +1 -0
  192. package/dist/runtime-replacement-stop.js +145 -0
  193. package/dist/runtime-replacement-stop.js.map +1 -0
  194. package/dist/settings-command.d.ts +1 -0
  195. package/dist/settings-command.d.ts.map +1 -1
  196. package/dist/settings-command.js +27 -21
  197. package/dist/settings-command.js.map +1 -1
  198. package/dist/settings-service.d.ts +7 -7
  199. package/dist/settings-service.d.ts.map +1 -1
  200. package/dist/settings-service.js +3 -3
  201. package/dist/settings-service.js.map +1 -1
  202. package/dist/setup-client-pty.d.ts +14 -0
  203. package/dist/setup-client-pty.d.ts.map +1 -0
  204. package/dist/setup-client-pty.js +61 -0
  205. package/dist/setup-client-pty.js.map +1 -0
  206. package/dist/setup-device-code-disclosure.d.ts +15 -0
  207. package/dist/setup-device-code-disclosure.d.ts.map +1 -0
  208. package/dist/setup-device-code-disclosure.js +52 -0
  209. package/dist/setup-device-code-disclosure.js.map +1 -0
  210. package/dist/setup-job-reducer.d.ts.map +1 -1
  211. package/dist/setup-job-reducer.js +1 -0
  212. package/dist/setup-job-reducer.js.map +1 -1
  213. package/dist/setup-job-store.d.ts +275 -75
  214. package/dist/setup-job-store.d.ts.map +1 -1
  215. package/dist/setup-job-store.js.map +1 -1
  216. package/dist/setup-job-support.js.map +1 -1
  217. package/dist/setup-jobs.d.ts +517 -137
  218. package/dist/setup-jobs.d.ts.map +1 -1
  219. package/dist/setup-jobs.js +68 -118
  220. package/dist/setup-jobs.js.map +1 -1
  221. package/dist/setup-lifecycle-binding.d.ts +7 -0
  222. package/dist/setup-lifecycle-binding.d.ts.map +1 -1
  223. package/dist/setup-lifecycle-binding.js +6 -0
  224. package/dist/setup-lifecycle-binding.js.map +1 -1
  225. package/dist/setup-login-child-lifecycle.d.ts +13 -0
  226. package/dist/setup-login-child-lifecycle.d.ts.map +1 -0
  227. package/dist/setup-login-child-lifecycle.js +45 -0
  228. package/dist/setup-login-child-lifecycle.js.map +1 -0
  229. package/dist/setup-login-inline.d.ts +11 -12
  230. package/dist/setup-login-inline.d.ts.map +1 -1
  231. package/dist/setup-login-inline.js +26 -15
  232. package/dist/setup-login-inline.js.map +1 -1
  233. package/dist/setup-login-permit.d.ts +10 -0
  234. package/dist/setup-login-permit.d.ts.map +1 -0
  235. package/dist/setup-login-permit.js +52 -0
  236. package/dist/setup-login-permit.js.map +1 -0
  237. package/dist/setup-login-protocol.d.ts.map +1 -1
  238. package/dist/setup-login-protocol.js +9 -2
  239. package/dist/setup-login-protocol.js.map +1 -1
  240. package/dist/setup-login-runner.d.ts +1 -0
  241. package/dist/setup-login-runner.d.ts.map +1 -1
  242. package/dist/setup-login-runner.js +20 -31
  243. package/dist/setup-login-runner.js.map +1 -1
  244. package/dist/setup-supervisor.d.ts.map +1 -1
  245. package/dist/setup-supervisor.js.map +1 -1
  246. package/dist/terminal-run-output.d.ts +12 -0
  247. package/dist/terminal-run-output.d.ts.map +1 -0
  248. package/dist/terminal-run-output.js +33 -0
  249. package/dist/terminal-run-output.js.map +1 -0
  250. package/dist/thread-delivery.d.ts.map +1 -1
  251. package/dist/thread-delivery.js +4 -3
  252. package/dist/thread-delivery.js.map +1 -1
  253. package/dist/thread-execution-workspace.d.ts +15 -0
  254. package/dist/thread-execution-workspace.d.ts.map +1 -1
  255. package/dist/thread-execution-workspace.js +39 -5
  256. package/dist/thread-execution-workspace.js.map +1 -1
  257. package/dist/thread-turn-services.d.ts +104 -0
  258. package/dist/thread-turn-services.d.ts.map +1 -0
  259. package/dist/thread-turn-services.js +27 -0
  260. package/dist/thread-turn-services.js.map +1 -0
  261. package/package.json +23 -23
package/dist/cli.js CHANGED
@@ -1,8 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import process from "node:process";
3
- import { createReadStream, existsSync, lstatSync, readFileSync, readdirSync } from "node:fs";
4
- import { createHash } from "node:crypto";
5
- import { Readable, Transform } from "node:stream";
3
+ import { existsSync, lstatSync, readFileSync, readdirSync } from "node:fs";
6
4
  import { dirname, join, relative, resolve, sep } from "node:path";
7
5
  import { ArtifactStore } from "@claudexor/artifact-store";
8
6
  import { CLAUDEXOR_VERSION, noProjectRepoRoot, readTextSafe, userConfigDir } from "@claudexor/util";
@@ -10,13 +8,15 @@ import { releaseCommand } from "./release-command.js";
10
8
  import { serveBeltBridge, serveMcpBridge } from "./bridge-serve.js";
11
9
  import { dispatchAcpCommand } from "./acp-auth-command.js";
12
10
  import { initProjectConfig } from "@claudexor/config";
13
- import { DecisionRecord, EFFORT_HINT_HELP, EffortHint, ExternalContextPolicy, ModeKind as ModeKindSchema, RoutingGoal, ControlThreadListResponse, RunTelemetry, StructuredOutputConformance, TaskContract, runOutcomeLabel, } from "@claudexor/schema";
11
+ import { DecisionRecord, EFFORT_HINT_HELP, EffortHint, ExternalContextPolicy, ModeKind as ModeKindSchema, RoutingGoal, ControlThreadListResponse, RunFailure, RunTelemetry, StructuredOutputConformance, TaskContract, runOutcomeLabel, } from "@claudexor/schema";
14
12
  import { flagBool, flagStr, flagStringList, flagValues, parseArgs, requiredStringFlagError, } from "./args.js";
15
13
  import { print, printJson, printJsonLine, printUsageError, statusGlyph } from "./cli-io.js";
16
14
  import { controlProblemError, minIntError, renderCliFailure, usageError } from "./cli-error.js";
15
+ import { cliOutputMode, outputModeIsMachine, outputModeIsStream, renderOutputFailure, renderOutputUsageFailure, } from "./output-mode.js";
17
16
  import { handleHelpRequest } from "./command-help.js";
18
17
  import { pickResumableThread } from "./thread-select.js";
19
- import { KNOWN_FLAGS, VALUE_FLAGS, commandFlagScopeError, helpJson, renderHelp, } from "./command-registry.js";
18
+ import { KNOWN_FLAGS, VALUE_FLAGS, helpJson, renderHelp } from "./command-registry.js";
19
+ import { commandFlagScopeError, commandPositionalError, runModeFlagScopeError, } from "./command-scope.js";
20
20
  import { buildAgentCapabilityCatalog } from "./capabilities.js";
21
21
  import { aboutJson, renderAbout } from "./about-command.js";
22
22
  import { dispatchOpsCommand } from "./ops-commands.js";
@@ -24,17 +24,22 @@ import { reviewCommand } from "./review-command.js";
24
24
  import { controlApiFetch, followRun } from "./live.js";
25
25
  import { retryCommand, runAgainCommand } from "./retry-command.js";
26
26
  import { assertCliRunParamsHaveNoInlineSecrets } from "./run-secret-scan.js";
27
- import { openLocalAttachment, resolveLocalAttachment, } from "./local-attachment.js";
28
- import { connectDaemonIfRunning, daemonOutcomeProblemFields, daemonOutcomeSummary, ensureDaemon, enqueueAndAwait, exitCodeForState, fetchApplyEligibility, fetchCouncil, fetchOutcomeBanner, fetchRunDetail, fetchRunOutcomeFacts, projectApplyEligibility, projectOutcomeBanner, projectRunOutcomeFacts, mergeDaemonRunOutcome, } from "./daemon-run.js";
27
+ import { resolveLocalAttachment } from "./local-attachment.js";
28
+ import { uploadLocalAttachment } from "./attachment-upload.js";
29
+ import { connectDaemonIfRunning, daemonOutcomeSummary, ensureDaemon, enqueueAndAwait, exitCodeForState, fetchApplyEligibility, fetchCouncil, fetchRunDetail, fetchRunOutcomeFacts, projectOutcomeBanner, projectRunOutcomeFacts, mergeDaemonRunOutcome, } from "./daemon-run.js";
30
+ import { inspectDelegationLines, projectDelegation, terminalDelegationLines, } from "./delegation-output.js";
31
+ import { readRunFactsArtifact } from "./run-facts-projection.js";
32
+ import { projectTerminalRunOutput, terminalRunRequiredActionLines } from "./terminal-run-output.js";
29
33
  import { runPlanQuestionLoop } from "./plan-question-loop.js";
30
34
  import { resolveDecisionBody } from "./decision.js";
31
35
  import { primaryOutputForCli } from "./primary-output.js";
32
36
  import { PLUGIN_TARGETS, PLUGIN_VERBS, formatPluginResult, pluginCommandErrorResult, runPluginCommand, } from "./plugins.js";
33
- import { buildRegistry } from "./registry.js";
34
37
  import { settingsCommand } from "./settings-command.js";
35
38
  import { quotaCommand } from "./quota-command.js";
36
39
  import { trustCommand } from "./trust-command.js";
37
40
  import { projectCommand } from "./project-command.js";
41
+ import { remoteCommand, setupCommand } from "./remote-command.js";
42
+ import { harnessCommand } from "./harness-command.js";
38
43
  import { runRepl } from "./repl.js";
39
44
  import { parseProtectedPathApprovalFlags, parseTestCommandFlags, parseReviewerEffortFlags, parseReviewerModelFlags, parseReviewerPanelFlags, } from "./run-options.js";
40
45
  const CLI_VERSION = CLAUDEXOR_VERSION;
@@ -159,55 +164,6 @@ function attachmentInputs(args) {
159
164
  const out = attachmentPaths(args).map(({ path, forceImage }) => resolveLocalAttachment(path, forceImage));
160
165
  return out.length > 0 ? out : undefined;
161
166
  }
162
- async function uploadLocalAttachment(addr, attachment) {
163
- const created = (await controlJson(addr, "/uploads", {
164
- method: "POST",
165
- headers: { "content-type": "application/json" },
166
- body: JSON.stringify({
167
- kind: attachment.kind,
168
- mime: attachment.mime,
169
- name: attachment.name,
170
- sizeBytes: attachment.sizeBytes,
171
- }),
172
- }));
173
- try {
174
- const source = createReadStream(attachment.path, {
175
- fd: openLocalAttachment(attachment),
176
- autoClose: true,
177
- });
178
- const hash = createHash("sha256");
179
- const hashingStream = new Transform({
180
- transform(chunk, _encoding, callback) {
181
- hash.update(chunk);
182
- callback(null, chunk);
183
- },
184
- });
185
- source.pipe(hashingStream);
186
- const response = await controlApiFetch(addr, `/uploads/${encodeURIComponent(created.uploadId)}/bytes`, {
187
- method: "PUT",
188
- headers: { "content-type": "application/octet-stream" },
189
- body: Readable.toWeb(hashingStream),
190
- duplex: "half",
191
- });
192
- if (!response.ok) {
193
- const detail = (await response.json().catch(() => ({})));
194
- throw new Error(String(detail["message"] ?? detail["error"] ?? `HTTP ${response.status}`));
195
- }
196
- const expectedSha256 = `sha256:${hash.digest("hex")}`;
197
- const resource = (await controlJson(addr, `/uploads/${encodeURIComponent(created.uploadId)}/finalize`, {
198
- method: "POST",
199
- headers: { "content-type": "application/json" },
200
- body: JSON.stringify({ expectedSha256 }),
201
- }));
202
- return { resourceId: resource.resourceId };
203
- }
204
- catch (error) {
205
- await controlApiFetch(addr, `/uploads/${encodeURIComponent(created.uploadId)}`, {
206
- method: "DELETE",
207
- }).catch(() => undefined);
208
- throw error;
209
- }
210
- }
211
167
  /** Per-family reviewer model map from `--reviewer-model "openai=gpt-4o-mini,anthropic=claude-haiku"`. Fails loudly on malformed input. */
212
168
  function reviewerModels(args) {
213
169
  return parseReviewerModelFlags(flagValues(args, "reviewer-model"));
@@ -220,7 +176,7 @@ function reviewerEfforts(args) {
220
176
  function reviewerPanel(args) {
221
177
  return parseReviewerPanelFlags(flagValues(args, "reviewer-panel"));
222
178
  }
223
- async function orchestrate(args, mode, json, forced = {}) {
179
+ async function orchestrate(args, mode, outputMode, forced = {}) {
224
180
  let rawPrompt = args._.slice(1).join(" ").trim();
225
181
  // Headless prompt sources (W13): `-` reads the prompt from stdin; a file
226
182
  // beats retyping. Exactly ONE source — ambiguity is a usage error, never a
@@ -228,34 +184,34 @@ async function orchestrate(args, mode, json, forced = {}) {
228
184
  const promptFile = flagStr(args, "prompt-file");
229
185
  if (promptFile !== undefined) {
230
186
  if (rawPrompt && rawPrompt !== "-") {
231
- return printUsageError(json, "claudexor: pass either an inline prompt or --prompt-file, not both");
187
+ return renderOutputUsageFailure(outputMode, "claudexor: pass either an inline prompt or --prompt-file, not both");
232
188
  }
233
189
  try {
234
190
  rawPrompt = readFileSync(promptFile, "utf8").trim();
235
191
  }
236
192
  catch (err) {
237
- return printUsageError(json, `claudexor: --prompt-file: cannot read ${promptFile}: ${err instanceof Error ? err.message : String(err)}`);
193
+ return renderOutputUsageFailure(outputMode, `claudexor: --prompt-file: cannot read ${promptFile}: ${err instanceof Error ? err.message : String(err)}`);
238
194
  }
239
195
  }
240
196
  else if (rawPrompt === "-") {
241
197
  rawPrompt = readFileSync(0, "utf8").trim();
242
198
  if (!rawPrompt) {
243
- return printUsageError(json, "claudexor: stdin prompt (`-`) was empty");
199
+ return renderOutputUsageFailure(outputMode, "claudexor: stdin prompt (`-`) was empty");
244
200
  }
245
201
  }
246
202
  const prompt = rawPrompt;
247
203
  if (!prompt) {
248
- return printUsageError(json, "claudexor: missing prompt");
204
+ return renderOutputUsageFailure(outputMode, "claudexor: missing prompt");
249
205
  }
250
206
  const portfolioRaw = flagStr(args, "portfolio");
251
207
  if (portfolioRaw !== undefined) {
252
- return printUsageError(json, "claudexor: --portfolio was removed in v2; use --routing-goal auto|quality|economy");
208
+ return renderOutputUsageFailure(outputMode, "claudexor: --portfolio was removed in v2; use --routing-goal auto|quality|economy");
253
209
  }
254
210
  const credentialProfileId = flagStr(args, "profile");
255
211
  const routingGoalRaw = flagStr(args, "routing-goal");
256
212
  const routingGoal = routingGoalRaw !== undefined ? RoutingGoal.safeParse(routingGoalRaw) : null;
257
213
  if (routingGoalRaw !== undefined && !routingGoal?.success) {
258
- return printUsageError(json, `claudexor: unknown --routing-goal '${routingGoalRaw}'`);
214
+ return renderOutputUsageFailure(outputMode, `claudexor: unknown --routing-goal '${routingGoalRaw}'`);
259
215
  }
260
216
  let reviewerEffortOverrides;
261
217
  let resolvedReviewerModels;
@@ -332,7 +288,10 @@ async function orchestrate(args, mode, json, forced = {}) {
332
288
  }
333
289
  catch (err) {
334
290
  // Projector: typed field errors / domain codes survive; a plain flag-parse Error is usage (exit 2).
335
- return renderCliFailure(json, err, { defaultCategory: "usage", messagePrefix: "claudexor:" });
291
+ return renderOutputFailure(outputMode, err, {
292
+ defaultCategory: "usage",
293
+ messagePrefix: "claudexor:",
294
+ });
336
295
  }
337
296
  let tests;
338
297
  try {
@@ -361,15 +320,18 @@ async function orchestrate(args, mode, json, forced = {}) {
361
320
  }
362
321
  catch (err) {
363
322
  // Preserves the typed `inline_secret_rejected` code (never echoing the token).
364
- return renderCliFailure(json, err, { defaultCategory: "usage", messagePrefix: "claudexor:" });
323
+ return renderOutputFailure(outputMode, err, {
324
+ defaultCategory: "usage",
325
+ messagePrefix: "claudexor:",
326
+ });
365
327
  }
366
328
  if (delegate && mode !== "agent") {
367
- return printUsageError(json, `claudexor: --delegate is an agent strategy (got mode '${mode}')`);
329
+ return renderOutputUsageFailure(outputMode, `claudexor: --delegate is an agent strategy (got mode '${mode}')`);
368
330
  }
369
331
  if (council && mode !== "plan") {
370
- return printUsageError(json, `claudexor: --council is a plan strategy (got mode '${mode}')`);
332
+ return renderOutputUsageFailure(outputMode, `claudexor: --council is a plan strategy (got mode '${mode}')`);
371
333
  }
372
- return daemonRun(args, json, {
334
+ return daemonRun(args, outputMode, {
373
335
  mode,
374
336
  delegate,
375
337
  council,
@@ -404,12 +366,10 @@ async function orchestrate(args, mode, json, forced = {}) {
404
366
  * All five product modes enter through the managed daemon and control API.
405
367
  * `--json` prints one stable `{ runId, runDir, status }` machine envelope.
406
368
  */
407
- async function daemonRun(args, json, p) {
369
+ async function daemonRun(args, outputMode, p) {
408
370
  const inPlace = flagBool(args, "in-place");
409
- const jsonStream = flagBool(args, "json-stream");
410
- if (json && jsonStream) {
411
- return printUsageError(json, "claudexor: --json prints exactly one object and --json-stream prints NDJSON; pass one, not both");
412
- }
371
+ const json = outputModeIsMachine(outputMode);
372
+ const jsonStream = outputModeIsStream(outputMode);
413
373
  let client;
414
374
  let addr;
415
375
  try {
@@ -503,6 +463,10 @@ async function daemonRun(args, json, p) {
503
463
  ...(p.reviewerModels ? { reviewerModels: p.reviewerModels } : {}),
504
464
  ...(p.reviewerEfforts ? { reviewerEfforts: p.reviewerEfforts } : {}),
505
465
  };
466
+ // The last run id this command bound, threaded into the failure envelope as
467
+ // a per-command extra: a run that DID start (or finish) must keep its handle
468
+ // even when a post-terminal read raises (e.g. 500 run_facts_invalid).
469
+ let terminalRunId = "";
506
470
  try {
507
471
  if (jsonStream) {
508
472
  // NDJSON machine surface (W13): an EARLY runId frame first, every run
@@ -512,18 +476,10 @@ async function daemonRun(args, json, p) {
512
476
  const started = await enqueueAndAwait(client, addr, body, { waitForTerminal: false });
513
477
  if (!started.runId) {
514
478
  // Even the early-failure path stays valid NDJSON (compact, one line).
515
- printJsonLine({
516
- frame: "run.terminal",
517
- runId: "",
518
- runDir: started.runDir,
519
- status: started.status,
520
- jobId: started.jobId,
521
- mode: p.mode,
522
- ...(started.error ? { error: started.error } : {}),
523
- ...daemonOutcomeProblemFields(started),
524
- });
479
+ printJsonLine(projectTerminalRunOutput(started, p.mode, null, { frame: "run.terminal" }));
525
480
  return exitCodeForState(started.status);
526
481
  }
482
+ terminalRunId = started.runId;
527
483
  printJsonLine({
528
484
  frame: "run.started",
529
485
  runId: started.runId,
@@ -537,50 +493,31 @@ async function daemonRun(args, json, p) {
537
493
  const final = started.jobId ? await client.status(started.jobId) : null;
538
494
  const out = mergeDaemonRunOutcome(started, final);
539
495
  const status = out.status;
540
- const reason = daemonOutcomeSummary({ ...started, status, error: out.error });
541
- // ONE GET /runs/:id feeds all three terminal projections (INV-120/122).
542
496
  const detail = await fetchRunDetail(addr, out.runId);
543
- const applyEligibility = projectApplyEligibility(detail);
544
- const outcomeBanner = projectOutcomeBanner(detail);
545
- printJsonLine({
497
+ const facts = projectRunOutcomeFacts(detail);
498
+ const reason = daemonOutcomeSummary({ ...out, outcomeFacts: facts ?? undefined });
499
+ printJsonLine(projectTerminalRunOutput(out, p.mode, detail, {
546
500
  frame: "run.terminal",
547
- runId: out.runId,
548
- runDir: out.runDir,
549
- status: out.status,
550
- jobId: out.jobId,
551
- mode: p.mode,
552
- ...(out.error ? { error: out.error } : {}),
553
- ...daemonOutcomeProblemFields(out),
554
- ...(reason ? { summary: reason } : {}),
555
- ...(outcomeBanner ? { outcomeBanner } : {}),
556
- ...(applyEligibility ? { applyEligibility } : {}),
557
- });
501
+ summary: reason,
502
+ }));
558
503
  return exitCodeForState(status, projectRunOutcomeFacts(detail));
559
504
  }
560
505
  if (json) {
561
506
  // Pure machine surface: await the terminal outcome and print one JSON object.
562
507
  const out = await enqueueAndAwait(client, addr, body, { waitForTerminal: true });
563
- // Preserve bench keys while adding mode and honest non-success detail.
564
- const reason = daemonOutcomeSummary(out);
508
+ terminalRunId = out.runId;
565
509
  // ADD-ONLY key (bench contract keeps {runId,runDir,status}): the derived
566
510
  // apply-gate verdict, so machine callers act on truth instead of
567
511
  // re-implying eligibility from status. ONE GET /runs/:id feeds all three
568
512
  // terminal projections (INV-120/122).
569
513
  const detail = await fetchRunDetail(addr, out.runId);
570
- const applyEligibility = projectApplyEligibility(detail);
571
- const outcomeBanner = projectOutcomeBanner(detail);
572
- printJson({
573
- runId: out.runId,
574
- runDir: out.runDir,
575
- status: out.status,
576
- jobId: out.jobId,
577
- mode: p.mode,
578
- ...(out.error ? { error: out.error } : {}),
579
- ...daemonOutcomeProblemFields(out),
580
- ...(reason ? { summary: reason } : {}),
581
- ...(outcomeBanner ? { outcomeBanner } : {}),
582
- ...(applyEligibility ? { applyEligibility } : {}),
514
+ // Preserve bench keys while deriving the human summary from the same
515
+ // canonical receipt that feeds the machine terminal fields.
516
+ const reason = daemonOutcomeSummary({
517
+ ...out,
518
+ outcomeFacts: projectRunOutcomeFacts(detail) ?? undefined,
583
519
  });
520
+ printJson(projectTerminalRunOutput(out, p.mode, detail, { summary: reason }));
584
521
  return exitCodeForState(out.status, projectRunOutcomeFacts(detail));
585
522
  }
586
523
  // Text mode: enqueue, then live-stream the run through the shared follow
@@ -589,19 +526,30 @@ async function daemonRun(args, json, p) {
589
526
  const started = await enqueueAndAwait(client, addr, body, { waitForTerminal: false });
590
527
  if (!started.runId) {
591
528
  print(`run did not start: ${started.status}${started.error ? ` — ${started.error}` : ""}`);
529
+ for (const line of terminalRunRequiredActionLines(started))
530
+ print(line);
592
531
  return exitCodeForState(started.status);
593
532
  }
533
+ terminalRunId = started.runId;
594
534
  await followRun(started.runId, false);
595
535
  const final = started.jobId ? await client.status(started.jobId) : null;
596
- const status = final?.state ?? started.status;
536
+ const out = mergeDaemonRunOutcome(started, final);
537
+ const status = out.status;
597
538
  const publicStatus = status;
598
539
  print("");
599
540
  print(`run ${started.runId} [${publicStatus}]`);
600
541
  // Server-owned outcome headline (D18), printed verbatim above any output.
601
- const terminalBanner = await fetchOutcomeBanner(addr, started.runId);
542
+ const terminalDetail = await fetchRunDetail(addr, started.runId);
543
+ const terminalBanner = projectOutcomeBanner(terminalDetail);
602
544
  if (terminalBanner)
603
545
  print(` ${terminalBanner}`);
604
- print(` artifacts: ${final?.runDir ?? started.runDir}`);
546
+ for (const line of terminalDelegationLines(projectDelegation(terminalDetail)))
547
+ print(line);
548
+ print(` artifacts: ${out.runDir}`);
549
+ if (out.error)
550
+ print(` error: ${out.error}`);
551
+ for (const line of terminalRunRequiredActionLines(out))
552
+ print(line);
605
553
  // A succeeded lifecycle exits 0 — INCLUDING a "Done · needs review" run
606
554
  // (review blocked / checks failed). The apply-eligibility verdict (state
607
555
  // needs_review) is the ONE source for the unblock guidance (D8).
@@ -629,6 +577,7 @@ async function daemonRun(args, json, p) {
629
577
  runId: started.runId,
630
578
  interactive: !json && !jsonStream, // TTY plan turns answer inline
631
579
  });
580
+ terminalRunId = effectiveRunId;
632
581
  }
633
582
  // Offer apply only after a positive gate; otherwise print inspect/unblock guidance.
634
583
  const eligibility = await fetchApplyEligibility(addr, effectiveRunId);
@@ -650,9 +599,11 @@ async function daemonRun(args, json, p) {
650
599
  catch (err) {
651
600
  // D-7 projector: a typed control problem (code/retryable/context) survives
652
601
  // intact; --json-stream stays valid NDJSON via the compact-line stream option.
602
+ // A run that already started keeps its handle in the envelope (extras).
653
603
  return renderCliFailure(json || jsonStream, err, {
654
604
  messagePrefix: "claudexor:",
655
605
  stream: jsonStream,
606
+ ...(terminalRunId ? { extras: { runId: terminalRunId } } : {}),
656
607
  });
657
608
  }
658
609
  }
@@ -761,21 +712,12 @@ async function resolveRunStore(runId) {
761
712
  }
762
713
  return null;
763
714
  }
764
- async function controlJson(addr, path, init = {}) {
765
- const response = await controlApiFetch(addr, path, init);
766
- const text = await response.text();
767
- const body = text ? JSON.parse(text) : {};
768
- if (response.ok)
769
- return body;
770
- const detail = body && typeof body === "object" ? body : {};
771
- throw new Error(String(detail["message"] ?? detail["error"] ?? `HTTP ${response.status}`));
772
- }
773
- function printPreflightError(args, json, error) {
774
- if (json && (args._[0] ?? "help") === "plugin") {
715
+ function printPreflightError(args, outputMode, error) {
716
+ if (outputMode === "json" && (args._[0] ?? "help") === "plugin") {
775
717
  printJson(pluginCommandErrorResult(args._[1], args._[2], flagBool(args, "dry-run"), 2, error));
776
718
  return 2;
777
719
  }
778
- return printUsageError(json, error);
720
+ return renderOutputUsageFailure(outputMode, error);
779
721
  }
780
722
  function listCliArtifacts(root) {
781
723
  if (!existsSync(root))
@@ -797,8 +739,12 @@ function listCliArtifacts(root) {
797
739
  // KNOWN_FLAGS / VALUE_FLAGS (imported above) and the per-command scope check
798
740
  // are projections of the command registry. Unknown flags FAIL LOUDLY: `--harnes
799
741
  // codex` must never silently run all harnesses.
800
- async function dispatch(args, json) {
742
+ async function dispatch(args, outputMode) {
801
743
  const cmd = args._[0] ?? "help";
744
+ if (outputMode === "conflict") {
745
+ return renderOutputUsageFailure(outputMode, "claudexor: --json prints exactly one object and --json-stream prints NDJSON; pass one, not both");
746
+ }
747
+ const json = outputModeIsMachine(outputMode);
802
748
  // `--help` resolves the COMMAND first (QA-057): scoped usage for a known verb,
803
749
  // global help for a bare/`help` verb, a usage error (exit 2) for a typo.
804
750
  if (flagBool(args, "help"))
@@ -806,18 +752,21 @@ async function dispatch(args, json) {
806
752
  const unknownFlags = Object.keys(args.flags).filter((f) => !KNOWN_FLAGS.has(f));
807
753
  if (unknownFlags.length > 0) {
808
754
  const error = `claudexor: unknown flag(s): ${unknownFlags.map((f) => `--${f}`).join(", ")} (see \`claudexor help\`)`;
809
- return printPreflightError(args, json, error);
755
+ return printPreflightError(args, outputMode, error);
810
756
  }
811
757
  const valueFlagError = requiredStringFlagError(args, VALUE_FLAGS);
812
758
  if (valueFlagError)
813
- return printPreflightError(args, json, valueFlagError);
759
+ return printPreflightError(args, outputMode, valueFlagError);
814
760
  // Registry-enforced per-command flag scope: a KNOWN flag outside the
815
761
  // command's declared set (e.g. `plan --create`, `ask --force`) fails loudly
816
762
  // instead of being silently ignored. Data-driven from CLI_COMMANDS for
817
763
  // every verb (this replaced the old hand-listed plugin/--force special cases).
818
764
  const scopeError = commandFlagScopeError(cmd, Object.keys(args.flags));
819
765
  if (scopeError)
820
- return printPreflightError(args, json, scopeError);
766
+ return printPreflightError(args, outputMode, scopeError);
767
+ const positionalError = commandPositionalError(cmd, args._.slice(1));
768
+ if (positionalError)
769
+ return printPreflightError(args, outputMode, positionalError);
821
770
  // No arguments at all = the interactive REPL: a thread of turns over the
822
771
  // current project with native session continuity (chat is the normal loop).
823
772
  if (args._.length === 0 && process.stdin.isTTY) {
@@ -838,21 +787,28 @@ async function dispatch(args, json) {
838
787
  }
839
788
  case "project":
840
789
  return projectCommand(args, json);
790
+ case "remote":
791
+ return remoteCommand(args, json);
792
+ case "setup":
793
+ return setupCommand(args, json);
841
794
  case "agent": {
842
795
  const modeStr = flagStr(args, "mode");
843
796
  if (modeStr !== undefined) {
844
797
  const mode = normalizeMode(modeStr);
845
798
  if (!MODES.has(mode)) {
846
- return printUsageError(json, `claudexor: unknown --mode '${modeStr}'. valid: ${[...MODES].join(", ")}`);
799
+ return renderOutputUsageFailure(outputMode, `claudexor: unknown --mode '${modeStr}'. valid: ${[...MODES].join(", ")}`);
847
800
  }
848
- return orchestrate(args, mode, json);
801
+ const modeScopeError = runModeFlagScopeError(mode, Object.keys(args.flags));
802
+ if (modeScopeError)
803
+ return printPreflightError(args, outputMode, modeScopeError);
804
+ return orchestrate(args, mode, outputMode);
849
805
  }
850
- return orchestrate(args, "agent", json);
806
+ return orchestrate(args, "agent", outputMode);
851
807
  }
852
808
  case "ask":
853
- return orchestrate(args, "ask", json);
809
+ return orchestrate(args, "ask", outputMode);
854
810
  case "best-of":
855
- return orchestrate(args, "agent", json, { race: true });
811
+ return orchestrate(args, "agent", outputMode, { race: true });
856
812
  // RETIRED verb spellings hard-error with the new spelling — no silent
857
813
  // aliases (same doctrine as retired mode ids: stale scripts fail loudly).
858
814
  case "run":
@@ -871,12 +827,12 @@ async function dispatch(args, json) {
871
827
  : cmd === "spec"
872
828
  ? "claudexor plan <prompt> then Implement (the plan lifecycle surfaces open questions and freezes the plan on implement)"
873
829
  : "claudexor ask --deep-scan <prompt>";
874
- return printPreflightError(args, json, `claudexor: the '${cmd}' verb was retired; use ${replacement}`);
830
+ return printPreflightError(args, outputMode, `claudexor: the '${cmd}' verb was retired; use ${replacement}`);
875
831
  }
876
832
  case "plan":
877
- return orchestrate(args, "plan", json);
833
+ return orchestrate(args, "plan", outputMode);
878
834
  case "create":
879
- return orchestrate(args, "agent", json, { create: true });
835
+ return orchestrate(args, "agent", outputMode, { create: true });
880
836
  case "settings":
881
837
  return settingsCommand(args, json);
882
838
  case "quota":
@@ -925,12 +881,23 @@ async function dispatch(args, json) {
925
881
  const decision = store.readYaml(join(paths.arbitrationDir, "decision.yaml"));
926
882
  const workProduct = store.readYaml(join(paths.finalDir, "work_product.yaml"));
927
883
  const contract = TaskContract.safeParse(store.readYaml(join(paths.contextDir, "task.yaml")));
928
- const primary = primaryOutputForCli(paths.root, contract.success ? contract.data.mode.kind : undefined);
884
+ const parsedFailure = RunFailure.safeParse(store.readYaml(join(paths.finalDir, "failure.yaml")));
885
+ const failure = parsedFailure.success ? parsedFailure.data : null;
929
886
  // The CLI projects the orchestrator-owned telemetry artifact and NEVER
930
887
  // recomputes evidence from raw events (single-owner rule); a missing
931
888
  // artifact (legacy run) renders "telemetry unavailable".
932
889
  const parsedTelemetry = RunTelemetry.safeParse(store.readYaml(join(paths.finalDir, "telemetry.yaml")));
933
890
  const telemetry = parsedTelemetry.success ? parsedTelemetry.data : null;
891
+ // GH #29: inspect reads the same immutable receipt as GET and terminal JSON.
892
+ const runFacts = readRunFactsArtifact(store, paths.finalDir, {
893
+ runId,
894
+ ...(contract.success ? { taskId: contract.data.task_id } : {}),
895
+ });
896
+ const primary = primaryOutputForCli(paths.root, contract.success ? contract.data.mode.kind : undefined, {
897
+ failure,
898
+ lifecycle: runFacts?.outcome.lifecycle,
899
+ ...(runFacts?.presentation ? { presentation: runFacts.presentation } : {}),
900
+ });
934
901
  const toolErrors = telemetry
935
902
  ? telemetry.attempts.flatMap((a) => a.tool_errors
936
903
  .filter((e) => !e.recovered && e.kind === "web")
@@ -952,22 +919,26 @@ async function dispatch(args, json) {
952
919
  })))
953
920
  : [];
954
921
  const artifacts = listCliArtifacts(paths.root).filter((p) => !p.endsWith("/"));
955
- const outputReadyState = primary?.kind === "diagnostic"
956
- ? "diagnostic"
957
- : primary?.text.trim()
958
- ? "ready"
959
- : readTextSafe(join(paths.finalDir, "failure.yaml"))
960
- ? "diagnostic"
961
- : "finalizing";
922
+ const outputReadyState = runFacts?.presentation?.state ??
923
+ (primary?.kind === "diagnostic"
924
+ ? "diagnostic"
925
+ : primary?.text.trim()
926
+ ? "ready"
927
+ : (failure ?? readTextSafe(join(paths.finalDir, "failure.yaml")))
928
+ ? "diagnostic"
929
+ : "finalizing");
962
930
  const parsedDecision = DecisionRecord.safeParse(decision);
963
931
  const summary = readTextSafe(join(paths.finalDir, "summary.md"));
964
932
  if (json) {
965
933
  printJson({
966
934
  runId,
967
935
  runDir: paths.root,
936
+ lifecycle: runFacts?.outcome.lifecycle ?? null,
968
937
  outputReadyState,
969
938
  contract: contract.success ? contract.data : null,
970
939
  telemetry,
940
+ runFacts,
941
+ failure,
971
942
  toolErrors,
972
943
  toolWarnings,
973
944
  primaryOutput: primary,
@@ -979,10 +950,14 @@ async function dispatch(args, json) {
979
950
  return summary || primary ? 0 : 1;
980
951
  }
981
952
  print(`run ${runId} @ ${paths.root}`);
953
+ if (runFacts)
954
+ print(`lifecycle: ${runFacts.outcome.lifecycle}`);
982
955
  if (contract.success) {
983
956
  print(`mode: ${contract.data.mode.kind}`);
984
957
  print(`access: requested=${contract.data.access.requested_profile} effective=${contract.data.access.effective_profile}`);
985
958
  }
959
+ for (const line of inspectDelegationLines(telemetry?.delegation ?? null))
960
+ print(line);
986
961
  if (telemetry) {
987
962
  print(`web: policy=${telemetry.external_context_policy} effective=${telemetry.effective_web_mode} required=${telemetry.web_required} evidence=${telemetry.web.status}`);
988
963
  for (const requirement of telemetry.request_requirements.filter((item) => item.requested)) {
@@ -993,6 +968,12 @@ async function dispatch(args, json) {
993
968
  print(`web: policy=${contract.data.external_context.policy} required=${contract.data.external_context.web_required} evidence=unavailable (no telemetry.yaml)`);
994
969
  }
995
970
  print(`output: ${outputReadyState}${primary ? ` ${primary.path}` : ""}`);
971
+ if (failure) {
972
+ print(`failure: ${failure.category}${failure.code ? `/${failure.code}` : ""} phase=${failure.phase}${failure.harnessId ? ` harness=${failure.harnessId}` : ""}`);
973
+ print(`failure message: ${failure.safeMessage}`);
974
+ for (const action of failure.nextActions)
975
+ print(`next action: ${action}`);
976
+ }
996
977
  {
997
978
  // Structured-output contract receipt (only present when the run was
998
979
  // started with --output-schema); projected, never re-validated here.
@@ -1149,20 +1130,8 @@ async function dispatch(args, json) {
1149
1130
  throw err;
1150
1131
  }
1151
1132
  }
1152
- case "harness": {
1153
- const sub = args._[1];
1154
- if (sub === "list") {
1155
- // Fakes are test fixtures, not real harnesses; `--all` reveals them.
1156
- const includeFakes = flagBool(args, "all");
1157
- const ids = [...buildRegistry({ includeFakes }).keys()];
1158
- if (json)
1159
- printJson({ harnesses: ids });
1160
- else
1161
- ids.forEach((id) => print(id));
1162
- return 0;
1163
- }
1164
- return printUsageError(json, "usage: claudexor harness list [--all]");
1165
- }
1133
+ case "harness":
1134
+ return harnessCommand(args, json);
1166
1135
  case "capabilities": {
1167
1136
  // The derived AgentCapabilityCatalog — same composer as the daemon's
1168
1137
  // GET /agent-capabilities and the MCP claudexor_capabilities tool.
@@ -1172,6 +1141,7 @@ async function dispatch(args, json) {
1172
1141
  else {
1173
1142
  print(`claudexor ${catalog.version} — capability catalog`);
1174
1143
  print(`modes: ${catalog.modes.join(", ")}`);
1144
+ print(`git: ${catalog.git.status}${catalog.git.version ? ` (${catalog.git.version})` : ""}${catalog.git.remediation ? ` — ${catalog.git.remediation}` : ""}`);
1175
1145
  print(`available harnesses: ${catalog.availableHarnesses.join(", ") || "(none)"}`);
1176
1146
  for (const h of catalog.harnesses) {
1177
1147
  const model = h.configuredModel
@@ -1208,7 +1178,7 @@ async function dispatch(args, json) {
1208
1178
  // callers get the ONE projector envelope (with message/code shape, no
1209
1179
  // longer a partial {ok,exitCode,error}); text mode prints the full help.
1210
1180
  if (json) {
1211
- return renderCliFailure(true, usageError(`claudexor: unknown command '${cmd}' (see \`claudexor help --json\`)`), {});
1181
+ return renderOutputFailure(outputMode, usageError(`claudexor: unknown command '${cmd}' (see \`claudexor help --json\`)`), {});
1212
1182
  }
1213
1183
  process.stderr.write(`claudexor: unknown command '${cmd}'\n\n${HELP}\n`);
1214
1184
  return 2;
@@ -1224,18 +1194,27 @@ async function main() {
1224
1194
  process.stdout.write(`${CLI_VERSION}\n`);
1225
1195
  return 0;
1226
1196
  }
1227
- const json = flagBool(args, "json");
1197
+ const outputMode = cliOutputMode(args);
1228
1198
  try {
1229
- return await dispatch(args, json);
1199
+ return await dispatch(args, outputMode);
1230
1200
  }
1231
1201
  catch (err) {
1232
- return renderCliFailure(json, err);
1202
+ return renderOutputFailure(outputMode, err);
1233
1203
  }
1234
1204
  }
1235
1205
  main()
1236
1206
  .then((code) => process.exit(code))
1237
1207
  .catch((err) => {
1238
- // Last-resort projector: still emit ONE envelope if the parse threw before json was known.
1239
- process.exit(renderCliFailure(process.argv.includes("--json"), err));
1208
+ // Last-resort projector: infer the complete mode even if parsing itself threw.
1209
+ const json = process.argv.includes("--json");
1210
+ const stream = process.argv.includes("--json-stream");
1211
+ const outputMode = json
1212
+ ? stream
1213
+ ? "conflict"
1214
+ : "json"
1215
+ : stream
1216
+ ? "json-stream"
1217
+ : "human";
1218
+ process.exit(renderOutputFailure(outputMode, err));
1240
1219
  });
1241
1220
  //# sourceMappingURL=cli.js.map