@wrongstack/cli 0.295.1 → 0.296.3

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 (398) hide show
  1. package/data/providers.json +0 -24
  2. package/dist/acp-A5OSIU2P.js +824 -0
  3. package/dist/acp-A5OSIU2P.js.map +7 -0
  4. package/dist/acp-server-agent.d.ts.map +1 -1
  5. package/dist/arg-parser.d.ts.map +1 -1
  6. package/dist/audit-2ZSFGNIF.js +102 -0
  7. package/dist/audit-2ZSFGNIF.js.map +7 -0
  8. package/dist/auth-CJWQJVRH.js +504 -0
  9. package/dist/auth-CJWQJVRH.js.map +7 -0
  10. package/dist/auth-menu/auth-menu-audit.d.ts +0 -4
  11. package/dist/auth-menu/auth-menu-audit.d.ts.map +1 -1
  12. package/dist/bench-7ZDWSNEF.js +278 -0
  13. package/dist/bench-7ZDWSNEF.js.map +7 -0
  14. package/dist/boot/brain-decision-log.d.ts +7 -5
  15. package/dist/boot/brain-decision-log.d.ts.map +1 -1
  16. package/dist/boot/dispatch-webui.d.ts +5 -8
  17. package/dist/boot/dispatch-webui.d.ts.map +1 -1
  18. package/dist/boot/system-prompt-builder.d.ts +3 -0
  19. package/dist/boot/system-prompt-builder.d.ts.map +1 -1
  20. package/dist/boot/tui-coordinator-callbacks.d.ts +45 -0
  21. package/dist/boot/tui-coordinator-callbacks.d.ts.map +1 -0
  22. package/dist/boot/tui-project-switch.d.ts +2 -3
  23. package/dist/boot/tui-project-switch.d.ts.map +1 -1
  24. package/dist/boot/tui-runtime-state.d.ts +5 -6
  25. package/dist/boot/tui-runtime-state.d.ts.map +1 -1
  26. package/dist/boot/tui-session-resume.d.ts.map +1 -1
  27. package/dist/boot/tui-settings-adapter.d.ts +2 -3
  28. package/dist/boot/tui-settings-adapter.d.ts.map +1 -1
  29. package/dist/boot.d.ts.map +1 -1
  30. package/dist/brain-menu/panel-service.d.ts.map +1 -1
  31. package/dist/chimera-review-task.d.ts +6 -0
  32. package/dist/chimera-review-task.d.ts.map +1 -0
  33. package/dist/chimera-reviewer-policy.d.ts +34 -0
  34. package/dist/chimera-reviewer-policy.d.ts.map +1 -0
  35. package/dist/chronicle-QQEQFSSE.js +256 -0
  36. package/dist/chronicle-QQEQFSSE.js.map +7 -0
  37. package/dist/chunk-5N34EWJR.js +418 -0
  38. package/dist/chunk-5N34EWJR.js.map +7 -0
  39. package/dist/chunk-66T43Q4Y.js +90 -0
  40. package/dist/chunk-66T43Q4Y.js.map +7 -0
  41. package/dist/chunk-7OCVIDC7.js +11 -0
  42. package/dist/chunk-7OCVIDC7.js.map +7 -0
  43. package/dist/chunk-ABXK4VVY.js +84 -0
  44. package/dist/chunk-ABXK4VVY.js.map +7 -0
  45. package/dist/chunk-AXVZMEWG.js +306 -0
  46. package/dist/chunk-AXVZMEWG.js.map +7 -0
  47. package/dist/chunk-AZESNNIC.js +216 -0
  48. package/dist/chunk-AZESNNIC.js.map +7 -0
  49. package/dist/chunk-BGFM3PD7.js +41 -0
  50. package/dist/chunk-BGFM3PD7.js.map +7 -0
  51. package/dist/chunk-BQCAJ7FI.js +425 -0
  52. package/dist/chunk-BQCAJ7FI.js.map +7 -0
  53. package/dist/chunk-DPJZQCVP.js +13 -0
  54. package/dist/chunk-DPJZQCVP.js.map +7 -0
  55. package/dist/chunk-F4PEGAJZ.js +176 -0
  56. package/dist/chunk-F4PEGAJZ.js.map +7 -0
  57. package/dist/chunk-IHBYBYGA.js +443 -0
  58. package/dist/chunk-IHBYBYGA.js.map +7 -0
  59. package/dist/chunk-JBSQ7ONN.js +1023 -0
  60. package/dist/chunk-JBSQ7ONN.js.map +7 -0
  61. package/dist/chunk-JBTFWWKV.js +1325 -0
  62. package/dist/chunk-JBTFWWKV.js.map +7 -0
  63. package/dist/chunk-JUHRI6OV.js +100 -0
  64. package/dist/chunk-JUHRI6OV.js.map +7 -0
  65. package/dist/chunk-OI7GWJDS.js +3303 -0
  66. package/dist/chunk-OI7GWJDS.js.map +7 -0
  67. package/dist/chunk-OP47B3IX.js +142 -0
  68. package/dist/chunk-OP47B3IX.js.map +7 -0
  69. package/dist/chunk-PJHET45A.js +158 -0
  70. package/dist/chunk-PJHET45A.js.map +7 -0
  71. package/dist/chunk-Q5GTM25S.js +17 -0
  72. package/dist/chunk-Q5GTM25S.js.map +7 -0
  73. package/dist/chunk-QD544J2B.js +19 -0
  74. package/dist/chunk-QD544J2B.js.map +7 -0
  75. package/dist/chunk-QUNHS6O3.js +258 -0
  76. package/dist/chunk-QUNHS6O3.js.map +7 -0
  77. package/dist/chunk-TADVX644.js +480 -0
  78. package/dist/chunk-TADVX644.js.map +7 -0
  79. package/dist/chunk-TGDHN4LM.js +81 -0
  80. package/dist/chunk-TGDHN4LM.js.map +7 -0
  81. package/dist/chunk-TYO2OVAD.js +50 -0
  82. package/dist/chunk-TYO2OVAD.js.map +7 -0
  83. package/dist/chunk-VCYZ3WZV.js +14 -0
  84. package/dist/chunk-VCYZ3WZV.js.map +7 -0
  85. package/dist/chunk-VJUEZZYB.js +29 -0
  86. package/dist/chunk-VJUEZZYB.js.map +7 -0
  87. package/dist/chunk-WHOIR577.js +1 -0
  88. package/dist/chunk-WHOIR577.js.map +7 -0
  89. package/dist/chunk-WY7KKOUK.js +460 -0
  90. package/dist/chunk-WY7KKOUK.js.map +7 -0
  91. package/dist/chunk-XJXDOF63.js +28 -0
  92. package/dist/chunk-XJXDOF63.js.map +7 -0
  93. package/dist/chunk-XWEGWYDU.js +560 -0
  94. package/dist/chunk-XWEGWYDU.js.map +7 -0
  95. package/dist/chunk-YWUPHRGB.js +346 -0
  96. package/dist/chunk-YWUPHRGB.js.map +7 -0
  97. package/dist/chunk-Z7YGASQP.js +313 -0
  98. package/dist/chunk-Z7YGASQP.js.map +7 -0
  99. package/dist/cli-entry-main.d.ts +2 -0
  100. package/dist/cli-entry-main.d.ts.map +1 -0
  101. package/dist/cli-entry-point.d.ts +3 -3
  102. package/dist/cli-entry-point.d.ts.map +1 -1
  103. package/dist/cli-main-RTY35OPD.js +29268 -0
  104. package/dist/cli-main-RTY35OPD.js.map +7 -0
  105. package/dist/cli-main-helpers.d.ts +21 -0
  106. package/dist/cli-main-helpers.d.ts.map +1 -0
  107. package/dist/cli-main.d.ts +25 -1
  108. package/dist/cli-main.d.ts.map +1 -1
  109. package/dist/context-limit.d.ts +48 -0
  110. package/dist/context-limit.d.ts.map +1 -1
  111. package/dist/diag-doctor-OQ3AA63O.js +170 -0
  112. package/dist/diag-doctor-OQ3AA63O.js.map +7 -0
  113. package/dist/execute-deps.d.ts +11 -12
  114. package/dist/execute-deps.d.ts.map +1 -1
  115. package/dist/execution-O7I5DYE6.js +4682 -0
  116. package/dist/execution-O7I5DYE6.js.map +7 -0
  117. package/dist/execution-chimera-ask.d.ts +10 -0
  118. package/dist/execution-chimera-ask.d.ts.map +1 -0
  119. package/dist/execution-chimera-cascade.d.ts +15 -0
  120. package/dist/execution-chimera-cascade.d.ts.map +1 -0
  121. package/dist/execution-cleanup.d.ts +23 -0
  122. package/dist/execution-cleanup.d.ts.map +1 -0
  123. package/dist/execution-kanban-dispatch.d.ts +10 -0
  124. package/dist/execution-kanban-dispatch.d.ts.map +1 -0
  125. package/dist/execution-repl-fleet-callbacks.d.ts +11 -0
  126. package/dist/execution-repl-fleet-callbacks.d.ts.map +1 -0
  127. package/dist/execution-storage-observability.d.ts +3 -0
  128. package/dist/execution-storage-observability.d.ts.map +1 -0
  129. package/dist/execution-tui-next-step-callbacks.d.ts +20 -0
  130. package/dist/execution-tui-next-step-callbacks.d.ts.map +1 -0
  131. package/dist/execution.d.ts +2 -139
  132. package/dist/execution.d.ts.map +1 -1
  133. package/dist/export-TPORYVRZ.js +64 -0
  134. package/dist/export-TPORYVRZ.js.map +7 -0
  135. package/dist/fleet/host-acp-runner-cache.d.ts +9 -0
  136. package/dist/fleet/host-acp-runner-cache.d.ts.map +1 -0
  137. package/dist/fleet/host-acp.d.ts +3 -0
  138. package/dist/fleet/host-acp.d.ts.map +1 -0
  139. package/dist/fleet/host-concurrency.d.ts +2 -0
  140. package/dist/fleet/host-concurrency.d.ts.map +1 -0
  141. package/dist/fleet/host-context.d.ts +5 -0
  142. package/dist/fleet/host-context.d.ts.map +1 -0
  143. package/dist/fleet/host-director-builder.d.ts +24 -0
  144. package/dist/fleet/host-director-builder.d.ts.map +1 -0
  145. package/dist/fleet/host-director-event-bridges.d.ts +37 -0
  146. package/dist/fleet/host-director-event-bridges.d.ts.map +1 -0
  147. package/dist/fleet/host-director-services.d.ts +20 -0
  148. package/dist/fleet/host-director-services.d.ts.map +1 -0
  149. package/dist/fleet/host-event-bridge.d.ts +17 -0
  150. package/dist/fleet/host-event-bridge.d.ts.map +1 -0
  151. package/dist/fleet/host-helpers.d.ts +22 -0
  152. package/dist/fleet/host-helpers.d.ts.map +1 -0
  153. package/dist/fleet/host-learning-tracker.d.ts +11 -0
  154. package/dist/fleet/host-learning-tracker.d.ts.map +1 -0
  155. package/dist/fleet/host-learning.d.ts +4 -0
  156. package/dist/fleet/host-learning.d.ts.map +1 -0
  157. package/dist/fleet/host-lifecycle-events.d.ts +4 -0
  158. package/dist/fleet/host-lifecycle-events.d.ts.map +1 -0
  159. package/dist/fleet/host-paths.d.ts +3 -0
  160. package/dist/fleet/host-paths.d.ts.map +1 -0
  161. package/dist/fleet/host-provider.d.ts +22 -0
  162. package/dist/fleet/host-provider.d.ts.map +1 -0
  163. package/dist/fleet/host-session-writer.d.ts +3 -0
  164. package/dist/fleet/host-session-writer.d.ts.map +1 -0
  165. package/dist/fleet/host-shadow-pass.d.ts +21 -0
  166. package/dist/fleet/host-shadow-pass.d.ts.map +1 -0
  167. package/dist/fleet/host-spawn-types.d.ts +25 -0
  168. package/dist/fleet/host-spawn-types.d.ts.map +1 -0
  169. package/dist/fleet/host-status.d.ts +48 -0
  170. package/dist/fleet/host-status.d.ts.map +1 -0
  171. package/dist/fleet/host-subagent-factory.d.ts +16 -0
  172. package/dist/fleet/host-subagent-factory.d.ts.map +1 -0
  173. package/dist/fleet/host-supervisor.d.ts +16 -0
  174. package/dist/fleet/host-supervisor.d.ts.map +1 -0
  175. package/dist/fleet/host-task-result-report.d.ts +11 -0
  176. package/dist/fleet/host-task-result-report.d.ts.map +1 -0
  177. package/dist/fleet/host-types.d.ts +89 -0
  178. package/dist/fleet/host-types.d.ts.map +1 -0
  179. package/dist/fleet/host.d.ts +10 -357
  180. package/dist/fleet/host.d.ts.map +1 -1
  181. package/dist/hq-JUYKUNJX.js +547 -0
  182. package/dist/hq-JUYKUNJX.js.map +7 -0
  183. package/dist/hq-command-controller.d.ts +2 -2
  184. package/dist/hq-command-controller.d.ts.map +1 -1
  185. package/dist/hq-server/auth.d.ts +15 -4
  186. package/dist/hq-server/auth.d.ts.map +1 -1
  187. package/dist/hq-server/project-root.d.ts +5 -0
  188. package/dist/hq-server/project-root.d.ts.map +1 -0
  189. package/dist/hq-server/routes/auth-handlers.d.ts +25 -0
  190. package/dist/hq-server/routes/auth-handlers.d.ts.map +1 -0
  191. package/dist/hq-server/routes/session-handlers.d.ts +8 -0
  192. package/dist/hq-server/routes/session-handlers.d.ts.map +1 -0
  193. package/dist/hq-server/routes.d.ts +8 -6
  194. package/dist/hq-server/routes.d.ts.map +1 -1
  195. package/dist/hq-server/snapshot.d.ts +44 -0
  196. package/dist/hq-server/snapshot.d.ts.map +1 -1
  197. package/dist/hq-server/types.d.ts +22 -4
  198. package/dist/hq-server/types.d.ts.map +1 -1
  199. package/dist/hq-server/utils.d.ts +8 -0
  200. package/dist/hq-server/utils.d.ts.map +1 -1
  201. package/dist/hq-server/ws.d.ts +1 -1
  202. package/dist/hq-server/ws.d.ts.map +1 -1
  203. package/dist/hq-server-6AQ3BXZA.js +39 -0
  204. package/dist/hq-server-6AQ3BXZA.js.map +7 -0
  205. package/dist/hq-server.d.ts +3 -3
  206. package/dist/hq-server.d.ts.map +1 -1
  207. package/dist/hq-ttl-U2FAYFAT.js +8 -0
  208. package/dist/hq-ttl-U2FAYFAT.js.map +7 -0
  209. package/dist/hq-tunnel-RFKGC4HI.js +114 -0
  210. package/dist/hq-tunnel-RFKGC4HI.js.map +7 -0
  211. package/dist/hq-tunnel.d.ts +1 -1
  212. package/dist/hq-tunnel.d.ts.map +1 -1
  213. package/dist/index.d.ts +2 -1
  214. package/dist/index.d.ts.map +1 -1
  215. package/dist/index.js +2503 -53026
  216. package/dist/index.js.map +4 -4
  217. package/dist/init-E2NDDOHI.js +27 -0
  218. package/dist/init-E2NDDOHI.js.map +7 -0
  219. package/dist/kanban-dispatch-route.d.ts +14 -0
  220. package/dist/kanban-dispatch-route.d.ts.map +1 -0
  221. package/dist/kanban-hq-sync.d.ts +2 -0
  222. package/dist/kanban-hq-sync.d.ts.map +1 -1
  223. package/dist/kanban-run-mirror-EKVNGXWW.js +12 -0
  224. package/dist/kanban-run-mirror-EKVNGXWW.js.map +7 -0
  225. package/dist/live-settings-input.d.ts +69 -0
  226. package/dist/live-settings-input.d.ts.map +1 -0
  227. package/dist/mailbox-serve-2UWHEN3M.js +302 -0
  228. package/dist/mailbox-serve-2UWHEN3M.js.map +7 -0
  229. package/dist/mcp-CYLPUEHC.js +413 -0
  230. package/dist/mcp-CYLPUEHC.js.map +7 -0
  231. package/dist/modeldiag-HENYJ4N7.js +1290 -0
  232. package/dist/modeldiag-HENYJ4N7.js.map +7 -0
  233. package/dist/permissions-FNYLZIL4.js +115 -0
  234. package/dist/permissions-FNYLZIL4.js.map +7 -0
  235. package/dist/picker-key-state.d.ts +7 -0
  236. package/dist/picker-key-state.d.ts.map +1 -0
  237. package/dist/picker-model-picker.d.ts +6 -0
  238. package/dist/picker-model-picker.d.ts.map +1 -0
  239. package/dist/picker-ui.d.ts +17 -0
  240. package/dist/picker-ui.d.ts.map +1 -0
  241. package/dist/picker.d.ts +4 -37
  242. package/dist/picker.d.ts.map +1 -1
  243. package/dist/plugin-usage-OU6X6LM3.js +40 -0
  244. package/dist/plugin-usage-OU6X6LM3.js.map +7 -0
  245. package/dist/plugins-4LJ5IUBI.js +24 -0
  246. package/dist/plugins-4LJ5IUBI.js.map +7 -0
  247. package/dist/pre-launch.d.ts +0 -1
  248. package/dist/pre-launch.d.ts.map +1 -1
  249. package/dist/project-BLQ3ALVL.js +76 -0
  250. package/dist/project-BLQ3ALVL.js.map +7 -0
  251. package/dist/project-manifest-GE25PW4S.js +18 -0
  252. package/dist/project-manifest-GE25PW4S.js.map +7 -0
  253. package/dist/project-picker-BOCWHBXP.js +17 -0
  254. package/dist/project-picker-BOCWHBXP.js.map +7 -0
  255. package/dist/projects-XE76NFNZ.js +38 -0
  256. package/dist/projects-XE76NFNZ.js.map +7 -0
  257. package/dist/provider-helpers.d.ts +22 -6
  258. package/dist/provider-helpers.d.ts.map +1 -1
  259. package/dist/providers-models-GDELBHZK.js +589 -0
  260. package/dist/providers-models-GDELBHZK.js.map +7 -0
  261. package/dist/quick-VIO2BEHS.js +10 -0
  262. package/dist/quick-VIO2BEHS.js.map +7 -0
  263. package/dist/repl-auto-proceed.d.ts +19 -0
  264. package/dist/repl-auto-proceed.d.ts.map +1 -0
  265. package/dist/repl-client-registration.d.ts +7 -0
  266. package/dist/repl-client-registration.d.ts.map +1 -0
  267. package/dist/repl-input.d.ts +11 -0
  268. package/dist/repl-input.d.ts.map +1 -0
  269. package/dist/repl-options.d.ts +54 -0
  270. package/dist/repl-options.d.ts.map +1 -0
  271. package/dist/repl-rendering.d.ts +4 -0
  272. package/dist/repl-rendering.d.ts.map +1 -0
  273. package/dist/repl-suggestions.d.ts +21 -0
  274. package/dist/repl-suggestions.d.ts.map +1 -0
  275. package/dist/repl.d.ts +3 -162
  276. package/dist/repl.d.ts.map +1 -1
  277. package/dist/replay-KCTXNMZQ.js +75 -0
  278. package/dist/replay-KCTXNMZQ.js.map +7 -0
  279. package/dist/rewind-EJMVFVLS.js +185 -0
  280. package/dist/rewind-EJMVFVLS.js.map +7 -0
  281. package/dist/sdd-runtime-JG4EEKL6.js +54 -0
  282. package/dist/sdd-runtime-JG4EEKL6.js.map +7 -0
  283. package/dist/services/sdd/project-context.d.ts +1 -1
  284. package/dist/services/sdd/project-context.d.ts.map +1 -1
  285. package/dist/services/sdd/task-manager.d.ts.map +1 -1
  286. package/dist/services/statusline-config.d.ts +1 -1
  287. package/dist/services/statusline-config.d.ts.map +1 -1
  288. package/dist/session-event-wiring.d.ts +2 -2
  289. package/dist/session-event-wiring.d.ts.map +1 -1
  290. package/dist/sessions-config-NLCMK3BY.js +409 -0
  291. package/dist/sessions-config-NLCMK3BY.js.map +7 -0
  292. package/dist/settings-menu.d.ts +3 -18
  293. package/dist/settings-menu.d.ts.map +1 -1
  294. package/dist/simpleui-dist-JIFGUJO7.js +93 -0
  295. package/dist/simpleui-dist-JIFGUJO7.js.map +7 -0
  296. package/dist/slash-commands/brain.d.ts.map +1 -1
  297. package/dist/slash-commands/clear.d.ts.map +1 -1
  298. package/dist/slash-commands/fallback.d.ts.map +1 -1
  299. package/dist/slash-commands/kanban-agent-helpers.d.ts +18 -0
  300. package/dist/slash-commands/kanban-agent-helpers.d.ts.map +1 -0
  301. package/dist/slash-commands/kanban-format.d.ts +16 -0
  302. package/dist/slash-commands/kanban-format.d.ts.map +1 -0
  303. package/dist/slash-commands/kanban-help.d.ts +2 -0
  304. package/dist/slash-commands/kanban-help.d.ts.map +1 -0
  305. package/dist/slash-commands/kanban.d.ts.map +1 -1
  306. package/dist/slash-commands/mailbox-demo.d.ts.map +1 -1
  307. package/dist/slash-commands/mailbox.d.ts +2 -1
  308. package/dist/slash-commands/mailbox.d.ts.map +1 -1
  309. package/dist/slash-commands/memory-compact.d.ts +5 -0
  310. package/dist/slash-commands/memory-compact.d.ts.map +1 -0
  311. package/dist/slash-commands/memory-flags.d.ts +36 -0
  312. package/dist/slash-commands/memory-flags.d.ts.map +1 -0
  313. package/dist/slash-commands/memory-formatters.d.ts +16 -0
  314. package/dist/slash-commands/memory-formatters.d.ts.map +1 -0
  315. package/dist/slash-commands/memory.d.ts.map +1 -1
  316. package/dist/slash-commands/mouse.d.ts +2 -2
  317. package/dist/slash-commands/review.d.ts.map +1 -1
  318. package/dist/slash-commands/sdd-command-helpers.d.ts +23 -0
  319. package/dist/slash-commands/sdd-command-helpers.d.ts.map +1 -0
  320. package/dist/slash-commands/sdd-format.d.ts +32 -0
  321. package/dist/slash-commands/sdd-format.d.ts.map +1 -0
  322. package/dist/slash-commands/sdd.d.ts.map +1 -1
  323. package/dist/slash-commands/settings.d.ts.map +1 -1
  324. package/dist/subcommands/handlers/chronicle.d.ts.map +1 -1
  325. package/dist/subcommands/handlers/mailbox-serve.d.ts.map +1 -1
  326. package/dist/subcommands/handlers/model-smoke-test.d.ts +51 -0
  327. package/dist/subcommands/handlers/model-smoke-test.d.ts.map +1 -0
  328. package/dist/subcommands/handlers/modeldiag-profiles.d.ts +64 -0
  329. package/dist/subcommands/handlers/modeldiag-profiles.d.ts.map +1 -0
  330. package/dist/subcommands/handlers/modeldiag.d.ts.map +1 -1
  331. package/dist/subcommands/handlers/per-subcommand-deep-help-table.d.ts +3 -0
  332. package/dist/subcommands/handlers/per-subcommand-deep-help-table.d.ts.map +1 -0
  333. package/dist/subcommands/handlers/per-subcommand-help-data.d.ts +3 -0
  334. package/dist/subcommands/handlers/per-subcommand-help-data.d.ts.map +1 -0
  335. package/dist/subcommands/handlers/per-subcommand-help-table.d.ts +36 -0
  336. package/dist/subcommands/handlers/per-subcommand-help-table.d.ts.map +1 -0
  337. package/dist/subcommands/handlers/per-subcommand-help-types.d.ts +18 -0
  338. package/dist/subcommands/handlers/per-subcommand-help-types.d.ts.map +1 -0
  339. package/dist/subcommands/handlers/per-subcommand-help.d.ts +2 -80
  340. package/dist/subcommands/handlers/per-subcommand-help.d.ts.map +1 -1
  341. package/dist/subcommands/handlers/rewind.d.ts.map +1 -1
  342. package/dist/subcommands/index.d.ts +7 -0
  343. package/dist/subcommands/index.d.ts.map +1 -1
  344. package/dist/tools-skills-UNKLOB7M.js +29 -0
  345. package/dist/tools-skills-UNKLOB7M.js.map +7 -0
  346. package/dist/update-YC4TYW2Y.js +16 -0
  347. package/dist/update-YC4TYW2Y.js.map +7 -0
  348. package/dist/update-check-UMYTOH7Z.js +18 -0
  349. package/dist/update-check-UMYTOH7Z.js.map +7 -0
  350. package/dist/version-help-DROHZRW6.js +11 -0
  351. package/dist/version-help-DROHZRW6.js.map +7 -0
  352. package/dist/webui-server/connection-handler.d.ts.map +1 -1
  353. package/dist/webui-server/kanban-host-adapter.d.ts +5 -1
  354. package/dist/webui-server/kanban-host-adapter.d.ts.map +1 -1
  355. package/dist/webui-server/listen-helpers.d.ts +18 -0
  356. package/dist/webui-server/listen-helpers.d.ts.map +1 -0
  357. package/dist/webui-server/node-pty-loader.d.ts +6 -0
  358. package/dist/webui-server/node-pty-loader.d.ts.map +1 -0
  359. package/dist/webui-server-ZF5KVGC5.js +1460 -0
  360. package/dist/webui-server-ZF5KVGC5.js.map +7 -0
  361. package/dist/webui-server-options.d.ts +191 -0
  362. package/dist/webui-server-options.d.ts.map +1 -0
  363. package/dist/webui-server.d.ts +2 -178
  364. package/dist/webui-server.d.ts.map +1 -1
  365. package/dist/wiring/brain-and-orchestration.d.ts.map +1 -1
  366. package/dist/wiring/codebase-index.d.ts.map +1 -1
  367. package/dist/wiring/command-host-state.d.ts +4 -4
  368. package/dist/wiring/dep-watcher-bridge.d.ts +23 -0
  369. package/dist/wiring/dep-watcher-bridge.d.ts.map +1 -0
  370. package/dist/wiring/dep-watcher.d.ts.map +1 -1
  371. package/dist/wiring/hq-telemetry.d.ts +2 -2
  372. package/dist/wiring/lifecycle-plugins.d.ts +4 -3
  373. package/dist/wiring/lifecycle-plugins.d.ts.map +1 -1
  374. package/dist/wiring/mailbox-bridge-bootstrap.d.ts +1 -1
  375. package/dist/wiring/management-tools.d.ts +15 -0
  376. package/dist/wiring/management-tools.d.ts.map +1 -0
  377. package/dist/wiring/pipeline.d.ts +2 -1
  378. package/dist/wiring/pipeline.d.ts.map +1 -1
  379. package/dist/wiring/plugins.d.ts +1 -1
  380. package/dist/wiring/plugins.d.ts.map +1 -1
  381. package/dist/wiring/provider-runtime-setup.d.ts +14 -1
  382. package/dist/wiring/provider-runtime-setup.d.ts.map +1 -1
  383. package/dist/wiring/provider.d.ts.map +1 -1
  384. package/dist/wiring/runtime-controller-deps.d.ts +1 -1
  385. package/dist/wiring/runtime-controller-deps.d.ts.map +1 -1
  386. package/dist/wiring/sage.d.ts.map +1 -1
  387. package/dist/wiring/session-registry.d.ts +38 -0
  388. package/dist/wiring/session-registry.d.ts.map +1 -0
  389. package/dist/wiring/session.d.ts +10 -6
  390. package/dist/wiring/session.d.ts.map +1 -1
  391. package/dist/wiring/tools.d.ts.map +1 -1
  392. package/package.json +22 -22
  393. package/dist/cli-recovery-prompt.d.ts +0 -19
  394. package/dist/cli-recovery-prompt.d.ts.map +0 -1
  395. package/dist/first-run-auth-gate.d.ts +0 -20
  396. package/dist/first-run-auth-gate.d.ts.map +0 -1
  397. package/dist/pre-launch/indexing-question.d.ts +0 -28
  398. package/dist/pre-launch/indexing-question.d.ts.map +0 -1
@@ -0,0 +1,480 @@
1
+ import {
2
+ mutateConfigProviders,
3
+ normalizeKeys,
4
+ nowIso,
5
+ writeKeysBack
6
+ } from "./chunk-PJHET45A.js";
7
+
8
+ // src/auth-menu/openai-codex-oauth.ts
9
+ import { createHash, randomBytes } from "node:crypto";
10
+
11
+ // src/auth-menu/loopback-server.ts
12
+ import { spawn } from "node:child_process";
13
+ import { createServer } from "node:http";
14
+ var ESCAPE_HTML = {
15
+ "&": "&",
16
+ "<": "&lt;",
17
+ ">": "&gt;",
18
+ '"': "&quot;",
19
+ "'": "&#39;"
20
+ };
21
+ function escapeHtml(s) {
22
+ return s.replace(/[&<>"']/g, (ch) => ESCAPE_HTML[ch] ?? ch);
23
+ }
24
+ function callbackHtml(ok, message) {
25
+ const heading = ok ? "Authentication successful" : "Authentication failed";
26
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8"/><title>${escapeHtml(heading)}</title><style>body{margin:0;min-height:100vh;display:flex;align-items:center;justify-content:center;background:#09090b;color:#fafafa;font-family:ui-sans-serif,system-ui,sans-serif;text-align:center}h1{font-size:26px;margin:0 0 8px}p{color:#a1a1aa}</style></head><body><main><h1>${escapeHtml(heading)}</h1><p>${escapeHtml(message)}</p></main></body></html>`;
27
+ }
28
+ function openBrowser(url) {
29
+ try {
30
+ const platform = process.platform;
31
+ const { command, args } = platform === "win32" ? { command: "cmd", args: ["/c", "start", "", url] } : platform === "darwin" ? { command: "open", args: [url] } : { command: "xdg-open", args: [url] };
32
+ const child = spawn(command, args, { stdio: "ignore", windowsHide: true });
33
+ child.on("error", () => {
34
+ });
35
+ child.unref();
36
+ } catch {
37
+ }
38
+ }
39
+ function startLoopbackServer(expectedState, ports, signal, callbackPath = "/auth/callback", redirectHost = "127.0.0.1") {
40
+ let resolveCode = () => {
41
+ };
42
+ const codePromise = new Promise((resolve) => {
43
+ let settled = false;
44
+ resolveCode = (v) => {
45
+ if (settled) return;
46
+ settled = true;
47
+ resolve(v);
48
+ };
49
+ });
50
+ const server = createServer((req, res) => {
51
+ let url;
52
+ try {
53
+ url = new URL(req.url ?? "", `http://${redirectHost}`);
54
+ } catch {
55
+ res.statusCode = 400;
56
+ res.end();
57
+ return;
58
+ }
59
+ if (url.pathname !== callbackPath) {
60
+ res.statusCode = 404;
61
+ res.setHeader("content-type", "text/html; charset=utf-8");
62
+ res.end(callbackHtml(false, "Callback route not found."));
63
+ return;
64
+ }
65
+ res.setHeader("content-type", "text/html; charset=utf-8");
66
+ const err = url.searchParams.get("error");
67
+ if (err) {
68
+ res.statusCode = 400;
69
+ res.end(callbackHtml(false, `Authorization error: ${err}`));
70
+ resolveCode(null);
71
+ return;
72
+ }
73
+ const state = url.searchParams.get("state") ?? "";
74
+ if (state !== expectedState) {
75
+ res.statusCode = 400;
76
+ res.end(callbackHtml(false, "State mismatch \u2014 please restart the login."));
77
+ resolveCode(null);
78
+ return;
79
+ }
80
+ const code = url.searchParams.get("code");
81
+ if (!code) {
82
+ res.statusCode = 400;
83
+ res.end(callbackHtml(false, "Missing authorization code."));
84
+ return;
85
+ }
86
+ res.statusCode = 200;
87
+ res.end(callbackHtml(true, "You can close this window and return to the terminal."));
88
+ resolveCode({ code, state });
89
+ });
90
+ const onAbort = () => {
91
+ resolveCode(null);
92
+ try {
93
+ server.close();
94
+ } catch {
95
+ }
96
+ };
97
+ if (signal) {
98
+ if (signal.aborted) onAbort();
99
+ else signal.addEventListener("abort", onAbort, { once: true });
100
+ }
101
+ return new Promise((resolve) => {
102
+ const close = () => {
103
+ resolveCode(null);
104
+ try {
105
+ server.close();
106
+ } catch {
107
+ }
108
+ };
109
+ const fail = () => {
110
+ resolveCode(null);
111
+ resolve({
112
+ bound: false,
113
+ port: ports[0] ?? 1455,
114
+ waitForCode: () => Promise.resolve(null),
115
+ close
116
+ });
117
+ };
118
+ let index = 0;
119
+ server.on("error", () => {
120
+ index += 1;
121
+ const nextPort = ports[index];
122
+ if (nextPort !== void 0) {
123
+ server.listen(nextPort, redirectHost);
124
+ return;
125
+ }
126
+ fail();
127
+ });
128
+ server.on("listening", () => {
129
+ const address = server.address();
130
+ const port = typeof address === "object" && address ? address.port : ports[index] ?? 1455;
131
+ resolve({
132
+ bound: true,
133
+ port,
134
+ waitForCode: () => codePromise,
135
+ close
136
+ });
137
+ });
138
+ server.listen(ports[0] ?? 1455, redirectHost);
139
+ });
140
+ }
141
+
142
+ // src/auth-menu/openai-codex-oauth.ts
143
+ import { CODEX_MODELS } from "@wrongstack/core/models";
144
+ import { color } from "@wrongstack/core/utils";
145
+ import { FetchError, ParseError } from "@wrongstack/core/types";
146
+ var CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
147
+ var AUTH_BASE_URL = "https://auth.openai.com";
148
+ var AUTHORIZE_URL = `${AUTH_BASE_URL}/oauth/authorize`;
149
+ var TOKEN_URL = `${AUTH_BASE_URL}/oauth/token`;
150
+ var REDIRECT_PORT = 1455;
151
+ var FALLBACK_REDIRECT_PORT = 1457;
152
+ var REDIRECT_HOST = "127.0.0.1";
153
+ var REDIRECT_PATH = "/auth/callback";
154
+ var SCOPE = "openid profile email offline_access api.connectors.read api.connectors.invoke";
155
+ var JWT_CLAIM_PATH = "https://api.openai.com/auth";
156
+ var ORIGINATOR = "wrongstack";
157
+ var CODEX_PROVIDER_ID = "openai-codex";
158
+ var CODEX_BASE_URL = "https://chatgpt.com/backend-api";
159
+ function startLoopbackServer2(expectedState, signal) {
160
+ return startLoopbackServer(
161
+ expectedState,
162
+ [REDIRECT_PORT, FALLBACK_REDIRECT_PORT],
163
+ signal,
164
+ REDIRECT_PATH,
165
+ REDIRECT_HOST
166
+ );
167
+ }
168
+ function base64url(buf) {
169
+ return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
170
+ }
171
+ function generatePkce() {
172
+ const verifier = base64url(randomBytes(64));
173
+ const challenge = base64url(createHash("sha256").update(verifier).digest());
174
+ return { verifier, challenge };
175
+ }
176
+ function createState() {
177
+ return base64url(randomBytes(32));
178
+ }
179
+ function redirectUri(port) {
180
+ return `http://localhost:${port}${REDIRECT_PATH}`;
181
+ }
182
+ function buildAuthorizeUrl(challenge, state, port = REDIRECT_PORT) {
183
+ const url = new URL(AUTHORIZE_URL);
184
+ url.searchParams.set("response_type", "code");
185
+ url.searchParams.set("client_id", CLIENT_ID);
186
+ url.searchParams.set("redirect_uri", redirectUri(port));
187
+ url.searchParams.set("scope", SCOPE);
188
+ url.searchParams.set("code_challenge", challenge);
189
+ url.searchParams.set("code_challenge_method", "S256");
190
+ url.searchParams.set("state", state);
191
+ url.searchParams.set("id_token_add_organizations", "true");
192
+ url.searchParams.set("codex_cli_simplified_flow", "true");
193
+ url.searchParams.set("originator", ORIGINATOR);
194
+ return url.toString();
195
+ }
196
+ function decodeJwtPayload(token) {
197
+ try {
198
+ const parts = token.split(".");
199
+ if (parts.length !== 3) return null;
200
+ const json = Buffer.from(parts[1], "base64url").toString("utf8");
201
+ return JSON.parse(json);
202
+ } catch {
203
+ return null;
204
+ }
205
+ }
206
+ function extractAccountId(token) {
207
+ const payload = decodeJwtPayload(token);
208
+ const auth = payload?.[JWT_CLAIM_PATH];
209
+ const id = auth?.chatgpt_account_id;
210
+ return typeof id === "string" && id.length > 0 ? id : null;
211
+ }
212
+ var FALLBACK_CODEX_MODELS = CODEX_MODELS.map(
213
+ (m) => ({ id: m.id, name: m.name })
214
+ );
215
+ var CODEX_CATALOG_FAMILIES = /* @__PURE__ */ new Set(["gpt-codex", "gpt-codex-spark"]);
216
+ function fallbackCodexModelIds() {
217
+ return FALLBACK_CODEX_MODELS.map((m) => m.id);
218
+ }
219
+ function fallbackCodexProviderModels() {
220
+ return FALLBACK_CODEX_MODELS.map((m) => ({ id: m.id, name: m.name }));
221
+ }
222
+ function filterCurrentCodexModelIds(ids) {
223
+ const available = new Set(ids);
224
+ return FALLBACK_CODEX_MODELS.map((m) => m.id).filter((id) => available.has(id));
225
+ }
226
+ function isCodexCatalogModel(model) {
227
+ return typeof model.family === "string" && CODEX_CATALOG_FAMILIES.has(model.family);
228
+ }
229
+ async function resolveCodexModels(modelsRegistry, accessToken, baseUrl, signal) {
230
+ const token = typeof accessToken === "string" ? accessToken : await accessToken;
231
+ const live = filterCurrentCodexModelIds(await fetchCodexModels(token, baseUrl, signal));
232
+ if (live.length > 0) return live;
233
+ try {
234
+ const openaiProvider = await modelsRegistry.getProvider("openai");
235
+ if (openaiProvider) {
236
+ const catalog = openaiProvider.models.filter(isCodexCatalogModel).map((m) => m.id).filter((id) => typeof id === "string" && id.length > 0);
237
+ const currentCatalog = filterCurrentCodexModelIds(catalog);
238
+ if (currentCatalog.length > 0) return currentCatalog;
239
+ }
240
+ } catch {
241
+ }
242
+ return fallbackCodexModelIds();
243
+ }
244
+ async function fetchCodexModels(accessToken, baseUrl, signal) {
245
+ const url = `${(baseUrl ?? CODEX_BASE_URL).replace(/\/+$/, "")}/models`;
246
+ try {
247
+ const res = await fetch(url, {
248
+ headers: {
249
+ accept: "application/json",
250
+ authorization: `Bearer ${accessToken}`,
251
+ originator: "wrongstack",
252
+ "OpenAI-Beta": "responses=experimental"
253
+ },
254
+ signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(8e3)]) : AbortSignal.timeout(8e3)
255
+ });
256
+ if (!res.ok) return [];
257
+ const json = await res.json();
258
+ if (!json) return [];
259
+ const rawList = "data" in json && Array.isArray(json.data) ? json.data : "models" in json && Array.isArray(json.models) ? json.models : [];
260
+ const ids = [];
261
+ for (const entry of rawList) {
262
+ if (!entry || typeof entry !== "object") continue;
263
+ const id = entry.id;
264
+ if (typeof id === "string" && id.length > 0) ids.push(id);
265
+ }
266
+ return ids;
267
+ } catch {
268
+ return [];
269
+ }
270
+ }
271
+ async function readTokens(res, op, url) {
272
+ if (!res.ok) {
273
+ const text = await res.text().catch(() => "");
274
+ throw new FetchError({
275
+ message: `Codex token ${op} failed (${res.status}): ${text || res.statusText}`,
276
+ status: res.status,
277
+ context: { provider: "openai-codex", op, url }
278
+ });
279
+ }
280
+ const json = await res.json();
281
+ if (!json?.access_token || !json.refresh_token || typeof json.expires_in !== "number") {
282
+ throw new ParseError({
283
+ message: `Codex token ${op} response missing fields`,
284
+ source: "openai-codex-token-response",
285
+ context: { op }
286
+ });
287
+ }
288
+ return {
289
+ access: json.access_token,
290
+ refresh: json.refresh_token,
291
+ expires: Date.now() + json.expires_in * 1e3,
292
+ idToken: json.id_token
293
+ };
294
+ }
295
+ async function exchangeAuthorizationCode(code, verifier, signal, port = REDIRECT_PORT) {
296
+ const res = await fetch(TOKEN_URL, {
297
+ method: "POST",
298
+ headers: { "content-type": "application/x-www-form-urlencoded" },
299
+ body: new URLSearchParams({
300
+ grant_type: "authorization_code",
301
+ client_id: CLIENT_ID,
302
+ code,
303
+ code_verifier: verifier,
304
+ redirect_uri: redirectUri(port)
305
+ }).toString(),
306
+ signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(3e4)]) : AbortSignal.timeout(3e4)
307
+ });
308
+ return readTokens(res, "exchange", TOKEN_URL);
309
+ }
310
+ function parseAuthorizationInput(input) {
311
+ const value = input.trim();
312
+ if (!value) return {};
313
+ try {
314
+ const url = new URL(value);
315
+ return {
316
+ code: url.searchParams.get("code") ?? void 0,
317
+ state: url.searchParams.get("state") ?? void 0
318
+ };
319
+ } catch {
320
+ }
321
+ if (value.includes("code=")) {
322
+ const params = new URLSearchParams(value);
323
+ return {
324
+ code: params.get("code") ?? void 0,
325
+ state: params.get("state") ?? void 0
326
+ };
327
+ }
328
+ return { code: value };
329
+ }
330
+ async function runCodexOAuthLogin(deps, opts = {}) {
331
+ const providerId = opts.providerId ?? CODEX_PROVIDER_ID;
332
+ const pkce = generatePkce();
333
+ const state = createState();
334
+ const ac = new AbortController();
335
+ let sigintCount = 0;
336
+ const onSig = () => {
337
+ sigintCount += 1;
338
+ if (sigintCount === 1) {
339
+ deps.renderer.write(color.dim("\n Cancelling sign-in\u2026 (press Ctrl+C again to force-quit)\n"));
340
+ ac.abort();
341
+ } else {
342
+ process.exit(130);
343
+ }
344
+ };
345
+ const external = opts.signal;
346
+ if (external) {
347
+ if (external.aborted) ac.abort();
348
+ else external.addEventListener("abort", () => ac.abort(), { once: true });
349
+ } else {
350
+ process.on("SIGINT", onSig);
351
+ }
352
+ const server = await startLoopbackServer2(state, ac.signal);
353
+ const authorizeUrl = buildAuthorizeUrl(pkce.challenge, state, server.port);
354
+ const osc8 = (url) => `\x1B]8;;${url}\x1B\\${url}\x1B]8;;\x1B\\`;
355
+ deps.renderer.write(
356
+ color.bold(`
357
+ Sign in with ChatGPT \u2014 ${color.cyan(providerId)}
358
+ `) + color.dim(" Uses your ChatGPT Plus/Pro/Team subscription (not an API key).\n") + color.amber(" \u26A0 Using a subscription outside the official Codex client may violate\n") + color.amber(" OpenAI\u2019s Terms \u2014 your account could be rate-limited or banned.\n") + color.dim(" Sanctioned programmatic use = an API key: ") + color.bold("wstack auth openai") + color.dim("\n\n") + color.bold(` ${"\u2500".repeat(56)}
359
+ `) + color.bold(" Open this URL in your browser to sign in:\n") + color.cyan(` ${osc8(authorizeUrl)}
360
+ `) + color.bold(` ${"\u2500".repeat(56)}
361
+
362
+ `)
363
+ );
364
+ if (server.bound) {
365
+ openBrowser(authorizeUrl);
366
+ deps.renderer.write(
367
+ color.dim(" A browser window should open. Waiting for you to finish signing in...\n") + color.dim(` (Listening on ${redirectUri(server.port)} \u2014 press Ctrl+C to cancel.)
368
+ `)
369
+ );
370
+ } else {
371
+ deps.renderer.write(
372
+ color.amber(" \u26A0 Could not start the local callback listener (ports 1455 and 1457 in use).\n") + color.dim(" After signing in, copy the full redirect URL from your browser\n") + color.dim(" (it starts with http://localhost:1455/auth/callback or :1457) and paste it below.\n")
373
+ );
374
+ }
375
+ let code;
376
+ try {
377
+ if (server.bound) {
378
+ const callback = await server.waitForCode();
379
+ if (ac.signal.aborted) {
380
+ deps.renderer.write(color.dim(" Cancelled.\n"));
381
+ return 1;
382
+ }
383
+ if (callback) code = callback.code;
384
+ }
385
+ if (!code) {
386
+ const input = (await deps.reader.readLine(
387
+ `
388
+ ${color.amber("?")} Paste the redirect URL or code ${color.dim("(or q to cancel)")}: `
389
+ )).trim();
390
+ if (input.toLowerCase() === "q" || input === "") {
391
+ deps.renderer.write(color.dim(" Cancelled.\n"));
392
+ return 1;
393
+ }
394
+ const parsed = parseAuthorizationInput(input);
395
+ if (parsed.state && parsed.state !== state) {
396
+ deps.renderer.writeError(" State mismatch \u2014 please restart the login flow.");
397
+ return 1;
398
+ }
399
+ code = parsed.code;
400
+ }
401
+ if (!code) {
402
+ deps.renderer.writeError(" No authorization code received.");
403
+ return 1;
404
+ }
405
+ deps.renderer.write(color.dim("\n Exchanging authorization code for tokens...\n"));
406
+ const tokens = await exchangeAuthorizationCode(code, pkce.verifier, ac.signal, server.port);
407
+ const accountId = extractAccountId(tokens.access) ?? (tokens.idToken ? extractAccountId(tokens.idToken) : null);
408
+ if (!accountId) {
409
+ deps.renderer.writeError(
410
+ " Signed in, but the token has no ChatGPT account id.\n This account may not have Codex/ChatGPT subscription access."
411
+ );
412
+ return 1;
413
+ }
414
+ deps.renderer.write(color.dim(" Fetching available models...\n"));
415
+ const models = await resolveCodexModels(
416
+ deps.modelsRegistry,
417
+ tokens.access,
418
+ CODEX_BASE_URL,
419
+ ac.signal
420
+ );
421
+ const saved = await saveCodexTokens(deps, providerId, tokens, accountId, models);
422
+ if (!saved) return 1;
423
+ const modelHint = models[0] ?? "gpt-5.5";
424
+ deps.renderer.write(color.green("\n \u2713 Signed in with ChatGPT!\n"));
425
+ deps.renderer.writeInfo(
426
+ ` Saved as provider ${color.bold(providerId)}${models.length > 0 ? ` (${models.length} models)` : ""}.
427
+ Use: ${color.bold(`wstack --provider ${providerId} --model ${modelHint}`)} "<task>"
428
+ ` + color.dim(" Tokens refresh automatically before they expire.\n")
429
+ );
430
+ return 0;
431
+ } catch (err) {
432
+ const msg = err instanceof DOMException && err.name === "AbortError" ? "Login cancelled." : err.message;
433
+ deps.renderer.writeError(` Login failed: ${msg}`);
434
+ return 1;
435
+ } finally {
436
+ server.close();
437
+ if (!external) process.off("SIGINT", onSig);
438
+ }
439
+ }
440
+ async function saveCodexTokens(deps, providerId, tokens, accountId, models) {
441
+ const entry = {
442
+ label: "oauth-default",
443
+ apiKey: tokens.access,
444
+ createdAt: nowIso(),
445
+ authMethod: "oauth",
446
+ expiresAt: new Date(tokens.expires).toISOString(),
447
+ refreshToken: tokens.refresh,
448
+ tokenType: "bearer",
449
+ scope: SCOPE,
450
+ accountId
451
+ };
452
+ try {
453
+ await mutateConfigProviders(deps.profileConfigPath, deps.vault, (all) => {
454
+ const existing = all[providerId];
455
+ const p = existing ? { ...existing } : { type: providerId };
456
+ p.family = "openai-codex";
457
+ if (!p.baseUrl) p.baseUrl = CODEX_BASE_URL;
458
+ p.models = [...models];
459
+ const keys = normalizeKeys(p).filter((k) => k.label !== entry.label);
460
+ keys.push(entry);
461
+ writeKeysBack(p, keys);
462
+ p.activeKey = entry.label;
463
+ all[providerId] = p;
464
+ }, deps.profileConfigPath);
465
+ return true;
466
+ } catch (err) {
467
+ deps.renderer.writeError(` Failed to save tokens: ${err.message}`);
468
+ return false;
469
+ }
470
+ }
471
+
472
+ export {
473
+ openBrowser,
474
+ startLoopbackServer,
475
+ fallbackCodexProviderModels,
476
+ filterCurrentCodexModelIds,
477
+ isCodexCatalogModel,
478
+ runCodexOAuthLogin
479
+ };
480
+ //# sourceMappingURL=chunk-TADVX644.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/auth-menu/openai-codex-oauth.ts", "../src/auth-menu/loopback-server.ts"],
4
+ "sourcesContent": ["/**\n * OpenAI Codex \u2014 \"Sign in with ChatGPT\" OAuth (Authorization Code + PKCE).\n *\n * This is the ONLY login flow that actually works for using a ChatGPT\n * Plus/Pro/Team subscription programmatically. It mirrors the real Codex CLI:\n *\n * 1. Generate a PKCE verifier/challenge (S256) + CSRF state.\n * 2. Start a loopback HTTP server on http://localhost:1455/auth/callback.\n * 3. Open the browser to https://auth.openai.com/oauth/authorize.\n * 4. The browser redirects back to the loopback with `?code=...&state=...`.\n * 5. Exchange the code at https://auth.openai.com/oauth/token for an\n * access_token + refresh_token (and an id_token carrying the account id).\n * 6. Store the tokens in the vault as an `oauth` ProviderApiKey.\n *\n * The resulting access token is a JWT whose `https://api.openai.com/auth`\n * claim contains `chatgpt_account_id`. Requests then go to the ChatGPT\n * backend (`chatgpt.com/backend-api/codex/responses`) \u2014 NOT api.openai.com \u2014\n * which is what the `openai-codex` wire family handles. The standard\n * API-key flow (`family: openai` \u2192 api.openai.com/chat/completions) is left\n * completely untouched.\n *\n * NOTE: a ChatGPT web *session cookie* is NOT an API token. The old\n * device-code + cookie-scraping attempts could never produce a usable\n * credential; this module replaces them.\n */\n\nimport { createHash, randomBytes } from 'node:crypto';\nimport {\n type LoopbackServer,\n openBrowser,\n startLoopbackServer as startSharedLoopbackServer,\n} from './loopback-server.js';\nimport { CODEX_MODELS } from '@wrongstack/core/models';\nimport { color } from '@wrongstack/core/utils';\nimport { FetchError, ParseError, type ModelsRegistry, type ProviderApiKey, type ProviderConfig } from '@wrongstack/core/types';\nimport {\n mutateConfigProviders,\n normalizeKeys,\n nowIso,\n writeKeysBack,\n} from '../provider-config-utils.js';\nimport type { AuthMenuDeps } from './types.js';\n\n// \u2500\u2500 Codex OAuth constants (verified against the real Codex CLI) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst CLIENT_ID = 'app_EMoamEEZ73f0CkXaXp7hrann';\nconst AUTH_BASE_URL = 'https://auth.openai.com';\nconst AUTHORIZE_URL = `${AUTH_BASE_URL}/oauth/authorize`;\nconst TOKEN_URL = `${AUTH_BASE_URL}/oauth/token`;\nconst REDIRECT_PORT = 1455;\nconst FALLBACK_REDIRECT_PORT = 1457;\nconst REDIRECT_HOST = '127.0.0.1';\nconst REDIRECT_PATH = '/auth/callback';\nconst SCOPE = 'openid profile email offline_access api.connectors.read api.connectors.invoke';\n/** JWT claim that carries the ChatGPT account id. */\nconst JWT_CLAIM_PATH = 'https://api.openai.com/auth';\n/** Telemetry/branding tag sent to authorize + as a request header. Free-form. */\nconst ORIGINATOR = 'wrongstack';\n/** Canonical provider id under which ChatGPT-login credentials are stored. */\nexport const CODEX_PROVIDER_ID = 'openai-codex';\n/** Default ChatGPT backend base. The wire family appends `/codex/responses`. */\nexport const CODEX_BASE_URL = 'https://chatgpt.com/backend-api';\n\n/**\n * Codex-specific compatibility wrapper retained for callers that imported the\n * helper from this module before the shared OAuth loopback server extraction.\n */\nexport function startLoopbackServer(\n expectedState: string,\n signal?: AbortSignal,\n): Promise<LoopbackServer> {\n return startSharedLoopbackServer(\n expectedState,\n [REDIRECT_PORT, FALLBACK_REDIRECT_PORT],\n signal,\n REDIRECT_PATH,\n REDIRECT_HOST,\n );\n}\n\n/**\n * Fallback model list used when BOTH the live backend AND the models.dev\n * catalog are unreachable. This should never happen in practice \u2014 the\n * catalog is cached locally and the only scenario where both fail is a\n * fresh install with no network on first login.\n *\n * The primary resolution order in `resolveCodexModels()` is:\n * 1. Live backend (`GET /models`)\n * 2. models.dev catalog (`family: gpt-codex*` under `openai` provider)\n * 3. This inline fallback\n */\n// \u2500\u2500 Token shapes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface CodexTokens {\n access: string;\n refresh: string;\n /** Absolute expiry in epoch milliseconds. */\n expires: number;\n idToken?: string | undefined;\n}\n\ninterface TokenEndpointResponse {\n access_token?: string;\n refresh_token?: string;\n expires_in?: number;\n id_token?: string;\n}\n\n// \u2500\u2500 PKCE \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction base64url(buf: Buffer): string {\n return buf.toString('base64').replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\nexport interface Pkce {\n verifier: string;\n challenge: string;\n}\n\n/** Generate a PKCE verifier + S256 challenge. */\nexport function generatePkce(): Pkce {\n const verifier = base64url(randomBytes(64));\n const challenge = base64url(createHash('sha256').update(verifier).digest());\n return { verifier, challenge };\n}\n\nfunction createState(): string {\n return base64url(randomBytes(32));\n}\n\nfunction redirectUri(port: number): string {\n return `http://localhost:${port}${REDIRECT_PATH}`;\n}\n\n/** Build the full authorize URL with all Codex-required query params. */\nexport function buildAuthorizeUrl(challenge: string, state: string, port = REDIRECT_PORT): string {\n const url = new URL(AUTHORIZE_URL);\n url.searchParams.set('response_type', 'code');\n url.searchParams.set('client_id', CLIENT_ID);\n url.searchParams.set('redirect_uri', redirectUri(port));\n url.searchParams.set('scope', SCOPE);\n url.searchParams.set('code_challenge', challenge);\n url.searchParams.set('code_challenge_method', 'S256');\n url.searchParams.set('state', state);\n url.searchParams.set('id_token_add_organizations', 'true');\n url.searchParams.set('codex_cli_simplified_flow', 'true');\n url.searchParams.set('originator', ORIGINATOR);\n return url.toString();\n}\n\n// \u2500\u2500 JWT account-id extraction \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ninterface JwtAuthClaim {\n chatgpt_account_id?: string;\n}\n\nfunction decodeJwtPayload(token: string): Record<string, unknown> | null {\n try {\n const parts = token.split('.');\n if (parts.length !== 3) return null;\n const json = Buffer.from(parts[1]!, 'base64url').toString('utf8');\n return JSON.parse(json) as Record<string, unknown>;\n } catch {\n return null;\n }\n}\n\n/**\n * Extract `chatgpt_account_id` from an access/id token JWT. Returns null when\n * the token isn't a JWT or lacks the claim (the caller decides whether that\n * is fatal \u2014 for login it is; for a cached display value it isn't).\n */\nexport function extractAccountId(token: string): string | null {\n const payload = decodeJwtPayload(token);\n const auth = payload?.[JWT_CLAIM_PATH] as JwtAuthClaim | undefined;\n const id = auth?.chatgpt_account_id;\n return typeof id === 'string' && id.length > 0 ? id : null;\n}\n\n// \u2500\u2500 Model discovery \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Recommended Codex models for ChatGPT sign-in. Live backend discovery\n * wins; these values are only used when the backend/catalog cannot\n * provide a list. Derived from `CODEX_MODELS` in core (the single source of\n * truth for Codex id/name/description) \u2014 used both by OAuth login\n * (resolveCodexModels \u2192 tier 3) and by the provider-boot synthesis\n * path (wiring/provider.ts when the catalog is unavailable).\n */\nexport const FALLBACK_CODEX_MODELS: ReadonlyArray<{ id: string; name: string }> = CODEX_MODELS.map(\n (m) => ({ id: m.id, name: m.name }),\n);\n\n/** Families in the models.dev catalog that indicate Codex / Responses API compatibility. */\nexport const CODEX_CATALOG_FAMILIES = new Set(['gpt-codex', 'gpt-codex-spark']);\n\nexport function fallbackCodexModelIds(): string[] {\n return FALLBACK_CODEX_MODELS.map((m) => m.id);\n}\n\nexport function fallbackCodexProviderModels(): Array<{ id: string; name: string }> {\n return FALLBACK_CODEX_MODELS.map((m) => ({ id: m.id, name: m.name }));\n}\n\n/**\n * Filter a list of available model ids down to the ones that are still\n * current for ChatGPT sign-in (i.e. present in FALLBACK_CODEX_MODELS).\n * Used by both OAuth login (to drop deprecated ids from the live /models\n * response) and by the provider-boot synthesis (to pick current ids out\n * of the models.dev catalog).\n */\nexport function filterCurrentCodexModelIds(ids: Iterable<string>): string[] {\n const available = new Set(ids);\n return FALLBACK_CODEX_MODELS.map((m) => m.id).filter((id) => available.has(id));\n}\n\nexport function isCodexCatalogModel(model: { family?: string | undefined }): boolean {\n return typeof model.family === 'string' && CODEX_CATALOG_FAMILIES.has(model.family);\n}\n\n/**\n * Resolve the list of available Codex models using a 3-tier fallback chain:\n *\n * 1. **Live backend** \u2014 `fetchCodexModels()` hits `GET <baseUrl>/models`\n * and keeps only current Codex model ids.\n * 2. **models.dev catalog** \u2014 queries the `openai` provider in the cached\n * models.dev registry and picks models whose `family` is `gpt-codex` or\n * `gpt-codex-spark` and whose ids are current for ChatGPT sign-in.\n * 3. **Inline fallback** \u2014 a minimal list of documented mainstream models\n * (`gpt-5.6-sol`, `gpt-5.5`, `gpt-5.6-terra`, `gpt-5.6-luna`,\n * `gpt-5.4`, `gpt-5.4-mini`, `gpt-5.3-codex-spark`).\n *\n * The live/catalog tiers gate account availability, while the shared current\n * model list filters out deprecated ChatGPT sign-in ids before config is saved.\n */\nexport async function resolveCodexModels(\n modelsRegistry: ModelsRegistry,\n accessToken: Promise<string> | string,\n baseUrl?: string | undefined,\n signal?: AbortSignal,\n): Promise<string[]> {\n // Tier 1 \u2014 live backend\n const token = typeof accessToken === 'string' ? accessToken : await accessToken;\n const live = filterCurrentCodexModelIds(await fetchCodexModels(token, baseUrl, signal));\n if (live.length > 0) return live;\n\n // Tier 2 \u2014 models.dev catalog\n try {\n const openaiProvider = await modelsRegistry.getProvider('openai');\n if (openaiProvider) {\n const catalog = openaiProvider.models\n .filter(isCodexCatalogModel)\n .map((m) => m.id)\n .filter((id): id is string => typeof id === 'string' && id.length > 0);\n const currentCatalog = filterCurrentCodexModelIds(catalog);\n if (currentCatalog.length > 0) return currentCatalog;\n }\n } catch {\n // catalog unavailable \u2014 fall through to tier 3\n }\n\n // Tier 3 \u2014 inline fallback\n return fallbackCodexModelIds();\n}\n\n/**\n * Fetch the account's available Codex model ids live from the ChatGPT backend.\n *\n * Hits `GET <baseUrl>/models` \u2014 the standard OpenAI-compatible model listing\n * endpoint, which the Codex backend may expose. Best-effort: returns an empty\n * array on any failure so login still succeeds with the hardcoded fallback.\n */\nexport async function fetchCodexModels(\n accessToken: string,\n baseUrl?: string | undefined,\n signal?: AbortSignal,\n): Promise<string[]> {\n const url = `${(baseUrl ?? CODEX_BASE_URL).replace(/\\/+$/, '')}/models`;\n try {\n const res = await fetch(url, {\n headers: {\n accept: 'application/json',\n authorization: `Bearer ${accessToken}`,\n originator: 'wrongstack',\n 'OpenAI-Beta': 'responses=experimental',\n },\n signal: signal\n ? AbortSignal.any([signal, AbortSignal.timeout(8_000)])\n : AbortSignal.timeout(8_000),\n });\n if (!res.ok) return [];\n const json = (await res.json()) as\n | { data?: Array<{ id?: string }> }\n | { models?: Array<{ id?: string }> }\n | null;\n if (!json) return [];\n // Standard OpenAI-compatible: { data: [{ id: \"gpt-...\", ... }] }\n const rawList: unknown[] =\n 'data' in json && Array.isArray(json.data)\n ? (json.data as unknown[])\n : 'models' in json && Array.isArray(json.models)\n ? (json.models as unknown[])\n : [];\n const ids: string[] = [];\n for (const entry of rawList) {\n if (!entry || typeof entry !== 'object') continue;\n const id = (entry as Record<string, unknown>).id;\n if (typeof id === 'string' && id.length > 0) ids.push(id);\n }\n return ids;\n } catch {\n return [];\n }\n}\n\n// \u2500\u2500 Token endpoint calls \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nasync function readTokens(res: Response, op: string, url: string): Promise<CodexTokens> {\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n throw new FetchError({\n message: `Codex token ${op} failed (${res.status}): ${text || res.statusText}`,\n status: res.status,\n context: { provider: 'openai-codex', op, url },\n });\n }\n const json = (await res.json()) as TokenEndpointResponse | null;\n if (!json?.access_token || !json.refresh_token || typeof json.expires_in !== 'number') {\n throw new ParseError({\n message: `Codex token ${op} response missing fields`,\n source: 'openai-codex-token-response',\n context: { op },\n });\n }\n return {\n access: json.access_token,\n refresh: json.refresh_token,\n expires: Date.now() + json.expires_in * 1000,\n idToken: json.id_token,\n };\n}\n\n/** Exchange an authorization code (+ PKCE verifier) for tokens. */\nexport async function exchangeAuthorizationCode(\n code: string,\n verifier: string,\n signal?: AbortSignal,\n port = REDIRECT_PORT,\n): Promise<CodexTokens> {\n const res = await fetch(TOKEN_URL, {\n method: 'POST',\n headers: { 'content-type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n client_id: CLIENT_ID,\n code,\n code_verifier: verifier,\n redirect_uri: redirectUri(port),\n }).toString(),\n signal: signal\n ? AbortSignal.any([signal, AbortSignal.timeout(30_000)])\n : AbortSignal.timeout(30_000),\n });\n return readTokens(res, 'exchange', TOKEN_URL);\n}\n\n/**\n * Refresh an expired access token using the stored refresh token. Exported for\n * the `openai-codex` provider, which calls it transparently before requests.\n */\nexport async function refreshCodexToken(\n refreshToken: string,\n signal?: AbortSignal,\n): Promise<CodexTokens> {\n const res = await fetch(TOKEN_URL, {\n method: 'POST',\n headers: { 'content-type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n client_id: CLIENT_ID,\n refresh_token: refreshToken,\n }).toString(),\n signal: signal\n ? AbortSignal.any([signal, AbortSignal.timeout(30_000)])\n : AbortSignal.timeout(30_000),\n });\n return readTokens(res, 'refresh', TOKEN_URL);\n}\n\n// \u2500\u2500 Loopback callback server \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Loopback server, browser opener, and callback HTML are now in\n// `./loopback-server.js` \u2014 imported at the top of this file.\n\n// \u2500\u2500 Manual paste fallback \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** Parse a pasted authorization code or full redirect URL into { code, state }. */\nexport function parseAuthorizationInput(input: string): {\n code?: string | undefined;\n state?: string | undefined;\n} {\n const value = input.trim();\n if (!value) return {};\n try {\n const url = new URL(value);\n return {\n code: url.searchParams.get('code') ?? undefined,\n state: url.searchParams.get('state') ?? undefined,\n };\n } catch {\n /* not a URL */\n }\n if (value.includes('code=')) {\n const params = new URLSearchParams(value);\n return {\n code: params.get('code') ?? undefined,\n state: params.get('state') ?? undefined,\n };\n }\n return { code: value };\n}\n\n// \u2500\u2500 Main login flow \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface CodexLoginOptions {\n /** Storage provider id. Defaults to the canonical `openai-codex`. */\n providerId?: string;\n /**\n * External cancellation signal (e.g. the TUI auth panel's Esc). When\n * provided, the flow does NOT install its own SIGINT handler \u2014 the\n * caller owns cancellation, and a raw `process.exit` from inside a\n * mounted Ink tree would corrupt the terminal.\n */\n signal?: AbortSignal | undefined;\n}\n\n/**\n * Run the \"Sign in with ChatGPT\" flow and persist the resulting OAuth tokens.\n * Returns 0 on success, 1 on cancel/error.\n */\nexport async function runCodexOAuthLogin(\n deps: AuthMenuDeps,\n opts: CodexLoginOptions = {},\n): Promise<number> {\n const providerId = opts.providerId ?? CODEX_PROVIDER_ID;\n const pkce = generatePkce();\n const state = createState();\n\n const ac = new AbortController();\n // First Ctrl+C cancels cleanly (aborts the controller, which unblocks the\n // loopback wait below); a second Ctrl+C is a hard escape hatch in case\n // anything downstream is still wedged. Skipped entirely when the caller\n // supplied an external signal (TUI) \u2014 the caller owns cancellation then.\n let sigintCount = 0;\n const onSig = () => {\n sigintCount += 1;\n if (sigintCount === 1) {\n deps.renderer.write(color.dim('\\n Cancelling sign-in\u2026 (press Ctrl+C again to force-quit)\\n'));\n ac.abort();\n } else {\n process.exit(130);\n }\n };\n const external = opts.signal;\n if (external) {\n if (external.aborted) ac.abort();\n else external.addEventListener('abort', () => ac.abort(), { once: true });\n } else {\n process.on('SIGINT', onSig);\n }\n\n const server = await startLoopbackServer(state, ac.signal);\n const authorizeUrl = buildAuthorizeUrl(pkce.challenge, state, server.port);\n // Wrap URL in OSC 8 terminal hyperlink sequences \u2014 makes it a clickable\n // link in modern terminals (Windows Terminal, iTerm2, Kitty, WezTerm, etc.).\n // The hyperlink is region-based: clicking any wrapped segment opens the URL.\n const osc8 = (url: string) => `\\x1b]8;;${url}\\x1b\\\\${url}\\x1b]8;;\\x1b\\\\`;\n\n deps.renderer.write(\n color.bold(`\\n Sign in with ChatGPT \u2014 ${color.cyan(providerId)}\\n`) +\n color.dim(' Uses your ChatGPT Plus/Pro/Team subscription (not an API key).\\n') +\n color.amber(' \u26A0 Using a subscription outside the official Codex client may violate\\n') +\n color.amber(' OpenAI\u2019s Terms \u2014 your account could be rate-limited or banned.\\n') +\n color.dim(' Sanctioned programmatic use = an API key: ') +\n color.bold('wstack auth openai') +\n color.dim('\\n\\n') +\n color.bold(` ${'\u2500'.repeat(56)}\\n`) +\n color.bold(' Open this URL in your browser to sign in:\\n') +\n color.cyan(` ${osc8(authorizeUrl)}\\n`) +\n color.bold(` ${'\u2500'.repeat(56)}\\n\\n`),\n );\n\n if (server.bound) {\n openBrowser(authorizeUrl);\n deps.renderer.write(\n color.dim(' A browser window should open. Waiting for you to finish signing in...\\n') +\n color.dim(` (Listening on ${redirectUri(server.port)} \u2014 press Ctrl+C to cancel.)\\n`),\n );\n } else {\n deps.renderer.write(\n color.amber(' \u26A0 Could not start the local callback listener (ports 1455 and 1457 in use).\\n') +\n color.dim(' After signing in, copy the full redirect URL from your browser\\n') +\n color.dim(' (it starts with http://localhost:1455/auth/callback or :1457) and paste it below.\\n'),\n );\n }\n\n let code: string | undefined;\n try {\n if (server.bound) {\n const callback = await server.waitForCode();\n // Ctrl+C while waiting on the loopback \u2192 cancel now rather than dropping\n // into the manual-paste prompt (which would block again).\n if (ac.signal.aborted) {\n deps.renderer.write(color.dim(' Cancelled.\\n'));\n return 1;\n }\n if (callback) code = callback.code;\n }\n\n // Manual paste fallback (port busy, or browser couldn't redirect back).\n if (!code) {\n const input = (\n await deps.reader.readLine(\n `\\n ${color.amber('?')} Paste the redirect URL or code ${color.dim('(or q to cancel)')}: `,\n )\n ).trim();\n if (input.toLowerCase() === 'q' || input === '') {\n deps.renderer.write(color.dim(' Cancelled.\\n'));\n return 1;\n }\n const parsed = parseAuthorizationInput(input);\n if (parsed.state && parsed.state !== state) {\n deps.renderer.writeError(' State mismatch \u2014 please restart the login flow.');\n return 1;\n }\n code = parsed.code;\n }\n\n if (!code) {\n deps.renderer.writeError(' No authorization code received.');\n return 1;\n }\n\n deps.renderer.write(color.dim('\\n Exchanging authorization code for tokens...\\n'));\n const tokens = await exchangeAuthorizationCode(code, pkce.verifier, ac.signal, server.port);\n const accountId = extractAccountId(tokens.access) ?? (tokens.idToken ? extractAccountId(tokens.idToken) : null);\n if (!accountId) {\n deps.renderer.writeError(\n ' Signed in, but the token has no ChatGPT account id.\\n' +\n ' This account may not have Codex/ChatGPT subscription access.',\n );\n return 1;\n }\n\n // Fetch available models from the Codex backend (best-effort).\n deps.renderer.write(color.dim(' Fetching available models...\\n'));\n const models = await resolveCodexModels(\n deps.modelsRegistry,\n tokens.access,\n CODEX_BASE_URL,\n ac.signal,\n );\n\n const saved = await saveCodexTokens(deps, providerId, tokens, accountId, models);\n if (!saved) return 1;\n\n const modelHint = models[0] ?? 'gpt-5.5';\n deps.renderer.write(color.green('\\n \u2713 Signed in with ChatGPT!\\n'));\n deps.renderer.writeInfo(\n ` Saved as provider ${color.bold(providerId)}${models.length > 0 ? ` (${models.length} models)` : ''}.\\n` +\n ` Use: ${color.bold(`wstack --provider ${providerId} --model ${modelHint}`)} \"<task>\"\\n` +\n color.dim(' Tokens refresh automatically before they expire.\\n'),\n );\n return 0;\n } catch (err) {\n const msg =\n err instanceof DOMException && err.name === 'AbortError'\n ? 'Login cancelled.'\n : (err as Error).message;\n deps.renderer.writeError(` Login failed: ${msg}`);\n return 1;\n } finally {\n server.close();\n if (!external) process.off('SIGINT', onSig);\n }\n}\n\n// \u2500\u2500 Persistence \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nasync function saveCodexTokens(\n deps: AuthMenuDeps,\n providerId: string,\n tokens: CodexTokens,\n accountId: string,\n models: string[],\n): Promise<boolean> {\n const entry: ProviderApiKey = {\n label: 'oauth-default',\n apiKey: tokens.access,\n createdAt: nowIso(),\n authMethod: 'oauth',\n expiresAt: new Date(tokens.expires).toISOString(),\n refreshToken: tokens.refresh,\n tokenType: 'bearer',\n scope: SCOPE,\n accountId,\n };\n\n try {\n await mutateConfigProviders(deps.profileConfigPath, deps.vault, (all) => {\n const existing = all[providerId];\n const p: ProviderConfig = existing ? { ...existing } : { type: providerId };\n p.family = 'openai-codex';\n if (!p.baseUrl) p.baseUrl = CODEX_BASE_URL;\n // The caller populates `models` from the live backend or the fallback\n // constant. Always overwrite \u2014 the backend is authoritative.\n p.models = [...models];\n\n const keys = normalizeKeys(p).filter((k) => k.label !== entry.label);\n keys.push(entry);\n writeKeysBack(p, keys);\n p.activeKey = entry.label;\n all[providerId] = p;\n }, deps.profileConfigPath);\n return true;\n } catch (err) {\n deps.renderer.writeError(` Failed to save tokens: ${(err as Error).message}`);\n return false;\n }\n}\n", "/**\n * Shared loopback OAuth server \u2014 HTTP callback listener for OAuth flows.\n *\n * Both the Codex (\"Sign in with ChatGPT\") and Anthropic (\"Sign in with Claude\")\n * OAuth flows start a local HTTP server on a loopback port to receive the\n * authorization code redirect from the provider's browser flow. Before this\n * module existed, each flow duplicated:\n *\n * - `escapeHtml` / `callbackHtml` \u2014 render success/error HTML pages\n * - `openBrowser` \u2014 best-effort platform browser opener\n * - `startLoopbackServer` \u2014 HTTP server with port fallback, abort handling,\n * state validation, and callback URL parsing\n *\n * Usage:\n * ```ts\n * import { startLoopbackServer, callbackHtml, openBrowser } from './loopback-server.js';\n *\n * const server = await startLoopbackServer(state, [1455, 1457], signal);\n * const code = await server.waitForCode();\n * ```\n *\n * @module auth-menu/loopback-server\n */\n\nimport { spawn } from 'node:child_process';\nimport { createServer, type Server } from 'node:http';\n\n// \u2500\u2500 HTML rendering \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst ESCAPE_HTML: Record<string, string> = {\n '&': '&amp;',\n '<': '&lt;',\n '>': '&gt;',\n '\"': '&quot;',\n \"'\": '&#39;',\n};\n\nfunction escapeHtml(s: string): string {\n return s.replace(/[&<>\"']/g, (ch) => ESCAPE_HTML[ch] ?? ch);\n}\n\n/**\n * Render a simple dark-themed HTML response for the browser callback page.\n * Shared by all loopback-based OAuth flows so the success/error page styling\n * is consistent and the HTML generation isn't duplicated.\n */\nexport function callbackHtml(ok: boolean, message: string): string {\n const heading = ok ? 'Authentication successful' : 'Authentication failed';\n return (\n `<!doctype html><html lang=\"en\"><head><meta charset=\"utf-8\"/>` +\n `<title>${escapeHtml(heading)}</title><style>body{margin:0;min-height:100vh;display:flex;` +\n `align-items:center;justify-content:center;background:#09090b;color:#fafafa;` +\n `font-family:ui-sans-serif,system-ui,sans-serif;text-align:center}` +\n `h1{font-size:26px;margin:0 0 8px}p{color:#a1a1aa}</style></head><body><main>` +\n `<h1>${escapeHtml(heading)}</h1><p>${escapeHtml(message)}</p></main></body></html>`\n );\n}\n\n// \u2500\u2500 Platform browser opener (best-effort) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Open a URL in the user's default browser. Best-effort \u2014 never throws.\n *\n * - Windows: `cmd /c start \"\" <url>`\n * - macOS: `open <url>`\n * - Linux: `xdg-open <url>`\n */\nexport function openBrowser(url: string): void {\n try {\n const platform = process.platform;\n const { command, args } =\n platform === 'win32'\n ? { command: 'cmd', args: ['/c', 'start', '', url] }\n : platform === 'darwin'\n ? { command: 'open', args: [url] }\n : { command: 'xdg-open', args: [url] };\n const child = spawn(command, args, { stdio: 'ignore', windowsHide: true });\n child.on('error', () => {});\n child.unref();\n } catch {\n // Best-effort \u2014 the URL is always printed in the terminal.\n }\n}\n\n// \u2500\u2500 Loopback callback server \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** Result of waiting for the OAuth callback. */\nexport interface LoopbackCallback {\n /** The authorization code from the provider. */\n code: string;\n /** The OAuth state parameter from the callback (validated against expected). */\n state: string;\n}\n\nexport interface LoopbackServer {\n /**\n * Wait for the browser to redirect back with the authorization code.\n * Resolves with the code and state, or `null` if cancelled/failed.\n */\n waitForCode(): Promise<LoopbackCallback | null>;\n /** Close the HTTP server and release the port. */\n close(): void;\n /** True when the server bound to a registered callback port. */\n readonly bound: boolean;\n /** The actual port the server is listening on (matches the authorize URL redirect_uri). */\n readonly port: number;\n}\n\n/**\n * Start a loopback HTTP server for OAuth authorization code callback.\n *\n * The server listens on the first available port from `ports`. If all ports\n * are busy, `bound` is `false` and the caller should fall back to a manual\n * paste prompt.\n *\n * When `signal` aborts (user pressed Ctrl+C or TUI Esc), the pending\n * `waitForCode()` promise resolves to `null` and the server is torn down\n * so the login flow can return promptly.\n *\n * @param expectedState - The OAuth `state` value to validate against the callback\n * @param ports - Ordered list of TCP ports to try (first available wins)\n * @param signal - Optional external cancellation signal\n * @param callbackPath - The URL path the provider redirects to (default `/auth/callback`)\n * @param redirectHost - The hostname to listen on (default `127.0.0.1`)\n */\nexport function startLoopbackServer(\n expectedState: string,\n ports: readonly number[],\n signal?: AbortSignal,\n callbackPath = '/auth/callback',\n redirectHost = '127.0.0.1',\n): Promise<LoopbackServer> {\n let resolveCode: (v: LoopbackCallback | null) => void = () => {};\n const codePromise = new Promise<LoopbackCallback | null>((resolve) => {\n let settled = false;\n resolveCode = (v) => {\n if (settled) return;\n settled = true;\n resolve(v);\n };\n });\n\n const server: Server = createServer((req, res) => {\n let url: URL;\n try {\n url = new URL(req.url ?? '', `http://${redirectHost}`);\n } catch {\n res.statusCode = 400;\n res.end();\n return;\n }\n if (url.pathname !== callbackPath) {\n res.statusCode = 404;\n res.setHeader('content-type', 'text/html; charset=utf-8');\n res.end(callbackHtml(false, 'Callback route not found.'));\n return;\n }\n res.setHeader('content-type', 'text/html; charset=utf-8');\n const err = url.searchParams.get('error');\n if (err) {\n res.statusCode = 400;\n res.end(callbackHtml(false, `Authorization error: ${err}`));\n resolveCode(null);\n return;\n }\n const state = url.searchParams.get('state') ?? '';\n if (state !== expectedState) {\n res.statusCode = 400;\n res.end(callbackHtml(false, 'State mismatch \u2014 please restart the login.'));\n resolveCode(null);\n return;\n }\n const code = url.searchParams.get('code');\n if (!code) {\n res.statusCode = 400;\n res.end(callbackHtml(false, 'Missing authorization code.'));\n return;\n }\n res.statusCode = 200;\n res.end(callbackHtml(true, 'You can close this window and return to the terminal.'));\n resolveCode({ code, state });\n });\n\n // Abort (Ctrl+C / TUI Esc) \u2192 unblock the pending wait and close the server.\n const onAbort = (): void => {\n resolveCode(null);\n try {\n server.close();\n } catch {\n /* ignore */\n }\n };\n if (signal) {\n if (signal.aborted) onAbort();\n else signal.addEventListener('abort', onAbort, { once: true });\n }\n\n return new Promise<LoopbackServer>((resolve) => {\n const close = (): void => {\n resolveCode(null);\n try {\n server.close();\n } catch {\n /* ignore */\n }\n };\n\n const fail = (): void => {\n resolveCode(null);\n resolve({\n bound: false,\n port: ports[0] ?? 1455,\n waitForCode: () => Promise.resolve(null),\n close,\n });\n };\n\n let index = 0;\n server.on('error', () => {\n index += 1;\n const nextPort = ports[index];\n if (nextPort !== undefined) {\n server.listen(nextPort, redirectHost);\n return;\n }\n fail();\n });\n\n server.on('listening', () => {\n const address = server.address();\n const port = typeof address === 'object' && address ? address.port : ports[index] ?? 1455;\n resolve({\n bound: true,\n port,\n waitForCode: () => codePromise,\n close,\n });\n });\n\n server.listen(ports[0] ?? 1455, redirectHost);\n });\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["startLoopbackServer", "startLoopbackServer"]
7
+ }
@@ -0,0 +1,81 @@
1
+
2
+ // src/boot/auto-discover-providers.ts
3
+ import * as fs from "node:fs/promises";
4
+ import * as path from "node:path";
5
+ import { COMPATIBLE_PRESETS, discoverOpenAICompatibleModels } from "@wrongstack/providers";
6
+ function resolveKey(cfg) {
7
+ if (Array.isArray(cfg.apiKeys) && cfg.apiKeys.length > 0) {
8
+ const active = cfg.activeKey ? cfg.apiKeys.find((k) => k.label === cfg.activeKey) : void 0;
9
+ return (active ?? cfg.apiKeys[0])?.apiKey;
10
+ }
11
+ return cfg.apiKey && cfg.apiKey.length > 0 ? cfg.apiKey : void 0;
12
+ }
13
+ function eligibleProviders(config) {
14
+ const out = [];
15
+ for (const [id, cfg] of Object.entries(config.providers ?? {})) {
16
+ const preset = COMPATIBLE_PRESETS[id];
17
+ const enabled = cfg.autoDiscoverModels ?? preset?.autoDiscover ?? false;
18
+ if (!enabled) continue;
19
+ const baseUrl = cfg.baseUrl ?? preset?.defaultBaseUrl;
20
+ if (!baseUrl) continue;
21
+ out.push({ id, cfg, baseUrl, apiKey: resolveKey(cfg) });
22
+ }
23
+ return out;
24
+ }
25
+ async function readCache(file) {
26
+ try {
27
+ return JSON.parse(await fs.readFile(file, "utf8"));
28
+ } catch {
29
+ return {};
30
+ }
31
+ }
32
+ async function discoverAndMergeProviders(opts) {
33
+ const targets = eligibleProviders(opts.config);
34
+ if (targets.length === 0) return;
35
+ const cacheFile = path.join(opts.cacheDir, "discovered-models-cache.json");
36
+ const cache = await readCache(cacheFile);
37
+ let cacheDirty = false;
38
+ await Promise.all(
39
+ targets.map(async ({ id, cfg, baseUrl, apiKey }) => {
40
+ const cacheKey = `${id}\0${baseUrl}`;
41
+ const provider = await discoverOpenAICompatibleModels(id, {
42
+ baseUrl,
43
+ apiKey,
44
+ headers: cfg.headers,
45
+ providerName: id,
46
+ fetchImpl: opts.fetchImpl
47
+ });
48
+ if (provider) {
49
+ cache[cacheKey] = { fetchedAt: (/* @__PURE__ */ new Date()).toISOString(), provider };
50
+ cacheDirty = true;
51
+ opts.registry.mergeOverlay({ [id]: provider });
52
+ opts.logger?.info(
53
+ `auto-discovered ${Object.keys(provider.models).length} models for "${id}" from ${baseUrl}`
54
+ );
55
+ return;
56
+ }
57
+ const cached = cache[cacheKey];
58
+ if (cached) {
59
+ opts.registry.mergeOverlay({ [id]: cached.provider });
60
+ opts.logger?.warn(
61
+ `auto-discovery for "${id}" failed; using ${Object.keys(cached.provider.models).length} cached models from ${cached.fetchedAt}`
62
+ );
63
+ } else {
64
+ opts.logger?.warn(
65
+ `auto-discovery for "${id}" failed and no cache available (server at ${baseUrl} unreachable?)`
66
+ );
67
+ }
68
+ })
69
+ );
70
+ if (cacheDirty) {
71
+ try {
72
+ await fs.writeFile(cacheFile, JSON.stringify(cache), "utf8");
73
+ } catch {
74
+ }
75
+ }
76
+ }
77
+
78
+ export {
79
+ discoverAndMergeProviders
80
+ };
81
+ //# sourceMappingURL=chunk-TGDHN4LM.js.map