@bitkyc08/opencodex 2.12.0 → 2.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/README.md +1 -1
  2. package/gui/dist/assets/index-BHldBl6_.js +76 -0
  3. package/gui/dist/index.html +1 -1
  4. package/package.json +1 -1
  5. package/src/adapters/anthropic-output-schema.ts +137 -0
  6. package/src/adapters/anthropic.ts +15 -0
  7. package/src/adapters/google-antigravity-replay.ts +269 -5
  8. package/src/adapters/openai-chat.ts +25 -18
  9. package/src/adapters/openai-responses.ts +63 -17
  10. package/src/bridge.ts +11 -5
  11. package/src/claude/inbound.ts +24 -3
  12. package/src/cli/account-api.ts +2 -0
  13. package/src/cli/account-extended.ts +262 -1
  14. package/src/cli/account.ts +3 -1
  15. package/src/cli/dispatch.ts +532 -0
  16. package/src/cli/doctor.ts +26 -21
  17. package/src/cli/help.ts +9 -278
  18. package/src/cli/index.ts +45 -506
  19. package/src/cli/lab.ts +171 -65
  20. package/src/cli/registry.ts +384 -0
  21. package/src/cli/root.ts +86 -0
  22. package/src/cli/status.ts +9 -2
  23. package/src/cli/v2.ts +37 -2
  24. package/src/codex/account-label.ts +14 -1
  25. package/src/codex/account-store.ts +12 -1
  26. package/src/codex/admission.ts +2 -1
  27. package/src/codex/auth-api.ts +3 -2
  28. package/src/codex/catalog/metadata.ts +6 -4
  29. package/src/codex/catalog/parsing.ts +31 -7
  30. package/src/codex/catalog/provider-fetch.ts +2 -1
  31. package/src/codex/catalog/sync.ts +65 -11
  32. package/src/codex/features.ts +437 -31
  33. package/src/codex/history-job.ts +6 -11
  34. package/src/codex/history-provider.ts +10 -9
  35. package/src/codex/inject.ts +14 -1
  36. package/src/codex/native-profile-startup.ts +49 -3
  37. package/src/codex/native-residue.ts +14 -2
  38. package/src/codex/paths.ts +75 -1
  39. package/src/codex/reset-credit-recovery.ts +1044 -0
  40. package/src/codex/shim.ts +876 -16
  41. package/src/codex/sync.ts +27 -2
  42. package/src/config.ts +148 -10
  43. package/src/generated/compatibility-version.json +2572 -0
  44. package/src/images/loop.ts +1 -1
  45. package/src/lab/artifacts/sanitize.ts +3 -3
  46. package/src/lab/automation/budgets.ts +78 -0
  47. package/src/lab/automation/config-persistence.ts +256 -0
  48. package/src/lab/automation/constants.ts +39 -0
  49. package/src/lab/automation/cooldown.ts +103 -0
  50. package/src/lab/automation/dispatch.ts +211 -0
  51. package/src/lab/automation/index.ts +13 -0
  52. package/src/lab/automation/orchestrator.ts +480 -0
  53. package/src/lab/automation/persistence.ts +512 -0
  54. package/src/lab/automation/planner.ts +371 -0
  55. package/src/lab/automation/policy.ts +136 -0
  56. package/src/lab/automation/queue.ts +191 -0
  57. package/src/lab/automation/recovery.ts +24 -0
  58. package/src/lab/automation/route-context.ts +21 -0
  59. package/src/lab/automation/run-key.ts +44 -0
  60. package/src/lab/automation/runs-query.ts +34 -0
  61. package/src/lab/automation/types.ts +160 -0
  62. package/src/lab/conformance/types.ts +23 -0
  63. package/src/lab/events/types.ts +2 -1
  64. package/src/lab/events/validate.ts +2 -1
  65. package/src/lab/fabric/constants.ts +40 -0
  66. package/src/lab/fabric/executor.ts +492 -0
  67. package/src/lab/fabric/index.ts +80 -0
  68. package/src/lab/fabric/manifest.ts +222 -0
  69. package/src/lab/fabric/observe.ts +454 -0
  70. package/src/lab/fabric/patch.ts +79 -0
  71. package/src/lab/fabric/producer-child.ts +139 -0
  72. package/src/lab/fabric/producer-isolate.ts +276 -0
  73. package/src/lab/fabric/producer-protocol.ts +61 -0
  74. package/src/lab/fabric/scratch.ts +439 -0
  75. package/src/lab/fabric/subject.ts +106 -0
  76. package/src/lab/fabric/types.ts +134 -0
  77. package/src/lab/fabric/verifier.ts +98 -0
  78. package/src/lab/index.ts +2 -0
  79. package/src/lab/ledger/store.ts +173 -0
  80. package/src/lab/live/executor.ts +29 -4
  81. package/src/lab/live/transport.ts +3 -1
  82. package/src/lab/observe/from-conformance.ts +12 -57
  83. package/src/lab/observe/from-live.ts +9 -1
  84. package/src/lab/paths.ts +74 -9
  85. package/src/lab/projection/schema.ts +14 -1
  86. package/src/lab/projection/verdicts.ts +89 -53
  87. package/src/lab/projection/verification.ts +83 -3
  88. package/src/lab/query/catalog.ts +34 -5
  89. package/src/lab/query/freshness.ts +53 -0
  90. package/src/lab/query/index.ts +11 -0
  91. package/src/lab/query/latest-observation.ts +59 -0
  92. package/src/lab/query/passive-production.ts +159 -0
  93. package/src/lab/subject/installation-salt.ts +24 -4
  94. package/src/lab/subject/protocol-subject.ts +80 -0
  95. package/src/lab/subject/route-subject.ts +2 -1
  96. package/src/lib/fabric-task-execution-authority.ts +7 -0
  97. package/src/lib/fabric-task-host.ts +29 -0
  98. package/src/lib/lab-live-route-production.ts +130 -0
  99. package/src/lib/local-management-capability.ts +100 -0
  100. package/src/lib/server-resource-ownership.ts +71 -0
  101. package/src/lib/windows-elevation.ts +8 -1
  102. package/src/lib/windows-user-principal.ts +62 -2
  103. package/src/lib/winsw.ts +8 -2
  104. package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
  105. package/src/oauth/account-import/index.ts +15 -0
  106. package/src/oauth/account-import/parser.ts +83 -0
  107. package/src/oauth/account-import/registry.ts +18 -0
  108. package/src/oauth/account-import/service.ts +75 -0
  109. package/src/oauth/account-import/types.ts +91 -0
  110. package/src/oauth/anthropic.ts +12 -1
  111. package/src/oauth/callback-server.ts +8 -2
  112. package/src/oauth/chatgpt.ts +12 -1
  113. package/src/oauth/google-antigravity.ts +27 -0
  114. package/src/oauth/health.ts +12 -48
  115. package/src/oauth/index.ts +122 -0
  116. package/src/oauth/kimi.ts +16 -2
  117. package/src/oauth/nous.ts +749 -0
  118. package/src/oauth/store.ts +56 -3
  119. package/src/providers/codex-capacity.ts +1 -0
  120. package/src/providers/registry.ts +161 -16
  121. package/src/responses/custom-tool-compat.ts +182 -0
  122. package/src/responses/parser.ts +22 -0
  123. package/src/responses/reasoning-replay-cache.ts +168 -11
  124. package/src/responses/schema.ts +1 -1
  125. package/src/router.ts +5 -20
  126. package/src/routing/capability.ts +9 -3
  127. package/src/routing/compatibility/assemble.ts +159 -0
  128. package/src/routing/compatibility/behavior.ts +224 -0
  129. package/src/routing/compatibility/catalog.ts +99 -0
  130. package/src/routing/compatibility/endpoint.ts +52 -0
  131. package/src/routing/compatibility/policy.ts +181 -0
  132. package/src/routing/compatibility/reader.ts +110 -0
  133. package/src/routing/compatibility/subject.ts +191 -0
  134. package/src/routing/compatibility/types.ts +64 -0
  135. package/src/routing/compatibility/version.ts +104 -0
  136. package/src/routing/evaluator.ts +27 -32
  137. package/src/routing/profile.ts +118 -4
  138. package/src/routing/trace.ts +82 -28
  139. package/src/server/auth-cors.ts +7 -0
  140. package/src/server/background-lifecycle.ts +20 -7
  141. package/src/server/chat-completions.ts +2 -2
  142. package/src/server/claude-messages.ts +6 -3
  143. package/src/server/direct-local-http.ts +343 -0
  144. package/src/server/index.ts +80 -18
  145. package/src/server/lifecycle.ts +12 -4
  146. package/src/server/local-management-read-client.ts +90 -0
  147. package/src/server/management/agent-settings-routes.ts +27 -3
  148. package/src/server/management/lab-automation-routes.ts +206 -0
  149. package/src/server/management/lab-routes.ts +13 -0
  150. package/src/server/management/logs-usage-routes.ts +1 -0
  151. package/src/server/management/oauth-account-routes.ts +57 -1
  152. package/src/server/management/provider-routes.ts +16 -0
  153. package/src/server/management/routing-profile-routes.ts +12 -24
  154. package/src/server/management-api.ts +2 -0
  155. package/src/server/management-auth.ts +68 -3
  156. package/src/server/proxy-liveness.ts +3 -2
  157. package/src/server/request-decompress.ts +9 -3
  158. package/src/server/request-log.ts +53 -4
  159. package/src/server/responses/collaboration.ts +93 -17
  160. package/src/server/responses/compact.ts +3 -0
  161. package/src/server/responses/core.ts +202 -15
  162. package/src/server/responses/policy-fallback.ts +1 -0
  163. package/src/server/responses-custom-tool-repair.ts +280 -0
  164. package/src/server/responses-terminal-repair.ts +342 -0
  165. package/src/server/startup-action-control.ts +8 -1
  166. package/src/service.ts +278 -12
  167. package/src/types.ts +49 -3
  168. package/src/usage/expected-prices.ts +30 -0
  169. package/src/usage/log.ts +30 -4
  170. package/src/usage/summary.ts +153 -2
  171. package/src/web-search/loop.ts +1 -1
  172. package/gui/dist/assets/index-Cw1_mxO8.js +0 -70
  173. package/src/cli/internal-dispatch.ts +0 -20
package/src/cli/index.ts CHANGED
@@ -24,7 +24,7 @@ import {
24
24
  writeRuntimePort,
25
25
  } from "../config";
26
26
  import { collectStatus } from "./status";
27
- import { dispatchInternalCliCommand, type InternalCliCommand } from "./internal-dispatch";
27
+
28
28
  import {
29
29
  discoverStableProxyForRestart,
30
30
  isProxyReplacement,
@@ -35,11 +35,12 @@ import {
35
35
  } from "./tray-proxy";
36
36
  import { requestBoundSystemRestart } from "./system-restart-client";
37
37
  import { installCrashGuards } from "../lib/crash-guard";
38
- import { hasHelpFlag, printSubcommandUsage, printUsage, printVersion } from "./help";
38
+ import { dispatchCommand } from "./dispatch";
39
39
  import { findAvailablePort, isAddrInUse, PortUnavailableError, shouldPersistSelectedPort, waitForPortAvailable } from "../server/ports";
40
40
  import { findLiveProxy, probeHostname, type LiveProxy } from "../server/proxy-liveness";
41
41
  import { createReadinessGate } from "../server/readiness";
42
- import { parseReadyArgs, runReady, type ReadyArgs } from "./ready";
42
+ import { runReady, type ReadyArgs } from "./ready";
43
+ import { runCli } from "./root";
43
44
  import { ProxyOwnershipRefusedError, stopProxy } from "../lib/process-control";
44
45
  import { loadServiceTokenFromFile } from "../lib/service-secrets";
45
46
  import { diagnoseService, isServiceOwnershipError, serviceCommand, serviceEnvironmentOwnedHere, serviceStartableFromTray, serviceStatusSummary, stopServiceIfInstalled, uninstallServiceIfInstalled } from "../service";
@@ -50,14 +51,13 @@ import { buildDesktop3pRegistry } from "../claude/desktop-3p";
50
51
  import { installShellHook, uninstallShellHook } from "../server/system-env";
51
52
  import { startTokenGuardian } from "../oauth/token-guardian";
52
53
  import { startHistoryMigrationGuardian } from "../codex/history-migration-guardian";
53
- import { maybeAutoRestoreCodexShim } from "./codex-shim-autorestore";
54
54
  import { maybeShowStarPrompt } from "./star-prompt";
55
55
  import { scheduleCatalogPrewarm } from "./catalog-prewarm";
56
56
  import { maybeShowUpdatePrompt } from "../update/notify";
57
57
  import { syncModelsToCodex } from "../codex/sync";
58
58
  import { setIntegrationEnabled, shouldSyncCodexOnStart, shouldSyncGrokOnStart, syncCodexOnStartIfEnabled } from "../codex/desired-state";
59
- import { normalizeUpdateChannel, runGuiUpdateWorker } from "../update/job";
60
- import { collectOrcaCodexHomeDiagnostic } from "../codex/home";
59
+
60
+
61
61
  import { removeOwnedConfigState } from "../lib/config-ownership";
62
62
  import { withProcessRuntimeProvenance } from "../lib/bun-runtime";
63
63
  import { initializeNodeLauncherContext } from "./launcher-context";
@@ -65,43 +65,15 @@ import { createLocalAttestationSecret } from "../lib/local-management-attestatio
65
65
  import { MEMORY_DRAIN_RESTART_MS, REPLACEMENT_READY_TIMEOUT_MS } from "../lib/system-restart-contract";
66
66
 
67
67
  initializeNodeLauncherContext();
68
- const args = process.argv.slice(2);
69
- const command = args[0];
70
-
71
- if (command === "--version" || command === "-v" || command === "version") {
72
- printVersion();
73
- process.exit(0);
74
- }
75
-
76
- if (command === undefined || command === "help" || command === "--help" || command === "-h") {
77
- if (command === "help" && args[1]) printSubcommandUsage(args[1]);
78
- else printUsage();
79
- process.exit(0);
80
- }
81
68
 
82
- if (command !== undefined && command !== "help" && hasHelpFlag(args.slice(1))) {
83
- printSubcommandUsage(command);
84
- process.exit(0);
85
- }
86
-
87
- // P1: pre-parse `ocx ready` and reject invalid arguments with exit 64 BEFORE
88
- // maybeAutoRestoreCodexShim (or any discovery/probe/filesystem-capable global
89
- // preflight) runs. `ready --help` / `help ready` already exited above, so this
90
- // only sees ready args without a help flag. Valid args are stashed so the
91
- // switch dispatch can call runReady without a second parse.
92
- let readyArgs: ReadyArgs | undefined;
93
- if (command === "ready") {
94
- const parsed = parseReadyArgs(args.slice(1));
95
- if (!parsed.ok) {
96
- console.error("Usage: ocx ready [--json] [--wait [--timeout <seconds>]]");
97
- console.error(" --timeout requires --wait; <seconds> must be a positive integer (1..300).");
98
- console.error(" Default wait timeout is 45 seconds.");
99
- process.exit(parsed.code);
100
- }
101
- readyArgs = parsed.args;
102
- }
103
-
104
- maybeAutoRestoreCodexShim(command, args);
69
+ // Head: version/help early exits, `ready` pre-parse (exit 64 before any
70
+ // preflight), and the bounded Codex-shim auto-restore preflight live in
71
+ // src/cli/root.ts (Phase 1 of the CLI deepening). runCli exits for
72
+ // version/help and returns the dispatchable head otherwise; the switch below
73
+ // owns command dispatch.
74
+ const head = await runCli(process.argv.slice(2));
75
+ const args = head.args;
76
+ const command = head.command;
105
77
 
106
78
  function parsePortOption(): number | undefined {
107
79
  if (args.length === 1) return undefined;
@@ -936,470 +908,37 @@ async function handleRecoverHistory() {
936
908
  * and exits with the returned code; it performs no parsing and no I/O of its
937
909
  * own. The full behavior is unit-testable without spawning a subprocess.
938
910
  */
939
- async function handleReady(args: ReadyArgs): Promise<never> {
940
- process.exit(await runReady(args));
911
+ async function handleReady(args: ReadyArgs): Promise<number> {
912
+ return runReady(args);
941
913
  }
942
914
 
943
- switch (command) {
944
- case "init":
945
- case "setup": {
946
- const { runInit } = await import("./init");
947
- await runInit();
948
- break;
949
- }
950
- case "start":
951
- await handleStart();
952
- break;
953
- case "stop": {
954
- // Downtime warning lives HERE, not in handleStop: `restart`/tray-restart callers
955
- // re-start the proxy immediately, so warning there would contradict the next line.
956
- if (await handleStop()) {
957
- console.log("⚠️ Codex/Claude requests through the proxy will fail until it is restarted ('ocx start' or 'ocx service start').");
958
- }
959
- break;
960
- }
961
- case "restore":
962
- case "eject": {
963
- const restoreJson = args[1] === "--json";
964
- if (args[1] === "back") {
965
- // Reverse switch: re-point plain `codex` at the RUNNING proxy without touching its
966
- // lifecycle — the counterpart of `ocx restore`. Start/stop triggers are unchanged;
967
- // this only re-runs the same inject (config + catalog + history) `ocx start` does.
968
- const live = await findLiveProxy();
969
- if (!live) {
970
- console.error("No running proxy found. Run 'ocx start' — it injects opencodex automatically.");
971
- process.exit(1);
972
- }
973
- const desired = setIntegrationEnabled("codex", true);
974
- if (!desired.ok) {
975
- process.exitCode = desired.reason === "conflict" ? 2 : 1;
976
- console.error(`Codex desired state was not saved (${desired.reason}).`);
977
- break;
978
- }
979
- const synced = await syncModelsToCodex(live.port);
980
- if (synced.status === "skipped") {
981
- process.exitCode = 2;
982
- console.error("Codex integration is OFF; restore back did not change Codex. Retry after the competing integration change finishes.");
983
- break;
984
- }
985
- if (!synced.ok) {
986
- process.exitCode = 1;
987
- console.error("Plain `codex` was not switched back to opencodex. Fix the reported Codex config issue and retry.");
988
- break;
989
- }
990
- const target = collectOrcaCodexHomeDiagnostic();
991
- console.log(`Plain \`codex\` now routes through opencodex in ${target.effectiveCodexHome} (undo with: ocx restore).`);
992
- break;
993
- }
994
- const desired = setIntegrationEnabled("codex", false);
995
- if (!desired.ok) {
996
- process.exitCode = desired.reason === "conflict" ? 2 : 1;
997
- if (restoreJson) {
998
- // Machine-readable contract: every restore --json outcome emits one
999
- // schema-complete envelope on stdout, including pre-machinery failures.
1000
- const { skippedRestoreEnvelope } = await import("../codex/inject");
1001
- console.log(JSON.stringify(skippedRestoreEnvelope(false, `Codex desired state was not saved (${desired.reason}).`)));
1002
- } else {
1003
- console.error(`Codex desired state was not saved (${desired.reason}).`);
1004
- }
1005
- break;
1006
- }
1007
- // A repeated OFF on an already-clean home is a policy no-op. Do not enter
1008
- // restore's native-profile machinery merely to prove there is nothing to
1009
- // restore: those locks live in CODEX_HOME and a skip must create nothing.
1010
- if (desired.status === "unchanged") {
1011
- const { classifyNativeRoutedResidue } = await import("../codex/native-residue");
1012
- if (classifyNativeRoutedResidue().kind === "clean") {
1013
- const alreadyOff = "Codex integration is already OFF and native; no Codex files changed.";
1014
- if (restoreJson) {
1015
- const { skippedRestoreEnvelope } = await import("../codex/inject");
1016
- console.log(JSON.stringify(skippedRestoreEnvelope(true, alreadyOff)));
1017
- } else {
1018
- console.log(alreadyOff);
1019
- }
1020
- break;
1021
- }
1022
- }
1023
- let r: { success: boolean; message: string };
1024
- try {
1025
- r = await restoreNativeCodexAsync({ revalidateDesiredState: true });
1026
- } catch (err) {
1027
- r = { success: false, message: err instanceof Error ? err.message : String(err) };
1028
- }
1029
- if (restoreJson) {
1030
- // Spawned callers need the artifact-level result to distinguish a busy
1031
- // history worker from a successful native restore. Keep stdout machine
1032
- // readable; human framing remains the default command contract.
1033
- console.log(JSON.stringify(r));
1034
- if (!r.success) process.exitCode = 1;
1035
- break;
1036
- }
1037
- if (r.success) console.log(`✅ ${r.message}`);
1038
- else {
1039
- console.error(`⚠️ ${r.message}`);
1040
- process.exitCode = 1;
1041
- }
1042
- try {
1043
- const g = stripGrokConfig();
1044
- if (g.changed) console.log(`✅ ${g.message}`);
1045
- else if (!g.ok) {
1046
- console.error(`⚠️ ${g.message}`);
1047
- process.exitCode = 1;
1048
- }
1049
- } catch { /* best-effort */ }
1050
- if (r.success) {
1051
- console.log("Codex integration is OFF and plain `codex` now runs natively. Switch back with: ocx restore back");
1052
- } else {
1053
- console.error("Plain `codex` was not fully restored. Inspect $CODEX_HOME/config.toml before using native Codex.");
1054
- }
1055
- break;
1056
- }
1057
- case "recover-history":
1058
- await handleRecoverHistory();
1059
- break;
1060
- case "uninstall":
1061
- case "remove":
1062
- await handleUninstall();
1063
- break;
1064
- case "status":
1065
- await handleStatus();
1066
- break;
1067
- case "doctor": {
1068
- const { runDoctor } = await import("./doctor");
1069
- await runDoctor(args.slice(1));
1070
- break;
1071
- }
1072
- case "debug": {
1073
- const { handleDebugCommand } = await import("./debug");
1074
- await handleDebugCommand(args.slice(1));
1075
- break;
1076
- }
1077
- case "ensure":
1078
- await handleEnsure();
1079
- break;
1080
- case "login": {
1081
- const { handleLogin } = await import("../oauth/login-cli");
1082
- await handleLogin(args[1]);
1083
- break;
1084
- }
1085
- case "logout": {
1086
- const { removeCredential } = await import("../oauth/store");
1087
- const name = (args[1] ?? "").trim().toLowerCase();
1088
- await removeCredential(name);
1089
- console.log(`Logged out of ${name || "(none)"}.`);
1090
- break;
1091
- }
1092
- case "sync": {
1093
- const restartCodex = args.slice(1).includes("--restart-codex");
1094
- const synced = await syncModelsToCodex((await findLiveProxy())?.port);
1095
- if (synced.status === "skipped") {
1096
- console.log("Codex integration is OFF; sync skipped and no Codex files changed.");
1097
- } else if (!synced.ok) {
1098
- process.exitCode = 1;
1099
- console.error("Codex sync did not complete. Fix the reported Codex config issue and retry.");
1100
- }
1101
- // Only warn/restart when a catalog or models_cache write actually happened. This is
1102
- // deliberately not an `else`: refreshCodexModelCatalog runs before injectCodexConfig,
1103
- // so a sync can fail (`ok: false`) after the catalog was already rewritten — which is
1104
- // exactly when a long-lived app-server is holding the stale list.
1105
- if (synced.catalogWritten || synced.cacheSynced) {
1106
- const { afterCatalogWriteHandleAppServers } = await import("../codex/app-server-processes");
1107
- afterCatalogWriteHandleAppServers({ restart: restartCodex, log: console });
1108
- }
1109
- break;
1110
- }
1111
- case "v2": {
1112
- const { cmdV2 } = await import("./v2");
1113
- process.exitCode = await cmdV2(args.slice(1), {}, async () => (await findLiveProxy())?.port);
1114
- break;
1115
- }
1116
- case "sync-cache": {
1117
- const restartCodex = args.slice(1).includes("--restart-codex");
1118
- if (!shouldSyncCodexOnStart(loadConfig())) {
1119
- console.log("Codex integration is OFF; cache sync skipped and no Codex files changed.");
1120
- break;
1121
- }
1122
- const { withCatalogWriteSerialization } = await import("../codex/catalog-write-serialization");
1123
- const { invalidateCodexModelsCacheWithPermit } = await import("../codex/catalog/sync");
1124
- const { getCodexHome } = await import("../codex/paths");
1125
- const owningCodexHome = getCodexHome();
1126
- const invalidated = withCatalogWriteSerialization(owningCodexHome, permit =>
1127
- invalidateCodexModelsCacheWithPermit(permit, owningCodexHome));
1128
- // Only warn/restart when models_cache was actually rewritten from a readable catalog.
1129
- if (invalidated.kind === "completed" && invalidated.value) {
1130
- const { afterCatalogWriteHandleAppServers } = await import("../codex/app-server-processes");
1131
- afterCatalogWriteHandleAppServers({ restart: restartCodex, log: console });
1132
- }
1133
- break;
1134
- }
1135
- case "gui": {
1136
- const cfg = await import("../config");
1137
- const config = cfg.loadConfig();
1138
- // Identity-checked liveness (not the pid file + a fixed sleep): finds a fallback-port
1139
- // proxy and waits until the spawned one actually answers before opening the browser.
1140
- let live = await findLiveProxy();
1141
- if (!live) {
1142
- console.log("Proxy not running. Starting...");
1143
- const child = spawn(process.execPath, startArgv((config.port ?? 10100) > 0 ? (config.port ?? 10100) : undefined), {
1144
- detached: true,
1145
- stdio: "ignore",
1146
- windowsHide: true,
1147
- env: withProcessRuntimeProvenance(process.env),
1148
- });
1149
- child.unref();
1150
- live = await waitForProxy();
1151
- if (!live) {
1152
- console.error("❌ Proxy did not become healthy after starting. Not opening the GUI.");
1153
- process.exit(1);
1154
- }
1155
- }
1156
- // Open the host the proxy actually binds — `localhost` only answers for
1157
- // loopback/wildcard binds, not a concrete LAN/IPv6 hostname.
1158
- const guiHost = probeHostname(live?.hostname ?? config.hostname);
1159
- const guiUrl = `http://${guiHost === "127.0.0.1" ? "localhost" : guiHost}:${live?.port ?? config.port}`;
1160
- console.log(`Opening ${guiUrl}`);
1161
- const { openUrl } = await import("../lib/open-url");
1162
- openUrl(guiUrl);
1163
- break;
1164
- }
1165
- case "service":
1166
- await serviceCommand(...args.slice(1));
1167
- break;
1168
- case "tray": {
1169
- const { windowsTrayCommand } = await import("../tray/windows");
1170
- await windowsTrayCommand(args.slice(1));
1171
- break;
1172
- }
1173
- case "codex-shim": {
1174
- const { codexShimStatus, diagnoseCodexShim, installCodexShim, uninstallCodexShim } = await import("../codex/shim");
1175
- switch (args[1]) {
1176
- case "install": {
1177
- const r = installCodexShim();
1178
- const { collectCodexShimReadinessWarnings } = await import("./codex-shim-readiness");
1179
- const warnings = diagnoseCodexShim().healthy
1180
- ? collectCodexShimReadinessWarnings()
1181
- : [];
1182
- console.log(`${r.installed && warnings.length === 0 ? "✅ " : "⚠️ "}${r.message}`);
1183
- for (const warning of warnings) console.warn(` ${warning}`);
1184
- break;
1185
- }
1186
- case "status":
1187
- console.log(codexShimStatus());
1188
- break;
1189
- case "uninstall":
1190
- case "remove": {
1191
- const r = uninstallCodexShim();
1192
- console.log(r.removed ? `✅ ${r.message}` : `⚠️ ${r.message}`);
1193
- break;
1194
- }
1195
- default:
1196
- console.error("Usage: ocx codex-shim <install|status|uninstall|remove>");
1197
- process.exit(1);
1198
- }
1199
- break;
1200
- }
1201
- case "update": {
1202
- // `ocx update --help` must print usage and exit WITHOUT side effects — running the
1203
- // real self-update stops the proxy and drops in-flight routed streams (issue #168).
1204
- if (hasHelpFlag(args.slice(1))) {
1205
- printSubcommandUsage("update");
1206
- break;
1207
- }
1208
- const { runUpdate } = await import("../update");
1209
- await runUpdate();
1210
- break;
1211
- }
1212
- case "__refresh-version": {
1213
- // Hidden, detached helper spawned by the update prompt to refresh the
1214
- // cached latest version without blocking the foreground start. Not in help.
1215
- const { refreshVersionCache } = await import("../update/notify");
1216
- const channel = args[1] === "preview" ? "preview" : "latest";
1217
- await refreshVersionCache(channel);
1218
- break;
1219
- }
1220
- case "__tray-start":
1221
- case "__tray-restart":
1222
- case "__startup-health":
1223
- await dispatchInternalCliCommand(command as InternalCliCommand, {
1224
- trayStart: async () => { await handleTrayProxyStart(); },
1225
- trayRestart: handleTrayProxyRestart,
1226
- startupHealth: async () => {
1227
- const { collectStartupHealth } = await import("../codex/autostart-health");
1228
- console.log(JSON.stringify(collectStartupHealth(loadConfig())));
1229
- },
915
+ process.exit(await dispatchCommand(head, {
916
+ args,
917
+ command,
918
+ head,
919
+ loadConfig,
920
+ findLiveProxy,
921
+ probeHostname,
922
+ waitForProxy,
923
+ startArgv,
924
+ spawnDetached: argv => {
925
+ const child = spawn(process.execPath, argv, {
926
+ detached: true,
927
+ stdio: "ignore",
928
+ windowsHide: true,
929
+ env: withProcessRuntimeProvenance(process.env),
1230
930
  });
1231
- break;
1232
- case "__tray-host": {
1233
- const { runWindowsTrayHost } = await import("../tray/windows");
1234
- await runWindowsTrayHost();
1235
- break;
1236
- }
1237
- case "__gui-update-worker": {
1238
- const jobId = args[1];
1239
- if (!jobId) process.exit(1);
1240
- const channel = normalizeUpdateChannel(args[2]);
1241
- await runGuiUpdateWorker(jobId, channel, args[3] === "restart");
1242
- break;
1243
- }
1244
- case "restart": {
1245
- // The running proxy owns its drain and replacement through /api/system/restart.
1246
- // If nothing is live, restart degrades to the documented `ensure` start behavior.
1247
- await handleProxyRestart(handleRestartStartWhenStopped);
1248
- break;
1249
- }
1250
- case "health": {
1251
- const healthArgs = args.slice(1);
1252
- const wantsHealthJson = healthArgs.includes("--json");
1253
- const live = await findLiveProxy();
1254
- if (wantsHealthJson) {
1255
- console.log(JSON.stringify({ ok: !!live, pid: live?.pid ?? null, port: live?.port ?? null }));
1256
- } else {
1257
- console.log(live ? `Proxy healthy (PID ${live.pid}, port ${live.port})` : "Proxy not healthy");
1258
- }
1259
- process.exit(live ? 0 : 1);
1260
- }
1261
- case "ready":
1262
- // Fail-closed impossible-state guard: readyArgs is populated by the
1263
- // preparse block before maybeAutoRestoreCodexShim, so reaching here
1264
- // without it means dispatch diverged. Refuse with code 64 and perform
1265
- // NO I/O (no discovery/probe). process.exit is `never`, narrowing below.
1266
- if (!readyArgs) process.exit(64);
1267
- await handleReady(readyArgs);
1268
- break;
1269
- case "provider": {
1270
- const { handleProviderCommand } = await import("./provider");
1271
- await handleProviderCommand(args.slice(1));
1272
- break;
1273
- }
1274
- case "account": {
1275
- const { cmdAccount } = await import("./account");
1276
- process.exitCode = await cmdAccount(args.slice(1));
1277
- break;
1278
- }
1279
- case "models":
1280
- case "model": {
1281
- const { handleModels } = await import("./models");
1282
- await handleModels(args.slice(1));
1283
- break;
1284
- }
1285
- case "combo": {
1286
- const { handleComboCommand } = await import("./combo");
1287
- process.exitCode = await handleComboCommand(args.slice(1));
1288
- break;
1289
- }
1290
- case "route": {
1291
- if (args[1] !== "combo" && args[1] !== "policy") {
1292
- console.error("Usage: ocx route <combo|policy> <subcommand>");
1293
- process.exitCode = 2;
1294
- break;
1295
- }
1296
- if (args[1] === "combo") {
1297
- const { handleComboCommand } = await import("./combo");
1298
- process.exitCode = await handleComboCommand(args.slice(2));
1299
- } else {
1300
- const { handleRoutePolicyCommand } = await import("./route-policy");
1301
- process.exitCode = await handleRoutePolicyCommand(args.slice(2));
1302
- }
1303
- break;
1304
- }
1305
- case "agent": {
1306
- const { handleAgentCommand } = await import("./agent");
1307
- process.exitCode = await handleAgentCommand(args.slice(1));
1308
- break;
1309
- }
1310
- case "observe": {
1311
- const { handleObserveCommand } = await import("./observe");
1312
- process.exitCode = await handleObserveCommand(args.slice(1));
1313
- break;
1314
- }
1315
- case "logs":
1316
- case "usage":
1317
- case "storage":
1318
- case "memory": {
1319
- const { handleObserveCommand } = await import("./observe");
1320
- process.exitCode = await handleObserveCommand([command, ...args.slice(1)]);
1321
- break;
1322
- }
1323
- case "access": {
1324
- const { handleAccessCommand } = await import("./access");
1325
- process.exitCode = await handleAccessCommand(args.slice(1));
1326
- break;
1327
- }
1328
- case "api-key": {
1329
- const { handleAccessCommand } = await import("./access");
1330
- process.exitCode = await handleAccessCommand(["key", ...args.slice(1)]);
1331
- break;
1332
- }
1333
- case "export": {
1334
- const { handleExportCommand } = await import("./export-command");
1335
- process.exitCode = await handleExportCommand(args.slice(1));
1336
- break;
1337
- }
1338
- case "grok": {
1339
- const { handleGrokCommand } = await import("./integrations");
1340
- process.exitCode = await handleGrokCommand(args.slice(1));
1341
- break;
1342
- }
1343
- case "integration": {
1344
- const integration = args[1];
1345
- if (integration === "grok") {
1346
- const { handleGrokCommand } = await import("./integrations");
1347
- process.exitCode = await handleGrokCommand(args.slice(2));
1348
- } else if (integration === "claude") {
1349
- const { handleClaudeConfigCommand } = await import("./integrations");
1350
- process.exitCode = await handleClaudeConfigCommand(args.slice(2));
1351
- } else if (integration === "client") {
1352
- const { handleClientIntegrationCommand } = await import("./integrations");
1353
- process.exitCode = await handleClientIntegrationCommand(args.slice(2));
1354
- } else {
1355
- console.error("Usage: ocx integration <claude|grok|client> <subcommand>");
1356
- process.exitCode = 2;
1357
- }
1358
- break;
1359
- }
1360
- case "system": {
1361
- const { handleSystemCommand } = await import("./system-command");
1362
- process.exitCode = await handleSystemCommand(args.slice(1));
1363
- break;
1364
- }
1365
- case "config": {
1366
- const { handleConfigCommand } = await import("./config-command");
1367
- process.exitCode = await handleConfigCommand(args.slice(1));
1368
- break;
1369
- }
1370
- case "lab": {
1371
- const { handleLabCommand } = await import("./lab");
1372
- process.exitCode = await handleLabCommand(args.slice(1));
1373
- break;
1374
- }
1375
- case "claude": {
1376
- const { cmdClaude } = await import("./claude");
1377
- // "ocx claude desktop" → write Desktop 3P config
1378
- if (args[1] === "desktop") {
1379
- const { handleClaudeDesktopCommand } = await import("./claude-desktop");
1380
- const exitCode = await handleClaudeDesktopCommand(args.slice(2));
1381
- if (exitCode !== 0) process.exit(exitCode);
1382
- break;
1383
- }
1384
- if (args[1] === "config") {
1385
- const { handleClaudeConfigCommand } = await import("./integrations");
1386
- process.exitCode = await handleClaudeConfigCommand(args.slice(2));
1387
- break;
1388
- }
1389
- process.exit(await cmdClaude(args.slice(1)));
1390
- }
1391
- case "opencode": {
1392
- const { cmdOpencode } = await import("./opencode");
1393
- process.exit(await cmdOpencode(args.slice(1)));
1394
- }
1395
- case "help":
1396
- case "--help":
1397
- case "-h":
1398
- case undefined:
1399
- printUsage();
1400
- break;
1401
- default:
1402
- console.error(`Unknown command: ${command}`);
1403
- printUsage();
1404
- process.exit(1);
1405
- }
931
+ child.unref();
932
+ },
933
+ handleStart,
934
+ handleStop,
935
+ handleEnsure,
936
+ handleTrayProxyStart,
937
+ handleTrayProxyRestart,
938
+ handleRestartStartWhenStopped,
939
+ handleProxyRestart,
940
+ handleUninstall,
941
+ handleStatus,
942
+ handleRecoverHistory,
943
+ handleReady,
944
+ }));