@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
package/src/cli/doctor.ts CHANGED
@@ -33,6 +33,7 @@ import {
33
33
  } from "../codex/subagent-model-fallback";
34
34
  import { readCatalog, readCodexCatalogPath, readConfiguredDefaultModel } from "../codex/catalog/parsing";
35
35
  import { diagnoseCodexShim, findCodexOnPath, isWindowsInteropDir, type CodexShimDiagnostic } from "../codex/shim";
36
+ import { overlayActivationHint } from "../codex/shim-overlay";
36
37
  import { providerTableString, rootTomlString } from "../codex/injected-marker";
37
38
  import { countPendingOpencodexHistory } from "../codex/history-provider";
38
39
  import {
@@ -481,28 +482,41 @@ export function collectProviderApiKeyDiagnostics(
481
482
 
482
483
  export type CodexEnvKeyReadinessDiagnostic = {
483
484
  envName: string;
484
- shimState: "missing" | "unhealthy";
485
+ shimState: "missing" | "unhealthy" | "inactive";
485
486
  detail: string;
486
487
  action: string;
487
488
  };
488
489
 
490
+ /** Shell activation and legacy migration are independent of admission-token readiness. */
491
+ export function formatCodexShimDoctorLines(shim: CodexShimDiagnostic, platform = process.platform): string[] {
492
+ if (platform === "win32" || !shim.installed) return [];
493
+ const inactiveOverlay = shim.runnable === true && shim.active === false;
494
+ const healthyLegacy = shim.healthy && shim.runnable === undefined;
495
+ return inactiveOverlay || healthyLegacy ? shim.summary.split("\n").map(line => ` ${line}`) : [];
496
+ }
497
+
489
498
  /** Warn when routed Codex cannot obtain its configured admission token at launch. */
490
499
  export function collectCodexEnvKeyReadiness(
491
500
  configText: string | null,
492
501
  env: EnvMap,
493
502
  shim: CodexShimDiagnostic,
494
503
  serviceTokenPresent: boolean,
504
+ shimActivationShown = false,
495
505
  ): CodexEnvKeyReadinessDiagnostic | null {
496
506
  if (!configText || rootTomlString(configText, "model_provider") !== "opencodex") return null;
497
507
  const envName = providerTableString(configText, "opencodex", "env_key")?.trim();
498
508
  const envValue = envName ? ownEnvValue(env, envName) : undefined;
499
509
  if (!envName || envValue?.trim() || shim.healthy || !serviceTokenPresent) return null;
500
- const shimState = shim.installed ? "unhealthy" : "missing";
510
+ const shimState = shim.runnable && shim.active === false ? "inactive" : shim.installed ? "unhealthy" : "missing";
501
511
  return {
502
512
  envName,
503
513
  shimState,
504
514
  detail: `Codex uses env_key ${envName}, but that variable is unset and the OpenCodex shim is ${shimState}; the service token file exists but plain Codex does not load it`,
505
- action: `Run 'ocx codex-shim install' to repair launch-time token injection, or export ${envName} in the process that starts Codex`,
515
+ action: shimState === "inactive"
516
+ ? shimActivationShown
517
+ ? `Activate the PATH shim as shown under Codex restart safety, or export ${envName} in the process that starts Codex`
518
+ : `${overlayActivationHint()} Or export ${envName} in the process that starts Codex`
519
+ : `Run 'ocx codex-shim install' to repair launch-time token injection, or export ${envName} in the process that starts Codex`,
506
520
  };
507
521
  }
508
522
 
@@ -1349,11 +1363,14 @@ export async function runDoctor(args: string[] = []): Promise<void> {
1349
1363
  try { return readFileSync(codexConfigPath, "utf8"); } catch { return null; }
1350
1364
  })();
1351
1365
  const serviceTokenPresent = Boolean(readInstalledServiceToken()?.trim());
1366
+ const codexShim = diagnoseCodexShim();
1367
+ const codexShimLines = formatCodexShimDoctorLines(codexShim);
1352
1368
  const codexEnvKeyReadiness = collectCodexEnvKeyReadiness(
1353
1369
  codexConfigText,
1354
1370
  process.env,
1355
- diagnoseCodexShim(),
1371
+ codexShim,
1356
1372
  serviceTokenPresent,
1373
+ codexShimLines.length > 0,
1357
1374
  );
1358
1375
  // Use the same attested live startup verdict as `ocx status` when the proxy is already
1359
1376
  // identity-verified. A shell-local systemd probe can be a false negative for a system-wide
@@ -1366,6 +1383,7 @@ export async function runDoctor(args: string[] = []): Promise<void> {
1366
1383
  console.log("\nCodex restart safety");
1367
1384
  console.log(` ${startup.rebootSafe ? "ok " : "!! "} ${startupHealthSummary(startup)}`);
1368
1385
  console.log(` ${formatStartupRoutingDetail(startup)}`);
1386
+ for (const line of codexShimLines) console.log(line);
1369
1387
 
1370
1388
  console.log("\nCodex runtime selection");
1371
1389
  {
@@ -66,12 +66,13 @@ export interface ExportCommandDeps extends RuntimeApiDeps {
66
66
  * `opencodeCatalogFromProxyRows` owns the visibility rules (drop `disabled`, drop dupes,
67
67
  * drop native under Codex Direct) — the export core does none of that, so a row filtered
68
68
  * here is the only thing keeping a disabled model out of a client's picker. It also carries
69
- * the effort ladder, so the ladder a client receives comes from the same filtered, deduped
70
- * row as the model itself: a second lookup over the raw rows would let a hidden or disabled
71
- * duplicate donate its ladder to the visible entry.
69
+ * the effort ladder and the effective custom-row projection, so every per-model field a
70
+ * client receives comes from the same filtered, deduped row as the model itself: a second
71
+ * lookup over the raw rows would let a hidden or disabled duplicate donate its ladder to
72
+ * the visible entry.
72
73
  *
73
- * Modalities need no such lookup: `opencodeCatalogFromProxyRows` carries them on the catalog
74
- * entry, so the clients that filter them are handed the same filtered, deduped row.
74
+ * The empty-vs-undefined ladder distinction survives the hop: a row declaring `[]` exports
75
+ * `[]` (explicit "no rungs"), a row declaring nothing exports nothing (unknown).
75
76
  */
76
77
  export function exportModelsFromProxyRows(
77
78
  rows: readonly OpencodeProxyModelRow[],
@@ -87,13 +88,21 @@ export function exportModelsFromProxyRows(
87
88
  if (entry.fastRowAvailable !== undefined) model.fastRowAvailable = entry.fastRowAvailable;
88
89
  if (entry.displayName) model.displayName = entry.displayName;
89
90
  if (entry.contextWindow !== undefined) model.contextWindow = entry.contextWindow;
90
- if (entry.reasoningEfforts && entry.reasoningEfforts.length > 0) {
91
- model.reasoningEfforts = [...entry.reasoningEfforts];
92
- }
91
+ // The catalog row's own output limit; dropping it here is what once made every CLI
92
+ // export fall back to the generated-metadata guess the management path never used.
93
+ if (entry.maxTokens !== undefined) model.maxTokens = entry.maxTokens;
94
+ if (entry.maxInputTokens !== undefined) model.maxInputTokens = entry.maxInputTokens;
95
+ if (entry.reasoningEfforts !== undefined) model.reasoningEfforts = [...entry.reasoningEfforts];
93
96
  if (entry.defaultReasoningEffort) model.defaultReasoningEffort = entry.defaultReasoningEffort;
94
97
  if (entry.inputModalities && entry.inputModalities.length > 0) {
95
98
  model.inputModalities = [...entry.inputModalities];
96
99
  }
100
+ // Explicit booleans survive, false included; nothing infers a negative from absence.
101
+ if (entry.supportsTools !== undefined) model.supportsTools = entry.supportsTools;
102
+ if (entry.supportsReasoning !== undefined) model.supportsReasoning = entry.supportsReasoning;
103
+ if (entry.supportsReasoningSummaries !== undefined) {
104
+ model.supportsReasoningSummaries = entry.supportsReasoningSummaries;
105
+ }
97
106
  return model;
98
107
  });
99
108
  }
@@ -194,7 +203,9 @@ export async function handleExportCommand(argv: string[], deps: ExportCommandDep
194
203
  // `--out` is the path that writes the selected client's native format.
195
204
  // Format metadata rides in the human lines below.
196
205
  printData(clientConfig, wantsJson, [
197
- text.trimEnd(),
206
+ // `lines` entries print one console line each and are control-escaped, so
207
+ // the document goes in as individual lines rather than one multi-line blob.
208
+ ...text.trimEnd().split("\n"),
198
209
  "",
199
210
  ...(out !== undefined ? [`Wrote ${out}`] : []),
200
211
  `Destination: ${spec.destination(process.env)}`,
package/src/cli/gui.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { OcxConfig } from "../types";
2
- import { canonicalGuiBrowserOrigin } from "../lib/gui-pair-capability";
2
+ import { canonicalGuiBrowserOrigin, standaloneGuiPairingOrigin } from "../lib/gui-pair-capability";
3
3
  import { findLiveProxy, type LiveProxy } from "../server/proxy-liveness";
4
4
  import {
5
5
  requestBoundGuiPairingGrant,
@@ -23,7 +23,7 @@ export interface GuiCommandDeps extends RuntimeApiDeps {
23
23
  }
24
24
 
25
25
  function allowedPairingOrigin(origin: string, config: OcxConfig): boolean {
26
- if (config.runtimeRole !== "hub") return false;
26
+ if (config.runtimeRole !== "hub") return standaloneGuiPairingOrigin(config) === origin;
27
27
  if (canonicalGuiBrowserOrigin(config.hub?.managementPublicOrigin) === origin) return true;
28
28
  return (config.corsAllowOrigins ?? []).some(value => canonicalGuiBrowserOrigin(value) === origin);
29
29
  }
@@ -62,7 +62,7 @@ export async function runGuiCommand(args: string[], deps: GuiCommandDeps): Promi
62
62
  }
63
63
  const config = deps.loadConfig();
64
64
  if (!allowedPairingOrigin(canonicalOrigin, config)) {
65
- console.error("The pairing origin is not enabled by hub.managementPublicOrigin or corsAllowOrigins.");
65
+ console.error("Pairing requires the standalone configured literal loopback origin, or an allowed hub origin.");
66
66
  return 1;
67
67
  }
68
68
  const target = await (deps.findLiveProxy ?? findLiveProxy)();
@@ -70,6 +70,11 @@ export async function runGuiCommand(args: string[], deps: GuiCommandDeps): Promi
70
70
  console.error("No running attested OpenCodex proxy is available for GUI pairing.");
71
71
  return 1;
72
72
  }
73
+ if ((config.runtimeRole ?? "standalone") === "standalone"
74
+ && (target.port !== config.port || (target.hostname ?? "127.0.0.1") !== (config.hostname ?? "127.0.0.1"))) {
75
+ console.error("Standalone pairing requires the running proxy to use its configured loopback address and port.");
76
+ return 1;
77
+ }
73
78
  const result = await (deps.requestPairingGrant ?? requestBoundGuiPairingGrant)(target, canonicalOrigin, {
74
79
  ...(deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {}),
75
80
  });
@@ -0,0 +1,101 @@
1
+ import { CAPABILITIES, type Capability } from "./capabilities";
2
+ import { CLI_COMMANDS, findCommand, type CliCommandEntry } from "./registry";
3
+
4
+ export type HelpResolution =
5
+ | { kind: "entry"; entry: CliCommandEntry; canonicalName: string; capability?: Capability; children: readonly Capability[] }
6
+ | { kind: "capability"; path: string[]; capability: Capability; children: readonly Capability[] }
7
+ | { kind: "prefix"; path: string[]; children: readonly Capability[] }
8
+ | { kind: "models-context"; path: string[] }
9
+ | { kind: "unavailable"; path: string[]; parent?: string[] };
10
+
11
+ function startsWithPath(command: readonly string[], prefix: readonly string[]): boolean {
12
+ return prefix.length <= command.length && prefix.every((token, index) => token === command[index]);
13
+ }
14
+
15
+ const DOCUMENTED_PATHS: readonly (readonly string[])[] = [
16
+ ...CAPABILITIES.filter(capability => !findCommand(capability.command[0])?.hidden).map(capability => capability.command),
17
+ ["models", "context"],
18
+ ];
19
+ const MAX_DOCUMENTED_DEPTH = Math.max(...DOCUMENTED_PATHS.map(path => path.length));
20
+
21
+ function canonicalEntry(entry: CliCommandEntry): CliCommandEntry {
22
+ return CLI_COMMANDS.find(candidate => candidate.aliases?.includes(entry.name)) ?? entry;
23
+ }
24
+
25
+ /** Help-only aliases from access dispatch; never rewrite execution argv or operands. */
26
+ function canonicalHelpPath(requested: readonly string[], entry: CliCommandEntry): string[] {
27
+ const path = entry.name === "api-key"
28
+ ? ["access", "key", ...requested.slice(1)]
29
+ : [canonicalEntry(entry).name, ...requested.slice(1)];
30
+ if (path[0] === "access") {
31
+ if (path[1] === "keys") path[1] = "key";
32
+ if (path[1] === "key" && path[2] === "delete") path[2] = "remove";
33
+ }
34
+ return path;
35
+ }
36
+
37
+ export interface HelpRecoveryCandidate {
38
+ names: string[];
39
+ path: string[];
40
+ }
41
+
42
+ /** Matching names may include aliases; destinations are always catalog-owned. */
43
+ export function helpRecoveryCandidates(parent: readonly string[] = []): HelpRecoveryCandidate[] {
44
+ const candidates = new Map<string, HelpRecoveryCandidate>();
45
+ if (parent.length === 0) {
46
+ for (const entry of CLI_COMMANDS) {
47
+ if (entry.hidden) continue;
48
+ const canonical = canonicalEntry(entry);
49
+ if (canonical.hidden) continue;
50
+ const path = canonicalHelpPath([entry.name], entry);
51
+ const identity = path.join(" ");
52
+ const candidate = candidates.get(identity) ?? { names: [], path };
53
+ candidate.names = [...new Set([...candidate.names, entry.name, ...(entry.aliases ?? [])])];
54
+ candidates.set(identity, candidate);
55
+ }
56
+ } else {
57
+ const entry = findCommand(parent[0]);
58
+ if (!entry || entry.hidden) return [];
59
+ const prefix = canonicalHelpPath(parent, entry);
60
+ if (prefix.length >= MAX_DOCUMENTED_DEPTH) return [];
61
+ for (const documented of DOCUMENTED_PATHS) {
62
+ if (documented.length <= prefix.length || !startsWithPath(documented, prefix)) continue;
63
+ const path = documented.slice(0, prefix.length + 1);
64
+ const names = [path[path.length - 1]];
65
+ if (path.join(" ") === "access key") names.push("keys");
66
+ if (path.join(" ") === "access key remove") names.push("delete");
67
+ candidates.set(path.join(" "), { names, path });
68
+ }
69
+ }
70
+ return [...candidates.values()];
71
+ }
72
+
73
+ /** Static declarations describe help coverage, never the runtime's complete grammar. */
74
+ export function resolveHelpPath(requested: readonly string[]): HelpResolution {
75
+ const entry = requested[0] ? findCommand(requested[0]) : undefined;
76
+ if (!entry) return { kind: "unavailable", path: [...requested] };
77
+ // Keep exact-name alias entries for root help; nested topics use the owner.
78
+ const path = canonicalHelpPath(requested, entry);
79
+ const declared = CAPABILITIES.filter(capability => !findCommand(capability.command[0])?.hidden);
80
+ if (requested.length === 1) return {
81
+ kind: "entry", entry, canonicalName: path.join(" "),
82
+ capability: entry.hidden ? undefined : declared.find(candidate => candidate.command.length === path.length && startsWithPath(candidate.command, path)),
83
+ children: entry.hidden ? [] : declared.filter(candidate => candidate.command.length > path.length && startsWithPath(candidate.command, path)),
84
+ };
85
+ if (path.length === 2 && path[0] === "models" && path[1] === "context") {
86
+ return { kind: "models-context", path };
87
+ }
88
+ const capability = declared.find(candidate => candidate.command.length === path.length && startsWithPath(candidate.command, path));
89
+ const children = declared.filter(candidate => candidate.command.length > path.length && startsWithPath(candidate.command, path));
90
+ if (capability) return { kind: "capability", path, capability, children };
91
+ if (children.length) return { kind: "prefix", path, children };
92
+
93
+ for (let length = Math.min(path.length - 1, MAX_DOCUMENTED_DEPTH); length > 1; length--) {
94
+ const parent = path.slice(0, length);
95
+ if ((length === 2 && parent[0] === "models" && parent[1] === "context")
96
+ || declared.some(candidate => startsWithPath(candidate.command, parent))) {
97
+ return { kind: "unavailable", path, parent };
98
+ }
99
+ }
100
+ return { kind: "unavailable", path, parent: [path[0]] };
101
+ }
@@ -0,0 +1,17 @@
1
+ /** Exact runtime usage row, including its indentation. No runtime imports. */
2
+ export const MODELS_CONTEXT_USAGE = " ocx models context <status|value <tokens> [--set-all]|provider <name> on [--value <tokens>]|provider <name> off|all <on|off>> [--json]";
3
+
4
+ export const MODELS_CONTEXT_DETAILS = [
5
+ "Manage context caps for routed providers.",
6
+ "status: read current settings without changing them (the default action).",
7
+ "value <tokens>: set a positive token cap for future toggles; --set-all also applies it to every routed provider.",
8
+ "provider <name> on|off: enable or disable a provider cap; on accepts --value <tokens> for that provider only.",
9
+ "all on|off: enable or disable caps for every routed provider.",
10
+ "--json: print the result as JSON.",
11
+ "",
12
+ "Examples:",
13
+ " ocx models context status --json",
14
+ " ocx models context value 128000",
15
+ " ocx models context provider openai on --value 128000",
16
+ " ocx models context all off",
17
+ ] as const;
@@ -0,0 +1,50 @@
1
+ import { findCommand } from "./registry";
2
+
3
+ // Presentation owns selection/order only; names and summaries belong to registry.
4
+ const ACTION_GROUPS = [
5
+ { title: "Start here", commands: ["setup", "start", "status"] },
6
+ { title: "Common tasks", commands: ["doctor", "logs", "usage"] },
7
+ ] as const;
8
+
9
+ const FAMILY_GROUPS = [
10
+ ["provider", "account", "models", "alias"],
11
+ ["combo", "route", "agent", "effort"],
12
+ ["observe", "inspect", "access", "api"],
13
+ ["config", "system", "integration"],
14
+ ] as const;
15
+
16
+ function publicCommand(name: string) {
17
+ const entry = findCommand(name);
18
+ if (!entry || entry.hidden) throw new Error(`Invalid public help group command: ${name}`);
19
+ return entry;
20
+ }
21
+
22
+ function actionLines(name: string): string[] {
23
+ const entry = publicCommand(name);
24
+ const prefix = ` ocx ${entry.name}`.padEnd(16);
25
+ const lines: string[] = [];
26
+ let line = prefix;
27
+ for (const word of entry.summary.split(/\s+/)) {
28
+ if (line.trim() && line.length + word.length + 1 > 80 && line !== prefix) {
29
+ lines.push(line);
30
+ line = " ".repeat(prefix.length);
31
+ }
32
+ line += `${line.endsWith(" ") ? "" : " "}${word}`;
33
+ }
34
+ lines.push(line);
35
+ return lines;
36
+ }
37
+
38
+ export function renderRootHelp(): string {
39
+ const lines = ["opencodex (ocx) — Universal provider proxy for Codex", "Usage: ocx <command> [options]"];
40
+ for (const group of ACTION_GROUPS) {
41
+ lines.push("", `${group.title}:`, ...group.commands.flatMap(actionLines));
42
+ }
43
+ lines.push("", "Explore:");
44
+ for (const group of FAMILY_GROUPS) lines.push(` ${group.map(name => publicCommand(name).name).join(" ")}`);
45
+ lines.push("", "More help:",
46
+ " ocx help <command> Command usage and declared topics",
47
+ " ocx help --all Complete command reference",
48
+ " ocx capabilities --json Declared capabilities for scripts");
49
+ return lines.join("\n");
50
+ }
@@ -0,0 +1,66 @@
1
+ import { helpRecoveryCandidates, resolveHelpPath } from "./help-catalog";
2
+ import { redactSecretString } from "../lib/redact";
3
+
4
+ const MAX_TOKEN_LENGTH = 64;
5
+ const MAX_PATH_DEPTH = 8;
6
+ const MAX_SUGGESTIONS = 3;
7
+
8
+ function safeToken(value: string | undefined): value is string {
9
+ return value !== undefined && value.length >= 3 && value.length <= MAX_TOKEN_LENGTH
10
+ && /^[a-z][a-z0-9-]*$/i.test(value) && redactSecretString(value) === value;
11
+ }
12
+
13
+ /** Banded edit distance with adjacent transposition, over already bounded tokens. */
14
+ function distance(left: string, right: string, limit: number): number {
15
+ if (Math.abs(left.length - right.length) > limit) return limit + 1;
16
+ let previous = Array.from({ length: right.length + 1 }, (_, index) => index);
17
+ let older = previous;
18
+ for (let i = 1; i <= left.length; i++) {
19
+ const current: number[] = Array(right.length + 1).fill(limit + 1);
20
+ current[0] = i;
21
+ for (let j = Math.max(1, i - limit); j <= Math.min(right.length, i + limit); j++) {
22
+ current[j] = Math.min(current[j - 1] + 1, previous[j] + 1,
23
+ previous[j - 1] + (left[i - 1] === right[j - 1] ? 0 : 1));
24
+ if (i > 1 && j > 1 && left[i - 1] === right[j - 2] && left[i - 2] === right[j - 1]) {
25
+ current[j] = Math.min(current[j], older[j - 2] + 1);
26
+ }
27
+ }
28
+ older = previous;
29
+ previous = current;
30
+ }
31
+ return previous[right.length];
32
+ }
33
+
34
+ /** Return complete help destinations, never user-supplied trailing operands. */
35
+ export function suggestHelpPaths(path: readonly string[]): string[][] {
36
+ if (!path.length || path.length > MAX_PATH_DEPTH || !path.every(safeToken)) return [];
37
+ const result = resolveHelpPath(path);
38
+ if (result.kind !== "unavailable" || result.path.length > MAX_PATH_DEPTH) return [];
39
+ const parent = result.parent ?? [];
40
+ const token = result.path[parent.length].toLowerCase();
41
+ const limit = token.length < 6 ? 1 : 2;
42
+ return helpRecoveryCandidates(parent).map(candidate => ({
43
+ path: candidate.path,
44
+ name: candidate.path.join(" "),
45
+ distance: Math.min(...candidate.names.filter(safeToken).map(name => distance(token, name.toLowerCase(), limit))),
46
+ })).filter(candidate => candidate.distance <= limit)
47
+ .sort((a, b) => a.distance - b.distance || (a.name < b.name ? -1 : a.name > b.name ? 1 : 0))
48
+ .slice(0, MAX_SUGGESTIONS).map(candidate => candidate.path);
49
+ }
50
+
51
+ export function formatHelpRecovery(path: readonly string[]): string {
52
+ const result = resolveHelpPath(path);
53
+ const parent = result.kind === "unavailable" ? result.parent : undefined;
54
+ const lines = [parent ? "Detailed help unavailable for the requested topic."
55
+ : safeToken(path[0]) ? `Unknown command: ${path[0]}` : "Unknown command."];
56
+ const suggestions = suggestHelpPaths(path);
57
+ if (suggestions.length) {
58
+ lines.push("Did you mean:", ...suggestions.map(suggestion => ` ocx help ${suggestion.join(" ")}`));
59
+ }
60
+ lines.push(parent ? `See: ocx help ${parent.join(" ")}` : "See: ocx help --all");
61
+ return lines.join("\n");
62
+ }
63
+
64
+ export function printUnknownCommand(command: string | undefined): void {
65
+ console.error(formatHelpRecovery(command === undefined ? [] : [command]));
66
+ }
package/src/cli/help.ts CHANGED
@@ -1,4 +1,8 @@
1
- import { findCommand } from "./registry";
1
+ import { resolveHelpPath } from "./help-catalog";
2
+ import type { Capability } from "./capabilities";
3
+ import { MODELS_CONTEXT_DETAILS, MODELS_CONTEXT_USAGE } from "./help-models-context";
4
+ import { renderRootHelp } from "./help-navigation";
5
+ import { formatHelpRecovery } from "./help-recovery";
2
6
  import { packageVersion as readPackageVersion } from "../lib/package-version";
3
7
 
4
8
  /**
@@ -17,6 +21,10 @@ export function printVersion(): void {
17
21
  }
18
22
 
19
23
  export function printUsage(): void {
24
+ console.log(renderRootHelp());
25
+ }
26
+
27
+ export function printFullUsage(): void {
20
28
  console.log(`opencodex (ocx) — Universal provider proxy for Codex
21
29
 
22
30
  Usage:
@@ -33,7 +41,7 @@ Usage:
33
41
  ocx recover-history --ocx-compaction <thread-id> --yes
34
42
  Back up and make one ocx1-compacted thread replayable by native Codex
35
43
  ocx uninstall Remove service/shim/config and restore native Codex (alias: remove)
36
- ocx service [sub] Run as a background service (default: install/update/start)
44
+ ocx service [sub] Run as a background service (default: install if absent, otherwise repair)
37
45
  ocx codex-shim <sub> Auto-start proxy when \`codex\` launches (install|status|uninstall|remove)
38
46
  ocx tray <sub> Windows status tray (install|start|stop|status|uninstall)
39
47
  ocx ensure Ensure the proxy is running and Codex config/cache are current
@@ -87,9 +95,9 @@ Usage:
87
95
  ocx integration client <sub> Enable, disable, inspect or roll back a client integration
88
96
  ocx grok <sub> Grok Build model selection and apply
89
97
  ocx system <sub> Runtime settings, startup, sync, OpenCodex updates, and Codex CLI inspection
90
- ocx config <sub> Validated configuration show/get/set/import/export
98
+ ocx config [sub] Validated configuration show/get/set/import/export
91
99
  ocx companion <show|set|reset> Menu-bar and widget companion usage settings
92
- ocx lab <sub> Read-only Compatibility Lab projection inspection
100
+ ocx lab <sub> Inspect Lab evidence and control local automation
93
101
  ocx chatgpt <sub> Experimental app-server shim: launch|restore|status (macOS)
94
102
  ocx claude [args...] Launch Claude Code wired to the proxy (model discovery on)
95
103
  ocx claude desktop [sub] Manage and apply Claude Desktop's four-family profile
@@ -115,13 +123,63 @@ export function hasHelpFlag(values: string[]): boolean {
115
123
  return values.some(value => value === "--help" || value === "-h" || value === "help");
116
124
  }
117
125
 
118
- export function printSubcommandUsage(name: string | undefined): void {
119
- const entry = name ? findCommand(name) : undefined;
120
- if (!entry) {
121
- console.error(`Unknown command: ${name ?? ""}`.trim());
122
- printUsage();
126
+ function printCapabilityDetails(capability: Capability, write: (text: string) => void, shown: readonly string[] = []): void {
127
+ if (capability.flags.length) {
128
+ write("\nDeclared flags:");
129
+ for (const flag of capability.flags) {
130
+ write(` ${flag.name}${flag.value && flag.value !== "boolean" ? ` <${flag.value}>` : ""}${flag.required ? " (required)" : ""} ${flag.summary}`);
131
+ }
132
+ }
133
+ const details = capability.details?.filter(detail => !shown.includes(detail));
134
+ if (details?.length) write(`\n${details.join("\n")}`);
135
+ }
136
+
137
+ export function printSubcommandUsage(
138
+ name: string | undefined,
139
+ path?: readonly string[],
140
+ options: { fallbackToParent?: boolean; write?: (text: string) => void } = {},
141
+ ): void {
142
+ const write = options.write ?? console.log;
143
+ const result = resolveHelpPath(path ?? (name ? [name] : []));
144
+ if (result.kind === "unavailable") {
145
+ // Appended flags may follow runtime operands. An explicit `help <path>`
146
+ // requests that exact detail instead, so only the CLI head enables fallback.
147
+ if (options.fallbackToParent && result.parent) {
148
+ printSubcommandUsage(result.parent[0], result.parent, { write });
149
+ return;
150
+ }
151
+ console.error(formatHelpRecovery(result.path));
123
152
  process.exit(1);
124
153
  }
125
- console.log(`Usage: ${entry.usage}\n\n${entry.summary}`);
126
- if (entry.details?.length) console.log(`\n${entry.details.join("\n")}`);
154
+ if (result.kind === "entry") {
155
+ write(`Usage: ${result.entry.usage}\n\n${result.entry.summary}`);
156
+ if (result.entry.details?.length) write(`\n${result.entry.details.join("\n")}`);
157
+ if (result.capability) printCapabilityDetails(result.capability, write, result.entry.details);
158
+ if (result.children.length) {
159
+ write("\nDeclared commands (incomplete):");
160
+ for (const child of result.children) write(` ocx help ${child.command.join(" ")} ${child.summary}`);
161
+ }
162
+ if (result.canonicalName === "models") write("\nContext cap help: ocx help models context");
163
+ if (result.entry.name !== result.canonicalName) write(`\nCanonical help: ocx help ${result.canonicalName}`);
164
+ return;
165
+ }
166
+ if (result.kind === "models-context") {
167
+ write(`Usage:\n${MODELS_CONTEXT_USAGE}\n\n${MODELS_CONTEXT_DETAILS.join("\n")}`);
168
+ } else if (result.kind === "capability") {
169
+ const { capability } = result;
170
+ const heading = capability.usage !== undefined ? `Usage: ${capability.usage}` : `Command: ocx ${result.path.join(" ")}`;
171
+ write(`${heading}\n\n${capability.summary}`);
172
+ printCapabilityDetails(capability, write);
173
+ if (result.children.length) {
174
+ write("\nDeclared commands (incomplete):");
175
+ for (const child of result.children) write(` ocx help ${child.command.join(" ")} ${child.summary}`);
176
+ }
177
+ if (capability.usage === undefined) {
178
+ write("\nCapability metadata is incomplete; this is not the full operand grammar.");
179
+ }
180
+ } else {
181
+ write(`Command group: ocx ${result.path.join(" ")}\n\nDeclared commands (incomplete):`);
182
+ for (const child of result.children) write(` ocx ${child.command.join(" ")} ${child.summary}`);
183
+ }
184
+ write(`\nParent help: ocx help ${result.path.slice(0, -1).join(" ")}`);
127
185
  }