@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,385 @@
1
+ /**
2
+ * Read-only snapshot of each coding client's *effective* on-disk config.
3
+ *
4
+ * Answers "what base URL / model is this agent actually pointed at right now?"
5
+ * without serializing secrets. Writers (inject, CC Switch, export) live elsewhere;
6
+ * this module only reads paths that already exist on the host.
7
+ *
8
+ * Per-client probe failures never fail the whole response — one broken settings
9
+ * file must not blank the Clients page.
10
+ */
11
+ import { homedir } from "node:os";
12
+ import type { OcxConfig } from "../types";
13
+ import { providerBaseHost } from "../codex/inject";
14
+ import { probeHostname } from "../server/proxy-liveness";
15
+ import { probeClaude } from "./probes/claude";
16
+ import { probeCodex } from "./probes/codex";
17
+ import { probePi } from "./probes/pi";
18
+ import { probeGrok } from "./probes/grok";
19
+ import { probeOpencode } from "./probes/opencode";
20
+ import { probeAgy } from "./probes/agy";
21
+ import { readCcSwitchCurrentProfiles, type CcSwitchProfile } from "./probes/cc-switch";
22
+ import { readPaseoProviderCommands, type PaseoProviderCommand } from "./probes/paseo";
23
+
24
+ export type ClientId = "claude" | "codex" | "pi" | "grok" | "opencode" | "agy";
25
+
26
+ export type ClientVerdict = "ocx" | "direct" | "mixed" | "missing" | "unknown";
27
+
28
+ export interface ClientSwitcherInfo {
29
+ name: string | null;
30
+ appType: string;
31
+ }
32
+
33
+ export interface ClientEffectiveStatus {
34
+ id: ClientId;
35
+ label: string;
36
+ present: boolean;
37
+ viaOcx: boolean | null;
38
+ verdict: ClientVerdict;
39
+ baseUrl: string | null;
40
+ model: string | null;
41
+ configPaths: string[];
42
+ launcher: string | null;
43
+ switcher: ClientSwitcherInfo | null;
44
+ notes: string[];
45
+ }
46
+
47
+ export interface ClientsEffectiveStatusResponse {
48
+ generatedAt: number;
49
+ proxy: {
50
+ baseUrl: string;
51
+ running: boolean;
52
+ port: number;
53
+ hostname: string;
54
+ };
55
+ clients: ClientEffectiveStatus[];
56
+ }
57
+
58
+ export interface EffectiveStatusOpts {
59
+ home?: string;
60
+ /** Override live proxy detection (tests). */
61
+ running?: boolean;
62
+ /** Override live bind port when runtime differs from config.port. */
63
+ livePort?: number;
64
+ liveHostname?: string;
65
+ /** Inject probe I/O for unit tests. */
66
+ probes?: {
67
+ claude?: typeof probeClaude;
68
+ codex?: typeof probeCodex;
69
+ pi?: typeof probePi;
70
+ grok?: typeof probeGrok;
71
+ opencode?: typeof probeOpencode;
72
+ agy?: typeof probeAgy;
73
+ ccSwitch?: typeof readCcSwitchCurrentProfiles;
74
+ paseo?: typeof readPaseoProviderCommands;
75
+ };
76
+ }
77
+
78
+ const CLIENT_LABELS: Record<ClientId, string> = {
79
+ claude: "Claude Code",
80
+ codex: "Codex",
81
+ pi: "Pi",
82
+ grok: "Grok Build",
83
+ opencode: "OpenCode",
84
+ agy: "Antigravity (agy)",
85
+ };
86
+
87
+ /** Map client id → CC Switch `app_type` column (when present). */
88
+ const CC_SWITCH_APP: Partial<Record<ClientId, string>> = {
89
+ claude: "claude",
90
+ codex: "codex",
91
+ grok: "grokbuild",
92
+ opencode: "opencode",
93
+ };
94
+
95
+ /** Map client id → Paseo `agents.providers` key. */
96
+ const PASEO_PROVIDER: Partial<Record<ClientId, string>> = {
97
+ pi: "pi",
98
+ grok: "grok",
99
+ agy: "antigravity-acp",
100
+ claude: "claude",
101
+ codex: "codex",
102
+ };
103
+
104
+ export interface ProxyTarget {
105
+ port: number;
106
+ hostname: string;
107
+ /** OpenAI-compatible surface: `http://host:port/v1`. */
108
+ openAiBaseUrl: string;
109
+ /** Claude Code surface: `http://host:port` (no /v1). */
110
+ claudeBaseUrl: string;
111
+ }
112
+
113
+ export function buildProxyTarget(
114
+ config: Pick<OcxConfig, "port" | "hostname">,
115
+ live?: { port?: number; hostname?: string },
116
+ ): ProxyTarget {
117
+ const port = live?.port ?? config.port ?? 10100;
118
+ const hostname = providerBaseHost(live?.hostname ?? config.hostname);
119
+ const host = probeHostname(hostname);
120
+ return {
121
+ port,
122
+ hostname: host,
123
+ openAiBaseUrl: `http://${host}:${port}/v1`,
124
+ claudeBaseUrl: `http://${host}:${port}`,
125
+ };
126
+ }
127
+
128
+ /**
129
+ * Does `candidate` point at this proxy?
130
+ *
131
+ * Compares host + port only. Path differences (`/v1` vs bare) are ignored so
132
+ * Claude's bare base and Codex's `/v1` both count as via-ocx when the port matches.
133
+ */
134
+ export function urlPointsAtProxy(candidate: string | null | undefined, proxy: ProxyTarget): boolean {
135
+ if (!candidate) return false;
136
+ let url: URL;
137
+ try {
138
+ url = new URL(candidate);
139
+ } catch {
140
+ return false;
141
+ }
142
+ const host = url.hostname.toLowerCase();
143
+ const proxyHost = proxy.hostname.replace(/^\[|\]$/g, "").toLowerCase();
144
+ const loopback = new Set(["127.0.0.1", "localhost", "::1"]);
145
+ const hostOk = host === proxyHost
146
+ || (loopback.has(host) && loopback.has(proxyHost));
147
+ if (!hostOk) return false;
148
+ const port = url.port ? Number(url.port) : (url.protocol === "https:" ? 443 : 80);
149
+ return port === proxy.port;
150
+ }
151
+
152
+ export function verdictFromBaseUrl(
153
+ baseUrl: string | null,
154
+ proxy: ProxyTarget,
155
+ extras?: { present?: boolean; mixed?: boolean },
156
+ ): { verdict: ClientVerdict; viaOcx: boolean | null } {
157
+ if (extras?.present === false && !baseUrl) {
158
+ return { verdict: "missing", viaOcx: null };
159
+ }
160
+ if (extras?.mixed) {
161
+ return { verdict: "mixed", viaOcx: baseUrl ? urlPointsAtProxy(baseUrl, proxy) : null };
162
+ }
163
+ if (!baseUrl) {
164
+ return {
165
+ verdict: extras?.present === false ? "missing" : "unknown",
166
+ viaOcx: null,
167
+ };
168
+ }
169
+ if (urlPointsAtProxy(baseUrl, proxy)) {
170
+ return { verdict: "ocx", viaOcx: true };
171
+ }
172
+ return { verdict: "direct", viaOcx: false };
173
+ }
174
+
175
+ function switcherFor(
176
+ id: ClientId,
177
+ profiles: CcSwitchProfile[],
178
+ ): ClientSwitcherInfo | null {
179
+ const appType = CC_SWITCH_APP[id];
180
+ if (!appType) return null;
181
+ const hit = profiles.find(p => p.appType === appType);
182
+ if (!hit) return null;
183
+ return { name: hit.name, appType: hit.appType };
184
+ }
185
+
186
+ function paseoLauncher(
187
+ id: ClientId,
188
+ commands: PaseoProviderCommand[],
189
+ ): string | null {
190
+ const key = PASEO_PROVIDER[id];
191
+ if (!key) return null;
192
+ const hit = commands.find(c => c.provider === key);
193
+ if (!hit?.command?.length) return null;
194
+ return `paseo: ${hit.command.join(" ")}`;
195
+ }
196
+
197
+ async function safeProbeAsync<T>(label: string, run: () => Promise<T>, fallback: T): Promise<T> {
198
+ try {
199
+ return await run();
200
+ } catch {
201
+ void label;
202
+ return fallback;
203
+ }
204
+ }
205
+
206
+ export async function readClientsEffectiveStatus(
207
+ config: Pick<OcxConfig, "port" | "hostname">,
208
+ opts: EffectiveStatusOpts = {},
209
+ ): Promise<ClientsEffectiveStatusResponse> {
210
+ const home = opts.home ?? homedir();
211
+ const proxy = buildProxyTarget(config, {
212
+ port: opts.livePort,
213
+ hostname: opts.liveHostname,
214
+ });
215
+ const running = opts.running ?? false;
216
+
217
+ const claudeFn = opts.probes?.claude ?? probeClaude;
218
+ const codexFn = opts.probes?.codex ?? probeCodex;
219
+ const piFn = opts.probes?.pi ?? probePi;
220
+ const grokFn = opts.probes?.grok ?? probeGrok;
221
+ const opencodeFn = opts.probes?.opencode ?? probeOpencode;
222
+ const agyFn = opts.probes?.agy ?? probeAgy;
223
+ const ccSwitchFn = opts.probes?.ccSwitch ?? readCcSwitchCurrentProfiles;
224
+ const paseoFn = opts.probes?.paseo ?? readPaseoProviderCommands;
225
+
226
+ const emptyProfiles: CcSwitchProfile[] = [];
227
+ const emptyPaseo: PaseoProviderCommand[] = [];
228
+
229
+ const [ccProfiles, paseoCommands, claude, codex, pi, grok, opencode, agy] = await Promise.all([
230
+ safeProbeAsync("cc-switch", () => Promise.resolve(ccSwitchFn({ home })), emptyProfiles),
231
+ safeProbeAsync("paseo", () => Promise.resolve(paseoFn({ home })), emptyPaseo),
232
+ safeProbeAsync("claude", () => Promise.resolve(claudeFn({ home })), {
233
+ present: false, baseUrl: null, model: null, configPaths: [] as string[], notes: ["probe failed"],
234
+ }),
235
+ safeProbeAsync("codex", () => Promise.resolve(codexFn({ home })), {
236
+ present: false, baseUrl: null, model: null, configPaths: [] as string[], notes: ["probe failed"], mixed: false, injected: false,
237
+ }),
238
+ safeProbeAsync("pi", () => Promise.resolve(piFn({ home })), {
239
+ present: false, baseUrl: null, model: null, configPaths: [] as string[], notes: ["probe failed"], binary: null,
240
+ }),
241
+ safeProbeAsync("grok", () => Promise.resolve(grokFn({ home })), {
242
+ present: false, baseUrl: null, model: null, configPaths: [] as string[], notes: ["probe failed"],
243
+ }),
244
+ safeProbeAsync("opencode", () => Promise.resolve(opencodeFn({ home })), {
245
+ present: false, baseUrl: null, model: null, configPaths: [] as string[], notes: ["probe failed"],
246
+ }),
247
+ safeProbeAsync("agy", () => Promise.resolve(agyFn({ home })), {
248
+ present: false, baseUrl: null, model: null, configPaths: [] as string[], notes: ["probe failed"], binary: null,
249
+ }),
250
+ ]);
251
+
252
+ const clients: ClientEffectiveStatus[] = [];
253
+
254
+ // Claude
255
+ {
256
+ const raw = claude;
257
+ const { verdict, viaOcx } = verdictFromBaseUrl(raw.baseUrl, proxy, { present: raw.present });
258
+ clients.push({
259
+ id: "claude",
260
+ label: CLIENT_LABELS.claude,
261
+ present: raw.present,
262
+ viaOcx,
263
+ verdict: raw.present ? verdict : "missing",
264
+ baseUrl: raw.baseUrl,
265
+ model: raw.model,
266
+ configPaths: raw.configPaths,
267
+ launcher: paseoLauncher("claude", paseoCommands) ?? (raw.present ? "claude" : null),
268
+ switcher: switcherFor("claude", ccProfiles),
269
+ notes: raw.notes,
270
+ });
271
+ }
272
+
273
+ // Codex
274
+ {
275
+ const raw = codex;
276
+ const { verdict, viaOcx } = verdictFromBaseUrl(raw.baseUrl, proxy, {
277
+ present: raw.present,
278
+ mixed: raw.mixed,
279
+ });
280
+ const notes = [...raw.notes];
281
+ if (raw.mixed && !notes.some(n => n.includes("model_providers"))) {
282
+ notes.push("Root openai_base_url points at ocx, but other model_providers remain on disk.");
283
+ }
284
+ clients.push({
285
+ id: "codex",
286
+ label: CLIENT_LABELS.codex,
287
+ present: raw.present,
288
+ viaOcx,
289
+ verdict: raw.present ? verdict : "missing",
290
+ baseUrl: raw.baseUrl,
291
+ model: raw.model,
292
+ configPaths: raw.configPaths,
293
+ launcher: paseoLauncher("codex", paseoCommands) ?? (raw.present ? "codex" : null),
294
+ switcher: switcherFor("codex", ccProfiles),
295
+ notes,
296
+ });
297
+ }
298
+
299
+ // Pi
300
+ {
301
+ const raw = pi;
302
+ const { verdict, viaOcx } = verdictFromBaseUrl(raw.baseUrl, proxy, { present: raw.present });
303
+ clients.push({
304
+ id: "pi",
305
+ label: CLIENT_LABELS.pi,
306
+ present: raw.present || Boolean(raw.binary),
307
+ viaOcx,
308
+ verdict: raw.present || raw.binary ? (raw.baseUrl ? verdict : (raw.present ? "unknown" : "missing")) : "missing",
309
+ baseUrl: raw.baseUrl,
310
+ model: raw.model,
311
+ configPaths: raw.configPaths,
312
+ launcher: paseoLauncher("pi", paseoCommands) ?? (raw.binary ? "pi" : null),
313
+ switcher: null,
314
+ notes: raw.notes,
315
+ });
316
+ }
317
+
318
+ // Grok
319
+ {
320
+ const raw = grok;
321
+ const { verdict, viaOcx } = verdictFromBaseUrl(raw.baseUrl, proxy, { present: raw.present });
322
+ clients.push({
323
+ id: "grok",
324
+ label: CLIENT_LABELS.grok,
325
+ present: raw.present,
326
+ viaOcx,
327
+ verdict: raw.present ? verdict : "missing",
328
+ baseUrl: raw.baseUrl,
329
+ model: raw.model,
330
+ configPaths: raw.configPaths,
331
+ launcher: paseoLauncher("grok", paseoCommands) ?? (raw.present ? "grok" : null),
332
+ switcher: switcherFor("grok", ccProfiles),
333
+ notes: raw.notes,
334
+ });
335
+ }
336
+
337
+ // OpenCode
338
+ {
339
+ const raw = opencode;
340
+ const { verdict, viaOcx } = verdictFromBaseUrl(raw.baseUrl, proxy, { present: raw.present });
341
+ clients.push({
342
+ id: "opencode",
343
+ label: CLIENT_LABELS.opencode,
344
+ present: raw.present,
345
+ viaOcx,
346
+ verdict: raw.present ? verdict : "missing",
347
+ baseUrl: raw.baseUrl,
348
+ model: raw.model,
349
+ configPaths: raw.configPaths,
350
+ launcher: raw.present ? "opencode" : null,
351
+ switcher: switcherFor("opencode", ccProfiles),
352
+ notes: raw.notes,
353
+ });
354
+ }
355
+
356
+ // Agy / Antigravity
357
+ {
358
+ const raw = agy;
359
+ const present = raw.present || Boolean(raw.binary);
360
+ clients.push({
361
+ id: "agy",
362
+ label: CLIENT_LABELS.agy,
363
+ present,
364
+ viaOcx: null,
365
+ verdict: present ? (raw.baseUrl ? verdictFromBaseUrl(raw.baseUrl, proxy).verdict : "unknown") : "missing",
366
+ baseUrl: raw.baseUrl,
367
+ model: raw.model,
368
+ configPaths: raw.configPaths,
369
+ launcher: paseoLauncher("agy", paseoCommands) ?? (raw.binary ? raw.binary : null),
370
+ switcher: null,
371
+ notes: raw.notes,
372
+ });
373
+ }
374
+
375
+ return {
376
+ generatedAt: Date.now(),
377
+ proxy: {
378
+ baseUrl: proxy.openAiBaseUrl,
379
+ running,
380
+ port: proxy.port,
381
+ hostname: proxy.hostname,
382
+ },
383
+ clients,
384
+ };
385
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Antigravity / agy best-effort probe.
3
+ *
4
+ * ocx does not own agy config; we only report binary presence, Paseo-facing
5
+ * hints (via aggregator), and any obvious config path if present.
6
+ */
7
+ import { existsSync } from "node:fs";
8
+ import { join } from "node:path";
9
+
10
+ export interface AgyProbeResult {
11
+ present: boolean;
12
+ baseUrl: string | null;
13
+ model: string | null;
14
+ configPaths: string[];
15
+ notes: string[];
16
+ binary: string | null;
17
+ }
18
+
19
+ function resolveBinary(name: string): string | null {
20
+ try {
21
+ // Bun.which is the project standard for PATH lookups.
22
+ const which = (globalThis as { Bun?: { which?: (cmd: string) => string | null } }).Bun?.which;
23
+ if (typeof which === "function") {
24
+ return which(name) ?? null;
25
+ }
26
+ } catch { /* ignore */ }
27
+ return null;
28
+ }
29
+
30
+ export function probeAgy(opts: { home: string } = { home: "" }): AgyProbeResult {
31
+ const notes: string[] = [];
32
+ const binary = resolveBinary("agy") ?? resolveBinary("agy-acp");
33
+ const candidates = [
34
+ join(opts.home, ".gemini", "antigravity"),
35
+ join(opts.home, ".local", "bin", "agy"),
36
+ join(opts.home, ".agy"),
37
+ ];
38
+ const configPaths = candidates.filter(p => existsSync(p));
39
+ const present = Boolean(binary) || configPaths.length > 0;
40
+ if (!present) {
41
+ notes.push("agy binary not on PATH and no known config dir found.");
42
+ } else if (!binary) {
43
+ notes.push("agy binary not on PATH.");
44
+ } else {
45
+ notes.push("Routing is managed by Antigravity/Google; ocx does not inject agy base URL.");
46
+ }
47
+ return {
48
+ present,
49
+ baseUrl: null,
50
+ model: null,
51
+ configPaths,
52
+ notes,
53
+ binary,
54
+ };
55
+ }
@@ -0,0 +1,110 @@
1
+ /**
2
+ * CC Switch current-profile probe (optional).
3
+ *
4
+ * Opens `~/.cc-switch/cc-switch.db` read-only and returns only
5
+ * `app_type` + `name` for `is_current=1` rows. Never reads settings_config.
6
+ *
7
+ * Falls back to settings.json currentProvider* ids → name lookup when the
8
+ * is_current flag is absent for an app (some builds only store ids in settings).
9
+ */
10
+ import { existsSync, readFileSync } from "node:fs";
11
+ import { join } from "node:path";
12
+ import { Database } from "bun:sqlite";
13
+
14
+ export interface CcSwitchProfile {
15
+ appType: string;
16
+ name: string | null;
17
+ id: string | null;
18
+ }
19
+
20
+ export function ccSwitchDbPath(home: string): string {
21
+ return join(home, ".cc-switch", "cc-switch.db");
22
+ }
23
+
24
+ export function ccSwitchSettingsPath(home: string): string {
25
+ return join(home, ".cc-switch", "settings.json");
26
+ }
27
+
28
+ const SETTINGS_CURRENT_KEYS: Record<string, string> = {
29
+ currentProviderClaude: "claude",
30
+ currentProviderCodex: "codex",
31
+ currentProviderGemini: "gemini",
32
+ currentProviderGrokbuild: "grokbuild",
33
+ currentProviderOpencode: "opencode",
34
+ };
35
+
36
+ function isRecord(value: unknown): value is Record<string, unknown> {
37
+ return typeof value === "object" && value !== null && !Array.isArray(value);
38
+ }
39
+
40
+ export function readCcSwitchCurrentProfiles(opts: {
41
+ home: string;
42
+ dbPath?: string;
43
+ settingsPath?: string;
44
+ } = { home: "" }): CcSwitchProfile[] {
45
+ const dbPath = opts.dbPath ?? ccSwitchDbPath(opts.home);
46
+ const settingsPath = opts.settingsPath ?? ccSwitchSettingsPath(opts.home);
47
+ if (!existsSync(dbPath)) return [];
48
+
49
+ let db: Database;
50
+ try {
51
+ // Read-only URI so we never take a write lock against a running CC Switch.
52
+ db = new Database(dbPath, { readonly: true, create: false });
53
+ } catch {
54
+ return [];
55
+ }
56
+
57
+ try {
58
+ const byCurrent = new Map<string, CcSwitchProfile>();
59
+ try {
60
+ const rows = db.query(
61
+ "SELECT id, app_type, name FROM providers WHERE is_current = 1",
62
+ ).all() as Array<{ id: string; app_type: string; name: string }>;
63
+ for (const row of rows) {
64
+ byCurrent.set(row.app_type, {
65
+ appType: row.app_type,
66
+ name: row.name ?? null,
67
+ id: row.id ?? null,
68
+ });
69
+ }
70
+ } catch {
71
+ // Schema may differ; fall through to settings.json ids.
72
+ }
73
+
74
+ // Fill gaps from settings.json currentProvider* → id → name (name only).
75
+ if (existsSync(settingsPath)) {
76
+ try {
77
+ const raw = JSON.parse(readFileSync(settingsPath, "utf8")) as unknown;
78
+ if (isRecord(raw)) {
79
+ for (const [key, appType] of Object.entries(SETTINGS_CURRENT_KEYS)) {
80
+ if (byCurrent.has(appType)) continue;
81
+ const id = raw[key];
82
+ if (typeof id !== "string" || !id) continue;
83
+ try {
84
+ const row = db.query(
85
+ "SELECT id, app_type, name FROM providers WHERE id = ? LIMIT 1",
86
+ ).get(id) as { id: string; app_type: string; name: string } | null;
87
+ if (row) {
88
+ byCurrent.set(appType, {
89
+ appType: row.app_type || appType,
90
+ name: row.name ?? null,
91
+ id: row.id,
92
+ });
93
+ } else {
94
+ byCurrent.set(appType, { appType, name: null, id });
95
+ }
96
+ } catch {
97
+ byCurrent.set(appType, { appType, name: null, id });
98
+ }
99
+ }
100
+ }
101
+ } catch {
102
+ // settings unreadable — keep whatever is_current gave us
103
+ }
104
+ }
105
+
106
+ return [...byCurrent.values()];
107
+ } finally {
108
+ try { db.close(); } catch { /* ignore */ }
109
+ }
110
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Claude Code disk settings probe.
3
+ *
4
+ * Reads only non-secret fields from `~/.claude/settings.json` env:
5
+ * ANTHROPIC_BASE_URL and model selectors. Never returns AUTH_TOKEN or any key.
6
+ */
7
+ import { existsSync, readFileSync } from "node:fs";
8
+ import { join } from "node:path";
9
+
10
+ export interface ClaudeProbeResult {
11
+ present: boolean;
12
+ baseUrl: string | null;
13
+ model: string | null;
14
+ configPaths: string[];
15
+ notes: string[];
16
+ }
17
+
18
+ function isRecord(value: unknown): value is Record<string, unknown> {
19
+ return typeof value === "object" && value !== null && !Array.isArray(value);
20
+ }
21
+
22
+ function stringEnv(env: Record<string, unknown>, key: string): string | null {
23
+ const value = env[key];
24
+ return typeof value === "string" && value.trim() ? value.trim() : null;
25
+ }
26
+
27
+ export function claudeSettingsPath(home: string): string {
28
+ return join(home, ".claude", "settings.json");
29
+ }
30
+
31
+ export function probeClaude(opts: { home: string; settingsPath?: string } = { home: "" }): ClaudeProbeResult {
32
+ const path = opts.settingsPath ?? claudeSettingsPath(opts.home);
33
+ const notes: string[] = [];
34
+ if (!existsSync(path)) {
35
+ return { present: false, baseUrl: null, model: null, configPaths: [path], notes };
36
+ }
37
+
38
+ let raw: string;
39
+ try {
40
+ raw = readFileSync(path, "utf8");
41
+ } catch (error) {
42
+ return {
43
+ present: true,
44
+ baseUrl: null,
45
+ model: null,
46
+ configPaths: [path],
47
+ notes: [`Could not read settings.json: ${error instanceof Error ? error.message : String(error)}`],
48
+ };
49
+ }
50
+
51
+ let parsed: unknown;
52
+ try {
53
+ parsed = JSON.parse(raw);
54
+ } catch {
55
+ return {
56
+ present: true,
57
+ baseUrl: null,
58
+ model: null,
59
+ configPaths: [path],
60
+ notes: ["settings.json is not valid JSON"],
61
+ };
62
+ }
63
+
64
+ if (!isRecord(parsed)) {
65
+ return {
66
+ present: true,
67
+ baseUrl: null,
68
+ model: null,
69
+ configPaths: [path],
70
+ notes: ["settings.json root is not an object"],
71
+ };
72
+ }
73
+
74
+ const env = isRecord(parsed.env) ? parsed.env : {};
75
+ // Only non-secret routing fields. AUTH_TOKEN / API keys are intentionally ignored.
76
+ const baseUrl = stringEnv(env, "ANTHROPIC_BASE_URL");
77
+ const model = stringEnv(env, "ANTHROPIC_MODEL")
78
+ ?? stringEnv(env, "ANTHROPIC_DEFAULT_OPUS_MODEL")
79
+ ?? stringEnv(env, "ANTHROPIC_DEFAULT_SONNET_MODEL")
80
+ ?? (typeof parsed.model === "string" && parsed.model.trim() ? parsed.model.trim() : null);
81
+
82
+ if (!baseUrl) notes.push("No ANTHROPIC_BASE_URL in settings.json env.");
83
+ return {
84
+ present: true,
85
+ baseUrl,
86
+ model,
87
+ configPaths: [path],
88
+ notes,
89
+ };
90
+ }