@iislee/opencodex 2.11.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 (476) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +225 -0
  3. package/assets/architecture.png +0 -0
  4. package/assets/banner.png +0 -0
  5. package/assets/claude-code-models.gif +0 -0
  6. package/assets/codex-app-picker.png +0 -0
  7. package/bin/ocx.mjs +451 -0
  8. package/bin/package-main.mjs +9 -0
  9. package/gui/dist/assets/index-DTpMHS4F.js +67 -0
  10. package/gui/dist/assets/index-ZNVDE3C7.css +1 -0
  11. package/gui/dist/favicon.png +0 -0
  12. package/gui/dist/icons.svg +24 -0
  13. package/gui/dist/index.html +25 -0
  14. package/gui/dist/logo.png +0 -0
  15. package/gui/dist/provider-icons/alibaba-color.svg +1 -0
  16. package/gui/dist/provider-icons/antigravity-color.svg +1 -0
  17. package/gui/dist/provider-icons/antigravity.svg +1 -0
  18. package/gui/dist/provider-icons/claude-color.svg +1 -0
  19. package/gui/dist/provider-icons/claude.svg +1 -0
  20. package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -0
  21. package/gui/dist/provider-icons/copilot-color.svg +1 -0
  22. package/gui/dist/provider-icons/copilot.svg +1 -0
  23. package/gui/dist/provider-icons/cursor-color.svg +2 -0
  24. package/gui/dist/provider-icons/cursor.svg +2 -0
  25. package/gui/dist/provider-icons/deepseek-color.svg +1 -0
  26. package/gui/dist/provider-icons/discord.svg +1 -0
  27. package/gui/dist/provider-icons/firepass-color.svg +1 -0
  28. package/gui/dist/provider-icons/fireworks-color.svg +1 -0
  29. package/gui/dist/provider-icons/gemini-color.svg +1 -0
  30. package/gui/dist/provider-icons/gemini.svg +1 -0
  31. package/gui/dist/provider-icons/github-copilot-color.svg +1 -0
  32. package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -0
  33. package/gui/dist/provider-icons/grok-color.svg +1 -0
  34. package/gui/dist/provider-icons/grok.svg +1 -0
  35. package/gui/dist/provider-icons/groq-color.svg +1 -0
  36. package/gui/dist/provider-icons/huggingface-color.svg +1 -0
  37. package/gui/dist/provider-icons/kimi-color.svg +1 -0
  38. package/gui/dist/provider-icons/kiro-color.svg +15 -0
  39. package/gui/dist/provider-icons/kiro.svg +14 -0
  40. package/gui/dist/provider-icons/lm-studio-color.svg +1 -0
  41. package/gui/dist/provider-icons/mistral-color.svg +1 -0
  42. package/gui/dist/provider-icons/moonshot-color.svg +1 -0
  43. package/gui/dist/provider-icons/nvidia-color.svg +1 -0
  44. package/gui/dist/provider-icons/ollama-color.svg +1 -0
  45. package/gui/dist/provider-icons/openai.svg +1 -0
  46. package/gui/dist/provider-icons/opencode.svg +1 -0
  47. package/gui/dist/provider-icons/openrouter-color.svg +1 -0
  48. package/gui/dist/provider-icons/pi.svg +21 -0
  49. package/gui/dist/provider-icons/qianfan-color.svg +1 -0
  50. package/gui/dist/provider-icons/qwen-portal-color.svg +1 -0
  51. package/gui/dist/provider-icons/telegram.svg +1 -0
  52. package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -0
  53. package/gui/dist/provider-icons/vllm-color.svg +1 -0
  54. package/gui/dist/provider-icons/xiaomi-color.svg +1 -0
  55. package/package.json +102 -0
  56. package/src/AGENTS.md +28 -0
  57. package/src/adapters/anthropic-image-guard.ts +251 -0
  58. package/src/adapters/anthropic-image-normalize.ts +518 -0
  59. package/src/adapters/anthropic.ts +1003 -0
  60. package/src/adapters/azure.ts +36 -0
  61. package/src/adapters/base.ts +72 -0
  62. package/src/adapters/client-fingerprint.ts +59 -0
  63. package/src/adapters/cursor/arg-codec.ts +38 -0
  64. package/src/adapters/cursor/arg-normalize.ts +104 -0
  65. package/src/adapters/cursor/cursor-errors.ts +165 -0
  66. package/src/adapters/cursor/discovery.ts +276 -0
  67. package/src/adapters/cursor/effort-map.ts +127 -0
  68. package/src/adapters/cursor/exec-policy.ts +88 -0
  69. package/src/adapters/cursor/framing.ts +211 -0
  70. package/src/adapters/cursor/gen/agent_pb.ts +15274 -0
  71. package/src/adapters/cursor/kv-store.ts +52 -0
  72. package/src/adapters/cursor/live-models.ts +153 -0
  73. package/src/adapters/cursor/live-smoke-gate.ts +41 -0
  74. package/src/adapters/cursor/live-transport.ts +1214 -0
  75. package/src/adapters/cursor/mcp-config.ts +42 -0
  76. package/src/adapters/cursor/mcp-manager.ts +333 -0
  77. package/src/adapters/cursor/message-mapper.ts +49 -0
  78. package/src/adapters/cursor/native-exec-common.ts +55 -0
  79. package/src/adapters/cursor/native-exec-desktop.ts +184 -0
  80. package/src/adapters/cursor/native-exec-fs.ts +329 -0
  81. package/src/adapters/cursor/native-exec-mcp.ts +153 -0
  82. package/src/adapters/cursor/native-exec-network.ts +43 -0
  83. package/src/adapters/cursor/native-exec-shell.ts +548 -0
  84. package/src/adapters/cursor/native-exec-tools.ts +118 -0
  85. package/src/adapters/cursor/native-exec.ts +576 -0
  86. package/src/adapters/cursor/protobuf-events.ts +563 -0
  87. package/src/adapters/cursor/protobuf-request.ts +714 -0
  88. package/src/adapters/cursor/request-builder.ts +255 -0
  89. package/src/adapters/cursor/thread-continuity.ts +67 -0
  90. package/src/adapters/cursor/tool-definitions.ts +505 -0
  91. package/src/adapters/cursor/transport-retry.ts +132 -0
  92. package/src/adapters/cursor/transport.ts +57 -0
  93. package/src/adapters/cursor/types.ts +52 -0
  94. package/src/adapters/cursor.ts +196 -0
  95. package/src/adapters/google-antigravity-replay.ts +303 -0
  96. package/src/adapters/google-antigravity-wire.ts +108 -0
  97. package/src/adapters/google-errors.ts +85 -0
  98. package/src/adapters/google-http.ts +100 -0
  99. package/src/adapters/google-tool-schema.ts +173 -0
  100. package/src/adapters/google-truncation.ts +13 -0
  101. package/src/adapters/google-wire-compiler.ts +232 -0
  102. package/src/adapters/google.ts +758 -0
  103. package/src/adapters/identity.ts +44 -0
  104. package/src/adapters/image.ts +23 -0
  105. package/src/adapters/kiro-constants.ts +16 -0
  106. package/src/adapters/kiro-errors.ts +197 -0
  107. package/src/adapters/kiro-events.ts +179 -0
  108. package/src/adapters/kiro-images.ts +129 -0
  109. package/src/adapters/kiro-retry.ts +312 -0
  110. package/src/adapters/kiro-thinking.ts +96 -0
  111. package/src/adapters/kiro-tool-fallback.ts +36 -0
  112. package/src/adapters/kiro-tools.ts +215 -0
  113. package/src/adapters/kiro-truncation.ts +33 -0
  114. package/src/adapters/kiro-wire.ts +129 -0
  115. package/src/adapters/kiro.ts +1898 -0
  116. package/src/adapters/mimo-free.ts +263 -0
  117. package/src/adapters/openai-chat.ts +1005 -0
  118. package/src/adapters/openai-responses.ts +1137 -0
  119. package/src/adapters/run-turn-queue.ts +114 -0
  120. package/src/adapters/tool-catalog-nudge.ts +71 -0
  121. package/src/adapters/upstream-http-error.ts +48 -0
  122. package/src/bridge.ts +1619 -0
  123. package/src/chat/inbound.ts +295 -0
  124. package/src/chat/outbound.ts +765 -0
  125. package/src/claude/agents-inject.ts +243 -0
  126. package/src/claude/alias.ts +149 -0
  127. package/src/claude/auth-detect.ts +229 -0
  128. package/src/claude/auth-mode-migration.ts +32 -0
  129. package/src/claude/auth-mode.ts +62 -0
  130. package/src/claude/context-windows.ts +189 -0
  131. package/src/claude/desktop-3p-guard.ts +35 -0
  132. package/src/claude/desktop-3p-paths.ts +84 -0
  133. package/src/claude/desktop-3p.ts +381 -0
  134. package/src/claude/desktop-health.ts +26 -0
  135. package/src/claude/desktop-profile.ts +263 -0
  136. package/src/claude/gateway-cache.ts +70 -0
  137. package/src/claude/inbound-debug.ts +163 -0
  138. package/src/claude/inbound.ts +509 -0
  139. package/src/claude/model-info.ts +151 -0
  140. package/src/claude/outbound.ts +872 -0
  141. package/src/cli/access.ts +108 -0
  142. package/src/cli/account-api.ts +268 -0
  143. package/src/cli/account-auth.ts +223 -0
  144. package/src/cli/account-extended.ts +350 -0
  145. package/src/cli/account.ts +275 -0
  146. package/src/cli/agent-driven.ts +70 -0
  147. package/src/cli/agent.ts +184 -0
  148. package/src/cli/catalog-prewarm.ts +27 -0
  149. package/src/cli/claude-desktop.ts +188 -0
  150. package/src/cli/claude.ts +286 -0
  151. package/src/cli/codex-shim-autorestore.ts +45 -0
  152. package/src/cli/combo.ts +119 -0
  153. package/src/cli/config-command.ts +145 -0
  154. package/src/cli/debug.ts +228 -0
  155. package/src/cli/doctor.ts +930 -0
  156. package/src/cli/export-command.ts +187 -0
  157. package/src/cli/help.ts +354 -0
  158. package/src/cli/index.ts +1113 -0
  159. package/src/cli/init.ts +224 -0
  160. package/src/cli/integrations.ts +142 -0
  161. package/src/cli/interactive-confirm.ts +133 -0
  162. package/src/cli/internal-dispatch.ts +20 -0
  163. package/src/cli/models-runtime.ts +212 -0
  164. package/src/cli/models.ts +336 -0
  165. package/src/cli/observe.ts +117 -0
  166. package/src/cli/opencode.ts +586 -0
  167. package/src/cli/pi.ts +188 -0
  168. package/src/cli/provider-runtime.ts +162 -0
  169. package/src/cli/provider.ts +463 -0
  170. package/src/cli/runtime-api.ts +325 -0
  171. package/src/cli/star-prompt.ts +155 -0
  172. package/src/cli/status-oauth.ts +78 -0
  173. package/src/cli/status.ts +321 -0
  174. package/src/cli/sync-cloud.ts +283 -0
  175. package/src/cli/system-command.ts +112 -0
  176. package/src/cli/tray-proxy.ts +52 -0
  177. package/src/cli/v2.ts +173 -0
  178. package/src/cli.ts +10 -0
  179. package/src/clients/config-export.ts +377 -0
  180. package/src/clients/effective-status.ts +385 -0
  181. package/src/clients/probes/agy.ts +55 -0
  182. package/src/clients/probes/cc-switch.ts +110 -0
  183. package/src/clients/probes/claude.ts +90 -0
  184. package/src/clients/probes/codex.ts +125 -0
  185. package/src/clients/probes/grok.ts +29 -0
  186. package/src/clients/probes/opencode.ts +109 -0
  187. package/src/clients/probes/paseo.ts +55 -0
  188. package/src/clients/probes/pi.ts +55 -0
  189. package/src/cloud/onedrive-auth.ts +666 -0
  190. package/src/cloud/onedrive-graph.ts +108 -0
  191. package/src/cloud/settings.ts +75 -0
  192. package/src/cloud/sync.ts +212 -0
  193. package/src/cloud/types.ts +56 -0
  194. package/src/cloud/vault.ts +89 -0
  195. package/src/codex/account-id.ts +34 -0
  196. package/src/codex/account-label.ts +34 -0
  197. package/src/codex/account-lifecycle.ts +55 -0
  198. package/src/codex/account-namespace-match.ts +63 -0
  199. package/src/codex/account-namespaces.ts +149 -0
  200. package/src/codex/account-pause.ts +20 -0
  201. package/src/codex/account-runtime-state.ts +31 -0
  202. package/src/codex/account-store.ts +517 -0
  203. package/src/codex/account-usability.ts +20 -0
  204. package/src/codex/app-server-processes.ts +756 -0
  205. package/src/codex/auth-api.ts +1540 -0
  206. package/src/codex/auth-collision.ts +107 -0
  207. package/src/codex/auth-context.ts +352 -0
  208. package/src/codex/autostart-health.ts +149 -0
  209. package/src/codex/catalog/aggregation.ts +378 -0
  210. package/src/codex/catalog/bundled.ts +251 -0
  211. package/src/codex/catalog/effort.ts +355 -0
  212. package/src/codex/catalog/metadata.ts +180 -0
  213. package/src/codex/catalog/parsing.ts +456 -0
  214. package/src/codex/catalog/provider-fetch.ts +902 -0
  215. package/src/codex/catalog/sync.ts +620 -0
  216. package/src/codex/catalog.ts +12 -0
  217. package/src/codex/data/upstream-models.json +830 -0
  218. package/src/codex/exec-invocation.ts +22 -0
  219. package/src/codex/features.ts +969 -0
  220. package/src/codex/history-migration-guardian.ts +102 -0
  221. package/src/codex/history-provider.ts +776 -0
  222. package/src/codex/home.ts +206 -0
  223. package/src/codex/inject.ts +799 -0
  224. package/src/codex/injected-marker.ts +72 -0
  225. package/src/codex/journal.ts +163 -0
  226. package/src/codex/main-account-cache.ts +32 -0
  227. package/src/codex/main-account.ts +40 -0
  228. package/src/codex/model-cache.ts +227 -0
  229. package/src/codex/paths.ts +65 -0
  230. package/src/codex/plugins-doctor.ts +242 -0
  231. package/src/codex/pool-rotation.ts +225 -0
  232. package/src/codex/project-config-warnings.ts +411 -0
  233. package/src/codex/quota.ts +411 -0
  234. package/src/codex/refresh.ts +53 -0
  235. package/src/codex/routing.ts +1477 -0
  236. package/src/codex/runtime.ts +538 -0
  237. package/src/codex/shim.ts +1189 -0
  238. package/src/codex/subagent-defaults.ts +550 -0
  239. package/src/codex/subagent-model-fallback.ts +469 -0
  240. package/src/codex/sync.ts +130 -0
  241. package/src/codex/warmup.ts +192 -0
  242. package/src/codex/websocket-registry.ts +100 -0
  243. package/src/combos/failover.ts +140 -0
  244. package/src/combos/index.ts +41 -0
  245. package/src/combos/request.ts +62 -0
  246. package/src/combos/resolve.ts +232 -0
  247. package/src/combos/types.ts +326 -0
  248. package/src/config.ts +2356 -0
  249. package/src/generated/jawcode-model-metadata.ts +104 -0
  250. package/src/github/star-state.ts +203 -0
  251. package/src/grok/inject.ts +545 -0
  252. package/src/grok/status.ts +121 -0
  253. package/src/grok/sync.ts +103 -0
  254. package/src/grok/usage-hook/report.mjs +348 -0
  255. package/src/grok/usage-hook.ts +278 -0
  256. package/src/images/artifacts.ts +516 -0
  257. package/src/images/fulfill-video.ts +163 -0
  258. package/src/images/fulfill.ts +149 -0
  259. package/src/images/index.ts +4 -0
  260. package/src/images/loop.ts +829 -0
  261. package/src/images/plan.ts +133 -0
  262. package/src/images/synthetic-tool.ts +133 -0
  263. package/src/images/types.ts +41 -0
  264. package/src/images/xai-client.ts +141 -0
  265. package/src/images/xai-video-client.ts +163 -0
  266. package/src/index.ts +22 -0
  267. package/src/lib/abort.ts +146 -0
  268. package/src/lib/admin-secrets.ts +25 -0
  269. package/src/lib/admission.ts +83 -0
  270. package/src/lib/app-owned-memory-stores.ts +173 -0
  271. package/src/lib/app-owned-memory.ts +265 -0
  272. package/src/lib/bounded-body.ts +202 -0
  273. package/src/lib/bun-binary-validator.d.mts +3 -0
  274. package/src/lib/bun-binary-validator.mjs +18 -0
  275. package/src/lib/bun-runtime.ts +71 -0
  276. package/src/lib/bun-stream-caps.ts +126 -0
  277. package/src/lib/config-ownership.ts +360 -0
  278. package/src/lib/crash-guard.ts +344 -0
  279. package/src/lib/debug-log-buffer.ts +83 -0
  280. package/src/lib/debug-settings.ts +108 -0
  281. package/src/lib/debug.ts +31 -0
  282. package/src/lib/destination-policy.ts +316 -0
  283. package/src/lib/errors.ts +364 -0
  284. package/src/lib/eventstream-decoder.ts +253 -0
  285. package/src/lib/gcp-adc.ts +341 -0
  286. package/src/lib/injection-debug-log.ts +58 -0
  287. package/src/lib/open-url.ts +25 -0
  288. package/src/lib/pinned-http.ts +151 -0
  289. package/src/lib/privacy.ts +20 -0
  290. package/src/lib/process-control.ts +165 -0
  291. package/src/lib/provider-outbound.ts +170 -0
  292. package/src/lib/provider-url.ts +14 -0
  293. package/src/lib/proxy-env.ts +18 -0
  294. package/src/lib/redact.ts +105 -0
  295. package/src/lib/retry-after.ts +55 -0
  296. package/src/lib/service-secrets.ts +25 -0
  297. package/src/lib/shadow-call.ts +30 -0
  298. package/src/lib/sidecar-tracker.ts +52 -0
  299. package/src/lib/sse-decoder.ts +323 -0
  300. package/src/lib/state-store-registrations.ts +109 -0
  301. package/src/lib/state-store-sweeper.ts +184 -0
  302. package/src/lib/test-home-guard.ts +90 -0
  303. package/src/lib/token-estimate.ts +69 -0
  304. package/src/lib/translator-budget.ts +356 -0
  305. package/src/lib/upstream-retry.ts +239 -0
  306. package/src/lib/win-exec.ts +115 -0
  307. package/src/lib/win-paths.ts +68 -0
  308. package/src/lib/windows-elevation.ts +705 -0
  309. package/src/lib/windows-secret-acl.ts +514 -0
  310. package/src/lib/winsw.ts +375 -0
  311. package/src/oauth/anthropic-routing.ts +594 -0
  312. package/src/oauth/anthropic.ts +177 -0
  313. package/src/oauth/callback-server.ts +294 -0
  314. package/src/oauth/chatgpt.ts +150 -0
  315. package/src/oauth/cursor.ts +211 -0
  316. package/src/oauth/github-copilot.ts +428 -0
  317. package/src/oauth/google-antigravity.ts +230 -0
  318. package/src/oauth/health.ts +399 -0
  319. package/src/oauth/index.ts +1174 -0
  320. package/src/oauth/key-providers.ts +108 -0
  321. package/src/oauth/kimi.ts +213 -0
  322. package/src/oauth/kiro-credentials.ts +726 -0
  323. package/src/oauth/kiro.ts +577 -0
  324. package/src/oauth/local-token-detect.ts +121 -0
  325. package/src/oauth/log.ts +48 -0
  326. package/src/oauth/login-cli.ts +163 -0
  327. package/src/oauth/pkce.ts +15 -0
  328. package/src/oauth/store.ts +630 -0
  329. package/src/oauth/token-guardian.ts +303 -0
  330. package/src/oauth/types.ts +62 -0
  331. package/src/oauth/xai.ts +241 -0
  332. package/src/pi/extensions.ts +72 -0
  333. package/src/pi/home.ts +42 -0
  334. package/src/pi/index.ts +40 -0
  335. package/src/pi/models.ts +278 -0
  336. package/src/pi/packages.ts +219 -0
  337. package/src/pi/settings.ts +365 -0
  338. package/src/pi/status.ts +68 -0
  339. package/src/pi/sync.ts +75 -0
  340. package/src/providers/alibaba-region-backup.ts +75 -0
  341. package/src/providers/alibaba-region-migration.ts +156 -0
  342. package/src/providers/alibaba-region-startup.ts +36 -0
  343. package/src/providers/antigravity-models.ts +205 -0
  344. package/src/providers/api-keys.ts +140 -0
  345. package/src/providers/base-url-choices.ts +64 -0
  346. package/src/providers/context-cap.ts +65 -0
  347. package/src/providers/derive.ts +339 -0
  348. package/src/providers/free-directory.ts +184 -0
  349. package/src/providers/github-copilot-transport.ts +56 -0
  350. package/src/providers/key-failover.ts +203 -0
  351. package/src/providers/kiro-models.ts +67 -0
  352. package/src/providers/label.ts +19 -0
  353. package/src/providers/model-discovery.ts +356 -0
  354. package/src/providers/openai-sidecar.ts +175 -0
  355. package/src/providers/openai-tier-startup.ts +27 -0
  356. package/src/providers/openai-tiers.ts +301 -0
  357. package/src/providers/openai-virtual-models.ts +82 -0
  358. package/src/providers/openrouter-routing.ts +102 -0
  359. package/src/providers/provider-id-rewrite.ts +150 -0
  360. package/src/providers/quota.ts +1260 -0
  361. package/src/providers/registry.ts +1600 -0
  362. package/src/providers/slug-codec.ts +67 -0
  363. package/src/providers/xai-transport.ts +141 -0
  364. package/src/reasoning-effort.ts +135 -0
  365. package/src/responses/compaction.ts +117 -0
  366. package/src/responses/parser.ts +656 -0
  367. package/src/responses/reasoning-envelope.ts +52 -0
  368. package/src/responses/schema.ts +159 -0
  369. package/src/responses/spill-store.ts +394 -0
  370. package/src/responses/state.ts +895 -0
  371. package/src/responses/tool-groups.ts +19 -0
  372. package/src/router.ts +425 -0
  373. package/src/server/adapter-resolve.ts +80 -0
  374. package/src/server/auth-cors.ts +530 -0
  375. package/src/server/chat-completions.ts +368 -0
  376. package/src/server/claude-messages.ts +914 -0
  377. package/src/server/effort-policy.ts +172 -0
  378. package/src/server/gui-static.ts +123 -0
  379. package/src/server/image-retry.ts +42 -0
  380. package/src/server/images.ts +476 -0
  381. package/src/server/index.ts +1126 -0
  382. package/src/server/lifecycle.ts +227 -0
  383. package/src/server/live.ts +598 -0
  384. package/src/server/management/agent-settings-routes.ts +1169 -0
  385. package/src/server/management/api-access.ts +141 -0
  386. package/src/server/management/api-key-usage.ts +167 -0
  387. package/src/server/management/body.ts +35 -0
  388. package/src/server/management/clients-routes.ts +63 -0
  389. package/src/server/management/cloud-sync-routes.ts +266 -0
  390. package/src/server/management/combo-routes.ts +220 -0
  391. package/src/server/management/config-routes.ts +422 -0
  392. package/src/server/management/context.ts +31 -0
  393. package/src/server/management/logs-usage-routes.ts +707 -0
  394. package/src/server/management/model-routes.ts +525 -0
  395. package/src/server/management/oauth-account-routes.ts +563 -0
  396. package/src/server/management/provider-routes.ts +556 -0
  397. package/src/server/management/shared.ts +277 -0
  398. package/src/server/management/sidebar-routes.ts +90 -0
  399. package/src/server/management/system-restart.ts +179 -0
  400. package/src/server/management/system-routes.ts +117 -0
  401. package/src/server/management/usage-summary-cache.ts +86 -0
  402. package/src/server/management-api.ts +215 -0
  403. package/src/server/management-auth.ts +267 -0
  404. package/src/server/memory-watchdog.ts +156 -0
  405. package/src/server/port-reclaim.ts +307 -0
  406. package/src/server/ports.ts +116 -0
  407. package/src/server/proxy-liveness.ts +201 -0
  408. package/src/server/relay-eager.ts +313 -0
  409. package/src/server/relay.ts +1049 -0
  410. package/src/server/request-decompress.ts +132 -0
  411. package/src/server/request-log-conversation.ts +168 -0
  412. package/src/server/request-log.ts +1046 -0
  413. package/src/server/responses/collaboration.ts +354 -0
  414. package/src/server/responses/compact.ts +384 -0
  415. package/src/server/responses/core.ts +2758 -0
  416. package/src/server/responses/encrypted-payload.ts +308 -0
  417. package/src/server/responses/fetch-helpers.ts +157 -0
  418. package/src/server/responses/passthrough-error.ts +78 -0
  419. package/src/server/responses/terminal-guard.ts +230 -0
  420. package/src/server/responses/upstream-error.ts +48 -0
  421. package/src/server/responses-image-gen-repair.ts +132 -0
  422. package/src/server/responses-item-id-repair.ts +224 -0
  423. package/src/server/responses.ts +9 -0
  424. package/src/server/search.ts +136 -0
  425. package/src/server/sse-payload-rewrite.ts +175 -0
  426. package/src/server/startup-action-control.ts +308 -0
  427. package/src/server/startup-health-cache.ts +113 -0
  428. package/src/server/system-env.ts +413 -0
  429. package/src/server/windows-tcp-drop.ts +184 -0
  430. package/src/server/windows-tray-control.ts +41 -0
  431. package/src/server/ws-bridge.ts +471 -0
  432. package/src/service.ts +2554 -0
  433. package/src/stall-timeout.ts +20 -0
  434. package/src/storage/cleanup-job.ts +57 -0
  435. package/src/storage/cleanup.ts +3085 -0
  436. package/src/storage/policy-job.ts +457 -0
  437. package/src/storage/policy-scheduler.ts +40 -0
  438. package/src/storage/policy-worker.ts +59 -0
  439. package/src/storage/policy.ts +527 -0
  440. package/src/storage/restore-job.ts +299 -0
  441. package/src/storage/restore-worker.ts +58 -0
  442. package/src/storage/scanner.ts +238 -0
  443. package/src/storage/storage-mutation-coordinator.ts +139 -0
  444. package/src/storage/worker-lifecycle.ts +215 -0
  445. package/src/tray/assets/opencodex-tray-offline.ico +0 -0
  446. package/src/tray/assets/opencodex-tray-online.ico +0 -0
  447. package/src/tray/assets/opencodex-tray-warning.ico +0 -0
  448. package/src/tray/assets/opencodex-tray.png +0 -0
  449. package/src/tray/windows-tray.ps1 +290 -0
  450. package/src/tray/windows.ts +730 -0
  451. package/src/types.ts +1237 -0
  452. package/src/update/badge.ts +72 -0
  453. package/src/update/index.ts +407 -0
  454. package/src/update/job.ts +1520 -0
  455. package/src/update/notify.ts +257 -0
  456. package/src/update/npm-invocation.d.mts +23 -0
  457. package/src/update/npm-invocation.mjs +94 -0
  458. package/src/update/tray-update-plan.d.mts +18 -0
  459. package/src/update/tray-update-plan.mjs +38 -0
  460. package/src/usage/cost.ts +0 -0
  461. package/src/usage/debug.ts +97 -0
  462. package/src/usage/expected-prices.ts +164 -0
  463. package/src/usage/log.ts +658 -0
  464. package/src/usage/summary.ts +585 -0
  465. package/src/usage/totals.ts +14 -0
  466. package/src/vision/anthropic-describe.ts +185 -0
  467. package/src/vision/describe.ts +125 -0
  468. package/src/vision/index.ts +467 -0
  469. package/src/web-search/anthropic-executor.ts +189 -0
  470. package/src/web-search/executor.ts +105 -0
  471. package/src/web-search/format-result.ts +89 -0
  472. package/src/web-search/index.ts +196 -0
  473. package/src/web-search/loop.ts +664 -0
  474. package/src/web-search/parse.ts +220 -0
  475. package/src/web-search/progress-stream.ts +342 -0
  476. package/src/web-search/synthetic-tool.ts +47 -0
@@ -0,0 +1,108 @@
1
+ import {
2
+ CliUsageError,
3
+ printData,
4
+ rejectArgs,
5
+ runCliAction,
6
+ runtimeRequest,
7
+ takeFlag,
8
+ takeOption,
9
+ type RuntimeApiDeps,
10
+ } from "./runtime-api";
11
+
12
+ const USAGE = `Usage:
13
+ ocx access key [list] [--json]
14
+ ocx access key create [name] [--json]
15
+ ocx access key remove <id> --yes [--json]
16
+ ocx access endpoints [--json]
17
+ ocx access models [--json]
18
+ ocx access test <model> [--protocol <chat|responses|messages>] [--json]`;
19
+
20
+ async function key(argv: string[], deps: RuntimeApiDeps): Promise<void> {
21
+ const args = [...argv];
22
+ const action = (args.shift() ?? "list").toLowerCase();
23
+ const wantsJson = takeFlag(args, "--json");
24
+ if (action === "list") {
25
+ rejectArgs(args, USAGE);
26
+ const result = await runtimeRequest<Record<string, unknown>>("/api/keys", {}, deps);
27
+ const keys = Array.isArray(result.keys) ? result.keys as Array<Record<string, unknown>> : [];
28
+ printData(result, wantsJson, keys.length
29
+ ? keys.map(entry => `${String(entry.id)} ${String(entry.name)} ${String(entry.prefix ?? "")}`)
30
+ : ["No API access keys configured."]);
31
+ return;
32
+ }
33
+ if (action === "create") {
34
+ const name = args.shift() ?? "default";
35
+ rejectArgs(args, USAGE);
36
+ const result = await runtimeRequest<Record<string, unknown>>("/api/keys", {
37
+ method: "POST",
38
+ body: JSON.stringify({ name }),
39
+ }, deps);
40
+ // The plaintext key is returned once. Keep text output explicit so callers know to store it.
41
+ printData(result, wantsJson, [
42
+ `Created API key ${String(result.name ?? name)} (${String(result.id ?? "")}).`,
43
+ `Key (shown once): ${String(result.key ?? "")}`,
44
+ ]);
45
+ return;
46
+ }
47
+ if (action === "remove" || action === "delete") {
48
+ const id = args.shift();
49
+ const yes = takeFlag(args, "--yes");
50
+ if (!id) throw new CliUsageError("key id is required", USAGE);
51
+ if (!yes) throw new CliUsageError("remove requires --yes", USAGE);
52
+ rejectArgs(args, USAGE);
53
+ const result = await runtimeRequest("/api/keys", { method: "DELETE", body: JSON.stringify({ id }) }, deps);
54
+ printData(result, wantsJson, [`Removed API key ${id}.`]);
55
+ return;
56
+ }
57
+ throw new CliUsageError(`unknown key command ${action}`, USAGE);
58
+ }
59
+
60
+ async function endpoints(argv: string[], deps: RuntimeApiDeps): Promise<void> {
61
+ const args = [...argv];
62
+ const wantsJson = takeFlag(args, "--json");
63
+ rejectArgs(args, USAGE);
64
+ const result = await runtimeRequest<Record<string, unknown>>("/api/keys", {}, deps);
65
+ const view = Object.fromEntries(Object.entries(result).filter(([key]) => key.endsWith("Endpoint") || key === "baseUrl" || key === "endpoint"));
66
+ printData(view, wantsJson, Object.entries(view).map(([name, value]) => `${name}: ${String(value)}`));
67
+ }
68
+
69
+ async function models(argv: string[], deps: RuntimeApiDeps): Promise<void> {
70
+ const args = [...argv];
71
+ const wantsJson = takeFlag(args, "--json");
72
+ rejectArgs(args, USAGE);
73
+ const result = await runtimeRequest<Record<string, unknown>>("/v1/models", {}, deps);
74
+ const rows = Array.isArray(result.data) ? result.data as Array<Record<string, unknown>> : [];
75
+ printData(result, wantsJson, rows.map(row => `${String(row.id)} ${String(row.owned_by ?? "")}`.trimEnd()));
76
+ }
77
+
78
+ async function testModel(argv: string[], deps: RuntimeApiDeps): Promise<void> {
79
+ const args = [...argv];
80
+ const model = args.shift();
81
+ const wantsJson = takeFlag(args, "--json");
82
+ const protocol = takeOption(args, "--protocol") ?? "chat";
83
+ if (!model) throw new CliUsageError("model is required", USAGE);
84
+ if (!(["chat", "responses", "messages"] as const).includes(protocol as "chat" | "responses" | "messages")) {
85
+ throw new CliUsageError("--protocol must be chat, responses, or messages", USAGE);
86
+ }
87
+ rejectArgs(args, USAGE);
88
+ const request = protocol === "responses"
89
+ ? { path: "/v1/responses", body: { model, input: "Reply with OK.", max_output_tokens: 16 } }
90
+ : protocol === "messages"
91
+ ? { path: "/v1/messages", body: { model, messages: [{ role: "user", content: "Reply with OK." }], max_tokens: 16 } }
92
+ : { path: "/v1/chat/completions", body: { model, messages: [{ role: "user", content: "Reply with OK." }], max_tokens: 16, stream: false } };
93
+ const result = await runtimeRequest(request.path, { method: "POST", body: JSON.stringify(request.body) }, deps);
94
+ printData(result, wantsJson, [`${model}: ${protocol} request succeeded.`]);
95
+ }
96
+
97
+ export async function handleAccessCommand(argv: string[], deps: RuntimeApiDeps = {}): Promise<number> {
98
+ return runCliAction(async () => {
99
+ const [sub = "key", ...rest] = argv;
100
+ if (sub === "key" || sub === "keys") await key(rest, deps);
101
+ else if (sub === "endpoints") await endpoints(rest, deps);
102
+ else if (sub === "models") await models(rest, deps);
103
+ else if (sub === "test") await testModel(rest, deps);
104
+ else throw new CliUsageError(`unknown access command ${sub}`, USAGE);
105
+ });
106
+ }
107
+
108
+ export const ACCESS_USAGE = USAGE;
@@ -0,0 +1,268 @@
1
+ /**
2
+ * Data-access layer for `ocx account` (issue #180) — live-proxy HTTP client and
3
+ * per-family account readers. Kept separate from account.ts (command handlers)
4
+ * per the 400-line module budget.
5
+ */
6
+ import { findLiveProxy, probeHostname } from "../server/proxy-liveness";
7
+ import { runningProxyUpdateHeaders } from "../oauth/login-cli";
8
+ import { isPublicOAuthProvider } from "../oauth/index";
9
+ import { getProviderRegistryEntry, providerCodexAccountMode } from "../providers/registry";
10
+ import type { OcxConfig } from "../types";
11
+
12
+ export type AccountType = "codex" | "oauth" | "api-key";
13
+
14
+ export interface AccountRow {
15
+ provider: string;
16
+ type: AccountType;
17
+ id: string;
18
+ label?: string;
19
+ email?: string;
20
+ plan?: string;
21
+ masked?: string;
22
+ active: boolean;
23
+ needsReauth?: boolean;
24
+ quota?: CodexQuotaDto | null;
25
+ }
26
+
27
+ export type ClassifyResult = { type: AccountType } | { error: string };
28
+
29
+ export type AccountStdin = NodeJS.ReadableStream & { isTTY?: boolean };
30
+
31
+ export interface AccountDeps {
32
+ /** Test injection: skip findLiveProxy and call the API at this base URL. */
33
+ baseUrl?: string;
34
+ fetchImpl?: typeof fetch;
35
+ loadConfigImpl?: () => OcxConfig;
36
+ stdinImpl?: AccountStdin;
37
+ stdinTimeoutMs?: number;
38
+ }
39
+
40
+ export function classifyAccount(config: OcxConfig, name: string): ClassifyResult {
41
+ const provider = config.providers?.[name];
42
+ if (providerCodexAccountMode(name, provider)) return { type: "codex" };
43
+ const entry = getProviderRegistryEntry(name);
44
+ if (entry?.authKind === "local") {
45
+ return { error: `provider "${name}" is a local provider and has no credentials` };
46
+ }
47
+ if (provider?.authMode === "forward") {
48
+ return { error: `provider "${name}" uses forward auth and has no switchable credentials` };
49
+ }
50
+ if (provider?.authMode === "key") return { type: "api-key" };
51
+ if (provider && !provider.authMode && (provider.apiKey || (provider.apiKeyPool?.length ?? 0) > 0)) {
52
+ return { type: "api-key" };
53
+ }
54
+ if (isPublicOAuthProvider(name)) return { type: "oauth" };
55
+ if (provider) return { type: "api-key" };
56
+ return { error: `unknown provider "${name}"` };
57
+ }
58
+
59
+ export interface ApiResult {
60
+ /** 0 = network-level failure (proxy unreachable). */
61
+ status: number;
62
+ json: Record<string, unknown>;
63
+ }
64
+
65
+ export async function apiJson(
66
+ deps: AccountDeps,
67
+ baseUrl: string,
68
+ method: "GET" | "PUT" | "POST" | "DELETE",
69
+ path: string,
70
+ body?: unknown,
71
+ ): Promise<ApiResult> {
72
+ const fetchImpl = deps.fetchImpl ?? fetch;
73
+ try {
74
+ const res = await fetchImpl(`${baseUrl}${path}`, {
75
+ method,
76
+ headers: runningProxyUpdateHeaders(),
77
+ body: body === undefined ? undefined : JSON.stringify(body),
78
+ });
79
+ const json = (await res.json().catch(() => ({}))) as Record<string, unknown>;
80
+ return { status: res.status, json };
81
+ } catch {
82
+ return { status: 0, json: {} };
83
+ }
84
+ }
85
+
86
+ export async function resolveBaseUrl(deps: AccountDeps): Promise<string | null> {
87
+ if (deps.baseUrl) return deps.baseUrl;
88
+ const live = await findLiveProxy();
89
+ if (!live) return null;
90
+ return `http://${probeHostname(live.hostname)}:${live.port}`;
91
+ }
92
+
93
+ export function proxyUnreachable(): number {
94
+ console.error("Proxy not reachable. Start it with 'ocx start' or 'ocx ensure'.");
95
+ return 1;
96
+ }
97
+
98
+ export function apiError(json: Record<string, unknown>, fallback: string): number {
99
+ const message = typeof json.error === "string" ? json.error : fallback;
100
+ console.error(`Error: ${message}`);
101
+ return 1;
102
+ }
103
+
104
+ export interface FamilyRows {
105
+ rows: AccountRow[];
106
+ activeId: string | null;
107
+ autoSwitchThreshold?: number;
108
+ /** HTTP status for a completed family read, including failures. */
109
+ status?: number;
110
+ /** Set when the family endpoint returned an error. */
111
+ errorJson?: Record<string, unknown>;
112
+ networkDown?: boolean;
113
+ }
114
+
115
+ export interface CodexQuotaDto {
116
+ weeklyPercent?: number;
117
+ monthlyPercent?: number;
118
+ weeklyResetAt?: number;
119
+ monthlyResetAt?: number;
120
+ }
121
+
122
+ export interface ProviderQuotaWindowDto {
123
+ label: string;
124
+ percent: number;
125
+ resetAt?: number;
126
+ }
127
+
128
+ export interface ProviderQuotaDto extends CodexQuotaDto {
129
+ fiveHourPercent?: number;
130
+ fiveHourResetAt?: number;
131
+ customWindows?: ProviderQuotaWindowDto[];
132
+ updatedAt?: number;
133
+ }
134
+
135
+ export interface ProviderQuotaReportDto {
136
+ provider: string;
137
+ label?: string;
138
+ source?: string;
139
+ quota: ProviderQuotaDto;
140
+ updatedAt?: number;
141
+ reverseEngineered?: boolean;
142
+ }
143
+
144
+ interface CodexAccountDto {
145
+ id: string;
146
+ alias?: string;
147
+ email?: string;
148
+ plan?: string;
149
+ isMain?: boolean;
150
+ needsReauth?: boolean;
151
+ quota?: CodexQuotaDto | null;
152
+ }
153
+
154
+ function projectQuota(quota: CodexQuotaDto | null | undefined): CodexQuotaDto | null {
155
+ if (!quota) return null;
156
+ const projected: CodexQuotaDto = {};
157
+ for (const key of ["weeklyPercent", "monthlyPercent", "weeklyResetAt", "monthlyResetAt"] as const) {
158
+ if (typeof quota[key] === "number" && Number.isFinite(quota[key])) projected[key] = quota[key];
159
+ }
160
+ return projected;
161
+ }
162
+
163
+ export async function fetchCodexRows(
164
+ deps: AccountDeps,
165
+ baseUrl: string,
166
+ forceRefresh = false,
167
+ ): Promise<FamilyRows> {
168
+ const accountsPath = `/api/codex-auth/accounts${forceRefresh ? "?refresh=1" : ""}`;
169
+ const [accountsRes, activeRes] = await Promise.all([
170
+ apiJson(deps, baseUrl, "GET", accountsPath),
171
+ apiJson(deps, baseUrl, "GET", "/api/codex-auth/active"),
172
+ ]);
173
+ if (accountsRes.status !== 0 && accountsRes.status !== 200) {
174
+ return { rows: [], activeId: null, status: accountsRes.status, errorJson: accountsRes.json };
175
+ }
176
+ if (activeRes.status !== 0 && activeRes.status !== 200) {
177
+ return { rows: [], activeId: null, status: activeRes.status, errorJson: activeRes.json };
178
+ }
179
+ if (accountsRes.status === 0 || activeRes.status === 0) {
180
+ return { rows: [], activeId: null, status: 0, networkDown: true };
181
+ }
182
+ const activeId = typeof activeRes.json.activeCodexAccountId === "string"
183
+ ? activeRes.json.activeCodexAccountId
184
+ : null;
185
+ const autoSwitchThreshold = typeof activeRes.json.autoSwitchThreshold === "number"
186
+ ? activeRes.json.autoSwitchThreshold
187
+ : undefined;
188
+ const accounts = Array.isArray(accountsRes.json.accounts) ? accountsRes.json.accounts as CodexAccountDto[] : [];
189
+ const rows = accounts.map(a => ({
190
+ provider: "openai",
191
+ type: "codex" as const,
192
+ id: a.id,
193
+ label: a.alias ?? a.plan ?? a.email,
194
+ email: a.email,
195
+ plan: a.plan,
196
+ active: a.id === activeId,
197
+ needsReauth: a.needsReauth,
198
+ ...(forceRefresh ? { quota: projectQuota(a.quota) } : {}),
199
+ }));
200
+ return { rows, activeId, autoSwitchThreshold, status: 200 };
201
+ }
202
+
203
+ interface OAuthAccountDto {
204
+ id: string;
205
+ alias?: string;
206
+ email?: string;
207
+ active?: boolean;
208
+ needsReauth?: boolean;
209
+ }
210
+
211
+ async function fetchOAuthRows(deps: AccountDeps, baseUrl: string, name: string): Promise<FamilyRows> {
212
+ const res = await apiJson(deps, baseUrl, "GET", `/api/oauth/accounts?provider=${encodeURIComponent(name)}`);
213
+ if (res.status === 0) return { rows: [], activeId: null, status: 0, networkDown: true };
214
+ if (res.status !== 200) return { rows: [], activeId: null, status: res.status, errorJson: res.json };
215
+ const activeId = typeof res.json.activeAccountId === "string" ? res.json.activeAccountId : null;
216
+ const accounts = Array.isArray(res.json.accounts) ? res.json.accounts as OAuthAccountDto[] : [];
217
+ const rows = accounts.map((a, i) => ({
218
+ provider: name,
219
+ type: "oauth" as const,
220
+ id: a.id,
221
+ label: a.alias ?? a.email ?? `Account ${i + 1}`,
222
+ email: a.email,
223
+ active: a.active ?? a.id === activeId,
224
+ needsReauth: a.needsReauth,
225
+ }));
226
+ return { rows, activeId, status: 200 };
227
+ }
228
+
229
+ interface ApiKeyDto {
230
+ id: string;
231
+ label?: string;
232
+ masked?: string;
233
+ active?: boolean;
234
+ }
235
+
236
+ async function fetchKeyRows(deps: AccountDeps, baseUrl: string, name: string): Promise<FamilyRows> {
237
+ const res = await apiJson(deps, baseUrl, "GET", `/api/providers/keys?name=${encodeURIComponent(name)}`);
238
+ if (res.status === 0) return { rows: [], activeId: null, status: 0, networkDown: true };
239
+ if (res.status !== 200) return { rows: [], activeId: null, status: res.status, errorJson: res.json };
240
+ const activeId = typeof res.json.activeId === "string" ? res.json.activeId : null;
241
+ const keys = Array.isArray(res.json.keys) ? res.json.keys as ApiKeyDto[] : [];
242
+ const rows = keys.map(k => ({
243
+ provider: name,
244
+ type: "api-key" as const,
245
+ id: k.id,
246
+ label: k.label ?? k.masked,
247
+ masked: k.masked,
248
+ active: k.active ?? k.id === activeId,
249
+ }));
250
+ return { rows, activeId, status: 200 };
251
+ }
252
+
253
+ export function fetchRows(deps: AccountDeps, baseUrl: string, name: string, type: AccountType): Promise<FamilyRows> {
254
+ if (type === "codex") return fetchCodexRows(deps, baseUrl);
255
+ if (type === "oauth") return fetchOAuthRows(deps, baseUrl, name);
256
+ return fetchKeyRows(deps, baseUrl, name);
257
+ }
258
+
259
+ export async function fetchProviderQuotaReport(
260
+ deps: AccountDeps,
261
+ baseUrl: string,
262
+ name: string,
263
+ ): Promise<{ status: number; report: ProviderQuotaReportDto | null; errorJson?: Record<string, unknown> }> {
264
+ const res = await apiJson(deps, baseUrl, "GET", "/api/provider-quotas?refresh=1");
265
+ if (res.status !== 200) return { status: res.status, report: null, errorJson: res.json };
266
+ const reports = Array.isArray(res.json.reports) ? res.json.reports as ProviderQuotaReportDto[] : [];
267
+ return { status: 200, report: reports.find(report => report?.provider === name) ?? null };
268
+ }
@@ -0,0 +1,223 @@
1
+ import {
2
+ CliUsageError,
3
+ printData,
4
+ readSecretLine,
5
+ rejectArgs,
6
+ runCliAction,
7
+ runtimeRequest,
8
+ takeFlag,
9
+ takeOption,
10
+ takeOptionWithSyntax,
11
+ type CliStdin,
12
+ type RuntimeApiDeps,
13
+ } from "./runtime-api";
14
+
15
+ const USAGE = `Usage:
16
+ ocx account login <provider> [--id <account-id>] [--reauth] [--code -] [--no-wait] [--json]
17
+ ocx account code <provider> [--flow <flow-id>] [--json] (reads the code from stdin)
18
+ ocx account cancel <provider> [--flow <flow-id>] [--json]
19
+ ocx account reset-credits <account-id|main> [--consume --yes] [--json]
20
+
21
+ The redirect URL or authorization code is a short-lived credential. Pipe it in
22
+ rather than passing it as an argument, where it lands in shell history and is
23
+ visible to anyone who can run ps:
24
+ pbpaste | ocx account code <provider> --flow <flow-id>
25
+ ocx account login <provider> --code - (same, for the login flow)`;
26
+
27
+ const CODEX_NAMES = new Set(["openai", "codex", "chatgpt"]);
28
+
29
+ interface LoginStart {
30
+ url?: string;
31
+ flowId?: string;
32
+ instructions?: string;
33
+ deviceCode?: string;
34
+ }
35
+
36
+ /** `-` means "read it from stdin", the documented way to pass a code silently. */
37
+ const STDIN_SENTINEL = "-";
38
+
39
+ const ARGV_WARNING =
40
+ "warning: the authorization code was passed as a command-line argument, so it is now in your shell history and was visible in the process list while this ran. Pipe it on stdin instead, or pass `-` to read from stdin.";
41
+
42
+ /**
43
+ * Resolve the code, preferring stdin.
44
+ *
45
+ * Never logs the value itself — the warning names the exposure, not the
46
+ * credential. What this closes is shell history, `ps`, and this program's own
47
+ * output; a code pasted at an interactive prompt is still visible on the
48
+ * terminal, exactly as it is in the existing interactive login.
49
+ */
50
+ async function resolveCode(
51
+ supplied: { value: string; inline: boolean } | undefined,
52
+ deps: RuntimeApiDeps,
53
+ required: boolean,
54
+ ): Promise<string | undefined> {
55
+ if (supplied && supplied.value !== STDIN_SENTINEL) {
56
+ console.error(ARGV_WARNING);
57
+ return supplied.value;
58
+ }
59
+ if (!supplied && !required) return undefined;
60
+ const input: CliStdin = deps.stdinImpl ?? process.stdin;
61
+ if (input.isTTY) console.error("Paste the redirect URL or authorization code, then press Enter:");
62
+ return await readSecretLine(deps, "authorization code");
63
+ }
64
+
65
+ async function login(argv: string[], deps: RuntimeApiDeps): Promise<void> {
66
+ const args = [...argv];
67
+ const provider = args.shift()?.trim().toLowerCase();
68
+ const wantsJson = takeFlag(args, "--json");
69
+ const noWait = takeFlag(args, "--no-wait");
70
+ const reauth = takeFlag(args, "--reauth");
71
+ const id = takeOption(args, "--id");
72
+ const suppliedCode = takeOptionWithSyntax(args, "--code");
73
+ if (!provider) throw new CliUsageError("provider is required", USAGE);
74
+ rejectArgs(args, USAGE);
75
+ // Only resolve when --code was actually given: a plain `ocx account login`
76
+ // opens the browser flow and polls, and must not block on stdin.
77
+ const code = await resolveCode(suppliedCode, deps, false);
78
+
79
+ if (CODEX_NAMES.has(provider)) {
80
+ const start = await runtimeRequest<LoginStart>("/api/codex-auth/login", {
81
+ method: "POST",
82
+ body: JSON.stringify({ ...(id ? { id } : {}), ...(reauth ? { reauth: true } : {}) }),
83
+ }, deps);
84
+ if (!wantsJson) {
85
+ if (start.url) console.log(`Open this URL to sign in:\n${start.url}`);
86
+ if (start.instructions) console.log(start.instructions);
87
+ if (start.flowId) console.log(`Flow: ${start.flowId}`);
88
+ }
89
+ if (code && start.flowId) {
90
+ await runtimeRequest("/api/codex-auth/login/code", {
91
+ method: "POST",
92
+ body: JSON.stringify({ flowId: start.flowId, input: code }),
93
+ }, deps);
94
+ }
95
+ if (noWait) {
96
+ if (wantsJson) printData(start, true);
97
+ return;
98
+ }
99
+ if (!start.flowId) throw new CliUsageError("login did not return a flow id");
100
+ for (let attempt = 0; attempt < 150; attempt++) {
101
+ await Bun.sleep(2_000);
102
+ const state = await runtimeRequest<Record<string, unknown>>(
103
+ `/api/codex-auth/login-status?flowId=${encodeURIComponent(start.flowId)}${id ? `&accountId=${encodeURIComponent(id)}` : ""}${reauth ? "&reauth=1" : ""}`,
104
+ {}, deps,
105
+ );
106
+ if (state.status === "done") {
107
+ printData(state, wantsJson, [`Logged in${state.email ? ` as ${String(state.email)}` : ""}.`]);
108
+ return;
109
+ }
110
+ if (state.status === "error" || state.status === "expired") {
111
+ throw new CliUsageError(String(state.error ?? `login ${state.status}`));
112
+ }
113
+ }
114
+ throw new CliUsageError("login timed out");
115
+ }
116
+
117
+ if (id && !reauth) throw new CliUsageError("--id is only valid with --reauth for provider OAuth accounts", USAGE);
118
+ const start = await runtimeRequest<LoginStart>("/api/oauth/login", {
119
+ method: "POST",
120
+ body: JSON.stringify({ provider, addAccount: !reauth, ...(reauth && id ? { accountId: id, reauth: true } : {}) }),
121
+ }, deps);
122
+ if (!wantsJson) {
123
+ if (start.url) console.log(`Open this URL to sign in:\n${start.url}`);
124
+ if (start.instructions) console.log(start.instructions);
125
+ if (start.deviceCode) console.log(`Device code: ${start.deviceCode}`);
126
+ }
127
+ if (code) {
128
+ await runtimeRequest("/api/oauth/login/code", {
129
+ method: "POST",
130
+ body: JSON.stringify({ provider, input: code }),
131
+ }, deps);
132
+ }
133
+ if (noWait) {
134
+ if (wantsJson) printData(start, true);
135
+ return;
136
+ }
137
+ for (let attempt = 0; attempt < 100; attempt++) {
138
+ await Bun.sleep(2_000);
139
+ const state = await runtimeRequest<Record<string, unknown>>(`/api/oauth/status?provider=${encodeURIComponent(provider)}`, {}, deps);
140
+ if (state.error) throw new CliUsageError(String(state.error));
141
+ if (state.loggedIn === true) {
142
+ printData(state, wantsJson, [`Logged in to ${provider}.`]);
143
+ return;
144
+ }
145
+ }
146
+ throw new CliUsageError("login timed out");
147
+ }
148
+
149
+ async function code(argv: string[], deps: RuntimeApiDeps): Promise<void> {
150
+ const args = [...argv];
151
+ const provider = args.shift()?.trim().toLowerCase();
152
+ // Flags first. Taking the positional before parsing them made
153
+ // `ocx account code openai --flow f1` read `--flow` as the code and then
154
+ // reject `f1` as an unexpected argument.
155
+ const wantsJson = takeFlag(args, "--json");
156
+ const flowId = takeOption(args, "--flow");
157
+ const suppliedCode = takeOptionWithSyntax(args, "--code");
158
+ // An unknown flag is not a credential. Without this guard `--nope` becomes
159
+ // the positional code and the real complaint ("unexpected argument") is
160
+ // replaced by a confusing one about how the code was passed.
161
+ const positional = args[0]?.startsWith("--") ? undefined : args.shift();
162
+ if (!provider) throw new CliUsageError("provider is required", USAGE);
163
+ // A second positional here is most likely the code, split by an unquoted
164
+ // space or a stray shell expansion. Naming it back would put it on stderr.
165
+ rejectArgs(args, USAGE, { redactValues: true });
166
+ if (suppliedCode && positional !== undefined) {
167
+ throw new CliUsageError("pass the code either positionally or with --code, not both", USAGE);
168
+ }
169
+ const input = await resolveCode(
170
+ suppliedCode ?? (positional === undefined ? undefined : { value: positional, inline: false }),
171
+ deps,
172
+ true,
173
+ );
174
+ if (!input) throw new CliUsageError("provider and redirect/code are required", USAGE);
175
+ const path = CODEX_NAMES.has(provider) ? "/api/codex-auth/login/code" : "/api/oauth/login/code";
176
+ if (CODEX_NAMES.has(provider) && !flowId) throw new CliUsageError("Codex login code requires --flow <flow-id>", USAGE);
177
+ const body = CODEX_NAMES.has(provider) ? { flowId, input } : { provider, input };
178
+ const result = await runtimeRequest(path, { method: "POST", body: JSON.stringify(body) }, deps);
179
+ printData(result, wantsJson, ["Login code submitted."]);
180
+ }
181
+
182
+ async function cancel(argv: string[], deps: RuntimeApiDeps): Promise<void> {
183
+ const args = [...argv];
184
+ const provider = args.shift()?.trim().toLowerCase();
185
+ const wantsJson = takeFlag(args, "--json");
186
+ const flowId = takeOption(args, "--flow");
187
+ if (!provider) throw new CliUsageError("provider is required", USAGE);
188
+ rejectArgs(args, USAGE);
189
+ const codex = CODEX_NAMES.has(provider);
190
+ const result = await runtimeRequest(codex ? "/api/codex-auth/login/cancel" : "/api/oauth/login/cancel", {
191
+ method: "POST",
192
+ body: JSON.stringify(codex ? { flowId } : { provider }),
193
+ }, deps);
194
+ printData(result, wantsJson, [`Cancelled ${provider} login.`]);
195
+ }
196
+
197
+ async function resetCredits(argv: string[], deps: RuntimeApiDeps): Promise<void> {
198
+ const args = [...argv];
199
+ const rawId = args.shift()?.trim();
200
+ const wantsJson = takeFlag(args, "--json");
201
+ const consume = takeFlag(args, "--consume");
202
+ const yes = takeFlag(args, "--yes");
203
+ if (!rawId) throw new CliUsageError("account id is required", USAGE);
204
+ if (consume && !yes) throw new CliUsageError("consuming a reset credit requires --yes", USAGE);
205
+ rejectArgs(args, USAGE);
206
+ const accountId = rawId === "main" ? "__main__" : rawId;
207
+ const result = consume
208
+ ? await runtimeRequest("/api/codex-auth/reset-credits/consume", { method: "POST", body: JSON.stringify({ accountId }) }, deps)
209
+ : await runtimeRequest(`/api/codex-auth/reset-credits?accountId=${encodeURIComponent(accountId)}`, {}, deps);
210
+ printData(result, wantsJson);
211
+ }
212
+
213
+ export async function handleAccountAuthCommand(sub: string, argv: string[], deps: RuntimeApiDeps = {}): Promise<number | null> {
214
+ let action: (() => Promise<void>) | undefined;
215
+ if (sub === "login" || sub === "reauth") action = () => login(sub === "reauth" ? [...argv, "--reauth"] : argv, deps);
216
+ else if (sub === "code") action = () => code(argv, deps);
217
+ else if (sub === "cancel") action = () => cancel(argv, deps);
218
+ else if (sub === "reset-credits") action = () => resetCredits(argv, deps);
219
+ if (!action) return null;
220
+ return runCliAction(action);
221
+ }
222
+
223
+ export const ACCOUNT_AUTH_USAGE = USAGE;