@bitkyc08/opencodex 2.79.0 → 2.81.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 (245) hide show
  1. package/README.md +3 -3
  2. package/gui/dist/assets/App-7yhoys8a.js +54 -0
  3. package/gui/dist/assets/Tray-EwAOsP0S.js +1 -0
  4. package/gui/dist/assets/index-RsoaAxXY.js +94 -0
  5. package/gui/dist/assets/quota-summary-D2lN8tDm.js +1 -0
  6. package/gui/dist/index.html +1 -1
  7. package/gui/dist/provider-icons/claude-green.svg +1 -0
  8. package/package.json +3 -2
  9. package/src/adapters/anthropic-model-contract.ts +8 -2
  10. package/src/adapters/anthropic.ts +7 -2
  11. package/src/adapters/codebuddy/mcp-server.ts +2 -25
  12. package/src/adapters/codebuddy/tool-bridge.ts +1 -7
  13. package/src/adapters/coding-agent/mcp-server.ts +33 -0
  14. package/src/adapters/coding-agent/protocol.ts +122 -17
  15. package/src/adapters/coding-agent/tool-catalog.ts +75 -0
  16. package/src/adapters/coding-agent/turn.ts +101 -44
  17. package/src/adapters/cursor/catalog.ts +8 -0
  18. package/src/adapters/cursor/effort-map.ts +2 -0
  19. package/src/adapters/cursor/live-transport.ts +5 -3
  20. package/src/adapters/cursor/native-exec-fs.ts +12 -9
  21. package/src/adapters/cursor/native-exec.ts +13 -5
  22. package/src/adapters/cursor/protobuf-request.ts +13 -7
  23. package/src/adapters/cursor/request-builder.ts +23 -10
  24. package/src/adapters/cursor/tool-definitions.ts +9 -6
  25. package/src/adapters/cursor/tool-guidance.ts +29 -8
  26. package/src/adapters/cursor/tool-naming.ts +40 -7
  27. package/src/adapters/cursor/tool-wording.ts +46 -0
  28. package/src/adapters/cursor.ts +3 -0
  29. package/src/adapters/devin/live-models.ts +2 -0
  30. package/src/adapters/devin.ts +25 -11
  31. package/src/adapters/kiro-retry.ts +9 -4
  32. package/src/adapters/openai-chat/messages.ts +7 -4
  33. package/src/adapters/physical-send.ts +8 -5
  34. package/src/adapters/qoder/adapter.ts +41 -6
  35. package/src/adapters/qoder/scaffold-guard.ts +2 -3
  36. package/src/adapters/zed.ts +16 -1
  37. package/src/claude/inbound-cache-stabilize.ts +4 -9
  38. package/src/claude/inbound.ts +10 -0
  39. package/src/claude/intercept/model-bindings.ts +1 -0
  40. package/src/claude/intercept/picker-listener.ts +71 -4
  41. package/src/cli/account-anthropic-threshold.ts +14 -5
  42. package/src/cli/account-api.ts +3 -1
  43. package/src/cli/account-extended.ts +13 -9
  44. package/src/cli/account-policy-dto.ts +1 -1
  45. package/src/cli/account-policy.ts +13 -4
  46. package/src/cli/account.ts +3 -3
  47. package/src/cli/capabilities-accounts.ts +2 -2
  48. package/src/cli/capabilities-agents-routing.ts +24 -0
  49. package/src/cli/capabilities-integrations.ts +16 -0
  50. package/src/cli/codex-shim-autorestore.ts +3 -0
  51. package/src/cli/dispatch.ts +10 -1
  52. package/src/cli/help.ts +4 -1
  53. package/src/cli/index.ts +11 -5
  54. package/src/cli/integration-plan-dto.ts +1 -0
  55. package/src/cli/integration-preview.ts +8 -1
  56. package/src/cli/integrations.ts +32 -3
  57. package/src/cli/message-args.ts +34 -0
  58. package/src/cli/message-command.ts +43 -0
  59. package/src/cli/message-runtime.ts +6 -0
  60. package/src/cli/observe.ts +3 -1
  61. package/src/cli/provider.ts +29 -6
  62. package/src/cli/registry.ts +24 -2
  63. package/src/clients/config-export/commandcode.ts +131 -0
  64. package/src/clients/config-export/contracts.ts +12 -0
  65. package/src/clients/config-export.ts +64 -1
  66. package/src/codex/auth-api/account-list.ts +11 -3
  67. package/src/codex/auth-api/main-account-probe.ts +25 -8
  68. package/src/codex/auth-api/pool-mode-gate.ts +33 -9
  69. package/src/codex/auth-api/pool-quota-probe.ts +15 -1
  70. package/src/codex/auth-api/routes.ts +1 -0
  71. package/src/codex/autostart-health.ts +3 -3
  72. package/src/codex/catalog/auto-review.ts +13 -6
  73. package/src/codex/catalog/build-entries.ts +15 -7
  74. package/src/codex/catalog/control-plane.ts +45 -0
  75. package/src/codex/catalog/effort.ts +11 -1
  76. package/src/codex/catalog/gather-capture.ts +5 -3
  77. package/src/codex/catalog/parsing.ts +2 -1
  78. package/src/codex/catalog/provider-models.ts +15 -9
  79. package/src/codex/control-plane-models.ts +6 -0
  80. package/src/codex/journal.ts +36 -5
  81. package/src/codex/shim-overlay.ts +9 -1
  82. package/src/combos/jev-decision-contract.ts +25 -2
  83. package/src/combos/jev.ts +6 -3
  84. package/src/combos/types.ts +1 -1
  85. package/src/config/diagnostics.ts +27 -16
  86. package/src/config/load-degrade.ts +34 -0
  87. package/src/config/schema/anthropic-account-pool.ts +143 -0
  88. package/src/config/schema/config-schema.ts +7 -1
  89. package/src/config/schema/leaf-validators.ts +4 -0
  90. package/src/config.ts +2 -0
  91. package/src/generated/compatibility-version.json +347 -199
  92. package/src/generated/model-metadata.ts +5 -4
  93. package/src/images/loop.ts +2 -1
  94. package/src/integrations/catalog-refresh.ts +1 -1
  95. package/src/integrations/mutation-plan.ts +7 -0
  96. package/src/integrations/registry.ts +8 -0
  97. package/src/integrations/state.ts +9 -2
  98. package/src/integrations/writer.ts +8 -2
  99. package/src/lib/lab-live-route-production.ts +9 -1
  100. package/src/lib/request-execution-budget.ts +236 -58
  101. package/src/lib/spend-pool-alias-validation.ts +79 -0
  102. package/src/lib/spend-pool-continuity.ts +34 -0
  103. package/src/lib/spend-reservation-ledger.ts +412 -50
  104. package/src/lib/state-store-registrations.ts +20 -3
  105. package/src/lib/upstream-retry.ts +17 -5
  106. package/src/lib/workflow-budget.ts +53 -11
  107. package/src/messaging/budget.ts +45 -0
  108. package/src/messaging/discovery.ts +57 -0
  109. package/src/messaging/envelope.ts +49 -0
  110. package/src/messaging/input.ts +37 -0
  111. package/src/messaging/rpc.ts +196 -0
  112. package/src/messaging/send.ts +58 -0
  113. package/src/messaging/socket.ts +72 -0
  114. package/src/messaging/types.ts +29 -0
  115. package/src/oauth/anthropic-account-refusal.ts +51 -17
  116. package/src/oauth/anthropic-account-threshold.ts +8 -2
  117. package/src/oauth/anthropic-continuity.ts +15 -1
  118. package/src/oauth/anthropic-model-quota.ts +120 -48
  119. package/src/oauth/anthropic-model-routes.ts +11 -3
  120. package/src/oauth/anthropic-oauth-definitions.ts +98 -0
  121. package/src/oauth/anthropic-pool-config.ts +30 -0
  122. package/src/oauth/anthropic-rate-limit-policy.ts +64 -9
  123. package/src/oauth/anthropic-routing.ts +1202 -866
  124. package/src/oauth/anthropic-send-ownership.ts +41 -0
  125. package/src/oauth/anthropic.ts +4 -1
  126. package/src/oauth/generic-account-failover.ts +3 -1
  127. package/src/oauth/health.ts +8 -6
  128. package/src/oauth/index.ts +90 -173
  129. package/src/oauth/kiro.ts +22 -9
  130. package/src/oauth/login-cli.ts +2 -0
  131. package/src/oauth/model-discovery-auth.ts +43 -0
  132. package/src/oauth/pool-kernel.ts +11 -5
  133. package/src/oauth/pool-settings-capability.ts +5 -3
  134. package/src/oauth/store-anthropic-instance.ts +87 -0
  135. package/src/oauth/store.ts +67 -9
  136. package/src/oauth/token-guardian.ts +2 -0
  137. package/src/oauth/types.ts +2 -0
  138. package/src/protocols/plan-snapshot.ts +5 -0
  139. package/src/protocols/settings.ts +20 -5
  140. package/src/providers/anthropic-instance-id.ts +47 -0
  141. package/src/providers/anthropic-instance.ts +48 -0
  142. package/src/providers/anthropic-reset-grant-ledger.ts +11 -1
  143. package/src/providers/derive.ts +2 -0
  144. package/src/providers/kiro-models.ts +3 -0
  145. package/src/providers/label.ts +1 -1
  146. package/src/providers/model-rename-fields.ts +2 -0
  147. package/src/providers/quota/account-cache.ts +37 -11
  148. package/src/providers/quota/anthropic-account-quota.ts +93 -0
  149. package/src/providers/quota/anthropic-cooldown-recovery.ts +222 -111
  150. package/src/providers/quota/report-cache.ts +24 -1
  151. package/src/providers/quota/vendor-probes-oauth.ts +46 -14
  152. package/src/providers/quota.ts +22 -32
  153. package/src/providers/registry/entries-core.ts +35 -30
  154. package/src/providers/registry/model-ids.ts +1 -0
  155. package/src/providers/registry/model-seeds.ts +7 -4
  156. package/src/providers/registry/types.ts +3 -0
  157. package/src/providers/registry.ts +3 -3
  158. package/src/responses/spill-store.ts +5 -1
  159. package/src/responses/state/metrics.ts +87 -0
  160. package/src/responses/state/snapshot-codec.ts +6 -0
  161. package/src/responses/state/spill-queue.ts +1 -0
  162. package/src/responses/state/unforced-store-false.ts +55 -0
  163. package/src/responses/state.ts +50 -66
  164. package/src/router.ts +28 -4
  165. package/src/routing/compatibility/assemble.ts +1 -1
  166. package/src/routing/quota.ts +7 -3
  167. package/src/server/auth-cors.ts +5 -0
  168. package/src/server/chat-completions.ts +6 -1
  169. package/src/server/chat-native.ts +45 -25
  170. package/src/server/claude-messages.ts +16 -2
  171. package/src/server/gui-static.ts +38 -4
  172. package/src/server/index/spend-ledger-lifecycle.ts +10 -4
  173. package/src/server/index.ts +16 -16
  174. package/src/server/inference/context.ts +1 -1
  175. package/src/server/lifecycle.ts +1 -1
  176. package/src/server/management/agent-settings-routes.ts +30 -6
  177. package/src/server/management/anthropic-account-threshold.ts +12 -9
  178. package/src/server/management/anthropic-pool-settings.ts +72 -0
  179. package/src/server/management/anthropic-reset-grant-routes.ts +98 -39
  180. package/src/server/management/config-routes.ts +27 -7
  181. package/src/server/management/decision-routes.ts +2 -1
  182. package/src/server/management/integration-routes.ts +25 -2
  183. package/src/server/management/oauth-account-routes.ts +79 -98
  184. package/src/server/management/provider-instance-ownership.ts +44 -0
  185. package/src/server/management/provider-routes.ts +21 -28
  186. package/src/server/management/route-registry.ts +3 -1
  187. package/src/server/management/routing-profile-routes.ts +4 -4
  188. package/src/server/management/system-restart.ts +44 -14
  189. package/src/server/management/system-routes.ts +24 -2
  190. package/src/server/management/vision-sidecar-options.ts +9 -6
  191. package/src/server/management/web-search-sidecar-options.ts +51 -2
  192. package/src/server/management-auth.ts +6 -0
  193. package/src/server/messages-native-eligibility.ts +3 -2
  194. package/src/server/messages-native-oauth.ts +86 -41
  195. package/src/server/messages-native-scope.ts +4 -1
  196. package/src/server/messages-native-selector.ts +39 -0
  197. package/src/server/messages-native.ts +84 -25
  198. package/src/server/request-log.ts +5 -0
  199. package/src/server/responses/adapter-continuation.ts +29 -17
  200. package/src/server/responses/adapter-dispatch.ts +47 -17
  201. package/src/server/responses/antigravity-validation-refusal.ts +20 -6
  202. package/src/server/responses/combo-requested-effort.ts +14 -0
  203. package/src/server/responses/compact.ts +25 -8
  204. package/src/server/responses/compaction-recovery.ts +1 -1
  205. package/src/server/responses/core-combo-native.ts +4 -1
  206. package/src/server/responses/core-combo.ts +21 -18
  207. package/src/server/responses/core-normalize.ts +1 -0
  208. package/src/server/responses/core-options.ts +3 -1
  209. package/src/server/responses/core-replay.ts +5 -0
  210. package/src/server/responses/passthrough-delivery.ts +7 -3
  211. package/src/server/responses/passthrough-dispatch.ts +21 -8
  212. package/src/server/responses/request-prepare.ts +37 -5
  213. package/src/server/responses/request-send-budget.ts +74 -37
  214. package/src/server/responses/request-spend.ts +325 -33
  215. package/src/server/responses/request-transport.ts +132 -52
  216. package/src/server/responses/run-turn-execution.ts +11 -3
  217. package/src/server/responses/sidecar-execution.ts +96 -14
  218. package/src/server/responses-undeclared-tool-guard.ts +5 -1
  219. package/src/server/workflow-refusal.ts +30 -0
  220. package/src/service/desktop-startup.ts +100 -3
  221. package/src/service/ownership-mutation-lease.d.mts +1 -1
  222. package/src/service/ownership-mutation-lease.mjs +5 -7
  223. package/src/sidecar/anthropic-binding.ts +79 -0
  224. package/src/sidecar/auth.ts +76 -2
  225. package/src/types/anthropic-account-pool.ts +33 -0
  226. package/src/types/config.ts +12 -28
  227. package/src/types/provider.ts +5 -0
  228. package/src/types/tools.ts +7 -0
  229. package/src/types.ts +4 -0
  230. package/src/usage/cost.ts +3 -2
  231. package/src/usage/expected-prices.ts +66 -14
  232. package/src/vision/anthropic-describe.ts +16 -3
  233. package/src/vision/index.ts +3 -0
  234. package/src/vision/plan.ts +19 -6
  235. package/src/web-search/alpha-search.ts +7 -1
  236. package/src/web-search/anthropic-executor.ts +16 -4
  237. package/src/web-search/header-deadline.ts +67 -0
  238. package/src/web-search/index.ts +7 -3
  239. package/src/web-search/loop.ts +32 -10
  240. package/src/web-search/passthrough-bridge.ts +4 -1
  241. package/src/web-search/sidecar-providers.ts +4 -3
  242. package/gui/dist/assets/App-BOUa-ek3.js +0 -54
  243. package/gui/dist/assets/Tray-DUtzB4zF.js +0 -1
  244. package/gui/dist/assets/index-DwHNDF4s.js +0 -94
  245. package/gui/dist/assets/quota-summary-BuL4WIBo.js +0 -1
@@ -111,6 +111,22 @@ export const INTEGRATION_CAPABILITIES: readonly Capability[] = [
111
111
  json: "payload",
112
112
  details: ["With --client, restore requires --profile; only Aside profiles accept that selector. No automatic drift confirmation or retry.","Preview preserves exact opId/profile/confirm-drift intent. Generic restore derives the client on the server; the returned plan does not embed opId or complete command input.","A refused or no-op preview is completed inspection, not applied work. A stale bound mutation exits5 with a fixed re-preview hint; no replacement token is automatically adopted."],
113
113
  },
114
+ {
115
+ command: ["commandcode", "restore"],
116
+ usage: "ocx commandcode restore --op <opId> [--confirm-drift] [--preview | --plan-fingerprint <token>] [--json]",
117
+ summary: "Restore a Command Code rollback operation, retaining client ownership and drift checks.",
118
+ routes: [{ method: "POST", path: "/api/client-integrations/commandcode/restore" }, { method: "POST", path: "/api/client-integrations/commandcode/restore/preview" }],
119
+ flags: [
120
+ { name: "--op", value: "string", summary: "Command Code operation ID; --op-id is an alias.", required: true },
121
+ { name: "--confirm-drift", value: "boolean", summary: "Explicitly allow replacing edits made after the snapshot." },
122
+ { name: "--preview", value: "boolean", summary: "Inspect restoration without mutating; exclusive with a commit fingerprint." },
123
+ { name: "--plan-fingerprint", value: "string", summary: "Optional versioned token from a matching preview; stale intent is refused without retry." },
124
+ { name: "--json", value: "boolean", summary: "Emit the result as JSON." },
125
+ ],
126
+ mutates: true,
127
+ json: "payload",
128
+ details: ["cmd restore is an alias. --client and --profile are rejected; the dedicated route binds the operation to Command Code.", "Older proxies without the dedicated route fail without a generic restore fallback. Redirects are refused.", "Preview preserves the operation and drift intent. A stale bound mutation requires a new explicit preview."],
129
+ },
114
130
  {
115
131
  command: ["claude", "config", "status"],
116
132
  usage: "ocx claude config status [--json]",
@@ -15,11 +15,14 @@ const DEFAULT_DEPS: CodexShimAutoRestoreCliDeps = {
15
15
  readConfig: readConfigDiagnostics,
16
16
  };
17
17
 
18
+ /** Identify command namespaces whose execution must not trigger automatic shim repair. */
18
19
  export function skipsCodexShimAutoRestore(command: string | undefined, args: string[]): boolean {
19
20
  if (command === "uninstall" || command === "remove") return true;
20
21
  if (command === "status" || command === "doctor") return true;
21
22
  // `lab` is read-only inspection; it must not trigger shim side effects.
22
23
  if (command === "lab") return true;
24
+ // Local messaging owns its helpers; even malformed usage must not repair/start anything.
25
+ if (command === "message") return true;
23
26
  // `resolve` is read-only inspection for embedding shells: a lookup made to populate
24
27
  // a consent surface must not trigger a shim repair side effect first.
25
28
  if (command === "resolve") return true;
@@ -145,6 +145,7 @@ const commandRunners: Record<string, CommandRunner> = {
145
145
  if (!deps.head.resolveArgs) return 64;
146
146
  return await deps.handleResolve(deps.head.resolveArgs);
147
147
  },
148
+ message: async deps => (await import("./message-command")).runMessageCommand(deps.args.slice(1)),
148
149
  restore: async deps => {
149
150
  const restoreArgs = deps.args.slice(1);
150
151
  const restoreJson = takeFlag(restoreArgs, "--json");
@@ -465,7 +466,7 @@ const commandRunners: Record<string, CommandRunner> = {
465
466
  },
466
467
  config,
467
468
  port: live.port,
468
- }, ["mcode", "pi", "raycast", "omo", "cline", "droid", "opencode", "kilo"]));
469
+ }, ["mcode", "pi", "raycast", "omo", "cline", "commandcode", "droid", "opencode", "kilo"]));
469
470
  } catch (error) {
470
471
  console.warn(`Client integrations were not refreshed: ${error instanceof Error ? error.message : String(error)}`);
471
472
  }
@@ -961,6 +962,14 @@ const commandRunners: Record<string, CommandRunner> = {
961
962
  const { handleZcodeCommand } = await import("./integrations");
962
963
  return await handleZcodeCommand(deps.args.slice(1));
963
964
  },
965
+ commandcode: async deps => {
966
+ const { handleCommandcodeCommand } = await import("./integrations");
967
+ return await handleCommandcodeCommand(deps.args.slice(1));
968
+ },
969
+ cmd: async deps => {
970
+ const { handleCommandcodeCommand } = await import("./integrations");
971
+ return await handleCommandcodeCommand(deps.args.slice(1));
972
+ },
964
973
  help: async () => {
965
974
  printUsage();
966
975
  return 0;
package/src/cli/help.ts CHANGED
@@ -79,6 +79,7 @@ Usage:
79
79
  ocx alias <sub> Short names for providers and models (list, set, rm, defaults)
80
80
  ocx combo <sub> Combo routing strategies and failover
81
81
  ocx agent <sub> Subagents, injection, effort caps, and sidecars
82
+ ocx message <sub> Loaded local Codex sessions and queued peer messages (sessions|send)
82
83
  ocx effort [sub] Inspect and configure reasoning effort caps and defaults
83
84
  ocx observe <sub> Logs, usage, storage, memory, and debug data
84
85
  ocx inspect <sub> Effective config, catalog, analytics, pacing, client-config
@@ -91,7 +92,7 @@ Usage:
91
92
  ocx api-key <sub> Alias of ocx access key
92
93
  ocx access <sub> External API keys and endpoint information
93
94
  ocx api <sub> Protocol paths: vocabulary, request-path preview, and policy
94
- ocx export --client <id> Print a client config wired to the running proxy (17 clients)
95
+ ocx export --client <id> Print a client config wired to the running proxy (18 clients)
95
96
  ocx integration client <sub> Enable, disable, inspect or roll back a client integration
96
97
  ocx grok <sub> Grok Build model selection and apply
97
98
  ocx system <sub> Runtime settings, startup, sync, OpenCodex updates, and Codex CLI inspection
@@ -105,6 +106,8 @@ Usage:
105
106
  ocx mcode [args...] Launch MiniMax Code through its managed provider
106
107
  ocx mmx text <sub> [args] Launch MiniMax CLI text through the proxy
107
108
  ocx zcode [sub] Connect ZCode to the proxy (managed provider)
109
+ ocx commandcode [sub] Connect Command Code CLI to the proxy (managed provider)
110
+ ocx cmd [sub] Alias of ocx commandcode
108
111
  ocx help [command] Show help
109
112
  ocx --version | -v Print version
110
113
 
package/src/cli/index.ts CHANGED
@@ -210,11 +210,17 @@ initializeNodeLauncherContext();
210
210
  // The compiled executable is also the capture-only MCP server's launcher.
211
211
  // Handle this private entrypoint before CLI preflight or command dispatch.
212
212
  if (process.argv[2] === "__keyring-load-check") { console.log(JSON.stringify((await import("../lib/keyring-native")).inspectKeyringBinding())); process.exit(0); }
213
- if (process.argv[2] === "__codebuddy-mcp") {
214
- const { runCodeBuddyMcpServer } = await import("../adapters/codebuddy/mcp-server");
215
- await runCodeBuddyMcpServer(process.argv[3] ?? "");
216
- // The MCP stdio loop owns this process until stdin closes; do not fall through
217
- // to ordinary CLI dispatch or exit after the handshake completes.
213
+
214
+ // The compiled executable also launches the isolated MCP servers.
215
+ // Handle these private entrypoints before CLI preflight or command dispatch.
216
+ if (process.argv[2] === "__codebuddy-mcp" || process.argv[2] === "__qoder-mcp") {
217
+ if (process.argv[2] === "__codebuddy-mcp") {
218
+ const { runCodeBuddyMcpServer } = await import("../adapters/codebuddy/mcp-server");
219
+ await runCodeBuddyMcpServer(process.argv[3] ?? "");
220
+ } else {
221
+ const { runCodingAgentMcpServer } = await import("../adapters/coding-agent/mcp-server");
222
+ await runCodingAgentMcpServer(process.argv[3] ?? "");
223
+ }
218
224
  await new Promise<never>(() => {});
219
225
  }
220
226
 
@@ -23,6 +23,7 @@ export const FILE_INTEGRATION_CLIENTS = [
23
23
  "raycast",
24
24
  "omo",
25
25
  "cline",
26
+ "commandcode",
26
27
  "kilo",
27
28
  "droid",
28
29
  ] as const;
@@ -1,5 +1,6 @@
1
1
  /** Preview and explicitly bound mutations use the existing management authority. */
2
2
  import type { IntegrationMutationPlan, IntegrationPlanOperation } from "../integrations/mutation-plan";
3
+ import type { IntegrationClientId } from "../integrations/registry";
3
4
  import { redactSecretString, redactUserPath } from "../lib/redact";
4
5
  import {
5
6
  CliUsageError, RuntimeApiError, printData, runtimeRequest, terminalSafeText,
@@ -186,9 +187,15 @@ function reportError(error: unknown): number {
186
187
  return 1;
187
188
  }
188
189
 
189
- export async function handleIntegrationPreviewCommand(argv: string[], deps: RuntimeApiDeps = {}): Promise<number> {
190
+ export async function handleIntegrationPreviewCommand(argv: string[], deps: RuntimeApiDeps = {}, expectedClientId?: IntegrationClientId): Promise<number> {
190
191
  try {
191
192
  const intent = parseIntent(argv);
193
+ if (expectedClientId !== undefined && intent.operation === "restore") {
194
+ if (intent.client !== undefined || intent.profile !== undefined) throw new CliUsageError("client-specific restore does not accept --client or --profile", USAGE);
195
+ intent.client = expectedClientId;
196
+ intent.path = `${clientIntegrationPath(expectedClientId)}/restore${intent.preview ? "/preview" : ""}`;
197
+ intent.body.expectedClientId = expectedClientId;
198
+ }
192
199
  const result = await runtimeRequest(intent.path, {
193
200
  method: intent.preview || intent.operation === "restore" ? "POST" : "PUT",
194
201
  redirect: "error", body: JSON.stringify(intent.body),
@@ -13,6 +13,7 @@ import {
13
13
  type RuntimeApiDeps,
14
14
  } from "./runtime-api";
15
15
  import { clientIntegrationPath, validateAsideProfile } from "./integration-input";
16
+ import type { IntegrationClientId } from "../integrations/registry";
16
17
 
17
18
  const CLAUDE_USAGE = `Usage:
18
19
  ocx claude config [status] [--json]
@@ -215,7 +216,11 @@ function singleClientStatusLines(result: unknown): string[] {
215
216
  export async function handleClientIntegrationCommand(
216
217
  argv: string[],
217
218
  deps: RuntimeApiDeps = {},
219
+ expectedClientId?: IntegrationClientId,
218
220
  ): Promise<number> {
221
+ if (expectedClientId !== undefined && argv.some(arg => ["--client", "--profile"].some(flag => arg === flag || arg.startsWith(`${flag}=`)))) {
222
+ return runCliAction(async () => { throw new CliUsageError("client-specific restore does not accept --client or --profile", CLIENT_USAGE); });
223
+ }
219
224
  if ((argv[0] === "history" || argv[0] === "journal") && argv[1] === "remove") {
220
225
  const { handleIntegrationJournalRemove } = await import("./integration-journal");
221
226
  return handleIntegrationJournalRemove(argv.slice(2), deps);
@@ -227,7 +232,7 @@ export async function handleClientIntegrationCommand(
227
232
  if (argv[0] === "preview" || argv.some(arg => ["--preview", "--plan-fingerprint", "--reasoning-default", "--clear-reasoning-defaults"]
228
233
  .some(flag => arg === flag || arg.startsWith(`${flag}=`)))) {
229
234
  const { handleIntegrationPreviewCommand } = await import("./integration-preview");
230
- return handleIntegrationPreviewCommand(argv, deps);
235
+ return handleIntegrationPreviewCommand(argv, deps, expectedClientId);
231
236
  }
232
237
  return runCliAction(async () => {
233
238
  const args = [...argv];
@@ -289,9 +294,12 @@ export async function handleClientIntegrationCommand(
289
294
  if (client !== undefined && profile === undefined) throw new CliUsageError("restore --client requires --profile", CLIENT_USAGE);
290
295
  rejectArgs(args, CLIENT_USAGE);
291
296
  if (!opId) throw new CliUsageError("--op <opId> is required", CLIENT_USAGE);
292
- const result = await runtimeRequest(profile === undefined ? "/api/client-integrations/restore" : `${clientIntegrationPath("aside", profile)}/restore`, {
297
+ const restorePath = expectedClientId !== undefined ? `${clientIntegrationPath(expectedClientId)}/restore`
298
+ : profile === undefined ? "/api/client-integrations/restore" : `${clientIntegrationPath("aside", profile)}/restore`;
299
+ const result = await runtimeRequest(restorePath, {
293
300
  method: "POST",
294
- body: JSON.stringify({ opId, confirmDrift }),
301
+ ...(expectedClientId === undefined ? {} : { redirect: "error" as const }),
302
+ body: JSON.stringify({ opId, confirmDrift, ...(expectedClientId === undefined ? {} : { expectedClientId }) }),
295
303
  }, deps);
296
304
  printData(result, wantsJson, [String((result as Record<string, unknown>).message ?? "Restored.")]);
297
305
  return;
@@ -387,3 +395,24 @@ export async function handleClaudeInterceptCommand(argv: string[], deps: Runtime
387
395
  printData(result, wantsJson, summaryLines(result));
388
396
  });
389
397
  }
398
+
399
+ const COMMANDCODE_USAGE = "Usage: ocx commandcode [status|show|list|enable|disable|history|journal|restore] [--json]";
400
+
401
+ export async function handleCommandcodeCommand(argv: string[], deps: RuntimeApiDeps = {}): Promise<number> {
402
+ const args = [...argv];
403
+ const verbIndex = args.findIndex(arg => !arg.startsWith("-"));
404
+ const action = (verbIndex === -1 ? "status" : args[verbIndex]).toLowerCase();
405
+ const known = ["status", "show", "list", "enable", "disable", "history", "journal", "restore"];
406
+ if (!known.includes(action)) {
407
+ console.error(`unknown commandcode command ${action}`);
408
+ console.error(COMMANDCODE_USAGE);
409
+ return 2;
410
+ }
411
+ const rest = verbIndex === -1 ? args : [...args.slice(0, verbIndex), ...args.slice(verbIndex + 1)];
412
+ const forwarded = action === "restore" ? [action, ...rest] : [action, ...rest, "--client", "commandcode"];
413
+ const code = await handleClientIntegrationCommand(forwarded, deps, action === "restore" ? "commandcode" : undefined);
414
+ if (code === 0 && (action === "enable" || action === "disable")) {
415
+ console.error("Command Code reads providers on startup.");
416
+ }
417
+ return code;
418
+ }
@@ -0,0 +1,34 @@
1
+ import type { MessageKind } from "../messaging/envelope";
2
+ import { isThreadId } from "../messaging/types";
3
+
4
+ export type MessageArgs = { action: "sessions"; json: boolean }
5
+ | { action: "send"; json: boolean; kind: MessageKind; thread?: string; name?: string; inReplyTo?: string };
6
+
7
+ /** Pure pre-parse: unknown/duplicate options and unsupported remote surfaces fail closed. */
8
+ export function parseMessageArgs(argv: readonly string[]): MessageArgs | null {
9
+ const action = argv[0];
10
+ if (action !== "sessions" && action !== "send") return null;
11
+ const seen = new Set<string>();
12
+ const values: Record<string, string> = {};
13
+ for (let index = 1; index < argv.length; index++) {
14
+ const flag = argv[index]!;
15
+ if (seen.has(flag)) return null;
16
+ seen.add(flag);
17
+ if (flag === "--json" || (action === "send" && flag === "--stdin")) continue;
18
+ if (action !== "send" || !["--thread", "--name", "--kind", "--in-reply-to"].includes(flag)) return null;
19
+ const value = argv[++index];
20
+ if (!value || value.startsWith("--")) return null;
21
+ values[flag] = value;
22
+ }
23
+ const json = seen.has("--json");
24
+ if (action === "sessions") return { action, json };
25
+ const thread = values["--thread"], name = values["--name"], inReplyTo = values["--in-reply-to"];
26
+ const kind = values["--kind"] ?? "request";
27
+ if (!seen.has("--stdin") || (thread !== undefined) === (name !== undefined)
28
+ || (thread !== undefined && !isThreadId(thread))
29
+ || (name !== undefined && (!name.trim() || name.length > 4096 || /[\x00-\x1f\x7f-\x9f]/.test(name)))
30
+ || !["request", "response", "notification"].includes(kind)
31
+ || (kind === "response" ? !isThreadId(inReplyTo) : inReplyTo !== undefined)) return null;
32
+ return { action, json, kind: kind as MessageKind, ...(thread ? { thread } : {}),
33
+ ...(name ? { name } : {}), ...(inReplyTo ? { inReplyTo } : {}) };
34
+ }
@@ -0,0 +1,43 @@
1
+ import { MessageBudget } from "../messaging/budget";
2
+ import { readMessageInput } from "../messaging/input";
3
+ import { localSessions, messageFailure, sendLocalMessage } from "../messaging/send";
4
+ import { parseMessageArgs } from "./message-args";
5
+ import { terminalSafeText } from "./runtime-api";
6
+
7
+ /** Run one local messaging operation after pure syntax validation, then release owned resources. */
8
+ export async function runMessageCommand(argv: readonly string[], env: NodeJS.ProcessEnv = process.env): Promise<number> {
9
+ const args = parseMessageArgs(argv);
10
+ if (!args) {
11
+ console.error("Usage: ocx message sessions [--json] | send (--thread <uuid> | --name <exact-name>) --stdin [--kind request|response|notification] [--in-reply-to <uuid>] [--json]");
12
+ return 64;
13
+ }
14
+ const controller = new AbortController();
15
+ const cancel = () => controller.abort();
16
+ process.once("SIGINT", cancel); process.once("SIGTERM", cancel);
17
+ const budget = new MessageBudget(30_000, controller.signal);
18
+ try {
19
+ // Keep home selection on this command path and after syntax validation.
20
+ const { messageCodexHome } = await import("./message-runtime");
21
+ const home = messageCodexHome(env);
22
+ if (args.action === "sessions") {
23
+ const sessions = await localSessions(home, budget);
24
+ if (args.json) console.log(JSON.stringify({ schema: "ocx-message-sessions/1", sessions }));
25
+ else for (const session of sessions) console.log(terminalSafeText(`${session.id}\t${session.status}\t${session.name ?? "(unnamed)"}`));
26
+ return 0;
27
+ }
28
+ const body = await readMessageInput(Bun.stdin.stream(), budget);
29
+ const receipt = await sendLocalMessage({ ...args, body },
30
+ { home, senderId: env.CODEX_THREAD_ID }, budget);
31
+ if (args.json) console.log(JSON.stringify(receipt));
32
+ else console.log(terminalSafeText(`${receipt.status}: ${receipt.messageId}${receipt.error ? ` — ${receipt.error.message}` : " (submitted, not proof of processing)"}`));
33
+ return receipt.status === "queued" ? 0 : receipt.status === "unknown" ? 3 : 1;
34
+ } catch (error) {
35
+ const failure = { schema: "ocx-message-error/1", ...(args.action === "send" ? { status: "not_sent" } : {}), error: messageFailure(error) };
36
+ if (args.json) console.log(JSON.stringify(failure));
37
+ else console.error(failure.error.message);
38
+ return 1;
39
+ } finally {
40
+ budget.dispose();
41
+ process.removeListener("SIGINT", cancel); process.removeListener("SIGTERM", cancel);
42
+ }
43
+ }
@@ -0,0 +1,6 @@
1
+ import { resolveCodexHomeDir } from "../codex/home";
2
+
3
+ /** Resolve the effective existing Codex home using the shared policy, without creating it. */
4
+ export function messageCodexHome(env: NodeJS.ProcessEnv): string {
5
+ return resolveCodexHomeDir({ env });
6
+ }
@@ -129,7 +129,9 @@ async function explain(argv: string[], deps: RuntimeApiDeps): Promise<void> {
129
129
  rejectArgs(args, USAGE);
130
130
  const encoded = encodeURIComponent(requestId);
131
131
  const result = await runtimeRequest(`/api/request-history/${encoded}/route-decision`, {}, deps);
132
- printData(result, wantsJson, wantsJson ? undefined : [JSON.stringify(result, null, 2)]);
132
+ // One entry per line: printData escapes control characters per entry, so a single
133
+ // pretty-printed string would print its line breaks as literal `\x0a`.
134
+ printData(result, wantsJson, wantsJson ? undefined : JSON.stringify(result, null, 2).split("\n"));
133
135
  }
134
136
 
135
137
  async function rebuildIndex(argv: string[], deps: RuntimeApiDeps): Promise<void> {
@@ -8,11 +8,12 @@
8
8
  * show <name> Show provider config details (secrets masked)
9
9
  * set-default <name> Change the default provider
10
10
  */
11
- import { hasOwnProvider, isValidProviderName, loadConfig, sanitizeModelCostsForDisplay, saveConfig, validateConfigCandidate } from "../config";
11
+ import { hasOwnProvider, isValidProviderName, loadConfig, sanitizeModelCostsForDisplay, saveConfig, validateConfigCandidate, withConfigMutationLockSync } from "../config";
12
12
  import { apiKeyTransportConfigError, modelCapabilitiesConfigError, mergeModelCapabilities } from "../config/provider-validation";
13
13
  import { hasHelpFlag, printSubcommandUsage } from "./help";
14
14
  import { getProviderRegistryEntry, PROVIDER_REGISTRY } from "../providers/registry";
15
15
  import { providerConfigSeed } from "../providers/derive";
16
+ import { assertAnthropicInstanceLoginConfig } from "../oauth/store-anthropic-instance";
16
17
  import { dropProviderCustomModels } from "../providers/provider-id-rewrite";
17
18
  import type { OcxProviderConfig } from "../types";
18
19
  import { findLiveProxy } from "../server/proxy-liveness";
@@ -249,6 +250,17 @@ async function handleAdd(args: string[], deps: ProviderCommandDeps): Promise<voi
249
250
 
250
251
  if (responsesPath !== undefined) provConfig.responsesPath = responsesPath;
251
252
  if (authMode !== undefined) provConfig.authMode = authMode as OcxProviderConfig["authMode"];
253
+ if (name === "anthropic2") {
254
+ if (provConfig.adapter !== "anthropic" || provConfig.authMode !== "oauth") delete provConfig.anthropicOAuthInstance;
255
+ if (provConfig.anthropicOAuthInstance) {
256
+ try { assertAnthropicInstanceLoginConfig(config, name); }
257
+ catch {
258
+ console.error("Error: cannot add Pool 2 over an existing custom or unreadable provider configuration; resolve it first.");
259
+ process.exitCode = 2;
260
+ return;
261
+ }
262
+ }
263
+ }
252
264
  if (name === "openai" && (authMode !== undefined || responsesPath !== undefined)
253
265
  && !isCanonicalOpenAiForwardProvider(provConfig)) {
254
266
  console.error("Error: Canonical OpenAI must keep its built-in forward destination and authentication. Use a separate provider name for a custom endpoint.");
@@ -315,11 +327,22 @@ async function handleAdd(args: string[], deps: ProviderCommandDeps): Promise<voi
315
327
  return;
316
328
  }
317
329
  const { initializeProviderModelSelection } = await import("../providers/initial-model-selection");
318
- initializeProviderModelSelection(name, provConfig, existingProvider, config);
319
- config.providers[name] = provConfig;
320
- if (setDefault) config.defaultProvider = name;
321
-
322
- validateAndSave(config);
330
+ let markerCollision = false;
331
+ withConfigMutationLockSync(() => {
332
+ if (provConfig.anthropicOAuthInstance) {
333
+ try { assertAnthropicInstanceLoginConfig(config, name); }
334
+ catch { markerCollision = true; return; }
335
+ }
336
+ initializeProviderModelSelection(name, provConfig, existingProvider, config);
337
+ config.providers[name] = provConfig;
338
+ if (setDefault) config.defaultProvider = name;
339
+ validateAndSave(config);
340
+ });
341
+ if (markerCollision) {
342
+ console.error("Error: provider configuration changed while adding Pool 2; resolve the ownership collision first.");
343
+ process.exitCode = 2;
344
+ return;
345
+ }
323
346
 
324
347
  let sync: LocalSyncResult | undefined;
325
348
  if (wantsSync) {
@@ -8,6 +8,12 @@ export interface CliCommandEntry {
8
8
  }
9
9
 
10
10
  export const CLI_COMMANDS: CliCommandEntry[] = [
11
+ {
12
+ name: "message",
13
+ usage: "ocx message <sessions|send> [options]",
14
+ summary: "Discover loaded local Codex sessions and submit one queued peer message.",
15
+ details: ["Local Unix transport only; starts no daemon. queued is submission, not processing; never replay an unknown send."],
16
+ },
11
17
  {
12
18
  name: "chatgpt",
13
19
  usage: "ocx chatgpt <launch|restore|status>",
@@ -467,8 +473,8 @@ export const CLI_COMMANDS: CliCommandEntry[] = [
467
473
  },
468
474
  {
469
475
  name: "export",
470
- usage: "ocx export --client <opencode|pi|omp|hermes|openclaw|kimi|gajae|dsh|mcode|zcode|prime|aside|raycast|omo|cline|kilo|droid> [--json] [--out <path>] [--force]",
471
- summary: "Print a client config (OpenCode, Pi, OMP, Hermes, OpenClaw, Kimi Code, gjc, DeepSeek Harness, MiniMax Code, ZCode, Prime Agent, Aside, Raycast, omo, Cline, Kilo, Factory Droid) wired to the running proxy.",
476
+ usage: "ocx export --client <opencode|pi|omp|hermes|openclaw|kimi|gajae|dsh|mcode|zcode|commandcode|prime|aside|raycast|omo|cline|kilo|droid> [--json] [--out <path>] [--force]",
477
+ summary: "Print a client config (OpenCode, Pi, OMP, Hermes, OpenClaw, Kimi Code, gjc, DeepSeek Harness, MiniMax Code, ZCode, Command Code, Prime Agent, Aside, Raycast, omo, Cline, Kilo, Factory Droid) wired to the running proxy.",
472
478
  details: [
473
479
  "--json prints the generated document as JSON on stdout; use --out for the client's native format.",
474
480
  "--out <path> writes the native config there and refuses to replace an existing file without --force.",
@@ -598,6 +604,22 @@ export const CLI_COMMANDS: CliCommandEntry[] = [
598
604
  "Select OpenCodex Proxy/<provider>/<model> from ZCode's model picker.",
599
605
  ],
600
606
  },
607
+ {
608
+ name: "commandcode",
609
+ usage: "ocx commandcode [status|show|list|enable|disable|history|journal|restore --op <opId>] [--json]",
610
+ summary: "Connect Command Code CLI to the proxy via its managed provider.",
611
+ details: [
612
+ "Alias of ocx integration client <sub> --client commandcode.",
613
+ "enable manages the opencodex block under the existing provider or providers root in ~/.commandcode/providers.json; disable removes only that block.",
614
+ "Command Code reads providers on startup.",
615
+ "`restore --op <opId>` replays the exact previous managed block; take the id from `ocx commandcode history`.",
616
+ ],
617
+ },
618
+ {
619
+ name: "cmd",
620
+ usage: "ocx cmd [status|show|list|enable|disable|history|journal|restore --op <opId>] [--json]",
621
+ summary: "Alias of ocx commandcode.",
622
+ },
601
623
  {
602
624
  name: "restart",
603
625
  usage: "ocx restart",
@@ -0,0 +1,131 @@
1
+ // Command Code config export.
2
+ import type { ExportContext, ManagedContribution } from "./contracts";
3
+ import { normalizeExportModels, authoritativeContextWindow, singleFragment } from "./model-metadata";
4
+ import { OPENCODE_PROVIDER_ID, LOOPBACK_API_KEY_PLACEHOLDER } from "./constants";
5
+ import { sanitizeCodexReasoningEfforts } from "../../reasoning-effort";
6
+
7
+ export interface CommandCodeModelEntry {
8
+ reasoningEfforts?: string[];
9
+ contextWindow?: number;
10
+ }
11
+
12
+ export interface CommandCodeProviderBlock {
13
+ name: "OpenCodex";
14
+ api: "openai-completions";
15
+ baseURL: string;
16
+ apiKey: string | boolean;
17
+ models: Record<string, CommandCodeModelEntry>;
18
+ }
19
+
20
+ export interface CommandCodeGeneratedConfig {
21
+ provider: Record<string, CommandCodeProviderBlock>;
22
+ /** The root a pre-existing target already uses; see {@link commandCodeProviderRoot}. */
23
+ providers?: Record<string, CommandCodeProviderBlock>;
24
+ }
25
+
26
+ /**
27
+ * LOSSY spelling-collision key for a routed selector — not model identity.
28
+ *
29
+ * One provider model reaches this exporter under interchangeable spellings: the raw
30
+ * selector keeps inner slashes (`command-code/deepseek/deepseek-v4.1-flash`, what
31
+ * `/v1/models` publishes) while the Codex-facing form encodes them as dashes
32
+ * (`command-code/deepseek-deepseek-v4.1-flash`, what `~/.codex/config.toml` stores and
33
+ * what an operator typing `--model` copies). Command Code addresses models by exact key,
34
+ * so a catalog that carries both spellings can resolve the active model against one and
35
+ * miss it in the other.
36
+ *
37
+ * The slash is meaningful: it separates the provider from the model id, and a provider id
38
+ * never contains one. But the encoding is NOT bijective — `p/a/b` and `p/a-b` are
39
+ * different model ids that encode to the same string. That is the same lossy relation
40
+ * `slugEquivalenceKey` documents in src/providers/slug-codec.ts and the same ambiguity
41
+ * `resolveSlugAliasCollisions` guards in the catalog. Callers must therefore never drop
42
+ * a row on this key alone; buildCommandCodeClientConfig additionally compares provider
43
+ * and id before folding two rows into one.
44
+ */
45
+ function canonicalSpellingOf(namespaced: string): string {
46
+ const slash = namespaced.indexOf("/");
47
+ if (slash <= 0) return namespaced;
48
+ return namespaced.slice(0, slash) + "/" + namespaced.slice(slash + 1).replaceAll("/", "-");
49
+ }
50
+
51
+ export function buildCommandCodeClientConfig(ctx: ExportContext): CommandCodeGeneratedConfig {
52
+ const models: Record<string, CommandCodeModelEntry> = {};
53
+ // Fold interchangeable spellings of one model onto a single key. The first
54
+ // occurrence wins per provider+id identity; distinct model ids that happen to
55
+ // share a lossy spelling collision must both remain addressable.
56
+ const emittedIdentitiesByCollision = new Map<string, Set<string>>();
57
+ for (const model of normalizeExportModels(ctx.models)) {
58
+ const emittedKey = model.namespaced;
59
+ const collisionKey = canonicalSpellingOf(model.namespaced);
60
+ const identity = `${model.provider}\u0000${model.id}`;
61
+ const identities = emittedIdentitiesByCollision.get(collisionKey) ?? new Set<string>();
62
+ if (identities.has(identity)) continue;
63
+ identities.add(identity);
64
+ emittedIdentitiesByCollision.set(collisionKey, identities);
65
+ const entry: CommandCodeModelEntry = {};
66
+ const context = authoritativeContextWindow(model.contextWindow);
67
+ if (context !== undefined) {
68
+ entry.contextWindow = context;
69
+ }
70
+ const efforts = sanitizeCodexReasoningEfforts(model.reasoningEfforts)
71
+ ?.filter(effort => ["low", "medium", "high", "xhigh", "max"].includes(effort));
72
+ if (efforts && efforts.length > 0) {
73
+ entry.reasoningEfforts = efforts;
74
+ }
75
+ models[emittedKey] = entry;
76
+ }
77
+ return {
78
+ provider: {
79
+ [OPENCODE_PROVIDER_ID]: {
80
+ name: "OpenCodex",
81
+ api: "openai-completions",
82
+ baseURL: ctx.baseUrl.replace(/\/v1\/?$/, "") + "/v1",
83
+ apiKey: false,
84
+ models,
85
+ },
86
+ },
87
+ };
88
+ }
89
+
90
+ /**
91
+ * The root key Command Code actually reads: `provider`, else `providers`.
92
+ *
93
+ * Verified against the published client (`command-code@1.66.0`, `dist/cli.mjs`):
94
+ * `const o = e.provider ?? e.providers;` — singular wins, and the plural branch is
95
+ * only reached when the singular root is nullish (`null` or `undefined`). Adding a
96
+ * singular root to a document that already carries a plural one therefore does not
97
+ * merge the two: the old entries stay on disk, byte for byte, and become invisible
98
+ * to the consumer.
99
+ *
100
+ * Evaluates nullish precedence before container validation so that non-nullish
101
+ * invalid singular values (such as strings or arrays) remain selected and are refused
102
+ * by the container guard rather than falling through to write an unreachable plural block.
103
+ */
104
+ export function commandCodeProviderRoot(document: unknown): "provider" | "providers" {
105
+ const doc = document as { provider?: unknown; providers?: unknown } | null | undefined;
106
+ if (doc?.provider !== undefined && doc?.provider !== null) return "provider";
107
+ if (doc?.providers !== undefined && doc?.providers !== null) return "providers";
108
+ // Nothing established yet: the singular root is the one this exporter writes.
109
+ return "provider";
110
+ }
111
+
112
+ export function summarizeCommandCode(document: unknown): { modelCount: number; modelsWithoutLimits: number } {
113
+ const doc = document as CommandCodeGeneratedConfig | undefined;
114
+ const root = commandCodeProviderRoot(document);
115
+ const models = Object.values(doc?.[root]?.[OPENCODE_PROVIDER_ID]?.models ?? {}) as CommandCodeModelEntry[];
116
+ return { modelCount: models.length, modelsWithoutLimits: models.filter(model => model.contextWindow === undefined).length };
117
+ }
118
+
119
+ /**
120
+ * The provider block, written under whichever root the target document already uses.
121
+ *
122
+ * `ExportContext` carries the parsed target document so the root can be chosen from
123
+ * what is on disk rather than assumed. A fresh file gets the singular root this
124
+ * exporter has always written; a document that already carries a plural root keeps
125
+ * it, so enabling OpenCodex never makes the user's other providers unreadable.
126
+ */
127
+ export function buildCommandCodeContribution(ctx: ExportContext): ManagedContribution {
128
+ const doc = buildCommandCodeClientConfig(ctx);
129
+ const root = commandCodeProviderRoot(ctx.document);
130
+ return singleFragment("commandcode", [root, OPENCODE_PROVIDER_ID], doc.provider[OPENCODE_PROVIDER_ID]);
131
+ }
@@ -185,6 +185,17 @@ export interface ExportContext {
185
185
  */
186
186
  config?: OcxConfig;
187
187
  droidReasoningDefaults?: DroidReasoningDefaults;
188
+ /**
189
+ * The parsed target document, when the caller has one in hand.
190
+ *
191
+ * A client whose reader picks between two roots needs the bytes on disk to
192
+ * choose: Command Code resolves `document.provider ?? document.providers`, so
193
+ * writing the singular root into a document that already carries the plural one
194
+ * leaves the user's providers unreadable rather than merging with them. Absent
195
+ * for `ocx export`, which has no target file — the builder then writes its
196
+ * default root, exactly as before.
197
+ */
198
+ document?: unknown;
188
199
  }
189
200
 
190
201
  /** Namespaced model selector to one of that model's declared reasoning efforts. */
@@ -206,6 +217,7 @@ export type ExportClientId =
206
217
  | "raycast"
207
218
  | "omo"
208
219
  | "cline"
220
+ | "commandcode"
209
221
  | "kilo"
210
222
  | "droid";
211
223