rovecode 0.3.3 → 0.4.0-beta.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 (495) hide show
  1. package/CHANGELOG.md +527 -0
  2. package/README.md +46 -48
  3. package/bin/rovecode.js +24 -0
  4. package/dist/cli/app-j6gn14w3.js +2 -0
  5. package/dist/cli/ask-user-cwstt8fz.js +2 -0
  6. package/dist/cli/auth-login-9bbp9915.js +2 -0
  7. package/dist/cli/auth-m8p9grty.js +2 -0
  8. package/dist/cli/bench-16zqdms5.js +9 -0
  9. package/dist/cli/catalog-1xchffa4.js +2 -0
  10. package/dist/cli/cli-1n1zb64f.js +2 -0
  11. package/dist/cli/client-2t9gjkck.js +2 -0
  12. package/dist/cli/commands-exafvm2b.js +2 -0
  13. package/dist/cli/connect-6zde0kn3.js +2 -0
  14. package/dist/cli/context-cmd-5t43wgqt.js +2 -0
  15. package/dist/cli/context-report-kt01pw8y.js +2 -0
  16. package/dist/cli/count-remote-ap7x3vh6.js +2 -0
  17. package/dist/cli/design-ne5zszyh.js +2 -0
  18. package/dist/cli/dispatch-2r5myxye.js +2 -0
  19. package/dist/cli/doctor-ws4fh4tn.js +3 -0
  20. package/dist/cli/executor-bdrjn634.js +2 -0
  21. package/dist/cli/export-1mxb9g5p.js +2 -0
  22. package/dist/cli/files-g104xghh.js +2 -0
  23. package/dist/cli/gauntlet-07xrjpj7.js +2 -0
  24. package/dist/cli/gauntlet-runner-xvy64436.js +10 -0
  25. package/dist/cli/gauntlet-wave3-jm91yt5w.js +5 -0
  26. package/dist/cli/gauntlet-wave4-r13py7p1.js +14 -0
  27. package/dist/cli/hashline-znvrat11.js +2 -0
  28. package/dist/cli/http-xafw6fsh.js +143 -0
  29. package/dist/cli/index-1sgjm25y.js +2 -0
  30. package/dist/cli/init-g2m0tn4m.js +51 -0
  31. package/dist/cli/install-avaqjjqq.js +2 -0
  32. package/dist/cli/loop-mmpfft01.js +2 -0
  33. package/dist/cli/main-0904f6ps.js +5 -0
  34. package/dist/cli/main-0ab9fc26.js +9 -0
  35. package/dist/cli/main-0jys2ccn.js +3 -0
  36. package/dist/cli/main-0mtcdbs7.js +3 -0
  37. package/dist/cli/main-0z1w2zsg.js +3 -0
  38. package/dist/cli/main-1dchs7xv.js +18 -0
  39. package/dist/cli/main-1ereejm1.js +3 -0
  40. package/dist/cli/main-1k1kw6b5.js +3 -0
  41. package/dist/cli/main-27y4sm2k.js +38 -0
  42. package/dist/cli/main-2wwjex5j.js +58 -0
  43. package/dist/cli/main-2yeveeve.js +6 -0
  44. package/dist/cli/main-2yfck9b5.js +3 -0
  45. package/dist/cli/main-2zmzgkwh.js +3 -0
  46. package/dist/cli/main-351pz3z7.js +7 -0
  47. package/dist/cli/main-3gjqfh7a.js +6 -0
  48. package/dist/cli/main-3nf3kgve.js +3 -0
  49. package/dist/cli/main-3pjrb2hd.js +3 -0
  50. package/dist/cli/main-3rxcvgna.js +19 -0
  51. package/dist/cli/main-4b3jgy66.js +19 -0
  52. package/dist/cli/main-4wndhjdc.js +7 -0
  53. package/dist/cli/main-4xcmvxnk.js +3 -0
  54. package/dist/cli/main-5tbz0wbz.js +4 -0
  55. package/dist/cli/main-5ywnwthm.js +3 -0
  56. package/dist/cli/main-6b62vkz0.js +14 -0
  57. package/dist/cli/main-6dnk69vp.js +3 -0
  58. package/dist/cli/main-6genrmhs.js +136 -0
  59. package/dist/cli/main-73g7eff4.js +15 -0
  60. package/dist/cli/main-7c5thhjd.js +5 -0
  61. package/dist/cli/main-7rn6bqje.js +3 -0
  62. package/dist/cli/main-80haw7qk.js +4 -0
  63. package/dist/cli/main-875s60s2.js +4 -0
  64. package/dist/cli/main-8kjxbpw4.js +8 -0
  65. package/dist/cli/main-90ds1z4e.js +10 -0
  66. package/dist/cli/main-9etavkew.js +3 -0
  67. package/dist/cli/main-a9njrkk1.js +3 -0
  68. package/dist/cli/main-aecrjq2d.js +12 -0
  69. package/dist/cli/main-ck9asesq.js +9 -0
  70. package/dist/cli/main-cta9racd.js +4 -0
  71. package/dist/cli/main-ddv7j2ag.js +3 -0
  72. package/dist/cli/main-dfreez27.js +10 -0
  73. package/dist/cli/main-f7rw7des.js +3 -0
  74. package/dist/cli/main-ggcn7rd7.js +5 -0
  75. package/dist/cli/main-gzkmycnv.js +3 -0
  76. package/dist/cli/main-hq51jg8v.js +18 -0
  77. package/dist/cli/main-jft389w9.js +8 -0
  78. package/dist/cli/main-k1eqkg83.js +3 -0
  79. package/dist/cli/main-k2y8a2aw.js +9 -0
  80. package/dist/cli/main-kcpbykxz.js +4 -0
  81. package/dist/cli/main-kd488vje.js +22 -0
  82. package/dist/cli/main-kh32yvgk.js +5 -0
  83. package/dist/cli/main-kqxnqjnv.js +25 -0
  84. package/dist/cli/main-kyn0xnsg.js +3 -0
  85. package/dist/cli/main-m1kk6fp5.js +21 -0
  86. package/dist/cli/main-mv40pcr2.js +4 -0
  87. package/dist/cli/main-n0t3973w.js +3 -0
  88. package/dist/cli/main-nqveez48.js +4 -0
  89. package/dist/cli/main-pknhvrmj.js +3 -0
  90. package/dist/cli/main-pn1w7a7j.js +3 -0
  91. package/dist/cli/main-prxxs70n.js +4 -0
  92. package/dist/cli/main-q3vsesf9.js +3 -0
  93. package/dist/cli/main-qsevpgsv.js +3 -0
  94. package/dist/cli/main-rdgdw24b.js +25 -0
  95. package/dist/cli/main-rfth4tbm.js +16 -0
  96. package/dist/cli/main-rg0wn0xf.js +5 -0
  97. package/dist/cli/main-sdmxhtv8.js +4 -0
  98. package/dist/cli/main-skbp13js.js +18 -0
  99. package/dist/cli/main-t4xnd213.js +7 -0
  100. package/dist/cli/main-vqak588n.js +4 -0
  101. package/dist/cli/main-w2n1303f.js +9 -0
  102. package/dist/cli/main-wbrdspr2.js +5 -0
  103. package/dist/cli/main-wsrg79c1.js +7 -0
  104. package/dist/cli/main-x4r0fne4.js +5 -0
  105. package/dist/cli/main-xea2f3tn.js +6 -0
  106. package/dist/cli/main-xg704a3c.js +3 -0
  107. package/dist/cli/main-xvnrabfp.js +16 -0
  108. package/dist/cli/main-xy53xf0r.js +4 -0
  109. package/dist/cli/main-y1fqy60y.js +3 -0
  110. package/dist/cli/main-yn8cd281.js +34 -0
  111. package/dist/cli/main-yr0ksc0h.js +4 -0
  112. package/dist/cli/main-z2ex2vyf.js +4 -0
  113. package/dist/cli/main-z3aayzvq.js +3 -0
  114. package/dist/cli/main-zaqh35jg.js +3 -0
  115. package/dist/cli/main-zc2e8e46.js +4 -0
  116. package/dist/cli/main-zzrfw6cf.js +13 -0
  117. package/dist/cli/main.js +280 -0
  118. package/dist/cli/market-cmd-e14kmx9n.js +5 -0
  119. package/dist/cli/mcp-login-wq7ktdek.js +2 -0
  120. package/dist/cli/mcp-market-cmd-9mg3jecy.js +2 -0
  121. package/dist/cli/notify-b7qc0cjb.js +2 -0
  122. package/dist/cli/oauth-z8whcgfx.js +2 -0
  123. package/dist/cli/output-b3ewj3ps.js +16 -0
  124. package/dist/cli/profiles-6mr5he5e.js +2 -0
  125. package/dist/cli/provider-config-g7j42q8x.js +2 -0
  126. package/dist/cli/provider-jr1y8vvm.js +2 -0
  127. package/dist/cli/registry-s8yk86g0.js +2 -0
  128. package/dist/cli/registry-t6p8d4mn.js +2 -0
  129. package/dist/cli/repl-bajwe1mh.js +11 -0
  130. package/dist/cli/resume-rwn9nz7y.js +2 -0
  131. package/dist/cli/run-flags-nah7ndpt.js +2 -0
  132. package/dist/cli/runtime-n7gafzhb.js +2 -0
  133. package/dist/cli/sandbox-config-emdy18x4.js +2 -0
  134. package/dist/cli/server-b0nvs2bn.js +5 -0
  135. package/dist/cli/session-arg-y75wd4kj.js +2 -0
  136. package/dist/cli/session-j62evmjq.js +2 -0
  137. package/dist/cli/sessions-cmd-tsnwz0ns.js +7 -0
  138. package/dist/cli/settings-df10wfez.js +2 -0
  139. package/dist/cli/setup-jzvv72fg.js +2 -0
  140. package/dist/cli/sextant-smoke-37m81ke6.js +5 -0
  141. package/dist/cli/skills-cmd-gjxnxnhx.js +2 -0
  142. package/dist/cli/smoke-p7748apt.js +8 -0
  143. package/dist/cli/start-chat-s4st3mm0.js +12 -0
  144. package/dist/cli/stream-gmeyewds.js +2 -0
  145. package/dist/cli/task-gh0kkp3n.js +2 -0
  146. package/dist/cli/tasks-z1kfpe8e.js +2 -0
  147. package/dist/cli/thinking-0eqkrz6t.js +2 -0
  148. package/dist/cli/todo-5brcrt9m.js +2 -0
  149. package/dist/cli/tools-7pzm0vj9.js +2 -0
  150. package/dist/cli/tools-s635p6s8.js +2 -0
  151. package/dist/cli/trust-cmd-cjav8zgm.js +2 -0
  152. package/dist/cli/update-check-pt31bm2f.js +2 -0
  153. package/dist/cli/update-cmd-tk131s9t.js +2 -0
  154. package/dist/cli/voice-56nabd8d.js +2 -0
  155. package/dist/cli/webfetch-xd8q596m.js +2 -0
  156. package/dist/cli/websearch-5hkf98k1.js +2 -0
  157. package/dist/cli/workflow-cmd-cy3cvzjp.js +4 -0
  158. package/dist/cli/workspace-q10g5z3e.js +2 -0
  159. package/dist/lib/index.js +62 -0
  160. package/dist/lib/models-index.json +1 -0
  161. package/dist/lib/plugins.js +55 -0
  162. package/dist/lib/providers.js +17 -0
  163. package/dist/lib/public-api.js +20 -0
  164. package/dist/lib/sdk.js +360 -0
  165. package/package.json +84 -72
  166. package/bin/rovecode.ts +0 -21
  167. package/src/acp/server.ts +0 -374
  168. package/src/cli/auth-login.ts +0 -122
  169. package/src/cli/connect.ts +0 -244
  170. package/src/cli/context-cmd.ts +0 -199
  171. package/src/cli/dispatch.ts +0 -82
  172. package/src/cli/doctor.ts +0 -362
  173. package/src/cli/export.ts +0 -276
  174. package/src/cli/help.ts +0 -293
  175. package/src/cli/is-tui-invocation.ts +0 -8
  176. package/src/cli/main.ts +0 -583
  177. package/src/cli/market-cmd.ts +0 -658
  178. package/src/cli/mcp-login.ts +0 -141
  179. package/src/cli/mcp-market-cmd.ts +0 -302
  180. package/src/cli/output.ts +0 -382
  181. package/src/cli/repl.ts +0 -250
  182. package/src/cli/repomap-root.ts +0 -14
  183. package/src/cli/resume.ts +0 -57
  184. package/src/cli/run-flags.ts +0 -43
  185. package/src/cli/run-limits.ts +0 -78
  186. package/src/cli/runtime.ts +0 -931
  187. package/src/cli/session-arg.ts +0 -30
  188. package/src/cli/sessions-cmd.ts +0 -145
  189. package/src/cli/setup.ts +0 -153
  190. package/src/cli/skills-cmd.ts +0 -194
  191. package/src/cli/start-chat.ts +0 -65
  192. package/src/cli/trust-cmd.ts +0 -52
  193. package/src/coding/bash.ts +0 -148
  194. package/src/coding/checkpoints.ts +0 -327
  195. package/src/coding/diff.ts +0 -138
  196. package/src/coding/files.ts +0 -341
  197. package/src/coding/hashline.ts +0 -274
  198. package/src/coding/lsp-gate.ts +0 -254
  199. package/src/coding/lsp-servers.ts +0 -147
  200. package/src/coding/lsp.ts +0 -283
  201. package/src/coding/repomap-cache.ts +0 -99
  202. package/src/coding/repomap-files.ts +0 -192
  203. package/src/coding/repomap.ts +0 -481
  204. package/src/core/agents.ts +0 -255
  205. package/src/core/compaction.ts +0 -259
  206. package/src/core/config.ts +0 -289
  207. package/src/core/context-report.ts +0 -228
  208. package/src/core/context.ts +0 -60
  209. package/src/core/count-remote.ts +0 -107
  210. package/src/core/execpolicy-rules.ts +0 -196
  211. package/src/core/execpolicy.ts +0 -385
  212. package/src/core/executor.ts +0 -454
  213. package/src/core/guardrails.ts +0 -400
  214. package/src/core/hooks.ts +0 -411
  215. package/src/core/images.ts +0 -230
  216. package/src/core/intro.ts +0 -266
  217. package/src/core/loop.ts +0 -567
  218. package/src/core/modes.ts +0 -372
  219. package/src/core/orchestrator.ts +0 -245
  220. package/src/core/proc-group.ts +0 -48
  221. package/src/core/project-trust.ts +0 -98
  222. package/src/core/reflection.ts +0 -165
  223. package/src/core/sandbox-config.ts +0 -186
  224. package/src/core/session-id.ts +0 -24
  225. package/src/core/session-images.ts +0 -73
  226. package/src/core/session-ops.ts +0 -183
  227. package/src/core/session-text.ts +0 -29
  228. package/src/core/session.ts +0 -469
  229. package/src/core/settings.ts +0 -170
  230. package/src/core/tasks.ts +0 -646
  231. package/src/core/token-scale.ts +0 -108
  232. package/src/core/tools.ts +0 -309
  233. package/src/core/trust.ts +0 -104
  234. package/src/core/types.ts +0 -330
  235. package/src/core/update-check.ts +0 -171
  236. package/src/core/usage.ts +0 -204
  237. package/src/core/validate.ts +0 -121
  238. package/src/core/verify-gate.ts +0 -159
  239. package/src/core/verify.ts +0 -236
  240. package/src/core/voice.ts +0 -158
  241. package/src/core/win-job.ts +0 -183
  242. package/src/core/workspace.ts +0 -184
  243. package/src/design/audit.ts +0 -797
  244. package/src/design/direction.ts +0 -190
  245. package/src/design/rules.ts +0 -157
  246. package/src/eval/bench.ts +0 -150
  247. package/src/eval/gauntlet-runner.ts +0 -215
  248. package/src/eval/gauntlet-support.ts +0 -84
  249. package/src/eval/gauntlet-wave3.ts +0 -269
  250. package/src/eval/gauntlet-wave4.ts +0 -217
  251. package/src/eval/gauntlet.ts +0 -253
  252. package/src/index.ts +0 -17
  253. package/src/lanes/agy.ts +0 -95
  254. package/src/lanes/approval.ts +0 -24
  255. package/src/lanes/claude.ts +0 -129
  256. package/src/lanes/codex.ts +0 -127
  257. package/src/lanes/events.ts +0 -130
  258. package/src/lanes/job.ts +0 -142
  259. package/src/lanes/opencode.ts +0 -122
  260. package/src/lanes/process.ts +0 -184
  261. package/src/lanes/progress.ts +0 -183
  262. package/src/lanes/registry.ts +0 -178
  263. package/src/lanes/runner.ts +0 -124
  264. package/src/lanes/types.ts +0 -112
  265. package/src/market/catalogs/mcp-docs.json +0 -111
  266. package/src/market/catalogs/plugins.json +0 -111
  267. package/src/market/catalogs/skills.json +0 -478
  268. package/src/market/clone.ts +0 -72
  269. package/src/market/context-cost.ts +0 -121
  270. package/src/market/digest.ts +0 -106
  271. package/src/market/index.ts +0 -22
  272. package/src/market/install.ts +0 -578
  273. package/src/market/manifest.ts +0 -187
  274. package/src/market/prereq.ts +0 -145
  275. package/src/market/registry.ts +0 -363
  276. package/src/market/resolve.ts +0 -111
  277. package/src/market/types.ts +0 -236
  278. package/src/market/validate.ts +0 -227
  279. package/src/mcp/client.ts +0 -449
  280. package/src/mcp/config.ts +0 -252
  281. package/src/mcp/local-package.ts +0 -211
  282. package/src/mcp/market-catalog.ts +0 -84
  283. package/src/mcp/market-install.ts +0 -289
  284. package/src/mcp/market.ts +0 -362
  285. package/src/mcp/oauth.ts +0 -251
  286. package/src/mcp/prompts-resources.ts +0 -249
  287. package/src/mcp/shared.ts +0 -149
  288. package/src/mcp/status.ts +0 -67
  289. package/src/mcp/tools.ts +0 -275
  290. package/src/mcp/transport.ts +0 -122
  291. package/src/mcp/trust.ts +0 -25
  292. package/src/memory/blocks.ts +0 -278
  293. package/src/memory/recall.ts +0 -355
  294. package/src/memory/scope.ts +0 -182
  295. package/src/memory/store.ts +0 -105
  296. package/src/memory/tools.ts +0 -99
  297. package/src/plugins/cli.ts +0 -119
  298. package/src/plugins/discover.ts +0 -108
  299. package/src/plugins/index.ts +0 -50
  300. package/src/plugins/install.ts +0 -184
  301. package/src/plugins/load.ts +0 -124
  302. package/src/plugins/manifest.ts +0 -92
  303. package/src/plugins/state.ts +0 -83
  304. package/src/providers/auth.ts +0 -408
  305. package/src/providers/cache.ts +0 -223
  306. package/src/providers/catalog-local.ts +0 -160
  307. package/src/providers/catalog.ts +0 -421
  308. package/src/providers/middleware-context.ts +0 -86
  309. package/src/providers/middleware.ts +0 -373
  310. package/src/providers/model-list.ts +0 -23
  311. package/src/providers/oauth/common.ts +0 -105
  312. package/src/providers/oauth/device-code.ts +0 -107
  313. package/src/providers/oauth/github-copilot.ts +0 -146
  314. package/src/providers/oauth/loopback.ts +0 -158
  315. package/src/providers/oauth/openai.ts +0 -163
  316. package/src/providers/oauth/openrouter.ts +0 -89
  317. package/src/providers/oauth/pkce.ts +0 -45
  318. package/src/providers/oauth/registry.ts +0 -39
  319. package/src/providers/oauth/seam.ts +0 -89
  320. package/src/providers/profile-glm53.ts +0 -111
  321. package/src/providers/profile-sonnet5-persona.ts +0 -65
  322. package/src/providers/profile-sonnet5-voice.ts +0 -23
  323. package/src/providers/profiles.ts +0 -156
  324. package/src/providers/provider-config.ts +0 -311
  325. package/src/providers/registry.ts +0 -333
  326. package/src/providers/responses.ts +0 -209
  327. package/src/providers/retry.ts +0 -234
  328. package/src/providers/router.ts +0 -294
  329. package/src/providers/sse.ts +0 -26
  330. package/src/providers/stream-errors.ts +0 -117
  331. package/src/providers/stream.ts +0 -566
  332. package/src/providers/thinking.ts +0 -189
  333. package/src/providers/wire-messages.ts +0 -129
  334. package/src/providers/wire-responses.ts +0 -79
  335. package/src/providers/wire-select.ts +0 -53
  336. package/src/server/http.ts +0 -291
  337. package/src/server/openapi.ts +0 -246
  338. package/src/sextant/card-hits.ts +0 -102
  339. package/src/sextant/card-keys.ts +0 -55
  340. package/src/sextant/context-source.ts +0 -157
  341. package/src/sextant/crew-cards.ts +0 -350
  342. package/src/sextant/draw-agents.ts +0 -273
  343. package/src/sextant/draw-code.ts +0 -388
  344. package/src/sextant/draw-context.ts +0 -222
  345. package/src/sextant/draw-frame.ts +0 -164
  346. package/src/sextant/draw-market.ts +0 -573
  347. package/src/sextant/draw-messages.ts +0 -386
  348. package/src/sextant/draw-pet.ts +0 -230
  349. package/src/sextant/draw-plan.ts +0 -187
  350. package/src/sextant/draw-tabs.ts +0 -85
  351. package/src/sextant/draw-util.ts +0 -65
  352. package/src/sextant/draw-wizard.ts +0 -378
  353. package/src/sextant/engine.ts +0 -230
  354. package/src/sextant/frame-hits.ts +0 -25
  355. package/src/sextant/frame.ts +0 -101
  356. package/src/sextant/git-status.ts +0 -197
  357. package/src/sextant/grid.ts +0 -59
  358. package/src/sextant/input.ts +0 -119
  359. package/src/sextant/keys.ts +0 -521
  360. package/src/sextant/layout.ts +0 -86
  361. package/src/sextant/local-commands.ts +0 -169
  362. package/src/sextant/market-source.ts +0 -287
  363. package/src/sextant/mentions.ts +0 -200
  364. package/src/sextant/message-hits.ts +0 -26
  365. package/src/sextant/model.ts +0 -387
  366. package/src/sextant/overlays.ts +0 -456
  367. package/src/sextant/panel-hits.ts +0 -38
  368. package/src/sextant/pet.ts +0 -399
  369. package/src/sextant/screen.ts +0 -324
  370. package/src/sextant/scroll-hits.ts +0 -66
  371. package/src/sextant/scrollbar.ts +0 -82
  372. package/src/sextant/sextant-bridge.ts +0 -174
  373. package/src/sextant/sextant-cards.ts +0 -142
  374. package/src/sextant/sextant-diff-base.ts +0 -63
  375. package/src/sextant/sextant-files.ts +0 -154
  376. package/src/sextant/sextant-frame-loop.ts +0 -335
  377. package/src/sextant/sextant-renderer.ts +0 -574
  378. package/src/sextant/sextant-repo.ts +0 -140
  379. package/src/sextant/theme.ts +0 -66
  380. package/src/sextant/tool-rows.ts +0 -189
  381. package/src/sextant/types.ts +0 -493
  382. package/src/skills/index.ts +0 -387
  383. package/src/skills/pack.ts +0 -220
  384. package/src/skills/spec.ts +0 -162
  385. package/src/skills/tools.ts +0 -69
  386. package/src/skills/versioned.ts +0 -227
  387. package/src/telemetry/otel-export.ts +0 -122
  388. package/src/telemetry/otel-lanes.ts +0 -89
  389. package/src/telemetry/otel-logs.ts +0 -131
  390. package/src/telemetry/otel-metrics.ts +0 -136
  391. package/src/telemetry/otel.ts +0 -397
  392. package/src/telemetry/otlp.ts +0 -76
  393. package/src/tools/ask-user.ts +0 -156
  394. package/src/tools/bash-bg.ts +0 -94
  395. package/src/tools/bash-jobs.ts +0 -237
  396. package/src/tools/design.ts +0 -151
  397. package/src/tools/evalcell.ts +0 -338
  398. package/src/tools/html-text.ts +0 -139
  399. package/src/tools/provider.ts +0 -149
  400. package/src/tools/task.ts +0 -250
  401. package/src/tools/todo.ts +0 -320
  402. package/src/tools/webfetch.ts +0 -332
  403. package/src/tools/websearch.ts +0 -359
  404. package/src/tui/agents-cmd.ts +0 -41
  405. package/src/tui/app.ts +0 -749
  406. package/src/tui/attach.ts +0 -127
  407. package/src/tui/boot-notes.ts +0 -41
  408. package/src/tui/builtin-prompts.ts +0 -59
  409. package/src/tui/checkpoints-cmd.ts +0 -70
  410. package/src/tui/clipboard-image.ts +0 -81
  411. package/src/tui/clipboard.ts +0 -78
  412. package/src/tui/commands.ts +0 -283
  413. package/src/tui/config-view.ts +0 -53
  414. package/src/tui/context-cmds.ts +0 -282
  415. package/src/tui/cost.ts +0 -108
  416. package/src/tui/crash-guard.ts +0 -173
  417. package/src/tui/focus-terminal.ts +0 -34
  418. package/src/tui/git-cmds.ts +0 -273
  419. package/src/tui/git-plain.ts +0 -58
  420. package/src/tui/info-cmd.ts +0 -150
  421. package/src/tui/input-plain.ts +0 -76
  422. package/src/tui/mcp-cmd.ts +0 -128
  423. package/src/tui/memory-note.ts +0 -77
  424. package/src/tui/modes-cmd.ts +0 -45
  425. package/src/tui/notify-seq.ts +0 -100
  426. package/src/tui/notify.ts +0 -318
  427. package/src/tui/overlays.ts +0 -97
  428. package/src/tui/pi-renderer.ts +0 -428
  429. package/src/tui/providers-cmd.ts +0 -377
  430. package/src/tui/reasoning-view.ts +0 -56
  431. package/src/tui/renderer.ts +0 -128
  432. package/src/tui/replay-marker.ts +0 -29
  433. package/src/tui/session-cmd.ts +0 -148
  434. package/src/tui/session-manage.ts +0 -95
  435. package/src/tui/sextant-attach.ts +0 -102
  436. package/src/tui/sextant-io.ts +0 -202
  437. package/src/tui/sextant-smoke.ts +0 -110
  438. package/src/tui/shell-cmd.ts +0 -158
  439. package/src/tui/smoke.ts +0 -72
  440. package/src/tui/staged-terminal.ts +0 -50
  441. package/src/tui/startup.ts +0 -12
  442. package/src/tui/theme.ts +0 -59
  443. package/src/tui/todo-label.ts +0 -7
  444. package/src/tui/trust-card.ts +0 -107
  445. package/src/tui/tui-commands.ts +0 -87
  446. package/tsconfig.json +0 -30
  447. package/vendor/pi-tui/LICENSE +0 -21
  448. package/vendor/pi-tui/PATCHES.md +0 -12
  449. package/vendor/pi-tui/PROVENANCE.md +0 -12
  450. package/vendor/pi-tui/README.upstream.md +0 -854
  451. package/vendor/pi-tui/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
  452. package/vendor/pi-tui/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
  453. package/vendor/pi-tui/src/alt-screen-search.ts +0 -158
  454. package/vendor/pi-tui/src/autocomplete.ts +0 -827
  455. package/vendor/pi-tui/src/components/alt-screen-flash.ts +0 -52
  456. package/vendor/pi-tui/src/components/box.ts +0 -138
  457. package/vendor/pi-tui/src/components/cancellable-loader.ts +0 -41
  458. package/vendor/pi-tui/src/components/editor.ts +0 -2364
  459. package/vendor/pi-tui/src/components/h-stack.ts +0 -45
  460. package/vendor/pi-tui/src/components/image.ts +0 -128
  461. package/vendor/pi-tui/src/components/input.ts +0 -448
  462. package/vendor/pi-tui/src/components/loader.ts +0 -93
  463. package/vendor/pi-tui/src/components/markdown.ts +0 -1016
  464. package/vendor/pi-tui/src/components/scroll-view.ts +0 -217
  465. package/vendor/pi-tui/src/components/select-list.ts +0 -230
  466. package/vendor/pi-tui/src/components/settings-list.ts +0 -277
  467. package/vendor/pi-tui/src/components/spacer.ts +0 -29
  468. package/vendor/pi-tui/src/components/stack.ts +0 -155
  469. package/vendor/pi-tui/src/components/text.ts +0 -108
  470. package/vendor/pi-tui/src/components/truncated-text.ts +0 -66
  471. package/vendor/pi-tui/src/components/v-stack.ts +0 -34
  472. package/vendor/pi-tui/src/editor-component.ts +0 -75
  473. package/vendor/pi-tui/src/fuzzy.ts +0 -138
  474. package/vendor/pi-tui/src/index.ts +0 -149
  475. package/vendor/pi-tui/src/keybindings.ts +0 -321
  476. package/vendor/pi-tui/src/keys.ts +0 -1402
  477. package/vendor/pi-tui/src/kill-ring.ts +0 -47
  478. package/vendor/pi-tui/src/latex.ts +0 -1381
  479. package/vendor/pi-tui/src/layout-node.ts +0 -52
  480. package/vendor/pi-tui/src/layout.ts +0 -411
  481. package/vendor/pi-tui/src/native-modifiers.ts +0 -60
  482. package/vendor/pi-tui/src/native-module-path.ts +0 -32
  483. package/vendor/pi-tui/src/stdin-buffer.ts +0 -445
  484. package/vendor/pi-tui/src/terminal-colors.ts +0 -74
  485. package/vendor/pi-tui/src/terminal-image.ts +0 -701
  486. package/vendor/pi-tui/src/terminal.ts +0 -554
  487. package/vendor/pi-tui/src/tui-alt-screen.ts +0 -1379
  488. package/vendor/pi-tui/src/tui-main-screen.ts +0 -655
  489. package/vendor/pi-tui/src/tui.ts +0 -1264
  490. package/vendor/pi-tui/src/undo-stack.ts +0 -29
  491. package/vendor/pi-tui/src/utils.ts +0 -1327
  492. package/vendor/pi-tui/src/word-navigation.ts +0 -118
  493. package/vendor/pi-tui/test/test-themes.ts +0 -39
  494. package/vendor/pi-tui/test/virtual-terminal.ts +0 -219
  495. /package/{src/providers → dist/cli}/models-index.json +0 -0
@@ -1,107 +0,0 @@
1
- /** OAuth 2.0 device-authorization polling (RFC 8628 §3.4–3.5) — VENDORED from pi.
2
- *
3
- * Source: earendil-works/pi, packages/ai/src/auth/oauth/device-code.ts (snapshot under
4
- * research/source_snapshots/earendil-works-pi, @earendil-works/ai 0.84.4). License: MIT —
5
- *
6
- * Copyright (c) 2025 Mario Zechner
7
- *
8
- * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
9
- * associated documentation files (the "Software"), to deal in the Software without restriction,
10
- * including without limitation the rights to use, copy, modify, merge, publish, distribute,
11
- * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
12
- * furnished to do so, subject to the following conditions: The above copyright notice and this
13
- * permission notice shall be included in all copies or substantial portions of the Software.
14
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
15
- * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
16
- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
17
- * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19
- *
20
- * Local changes (port #66): `sleep` and `now` are injectable (the tests drive pending → slow_down →
21
- * complete with a recording sleep and a fixed clock, and pin the interval arithmetic); the default
22
- * sleep is pi's abortableSleep; the messages lose the WSL clock-drift paragraph's second sentence.
23
- * Semantics unchanged: a missing `interval` means 5 s (§3.2), `slow_down` adds 5 s (§3.5) unless the
24
- * server states a new interval (GitHub does), never faster than 1 s, deadline = `expires_in`. */
25
-
26
- const CANCEL_MESSAGE = "login cancelled";
27
- const TIMEOUT_MESSAGE = "device flow timed out";
28
- const SLOW_DOWN_TIMEOUT_MESSAGE = "device flow timed out after one or more slow_down responses (often clock drift in a WSL/VM — sync the clock and retry)";
29
- const MINIMUM_INTERVAL_MS = 1000;
30
- /** RFC 8628 §3.2: when the server omits `interval` the client polls every 5 s */
31
- const DEFAULT_POLL_INTERVAL_SECONDS = 5;
32
- /** RFC 8628 §3.5: `slow_down` means the interval grows by 5 s */
33
- const SLOW_DOWN_INTERVAL_INCREMENT_MS = 5000;
34
-
35
- type DeviceCodeIncompletePollResult =
36
- | { status: "pending" }
37
- | { status: "slow_down"; intervalSeconds?: number }
38
- | { status: "failed"; message: string };
39
-
40
- export type DeviceCodePollResult<T> = DeviceCodeIncompletePollResult | { status: "complete"; value: T };
41
-
42
- export interface DeviceCodePollOptions<T> {
43
- intervalSeconds?: number;
44
- expiresInSeconds?: number;
45
- /** wait one interval before the first poll (GitHub's flow; the user has not even seen the code yet) */
46
- waitBeforeFirstPoll?: boolean;
47
- poll: () => Promise<DeviceCodePollResult<T>>;
48
- signal: AbortSignal;
49
- /** injectable timer (default abortableSleep) */
50
- sleep?: (ms: number, signal: AbortSignal) => Promise<void>;
51
- /** injectable clock (default Date.now) */
52
- now?: () => number;
53
- }
54
-
55
- export function abortableSleep(ms: number, signal: AbortSignal, cancelMessage: string = CANCEL_MESSAGE): Promise<void> {
56
- return new Promise((resolve, reject) => {
57
- if (signal.aborted) {
58
- reject(new Error(cancelMessage));
59
- return;
60
- }
61
- const onAbort = () => {
62
- clearTimeout(timeout);
63
- reject(new Error(cancelMessage));
64
- };
65
- const timeout = setTimeout(() => {
66
- signal.removeEventListener("abort", onAbort);
67
- resolve();
68
- }, ms);
69
- signal.addEventListener("abort", onAbort, { once: true });
70
- });
71
- }
72
-
73
- export async function pollDeviceCodeFlow<T>(options: DeviceCodePollOptions<T>): Promise<T> {
74
- const now = options.now ?? Date.now;
75
- const sleep = options.sleep ?? abortableSleep;
76
- const deadline = typeof options.expiresInSeconds === "number" ? now() + options.expiresInSeconds * 1000 : Number.POSITIVE_INFINITY;
77
- let intervalMs = Math.max(MINIMUM_INTERVAL_MS, Math.floor((options.intervalSeconds ?? DEFAULT_POLL_INTERVAL_SECONDS) * 1000));
78
-
79
- let slowDownResponses = 0;
80
- if (options.waitBeforeFirstPoll) {
81
- const remainingMs = deadline - now();
82
- if (remainingMs > 0) await sleep(Math.min(intervalMs, remainingMs), options.signal);
83
- }
84
-
85
- while (now() < deadline) {
86
- if (options.signal.aborted) throw new Error(CANCEL_MESSAGE);
87
-
88
- const result = await options.poll();
89
- if (result.status === "complete") return result.value;
90
- if (result.status === "failed") throw new Error(result.message);
91
- if (result.status === "slow_down") {
92
- slowDownResponses += 1;
93
- // Use the server-provided interval when given (GitHub reports the new required minimum in
94
- // `interval`); trusting only a client-tracked value risks polling early forever under WSL/VM
95
- // clock drift. Otherwise apply RFC 8628 §3.5: increase by 5 seconds.
96
- intervalMs = typeof result.intervalSeconds === "number" && Number.isFinite(result.intervalSeconds) && result.intervalSeconds > 0
97
- ? Math.max(MINIMUM_INTERVAL_MS, Math.floor(result.intervalSeconds * 1000))
98
- : Math.max(MINIMUM_INTERVAL_MS, intervalMs + SLOW_DOWN_INTERVAL_INCREMENT_MS);
99
- }
100
-
101
- const remainingMs = deadline - now();
102
- if (remainingMs <= 0) break;
103
- await sleep(Math.min(intervalMs, remainingMs), options.signal);
104
- }
105
-
106
- throw new Error(slowDownResponses > 0 ? SLOW_DOWN_TIMEOUT_MESSAGE : TIMEOUT_MESSAGE);
107
- }
@@ -1,146 +0,0 @@
1
- /** GitHub Copilot OAuth (port #66): the GitHub device-code flow, then the Copilot token exchange.
2
- *
3
- * Ported from pi packages/ai/src/auth/oauth/github-copilot.ts (earendil-works/pi, MIT — Copyright (c)
4
- * 2025 Mario Zechner; snapshot under research/source_snapshots/earendil-works-pi). Kept as pi has it:
5
- * - POST /login/device/code with the Copilot Chat client id + scope read:user → user_code +
6
- * verification_uri + interval + expires_in; the verification URI must parse as http(s) (never handed
7
- * to an opener otherwise);
8
- * - poll POST /login/oauth/access_token (grant urn:ietf:params:oauth:grant-type:device_code) through
9
- * pollDeviceCodeFlow, waiting one interval before the first poll; `authorization_pending` → pending,
10
- * `slow_down` → the server's new `interval`; a 200 body with `error` is how GitHub reports both;
11
- * - the GitHub OAuth token is the long-lived REFRESH credential; GET api.github.com/copilot_internal/
12
- * v2/token (Bearer <github token> + the Copilot editor headers) mints the short-lived Copilot
13
- * token (`token`, `expires_at` seconds) — stored as `access` with a 5-minute early expiry;
14
- * - the chat base URL comes from the Copilot token's `proxy-ep=` field (proxy.X → https://api.X),
15
- * default https://api.individual.githubcopilot.com; requests carry the editor headers.
16
- * Deviations: enterprise domains are not prompted for (github.com only — follow-up); pi's
17
- * /models catalog fetch + per-model policy enabling is not ported (rovecode fetches /models itself
18
- * through providers/stream.ts fetchModels); endpoints are injectable for the fake-server tests.
19
- * Honesty: the client id is the one the GitHub Copilot Chat VS Code extension uses (pi, opencode and
20
- * others sign in with it); the token endpoint is GitHub's internal Copilot API. Not live-verified
21
- * on this box (no Copilot account) — see README. */
22
-
23
- import { fetchOrCancel, httpFailure, jsonObject, type OAuthCredential, type OAuthDeps, type OAuthLoginIO, type OAuthProvider } from "./common.ts";
24
- import { pollDeviceCodeFlow } from "./device-code.ts";
25
-
26
- export interface GitHubCopilotEndpoints {
27
- deviceCodeUrl: string;
28
- accessTokenUrl: string;
29
- copilotTokenUrl: string;
30
- /** fixed chat base URL (tests; enterprise proxies). Default: derived from the token's proxy-ep. */
31
- chatBaseUrl?: string;
32
- }
33
-
34
- export const GITHUB_COPILOT_ENDPOINTS: GitHubCopilotEndpoints = {
35
- deviceCodeUrl: "https://github.com/login/device/code",
36
- accessTokenUrl: "https://github.com/login/oauth/access_token",
37
- copilotTokenUrl: "https://api.github.com/copilot_internal/v2/token",
38
- };
39
-
40
- /** pi: the GitHub Copilot Chat (VS Code extension) OAuth app id, base64 as upstream keeps it */
41
- const CLIENT_ID = atob("SXYxLmI1MDdhMDhjODdlY2ZlOTg=");
42
- const USER_AGENT = "GitHubCopilotChat/0.35.0";
43
- /** the editor headers the Copilot API expects on every request (pi COPILOT_HEADERS) */
44
- export const COPILOT_HEADERS: Readonly<Record<string, string>> = {
45
- "User-Agent": USER_AGENT,
46
- "Editor-Version": "vscode/1.107.0",
47
- "Editor-Plugin-Version": "copilot-chat/0.35.0",
48
- "Copilot-Integration-Id": "vscode-chat",
49
- };
50
- const DEFAULT_BASE_URL = "https://api.individual.githubcopilot.com";
51
- /** the stored expiry leads the server's by this much, so a request never races the real expiry */
52
- const EXPIRY_SKEW_MS = 5 * 60_000;
53
- export const GITHUB_COPILOT_DEFAULT_MODEL = "gpt-4o";
54
-
55
- interface DeviceCode { deviceCode: string; userCode: string; verificationUri: string; interval?: number; expiresIn: number }
56
-
57
- /** `tid=…;exp=…;proxy-ep=proxy.individual.githubcopilot.com;…` → https://api.individual.githubcopilot.com */
58
- export function baseUrlFromCopilotToken(token: string): string | null {
59
- const match = /proxy-ep=([^;]+)/.exec(token);
60
- if (!match || !match[1]) return null;
61
- return `https://${match[1].replace(/^proxy\./, "api.")}`;
62
- }
63
-
64
- async function postForm(deps: OAuthDeps, url: string, fields: Record<string, string>, signal: AbortSignal | undefined, what: string): Promise<Record<string, unknown>> {
65
- const res = await fetchOrCancel(deps, url, {
66
- method: "POST",
67
- headers: { Accept: "application/json", "Content-Type": "application/x-www-form-urlencoded", "User-Agent": USER_AGENT },
68
- body: new URLSearchParams(fields).toString(),
69
- }, signal);
70
- if (!res.ok) throw await httpFailure(what, res);
71
- return jsonObject(res, what);
72
- }
73
-
74
- async function startDeviceFlow(ep: GitHubCopilotEndpoints, deps: OAuthDeps, signal: AbortSignal): Promise<DeviceCode> {
75
- const data = await postForm(deps, ep.deviceCodeUrl, { client_id: CLIENT_ID, scope: "read:user" }, signal, "GitHub device code request");
76
- const { device_code: deviceCode, user_code: userCode, verification_uri: verificationUri, interval, expires_in: expiresIn } = data;
77
- if (typeof deviceCode !== "string" || typeof userCode !== "string" || typeof verificationUri !== "string"
78
- || (interval !== undefined && typeof interval !== "number") || typeof expiresIn !== "number") {
79
- throw new Error("GitHub device code response is missing fields");
80
- }
81
- // the verification URI is shown for the user to open — force it to be an http(s) URL (pi)
82
- let parsed: URL;
83
- try {
84
- parsed = new URL(verificationUri);
85
- } catch {
86
- throw new Error("GitHub device code response carries an untrusted verification_uri");
87
- }
88
- if (parsed.protocol !== "https:" && parsed.protocol !== "http:") throw new Error("GitHub device code response carries an untrusted verification_uri");
89
- return { deviceCode, userCode, verificationUri: parsed.href, interval: interval as number | undefined, expiresIn };
90
- }
91
-
92
- function pollAccessToken(ep: GitHubCopilotEndpoints, device: DeviceCode, deps: OAuthDeps, signal: AbortSignal): Promise<string> {
93
- return pollDeviceCodeFlow<string>({
94
- intervalSeconds: device.interval,
95
- expiresInSeconds: device.expiresIn,
96
- waitBeforeFirstPoll: true,
97
- signal,
98
- sleep: deps.sleep,
99
- now: deps.now,
100
- poll: async () => {
101
- const raw = await postForm(deps, ep.accessTokenUrl, {
102
- client_id: CLIENT_ID, device_code: device.deviceCode, grant_type: "urn:ietf:params:oauth:grant-type:device_code",
103
- }, signal, "GitHub device token poll");
104
- if (typeof raw.access_token === "string" && raw.access_token.length > 0) return { status: "complete", value: raw.access_token };
105
- if (typeof raw.error === "string") {
106
- if (raw.error === "authorization_pending") return { status: "pending" };
107
- if (raw.error === "slow_down") return { status: "slow_down", intervalSeconds: typeof raw.interval === "number" ? raw.interval : undefined };
108
- return { status: "failed", message: `GitHub device flow failed: ${raw.error}${typeof raw.error_description === "string" ? ` — ${raw.error_description.slice(0, 160)}` : ""}` };
109
- }
110
- return { status: "failed", message: "GitHub device token response is missing fields" };
111
- },
112
- });
113
- }
114
-
115
- /** Bearer <github token> → the Copilot token (`access`) + expiry; the GitHub token stays `refresh`. */
116
- async function exchangeCopilotToken(ep: GitHubCopilotEndpoints, githubToken: string, deps: OAuthDeps, signal: AbortSignal | undefined): Promise<OAuthCredential> {
117
- const res = await fetchOrCancel(deps, ep.copilotTokenUrl, {
118
- headers: { Accept: "application/json", Authorization: `Bearer ${githubToken}`, ...COPILOT_HEADERS },
119
- }, signal);
120
- if (!res.ok) throw await httpFailure("Copilot token exchange", res);
121
- const raw = await jsonObject(res, "Copilot token exchange");
122
- const { token, expires_at: expiresAt } = raw;
123
- if (typeof token !== "string" || token.length === 0 || typeof expiresAt !== "number") throw new Error("Copilot token response is missing fields");
124
- return { type: "oauth", access: token, refresh: githubToken, expires: expiresAt * 1000 - EXPIRY_SKEW_MS };
125
- }
126
-
127
- export function githubCopilotOAuth(endpoints: GitHubCopilotEndpoints = GITHUB_COPILOT_ENDPOINTS): OAuthProvider {
128
- return {
129
- id: "github-copilot",
130
- label: "GitHub Copilot (device code)",
131
- defaultModel: GITHUB_COPILOT_DEFAULT_MODEL,
132
- async login(io: OAuthLoginIO, deps: OAuthDeps): Promise<OAuthCredential> {
133
- const device = await startDeviceFlow(endpoints, deps, io.signal);
134
- io.notify({ type: "device_code", userCode: device.userCode, verificationUri: device.verificationUri, intervalSeconds: device.interval ?? 5, expiresInSeconds: device.expiresIn });
135
- const githubToken = await pollAccessToken(endpoints, device, deps, io.signal);
136
- io.notify({ type: "progress", message: "authorized — exchanging for a Copilot token" });
137
- return exchangeCopilotToken(endpoints, githubToken, deps, io.signal);
138
- },
139
- refresh(cred, signal, deps) {
140
- return exchangeCopilotToken(endpoints, cred.refresh, deps, signal);
141
- },
142
- toAuth(cred) {
143
- return { apiKey: cred.access, baseUrl: endpoints.chatBaseUrl ?? baseUrlFromCopilotToken(cred.access) ?? DEFAULT_BASE_URL, headers: { ...COPILOT_HEADERS } };
144
- },
145
- };
146
- }
@@ -1,158 +0,0 @@
1
- /** One-shot loopback OAuth callback server (port #66, extracted for port #76): binds 127.0.0.1 on an ephemeral
2
- * port, waits for ONE browser GET on `/callback/<nonce>` and hands its `?code=` to the caller's `onCode`.
3
- *
4
- * Extracted — behaviour unchanged — from the OpenRouter flow of port #66 (openrouter.ts, itself ported from pi
5
- * packages/ai/src/auth/oauth/openrouter.ts: earendil-works/pi, MIT, Copyright (c) 2025 Mario Zechner; snapshot under
6
- * research/source_snapshots/earendil-works-pi) so the MCP login (mcp/oauth.ts) shares every rule:
7
- * - Bun.serve on CALLBACK_HOST (127.0.0.1) port 0 — never 0.0.0.0, never a fixed port (opencode's 19876 is the
8
- * anti-pattern); the callback URL is built from the BOUND server (hostname + port), so a bind regression shows up
9
- * in the URL the tests pin;
10
- * - anything but GET is a 404 (pi: `request.method !== "GET"` → 404), so is a request whose Host is not the loopback
11
- * host (a DNS-rebinding page reaching the port) and a request off the `/callback/` prefix (a favicon probe) —
12
- * all three keep waiting;
13
- * - the callback PATH carries a fresh 32-byte nonce (pi randomizes the path with a UUID): a GET on
14
- * `/callback/<anything else>` is a STATE MISMATCH — 400 and the whole login rejects, not merely ignored, because a
15
- * forged redirect is the one thing the nonce exists to catch;
16
- * - `requireStateParam` (port #76; the MCP SDK appends `state` to the authorize URL): `?state=` must equal the nonce
17
- * too, else the same 400 + reject. OpenRouter passes none — whether it preserves a query string on the redirect is
18
- * not live-verified, and a wrong guess would abort every real login;
19
- * - `?error=` (RFC 6749 §4.1.2.1) rejects with the provider's reason; a GET without `code` is a 400 that keeps waiting;
20
- * - single use: once a callback is being exchanged (`claimed`) or the outcome is decided (`closing`, flipped
21
- * synchronously; `settled` follows SETTLE_DELAY_MS later so the browser gets its page first) every later request —
22
- * genuine or forged — is a 409 that never reaches `onCode`. The ONE accepted code runs `onCode` BEFORE the 200 is
23
- * sent (exchange-before-200); a throwing `onCode` answers 502 and rejects the login;
24
- * - the server closes when the login settles — success, mismatch, denial, `onCode` failure, abort (`signal`),
25
- * `stop()` or the timeout (5 minutes by default) — and refuses connections afterwards.
26
- * Every message is built from `what` ("OpenRouter", `MCP server "x"`) so OpenRouter's texts stay verbatim. */
27
-
28
- import { loginCancelled } from "./common.ts";
29
-
30
- /** the callback server binds loopback only — the browser must run on this machine */
31
- export const CALLBACK_HOST = "127.0.0.1";
32
- export const DEFAULT_LOGIN_TIMEOUT_MS = 5 * 60_000;
33
- /** the browser's response is flushed before the server goes away */
34
- const SETTLE_DELAY_MS = 20;
35
-
36
- export interface LoopbackOptions<T> {
37
- /** the per-login nonce (pkce.ts randomState): the callback path is `/callback/<nonce>` */
38
- nonce: string;
39
- /** Ctrl-C — aborting rejects `result` with loginCancelled() */
40
- signal: AbortSignal;
41
- /** who is being signed in to; prefixes every message and page ("OpenRouter", `MCP server "x"`) */
42
- what: string;
43
- /** the sentence the 400 mismatch page ends with ("Run rovecode auth login openrouter again.") */
44
- retryHint: string;
45
- /** what `onCode` does, for the 502 page (default "key exchange") */
46
- exchange?: string;
47
- /** also require `?state=` to equal the nonce (the MCP SDK sends it; OpenRouter does not) */
48
- requireStateParam?: boolean;
49
- timeoutMs?: number;
50
- /** runs with the accepted code BEFORE the browser gets its 200 page; a throw answers 502 and rejects `result` */
51
- onCode: (code: string) => Promise<T>;
52
- }
53
-
54
- export interface LoopbackCallback<T> {
55
- /** `http://127.0.0.1:<port>/callback/<nonce>` from the BOUND server */
56
- callbackUrl: string;
57
- /** onCode's value; rejects on mismatch, denial, onCode failure, abort, stop() or timeout — never with a token */
58
- result: Promise<T>;
59
- /** stop the server now (idempotent; runs on its own once `result` settles) */
60
- stop(): void;
61
- }
62
-
63
- /** the thrown text for a callback on a wrong path (or, with requireStateParam, a wrong `?state=`) */
64
- export function stateMismatchMessage(what: string): string {
65
- return `${what} OAuth: state mismatch on the callback — login aborted`;
66
- }
67
-
68
- function html(status: number, text: string): Response {
69
- return new Response(`<!doctype html><meta charset="utf-8"><title>rovecode</title><p>${text}</p>`, {
70
- status, headers: { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" },
71
- });
72
- }
73
-
74
- function minutes(ms: number): string {
75
- return ms >= 60_000 ? `${Math.round(ms / 60_000)} minutes` : `${ms} ms`;
76
- }
77
-
78
- export function startLoopbackCallback<T>(opts: LoopbackOptions<T>): LoopbackCallback<T> {
79
- type Outcome = { value: T } | { error: Error };
80
- const callbackPath = `/callback/${opts.nonce}`;
81
- const exchange = opts.exchange ?? "key exchange";
82
- let settle: (o: Outcome) => void = () => {};
83
- const outcome = new Promise<Outcome>((resolve) => { settle = resolve; });
84
- let settled = false;
85
- let claimed = false;
86
- /** the outcome is decided but `settled` waits SETTLE_DELAY_MS — flipped synchronously so no callback that lands in
87
- * that window is exchanged (a forged mismatch first, the genuine redirect 5 ms later) */
88
- let closing = false;
89
- const finish = (o: Outcome): void => {
90
- if (settled) return;
91
- settled = true;
92
- settle(o);
93
- };
94
- // the browser gets its page before the login settles and the server stops
95
- const finishAfterResponse = (o: Outcome): void => { closing = true; setTimeout(() => finish(o), SETTLE_DELAY_MS); };
96
- const mismatch = (): Response => {
97
- finishAfterResponse({ error: new Error(stateMismatchMessage(opts.what)) });
98
- return html(400, `State mismatch — this login was aborted. ${opts.retryHint}`);
99
- };
100
-
101
- const server = Bun.serve({
102
- port: 0,
103
- hostname: CALLBACK_HOST,
104
- async fetch(req) {
105
- // pi: the redirect is a browser GET — anything else is not a callback (404, keep waiting)
106
- if (req.method !== "GET") return html(404, "Not an OAuth callback.");
107
- const url = new URL(req.url);
108
- // a foreign Host header (a page at evil.example resolving to 127.0.0.1) is never the redirect we issued
109
- if (url.hostname !== CALLBACK_HOST) return html(404, "Not an OAuth callback.");
110
- if (!url.pathname.startsWith("/callback/")) return html(404, "Not an OAuth callback.");
111
- // single use: once a callback is being exchanged (claimed) or the outcome is decided (closing / settled),
112
- // every later request — genuine or forged — is a 409 that never reaches onCode
113
- if (settled || claimed || closing) return html(409, "This OAuth callback has already been used.");
114
- if (url.pathname !== callbackPath) return mismatch();
115
- if (opts.requireStateParam === true && url.searchParams.get("state") !== opts.nonce) return mismatch();
116
- const denied = url.searchParams.get("error");
117
- if (denied) {
118
- finishAfterResponse({ error: new Error(`${opts.what} authorization denied: ${(url.searchParams.get("error_description") ?? denied).slice(0, 160)}`) });
119
- return html(400, `${opts.what} authorization was denied.`);
120
- }
121
- const code = url.searchParams.get("code");
122
- if (!code) return html(400, `${opts.what} returned no authorization code.`);
123
- claimed = true;
124
- try {
125
- const value = await opts.onCode(code);
126
- finishAfterResponse({ value });
127
- return html(200, `Signed in to ${opts.what} — you can close this tab and return to rovecode.`);
128
- } catch (e) {
129
- finishAfterResponse({ error: e instanceof Error ? e : new Error(String(e)) });
130
- return html(502, `${opts.what} ${exchange} failed — see the rovecode terminal.`);
131
- }
132
- },
133
- });
134
-
135
- // from the BOUND server, not the constant: a bind regression (0.0.0.0) shows up in the printed URL the tests pin
136
- const callbackUrl = `http://${server.hostname}:${server.port}${callbackPath}`;
137
- const timeoutMs = opts.timeoutMs ?? DEFAULT_LOGIN_TIMEOUT_MS;
138
- const onAbort = (): void => finish({ error: loginCancelled() });
139
- opts.signal.addEventListener("abort", onAbort, { once: true });
140
- const timer = setTimeout(() => finish({ error: new Error(`${opts.what} OAuth login timed out (${minutes(timeoutMs)})`) }), timeoutMs);
141
- let stopped = false;
142
- const stop = (): void => {
143
- if (stopped) return;
144
- stopped = true;
145
- clearTimeout(timer);
146
- opts.signal.removeEventListener("abort", onAbort);
147
- server.stop(true);
148
- finish({ error: new Error(`${opts.what} OAuth login aborted`) }); // no-op when the outcome is already in
149
- };
150
- if (opts.signal.aborted) onAbort();
151
- const result = outcome.then((o) => {
152
- stop();
153
- if ("error" in o) throw o.error;
154
- return o.value;
155
- });
156
- result.catch(() => {}); // observed by the caller; a caller that stopped early must not leave it unhandled
157
- return { callbackUrl, result, stop };
158
- }
@@ -1,163 +0,0 @@
1
- /** OpenAI (ChatGPT) OAuth device-code flow (port #66).
2
- *
3
- * Ported from pi packages/ai/src/auth/oauth/openai-codex.ts (earendil-works/pi, MIT — Copyright (c)
4
- * 2025 Mario Zechner; snapshot under research/source_snapshots/earendil-works-pi), device-code half
5
- * only (pi's browser PKCE half on the fixed port 1455 is not ported — follow-up). Kept as pi has it:
6
- * - POST auth.openai.com/api/accounts/deviceauth/usercode {client_id} → device_auth_id + user_code +
7
- * interval (number or numeric string); the user opens https://auth.openai.com/codex/device;
8
- * - poll POST …/deviceauth/token {device_auth_id, user_code}: 200 → authorization_code + the
9
- * server-issued code_verifier; 403/404 or error code deviceauth_authorization_pending → pending;
10
- * slow_down → slow_down; anything else fails; 15-minute deadline;
11
- * - exchange at POST auth.openai.com/oauth/token (grant authorization_code, redirect_uri
12
- * …/deviceauth/callback) → access_token + refresh_token + expires_in; refresh = grant
13
- * refresh_token at the same URL; the ChatGPT account id is read from the access token's JWT claim
14
- * `https://api.openai.com/auth`.chatgpt_account_id and sent as the `chatgpt-account-id` header.
15
- * Deviations: a token without the account claim is accepted (pi throws) — accountId is optional;
16
- * a 404 on the usercode request is reported as "device code login not enabled" like pi.
17
- * Honesty: the client id is the Codex CLI's (pi and opencode sign in with it). The resulting token is a
18
- * ChatGPT-subscription token accepted by the Codex Responses backend ONLY, so `toAuth` (port #75) hands the
19
- * seam `baseUrl` https://chatgpt.com/backend-api/codex (codex model-provider-info/src/lib.rs:40
20
- * CHATGPT_CODEX_BASE_URL; pi openai-codex-responses.ts:45 + resolveCodexUrl :633-639), `wire: "responses"`
21
- * (providers/wire-select.ts sends every request through providers/responses.ts — never /chat/completions)
22
- * and the headers that backend requires: `chatgpt-account-id` (when the JWT carries the claim),
23
- * `OpenAI-Beta: responses=experimental` and `originator: rovecode` (pi openai-codex-responses.ts:1607-1609,
24
- * :1622; codex core/src/client.rs:688 adds its own originator). Not live-verified on this box (no ChatGPT
25
- * account): the path is pinned against loopback fakes (test/unit/oauth-openai-responses.test.ts) — README. */
26
-
27
- import { fetchOrCancel, httpFailure, jsonObject, type OAuthCredential, type OAuthDeps, type OAuthLoginIO, type OAuthProvider } from "./common.ts";
28
- import { pollDeviceCodeFlow } from "./device-code.ts";
29
-
30
- export interface OpenAIEndpoints {
31
- deviceUserCodeUrl: string;
32
- deviceTokenUrl: string;
33
- tokenUrl: string;
34
- deviceVerificationUri: string;
35
- deviceRedirectUri: string;
36
- /** the Responses base the token is sent to (…/responses is appended by providers/responses.ts) */
37
- responsesBaseUrl: string;
38
- }
39
-
40
- const AUTH_BASE_URL = "https://auth.openai.com";
41
- export const OPENAI_ENDPOINTS: OpenAIEndpoints = {
42
- deviceUserCodeUrl: `${AUTH_BASE_URL}/api/accounts/deviceauth/usercode`,
43
- deviceTokenUrl: `${AUTH_BASE_URL}/api/accounts/deviceauth/token`,
44
- tokenUrl: `${AUTH_BASE_URL}/oauth/token`,
45
- deviceVerificationUri: `${AUTH_BASE_URL}/codex/device`,
46
- deviceRedirectUri: `${AUTH_BASE_URL}/deviceauth/callback`,
47
- responsesBaseUrl: "https://chatgpt.com/backend-api/codex",
48
- };
49
-
50
- /** the two headers the Codex Responses backend expects beside the bearer + account id (pi :1607-1609, :1622) */
51
- export const CODEX_RESPONSES_HEADERS: Readonly<Record<string, string>> = { "OpenAI-Beta": "responses=experimental", originator: "rovecode" };
52
-
53
- /** pi: the Codex CLI OAuth client id */
54
- const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
55
- const DEVICE_CODE_TIMEOUT_SECONDS = 15 * 60;
56
- const JWT_CLAIM = "https://api.openai.com/auth";
57
-
58
- interface DeviceAuth { deviceAuthId: string; userCode: string; intervalSeconds: number }
59
- interface DeviceGrant { authorizationCode: string; codeVerifier: string }
60
-
61
- /** The ChatGPT account id claim of an access-token JWT; undefined when absent or not a JWT. */
62
- export function accountIdFromJwt(token: string): string | undefined {
63
- try {
64
- const parts = token.split(".");
65
- if (parts.length !== 3 || !parts[1]) return undefined;
66
- const b64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
67
- const payload = JSON.parse(atob(b64 + "=".repeat((4 - (b64.length % 4)) % 4))) as Record<string, unknown>;
68
- const claim = payload[JWT_CLAIM];
69
- const id = typeof claim === "object" && claim !== null ? (claim as { chatgpt_account_id?: unknown }).chatgpt_account_id : undefined;
70
- return typeof id === "string" && id.length > 0 ? id : undefined;
71
- } catch {
72
- return undefined;
73
- }
74
- }
75
-
76
- async function readTokenResponse(res: Response, what: string, now: () => number): Promise<OAuthCredential> {
77
- if (!res.ok) throw await httpFailure(what, res);
78
- const json = await jsonObject(res, what);
79
- const { access_token: access, refresh_token: refresh, expires_in: expiresIn } = json;
80
- if (typeof access !== "string" || access.length === 0 || typeof refresh !== "string" || typeof expiresIn !== "number") throw new Error(`${what}: response is missing fields`);
81
- const accountId = accountIdFromJwt(access);
82
- return { type: "oauth", access, refresh, expires: now() + expiresIn * 1000, ...(accountId !== undefined ? { accountId } : {}) };
83
- }
84
-
85
- async function startDeviceAuth(ep: OpenAIEndpoints, deps: OAuthDeps, signal: AbortSignal): Promise<DeviceAuth> {
86
- const res = await fetchOrCancel(deps, ep.deviceUserCodeUrl, {
87
- method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ client_id: CLIENT_ID }),
88
- }, signal);
89
- if (res.status === 404) throw new Error("OpenAI device code login is not enabled for this server (HTTP 404)");
90
- if (!res.ok) throw await httpFailure("OpenAI device code request", res);
91
- const json = await jsonObject(res, "OpenAI device code request");
92
- const { device_auth_id: deviceAuthId, user_code: userCode, interval } = json;
93
- const intervalSeconds = typeof interval === "string" ? Number(interval.trim()) : interval;
94
- if (typeof deviceAuthId !== "string" || !deviceAuthId || typeof userCode !== "string" || !userCode
95
- || typeof intervalSeconds !== "number" || !Number.isFinite(intervalSeconds) || intervalSeconds < 0) {
96
- throw new Error("OpenAI device code response is missing fields");
97
- }
98
- return { deviceAuthId, userCode, intervalSeconds };
99
- }
100
-
101
- function pollDeviceAuth(ep: OpenAIEndpoints, device: DeviceAuth, deps: OAuthDeps, signal: AbortSignal): Promise<DeviceGrant> {
102
- return pollDeviceCodeFlow<DeviceGrant>({
103
- intervalSeconds: device.intervalSeconds,
104
- expiresInSeconds: DEVICE_CODE_TIMEOUT_SECONDS,
105
- signal,
106
- sleep: deps.sleep,
107
- now: deps.now,
108
- poll: async () => {
109
- const res = await fetchOrCancel(deps, ep.deviceTokenUrl, {
110
- method: "POST", headers: { "Content-Type": "application/json" },
111
- body: JSON.stringify({ device_auth_id: device.deviceAuthId, user_code: device.userCode }),
112
- }, signal);
113
- if (res.ok) {
114
- const json = await jsonObject(res, "OpenAI device token poll");
115
- const { authorization_code: authorizationCode, code_verifier: codeVerifier } = json;
116
- if (typeof authorizationCode !== "string" || !authorizationCode || typeof codeVerifier !== "string" || !codeVerifier) {
117
- return { status: "failed", message: "OpenAI device token response is missing fields" };
118
- }
119
- return { status: "complete", value: { authorizationCode, codeVerifier } };
120
- }
121
- if (res.status === 403 || res.status === 404) return { status: "pending" };
122
- let errorCode: unknown;
123
- try {
124
- const json = (await res.json()) as { error?: string | { code?: string } } | null;
125
- errorCode = typeof json?.error === "object" ? json.error?.code : json?.error;
126
- } catch {
127
- // non-JSON error body: reported by status below
128
- }
129
- if (errorCode === "deviceauth_authorization_pending") return { status: "pending" };
130
- if (errorCode === "slow_down") return { status: "slow_down" };
131
- return { status: "failed", message: `OpenAI device authorization failed (HTTP ${res.status})${typeof errorCode === "string" ? `: ${errorCode.slice(0, 80)}` : ""}` };
132
- },
133
- });
134
- }
135
-
136
- async function postToken(ep: OpenAIEndpoints, fields: Record<string, string>, deps: OAuthDeps, signal: AbortSignal | undefined, what: string): Promise<OAuthCredential> {
137
- const res = await fetchOrCancel(deps, ep.tokenUrl, {
138
- method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams(fields).toString(),
139
- }, signal);
140
- return readTokenResponse(res, what, deps.now);
141
- }
142
-
143
- export function openAIOAuth(endpoints: OpenAIEndpoints = OPENAI_ENDPOINTS): OAuthProvider {
144
- return {
145
- id: "openai",
146
- label: "OpenAI / ChatGPT (device code)",
147
- async login(io: OAuthLoginIO, deps: OAuthDeps): Promise<OAuthCredential> {
148
- const device = await startDeviceAuth(endpoints, deps, io.signal);
149
- io.notify({ type: "device_code", userCode: device.userCode, verificationUri: endpoints.deviceVerificationUri, intervalSeconds: device.intervalSeconds, expiresInSeconds: DEVICE_CODE_TIMEOUT_SECONDS });
150
- const grant = await pollDeviceAuth(endpoints, device, deps, io.signal);
151
- io.notify({ type: "progress", message: "authorized — exchanging the authorization code" });
152
- return postToken(endpoints, {
153
- grant_type: "authorization_code", client_id: CLIENT_ID, code: grant.authorizationCode, code_verifier: grant.codeVerifier, redirect_uri: endpoints.deviceRedirectUri,
154
- }, deps, io.signal, "OpenAI token exchange");
155
- },
156
- refresh(cred, signal, deps) {
157
- return postToken(endpoints, { grant_type: "refresh_token", refresh_token: cred.refresh, client_id: CLIENT_ID }, deps, signal, "OpenAI token refresh");
158
- },
159
- toAuth(cred) {
160
- return { apiKey: cred.access, baseUrl: endpoints.responsesBaseUrl, wire: "responses", headers: { ...(cred.accountId ? { "chatgpt-account-id": cred.accountId } : {}), ...CODEX_RESPONSES_HEADERS } };
161
- },
162
- };
163
- }