@bitkyc08/opencodex 2.76.0 → 2.78.0-preview.20261005

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 (353) hide show
  1. package/bin/ocx.mjs +11 -10
  2. package/gui/dist/assets/App-CppMz-6w.js +54 -0
  3. package/gui/dist/assets/App-lCIslZ1L.css +1 -0
  4. package/gui/dist/assets/{Tray-DjNwwrWS.js → Tray-CJWQqtVv.js} +1 -1
  5. package/gui/dist/assets/index-DQt9srNi.js +94 -0
  6. package/gui/dist/assets/index-DmQB8D_R.css +1 -0
  7. package/gui/dist/assets/quota-summary-BMsyoZH2.js +1 -0
  8. package/gui/dist/index.html +4 -3
  9. package/package.json +1 -1
  10. package/src/adapters/anthropic/beta-allowlist.ts +19 -2
  11. package/src/adapters/anthropic/client-identity.ts +13 -4
  12. package/src/adapters/anthropic/native-client-preamble.ts +31 -0
  13. package/src/adapters/anthropic/passthrough.ts +103 -25
  14. package/src/adapters/antigravity-refusal.ts +28 -0
  15. package/src/adapters/cursor/protobuf-request.ts +6 -0
  16. package/src/adapters/cursor/request-builder.ts +3 -0
  17. package/src/adapters/cursor/structured-output.ts +13 -0
  18. package/src/adapters/cursor/types.ts +2 -0
  19. package/src/adapters/devin/cloud-direct/chat.ts +7 -2
  20. package/src/adapters/devin/trajectory.ts +49 -0
  21. package/src/adapters/devin.ts +13 -0
  22. package/src/adapters/ollama-native.ts +62 -19
  23. package/src/adapters/openai-responses/passthrough.ts +133 -103
  24. package/src/adapters/openai-responses/request-strips.ts +10 -4
  25. package/src/adapters/zed.ts +26 -4
  26. package/src/bridge/response-json.ts +14 -14
  27. package/src/bridge/sse.ts +15 -5
  28. package/src/chatgpt/app-server-shim/bundle-trust.ts +40 -10
  29. package/src/chatgpt/app-server-shim/gate-rewrite.ts +3 -0
  30. package/src/claude/agents-inject.ts +4 -43
  31. package/src/claude/gateway-cache.ts +16 -13
  32. package/src/claude/inbound-model-options.ts +9 -2
  33. package/src/claude/intercept/cli-catalog.ts +27 -16
  34. package/src/claude/intercept/client-hello.ts +103 -0
  35. package/src/claude/intercept/model-bindings.ts +3 -2
  36. package/src/claude/intercept/picker-bootstrap.ts +37 -16
  37. package/src/claude/intercept/picker-budget.ts +41 -0
  38. package/src/claude/intercept/picker-listener.ts +279 -66
  39. package/src/claude/outbound.ts +2 -0
  40. package/src/claude/subagent-force-status.ts +53 -0
  41. package/src/claude/subagent-model.ts +93 -0
  42. package/src/cli/access-audio-input.ts +84 -0
  43. package/src/cli/access-audio-live.ts +99 -0
  44. package/src/cli/access-audio.ts +123 -0
  45. package/src/cli/access-data-client.ts +127 -0
  46. package/src/cli/access-data-plane.ts +104 -0
  47. package/src/cli/access-data-response.ts +76 -0
  48. package/src/cli/access.ts +65 -2
  49. package/src/cli/account-api.ts +33 -6
  50. package/src/cli/account-auth.ts +111 -42
  51. package/src/cli/account-key-quota.ts +118 -0
  52. package/src/cli/account-main.ts +11 -4
  53. package/src/cli/account-next-actions.ts +63 -0
  54. package/src/cli/account-policy-dto.ts +68 -0
  55. package/src/cli/account-policy.ts +167 -0
  56. package/src/cli/account-target.ts +8 -0
  57. package/src/cli/account.ts +37 -9
  58. package/src/cli/agent-runtime-settings.ts +130 -0
  59. package/src/cli/agent-settings.ts +101 -0
  60. package/src/cli/agent.ts +29 -2
  61. package/src/cli/alias.ts +7 -2
  62. package/src/cli/aside-profiles.ts +2 -2
  63. package/src/cli/capabilities-access-remote.ts +147 -0
  64. package/src/cli/capabilities-accounts.ts +195 -0
  65. package/src/cli/capabilities-agents-routing.ts +414 -0
  66. package/src/cli/capabilities-base.ts +783 -0
  67. package/src/cli/capabilities-command.ts +1 -0
  68. package/src/cli/capabilities-integrations.ts +264 -0
  69. package/src/cli/capabilities-lab.ts +214 -0
  70. package/src/cli/capabilities-observe-system.ts +662 -0
  71. package/src/cli/capabilities-provider-models.ts +302 -0
  72. package/src/cli/capabilities.ts +30 -1190
  73. package/src/cli/capability-types.ts +54 -0
  74. package/src/cli/catalog-command-result.ts +57 -0
  75. package/src/cli/chatgpt-command.ts +35 -9
  76. package/src/cli/claude-agent-startup-sync.ts +17 -0
  77. package/src/cli/claude-desktop-profile.ts +100 -0
  78. package/src/cli/claude-desktop.ts +39 -6
  79. package/src/cli/claude.ts +22 -6
  80. package/src/cli/codex-shim-autorestore.ts +2 -1
  81. package/src/cli/codex-shim-readiness.ts +109 -0
  82. package/src/cli/combo-input.ts +88 -0
  83. package/src/cli/combo-result.ts +52 -0
  84. package/src/cli/combo-stats.ts +123 -0
  85. package/src/cli/combo.ts +37 -14
  86. package/src/cli/companion-timeline.ts +142 -0
  87. package/src/cli/companion-usage.ts +146 -0
  88. package/src/cli/companion.ts +12 -3
  89. package/src/cli/config-command.ts +24 -8
  90. package/src/cli/connect.ts +19 -0
  91. package/src/cli/dispatch.ts +55 -82
  92. package/src/cli/doctor.ts +22 -4
  93. package/src/cli/export-command.ts +20 -9
  94. package/src/cli/gui.ts +8 -3
  95. package/src/cli/help-catalog.ts +101 -0
  96. package/src/cli/help-models-context.ts +17 -0
  97. package/src/cli/help-navigation.ts +50 -0
  98. package/src/cli/help-recovery.ts +66 -0
  99. package/src/cli/help.ts +69 -11
  100. package/src/cli/index.ts +38 -27
  101. package/src/cli/injection-follow.ts +35 -0
  102. package/src/cli/inspect.ts +5 -0
  103. package/src/cli/integration-aside-sync.ts +69 -0
  104. package/src/cli/integration-cursor.ts +106 -0
  105. package/src/cli/integration-input.ts +60 -0
  106. package/src/cli/integration-journal.ts +49 -0
  107. package/src/cli/integration-plan-dto.ts +156 -0
  108. package/src/cli/integration-preview.ts +215 -0
  109. package/src/cli/integrations.ts +24 -18
  110. package/src/cli/json-input.ts +134 -0
  111. package/src/cli/link.ts +32 -4
  112. package/src/cli/local-sync-result.ts +41 -0
  113. package/src/cli/log-follow.ts +84 -0
  114. package/src/cli/log-view-filter.ts +146 -0
  115. package/src/cli/logout-command.ts +44 -0
  116. package/src/cli/model-picker-ordering.ts +137 -0
  117. package/src/cli/models-custom-input.ts +107 -0
  118. package/src/cli/models-custom-runtime.ts +112 -0
  119. package/src/cli/models-order.ts +194 -0
  120. package/src/cli/models-runtime-subcommands.ts +2 -0
  121. package/src/cli/models-runtime.ts +9 -1
  122. package/src/cli/models.ts +66 -81
  123. package/src/cli/observe-stream.ts +142 -0
  124. package/src/cli/observe.ts +75 -34
  125. package/src/cli/opencode.ts +54 -10
  126. package/src/cli/provider-batch.ts +77 -0
  127. package/src/cli/provider-lifecycle-runtime.ts +170 -0
  128. package/src/cli/provider-result.ts +124 -0
  129. package/src/cli/provider-runtime.ts +15 -12
  130. package/src/cli/provider-settings.ts +143 -0
  131. package/src/cli/provider.ts +116 -64
  132. package/src/cli/registry.ts +90 -24
  133. package/src/cli/remote-workspace-hub.ts +83 -0
  134. package/src/cli/remote-workspace.ts +6 -1
  135. package/src/cli/restart-failure.ts +27 -0
  136. package/src/cli/root.ts +42 -9
  137. package/src/cli/route-policy-write.ts +137 -0
  138. package/src/cli/route-policy.ts +8 -0
  139. package/src/cli/runtime-api.ts +57 -80
  140. package/src/cli/secret-args.ts +149 -0
  141. package/src/cli/settings-result.ts +30 -0
  142. package/src/cli/storage.ts +77 -7
  143. package/src/cli/system-command.ts +60 -2
  144. package/src/cli/system-restart-client.ts +20 -3
  145. package/src/cli/system-settings-parity.ts +83 -0
  146. package/src/cli/tray-proxy.ts +3 -2
  147. package/src/cli/update-restart-candidate.ts +21 -0
  148. package/src/cli/update-restart-child.ts +81 -0
  149. package/src/cli/update-restart-home.ts +38 -0
  150. package/src/cli/update-restart-transport.ts +193 -0
  151. package/src/cli/update-restart.ts +260 -0
  152. package/src/cli/usage-model-search.ts +58 -0
  153. package/src/cli/usage-report.ts +13 -5
  154. package/src/cli/v2-input.ts +66 -0
  155. package/src/cli/v2-local-output.ts +37 -0
  156. package/src/cli/v2-runtime.ts +90 -0
  157. package/src/cli/v2.ts +102 -144
  158. package/src/clients/config-export/contracts.ts +100 -0
  159. package/src/clients/config-export/droid.ts +54 -2
  160. package/src/clients/config-export/dsh.ts +21 -0
  161. package/src/clients/config-export/kilo.ts +12 -2
  162. package/src/clients/config-export/model-metadata.ts +10 -4
  163. package/src/clients/config-export/reasoning-metadata.ts +47 -0
  164. package/src/clients/config-export.ts +53 -67
  165. package/src/clients/omo-role-models.ts +17 -3
  166. package/src/codex/account-credit-use.ts +4 -2
  167. package/src/codex/account-runtime-state.ts +21 -0
  168. package/src/codex/account-store.ts +11 -47
  169. package/src/codex/account-usability.ts +11 -8
  170. package/src/codex/auth-api/account-list.ts +73 -63
  171. package/src/codex/auth-api/main-account-probe.ts +25 -12
  172. package/src/codex/auth-api/pool-mode-gate.ts +6 -9
  173. package/src/codex/auth-api/pool-quota-probe.ts +2 -4
  174. package/src/codex/auth-api/reset-credit-service.ts +3 -2
  175. package/src/codex/auth-context.ts +30 -11
  176. package/src/codex/catalog/aggregation.ts +3 -0
  177. package/src/codex/catalog/build-entries.ts +1 -1
  178. package/src/codex/catalog/bundled.ts +9 -2
  179. package/src/codex/catalog/heal-observation.ts +66 -0
  180. package/src/codex/catalog/model-hints.ts +2 -1
  181. package/src/codex/catalog/model-visibility.ts +6 -3
  182. package/src/codex/catalog/parsing.ts +2 -0
  183. package/src/codex/catalog/provider-models.ts +13 -4
  184. package/src/codex/catalog/publication-observer.ts +19 -0
  185. package/src/codex/catalog/remote.ts +13 -15
  186. package/src/codex/catalog/restore.ts +19 -6
  187. package/src/codex/catalog/retained-sync.ts +60 -11
  188. package/src/codex/catalog/routed-removal.ts +116 -0
  189. package/src/codex/catalog/write-audit-contract.ts +21 -0
  190. package/src/codex/catalog/write-audit.ts +171 -0
  191. package/src/codex/catalog-self-heal.ts +284 -0
  192. package/src/codex/catalog-write-serialization.ts +71 -7
  193. package/src/codex/chatgpt-refresh-failure.ts +56 -0
  194. package/src/codex/codex-home-owner.ts +124 -0
  195. package/src/codex/convergence.ts +57 -16
  196. package/src/codex/features.ts +8 -1
  197. package/src/codex/inject/restore.ts +46 -4
  198. package/src/codex/inject.ts +11 -1
  199. package/src/codex/internal/catalog-writer.ts +120 -13
  200. package/src/codex/journal.ts +49 -13
  201. package/src/codex/main-account.ts +144 -11
  202. package/src/codex/management-convergence.ts +3 -0
  203. package/src/codex/quota-refresh-outcome.ts +17 -2
  204. package/src/codex/quota-types.ts +36 -1
  205. package/src/codex/quota.ts +67 -16
  206. package/src/codex/refresh.ts +20 -9
  207. package/src/codex/routing/cooldown-math.ts +10 -4
  208. package/src/codex/routing/selection.ts +11 -4
  209. package/src/codex/routing.ts +9 -9
  210. package/src/codex/runtime.ts +7 -1
  211. package/src/codex/shim-diagnostics.ts +80 -0
  212. package/src/codex/shim-fingerprint.ts +7 -2
  213. package/src/codex/shim-migration.ts +153 -0
  214. package/src/codex/shim-overlay.ts +509 -0
  215. package/src/codex/shim-path-resolution.ts +145 -0
  216. package/src/codex/shim-probe.ts +4 -3
  217. package/src/codex/shim-state-file.ts +93 -7
  218. package/src/codex/shim.ts +54 -107
  219. package/src/codex/subagent-model-fallback.ts +2 -1
  220. package/src/codex/sync.ts +24 -4
  221. package/src/combos/failover.ts +101 -1
  222. package/src/combos/index.ts +1 -0
  223. package/src/combos/jev.ts +13 -3
  224. package/src/combos/resolve.ts +12 -0
  225. package/src/config/atomic-write.ts +13 -8
  226. package/src/config/diagnostics.ts +23 -1
  227. package/src/config/load-degrade.ts +7 -0
  228. package/src/config/provider-validation.ts +3 -0
  229. package/src/config/schema/chatgpt-desktop.ts +22 -0
  230. package/src/config/schema/compaction-triggers.ts +8 -0
  231. package/src/config/schema/config-schema.ts +43 -14
  232. package/src/config/schema/leaf-validators.ts +8 -3
  233. package/src/config/subagent-models.ts +12 -0
  234. package/src/generated/compatibility-version.json +645 -237
  235. package/src/grok/session-identity.ts +33 -0
  236. package/src/integrations/catalog-refresh.ts +1 -1
  237. package/src/integrations/merge.ts +13 -5
  238. package/src/integrations/mutation-plan.ts +50 -5
  239. package/src/integrations/omp-yaml-source.ts +181 -1
  240. package/src/integrations/ownership-policy.ts +66 -0
  241. package/src/integrations/registry.ts +50 -1
  242. package/src/integrations/state.ts +106 -19
  243. package/src/integrations/target.ts +25 -2
  244. package/src/integrations/writer.ts +107 -35
  245. package/src/lib/gui-pair-capability.ts +17 -0
  246. package/src/lib/package-tree-integrity.ts +42 -1
  247. package/src/lib/provider-client-headers.ts +62 -0
  248. package/src/lib/proxy-env.ts +3 -0
  249. package/src/lib/test-home-guard.ts +2 -1
  250. package/src/oauth/anthropic-account-refusal.ts +19 -8
  251. package/src/oauth/anthropic-continuity.ts +65 -0
  252. package/src/oauth/anthropic-identity.ts +75 -0
  253. package/src/oauth/anthropic-routing.ts +20 -15
  254. package/src/oauth/anthropic.ts +34 -3
  255. package/src/oauth/generic-account-failover.ts +21 -0
  256. package/src/oauth/health.ts +32 -5
  257. package/src/oauth/index.ts +42 -35
  258. package/src/oauth/local-token-detect.ts +7 -0
  259. package/src/oauth/pool-settings-capability.ts +9 -2
  260. package/src/oauth/refresh-policy.ts +2 -0
  261. package/src/oauth/store.ts +55 -6
  262. package/src/oauth/types.ts +9 -0
  263. package/src/protocols/encoders/messages.ts +4 -3
  264. package/src/protocols/plan-snapshot.ts +1 -1
  265. package/src/protocols/settings.ts +28 -4
  266. package/src/providers/antigravity-effort-families.ts +75 -0
  267. package/src/providers/antigravity-models.ts +54 -16
  268. package/src/providers/antigravity-wire-snapshot.ts +66 -0
  269. package/src/providers/derive.ts +2 -0
  270. package/src/providers/model-rename-fields.ts +3 -0
  271. package/src/providers/new-model-policy.ts +48 -10
  272. package/src/providers/registry/entries-extended.ts +3 -0
  273. package/src/providers/registry/model-ids.ts +2 -0
  274. package/src/providers/registry/model-seeds.ts +5 -0
  275. package/src/providers/registry/types.ts +4 -0
  276. package/src/responses/compaction.ts +19 -0
  277. package/src/router.ts +6 -0
  278. package/src/server/auth-cors.ts +9 -0
  279. package/src/server/caller-session-identity.ts +32 -0
  280. package/src/server/chat-completions.ts +10 -0
  281. package/src/server/claude-messages.ts +29 -15
  282. package/src/server/droid-reasoning-default.ts +50 -0
  283. package/src/server/github-copilot-responses-repair.ts +3 -6
  284. package/src/server/grok-responses-control-frame.ts +2 -2
  285. package/src/server/grok-responses-snapshot-repair.ts +4 -5
  286. package/src/server/gui-session.ts +33 -11
  287. package/src/server/index/claude-intercept-lifecycle.ts +4 -0
  288. package/src/server/index/serve-options.ts +13 -7
  289. package/src/server/index.ts +1 -1
  290. package/src/server/management/agent-settings-routes.ts +12 -134
  291. package/src/server/management/config-routes.ts +1 -1
  292. package/src/server/management/context.ts +8 -1
  293. package/src/server/management/cursor-integration-routes.ts +3 -3
  294. package/src/server/management/integration-routes.ts +69 -11
  295. package/src/server/management/link-routes.ts +20 -13
  296. package/src/server/management/model-routes.ts +3 -3
  297. package/src/server/management/model-row-export-metadata.ts +150 -0
  298. package/src/server/management/model-rows.ts +148 -9
  299. package/src/server/management/native-integration-routes.ts +2 -2
  300. package/src/server/management/oauth-account-routes.ts +96 -30
  301. package/src/server/management/route-registry.ts +29 -27
  302. package/src/server/management/shared.ts +19 -5
  303. package/src/server/management/subagent-model-routes.ts +214 -0
  304. package/src/server/messages-native-eligibility.ts +3 -6
  305. package/src/server/messages-native-oauth.ts +82 -35
  306. package/src/server/messages-native.ts +78 -16
  307. package/src/server/relay.ts +14 -44
  308. package/src/server/request-log-generation-window.ts +38 -0
  309. package/src/server/request-log.ts +13 -0
  310. package/src/server/responses/adapter-dispatch.ts +152 -28
  311. package/src/server/responses/codex-auth-error.ts +10 -2
  312. package/src/server/responses/combo-stream-preflight.ts +29 -3
  313. package/src/server/responses/compaction-routing.ts +10 -1
  314. package/src/server/responses/core-auth.ts +67 -30
  315. package/src/server/responses/core-codex-account.ts +6 -3
  316. package/src/server/responses/core-combo-failure.ts +5 -1
  317. package/src/server/responses/core-combo-native.ts +7 -1
  318. package/src/server/responses/core-combo.ts +64 -7
  319. package/src/server/responses/core-normalize.ts +7 -0
  320. package/src/server/responses/core-options.ts +5 -0
  321. package/src/server/responses/fetch-helpers.ts +9 -0
  322. package/src/server/responses/passthrough-delivery.ts +1 -1
  323. package/src/server/responses/policy-fallback.ts +1 -1
  324. package/src/server/responses/request-prepare.ts +5 -0
  325. package/src/server/responses/request-spend.ts +30 -30
  326. package/src/server/responses-custom-tool-repair.ts +2 -2
  327. package/src/server/responses-hosted-image-display.ts +70 -9
  328. package/src/server/responses-terminal-repair.ts +15 -2
  329. package/src/server/sse-frame-buffer.ts +38 -36
  330. package/src/server/sse-payload-rewrite.ts +78 -49
  331. package/src/server/ws-bridge.ts +1 -11
  332. package/src/service/cli.ts +6 -1
  333. package/src/service/ownership-mutation-lease.d.mts +26 -0
  334. package/src/service/ownership-mutation-lease.mjs +76 -1
  335. package/src/service/windows-taskxml.ts +1 -2
  336. package/src/service-manager-probe.ts +36 -2
  337. package/src/types/config.ts +6 -0
  338. package/src/types/provider.ts +28 -10
  339. package/src/update/index.ts +2 -1
  340. package/src/update/npm-invocation.mjs +35 -3
  341. package/src/update/proxy-liveness-probe.mjs +38 -8
  342. package/src/update/update-failure-guidance.d.mts +6 -0
  343. package/src/update/update-failure-guidance.mjs +30 -5
  344. package/src/usage/expected-prices.ts +9 -0
  345. package/src/usage/log.ts +7 -0
  346. package/src/usage/summary.ts +82 -1
  347. package/src/web-search/loop.ts +11 -8
  348. package/src/web-search/run-turn-loop.ts +10 -7
  349. package/gui/dist/assets/App-CddorHdA.js +0 -51
  350. package/gui/dist/assets/App-DMudtISW.css +0 -1
  351. package/gui/dist/assets/index-Btze1I3T.css +0 -1
  352. package/gui/dist/assets/index-CvnY7E6i.js +0 -86
  353. package/gui/dist/assets/quota-summary-B_AWpHfP.js +0 -1
@@ -0,0 +1,146 @@
1
+ /** Saved companion display filters applied to two bounded management reads. */
2
+ import { ObservationError, withObserveStream, type ObserveStreamDeps } from "./observe-stream";
3
+ import { printData, rejectArgs, runCliAction, takeFlag } from "./runtime-api";
4
+
5
+ const USAGE = "Usage: ocx companion usage [--json]";
6
+ const TOTAL_FIELDS = ["requests", "totalTokens", "inputTokens", "outputTokens", "cachedInputTokens", "cacheReadInputTokens", "estimatedCostUsd", "measuredRequests", "pricedRequests", "coverageRatio"] as const;
7
+ type Totals = Partial<Record<typeof TOTAL_FIELDS[number], number>>;
8
+ type Model = Totals & { model: string; provider: string };
9
+ interface Usage {
10
+ summary: Totals;
11
+ models: Model[];
12
+ customWindow?: boolean;
13
+ since?: number | null;
14
+ until?: number;
15
+ usageIncomplete?: boolean;
16
+ historyTruncated?: boolean;
17
+ entriesTruncated?: boolean;
18
+ }
19
+ interface Filters { models: string[] | null; hiddenProviders: string[] }
20
+ interface Settings { filters: Filters; updatedAt: number | null; corrupt: boolean }
21
+ type RangeResult = { status: "available"; data: Usage } | { status: "unavailable" };
22
+
23
+ function object(value: unknown): Record<string, unknown> {
24
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new ObservationError("Invalid companion response. Check the runtime and retry the command.");
25
+ return value as Record<string, unknown>;
26
+ }
27
+
28
+ function stringList(value: unknown): value is string[] {
29
+ return Array.isArray(value) && value.length <= 100 && value.every(item => typeof item === "string" && item.length > 0 && !/[\x00-\x1f\x7f-\x9f]/.test(item));
30
+ }
31
+
32
+ function parseSettings(value: unknown): Settings {
33
+ const body = object(value), settings = object(body.settings);
34
+ if ((settings.models !== null && !stringList(settings.models)) || !stringList(settings.hiddenProviders)
35
+ || (body.updatedAt !== null && (typeof body.updatedAt !== "number" || !Number.isFinite(body.updatedAt)))
36
+ || (body.corrupt !== undefined && typeof body.corrupt !== "boolean")) {
37
+ throw new ObservationError("Invalid companion settings. Check the runtime and retry the command.");
38
+ }
39
+ return { filters: { models: settings.models, hiddenProviders: settings.hiddenProviders }, updatedAt: body.updatedAt, corrupt: body.corrupt === true };
40
+ }
41
+
42
+ function parseTotals(value: unknown): Totals {
43
+ const row = object(value), totals: Totals = {};
44
+ for (const key of TOTAL_FIELDS) {
45
+ const metric = row[key];
46
+ if (metric === undefined) continue;
47
+ if (typeof metric !== "number" || !Number.isFinite(metric) || metric < 0) throw new ObservationError("Invalid companion usage metrics. Check the runtime and retry the command.");
48
+ totals[key] = metric;
49
+ }
50
+ return totals;
51
+ }
52
+
53
+ function parseUsage(value: unknown): Usage {
54
+ const body = object(value);
55
+ if (body.error || !Array.isArray(body.models)) throw new ObservationError("Invalid companion usage report. Check the runtime and retry the command.");
56
+ const usage: Usage = {
57
+ summary: parseTotals(body.summary),
58
+ models: body.models.map(raw => {
59
+ const row = object(raw);
60
+ if (typeof row.model !== "string" || typeof row.provider !== "string") throw new ObservationError("Invalid companion model row. Check the runtime and retry the command.");
61
+ return { ...parseTotals(row), model: row.model, provider: row.provider };
62
+ }),
63
+ };
64
+ for (const key of ["customWindow", "usageIncomplete", "historyTruncated", "entriesTruncated"] as const) {
65
+ if (body[key] === undefined) continue;
66
+ if (typeof body[key] !== "boolean") throw new ObservationError("Invalid companion usage metadata. Check the runtime and retry the command.");
67
+ usage[key] = body[key];
68
+ }
69
+ for (const key of ["since", "until"] as const) {
70
+ const timestamp = body[key];
71
+ if (timestamp === undefined) continue;
72
+ if (key === "since" && timestamp === null) { usage.since = null; continue; }
73
+ if (typeof timestamp !== "number" || !Number.isFinite(timestamp)) throw new ObservationError("Invalid companion usage window. Check the runtime and retry the command.");
74
+ usage[key] = timestamp;
75
+ }
76
+ return usage;
77
+ }
78
+
79
+ /** Keep in conformance with tray-data's display rules without a GUI runtime import. */
80
+ function filterUsage(usage: Usage, filters: Filters): Usage {
81
+ const hidden = new Set(filters.hiddenProviders), selected = filters.models === null ? null : new Set(filters.models);
82
+ const models = usage.models.filter(row => !hidden.has(row.provider)
83
+ && (selected === null || selected.has(`${row.provider}/${row.model}`) || selected.has(row.model)));
84
+ if (filters.models === null && filters.hiddenProviders.length === 0) return { ...usage, models };
85
+ if (filters.models?.length !== 0 && usage.models.some(row => !row.provider || !row.model || (row.provider === "other" && row.model === "other"))) {
86
+ return { ...usage, models: models.filter(row => row.provider !== "other" || row.model !== "other"), summary: {}, usageIncomplete: true };
87
+ }
88
+ const summary: Totals = {};
89
+ for (const key of TOTAL_FIELDS) {
90
+ if (key === "coverageRatio") continue;
91
+ if (models.length && models.every(row => row[key] !== undefined)) summary[key] = models.reduce((sum, row) => sum + row[key]!, 0);
92
+ }
93
+ return { ...usage, models, summary };
94
+ }
95
+
96
+ function measuredTotals<T extends Totals>(data: T): T {
97
+ const next = { ...data };
98
+ if ((data.requests ?? 0) > 0 && (data.measuredRequests === 0 || data.coverageRatio === 0)) {
99
+ for (const key of ["totalTokens", "inputTokens", "outputTokens", "cachedInputTokens", "cacheReadInputTokens"] as const) delete next[key];
100
+ }
101
+ if ((data.requests ?? 0) > 0 && data.pricedRequests === 0) delete next.estimatedCostUsd;
102
+ return next;
103
+ }
104
+
105
+ function formatRange(label: string, result: RangeResult): string[] {
106
+ if (result.status === "unavailable") return [`${label}: unavailable`];
107
+ const count = (value: number | undefined) => value === undefined ? "unknown" : value.toLocaleString("en-US");
108
+ const cost = (value: number | undefined) => value === undefined ? "unknown" : `~$${value.toFixed(4)}`;
109
+ const data = result.data;
110
+ return [`${label}: ${count(data.summary.requests)} requests; ${count(data.summary.totalTokens)} tokens; cost ${cost(data.summary.estimatedCostUsd)}`,
111
+ ...(data.usageIncomplete || data.historyTruncated || data.entriesTruncated ? [" Incomplete observation; totals include only attributable measured data."] : []),
112
+ ...data.models.map(row => ` ${row.provider}/${row.model}: ${count(row.requests)} requests; ${count(row.totalTokens)} tokens; cost ${cost(row.estimatedCostUsd)}`)];
113
+ }
114
+
115
+ export async function handleCompanionUsageCommand(argv: string[], deps: ObserveStreamDeps = {}): Promise<number> {
116
+ let outcome = 0;
117
+ const parseExit = await runCliAction(async () => {
118
+ const args = [...argv], wantsJson = takeFlag(args, "--json");
119
+ rejectArgs(args, USAGE, { redactValues: true });
120
+ outcome = await withObserveStream(deps, async stream => {
121
+ const settings = parseSettings(await stream.get("/api/companion/settings", new URLSearchParams()));
122
+ const ranges: Record<"today" | "30d", RangeResult> = { today: { status: "unavailable" }, "30d": { status: "unavailable" } };
123
+ for (const range of ["today", "30d"] as const) {
124
+ try {
125
+ const data = filterUsage(parseUsage(await stream.get("/api/usage", new URLSearchParams({ range }))), settings.filters);
126
+ ranges[range] = { status: "available", data: { ...data, summary: measuredTotals(data.summary), models: data.models.map(measuredTotals) } };
127
+ } catch {
128
+ stream.signal.throwIfAborted();
129
+ }
130
+ }
131
+ stream.signal.throwIfAborted();
132
+ const partial = ranges.today.status === "unavailable" || ranges["30d"].status === "unavailable";
133
+ const result = { schemaVersion: 1, filters: settings.filters, settingsUpdatedAt: settings.updatedAt, settingsCorrupt: settings.corrupt,
134
+ settingsFallback: settings.corrupt || settings.updatedAt === null, ranges, partial };
135
+ const lines = ["Companion usage — saved display filters (sequential observations)",
136
+ `Models: ${settings.filters.models === null ? "all" : settings.filters.models.length === 0 ? "none" : settings.filters.models.join(", ")}`,
137
+ `Hidden providers: ${settings.filters.hiddenProviders.join(", ") || "none"}`,
138
+ ...(settings.corrupt ? ["WARNING: Saved settings are corrupt; the runtime supplied fallback settings."] : []),
139
+ ...formatRange("Today", ranges.today), ...formatRange("30 days", ranges["30d"])];
140
+ printData(result, wantsJson, wantsJson ? undefined : lines);
141
+ if (partial) console.error("Error: Some companion usage ranges are unavailable. Check the runtime and retry the command.");
142
+ return partial ? 1 : 0;
143
+ }, { kind: "snapshot" });
144
+ });
145
+ return parseExit || outcome;
146
+ }
@@ -1,9 +1,16 @@
1
+ import { handleCompanionTimelineCommand } from "./companion-timeline";
2
+ import { handleCompanionUsageCommand } from "./companion-usage";
3
+ import type { ObserveStreamDeps } from "./observe-stream";
1
4
  import { CliUsageError, printData, rejectArgs, runCliAction, runtimeRequest, takeFlag, type RuntimeApiDeps } from "./runtime-api";
2
5
 
3
6
  const USAGE = `Usage:
4
7
  ocx companion [show] [--json]
5
8
  ocx companion set <key>=<value> [...] [--json]
6
- ocx companion reset [--json]`;
9
+ ocx companion reset [--json]
10
+ ocx companion usage [--json]
11
+ ocx companion timeline [--hours <6|24|72|168>] [--bucket-minutes <1..1440>]
12
+ [--metric <total|input|output|cached>] [--aggregation <sum|average|max>]
13
+ [--grouping <model|modelAccount>] [--model <provider/model>]... [--hide-provider <name>]... [--json]`;
7
14
 
8
15
  function parseValue(raw: string): unknown {
9
16
  if (raw === "null") return null;
@@ -43,9 +50,11 @@ async function reset(argv: string[], deps: RuntimeApiDeps): Promise<void> {
43
50
  }, deps), wantsJson, ["Companion settings reset."]);
44
51
  }
45
52
 
46
- export async function handleCompanionCommand(argv: string[], deps: RuntimeApiDeps = {}): Promise<number> {
53
+ export async function handleCompanionCommand(argv: string[], deps: ObserveStreamDeps = {}): Promise<number> {
54
+ const [sub = "show", ...rest] = argv;
55
+ if (sub === "timeline") return handleCompanionTimelineCommand(rest, deps);
56
+ if (sub === "usage") return handleCompanionUsageCommand(rest, deps);
47
57
  return runCliAction(async () => {
48
- const [sub = "show", ...rest] = argv;
49
58
  if (sub === "show") await show(rest, deps);
50
59
  else if (sub === "set") await set(rest, deps);
51
60
  else if (sub === "reset") await reset(rest, deps);
@@ -14,7 +14,7 @@ const USAGE = `Usage:
14
14
  ocx config set <dot.path> <json-or-string> [--json]
15
15
  ocx config unset <dot.path> [--json]
16
16
  ocx config validate [path|-] [--json]
17
- ocx config export <path|->
17
+ ocx config export <path|-> [--json]
18
18
  ocx config import <path|-> --yes [--json]`;
19
19
 
20
20
  /**
@@ -205,14 +205,21 @@ function validate(value: unknown): OcxConfig {
205
205
  }
206
206
 
207
207
  export async function handleConfigCommand(argv: string[]): Promise<number> {
208
- return runCliAction(async () => {
208
+ let outcome = 0;
209
+ const status = await runCliAction(async () => {
209
210
  const args = [...argv];
210
- const action = (args.shift() ?? "show").toLowerCase();
211
211
  const wantsJson = takeFlag(args, "--json");
212
+ const source = takeFlag(args, "--source");
213
+ if (args.includes("--json") || args.includes("--source")) throw new CliUsageError("config flags may only be specified once", USAGE);
214
+ const action = (args.shift() ?? "show").toLowerCase();
215
+ if (source && action !== "show") throw new CliUsageError("--source is only supported for config show", USAGE);
212
216
  if (action === "show") {
213
- const source = takeFlag(args, "--source");
214
217
  rejectArgs(args, USAGE);
215
218
  const diagnostics = readConfigDiagnostics();
219
+ if (diagnostics.source === "fallback" || diagnostics.error) {
220
+ console.error(`Warning: Config at ${getConfigPath()} is invalid or unreadable; defaults are being shown for invalid settings. Run: ocx config validate. Inspect: ocx config show --source.`);
221
+ if (!source) outcome = 1;
222
+ }
216
223
  const redacted = redact(diagnostics.config);
217
224
  const note = await readRemoteHubConfigNote(diagnostics.config);
218
225
  // First key, not last: it has to be read before the empty `providers` map that misled a
@@ -228,7 +235,12 @@ export async function handleConfigCommand(argv: string[]): Promise<number> {
228
235
  const path = args.shift();
229
236
  if (!path) throw new CliUsageError("config path is required", USAGE);
230
237
  rejectArgs(args, USAGE);
231
- const value = redact(getPath(readConfigDiagnostics().config, path), pathSegments(path).at(-1));
238
+ const diagnostics = readConfigDiagnostics();
239
+ if (diagnostics.source === "fallback" || diagnostics.error) {
240
+ console.error(`Warning: Config at ${getConfigPath()} is invalid or unreadable; defaults are being shown for invalid settings. Run: ocx config validate. Inspect: ocx config show --source.`);
241
+ outcome = 1;
242
+ }
243
+ const value = redact(getPath(diagnostics.config, path), pathSegments(path).at(-1));
232
244
  if (wantsJson || typeof value === "object") console.log(JSON.stringify(value, null, 2));
233
245
  else console.log(String(value));
234
246
  return;
@@ -236,7 +248,7 @@ export async function handleConfigCommand(argv: string[]): Promise<number> {
236
248
  if (action === "set" || action === "unset") {
237
249
  const path = args.shift();
238
250
  const raw = action === "set" ? args.shift() : undefined;
239
- if (!path || (action === "set" && raw === undefined)) throw new CliUsageError("config path and value are required", USAGE);
251
+ if (!path || (action === "set" && raw === undefined)) throw new CliUsageError(action === "unset" ? "config path is required" : "config path and value are required", USAGE);
240
252
  rejectArgs(args, USAGE);
241
253
  // #1835/#1838: the read used to happen OUTSIDE the mutation lock, so a concurrent
242
254
  // edit landing between it and the save was reverted by this whole-snapshot write.
@@ -288,7 +300,7 @@ export async function handleConfigCommand(argv: string[]): Promise<number> {
288
300
  })();
289
301
  printData(result.ok ? { ok: true, source: path ?? getConfigPath() } : result, wantsJson,
290
302
  [result.ok ? "Config is valid." : `Config is invalid: ${result.error}`]);
291
- if (!result.ok) process.exitCode = 1;
303
+ if (!result.ok) outcome = 1;
292
304
  return;
293
305
  }
294
306
  if (action === "export") {
@@ -297,7 +309,10 @@ export async function handleConfigCommand(argv: string[]): Promise<number> {
297
309
  rejectArgs(args, USAGE);
298
310
  const content = `${JSON.stringify(readConfigDiagnostics().config, null, 2)}\n`;
299
311
  if (path === "-") process.stdout.write(content);
300
- else { writeFileSync(path, content, { encoding: "utf8", mode: 0o600 }); console.log(`Exported config to ${path}.`); }
312
+ else {
313
+ writeFileSync(path, content, { encoding: "utf8", mode: 0o600 });
314
+ printData({ ok: true, path }, wantsJson, [`Exported config to ${path}.`]);
315
+ }
301
316
  return;
302
317
  }
303
318
  if (action === "import") {
@@ -312,6 +327,7 @@ export async function handleConfigCommand(argv: string[]): Promise<number> {
312
327
  }
313
328
  throw new CliUsageError(`unknown config command ${action}`, USAGE);
314
329
  });
330
+ return status || outcome;
315
331
  }
316
332
 
317
333
  export const CONFIG_USAGE = USAGE;
@@ -40,6 +40,10 @@ import {
40
40
  terminalSafeText,
41
41
  type RuntimeApiDeps,
42
42
  } from "./runtime-api";
43
+ import {
44
+ inspectCodexShimForConnect,
45
+ type CodexConnectShimReadiness,
46
+ } from "./codex-shim-readiness";
43
47
 
44
48
  export interface ClientCommandDeps extends RuntimeApiDeps {
45
49
  lifecycleLockDeps?: ClientLifecycleLockDeps;
@@ -277,6 +281,19 @@ export type ConnectCompletionReport = {
277
281
  readonly failure: string | null;
278
282
  };
279
283
 
284
+ export function codexShimReadinessLine(
285
+ selectedClients: readonly OcxConnectedClientId[],
286
+ inspect: () => CodexConnectShimReadiness = inspectCodexShimForConnect,
287
+ ): string | null {
288
+ if (!selectedClients.includes("codex")) return null;
289
+ return `Codex autostart shim: ${terminalSafeText(inspect().message)}`;
290
+ }
291
+
292
+ function printCodexShimReadiness(selectedClients: readonly OcxConnectedClientId[]): void {
293
+ const line = codexShimReadinessLine(selectedClients);
294
+ if (line) console.log(line);
295
+ }
296
+
280
297
  /**
281
298
  * What `ocx connect` says once the hub and the credential are settled, and whether the command
282
299
  * still fails (#4207).
@@ -414,6 +431,7 @@ async function runHubConnect(argv: string[], deps: ClientCommandDeps): Promise<v
414
431
  const readiness = inspectInstalledCatalogReadiness(installedCatalogFileState(), deps.catalogProbeDeps ?? {});
415
432
  const report = connectCompletionReport(connection, clients, readiness);
416
433
  for (const line of report.lines) console.log(line);
434
+ printCodexShimReadiness(clients);
417
435
  if (report.failure) throw new Error(report.failure);
418
436
  }
419
437
 
@@ -463,6 +481,7 @@ async function runLinkConnect(argv: string[], deps: ClientCommandDeps): Promise<
463
481
  const readiness = inspectInstalledCatalogReadiness(installedCatalogFileState(), deps.catalogProbeDeps ?? {});
464
482
  const report = connectCompletionReport(connection, clients, readiness);
465
483
  for (const line of report.lines) console.log(line);
484
+ printCodexShimReadiness(clients);
466
485
  if (report.failure) throw new Error(report.failure);
467
486
  }
468
487
 
@@ -10,13 +10,13 @@ import type { ProxyRestartStartOutcome } from "./tray-proxy";
10
10
  * never needs to import the entry module back (no cycle).
11
11
  */
12
12
  import { CLI_COMMANDS } from "./registry";
13
- import { isValidProviderName } from "../config/provider-name";
14
- import type { CliHead } from "./root";
13
+ import { uninstallArgsError, type CliHead } from "./root";
15
14
  import type { ReadyArgs } from "./ready";
16
15
  import type { LivenessIo, LiveProxy } from "../server/proxy-liveness";
17
16
  import type { OcxConfig } from "../types";
18
17
  import type { OwnedIntegrationRefreshOutcome } from "../integrations/owned-refresh";
19
18
  import { hasHelpFlag, printSubcommandUsage, printUsage } from "./help";
19
+ import { printUnknownCommand } from "./help-recovery";
20
20
  import {
21
21
  HUB_GATED_SKIP_MESSAGE,
22
22
  localClientSkipMessage,
@@ -299,6 +299,11 @@ const commandRunners: Record<string, CommandRunner> = {
299
299
  return Number(process.exitCode ?? 0);
300
300
  },
301
301
  uninstall: async deps => {
302
+ const error = uninstallArgsError("uninstall", deps.args);
303
+ if (error) {
304
+ console.error(error);
305
+ return 2;
306
+ }
302
307
  await deps.handleUninstall();
303
308
  return Number(process.exitCode ?? 0);
304
309
  },
@@ -375,62 +380,8 @@ const commandRunners: Record<string, CommandRunner> = {
375
380
  return 0;
376
381
  },
377
382
  logout: async deps => {
378
- // Argv is parsed BEFORE any store access, which is the whole point of this shape.
379
- // Previously `args[1]` was taken as the provider name with no parsing, so
380
- // `ocx logout --json` called removeCredential("--json"), printed "Logged out of
381
- // --json." and exited 0 -- a silent false success, the worst outcome for a caller
382
- // that can only see the exit code.
383
- //
384
- // That is not merely a wasted call. `normalizeAuthStore` copies every top-level key
385
- // it finds, so a hand-edited, legacy, or corrupted auth.json containing a `--json`
386
- // key would have its active account deleted -- and the key dropped entirely if that
387
- // was its last account. A flag must never reach the store as a provider name.
388
- const logoutArgs = deps.args.slice(1);
389
- const wantsJson = logoutArgs.includes("--json");
390
- // Any leading dash is an option, not a provider. Matching only `--` left the same defect
391
- // one dash shorter: `ocx logout -j` treated `-j` as the provider name and, with a `-j` key
392
- // present in the store, deleted it and exited 0.
393
- const isOption = (arg: string): boolean => arg.startsWith("-");
394
- const positionals = logoutArgs.filter(arg => !isOption(arg));
395
- const unknownFlags = logoutArgs.filter(arg => isOption(arg) && arg !== "--json");
396
- const name = (positionals[0] ?? "").trim().toLowerCase();
397
-
398
- // Usage failures exit 2 and touch nothing. A missing provider is a usage error; a
399
- // provider that simply has no credential is a not-found (4) further down, because the
400
- // vocabulary distinguishes "you called this wrong" from "the thing is not there".
401
- //
402
- // The shape check is `isValidProviderName`, not another dash test. Rejecting a leading
403
- // ASCII `-` fixed `-j` and still let `logout —json` through with a Unicode dash, which is
404
- // the same defect a third time: each patch named one spelling instead of the class. The
405
- // canonical validator states the rule positively -- start and end alphanumeric, internal
406
- // `._-` allowed -- so `github-copilot` and `google-antigravity` pass while every dash
407
- // variant, empty string, and reserved name fails. Anything that is not a possible
408
- // provider id cannot reach the store at all.
409
- const malformedName = Boolean(name) && !isValidProviderName(name);
410
- if (unknownFlags.length > 0 || positionals.length > 1 || !name || malformedName) {
411
- const problem = unknownFlags.length > 0
412
- ? `unknown option ${unknownFlags[0]}`
413
- : positionals.length > 1 ? "too many arguments"
414
- : malformedName ? `not a valid provider name: ${name}`
415
- : "missing provider";
416
- console.error(`Usage: ocx logout <provider> [--json] (${problem})`);
417
- return 2;
418
- }
419
-
420
- // The disposition comes from inside the store mutation, not from a read-then-remove
421
- // preflight. `mutateStore` serializes writes, so a preflight leaves a window where a
422
- // concurrent logout removes the same account and BOTH callers exit 0 claiming a removal --
423
- // a false success again, just a narrower one than the flag bug above.
424
- const { removeCredential } = await import("../oauth/store");
425
- const outcome = await removeCredential(name);
426
- if (outcome === "not-found") {
427
- if (wantsJson) console.log(JSON.stringify({ schemaVersion: 1, ok: false, provider: name, removed: false, reason: "not_found" }, null, 2));
428
- else console.error(`No stored credential for '${name}'.`);
429
- return 4;
430
- }
431
- if (wantsJson) console.log(JSON.stringify({ schemaVersion: 1, ok: true, provider: name, removed: true }, null, 2));
432
- else console.log(`Logged out of ${name}.`);
433
- return 0;
383
+ const { handleLogoutCommand } = await import("./logout-command");
384
+ return handleLogoutCommand(deps.args.slice(1), { findLiveProxy: deps.findLiveProxy });
434
385
  },
435
386
  sync: async deps => {
436
387
  const syncArgs = deps.args.slice(1);
@@ -514,7 +465,7 @@ const commandRunners: Record<string, CommandRunner> = {
514
465
  },
515
466
  config,
516
467
  port: live.port,
517
- }, ["mcode", "pi", "raycast", "omo", "cline", "droid"]));
468
+ }, ["mcode", "pi", "raycast", "omo", "cline", "droid", "opencode", "kilo"]));
518
469
  } catch (error) {
519
470
  console.warn(`Client integrations were not refreshed: ${error instanceof Error ? error.message : String(error)}`);
520
471
  }
@@ -537,7 +488,7 @@ const commandRunners: Record<string, CommandRunner> = {
537
488
  },
538
489
  v2: async deps => {
539
490
  const { cmdV2 } = await import("./v2");
540
- return await cmdV2(deps.args.slice(1), {}, async () => (await deps.findLiveProxy())?.port);
491
+ return await cmdV2(deps.args.slice(1), { runtimeApi: { findLiveProxy: deps.findLiveProxy } }, async () => (await deps.findLiveProxy())?.port);
541
492
  },
542
493
  connect: async deps => {
543
494
  const { handleConnectCommand } = await import("./connect");
@@ -549,7 +500,7 @@ const commandRunners: Record<string, CommandRunner> = {
549
500
  },
550
501
  "remote-workspace": async deps => {
551
502
  const { runRemoteWorkspaceCommand } = await import("./remote-workspace");
552
- return await runRemoteWorkspaceCommand(deps.args.slice(1));
503
+ return await runRemoteWorkspaceCommand(deps.args.slice(1), { findLiveProxy: deps.findLiveProxy });
553
504
  },
554
505
  disconnect: async deps => {
555
506
  const { handleDisconnectCommand } = await import("./connect");
@@ -569,7 +520,8 @@ const commandRunners: Record<string, CommandRunner> = {
569
520
  const cacheGateSnapshot = deps.loadConfig();
570
521
  const desiredDisabled = !shouldSyncCodexOnStart(cacheGateSnapshot);
571
522
  const invalidated = withCatalogWriteSerialization(owningCodexHome, permit =>
572
- invalidateCodexModelsCacheWithPermitOutcome(permit, owningCodexHome, { allowWhenDesiredDisabled: true }));
523
+ invalidateCodexModelsCacheWithPermitOutcome(permit, owningCodexHome, { allowWhenDesiredDisabled: true }),
524
+ { intent: "cache", writer: "sync-cache" });
573
525
  const cacheJson = cacheArgs.includes("--json");
574
526
  const jsonSafeLog = cacheJson
575
527
  ? { log: (...values: unknown[]) => console.error(...values), error: (...values: unknown[]) => console.error(...values) }
@@ -632,6 +584,10 @@ const commandRunners: Record<string, CommandRunner> = {
632
584
  console.log("No Codex catalog to derive a cache from; nothing to sync.");
633
585
  } else if (unchanged) {
634
586
  console.log("Codex model cache is already current; nothing to sync.");
587
+ } else if (invalidated.kind === "unavailable"
588
+ && (invalidated.reason === "foreign-owner" || invalidated.reason === "owner-unknown")) {
589
+ const { FOREIGN_CODEX_HOME_OWNER_MESSAGE, UNKNOWN_CODEX_HOME_OWNER_MESSAGE } = await import("../codex/catalog/routed-removal");
590
+ console.error(invalidated.reason === "foreign-owner" ? FOREIGN_CODEX_HOME_OWNER_MESSAGE : UNKNOWN_CODEX_HOME_OWNER_MESSAGE);
635
591
  } else if (!ok) {
636
592
  console.error(`Cache refresh did not complete (${invalidated.kind}). The Codex model cache was not rewritten.`);
637
593
  }
@@ -696,19 +652,33 @@ const commandRunners: Record<string, CommandRunner> = {
696
652
  switch (deps.args[1]) {
697
653
  case "install": {
698
654
  const r = installCodexShim();
699
- const { healthy, summary } = diagnoseCodexShim();
655
+ const { healthy, runnable, active, summary } = diagnoseCodexShim();
656
+ const success = !r.refused && (runnable ?? healthy);
700
657
  const { collectCodexShimReadinessWarnings } = await import("./codex-shim-readiness");
701
- const warnings = healthy
658
+ const warnings = success
702
659
  ? collectCodexShimReadinessWarnings()
703
660
  : [];
704
- console.log(`${r.installed && warnings.length === 0 ? "✅ " : "⚠️ "}${r.message}`);
661
+ console.log(`${success && healthy && warnings.length === 0 ? "✅ " : "⚠️ "}${r.message}`);
705
662
  for (const warning of warnings) console.warn(` ${warning}`);
706
- if (!healthy) console.error(`Codex shim installation is unhealthy: ${summary}`);
707
- return healthy ? 0 : 1;
663
+ if (success && active !== undefined && active !== true) {
664
+ const { overlayActivationHint } = await import("../codex/shim-overlay");
665
+ console.warn(` ${overlayActivationHint()}`);
666
+ }
667
+ if (!success) console.error(`${r.refused ? "Codex shim installation was refused" : "Codex shim installation is unhealthy"}: ${summary}`);
668
+ return success ? 0 : 1;
708
669
  }
709
- case "status":
670
+ case "status": {
671
+ const extra = deps.args.slice(2);
672
+ if (extra.length > 0) {
673
+ console.error(extra.some(isJsonOption)
674
+ ? "ocx codex-shim status does not support --json; use ocx status --json (codexShim)."
675
+ : "ocx codex-shim status does not accept arguments or options.");
676
+ console.error("Usage: ocx codex-shim status");
677
+ return 2;
678
+ }
710
679
  console.log(codexShimStatus());
711
680
  break;
681
+ }
712
682
  case "uninstall":
713
683
  case "remove": {
714
684
  const r = uninstallCodexShim();
@@ -730,7 +700,7 @@ const commandRunners: Record<string, CommandRunner> = {
730
700
  }
731
701
  const { runUpdate } = await import("../update");
732
702
  await runUpdate();
733
- return 0;
703
+ return Number(process.exitCode ?? 0);
734
704
  },
735
705
  "__refresh-version": async deps => {
736
706
  // Hidden, detached helper spawned by the update prompt to refresh the
@@ -785,6 +755,10 @@ const commandRunners: Record<string, CommandRunner> = {
785
755
  },
786
756
  health: async deps => {
787
757
  const healthArgs = deps.args.slice(1);
758
+ if (healthArgs.length > 1 || (healthArgs.length === 1 && healthArgs[0] !== "--json")) {
759
+ console.error("Usage: ocx health [--json]\nSee: ocx help health");
760
+ return 2;
761
+ }
788
762
  const wantsHealthJson = healthArgs.includes("--json");
789
763
  // A proxy that has only just bound can miss a single probe while its event loop
790
764
  // is still settling startup work — the same just-started race the stop paths
@@ -843,7 +817,7 @@ const commandRunners: Record<string, CommandRunner> = {
843
817
  },
844
818
  companion: async deps => {
845
819
  const { handleCompanionCommand } = await import("./companion");
846
- return await handleCompanionCommand(deps.args.slice(1));
820
+ return await handleCompanionCommand(deps.args.slice(1), { findLiveProxy: deps.findLiveProxy });
847
821
  },
848
822
  route: async deps => {
849
823
  if (deps.args[1] !== "combo" && deps.args[1] !== "policy") {
@@ -868,7 +842,7 @@ const commandRunners: Record<string, CommandRunner> = {
868
842
  },
869
843
  observe: async deps => {
870
844
  const { handleObserveCommand } = await import("./observe");
871
- return await handleObserveCommand(deps.args.slice(1));
845
+ return await handleObserveCommand(deps.args.slice(1), { findLiveProxy: deps.findLiveProxy });
872
846
  },
873
847
  inspect: async deps => {
874
848
  const { handleInspectCommand } = await import("./inspect");
@@ -876,11 +850,11 @@ const commandRunners: Record<string, CommandRunner> = {
876
850
  },
877
851
  logs: async deps => {
878
852
  const { handleObserveCommand } = await import("./observe");
879
- return await handleObserveCommand([deps.command!, ...deps.args.slice(1)]);
853
+ return await handleObserveCommand([deps.command!, ...deps.args.slice(1)], { findLiveProxy: deps.findLiveProxy });
880
854
  },
881
855
  usage: async deps => {
882
856
  const { handleObserveCommand } = await import("./observe");
883
- return await handleObserveCommand([deps.command!, ...deps.args.slice(1)]);
857
+ return await handleObserveCommand([deps.command!, ...deps.args.slice(1)], { findLiveProxy: deps.findLiveProxy });
884
858
  },
885
859
  storage: async deps => {
886
860
  // `ocx storage` used to be a pure alias of `observe storage`, which reached only the report
@@ -896,11 +870,11 @@ const commandRunners: Record<string, CommandRunner> = {
896
870
  },
897
871
  access: async deps => {
898
872
  const { handleAccessCommand } = await import("./access");
899
- return await handleAccessCommand(deps.args.slice(1));
873
+ return await handleAccessCommand(deps.args.slice(1), { findLiveProxy: deps.findLiveProxy });
900
874
  },
901
875
  "api-key": async deps => {
902
876
  const { handleAccessCommand } = await import("./access");
903
- return await handleAccessCommand(["key", ...deps.args.slice(1)]);
877
+ return await handleAccessCommand(["key", ...deps.args.slice(1)], { findLiveProxy: deps.findLiveProxy });
904
878
  },
905
879
  api: async deps => {
906
880
  const { handleApiCommand } = await import("./api-protocols");
@@ -924,21 +898,21 @@ const commandRunners: Record<string, CommandRunner> = {
924
898
  // integrations `client` manages, so they get their own subcommand rather than being
925
899
  // folded into one that means something else.
926
900
  const { handleIntegrationCommand } = await import("./inspect");
927
- return await handleIntegrationCommand(deps.args.slice(1));
901
+ return await handleIntegrationCommand(deps.args.slice(1), { findLiveProxy: deps.findLiveProxy });
928
902
  } else if (integration === "claude") {
929
903
  const { handleClaudeConfigCommand } = await import("./integrations");
930
904
  return await handleClaudeConfigCommand(deps.args.slice(2));
931
905
  } else if (integration === "client") {
932
906
  const { handleClientIntegrationCommand } = await import("./integrations");
933
- return await handleClientIntegrationCommand(deps.args.slice(2));
907
+ return await handleClientIntegrationCommand(deps.args.slice(2), { findLiveProxy: deps.findLiveProxy });
934
908
  } else {
935
- console.error("Usage: ocx integration <claude|grok|client> <subcommand>");
909
+ console.error("Usage: ocx integration <claude|grok|client|native> <subcommand>\nSee: ocx help integration");
936
910
  return 2;
937
911
  }
938
912
  },
939
913
  system: async deps => {
940
914
  const { handleSystemCommand } = await import("./system-command");
941
- return await handleSystemCommand(deps.args.slice(1));
915
+ return await handleSystemCommand(deps.args.slice(1), { findLiveProxy: deps.findLiveProxy });
942
916
  },
943
917
  config: async deps => {
944
918
  const { handleConfigCommand } = await import("./config-command");
@@ -957,7 +931,7 @@ const commandRunners: Record<string, CommandRunner> = {
957
931
  // "ocx claude desktop" → write Desktop 3P config
958
932
  if (deps.args[1] === "desktop") {
959
933
  const { handleClaudeDesktopCommand } = await import("./claude-desktop");
960
- const exitCode = await handleClaudeDesktopCommand(deps.args.slice(2));
934
+ const exitCode = await handleClaudeDesktopCommand(deps.args.slice(2), { findLiveProxyImpl: deps.findLiveProxy });
961
935
  if (exitCode !== 0) return exitCode;
962
936
  return 0;
963
937
  }
@@ -1167,8 +1141,7 @@ export async function dispatchCommand(head: CliHead, deps: CliDispatchDeps): Pro
1167
1141
  }
1168
1142
  const runner = commandRunners[resolveDispatchCommand(command) ?? ""];
1169
1143
  if (!runner) {
1170
- console.error(`Unknown command: ${command}`);
1171
- printUsage();
1144
+ printUnknownCommand(command);
1172
1145
  return 1;
1173
1146
  }
1174
1147
  return await runner(deps);