rovecode 0.3.2 → 0.4.0-beta.2

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 (491) hide show
  1. package/CHANGELOG.md +512 -0
  2. package/README.md +33 -45
  3. package/bin/rovecode.js +24 -0
  4. package/dist/cli/app-dybnr56b.js +2 -0
  5. package/dist/cli/ask-user-p8hq4xgj.js +2 -0
  6. package/dist/cli/auth-login-ewpgw5sm.js +2 -0
  7. package/dist/cli/auth-m8p9grty.js +2 -0
  8. package/dist/cli/bench-xv3ypwev.js +9 -0
  9. package/dist/cli/catalog-737wb2s0.js +2 -0
  10. package/dist/cli/cli-arhg40m0.js +2 -0
  11. package/dist/cli/client-cf2pxx8q.js +2 -0
  12. package/dist/cli/commands-3p7e4xxs.js +2 -0
  13. package/dist/cli/connect-3q93d7cb.js +2 -0
  14. package/dist/cli/context-cmd-eqxmxhzq.js +2 -0
  15. package/dist/cli/context-report-hbw9zfes.js +2 -0
  16. package/dist/cli/count-remote-mby98cd0.js +2 -0
  17. package/dist/cli/design-122y0axd.js +2 -0
  18. package/dist/cli/dispatch-b4egzvvh.js +2 -0
  19. package/dist/cli/doctor-x4jkv72e.js +3 -0
  20. package/dist/cli/executor-ftvg6tsy.js +2 -0
  21. package/dist/cli/export-pdgdhkch.js +2 -0
  22. package/dist/cli/files-cez9a96p.js +2 -0
  23. package/dist/cli/gauntlet-r3xxaszc.js +2 -0
  24. package/dist/cli/gauntlet-runner-r515m7kk.js +10 -0
  25. package/dist/cli/gauntlet-wave3-bnkjk2v2.js +5 -0
  26. package/dist/cli/gauntlet-wave4-acs9s60q.js +14 -0
  27. package/dist/cli/hashline-ewg5hbe3.js +2 -0
  28. package/dist/cli/http-n0kehsk8.js +5 -0
  29. package/dist/cli/index-z5qt1s76.js +2 -0
  30. package/dist/cli/install-80mp63kx.js +2 -0
  31. package/dist/cli/loop-12twjcat.js +2 -0
  32. package/dist/cli/main-01pv9206.js +4 -0
  33. package/dist/cli/main-0jys2ccn.js +3 -0
  34. package/dist/cli/main-1ztz6fkj.js +10 -0
  35. package/dist/cli/main-23q7cmww.js +9 -0
  36. package/dist/cli/main-2rzbexn2.js +3 -0
  37. package/dist/cli/main-2wyax8k9.js +9 -0
  38. package/dist/cli/main-2yeveeve.js +6 -0
  39. package/dist/cli/main-2z3dek0b.js +3 -0
  40. package/dist/cli/main-2zgsknth.js +3 -0
  41. package/dist/cli/main-45ejth3a.js +4 -0
  42. package/dist/cli/main-45rn3trk.js +22 -0
  43. package/dist/cli/main-4p4e2w7x.js +4 -0
  44. package/dist/cli/main-4y0tnfpa.js +16 -0
  45. package/dist/cli/main-5py0rkmc.js +4 -0
  46. package/dist/cli/main-6dtqmbt6.js +7 -0
  47. package/dist/cli/main-6h9x282m.js +4 -0
  48. package/dist/cli/main-6vjeds42.js +3 -0
  49. package/dist/cli/main-78gq4bt9.js +6 -0
  50. package/dist/cli/main-7jd5vh3x.js +4 -0
  51. package/dist/cli/main-7kt6r53y.js +4 -0
  52. package/dist/cli/main-8c1tbazx.js +58 -0
  53. package/dist/cli/main-9a9rnh47.js +19 -0
  54. package/dist/cli/main-9ht36z12.js +3 -0
  55. package/dist/cli/main-a2yfvcy9.js +7 -0
  56. package/dist/cli/main-a3f51n0x.js +5 -0
  57. package/dist/cli/main-b8zq261k.js +3 -0
  58. package/dist/cli/main-bxtvnf6d.js +13 -0
  59. package/dist/cli/main-edxc3yzt.js +4 -0
  60. package/dist/cli/main-evgz4mp5.js +21 -0
  61. package/dist/cli/main-f33fc5je.js +9 -0
  62. package/dist/cli/main-fvnpq46y.js +12 -0
  63. package/dist/cli/main-gbbty4d4.js +3 -0
  64. package/dist/cli/main-gth53dnt.js +25 -0
  65. package/dist/cli/main-hqbz10aw.js +9 -0
  66. package/dist/cli/main-hrrvcfan.js +38 -0
  67. package/dist/cli/main-hzwtsb2m.js +5 -0
  68. package/dist/cli/main-j7ttv0sd.js +34 -0
  69. package/dist/cli/main-jak598k9.js +5 -0
  70. package/dist/cli/main-kba6zeyd.js +6 -0
  71. package/dist/cli/main-kwwsz6rq.js +3 -0
  72. package/dist/cli/main-m8vm17zq.js +3 -0
  73. package/dist/cli/main-mg4f96e1.js +3 -0
  74. package/dist/cli/main-mg9b20ac.js +18 -0
  75. package/dist/cli/main-mgb9ccnx.js +3 -0
  76. package/dist/cli/main-mjt2p7aj.js +3 -0
  77. package/dist/cli/main-n6qrdbmy.js +3 -0
  78. package/dist/cli/main-na7wse0x.js +5 -0
  79. package/dist/cli/main-nqveez48.js +4 -0
  80. package/dist/cli/main-ntqef02r.js +10 -0
  81. package/dist/cli/main-nvc3yjay.js +136 -0
  82. package/dist/cli/main-p0cfn6nr.js +16 -0
  83. package/dist/cli/main-qj2djy17.js +19 -0
  84. package/dist/cli/main-qsevpgsv.js +3 -0
  85. package/dist/cli/main-qvarybsp.js +3 -0
  86. package/dist/cli/main-rebtt91r.js +5 -0
  87. package/dist/cli/main-rpg7h8mb.js +3 -0
  88. package/dist/cli/main-rsy72qmw.js +15 -0
  89. package/dist/cli/main-rvetps99.js +18 -0
  90. package/dist/cli/main-s4bb0jav.js +3 -0
  91. package/dist/cli/main-s9v8k74e.js +3 -0
  92. package/dist/cli/main-tjvwmscs.js +3 -0
  93. package/dist/cli/main-tkgarpjj.js +4 -0
  94. package/dist/cli/main-v8y60bb2.js +3 -0
  95. package/dist/cli/main-vhrrq337.js +3 -0
  96. package/dist/cli/main-vp2dfb7s.js +4 -0
  97. package/dist/cli/main-vqbr22sz.js +8 -0
  98. package/dist/cli/main-vxnwe5xx.js +18 -0
  99. package/dist/cli/main-wgph00xf.js +5 -0
  100. package/dist/cli/main-wk2csfnj.js +5 -0
  101. package/dist/cli/main-wm997zjx.js +3 -0
  102. package/dist/cli/main-wpkyraxh.js +3 -0
  103. package/dist/cli/main-wqt32p5x.js +4 -0
  104. package/dist/cli/main-x9ct6y1a.js +3 -0
  105. package/dist/cli/main-xfekqh9m.js +7 -0
  106. package/dist/cli/main-xt9zc3n6.js +7 -0
  107. package/dist/cli/main-xx2z3zh5.js +4 -0
  108. package/dist/cli/main-y5c82rxr.js +3 -0
  109. package/dist/cli/main-yrjt2sqt.js +14 -0
  110. package/dist/cli/main-ys6zj3yr.js +3 -0
  111. package/dist/cli/main-ywbxshqc.js +8 -0
  112. package/dist/cli/main-z13755t8.js +25 -0
  113. package/dist/cli/main-zc7pyrbj.js +4 -0
  114. package/dist/cli/main.js +279 -0
  115. package/dist/cli/market-cmd-bm5xvn9f.js +5 -0
  116. package/dist/cli/mcp-login-bthtfpt7.js +2 -0
  117. package/dist/cli/mcp-market-cmd-mbeshfyd.js +2 -0
  118. package/dist/cli/notify-54v5z9dz.js +2 -0
  119. package/dist/cli/oauth-g5gme95c.js +2 -0
  120. package/dist/cli/output-satndjap.js +16 -0
  121. package/dist/cli/profiles-sfhpbq3m.js +2 -0
  122. package/dist/cli/provider-config-hv3xtdt4.js +2 -0
  123. package/dist/cli/provider-kwzq6g84.js +2 -0
  124. package/dist/cli/registry-fh0hdnyn.js +2 -0
  125. package/dist/cli/registry-y1y8e94r.js +2 -0
  126. package/dist/cli/repl-t4z03mqq.js +11 -0
  127. package/dist/cli/resume-fqt4chg8.js +2 -0
  128. package/dist/cli/run-flags-rysbag9t.js +2 -0
  129. package/dist/cli/runtime-j19fjbsa.js +2 -0
  130. package/dist/cli/sandbox-config-g4qxd7y5.js +2 -0
  131. package/dist/cli/server-r0b6bksk.js +5 -0
  132. package/dist/cli/session-arg-txmn5g4x.js +2 -0
  133. package/dist/cli/session-ed250d9j.js +2 -0
  134. package/dist/cli/sessions-cmd-adw7svfn.js +7 -0
  135. package/dist/cli/settings-y9rzcqx8.js +2 -0
  136. package/dist/cli/setup-jmbr11j0.js +2 -0
  137. package/dist/cli/sextant-smoke-tcth0vea.js +5 -0
  138. package/dist/cli/skills-cmd-zbdy99v6.js +2 -0
  139. package/dist/cli/smoke-1bg937kx.js +8 -0
  140. package/dist/cli/start-chat-p01cdks3.js +12 -0
  141. package/dist/cli/stream-4wmyaypz.js +2 -0
  142. package/dist/cli/task-eg4s093s.js +2 -0
  143. package/dist/cli/tasks-12v9rr9k.js +2 -0
  144. package/dist/cli/thinking-a5ngvqyh.js +2 -0
  145. package/dist/cli/todo-1wxpcecx.js +2 -0
  146. package/dist/cli/tools-2ftsya7w.js +2 -0
  147. package/dist/cli/tools-x1tj4fxm.js +2 -0
  148. package/dist/cli/trust-cmd-hccxehzb.js +2 -0
  149. package/dist/cli/update-check-ygt3vd7m.js +2 -0
  150. package/dist/cli/update-cmd-v23qhr8c.js +2 -0
  151. package/dist/cli/voice-g1gtck92.js +2 -0
  152. package/dist/cli/webfetch-0nnrjgb5.js +2 -0
  153. package/dist/cli/websearch-f0vr2p7d.js +2 -0
  154. package/dist/cli/workspace-9rq1w4ta.js +2 -0
  155. package/dist/lib/index.js +62 -0
  156. package/dist/lib/models-index.json +1 -0
  157. package/dist/lib/plugins.js +6 -0
  158. package/dist/lib/providers.js +17 -0
  159. package/dist/lib/public-api.js +20 -0
  160. package/dist/rovecode.exe +4 -0
  161. package/package.json +38 -11
  162. package/bin/rovecode.ts +0 -21
  163. package/src/acp/server.ts +0 -374
  164. package/src/cli/auth-login.ts +0 -122
  165. package/src/cli/connect.ts +0 -244
  166. package/src/cli/context-cmd.ts +0 -199
  167. package/src/cli/dispatch.ts +0 -82
  168. package/src/cli/doctor.ts +0 -362
  169. package/src/cli/export.ts +0 -276
  170. package/src/cli/help.ts +0 -293
  171. package/src/cli/is-tui-invocation.ts +0 -8
  172. package/src/cli/main.ts +0 -583
  173. package/src/cli/market-cmd.ts +0 -658
  174. package/src/cli/mcp-login.ts +0 -141
  175. package/src/cli/mcp-market-cmd.ts +0 -302
  176. package/src/cli/output.ts +0 -382
  177. package/src/cli/repl.ts +0 -250
  178. package/src/cli/repomap-root.ts +0 -14
  179. package/src/cli/resume.ts +0 -57
  180. package/src/cli/run-flags.ts +0 -43
  181. package/src/cli/run-limits.ts +0 -78
  182. package/src/cli/runtime.ts +0 -931
  183. package/src/cli/session-arg.ts +0 -30
  184. package/src/cli/sessions-cmd.ts +0 -145
  185. package/src/cli/setup.ts +0 -153
  186. package/src/cli/skills-cmd.ts +0 -194
  187. package/src/cli/start-chat.ts +0 -65
  188. package/src/cli/trust-cmd.ts +0 -52
  189. package/src/coding/bash.ts +0 -148
  190. package/src/coding/checkpoints.ts +0 -327
  191. package/src/coding/diff.ts +0 -138
  192. package/src/coding/files.ts +0 -341
  193. package/src/coding/hashline.ts +0 -274
  194. package/src/coding/lsp-gate.ts +0 -254
  195. package/src/coding/lsp-servers.ts +0 -147
  196. package/src/coding/lsp.ts +0 -283
  197. package/src/coding/repomap-cache.ts +0 -99
  198. package/src/coding/repomap-files.ts +0 -192
  199. package/src/coding/repomap.ts +0 -481
  200. package/src/core/agents.ts +0 -255
  201. package/src/core/compaction.ts +0 -259
  202. package/src/core/config.ts +0 -289
  203. package/src/core/context-report.ts +0 -228
  204. package/src/core/context.ts +0 -60
  205. package/src/core/count-remote.ts +0 -107
  206. package/src/core/execpolicy-rules.ts +0 -196
  207. package/src/core/execpolicy.ts +0 -385
  208. package/src/core/executor.ts +0 -454
  209. package/src/core/guardrails.ts +0 -400
  210. package/src/core/hooks.ts +0 -411
  211. package/src/core/images.ts +0 -230
  212. package/src/core/intro.ts +0 -266
  213. package/src/core/loop.ts +0 -567
  214. package/src/core/modes.ts +0 -372
  215. package/src/core/orchestrator.ts +0 -245
  216. package/src/core/proc-group.ts +0 -48
  217. package/src/core/project-trust.ts +0 -98
  218. package/src/core/reflection.ts +0 -165
  219. package/src/core/sandbox-config.ts +0 -186
  220. package/src/core/session-id.ts +0 -24
  221. package/src/core/session-images.ts +0 -73
  222. package/src/core/session-ops.ts +0 -183
  223. package/src/core/session-text.ts +0 -29
  224. package/src/core/session.ts +0 -469
  225. package/src/core/settings.ts +0 -170
  226. package/src/core/tasks.ts +0 -646
  227. package/src/core/token-scale.ts +0 -108
  228. package/src/core/tools.ts +0 -309
  229. package/src/core/trust.ts +0 -104
  230. package/src/core/types.ts +0 -330
  231. package/src/core/update-check.ts +0 -171
  232. package/src/core/usage.ts +0 -204
  233. package/src/core/validate.ts +0 -121
  234. package/src/core/verify-gate.ts +0 -159
  235. package/src/core/verify.ts +0 -236
  236. package/src/core/voice.ts +0 -158
  237. package/src/core/win-job.ts +0 -183
  238. package/src/core/workspace.ts +0 -184
  239. package/src/design/audit.ts +0 -797
  240. package/src/design/direction.ts +0 -190
  241. package/src/design/rules.ts +0 -157
  242. package/src/eval/bench.ts +0 -150
  243. package/src/eval/gauntlet-runner.ts +0 -215
  244. package/src/eval/gauntlet-support.ts +0 -84
  245. package/src/eval/gauntlet-wave3.ts +0 -269
  246. package/src/eval/gauntlet-wave4.ts +0 -217
  247. package/src/eval/gauntlet.ts +0 -253
  248. package/src/index.ts +0 -17
  249. package/src/lanes/agy.ts +0 -95
  250. package/src/lanes/approval.ts +0 -24
  251. package/src/lanes/claude.ts +0 -129
  252. package/src/lanes/codex.ts +0 -127
  253. package/src/lanes/events.ts +0 -130
  254. package/src/lanes/job.ts +0 -142
  255. package/src/lanes/opencode.ts +0 -122
  256. package/src/lanes/process.ts +0 -184
  257. package/src/lanes/progress.ts +0 -183
  258. package/src/lanes/registry.ts +0 -178
  259. package/src/lanes/runner.ts +0 -124
  260. package/src/lanes/types.ts +0 -112
  261. package/src/market/catalogs/mcp-docs.json +0 -111
  262. package/src/market/catalogs/plugins.json +0 -111
  263. package/src/market/catalogs/skills.json +0 -478
  264. package/src/market/clone.ts +0 -72
  265. package/src/market/context-cost.ts +0 -121
  266. package/src/market/digest.ts +0 -106
  267. package/src/market/index.ts +0 -22
  268. package/src/market/install.ts +0 -578
  269. package/src/market/manifest.ts +0 -187
  270. package/src/market/prereq.ts +0 -145
  271. package/src/market/registry.ts +0 -363
  272. package/src/market/resolve.ts +0 -111
  273. package/src/market/types.ts +0 -236
  274. package/src/market/validate.ts +0 -227
  275. package/src/mcp/client.ts +0 -449
  276. package/src/mcp/config.ts +0 -252
  277. package/src/mcp/local-package.ts +0 -211
  278. package/src/mcp/market-catalog.ts +0 -84
  279. package/src/mcp/market-install.ts +0 -289
  280. package/src/mcp/market.ts +0 -362
  281. package/src/mcp/oauth.ts +0 -251
  282. package/src/mcp/prompts-resources.ts +0 -249
  283. package/src/mcp/shared.ts +0 -149
  284. package/src/mcp/status.ts +0 -67
  285. package/src/mcp/tools.ts +0 -275
  286. package/src/mcp/transport.ts +0 -122
  287. package/src/mcp/trust.ts +0 -25
  288. package/src/memory/blocks.ts +0 -278
  289. package/src/memory/recall.ts +0 -355
  290. package/src/memory/scope.ts +0 -182
  291. package/src/memory/store.ts +0 -105
  292. package/src/memory/tools.ts +0 -99
  293. package/src/plugins/cli.ts +0 -119
  294. package/src/plugins/discover.ts +0 -108
  295. package/src/plugins/index.ts +0 -50
  296. package/src/plugins/install.ts +0 -184
  297. package/src/plugins/load.ts +0 -124
  298. package/src/plugins/manifest.ts +0 -92
  299. package/src/plugins/state.ts +0 -83
  300. package/src/providers/auth.ts +0 -408
  301. package/src/providers/cache.ts +0 -223
  302. package/src/providers/catalog-local.ts +0 -160
  303. package/src/providers/catalog.ts +0 -421
  304. package/src/providers/middleware-context.ts +0 -86
  305. package/src/providers/middleware.ts +0 -373
  306. package/src/providers/model-list.ts +0 -23
  307. package/src/providers/oauth/common.ts +0 -105
  308. package/src/providers/oauth/device-code.ts +0 -107
  309. package/src/providers/oauth/github-copilot.ts +0 -146
  310. package/src/providers/oauth/loopback.ts +0 -158
  311. package/src/providers/oauth/openai.ts +0 -163
  312. package/src/providers/oauth/openrouter.ts +0 -89
  313. package/src/providers/oauth/pkce.ts +0 -45
  314. package/src/providers/oauth/registry.ts +0 -39
  315. package/src/providers/oauth/seam.ts +0 -89
  316. package/src/providers/profile-glm53.ts +0 -111
  317. package/src/providers/profile-sonnet5-persona.ts +0 -65
  318. package/src/providers/profile-sonnet5-voice.ts +0 -23
  319. package/src/providers/profiles.ts +0 -156
  320. package/src/providers/provider-config.ts +0 -311
  321. package/src/providers/registry.ts +0 -333
  322. package/src/providers/responses.ts +0 -209
  323. package/src/providers/retry.ts +0 -234
  324. package/src/providers/router.ts +0 -294
  325. package/src/providers/sse.ts +0 -26
  326. package/src/providers/stream-errors.ts +0 -117
  327. package/src/providers/stream.ts +0 -566
  328. package/src/providers/thinking.ts +0 -189
  329. package/src/providers/wire-messages.ts +0 -129
  330. package/src/providers/wire-responses.ts +0 -79
  331. package/src/providers/wire-select.ts +0 -53
  332. package/src/server/http.ts +0 -291
  333. package/src/server/openapi.ts +0 -246
  334. package/src/sextant/card-hits.ts +0 -102
  335. package/src/sextant/card-keys.ts +0 -55
  336. package/src/sextant/context-source.ts +0 -157
  337. package/src/sextant/crew-cards.ts +0 -350
  338. package/src/sextant/draw-agents.ts +0 -273
  339. package/src/sextant/draw-code.ts +0 -388
  340. package/src/sextant/draw-context.ts +0 -222
  341. package/src/sextant/draw-frame.ts +0 -164
  342. package/src/sextant/draw-market.ts +0 -573
  343. package/src/sextant/draw-messages.ts +0 -386
  344. package/src/sextant/draw-pet.ts +0 -230
  345. package/src/sextant/draw-plan.ts +0 -187
  346. package/src/sextant/draw-tabs.ts +0 -85
  347. package/src/sextant/draw-util.ts +0 -65
  348. package/src/sextant/draw-wizard.ts +0 -378
  349. package/src/sextant/engine.ts +0 -230
  350. package/src/sextant/frame-hits.ts +0 -25
  351. package/src/sextant/frame.ts +0 -101
  352. package/src/sextant/git-status.ts +0 -197
  353. package/src/sextant/grid.ts +0 -59
  354. package/src/sextant/input.ts +0 -119
  355. package/src/sextant/keys.ts +0 -521
  356. package/src/sextant/layout.ts +0 -86
  357. package/src/sextant/local-commands.ts +0 -169
  358. package/src/sextant/market-source.ts +0 -287
  359. package/src/sextant/mentions.ts +0 -200
  360. package/src/sextant/message-hits.ts +0 -26
  361. package/src/sextant/model.ts +0 -387
  362. package/src/sextant/overlays.ts +0 -456
  363. package/src/sextant/panel-hits.ts +0 -38
  364. package/src/sextant/pet.ts +0 -399
  365. package/src/sextant/screen.ts +0 -324
  366. package/src/sextant/scroll-hits.ts +0 -66
  367. package/src/sextant/scrollbar.ts +0 -82
  368. package/src/sextant/sextant-bridge.ts +0 -174
  369. package/src/sextant/sextant-cards.ts +0 -142
  370. package/src/sextant/sextant-diff-base.ts +0 -63
  371. package/src/sextant/sextant-files.ts +0 -154
  372. package/src/sextant/sextant-frame-loop.ts +0 -335
  373. package/src/sextant/sextant-renderer.ts +0 -574
  374. package/src/sextant/sextant-repo.ts +0 -140
  375. package/src/sextant/theme.ts +0 -66
  376. package/src/sextant/tool-rows.ts +0 -189
  377. package/src/sextant/types.ts +0 -493
  378. package/src/skills/index.ts +0 -387
  379. package/src/skills/pack.ts +0 -220
  380. package/src/skills/spec.ts +0 -162
  381. package/src/skills/tools.ts +0 -69
  382. package/src/skills/versioned.ts +0 -227
  383. package/src/telemetry/otel-export.ts +0 -122
  384. package/src/telemetry/otel-lanes.ts +0 -89
  385. package/src/telemetry/otel-logs.ts +0 -131
  386. package/src/telemetry/otel-metrics.ts +0 -136
  387. package/src/telemetry/otel.ts +0 -397
  388. package/src/telemetry/otlp.ts +0 -76
  389. package/src/tools/ask-user.ts +0 -156
  390. package/src/tools/bash-bg.ts +0 -94
  391. package/src/tools/bash-jobs.ts +0 -237
  392. package/src/tools/design.ts +0 -151
  393. package/src/tools/evalcell.ts +0 -338
  394. package/src/tools/html-text.ts +0 -139
  395. package/src/tools/provider.ts +0 -149
  396. package/src/tools/task.ts +0 -250
  397. package/src/tools/todo.ts +0 -320
  398. package/src/tools/webfetch.ts +0 -332
  399. package/src/tools/websearch.ts +0 -359
  400. package/src/tui/agents-cmd.ts +0 -41
  401. package/src/tui/app.ts +0 -749
  402. package/src/tui/attach.ts +0 -127
  403. package/src/tui/boot-notes.ts +0 -41
  404. package/src/tui/builtin-prompts.ts +0 -59
  405. package/src/tui/checkpoints-cmd.ts +0 -70
  406. package/src/tui/clipboard-image.ts +0 -81
  407. package/src/tui/clipboard.ts +0 -78
  408. package/src/tui/commands.ts +0 -283
  409. package/src/tui/config-view.ts +0 -53
  410. package/src/tui/context-cmds.ts +0 -282
  411. package/src/tui/cost.ts +0 -108
  412. package/src/tui/crash-guard.ts +0 -173
  413. package/src/tui/focus-terminal.ts +0 -34
  414. package/src/tui/git-cmds.ts +0 -273
  415. package/src/tui/git-plain.ts +0 -58
  416. package/src/tui/info-cmd.ts +0 -150
  417. package/src/tui/input-plain.ts +0 -76
  418. package/src/tui/mcp-cmd.ts +0 -128
  419. package/src/tui/memory-note.ts +0 -77
  420. package/src/tui/modes-cmd.ts +0 -45
  421. package/src/tui/notify-seq.ts +0 -100
  422. package/src/tui/notify.ts +0 -318
  423. package/src/tui/overlays.ts +0 -97
  424. package/src/tui/pi-renderer.ts +0 -428
  425. package/src/tui/providers-cmd.ts +0 -377
  426. package/src/tui/reasoning-view.ts +0 -56
  427. package/src/tui/renderer.ts +0 -128
  428. package/src/tui/replay-marker.ts +0 -29
  429. package/src/tui/session-cmd.ts +0 -148
  430. package/src/tui/session-manage.ts +0 -95
  431. package/src/tui/sextant-attach.ts +0 -102
  432. package/src/tui/sextant-io.ts +0 -202
  433. package/src/tui/sextant-smoke.ts +0 -110
  434. package/src/tui/shell-cmd.ts +0 -158
  435. package/src/tui/smoke.ts +0 -72
  436. package/src/tui/staged-terminal.ts +0 -50
  437. package/src/tui/startup.ts +0 -12
  438. package/src/tui/theme.ts +0 -59
  439. package/src/tui/todo-label.ts +0 -7
  440. package/src/tui/trust-card.ts +0 -107
  441. package/src/tui/tui-commands.ts +0 -87
  442. package/tsconfig.json +0 -30
  443. package/vendor/pi-tui/LICENSE +0 -21
  444. package/vendor/pi-tui/PATCHES.md +0 -12
  445. package/vendor/pi-tui/PROVENANCE.md +0 -12
  446. package/vendor/pi-tui/README.upstream.md +0 -854
  447. package/vendor/pi-tui/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
  448. package/vendor/pi-tui/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
  449. package/vendor/pi-tui/src/alt-screen-search.ts +0 -158
  450. package/vendor/pi-tui/src/autocomplete.ts +0 -827
  451. package/vendor/pi-tui/src/components/alt-screen-flash.ts +0 -52
  452. package/vendor/pi-tui/src/components/box.ts +0 -138
  453. package/vendor/pi-tui/src/components/cancellable-loader.ts +0 -41
  454. package/vendor/pi-tui/src/components/editor.ts +0 -2364
  455. package/vendor/pi-tui/src/components/h-stack.ts +0 -45
  456. package/vendor/pi-tui/src/components/image.ts +0 -128
  457. package/vendor/pi-tui/src/components/input.ts +0 -448
  458. package/vendor/pi-tui/src/components/loader.ts +0 -93
  459. package/vendor/pi-tui/src/components/markdown.ts +0 -1016
  460. package/vendor/pi-tui/src/components/scroll-view.ts +0 -217
  461. package/vendor/pi-tui/src/components/select-list.ts +0 -230
  462. package/vendor/pi-tui/src/components/settings-list.ts +0 -277
  463. package/vendor/pi-tui/src/components/spacer.ts +0 -29
  464. package/vendor/pi-tui/src/components/stack.ts +0 -155
  465. package/vendor/pi-tui/src/components/text.ts +0 -108
  466. package/vendor/pi-tui/src/components/truncated-text.ts +0 -66
  467. package/vendor/pi-tui/src/components/v-stack.ts +0 -34
  468. package/vendor/pi-tui/src/editor-component.ts +0 -75
  469. package/vendor/pi-tui/src/fuzzy.ts +0 -138
  470. package/vendor/pi-tui/src/index.ts +0 -149
  471. package/vendor/pi-tui/src/keybindings.ts +0 -321
  472. package/vendor/pi-tui/src/keys.ts +0 -1402
  473. package/vendor/pi-tui/src/kill-ring.ts +0 -47
  474. package/vendor/pi-tui/src/latex.ts +0 -1381
  475. package/vendor/pi-tui/src/layout-node.ts +0 -52
  476. package/vendor/pi-tui/src/layout.ts +0 -411
  477. package/vendor/pi-tui/src/native-modifiers.ts +0 -60
  478. package/vendor/pi-tui/src/native-module-path.ts +0 -32
  479. package/vendor/pi-tui/src/stdin-buffer.ts +0 -445
  480. package/vendor/pi-tui/src/terminal-colors.ts +0 -74
  481. package/vendor/pi-tui/src/terminal-image.ts +0 -701
  482. package/vendor/pi-tui/src/terminal.ts +0 -554
  483. package/vendor/pi-tui/src/tui-alt-screen.ts +0 -1379
  484. package/vendor/pi-tui/src/tui-main-screen.ts +0 -655
  485. package/vendor/pi-tui/src/tui.ts +0 -1264
  486. package/vendor/pi-tui/src/undo-stack.ts +0 -29
  487. package/vendor/pi-tui/src/utils.ts +0 -1327
  488. package/vendor/pi-tui/src/word-navigation.ts +0 -118
  489. package/vendor/pi-tui/test/test-themes.ts +0 -39
  490. package/vendor/pi-tui/test/virtual-terminal.ts +0 -219
  491. /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
- }