@iislee/opencodex 2.11.0 → 2.35.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 (733) hide show
  1. package/AGENTS_INSTALL.md +109 -0
  2. package/README.md +117 -22
  3. package/bin/ocx.mjs +245 -88
  4. package/bin/package-main.mjs +1 -1
  5. package/gui/dist/assets/index-Be1sDpWw.css +1 -0
  6. package/gui/dist/assets/index-D4OKgUV4.js +114 -0
  7. package/gui/dist/index.html +2 -2
  8. package/gui/dist/provider-icons/alibaba-color.svg +1 -1
  9. package/gui/dist/provider-icons/antigravity-color.svg +1 -1
  10. package/gui/dist/provider-icons/claude-color.svg +1 -1
  11. package/gui/dist/provider-icons/cline-color.svg +16 -0
  12. package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -1
  13. package/gui/dist/provider-icons/commandcode-color.svg +1 -0
  14. package/gui/dist/provider-icons/copilot-color.svg +1 -1
  15. package/gui/dist/provider-icons/cursor-color.svg +1 -1
  16. package/gui/dist/provider-icons/deepseek-color.svg +1 -1
  17. package/gui/dist/provider-icons/firepass-color.svg +1 -1
  18. package/gui/dist/provider-icons/fireworks-color.svg +1 -1
  19. package/gui/dist/provider-icons/gemini-color.svg +1 -1
  20. package/gui/dist/provider-icons/github-copilot-color.svg +1 -1
  21. package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -1
  22. package/gui/dist/provider-icons/grok.svg +1 -1
  23. package/gui/dist/provider-icons/groq-color.svg +1 -1
  24. package/gui/dist/provider-icons/huggingface-color.svg +1 -1
  25. package/gui/dist/provider-icons/kimi-color.svg +1 -1
  26. package/gui/dist/provider-icons/kiro-color.svg +2 -2
  27. package/gui/dist/provider-icons/lm-studio-color.svg +1 -1
  28. package/gui/dist/provider-icons/mistral-color.svg +1 -1
  29. package/gui/dist/provider-icons/moonshot-color.svg +1 -1
  30. package/gui/dist/provider-icons/nvidia-color.svg +1 -1
  31. package/gui/dist/provider-icons/ollama-color.svg +1 -1
  32. package/gui/dist/provider-icons/openai.svg +1 -1
  33. package/gui/dist/provider-icons/opencode.svg +2 -1
  34. package/gui/dist/provider-icons/openrouter-color.svg +1 -1
  35. package/gui/dist/provider-icons/pi.svg +2 -2
  36. package/gui/dist/provider-icons/qianfan-color.svg +1 -1
  37. package/gui/dist/provider-icons/qwen-portal-color.svg +1 -1
  38. package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -1
  39. package/gui/dist/provider-icons/vllm-color.svg +1 -1
  40. package/gui/dist/provider-icons/xiaomi-color.svg +1 -1
  41. package/package.json +21 -10
  42. package/src/adapters/anthropic-output-schema.ts +137 -0
  43. package/src/adapters/anthropic.ts +475 -62
  44. package/src/adapters/base.ts +82 -7
  45. package/src/adapters/client-fingerprint.ts +18 -12
  46. package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
  47. package/src/adapters/command-code.ts +637 -0
  48. package/src/adapters/cursor/call-id.ts +44 -0
  49. package/src/adapters/cursor/catalog.ts +541 -0
  50. package/src/adapters/cursor/checkpoint-store.ts +308 -0
  51. package/src/adapters/cursor/cursor-errors.ts +144 -6
  52. package/src/adapters/cursor/discovery.ts +122 -14
  53. package/src/adapters/cursor/effort-map.ts +106 -4
  54. package/src/adapters/cursor/envelope-echo.ts +290 -0
  55. package/src/adapters/cursor/framing.ts +39 -0
  56. package/src/adapters/cursor/h2-pool.ts +123 -0
  57. package/src/adapters/cursor/http1-bidi.ts +361 -0
  58. package/src/adapters/cursor/images.ts +704 -0
  59. package/src/adapters/cursor/live-models.ts +180 -59
  60. package/src/adapters/cursor/live-transport.ts +623 -170
  61. package/src/adapters/cursor/message-mapper.ts +4 -1
  62. package/src/adapters/cursor/native-exec-common.ts +23 -2
  63. package/src/adapters/cursor/native-exec-desktop.ts +23 -0
  64. package/src/adapters/cursor/native-exec-fs.ts +10 -7
  65. package/src/adapters/cursor/native-exec-network.ts +1 -1
  66. package/src/adapters/cursor/native-exec-shell.ts +5 -3
  67. package/src/adapters/cursor/native-exec.ts +121 -14
  68. package/src/adapters/cursor/protobuf-events.ts +829 -11
  69. package/src/adapters/cursor/protobuf-request.ts +447 -75
  70. package/src/adapters/cursor/request-builder.ts +271 -35
  71. package/src/adapters/cursor/tool-definitions.ts +252 -12
  72. package/src/adapters/cursor/tool-result-normalize.ts +115 -0
  73. package/src/adapters/cursor/transport.ts +22 -0
  74. package/src/adapters/cursor/types.ts +48 -1
  75. package/src/adapters/cursor.ts +336 -39
  76. package/src/adapters/exec-tool-result-normalize.ts +99 -0
  77. package/src/adapters/google-antigravity-replay.ts +713 -41
  78. package/src/adapters/google-antigravity-wire.ts +48 -10
  79. package/src/adapters/google-errors.ts +44 -12
  80. package/src/adapters/google-http.ts +38 -10
  81. package/src/adapters/google-tool-schema.ts +80 -15
  82. package/src/adapters/google-truncation.ts +11 -0
  83. package/src/adapters/google.ts +742 -86
  84. package/src/adapters/identity.ts +39 -6
  85. package/src/adapters/image.ts +1 -1
  86. package/src/adapters/kiro-constants.ts +27 -0
  87. package/src/adapters/kiro-errors.ts +11 -0
  88. package/src/adapters/kiro-events.ts +19 -1
  89. package/src/adapters/kiro-thinking.ts +18 -2
  90. package/src/adapters/kiro-tools.ts +65 -17
  91. package/src/adapters/kiro.ts +270 -73
  92. package/src/adapters/mimo-free.ts +17 -0
  93. package/src/adapters/openai-chat-url.ts +11 -0
  94. package/src/adapters/openai-chat.ts +1377 -344
  95. package/src/adapters/openai-responses-url.ts +16 -0
  96. package/src/adapters/openai-responses.ts +1164 -64
  97. package/src/adapters/registry.ts +175 -0
  98. package/src/adapters/responses-tool-schema.ts +67 -0
  99. package/src/adapters/run-turn-queue.ts +36 -1
  100. package/src/adapters/tool-call-id.ts +119 -0
  101. package/src/adapters/tool-catalog-nudge.ts +105 -21
  102. package/src/adapters/xai-tool-schema.ts +436 -0
  103. package/src/adapters/xai-web-search.ts +186 -0
  104. package/src/bridge.ts +512 -92
  105. package/src/chat/inbound.ts +57 -20
  106. package/src/chat/outbound.ts +97 -33
  107. package/src/claude/agents-inject.ts +40 -10
  108. package/src/claude/context-windows.ts +37 -14
  109. package/src/claude/desktop-3p.ts +243 -9
  110. package/src/claude/gateway-cache.ts +41 -4
  111. package/src/claude/inbound.ts +72 -3
  112. package/src/claude/model-info.ts +38 -15
  113. package/src/claude/outbound.ts +88 -21
  114. package/src/cli/access.ts +46 -3
  115. package/src/cli/account-api.ts +150 -19
  116. package/src/cli/account-auth.ts +33 -6
  117. package/src/cli/account-catalog-refresh.ts +14 -0
  118. package/src/cli/account-extended.ts +648 -28
  119. package/src/cli/account-main.ts +317 -0
  120. package/src/cli/account.ts +97 -19
  121. package/src/cli/agent.ts +53 -2
  122. package/src/cli/alias.ts +66 -0
  123. package/src/cli/capabilities-command.ts +94 -0
  124. package/src/cli/capabilities.ts +496 -0
  125. package/src/cli/claude-agent-startup-sync.ts +73 -0
  126. package/src/cli/claude-desktop.ts +60 -15
  127. package/src/cli/claude.ts +129 -35
  128. package/src/cli/codex-log-guard-doctor.ts +103 -0
  129. package/src/cli/codex-shim-autorestore.ts +2 -0
  130. package/src/cli/codex-shim-readiness.ts +76 -0
  131. package/src/cli/combo.ts +8 -0
  132. package/src/cli/config-command.ts +74 -10
  133. package/src/cli/dispatch.ts +821 -0
  134. package/src/cli/doctor.ts +440 -56
  135. package/src/cli/ensure-desired-integrations.ts +152 -0
  136. package/src/cli/export-command.ts +46 -20
  137. package/src/cli/help.ts +34 -274
  138. package/src/cli/index.ts +417 -528
  139. package/src/cli/init.ts +5 -17
  140. package/src/cli/inspect.ts +230 -0
  141. package/src/cli/integrations.ts +120 -2
  142. package/src/cli/lab.ts +607 -0
  143. package/src/cli/launcher-context.ts +77 -0
  144. package/src/cli/minimax.ts +497 -0
  145. package/src/cli/models-runtime.ts +130 -2
  146. package/src/cli/models.ts +118 -16
  147. package/src/cli/observe.ts +123 -11
  148. package/src/cli/opencode.ts +4 -2
  149. package/src/cli/provider-runtime.ts +34 -3
  150. package/src/cli/provider.ts +25 -3
  151. package/src/cli/ready.ts +301 -0
  152. package/src/cli/registry.ts +476 -0
  153. package/src/cli/root.ts +86 -0
  154. package/src/cli/route-policy.ts +92 -0
  155. package/src/cli/runtime-api.ts +57 -10
  156. package/src/cli/star-prompt.ts +71 -15
  157. package/src/cli/status.ts +51 -4
  158. package/src/cli/storage.ts +234 -0
  159. package/src/cli/system-command.ts +16 -0
  160. package/src/cli/system-restart-client.ts +146 -0
  161. package/src/cli/tray-proxy.ts +153 -6
  162. package/src/cli/usage-report.ts +184 -0
  163. package/src/cli/v2.ts +137 -18
  164. package/src/cli/version-skew.ts +46 -0
  165. package/src/cli.ts +1 -1
  166. package/src/clients/config-export.ts +1442 -23
  167. package/src/clients/effective-status.ts +30 -2
  168. package/src/clients/probes/cline.ts +81 -0
  169. package/src/codex/account-label.ts +35 -1
  170. package/src/codex/account-lifecycle.ts +130 -13
  171. package/src/codex/account-namespaces.ts +49 -3
  172. package/src/codex/account-pause.ts +2 -1
  173. package/src/codex/account-priority.ts +84 -0
  174. package/src/codex/account-store.ts +29 -2
  175. package/src/codex/account-usability.ts +25 -2
  176. package/src/codex/admission.ts +256 -0
  177. package/src/codex/affinity-debug.ts +162 -0
  178. package/src/codex/app-server-processes.ts +570 -107
  179. package/src/codex/app-server-restart-service.ts +232 -0
  180. package/src/codex/auth-api.ts +893 -246
  181. package/src/codex/auth-collision.ts +5 -3
  182. package/src/codex/auth-context.ts +348 -32
  183. package/src/codex/autostart-health.ts +24 -1
  184. package/src/codex/catalog/account-models.ts +67 -0
  185. package/src/codex/catalog/aggregation.ts +86 -10
  186. package/src/codex/catalog/bundled.ts +331 -33
  187. package/src/codex/catalog/effort.ts +138 -32
  188. package/src/codex/catalog/filesystem-evidence.ts +302 -0
  189. package/src/codex/catalog/kinds.ts +2 -0
  190. package/src/codex/catalog/metadata.ts +555 -45
  191. package/src/codex/catalog/native-models.ts +75 -0
  192. package/src/codex/catalog/parsing.ts +269 -30
  193. package/src/codex/catalog/provider-fetch.ts +1626 -144
  194. package/src/codex/catalog/sync.ts +1576 -187
  195. package/src/codex/catalog-admission.ts +199 -0
  196. package/src/codex/catalog-refresh-status.ts +105 -0
  197. package/src/codex/catalog-write-serialization.ts +242 -0
  198. package/src/codex/catalog.ts +6 -3
  199. package/src/codex/codex-write-lock.ts +386 -0
  200. package/src/codex/convergence-types.ts +614 -0
  201. package/src/codex/convergence.ts +676 -0
  202. package/src/codex/coordinator-doctor.ts +332 -0
  203. package/src/codex/custom-model-catalog-migration.ts +176 -0
  204. package/src/codex/desired-state.ts +230 -0
  205. package/src/codex/desktop-app-restart.ts +355 -0
  206. package/src/codex/features.ts +636 -39
  207. package/src/codex/generation.ts +202 -0
  208. package/src/codex/history-job.ts +436 -0
  209. package/src/codex/history-lock.ts +242 -0
  210. package/src/codex/history-manifest.ts +112 -0
  211. package/src/codex/history-migration-guardian.ts +30 -24
  212. package/src/codex/history-provider.ts +1016 -235
  213. package/src/codex/history-transition.ts +105 -0
  214. package/src/codex/history-worker.ts +223 -0
  215. package/src/codex/inject-coordination.ts +373 -0
  216. package/src/codex/inject.ts +1114 -152
  217. package/src/codex/injected-marker.ts +37 -3
  218. package/src/codex/integration-record.ts +266 -0
  219. package/src/codex/internal/catalog-writer.ts +203 -0
  220. package/src/codex/internal/history-writer.ts +81 -0
  221. package/src/codex/journal.ts +66 -4
  222. package/src/codex/log-guard/inspect.ts +524 -0
  223. package/src/codex/log-guard/lock.ts +150 -0
  224. package/src/codex/log-guard/maintenance.ts +403 -0
  225. package/src/codex/log-guard/path-safety.ts +88 -0
  226. package/src/codex/log-guard/policy.ts +44 -0
  227. package/src/codex/log-guard/processes.ts +205 -0
  228. package/src/codex/log-guard/protection.ts +489 -0
  229. package/src/codex/log-guard/sqlite-errors.ts +9 -0
  230. package/src/codex/main-account-cache.ts +24 -0
  231. package/src/codex/main-account.ts +29 -1
  232. package/src/codex/management-convergence.ts +167 -0
  233. package/src/codex/model-cache.ts +56 -10
  234. package/src/codex/model-entitlements.ts +360 -0
  235. package/src/codex/native-main-admission.ts +47 -0
  236. package/src/codex/native-main-auth-temp.ts +187 -0
  237. package/src/codex/native-main-claim.ts +178 -0
  238. package/src/codex/native-main-lock-file.ts +162 -0
  239. package/src/codex/native-main-owner.ts +329 -0
  240. package/src/codex/native-profile-api.ts +247 -0
  241. package/src/codex/native-profile-manager.ts +1531 -0
  242. package/src/codex/native-profile-processes.ts +121 -0
  243. package/src/codex/native-profile-recovery.ts +99 -0
  244. package/src/codex/native-profile-stage-store.ts +387 -0
  245. package/src/codex/native-profile-startup.ts +622 -0
  246. package/src/codex/native-profile-store.ts +855 -0
  247. package/src/codex/native-profile-types.ts +120 -0
  248. package/src/codex/native-residue.ts +675 -0
  249. package/src/codex/paths.ts +80 -1
  250. package/src/codex/plan-from-token.ts +140 -0
  251. package/src/codex/plan.ts +40 -0
  252. package/src/codex/plugins-doctor.ts +1 -1
  253. package/src/codex/pool-rotation.ts +74 -4
  254. package/src/codex/project-config-warnings.ts +20 -6
  255. package/src/codex/prompt-journal.ts +352 -0
  256. package/src/codex/prompt-layers.ts +1313 -0
  257. package/src/codex/prompt-lock.ts +143 -0
  258. package/src/codex/prompt-text-probe.ts +238 -0
  259. package/src/codex/quota-rejection.ts +298 -0
  260. package/src/codex/quota.ts +264 -21
  261. package/src/codex/refresh.ts +11 -2
  262. package/src/codex/reset-credit-recovery.ts +1044 -0
  263. package/src/codex/routing.ts +514 -94
  264. package/src/codex/runtime.ts +159 -38
  265. package/src/codex/shim.ts +1065 -31
  266. package/src/codex/subagent-model-fallback.ts +437 -43
  267. package/src/codex/sync.ts +191 -2
  268. package/src/codex/transition-state.ts +720 -0
  269. package/src/codex/upstream-host-health.ts +368 -0
  270. package/src/codex/user-identity.ts +557 -0
  271. package/src/codex/warmup.ts +187 -81
  272. package/src/codex/write-coordination.ts +114 -0
  273. package/src/combos/failover.ts +47 -0
  274. package/src/combos/index.ts +4 -0
  275. package/src/combos/request.ts +32 -0
  276. package/src/combos/types.ts +91 -9
  277. package/src/compatibility/index.ts +26 -0
  278. package/src/compatibility/manifest.ts +253 -0
  279. package/src/compatibility/openai-responses.ts +121 -0
  280. package/src/config/atomic-write.ts +219 -0
  281. package/src/config/paths.ts +40 -0
  282. package/src/config/process-state.ts +309 -0
  283. package/src/config/provider-name.ts +24 -0
  284. package/src/config/provider-validation.ts +177 -0
  285. package/src/config/rebase-provenance.ts +68 -0
  286. package/src/config.ts +1713 -709
  287. package/src/generated/compatibility-version.json +3324 -0
  288. package/src/generated/{jawcode-model-metadata.ts → model-metadata.ts} +19 -17
  289. package/src/grok/inject.ts +16 -5
  290. package/src/grok/inspect.ts +45 -0
  291. package/src/grok/sync.ts +2 -2
  292. package/src/images/loop.ts +157 -33
  293. package/src/images/plan.ts +24 -13
  294. package/src/integrations/config-io.ts +269 -0
  295. package/src/integrations/journal.ts +315 -0
  296. package/src/integrations/merge.ts +135 -0
  297. package/src/integrations/mutation-flight.ts +71 -0
  298. package/src/integrations/native/ownership-preflight.ts +202 -0
  299. package/src/integrations/omp-yaml-source.ts +358 -0
  300. package/src/integrations/owned-refresh.ts +74 -0
  301. package/src/integrations/ownership-policy.ts +160 -0
  302. package/src/integrations/ownership.ts +155 -0
  303. package/src/integrations/registry.ts +166 -0
  304. package/src/integrations/serialize.ts +314 -0
  305. package/src/integrations/state.ts +433 -0
  306. package/src/integrations/store.ts +103 -0
  307. package/src/integrations/writer-lock.ts +98 -0
  308. package/src/integrations/writer.ts +715 -0
  309. package/src/lab/artifacts/sanitize.ts +586 -0
  310. package/src/lab/artifacts/secure-fs.ts +475 -0
  311. package/src/lab/artifacts/store.ts +310 -0
  312. package/src/lab/automation/budgets.ts +78 -0
  313. package/src/lab/automation/config-persistence.ts +256 -0
  314. package/src/lab/automation/constants.ts +39 -0
  315. package/src/lab/automation/cooldown.ts +103 -0
  316. package/src/lab/automation/dispatch.ts +211 -0
  317. package/src/lab/automation/index.ts +13 -0
  318. package/src/lab/automation/orchestrator.ts +499 -0
  319. package/src/lab/automation/persistence.ts +512 -0
  320. package/src/lab/automation/planner.ts +371 -0
  321. package/src/lab/automation/policy.ts +136 -0
  322. package/src/lab/automation/queue.ts +191 -0
  323. package/src/lab/automation/recovery.ts +24 -0
  324. package/src/lab/automation/route-context.ts +21 -0
  325. package/src/lab/automation/run-key.ts +44 -0
  326. package/src/lab/automation/runs-query.ts +34 -0
  327. package/src/lab/automation/types.ts +160 -0
  328. package/src/lab/conformance/assertion.ts +325 -0
  329. package/src/lab/conformance/digest.ts +22 -0
  330. package/src/lab/conformance/executor.ts +741 -0
  331. package/src/lab/conformance/fixture-provider.ts +27 -0
  332. package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
  333. package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
  334. package/src/lab/conformance/harness-budget.ts +47 -0
  335. package/src/lab/conformance/index.ts +5 -0
  336. package/src/lab/conformance/jcs.ts +64 -0
  337. package/src/lab/conformance/json-pointer.ts +39 -0
  338. package/src/lab/conformance/manifest.ts +180 -0
  339. package/src/lab/conformance/mcp-stub.ts +179 -0
  340. package/src/lab/conformance/negative-controls.ts +164 -0
  341. package/src/lab/conformance/observation.ts +355 -0
  342. package/src/lab/conformance/runner.ts +68 -0
  343. package/src/lab/conformance/sse-normalize.ts +59 -0
  344. package/src/lab/conformance/suite-manifest.ts +78 -0
  345. package/src/lab/conformance/types.ts +214 -0
  346. package/src/lab/constants.ts +126 -0
  347. package/src/lab/digest.ts +64 -0
  348. package/src/lab/events/errors.ts +9 -0
  349. package/src/lab/events/limits.ts +117 -0
  350. package/src/lab/events/types.ts +229 -0
  351. package/src/lab/events/validate.ts +781 -0
  352. package/src/lab/fabric/constants.ts +40 -0
  353. package/src/lab/fabric/executor.ts +492 -0
  354. package/src/lab/fabric/index.ts +80 -0
  355. package/src/lab/fabric/manifest.ts +222 -0
  356. package/src/lab/fabric/observe.ts +489 -0
  357. package/src/lab/fabric/patch.ts +79 -0
  358. package/src/lab/fabric/producer-child.ts +139 -0
  359. package/src/lab/fabric/producer-isolate.ts +276 -0
  360. package/src/lab/fabric/producer-protocol.ts +61 -0
  361. package/src/lab/fabric/scratch.ts +439 -0
  362. package/src/lab/fabric/subject.ts +106 -0
  363. package/src/lab/fabric/types.ts +134 -0
  364. package/src/lab/fabric/verifier.ts +98 -0
  365. package/src/lab/index.ts +54 -0
  366. package/src/lab/ledger/artifact-refs.ts +127 -0
  367. package/src/lab/ledger/invalidation.ts +136 -0
  368. package/src/lab/ledger/purge.ts +310 -0
  369. package/src/lab/ledger/store.ts +532 -0
  370. package/src/lab/live/credential-lease.ts +53 -0
  371. package/src/lab/live/destination.ts +155 -0
  372. package/src/lab/live/executor.ts +336 -0
  373. package/src/lab/live/inert-tools.ts +56 -0
  374. package/src/lab/live/manifest.ts +85 -0
  375. package/src/lab/live/mcp-loopback.ts +57 -0
  376. package/src/lab/live/runner.ts +19 -0
  377. package/src/lab/live/sandbox.ts +61 -0
  378. package/src/lab/live/suite-manifest.ts +41 -0
  379. package/src/lab/live/transport.ts +118 -0
  380. package/src/lab/live/types.ts +197 -0
  381. package/src/lab/observe/from-conformance.ts +301 -0
  382. package/src/lab/observe/from-live.ts +117 -0
  383. package/src/lab/paths.ts +153 -0
  384. package/src/lab/projection/rebuild.ts +495 -0
  385. package/src/lab/projection/schema.ts +135 -0
  386. package/src/lab/projection/verdicts.ts +474 -0
  387. package/src/lab/projection/verification.ts +412 -0
  388. package/src/lab/public/bundle.ts +217 -0
  389. package/src/lab/public/community-authority.ts +175 -0
  390. package/src/lab/public/community-files.ts +29 -0
  391. package/src/lab/public/community.ts +479 -0
  392. package/src/lab/public/file-safety.ts +155 -0
  393. package/src/lab/public/ids.ts +26 -0
  394. package/src/lab/public/index.ts +16 -0
  395. package/src/lab/public/mutation-lock.ts +424 -0
  396. package/src/lab/public/operator.ts +353 -0
  397. package/src/lab/public/origin-purge.ts +79 -0
  398. package/src/lab/public/origin.ts +203 -0
  399. package/src/lab/public/privacy.ts +143 -0
  400. package/src/lab/public/private-file.ts +261 -0
  401. package/src/lab/public/project.ts +124 -0
  402. package/src/lab/public/purge-test-fault.ts +21 -0
  403. package/src/lab/public/purge.ts +223 -0
  404. package/src/lab/public/registry.ts +44 -0
  405. package/src/lab/public/revocation.ts +252 -0
  406. package/src/lab/public/signature.ts +243 -0
  407. package/src/lab/public/storage.ts +105 -0
  408. package/src/lab/public/strict-json.ts +206 -0
  409. package/src/lab/public/time.ts +26 -0
  410. package/src/lab/public/types.ts +172 -0
  411. package/src/lab/public/validate.ts +391 -0
  412. package/src/lab/query/catalog.ts +101 -0
  413. package/src/lab/query/connection.ts +107 -0
  414. package/src/lab/query/constants.ts +4 -0
  415. package/src/lab/query/cursor.ts +132 -0
  416. package/src/lab/query/dto-map.ts +277 -0
  417. package/src/lab/query/errors.ts +22 -0
  418. package/src/lab/query/freshness.ts +53 -0
  419. package/src/lab/query/index.ts +45 -0
  420. package/src/lab/query/latest-observation.ts +59 -0
  421. package/src/lab/query/passive-production.ts +159 -0
  422. package/src/lab/query/queries.ts +444 -0
  423. package/src/lab/query/types.ts +266 -0
  424. package/src/lab/subject/behavior-fingerprint.ts +77 -0
  425. package/src/lab/subject/installation-salt.ts +112 -0
  426. package/src/lab/subject/protocol-subject.ts +80 -0
  427. package/src/lab/subject/route-subject.ts +74 -0
  428. package/src/lib/admin-secrets.ts +24 -0
  429. package/src/lib/app-owned-memory-stores.ts +22 -0
  430. package/src/lib/bounded-body.ts +167 -11
  431. package/src/lib/bun-runtime.ts +125 -12
  432. package/src/lib/bun-stream-caps.ts +13 -9
  433. package/src/lib/codex-restart-contract.ts +120 -0
  434. package/src/lib/config-ownership.ts +26 -2
  435. package/src/lib/destination-policy.ts +65 -1
  436. package/src/lib/errors.ts +80 -5
  437. package/src/lib/fabric-task-execution-authority.ts +7 -0
  438. package/src/lib/fabric-task-host.ts +29 -0
  439. package/src/lib/lab-activation.ts +223 -0
  440. package/src/lib/lab-live-execution-authority.ts +13 -0
  441. package/src/lib/lab-live-host.ts +30 -0
  442. package/src/lib/lab-live-pinned-sender.ts +56 -0
  443. package/src/lib/lab-live-route-production.ts +130 -0
  444. package/src/lib/lab-passive-linker-registration.ts +26 -0
  445. package/src/lib/local-management-attestation.ts +51 -0
  446. package/src/lib/local-management-capability.ts +100 -0
  447. package/src/lib/local-provider-reload-contract.ts +100 -0
  448. package/src/lib/optional-shutdown-hooks.ts +57 -0
  449. package/src/lib/package-tree-integrity.ts +101 -0
  450. package/src/lib/pinned-http.ts +145 -26
  451. package/src/lib/process-control.ts +6 -2
  452. package/src/lib/provider-outbound.ts +49 -9
  453. package/src/lib/redact.ts +419 -3
  454. package/src/lib/self-launch-argv.ts +15 -0
  455. package/src/lib/server-resource-ownership.ts +71 -0
  456. package/src/lib/service-secrets.ts +15 -0
  457. package/src/lib/shadow-call.ts +35 -4
  458. package/src/lib/sse-decoder.ts +41 -0
  459. package/src/lib/state-store-registrations.ts +10 -2
  460. package/src/lib/system-restart-contract.ts +73 -0
  461. package/src/lib/token-estimate.ts +19 -2
  462. package/src/lib/tool-argument-integers.ts +253 -0
  463. package/src/lib/translator-budget.ts +44 -0
  464. package/src/lib/upstream-http-version.ts +57 -0
  465. package/src/lib/upstream-reachability.ts +95 -0
  466. package/src/lib/upstream-retry.ts +156 -3
  467. package/src/lib/windows-atomic-replace.ts +155 -0
  468. package/src/lib/windows-elevation.ts +70 -2
  469. package/src/lib/windows-secret-acl.ts +409 -69
  470. package/src/lib/windows-service-wrappers.ts +72 -0
  471. package/src/lib/windows-text.ts +106 -0
  472. package/src/lib/windows-user-principal.ts +341 -0
  473. package/src/lib/winsw.ts +33 -5
  474. package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
  475. package/src/oauth/account-import/index.ts +15 -0
  476. package/src/oauth/account-import/parser.ts +83 -0
  477. package/src/oauth/account-import/registry.ts +18 -0
  478. package/src/oauth/account-import/service.ts +75 -0
  479. package/src/oauth/account-import/types.ts +91 -0
  480. package/src/oauth/anthropic.ts +12 -1
  481. package/src/oauth/callback-server.ts +30 -4
  482. package/src/oauth/chatgpt.ts +12 -1
  483. package/src/oauth/cline.ts +203 -0
  484. package/src/oauth/command-code.ts +239 -0
  485. package/src/oauth/cursor.ts +46 -5
  486. package/src/oauth/generic-account-failover.ts +231 -0
  487. package/src/oauth/google-antigravity.ts +35 -3
  488. package/src/oauth/health.ts +20 -12
  489. package/src/oauth/index.ts +462 -71
  490. package/src/oauth/key-providers.ts +25 -0
  491. package/src/oauth/kimi.ts +25 -3
  492. package/src/oauth/kiro.ts +95 -6
  493. package/src/oauth/local-token-detect.ts +13 -2
  494. package/src/oauth/log.ts +3 -1
  495. package/src/oauth/login-cli.ts +88 -28
  496. package/src/oauth/nous.ts +798 -0
  497. package/src/oauth/open-browser-choice.ts +26 -0
  498. package/src/oauth/store.ts +133 -26
  499. package/src/oauth/token-guardian.ts +9 -3
  500. package/src/oauth/types.ts +15 -0
  501. package/src/pi/models.ts +2 -2
  502. package/src/providers/alibaba-region-migration.ts +1 -1
  503. package/src/providers/antigravity-models.ts +521 -31
  504. package/src/providers/auto-compact-budget.ts +65 -0
  505. package/src/providers/base-url-choices.ts +10 -0
  506. package/src/providers/codex-capacity.ts +292 -0
  507. package/src/providers/command-code-efforts.ts +176 -0
  508. package/src/providers/context-cap.ts +26 -8
  509. package/src/providers/cursor-pool.ts +72 -0
  510. package/src/providers/default-aliases.ts +65 -0
  511. package/src/providers/derive.ts +281 -6
  512. package/src/providers/fastwire.ts +507 -0
  513. package/src/providers/free-directory.ts +10 -7
  514. package/src/providers/google-vertex-location.ts +14 -0
  515. package/src/providers/key-failover.ts +71 -3
  516. package/src/providers/label.ts +35 -2
  517. package/src/providers/model-discovery-limits.ts +16 -0
  518. package/src/providers/model-discovery.ts +115 -22
  519. package/src/providers/model-presets.ts +119 -0
  520. package/src/providers/model-rename-migration.ts +255 -0
  521. package/src/providers/model-rename-startup.ts +28 -0
  522. package/src/providers/new-model-policy.ts +146 -0
  523. package/src/providers/openai-sidecar.ts +72 -4
  524. package/src/providers/openai-tier-startup.ts +31 -2
  525. package/src/providers/openai-tiers.ts +119 -4
  526. package/src/providers/openai-virtual-models.ts +1 -0
  527. package/src/providers/opencode-zen-rate-limit.ts +102 -0
  528. package/src/providers/provider-id-rewrite.ts +30 -0
  529. package/src/providers/quota.ts +1379 -40
  530. package/src/providers/registry.ts +1545 -112
  531. package/src/providers/request-pacing.ts +310 -0
  532. package/src/providers/service-tier.ts +277 -0
  533. package/src/providers/slug-codec.ts +94 -6
  534. package/src/providers/static-model-discovery.ts +86 -0
  535. package/src/providers/xai-responses-opt-in.ts +15 -0
  536. package/src/providers/xai-transport.ts +32 -4
  537. package/src/reasoning-effort.ts +68 -3
  538. package/src/responses/apply-patch-envelope.ts +63 -0
  539. package/src/responses/code-mode-helper-compat.ts +50 -0
  540. package/src/responses/compaction.ts +26 -1
  541. package/src/responses/custom-tool-compat.ts +384 -0
  542. package/src/responses/hosted-tool-policy.ts +9 -0
  543. package/src/responses/namespace-tool-compat.ts +435 -0
  544. package/src/responses/parser.ts +225 -38
  545. package/src/responses/provider-continuation.ts +98 -0
  546. package/src/responses/provider-opaque-metadata.ts +73 -0
  547. package/src/responses/reasoning-envelope.ts +9 -1
  548. package/src/responses/reasoning-replay-cache.ts +504 -0
  549. package/src/responses/schema.ts +12 -2
  550. package/src/responses/spill-store.ts +75 -10
  551. package/src/responses/state.ts +659 -31
  552. package/src/responses/thought-signature-replay.ts +364 -0
  553. package/src/responses/tool-search-compat.ts +301 -0
  554. package/src/responses/truncated-stop-reason.ts +60 -0
  555. package/src/responses/turn-termination.ts +107 -0
  556. package/src/router.ts +405 -31
  557. package/src/routing/analytics.ts +379 -0
  558. package/src/routing/capability.ts +244 -0
  559. package/src/routing/compatibility/assemble.ts +73 -0
  560. package/src/routing/compatibility/behavior.ts +278 -0
  561. package/src/routing/compatibility/catalog.ts +99 -0
  562. package/src/routing/compatibility/endpoint.ts +52 -0
  563. package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
  564. package/src/routing/compatibility/policy.ts +181 -0
  565. package/src/routing/compatibility/provider-slot.ts +56 -0
  566. package/src/routing/compatibility/reader.ts +110 -0
  567. package/src/routing/compatibility/subject.ts +191 -0
  568. package/src/routing/compatibility/types.ts +64 -0
  569. package/src/routing/compatibility/version.ts +104 -0
  570. package/src/routing/cost.ts +77 -0
  571. package/src/routing/evaluator.ts +495 -0
  572. package/src/routing/health.ts +412 -0
  573. package/src/routing/history/cursor.ts +43 -0
  574. package/src/routing/history/indexer.ts +605 -0
  575. package/src/routing/history/schema.ts +72 -0
  576. package/src/routing/profile-namespace.ts +15 -0
  577. package/src/routing/profile.ts +547 -0
  578. package/src/routing/quota.ts +155 -0
  579. package/src/routing/request-evidence.ts +45 -0
  580. package/src/routing/trace.ts +776 -0
  581. package/src/server/adapter-resolve.ts +2 -29
  582. package/src/server/auth-cors.ts +289 -48
  583. package/src/server/background-lifecycle.ts +182 -0
  584. package/src/server/chat-completions.ts +154 -72
  585. package/src/server/chat-native-sse.ts +331 -0
  586. package/src/server/chat-native.ts +445 -0
  587. package/src/server/claude-messages.ts +160 -44
  588. package/src/server/direct-local-http.ts +347 -0
  589. package/src/server/effort-policy.ts +18 -0
  590. package/src/server/github-copilot-responses-repair.ts +338 -0
  591. package/src/server/gui-static.ts +39 -10
  592. package/src/server/images.ts +94 -12
  593. package/src/server/index.ts +1021 -190
  594. package/src/server/lifecycle.ts +336 -14
  595. package/src/server/live.ts +136 -17
  596. package/src/server/local-management-read-client.ts +90 -0
  597. package/src/server/local-provider-reload-client.ts +137 -0
  598. package/src/server/management/agent-settings-routes.ts +433 -116
  599. package/src/server/management/api-key-usage.ts +31 -5
  600. package/src/server/management/body.ts +6 -0
  601. package/src/server/management/codex-prompt-routes.ts +570 -0
  602. package/src/server/management/combo-routes.ts +64 -25
  603. package/src/server/management/config-routes.ts +511 -51
  604. package/src/server/management/context.ts +89 -2
  605. package/src/server/management/integration-routes.ts +498 -0
  606. package/src/server/management/lab-automation-routes.ts +206 -0
  607. package/src/server/management/lab-routes.ts +563 -0
  608. package/src/server/management/logs-usage-routes.ts +127 -52
  609. package/src/server/management/model-routes.ts +461 -133
  610. package/src/server/management/model-rows.ts +167 -0
  611. package/src/server/management/native-integration-routes.ts +770 -0
  612. package/src/server/management/oauth-account-routes.ts +90 -8
  613. package/src/server/management/provider-capability-config.ts +48 -0
  614. package/src/server/management/provider-routes.ts +836 -159
  615. package/src/server/management/request-history-routes.ts +191 -0
  616. package/src/server/management/route-registry.ts +332 -0
  617. package/src/server/management/routing-analytics-routes.ts +74 -0
  618. package/src/server/management/routing-profile-routes.ts +380 -0
  619. package/src/server/management/shared.ts +27 -11
  620. package/src/server/management/sidebar-routes.ts +47 -31
  621. package/src/server/management/storage-log-guard-routes.ts +186 -0
  622. package/src/server/management/sync-response.ts +69 -0
  623. package/src/server/management/system-restart.ts +289 -33
  624. package/src/server/management/system-routes.ts +77 -0
  625. package/src/server/management/usage-summary-cache.ts +9 -1
  626. package/src/server/management/vision-sidecar-options.ts +167 -0
  627. package/src/server/management/web-search-sidecar-options.ts +120 -0
  628. package/src/server/management-api.ts +126 -23
  629. package/src/server/management-auth.ts +220 -5
  630. package/src/server/passive-route-linker.ts +66 -0
  631. package/src/server/port-reclaim.ts +1 -1
  632. package/src/server/ports.ts +41 -1
  633. package/src/server/proxy-liveness.ts +161 -10
  634. package/src/server/readiness.ts +99 -0
  635. package/src/server/relay-eager.ts +221 -54
  636. package/src/server/relay.ts +479 -87
  637. package/src/server/request-decompress.ts +113 -6
  638. package/src/server/request-log-conversation.ts +51 -0
  639. package/src/server/request-log.ts +298 -31
  640. package/src/server/responses/agent-task-recovery-cache.ts +143 -0
  641. package/src/server/responses/agent-task-recovery.ts +465 -0
  642. package/src/server/responses/codex-auth-error.ts +55 -0
  643. package/src/server/responses/collaboration.ts +239 -36
  644. package/src/server/responses/combo-stream-preflight.ts +178 -0
  645. package/src/server/responses/compact.ts +444 -72
  646. package/src/server/responses/core.ts +3579 -436
  647. package/src/server/responses/empty-completion-guard.ts +333 -0
  648. package/src/server/responses/encrypted-payload.ts +62 -39
  649. package/src/server/responses/fetch-helpers.ts +89 -99
  650. package/src/server/responses/input-admission.ts +187 -0
  651. package/src/server/responses/pacing-overload.ts +13 -0
  652. package/src/server/responses/passthrough-error.ts +33 -9
  653. package/src/server/responses/policy-fallback.ts +178 -0
  654. package/src/server/responses/responses-field-backfill.ts +343 -0
  655. package/src/server/responses/terminal-guard.ts +26 -5
  656. package/src/server/responses/upstream-error.ts +5 -0
  657. package/src/server/responses/ws-upstream.ts +381 -0
  658. package/src/server/responses-custom-tool-repair.ts +363 -0
  659. package/src/server/responses-item-id-repair.ts +54 -6
  660. package/src/server/responses-json-events.ts +90 -0
  661. package/src/server/responses-model-rewrite.ts +29 -0
  662. package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
  663. package/src/server/responses-snapshot-repair.ts +621 -0
  664. package/src/server/responses-terminal-repair.ts +363 -0
  665. package/src/server/responses-tool-search-repair.ts +267 -0
  666. package/src/server/responses-undeclared-tool-guard.ts +376 -0
  667. package/src/server/responses.ts +18 -2
  668. package/src/server/search.ts +78 -13
  669. package/src/server/sse-frame-buffer.ts +319 -0
  670. package/src/server/sse-payload-rewrite.ts +110 -22
  671. package/src/server/startup-action-control.ts +8 -1
  672. package/src/server/startup-health-cache.ts +19 -1
  673. package/src/server/system-env.ts +80 -9
  674. package/src/server/ws-bridge.ts +53 -40
  675. package/src/service-manager-probe.ts +892 -0
  676. package/src/service.ts +1153 -94
  677. package/src/sidecar/auth.ts +92 -0
  678. package/src/sidecar/candidates.ts +83 -0
  679. package/src/storage/cleanup.ts +2 -2
  680. package/src/storage/policy-job.ts +14 -4
  681. package/src/storage/policy.ts +88 -23
  682. package/src/storage/scanner.ts +1 -1
  683. package/src/storage/worker-lifecycle.ts +14 -14
  684. package/src/tray/windows-tray.ps1 +83 -9
  685. package/src/tray/windows.ts +43 -16
  686. package/src/types/accounts.ts +37 -0
  687. package/src/types/config.ts +911 -0
  688. package/src/types/provider.ts +614 -0
  689. package/src/types/request.ts +397 -0
  690. package/src/types/tools.ts +236 -0
  691. package/src/types/wire.ts +80 -0
  692. package/src/types.ts +107 -1237
  693. package/src/update/index.ts +37 -23
  694. package/src/update/job.ts +445 -68
  695. package/src/update/notify.ts +12 -6
  696. package/src/update/npm-cache-preflight.d.mts +47 -0
  697. package/src/update/npm-cache-preflight.mjs +201 -0
  698. package/src/update/transactional-install.d.mts +22 -0
  699. package/src/update/transactional-install.mjs +266 -0
  700. package/src/usage/cost.ts +0 -0
  701. package/src/usage/expected-prices.ts +287 -16
  702. package/src/usage/log.ts +666 -50
  703. package/src/usage/summary.ts +388 -17
  704. package/src/usage/user-cost-overlay-reconciler.ts +313 -0
  705. package/src/usage/user-cost-overlays.ts +314 -0
  706. package/src/vision/anthropic-describe.ts +10 -6
  707. package/src/vision/backends.ts +97 -0
  708. package/src/vision/describe.ts +27 -16
  709. package/src/vision/eligibility.ts +250 -0
  710. package/src/vision/index.ts +238 -24
  711. package/src/vision/reasoning.ts +55 -0
  712. package/src/vision/routed-describe.ts +175 -0
  713. package/src/vision/timeout-bounds.ts +9 -0
  714. package/src/web-search/anthropic-executor.ts +13 -7
  715. package/src/web-search/backends.ts +108 -0
  716. package/src/web-search/exa-executor.ts +119 -0
  717. package/src/web-search/executor.ts +21 -6
  718. package/src/web-search/gemini-executor.ts +141 -0
  719. package/src/web-search/index.ts +150 -15
  720. package/src/web-search/loop.ts +284 -54
  721. package/src/web-search/parse.ts +125 -30
  722. package/src/web-search/sources.ts +60 -0
  723. package/src/web-search/xai-executor.ts +219 -0
  724. package/gui/dist/assets/index-DTpMHS4F.js +0 -67
  725. package/gui/dist/assets/index-ZNVDE3C7.css +0 -1
  726. package/gui/dist/provider-icons/antigravity.svg +0 -1
  727. package/gui/dist/provider-icons/claude.svg +0 -1
  728. package/gui/dist/provider-icons/copilot.svg +0 -1
  729. package/gui/dist/provider-icons/cursor.svg +0 -2
  730. package/gui/dist/provider-icons/gemini.svg +0 -1
  731. package/gui/dist/provider-icons/grok-color.svg +0 -1
  732. package/gui/dist/provider-icons/kiro.svg +0 -14
  733. package/src/cli/internal-dispatch.ts +0 -20
@@ -1,4 +1,11 @@
1
1
  import { loadConfig } from "../config";
2
+ import { closeSync, openSync, readSync } from "node:fs";
3
+ import {
4
+ MAX_ACCOUNT_PRIORITY,
5
+ MIN_ACCOUNT_PRIORITY,
6
+ normalizeAccountPriority,
7
+ parseAccountPriority,
8
+ } from "../codex/pool-rotation";
2
9
  import {
3
10
  apiError,
4
11
  apiJson,
@@ -11,6 +18,20 @@ import {
11
18
  type AccountDeps, type AccountStdin, type FamilyRows,
12
19
  type ProviderQuotaDto, type ProviderQuotaReportDto,
13
20
  } from "./account-api";
21
+ import {
22
+ codexCatalogRefreshPending,
23
+ warnIfCodexCatalogRefreshPending,
24
+ } from "./account-catalog-refresh";
25
+ import {
26
+ ACCOUNT_IMPORT_DEADLINE_MS,
27
+ ACCOUNT_IMPORT_FORMAT,
28
+ ACCOUNT_IMPORT_MAX_BYTES,
29
+ ACCOUNT_IMPORT_PROVIDER,
30
+ type AccountImportCode,
31
+ type AccountImportRecordResult,
32
+ type AccountImportResult,
33
+ type AccountImportStatus,
34
+ } from "../oauth/account-import";
14
35
 
15
36
  const MAIN_ID = "__main__";
16
37
  const AUTO_NOTE = "auto (no pin — lowest-usage account is selected per request)";
@@ -18,12 +39,20 @@ const EXTENDED_USAGE = `Usage:
18
39
  ocx account refresh <provider> [--json]
19
40
  ocx account auto-switch <provider> <on|off|status|threshold <0-100>> [--json]
20
41
  ocx account alias <provider> <id|main> <display-name|-> [--json]
42
+ ocx account priority <provider> <id|main> [<-100..100|first|earlier|normal|later|last|reset>] [--json]
43
+ ocx account pause <provider> <id|main> [--json]
44
+ ocx account resume <provider> <id|main> [--json]
45
+ ocx account pause-exhausted <provider> [--json]
46
+ ocx account strategy <provider> [<quota|round-robin|fill-first>] [--json]
47
+ ocx account sticky <provider> [<1-100>] [--json]
21
48
  ocx account remove <provider> <id|main> --yes [--json]
22
49
  ocx account clear-cooldown <provider> <id|main> [--json]
23
- ocx account add-key <provider> [--label <label>] [--json]`;
50
+ ocx account add-key <provider> [--label <label>] [--json]
51
+ ocx account import <provider> --format <format> (--file <path>|--stdin) [--json]`;
24
52
  const PIPE_GUIDANCE = `Pipe the API key on stdin, for example:
25
53
  ocx account add-key <provider> <<< "$MY_KEY"
26
54
  security find-generic-password -w <item> | ocx account add-key <provider>`;
55
+ const ACCOUNT_IMPORT_TIMEOUT_MS = ACCOUNT_IMPORT_DEADLINE_MS;
27
56
 
28
57
  function flag(args: string[], value: string): boolean {
29
58
  const index = args.indexOf(value);
@@ -41,6 +70,162 @@ function flagValue(args: string[], value: string): { found: boolean; value?: str
41
70
  return { found: true, value: result };
42
71
  }
43
72
 
73
+ function boundedUtf8(bytes: Uint8Array): string {
74
+ try {
75
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
76
+ } catch {
77
+ throw new Error("invalid_document");
78
+ }
79
+ }
80
+
81
+ function readBoundedImportFile(path: string): string {
82
+ let descriptor: number | undefined;
83
+ try {
84
+ descriptor = openSync(path, "r");
85
+ const buffer = Buffer.allocUnsafe(ACCOUNT_IMPORT_MAX_BYTES + 1);
86
+ let offset = 0;
87
+ while (offset < buffer.length) {
88
+ const read = readSync(descriptor, buffer, offset, buffer.length - offset, null);
89
+ if (read === 0) break;
90
+ offset += read;
91
+ }
92
+ if (offset > ACCOUNT_IMPORT_MAX_BYTES) throw new Error("invalid_document");
93
+ return boundedUtf8(buffer.subarray(0, offset));
94
+ } catch (error) {
95
+ if (error instanceof Error && error.message === "invalid_document") throw error;
96
+ throw new Error("source_read_failed");
97
+ } finally {
98
+ if (descriptor !== undefined) closeSync(descriptor);
99
+ }
100
+ }
101
+
102
+ async function readBoundedImportStdin(deps: AccountDeps): Promise<string> {
103
+ const input: AccountStdin = deps.stdinImpl ?? process.stdin;
104
+ if (input.isTTY) throw new Error("stdin_required");
105
+ const timeoutMs = deps.stdinTimeoutMs ?? 15_000;
106
+ return await new Promise<string>((resolve, reject) => {
107
+ const chunks: Buffer[] = [];
108
+ let bytes = 0;
109
+ let settled = false;
110
+ const cleanup = () => {
111
+ clearTimeout(timer);
112
+ input.removeListener("data", onData);
113
+ input.removeListener("end", onEnd);
114
+ input.removeListener("error", onError);
115
+ };
116
+ const finish = (fn: () => void) => {
117
+ if (settled) return;
118
+ settled = true;
119
+ cleanup();
120
+ fn();
121
+ };
122
+ const onData = (chunk: unknown) => {
123
+ const encoded = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
124
+ bytes += encoded.byteLength;
125
+ if (bytes > ACCOUNT_IMPORT_MAX_BYTES) {
126
+ finish(() => reject(new Error("invalid_document")));
127
+ return;
128
+ }
129
+ chunks.push(encoded);
130
+ };
131
+ const onEnd = () => finish(() => {
132
+ try { resolve(boundedUtf8(Buffer.concat(chunks, bytes))); }
133
+ catch (error) { reject(error); }
134
+ });
135
+ const onError = () => finish(() => reject(new Error("source_read_failed")));
136
+ const timer = setTimeout(() => finish(() => reject(new Error("stdin_timeout"))), timeoutMs);
137
+ input.on("data", onData);
138
+ input.on("end", onEnd);
139
+ input.on("error", onError);
140
+ });
141
+ }
142
+
143
+ const IMPORT_STATUSES = new Set<AccountImportStatus>(["imported", "updated", "failed", "unsupported"]);
144
+ const IMPORT_CODES = new Set<AccountImportCode>([
145
+ "imported", "updated", "import_cancelled", "unsupported_provider", "unsupported_format", "invalid_document",
146
+ "invalid_record", "credential_rejected", "identity_mismatch", "missing_project", "persist_failed",
147
+ ]);
148
+ const IMPORT_RESULT_FIELDS = new Set([
149
+ "totalCount", "importedCount", "updatedCount", "failedCount", "unsupportedCount", "results",
150
+ ]);
151
+ const IMPORT_RECORD_FIELDS = new Set(["index", "status", "code"]);
152
+ const IMPORT_STATUS_CODES: Record<AccountImportStatus, ReadonlySet<AccountImportCode>> = {
153
+ imported: new Set(["imported"]),
154
+ updated: new Set(["updated"]),
155
+ failed: new Set(["invalid_record", "credential_rejected", "identity_mismatch", "missing_project", "persist_failed"]),
156
+ unsupported: new Set(["unsupported_provider", "unsupported_format"]),
157
+ };
158
+
159
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
160
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
161
+ const prototype = Object.getPrototypeOf(value);
162
+ return prototype === Object.prototype || prototype === null;
163
+ }
164
+
165
+ function hasExactFields(value: Record<string, unknown>, allowed: ReadonlySet<string>): boolean {
166
+ const fields = Object.keys(value);
167
+ return fields.length === allowed.size && fields.every(field => allowed.has(field));
168
+ }
169
+
170
+ function isNonNegativeSafeInteger(value: unknown): value is number {
171
+ return typeof value === "number" && Number.isFinite(value) && Number.isSafeInteger(value) && value >= 0;
172
+ }
173
+
174
+ function safeImportResult(json: unknown): AccountImportResult | null {
175
+ if (!isPlainObject(json) || !hasExactFields(json, IMPORT_RESULT_FIELDS)) return null;
176
+ if (
177
+ !isNonNegativeSafeInteger(json.totalCount)
178
+ || !isNonNegativeSafeInteger(json.importedCount)
179
+ || !isNonNegativeSafeInteger(json.updatedCount)
180
+ || !isNonNegativeSafeInteger(json.failedCount)
181
+ || !isNonNegativeSafeInteger(json.unsupportedCount)
182
+ ) return null;
183
+ if (!Array.isArray(json.results)) return null;
184
+ if (json.results.length !== json.totalCount) return null;
185
+
186
+ const results: AccountImportRecordResult[] = [];
187
+ const indices = new Set<number>();
188
+ const aggregate: Record<AccountImportStatus, number> = {
189
+ imported: 0,
190
+ updated: 0,
191
+ failed: 0,
192
+ unsupported: 0,
193
+ };
194
+ for (const raw of json.results) {
195
+ if (!isPlainObject(raw) || !hasExactFields(raw, IMPORT_RECORD_FIELDS)) return null;
196
+ const item = raw;
197
+ if (!isNonNegativeSafeInteger(item.index) || item.index >= json.totalCount || indices.has(item.index)) return null;
198
+ if (!IMPORT_STATUSES.has(item.status as AccountImportStatus)) return null;
199
+ if (!IMPORT_CODES.has(item.code as AccountImportCode)) return null;
200
+ const status = item.status as AccountImportStatus;
201
+ const code = item.code as AccountImportCode;
202
+ if (!IMPORT_STATUS_CODES[status].has(code)) return null;
203
+ indices.add(item.index);
204
+ aggregate[status] += 1;
205
+ results.push({
206
+ index: item.index,
207
+ status,
208
+ code,
209
+ });
210
+ }
211
+ if (
212
+ indices.size !== json.totalCount
213
+ || aggregate.imported !== json.importedCount
214
+ || aggregate.updated !== json.updatedCount
215
+ || aggregate.failed !== json.failedCount
216
+ || aggregate.unsupported !== json.unsupportedCount
217
+ ) return null;
218
+
219
+ return {
220
+ totalCount: json.totalCount,
221
+ importedCount: json.importedCount,
222
+ updatedCount: json.updatedCount,
223
+ failedCount: json.failedCount,
224
+ unsupportedCount: json.unsupportedCount,
225
+ results,
226
+ };
227
+ }
228
+
44
229
  function usage(message?: string): number {
45
230
  if (message) console.error(message);
46
231
  console.error(EXTENDED_USAGE);
@@ -52,8 +237,8 @@ function configAndType(deps: AccountDeps, name: string) {
52
237
  }
53
238
 
54
239
  function familyFailure(result: FamilyRows, fallback: string): number | null {
55
- if (result.networkDown) return proxyUnreachable();
56
- if (result.errorJson) return apiError(result.errorJson, fallback);
240
+ if (result.networkDown) return proxyUnreachable(result.transportError);
241
+ if (result.errorJson) return apiError(result.errorJson, fallback, result.status);
57
242
  return null;
58
243
  }
59
244
 
@@ -69,17 +254,14 @@ function resetIso(value: number | undefined): string | null {
69
254
 
70
255
  function refreshLine(row: FamilyRows["rows"][number]): string {
71
256
  const parts = [row.id === MAIN_ID ? "main" : row.id, row.email, row.plan];
72
- const quota = row.quota;
73
- if (!quota || (quota.weeklyPercent === undefined && quota.monthlyPercent === undefined)) {
74
- parts.push("quota: unknown");
75
- } else {
76
- if (quota.weeklyPercent !== undefined) parts.push(`weekly ${quota.weeklyPercent}%`);
77
- const weeklyReset = resetIso(quota.weeklyResetAt);
78
- if (weeklyReset) parts.push(`resets ${weeklyReset}`);
79
- if (quota.monthlyPercent !== undefined) parts.push(`monthly ${quota.monthlyPercent}%`);
80
- const monthlyReset = resetIso(quota.monthlyResetAt);
81
- if (monthlyReset) parts.push(`resets ${monthlyReset}`);
82
- }
257
+ if (row.paused) parts.push("paused");
258
+ // Was a second quota dialect: it gated the whole block on weekly/monthly, so an account
259
+ // reporting only a 5h window printed `quota: unknown` while `quotaParts` five lines below
260
+ // rendered the same data correctly for the provider path (#2703). Two halves of one file
261
+ // disagreeing about how to read one DTO is the defect; delegating removes it rather than
262
+ // teaching the second dialect a third window.
263
+ const quotaText = row.quota ? quotaParts(row.quota).join(" ") : "";
264
+ parts.push(quotaText.length > 0 ? quotaText : "quota: unknown");
83
265
  if (row.needsReauth) parts.push("needs-reauth");
84
266
  return parts.filter(Boolean).join(" ");
85
267
  }
@@ -99,7 +281,7 @@ function quotaParts(quota: ProviderQuotaDto): string[] {
99
281
  return parts;
100
282
  }
101
283
 
102
- function providerQuotaLine(name: string, report: ProviderQuotaReportDto): string {
284
+ export function providerQuotaLine(name: string, report: ProviderQuotaReportDto): string {
103
285
  return [name, ...quotaParts(report.quota)].join(" ");
104
286
  }
105
287
 
@@ -145,8 +327,8 @@ export async function cmdRefresh(args: string[], deps: AccountDeps): Promise<num
145
327
  if (!baseUrl) return proxyUnreachable();
146
328
  if (classified.type !== "codex") {
147
329
  const result = await fetchProviderQuotaReport(deps, baseUrl, name);
148
- if (result.status === 0) return proxyUnreachable();
149
- if (result.status !== 200) return apiError(result.errorJson ?? {}, `failed to refresh ${name}`);
330
+ if (result.status === 0) return proxyUnreachable(result.transportError);
331
+ if (result.status !== 200) return apiError(result.errorJson ?? {}, `failed to refresh ${name}`, result.status);
150
332
  if (wantsJson) console.log(JSON.stringify({ provider: name, report: result.report }, null, 2));
151
333
  else console.log(result.report ? providerQuotaLine(name, result.report) : `no quota report available for ${name}`);
152
334
  return 0;
@@ -180,15 +362,15 @@ export async function cmdAutoSwitch(args: string[], deps: AccountDeps): Promise<
180
362
  if (!baseUrl) return proxyUnreachable();
181
363
  if (action === "status") {
182
364
  const response = await apiJson(deps, baseUrl, "GET", "/api/codex-auth/active");
183
- if (response.status === 0) return proxyUnreachable();
365
+ if (response.status === 0) return proxyUnreachable(response.transportError);
184
366
  if (response.status !== 200 || typeof response.json.autoSwitchThreshold !== "number") {
185
- return apiError(response.json, "failed to read auto-switch status");
367
+ return apiError(response.json, "failed to read auto-switch status", response.status);
186
368
  }
187
369
  threshold = response.json.autoSwitchThreshold;
188
370
  } else {
189
371
  const response = await apiJson(deps, baseUrl, "PUT", "/api/codex-auth/auto-switch", { threshold });
190
- if (response.status === 0) return proxyUnreachable();
191
- if (response.status !== 200) return apiError(response.json, "failed to update auto-switch");
372
+ if (response.status === 0) return proxyUnreachable(response.transportError);
373
+ if (response.status !== 200) return apiError(response.json, "failed to update auto-switch", response.status);
192
374
  }
193
375
  const enabled = threshold! > 0;
194
376
  if (wantsJson) console.log(JSON.stringify({ provider: name, autoSwitchThreshold: threshold, enabled }, null, 2));
@@ -234,18 +416,28 @@ export async function cmdRemove(args: string[], deps: AccountDeps): Promise<numb
234
416
  const response = await apiJson(deps, baseUrl, "DELETE", deletePath(classified.type, name, id));
235
417
  if (response.status === 0) return fail("Proxy not reachable. Start it with 'ocx start' or 'ocx ensure'.");
236
418
  if (response.status !== 200) return fail(errorText(response.json, `failed to remove ${requestedId}`));
419
+ const catalogRefreshPending = classified.type === "codex"
420
+ && codexCatalogRefreshPending(response.json);
237
421
  const after = await fetchRows(deps, baseUrl, name, classified.type);
238
422
  if (after.networkDown || after.errorJson) {
239
423
  const detail = after.networkDown ? "proxy not reachable" : typeof after.errorJson?.error === "string" ? after.errorJson.error : "unknown error";
240
424
  return fail(`post-delete verification failed; delete may have succeeded: ${detail}`);
241
425
  }
242
426
  const removedActive = before.activeId === id;
243
- const result = { ok: true, provider: name, id, removedActive, promotedActiveId: after.activeId };
427
+ const result = {
428
+ ok: true,
429
+ provider: name,
430
+ id,
431
+ removedActive,
432
+ promotedActiveId: after.activeId,
433
+ ...(classified.type === "codex" ? { catalogRefreshPending } : {}),
434
+ };
244
435
  if (wantsJson) console.log(JSON.stringify(result, null, 2));
245
436
  else if (classified.type === "codex" && removedActive && after.activeId === null) console.log(`openai: ${AUTO_NOTE}`);
246
437
  else if (classified.type === "oauth") console.log(after.rows.length ? `${name}: active account is now ${after.activeId}` : `${name}: no accounts remaining`);
247
438
  else if (classified.type === "api-key") console.log(after.rows.length ? `${name}: active key is now ${after.activeId}` : `${name}: no keys remaining`);
248
439
  else console.log(`${name}: removed account ${requestedId}`);
440
+ if (!wantsJson && catalogRefreshPending) warnIfCodexCatalogRefreshPending(response.json);
249
441
  return 0;
250
442
  }
251
443
 
@@ -269,8 +461,8 @@ export async function cmdAddKey(args: string[], deps: AccountDeps): Promise<numb
269
461
  const baseUrl = await resolveBaseUrl(deps);
270
462
  if (!baseUrl) return proxyUnreachable();
271
463
  const response = await apiJson(deps, baseUrl, "POST", "/api/providers/keys", { name, key, ...(label ? { label } : {}) });
272
- if (response.status === 0) return proxyUnreachable();
273
- if (response.status !== 201) return apiError(response.json, `failed to add a key for ${name}`);
464
+ if (response.status === 0) return proxyUnreachable(response.transportError);
465
+ if (response.status !== 201) return apiError(response.json, `failed to add a key for ${name}`, response.status);
274
466
  const id = typeof response.json.id === "string" ? response.json.id : null;
275
467
  // Redact the key inside the label BEFORE serialization — a key containing
276
468
  // JSON-escaped characters (" or \) would otherwise survive the whole-output
@@ -283,6 +475,98 @@ export async function cmdAddKey(args: string[], deps: AccountDeps): Promise<numb
283
475
  return 0;
284
476
  }
285
477
 
478
+ export async function cmdImport(args: string[], deps: AccountDeps): Promise<number> {
479
+ const wantsJson = flag(args, "--json");
480
+ const formatArg = flagValue(args, "--format");
481
+ const fileArg = flagValue(args, "--file");
482
+ const fromStdin = flag(args, "--stdin");
483
+ const provider = args.shift();
484
+
485
+ // Admission happens before source I/O. In particular, an inline JSON positional argument is
486
+ // rejected without inspecting or echoing it, keeping secrets out of any subsequent surface.
487
+ if (
488
+ provider !== ACCOUNT_IMPORT_PROVIDER
489
+ || formatArg.value !== ACCOUNT_IMPORT_FORMAT
490
+ || formatArg.found !== true
491
+ ) {
492
+ const code = provider === ACCOUNT_IMPORT_PROVIDER ? "unsupported_format" : "unsupported_provider";
493
+ console.error(`Error: ${code}`);
494
+ return 1;
495
+ }
496
+ const hasFile = fileArg.found && typeof fileArg.value === "string" && fileArg.value.length > 0;
497
+ if (args.length > 0 || hasFile === fromStdin || (fileArg.found && !hasFile)) {
498
+ return usage("Error: choose exactly one bounded source: --file <path> or --stdin");
499
+ }
500
+
501
+ let source: string;
502
+ try {
503
+ source = hasFile ? readBoundedImportFile(fileArg.value!) : await readBoundedImportStdin(deps);
504
+ } catch (error) {
505
+ const code = error instanceof Error ? error.message : "source_read_failed";
506
+ console.error(`Error: ${["invalid_document", "source_read_failed", "stdin_required", "stdin_timeout"].includes(code) ? code : "source_read_failed"}`);
507
+ return 1;
508
+ }
509
+
510
+ let document: unknown;
511
+ try {
512
+ document = JSON.parse(source) as unknown;
513
+ } catch {
514
+ console.error("Error: invalid_document");
515
+ return 1;
516
+ }
517
+
518
+ const baseUrl = await resolveBaseUrl(deps);
519
+ if (!baseUrl) return proxyUnreachable();
520
+ const configuredImportTimeout = deps.importTimeoutMs;
521
+ const importTimeoutMs = typeof configuredImportTimeout === "number" && Number.isFinite(configuredImportTimeout)
522
+ ? Math.min(ACCOUNT_IMPORT_TIMEOUT_MS, Math.max(1, Math.floor(configuredImportTimeout)))
523
+ : ACCOUNT_IMPORT_TIMEOUT_MS;
524
+ const controller = new AbortController();
525
+ let timedOut = false;
526
+ const timer = setTimeout(() => {
527
+ timedOut = true;
528
+ controller.abort();
529
+ }, importTimeoutMs);
530
+ let response;
531
+ try {
532
+ response = await apiJson(deps, baseUrl, "POST", "/api/oauth/accounts/import", {
533
+ provider,
534
+ format: formatArg.value,
535
+ document,
536
+ }, { signal: controller.signal });
537
+ } finally {
538
+ clearTimeout(timer);
539
+ }
540
+ if (response.status === 0) {
541
+ if (!timedOut) return proxyUnreachable(response.transportError);
542
+ console.error(`Error: import_timeout after ${importTimeoutMs}ms`);
543
+ return 1;
544
+ }
545
+ if (response.status !== 200) {
546
+ if (IMPORT_CODES.has(response.json.code as AccountImportCode)) {
547
+ console.error(`Error: ${response.json.code as AccountImportCode}`);
548
+ } else {
549
+ console.error(`Error: import_request_failed (status ${response.status})`);
550
+ }
551
+ return 1;
552
+ }
553
+ const result = safeImportResult(response.json);
554
+ if (!result) {
555
+ console.error("Error: invalid_response");
556
+ return 1;
557
+ }
558
+
559
+ if (wantsJson) {
560
+ console.log(JSON.stringify(result, null, 2));
561
+ } else {
562
+ console.log(
563
+ `${provider}: ${result.importedCount} imported, ${result.updatedCount} updated, ${result.failedCount} failed, ${result.unsupportedCount} unsupported`,
564
+ );
565
+ for (const item of result.results) console.log(` #${item.index + 1} ${item.status} (${item.code})`);
566
+ }
567
+ return result.failedCount > 0 || result.unsupportedCount > 0 ? 1 : 0;
568
+ }
569
+
286
570
  /**
287
571
  * Lift a quota cooldown on a Codex account.
288
572
  *
@@ -307,8 +591,8 @@ export async function cmdClearCooldown(args: string[], deps: AccountDeps): Promi
307
591
  const baseUrl = await resolveBaseUrl(deps);
308
592
  if (!baseUrl) return proxyUnreachable();
309
593
  const response = await apiJson(deps, baseUrl, "POST", "/api/codex-auth/accounts/clear-cooldown", { id });
310
- if (response.status === 0) return proxyUnreachable();
311
- if (response.status !== 200) return apiError(response.json, `failed to clear cooldown for ${requestedId}`);
594
+ if (response.status === 0) return proxyUnreachable(response.transportError);
595
+ if (response.status !== 200) return apiError(response.json, `failed to clear cooldown for ${requestedId}`, response.status);
312
596
  const cleared = response.json?.cleared === true;
313
597
  if (wantsJson) console.log(JSON.stringify({ ok: true, provider: name, id, cleared }, null, 2));
314
598
  else if (cleared) console.log(`${name}: cooldown lifted for ${requestedId}`);
@@ -316,6 +600,342 @@ export async function cmdClearCooldown(args: string[], deps: AccountDeps): Promi
316
600
  return 0;
317
601
  }
318
602
 
603
+ /**
604
+ * Named selection orders. The words convey sequence rather than rank because the
605
+ * pool moves down the list only when everything above it is drained — "high
606
+ * priority" would suggest the account gets more traffic, which is not what
607
+ * ordering does.
608
+ */
609
+ const PRIORITY_PRESETS: Record<string, number> = {
610
+ first: 2,
611
+ earlier: 1,
612
+ normal: 0,
613
+ later: -1,
614
+ last: -2,
615
+ };
616
+
617
+ function priorityPresetName(priority: number): string | null {
618
+ return Object.entries(PRIORITY_PRESETS).find(([, value]) => value === priority)?.[0] ?? null;
619
+ }
620
+
621
+ function formatPriority(priority: number): string {
622
+ const preset = priorityPresetName(priority);
623
+ const signed = priority > 0 ? `+${priority}` : String(priority);
624
+ return preset ? `${signed} (${preset})` : signed;
625
+ }
626
+
627
+ /** `null` = reset to the default; `undefined` = unparseable. */
628
+ function parsePriorityArgument(raw: string): number | null | undefined {
629
+ const word = raw.trim().toLowerCase();
630
+ if (word === "reset") return null;
631
+ // Own keys only: `in` also matches "constructor", "__proto__", and friends.
632
+ if (Object.hasOwn(PRIORITY_PRESETS, word)) return PRIORITY_PRESETS[word];
633
+ // The regex only rules out shapes Number() would coerce ("1e2", " 1 ", ""); the range
634
+ // itself comes from the core parser so the CLI cannot drift from what the API accepts.
635
+ if (!/^[+-]?\d+$/.test(word)) return undefined;
636
+ return parseAccountPriority(Number(word)) ?? undefined;
637
+ }
638
+
639
+ export async function cmdPriority(args: string[], deps: AccountDeps): Promise<number> {
640
+ const wantsJson = flag(args, "--json");
641
+ const name = args.shift();
642
+ const requestedId = args.shift();
643
+ const requestedPriority = args.shift();
644
+ if (!name || !requestedId || args.length) return usage();
645
+ const classified = configAndType(deps, name);
646
+ if ("error" in classified) return usage(`Error: ${classified.error}`);
647
+ if (classified.type !== "codex") {
648
+ return usage("Error: selection order only applies to the openai Codex account pool");
649
+ }
650
+ const id = requestedId === "main" ? MAIN_ID : requestedId;
651
+
652
+ // Validate before touching the network so a typo never reaches the proxy.
653
+ let priority: number | null | undefined;
654
+ if (requestedPriority !== undefined) {
655
+ priority = parsePriorityArgument(requestedPriority);
656
+ if (priority === undefined) {
657
+ return usage(`Error: selection order must be an integer ${MIN_ACCOUNT_PRIORITY}..${MAX_ACCOUNT_PRIORITY}, one of ${Object.keys(PRIORITY_PRESETS).join("/")}, or reset`);
658
+ }
659
+ }
660
+
661
+ const baseUrl = await resolveBaseUrl(deps);
662
+ if (!baseUrl) return proxyUnreachable();
663
+
664
+ // No value means "show" — a read must not rewrite what it is reporting.
665
+ if (priority === undefined) {
666
+ const result = await fetchCodexRows(deps, baseUrl);
667
+ const failed = familyFailure(result, `failed to read ${name} accounts`);
668
+ if (failed !== null) return failed;
669
+ const row = result.rows.find(candidate => candidate.id === id);
670
+ if (!row) return usage(`Error: no ${name} account ${requestedId}`);
671
+ const current = normalizeAccountPriority(row.priority);
672
+ if (wantsJson) {
673
+ console.log(JSON.stringify(
674
+ { ok: true, provider: name, id, priority: current, preset: priorityPresetName(current) },
675
+ null,
676
+ 2,
677
+ ));
678
+ } else {
679
+ console.log(`${name}: ${requestedId} selection order is ${formatPriority(current)}`);
680
+ }
681
+ return 0;
682
+ }
683
+
684
+ const response = await apiJson(deps, baseUrl, "PUT", "/api/codex-auth/accounts/priority", { id, priority });
685
+ if (response.status === 0) return proxyUnreachable(response.transportError);
686
+ if (response.status !== 200) return apiError(response.json, `failed to set selection order for ${requestedId}`, response.status);
687
+ const applied = typeof response.json.priority === "number" ? response.json.priority : (priority ?? 0);
688
+ if (wantsJson) {
689
+ console.log(JSON.stringify(
690
+ { ok: true, provider: name, id, priority: applied, preset: priorityPresetName(applied) },
691
+ null,
692
+ 2,
693
+ ));
694
+ } else {
695
+ console.log(`${name}: ${requestedId} selection order is now ${formatPriority(applied)}`);
696
+ }
697
+ // Not cmdUse's note: re-ordering takes effect on the next unbound request rather than
698
+ // only on new sessions, because preemption moves those requests up immediately.
699
+ console.error("Takes effect from the next unbound request; running threads keep their current account until drained.");
700
+ // The release is not optional and not conditional on the value changing, so it has to be
701
+ // stated: this route is the only way to clear a pin without immediately setting another,
702
+ // which means a write storing the order an account already had still releases it. Without
703
+ // this line that is a silent side effect of a command that looks purely declarative.
704
+ console.error('Also releases any manual "use this account now" pin, on any account.');
705
+ return 0;
706
+ }
707
+
708
+ /**
709
+ * `ocx account pause|resume <provider> <id>` (#2702).
710
+ *
711
+ * The server routes have always existed; only the CLI caller was missing, so pausing an
712
+ * account was dashboard-only. The issue reports these as POST; the code is PUT
713
+ * (`auth-api.ts:1494`), and the route is shared by both directions with a `paused` boolean
714
+ * rather than being two endpoints.
715
+ */
716
+ export async function cmdPause(args: string[], deps: AccountDeps, paused: boolean): Promise<number> {
717
+ const wantsJson = flag(args, "--json");
718
+ const name = args.shift();
719
+ const requestedId = args.shift();
720
+ const verb = paused ? "pause" : "resume";
721
+ if (!name || !requestedId || args.length) return usage();
722
+ const classified = configAndType(deps, name);
723
+ if ("error" in classified) return usage(`Error: ${classified.error}`);
724
+ if (classified.type !== "codex") {
725
+ return usage(`Error: ${verb} applies to the openai Codex account pool`);
726
+ }
727
+ const id = requestedId === "main" ? MAIN_ID : requestedId;
728
+
729
+ const baseUrl = await resolveBaseUrl(deps);
730
+ if (!baseUrl) return proxyUnreachable();
731
+
732
+ const response = await apiJson(deps, baseUrl, "PUT", "/api/codex-auth/accounts/pause", { id, paused });
733
+ // Transport sentinel first: status 0 means the proxy never answered, and comparing it to
734
+ // 200 would report an unreachable proxy as a management error.
735
+ if (response.status === 0) return proxyUnreachable(response.transportError);
736
+ if (response.status !== 200) return apiError(response.json, `failed to ${verb} ${requestedId}`, response.status);
737
+
738
+ if (wantsJson) {
739
+ console.log(JSON.stringify({ ok: true, provider: name, id, paused }, null, 2));
740
+ } else {
741
+ console.log(`${name}: ${requestedId} ${paused ? "paused" : "resumed"}`);
742
+ }
743
+ if (paused) {
744
+ // Both are server-side effects of this route (auth-api.ts:1508-1510), not consequences
745
+ // the operator would infer from the word "pause".
746
+ console.error("Threads bound to this account are unbound, and a fallback account is selected if this one was active.");
747
+ }
748
+ return 0;
749
+ }
750
+
751
+ /**
752
+ * `ocx account pause-exhausted [--off]` (#2702).
753
+ *
754
+ * Pauses every account whose quota is spent. The route refreshes quota for each account, so
755
+ * it can partially fail; the response distinguishes "checked none and some failed" from
756
+ * "checked some", and that distinction is reported rather than flattened into ok/not-ok.
757
+ */
758
+ export async function cmdPauseExhausted(args: string[], deps: AccountDeps): Promise<number> {
759
+ const wantsJson = flag(args, "--json");
760
+ const name = args.shift();
761
+ if (!name || args.length) return usage();
762
+ const classified = configAndType(deps, name);
763
+ if ("error" in classified) return usage(`Error: ${classified.error}`);
764
+ if (classified.type !== "codex") {
765
+ return usage("Error: pause-exhausted applies to the openai Codex account pool");
766
+ }
767
+
768
+ const baseUrl = await resolveBaseUrl(deps);
769
+ if (!baseUrl) return proxyUnreachable();
770
+
771
+ const response = await apiJson(deps, baseUrl, "PUT", "/api/codex-auth/accounts/pause-exhausted", {});
772
+ if (response.status === 0) return proxyUnreachable(response.transportError);
773
+ if (response.status !== 200) return apiError(response.json, "failed to pause exhausted accounts", response.status);
774
+
775
+ const pausedIds = Array.isArray(response.json.pausedAccountIds)
776
+ ? (response.json.pausedAccountIds as unknown[]).filter((value): value is string => typeof value === "string")
777
+ : [];
778
+ const checked = typeof response.json.checkedAccountCount === "number" ? response.json.checkedAccountCount : null;
779
+ const failed = typeof response.json.failedAccountCount === "number" ? response.json.failedAccountCount : null;
780
+
781
+ const complete = failed === null || failed === 0;
782
+ const ok = complete;
783
+ if (failed !== null && failed > 0) {
784
+ console.error(`Quota refresh failed for ${failed} account(s); those were not evaluated.`);
785
+ }
786
+ if (wantsJson) {
787
+ console.log(JSON.stringify({
788
+ ok,
789
+ complete,
790
+ provider: name,
791
+ pausedAccountIds: pausedIds,
792
+ checkedAccountCount: checked,
793
+ failedAccountCount: failed,
794
+ }, null, 2));
795
+ return ok ? 0 : 1;
796
+ }
797
+ console.log(pausedIds.length > 0
798
+ ? `${name}: paused ${pausedIds.length} exhausted account(s): ${pausedIds.join(", ")}`
799
+ : `${name}: no exhausted accounts to pause`);
800
+ return ok ? 0 : 1;
801
+ }
802
+
803
+ /**
804
+ * Two pools expose strategy and sticky, and they are NOT reached the same way:
805
+ *
806
+ * | | Codex pool | Anthropic pool |
807
+ * |---|---|---|
808
+ * | read | `GET /api/codex-auth/active` | `GET /api/oauth/accounts/pool?provider=` |
809
+ * | write | `PUT /api/codex-auth/pool-strategy` | `PUT /api/oauth/accounts/pool` |
810
+ * | keys | `accountPoolStrategy`/`accountPoolStickyLimit` | `strategy`/`stickyLimit` |
811
+ * | body | bare field | field **plus** a mandatory `provider` |
812
+ *
813
+ * Omitting `provider` from the Anthropic write body earns a 400
814
+ * (`oauth-account-routes.ts:344`), so the asymmetry has to be encoded somewhere. Encoding it
815
+ * here keeps ONE verb pair working on both pools. The alternative the plan left open -- a second
816
+ * `provider-strategy`/`provider-sticky` pair -- would double the surface an operator must learn
817
+ * to express one idea, and a CLI that can steer one pool and not the other is exactly the trap
818
+ * this unit exists to remove.
819
+ */
820
+ interface PoolTransport {
821
+ readPath: string;
822
+ writePath: string;
823
+ /** Response key carrying the applied strategy. */
824
+ strategyKey: string;
825
+ /** Response key carrying the applied sticky limit. */
826
+ stickyKey: string;
827
+ writeBody: (field: "strategy" | "stickyLimit", value: unknown) => Record<string, unknown>;
828
+ }
829
+
830
+ const CODEX_POOL_TRANSPORT: PoolTransport = {
831
+ readPath: "/api/codex-auth/active",
832
+ writePath: "/api/codex-auth/pool-strategy",
833
+ strategyKey: "accountPoolStrategy",
834
+ stickyKey: "accountPoolStickyLimit",
835
+ writeBody: (field, value) => ({ [field]: value }),
836
+ };
837
+
838
+ function anthropicPoolTransport(provider: string): PoolTransport {
839
+ return {
840
+ readPath: `/api/oauth/accounts/pool?provider=${encodeURIComponent(provider)}`,
841
+ writePath: "/api/oauth/accounts/pool",
842
+ strategyKey: "strategy",
843
+ stickyKey: "stickyLimit",
844
+ writeBody: (field, value) => ({ provider, [field]: value }),
845
+ };
846
+ }
847
+
848
+ /**
849
+ * The pool-config route supports `anthropic` only and says so with a 400. Any other OAuth
850
+ * provider is refused here with the same wording rather than spending a round-trip to learn it.
851
+ */
852
+ function poolTransportFor(
853
+ classified: { type: "codex" | "oauth" | "api-key" },
854
+ name: string,
855
+ ): PoolTransport | string {
856
+ if (classified.type === "codex") return CODEX_POOL_TRANSPORT;
857
+ if (classified.type === "oauth" && name === "anthropic") return anthropicPoolTransport(name);
858
+ return `pool settings apply to the openai Codex pool and the anthropic pool, not "${name}"`;
859
+ }
860
+
861
+ /**
862
+ * `ocx account strategy <provider> [<name>]` and `ocx account sticky <provider> [<n>]` (#2702).
863
+ *
864
+ * One implementation rather than two near-duplicates, because strategy and sticky are two
865
+ * fields of one setting on every pool that has them.
866
+ *
867
+ * Values are NOT re-validated here. The server owns both contracts -- three strategy names,
868
+ * a 1-100 sticky bound -- and a duplicated bound is a second thing to keep in sync. Its 400
869
+ * is actionable now that the CLI prints `reason`.
870
+ */
871
+ async function poolSetting(
872
+ args: string[],
873
+ deps: AccountDeps,
874
+ field: "strategy" | "stickyLimit",
875
+ ): Promise<number> {
876
+ const wantsJson = flag(args, "--json");
877
+ const name = args.shift();
878
+ const requested = args.shift();
879
+ const label = field === "strategy" ? "pool strategy" : "sticky limit";
880
+ if (!name || args.length) return usage();
881
+ const classified = configAndType(deps, name);
882
+ if ("error" in classified) return usage(`Error: ${classified.error}`);
883
+ const transport = poolTransportFor(classified, name);
884
+ if (typeof transport === "string") return usage(`Error: ${transport}`);
885
+
886
+ const baseUrl = await resolveBaseUrl(deps);
887
+ if (!baseUrl) return proxyUnreachable();
888
+
889
+ // No value means show. A read must not rewrite what it is reporting.
890
+ if (requested === undefined) {
891
+ const response = await apiJson(deps, baseUrl, "GET", transport.readPath);
892
+ if (response.status === 0) return proxyUnreachable(response.transportError);
893
+ if (response.status !== 200) return apiError(response.json, `failed to read ${label}`, response.status);
894
+ const strategy = response.json[transport.strategyKey];
895
+ const sticky = response.json[transport.stickyKey];
896
+ if (wantsJson) {
897
+ // Pool-neutral key names: the two routes spell the same two settings differently, and a
898
+ // `--json` consumer should not have to branch on which pool answered.
899
+ console.log(JSON.stringify({ ok: true, provider: name, strategy, stickyLimit: sticky }, null, 2));
900
+ } else {
901
+ console.log(`${name}: ${label} is ${String(field === "strategy" ? strategy : sticky)}`);
902
+ }
903
+ return 0;
904
+ }
905
+
906
+ // Sent as a number when it parses as one so the server sees the type it validates;
907
+ // a non-numeric string still goes through and earns the server's own 400.
908
+ const value = field === "strategy"
909
+ ? requested
910
+ : (Number.isNaN(Number(requested)) ? requested : Number(requested));
911
+ const response = await apiJson(deps, baseUrl, "PUT", transport.writePath, transport.writeBody(field, value));
912
+ if (response.status === 0) return proxyUnreachable(response.transportError);
913
+ if (response.status !== 200) return apiError(response.json, `failed to set ${label}`, response.status);
914
+
915
+ if (wantsJson) {
916
+ console.log(JSON.stringify({
917
+ ok: true,
918
+ provider: name,
919
+ strategy: response.json[transport.strategyKey],
920
+ stickyLimit: response.json[transport.stickyKey],
921
+ }, null, 2));
922
+ } else {
923
+ // Echo the APPLIED value from the response, not the requested one: the server normalizes,
924
+ // and printing the request would hide a normalization the operator should see.
925
+ const applied = field === "strategy" ? response.json[transport.strategyKey] : response.json[transport.stickyKey];
926
+ console.log(`${name}: ${label} is now ${String(applied)}`);
927
+ }
928
+ return 0;
929
+ }
930
+
931
+ export function cmdStrategy(args: string[], deps: AccountDeps): Promise<number> {
932
+ return poolSetting(args, deps, "strategy");
933
+ }
934
+
935
+ export function cmdSticky(args: string[], deps: AccountDeps): Promise<number> {
936
+ return poolSetting(args, deps, "stickyLimit");
937
+ }
938
+
319
939
  export async function cmdAlias(args: string[], deps: AccountDeps): Promise<number> {
320
940
  const wantsJson = flag(args, "--json");
321
941
  const name = args.shift();
@@ -341,8 +961,8 @@ export async function cmdAlias(args: string[], deps: AccountDeps): Promise<numbe
341
961
  ? { provider: name, accountId: id, alias }
342
962
  : { name, id, alias };
343
963
  const response = await apiJson(deps, baseUrl, "PUT", path, body);
344
- if (response.status === 0) return proxyUnreachable();
345
- if (response.status !== 200) return apiError(response.json, `failed to rename ${requestedId}`);
964
+ if (response.status === 0) return proxyUnreachable(response.transportError);
965
+ if (response.status !== 200) return apiError(response.json, `failed to rename ${requestedId}`, response.status);
346
966
  const result = { ok: true, provider: name, id, alias: alias || null };
347
967
  if (wantsJson) console.log(JSON.stringify(result, null, 2));
348
968
  else console.log(alias ? `${name}: ${requestedId} is now “${alias}”` : `${name}: cleared alias for ${requestedId}`);