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,408 +0,0 @@
1
- /** Provider credential store (port #37): backing for `rovecode auth set/list/remove`.
2
- *
3
- * Ported from opencode packages/opencode/src/auth/index.ts @ ebece6e (MIT):
4
- * - one JSON object keyed by provider id, entries discriminated by `type` (index.ts:14-36)
5
- * - the "api" variant carries the secret in `key` (index.ts:23-27); the provider loader
6
- * consumes it as the apiKey (provider.ts:1596-1601)
7
- * - file written with mode 0o600 (index.ts:79,88)
8
- * - a missing/corrupt file reads as {} and malformed entries are dropped per-entry, never
9
- * fatal (index.ts:65-66, Record.filterMap over the schema decode)
10
- * Deviations: the file lives at ~/.rovecode/credentials.json (bar) instead of opencode's
11
- * <data>/auth.json; entries carry an rovecode-only optional `keyName` (which env var the
12
- * secret stands in for); the "api" variant plus, since the OAuth port (aion port #66, brought over
13
- * 2026-09-07), the "oauth" variant (`rovecode auth login`): `access` (the bearer the provider seam
14
- * sends), `refresh` (what mints a new access token; "" when the provider issues a permanent key),
15
- * `expires` (ms since the epoch; Number.MAX_SAFE_INTEGER = never) — pi's OAuthCredential shape
16
- * (packages/ai/src/auth/types.ts). The `type` field is the record's kind discriminator. Entries with
17
- * any other `type` round-trip through save/remove unharmed but are not listed or resolved.
18
- *
19
- * Also home to readSecret (the `rovecode auth set` prompt) so its TTY path is unit-testable
20
- * in-process — main.ts cannot be imported by tests (it dispatches on load).
21
- *
22
- * SECRETS ARE NEVER LOGGED from this module: the only terminal output is readSecret's
23
- * prompt text plus cursor-control sequences, error messages never embed the credential
24
- * value, and the store itself prints nothing. Rendering (redacted) is the caller's job
25
- * via listProviders()/redactSecret().
26
- */
27
-
28
- import { readFileSync, writeFileSync, mkdirSync, chmodSync, cpSync, existsSync, rmSync } from "node:fs";
29
- import { createInterface } from "node:readline";
30
- import { Writable } from "node:stream";
31
- import { join } from "node:path";
32
- import { homedir } from "node:os";
33
- import { createRequire } from "node:module";
34
-
35
- /** The provider index, loaded ONLY when something asks for a key name.
36
- *
37
- * Two things were wrong here and they compounded. The import was at the top of the module, and what it
38
- * imported was the full models.dev snapshot: 63 MB resident, for one call site (`keyNameFor`). Meanwhile
39
- * `rovecodeHome`, three lines of path arithmetic in this same file, is imported by hooks.ts, settings.ts,
40
- * runtime.ts, the plugin loader and half the TUI — so all of them paid 63 MB to learn where `~/.rovecode`
41
- * is, and the TUI paid it at startup before drawing a frame.
42
- *
43
- * Now it is deferred, and it reads the trimmed index (src/providers/models-index.json, the same file the
44
- * catalog reads) rather than the upstream snapshot. Only `env[0]` is ever used from it.
45
- *
46
- * `require` rather than `await import` because `keyNameFor` is synchronous and called from synchronous
47
- * code; making it async would push the change through a dozen call sites to save nothing extra. */
48
- let snapshotCache: Record<string, { env?: string[] } | undefined> | null = null;
49
- function snapshotProvidersLazy(): Record<string, { env?: string[] } | undefined> {
50
- if (snapshotCache === null) {
51
- const req = createRequire(import.meta.url);
52
- const m = req("./models-index.json") as { providers?: Record<string, { env?: string[] }> };
53
- snapshotCache = m.providers ?? {};
54
- }
55
- return snapshotCache;
56
- }
57
-
58
- export interface StoredCredential {
59
- type: "api";
60
- /** the secret itself (upstream field name — auth/index.ts:24) */
61
- key: string;
62
- /** rovecode extension: env var name this secret stands in for (e.g. ANTHROPIC_API_KEY) */
63
- keyName?: string;
64
- }
65
-
66
- /** An OAuth login (`rovecode auth login <provider>`), pi's OAuthCredential shape. */
67
- export interface StoredOAuthCredential {
68
- type: "oauth";
69
- /** the bearer the provider seam sends (short-lived where the provider issues one) */
70
- access: string;
71
- /** what refresh() presents to mint a new access token (github-copilot: the GitHub OAuth token;
72
- * openai: the refresh_token; "" when the provider issued a permanent key — openrouter) */
73
- refresh: string;
74
- /** access-token expiry, ms since the epoch; Number.MAX_SAFE_INTEGER = never expires */
75
- expires: number;
76
- /** openai: the ChatGPT account id from the access token's JWT claim (request header) */
77
- accountId?: string;
78
- }
79
-
80
- /** Port #76: an MCP server's OAuth token (`rovecode mcp login <name>`, stored under `mcp:<name>`) — the SDK-driven
81
- * flow's whole session, so a connect needs no discovery or registration round trip: `url` (a record is attached
82
- * only to the server whose configured url EQUALS it), `access` ("" once invalidated — the record then still carries
83
- * the client + discovery), `refresh` ("" when the server issued none), `expires` (ms since the epoch;
84
- * Number.MAX_SAFE_INTEGER = never), the registered client (`client_id`, plus a DCR-issued secret when the server
85
- * insisted on one — never a configured one) and the SDK's discovery state (opaque here; mcp/oauth.ts reads it).
86
- * Listed by `auth list` (kind oauth, key name `mcp server token`), removable, NEVER resolved as a chat provider. */
87
- export interface StoredMcpOAuthRecord {
88
- type: "mcp-oauth";
89
- url: string;
90
- access: string;
91
- refresh: string;
92
- expires: number;
93
- clientInformation?: { client_id: string; client_secret?: string; client_id_issued_at?: number; client_secret_expires_at?: number };
94
- discovery?: Record<string, unknown>;
95
- }
96
-
97
- /** The mode the store is written with (create AND re-asserted on rewrite) — the single source
98
- * the tests pin; see the Windows note on writeStore. */
99
- export const CREDENTIALS_FILE_MODE = 0o600;
100
-
101
- /** User-scope rovecode dir: ROVECODE_HOME overrides ~/.rovecode wholesale (tests point it at a temp
102
- * dir). homedir() already respects HOME on POSIX and USERPROFILE on Windows. Mirrors the
103
- * skills store's user-scope default (skills/index.ts: join(homedir(), ".rovecode", ...)). */
104
- /** what node's homedir() promises, spelled out so every runtime agrees on every host: HOME on POSIX,
105
- * USERPROFILE on Windows, the OS account's directory only when neither is set (tests point HOME at a
106
- * scratch dir and expect the home to follow) */
107
- const userHome = (): string => (process.platform === "win32" ? process.env.USERPROFILE : process.env.HOME) || homedir();
108
-
109
- export function rovecodeHome(): string {
110
- const explicit = process.env.ROVECODE_HOME;
111
- const home = explicit ?? join(userHome(), ".rovecode");
112
- migrateLegacyHome(home, { explicit: explicit !== undefined });
113
- return home;
114
- }
115
-
116
- /** The config directory used to be `~/.cumulus` (the project was called nimbus). A rename must not
117
- * cost anyone their stored API keys and providers, so the first call that resolves the new home
118
- * COPIES the old one into it — copy, not move: the old directory is left exactly as it was, so
119
- * downgrading to an older build keeps working and nothing is destroyed if this goes wrong.
120
- *
121
- * Runs once per process, only when the new home does not exist yet and the old one does, and ONLY for
122
- * the default home. An explicit ROVECODE_HOME used to be migrated the same way "which is what makes it
123
- * testable", and that was a real hazard rather than a convenience: pointing ROVECODE_HOME at a fresh
124
- * path — the ordinary way to get an isolated home for a test, a script or a clean-room check — silently
125
- * filled it with a copy of the old credentials. It billed two real API calls during this repository's
126
- * own release verification (2026-09-06) before anyone noticed the scratch home was not scratch. Tests
127
- * that need to exercise the migration pass `legacyDir`; nothing else copies a user's keys into a path
128
- * they chose for isolation.
129
- *
130
- * Any failure is swallowed: a migration that cannot run must not stop the agent from starting — the
131
- * user simply sees "no provider configured" and runs `rovecode connect`. */
132
- let migrated = false;
133
- export function migrateLegacyHome(home: string, opts: { explicit?: boolean; legacyDir?: string; note?: (line: string) => void } = {}): void {
134
- if (migrated && opts.legacyDir === undefined) return;
135
- if (opts.legacyDir === undefined) migrated = true;
136
- try {
137
- if (existsSync(home)) return; // already living in the new place
138
- const legacy = opts.legacyDir ?? join(userHome(), ".cumulus");
139
- // an explicit ROVECODE_HOME is a request for THIS directory, not for a copy of another one. The
140
- // injected legacyDir does not override it: a test that wants the explicit case must SEE the refusal.
141
- if (opts.explicit === true) return;
142
- if (home === legacy || !existsSync(legacy)) return;
143
- cpSync(legacy, home, { recursive: true });
144
- // and it says so: a copy of someone's credentials appearing in a new directory is not a silent event
145
- (opts.note ?? ((l: string) => console.error(l)))(`migrated ${legacy} → ${home} (the config directory was renamed; the old one is untouched)`);
146
- // the credentials file carries the 0600 the old one had only on POSIX; re-assert it here
147
- const creds = join(home, "credentials.json");
148
- if (existsSync(creds) && process.platform !== "win32") chmodSync(creds, CREDENTIALS_FILE_MODE);
149
- } catch { /* best effort: a failed migration is a fresh config, never a crash */ }
150
- }
151
-
152
- export function credentialsPath(): string {
153
- return join(rovecodeHome(), "credentials.json");
154
- }
155
-
156
- /** Raw file contents: every entry as stored, including unknown `type`s. Missing file,
157
- * unreadable file, or non-object JSON -> {} (upstream orElseSucceed idiom). */
158
- function readRaw(): Record<string, unknown> {
159
- let parsed: unknown;
160
- try {
161
- parsed = JSON.parse(readFileSync(credentialsPath(), "utf8"));
162
- } catch {
163
- return {};
164
- }
165
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {};
166
- return parsed as Record<string, unknown>;
167
- }
168
-
169
- function isApiCredential(value: unknown): value is StoredCredential {
170
- if (typeof value !== "object" || value === null) return false;
171
- const v = value as { type?: unknown; key?: unknown; keyName?: unknown };
172
- // whitespace-only counts as empty: a hand-edited `"key": " "` must not shadow a valid env key
173
- if (v.type !== "api" || typeof v.key !== "string" || v.key.trim().length === 0) return false;
174
- return v.keyName === undefined || typeof v.keyName === "string";
175
- }
176
-
177
- /** Valid "api" credentials only, keyed by provider id (malformed entries dropped). */
178
- export function loadCredentials(): Record<string, StoredCredential> {
179
- const out: Record<string, StoredCredential> = {};
180
- for (const [id, value] of Object.entries(readRaw())) {
181
- if (isApiCredential(value)) out[id] = value;
182
- }
183
- return out;
184
- }
185
-
186
- function isOAuthCredential(value: unknown): value is StoredOAuthCredential {
187
- if (typeof value !== "object" || value === null) return false;
188
- const v = value as { type?: unknown; access?: unknown; refresh?: unknown; expires?: unknown; accountId?: unknown };
189
- if (v.type !== "oauth" || typeof v.access !== "string" || v.access.trim().length === 0) return false;
190
- if (typeof v.refresh !== "string" || typeof v.expires !== "number" || !Number.isFinite(v.expires)) return false;
191
- // `expires` must sit inside Date's range (±8.64e15 ms) or be the "never" sentinel — `auth list` renders it
192
- // through toISOString, which throws RangeError beyond that, and a hand-edited entry is never fatal
193
- if (Math.abs(v.expires) > 8.64e15 && v.expires !== Number.MAX_SAFE_INTEGER) return false;
194
- return v.accountId === undefined || typeof v.accountId === "string";
195
- }
196
-
197
- /** Valid "oauth" credentials only, keyed by provider id (malformed entries dropped). */
198
- export function loadOAuthCredentials(): Record<string, StoredOAuthCredential> {
199
- const out: Record<string, StoredOAuthCredential> = {};
200
- for (const [id, value] of Object.entries(readRaw())) {
201
- if (isOAuthCredential(value)) out[id] = value;
202
- }
203
- return out;
204
- }
205
-
206
- /** Port #76: the mcp-oauth variant — same range rule on `expires` as isOAuthCredential; `access` may be "" (invalidated). */
207
- export function isMcpOAuthRecord(value: unknown): value is StoredMcpOAuthRecord {
208
- if (typeof value !== "object" || value === null) return false;
209
- const v = value as Record<string, unknown>;
210
- if (v.type !== "mcp-oauth" || typeof v.url !== "string" || v.url.length === 0) return false;
211
- if (typeof v.access !== "string" || typeof v.refresh !== "string" || typeof v.expires !== "number" || !Number.isFinite(v.expires)) return false;
212
- if (Math.abs(v.expires) > 8.64e15 && v.expires !== Number.MAX_SAFE_INTEGER) return false;
213
- const ci = v.clientInformation;
214
- if (ci !== undefined && (typeof ci !== "object" || ci === null || typeof (ci as { client_id?: unknown }).client_id !== "string")) return false;
215
- const d = v.discovery;
216
- return d === undefined || (typeof d === "object" && d !== null && !Array.isArray(d));
217
- }
218
-
219
- /** Port #76: one raw entry as stored (any `type`), or undefined. The MCP module validates it (isMcpOAuthRecord). */
220
- export function readEntry(id: string): unknown {
221
- return readRaw()[id];
222
- }
223
-
224
- /** Port #76: store/replace one entry verbatim (0600 on create and rewrite), or delete it (`undefined` — the file goes
225
- * when it was the last entry, like removeCredential). The value is never echoed or thrown. */
226
- export function writeEntry(id: string, value: object | undefined): void {
227
- if (id.trim().length === 0) throw new Error("credential id must not be empty");
228
- if (value === undefined) {
229
- removeCredential(id);
230
- return;
231
- }
232
- const data = readRaw();
233
- data[id] = value;
234
- writeStore(data);
235
- }
236
-
237
- /** Store/replace a provider's OAuth credential (an "api" entry under the same id is replaced —
238
- * one entry per provider). Tokens are never echoed or thrown. */
239
- export function saveOAuthCredential(provider: string, cred: StoredOAuthCredential): void {
240
- const id = provider.trim();
241
- if (id.length === 0) throw new Error("provider id must not be empty");
242
- if (!isOAuthCredential(cred)) throw new Error(`refusing to store a malformed OAuth credential for ${id}`);
243
- const data = readRaw();
244
- data[id] = { type: "oauth", access: cred.access, refresh: cred.refresh, expires: cred.expires, ...(cred.accountId !== undefined ? { accountId: cred.accountId } : {}) };
245
- writeStore(data);
246
- }
247
-
248
- /** Write the store with restrictive permissions.
249
- *
250
- * Windows honesty note: fs mode bits on win32 map only onto the FILE_ATTRIBUTE_READONLY
251
- * flag — 0o600 does NOT create owner-only protection there. Real isolation on Windows
252
- * comes from the NTFS ACL on %USERPROFILE% (inherited by ~/.rovecode), which by default
253
- * denies other non-admin users. So this is best-effort hardening on POSIX (where the
254
- * 0o600/0o700 bits are enforced) and effectively a no-op on Windows beyond the profile
255
- * ACL it inherits — we do not claim otherwise. */
256
- function writeStore(data: Record<string, unknown>): void {
257
- const path = credentialsPath();
258
- mkdirSync(rovecodeHome(), { recursive: true, mode: 0o700 });
259
- writeFileSync(path, JSON.stringify(data, null, 2) + "\n", { mode: CREDENTIALS_FILE_MODE });
260
- try {
261
- chmodSync(path, CREDENTIALS_FILE_MODE); // `mode` above only applies on create; re-assert on rewrites
262
- } catch {
263
- // best-effort (see Windows note) — a failed chmod must not lose the write
264
- }
265
- }
266
-
267
- /** Store/replace the credential for a provider. `secret` is never echoed or thrown. */
268
- export function saveCredential(provider: string, secret: string, keyName?: string): void {
269
- const id = provider.trim();
270
- if (id.length === 0) throw new Error("provider id must not be empty");
271
- if (secret.trim().length === 0) throw new Error(`refusing to store an empty secret for ${id}`);
272
- const data = readRaw();
273
- const entry: StoredCredential = { type: "api", key: secret, keyName: keyName ?? keyNameFor(id) };
274
- data[id] = entry;
275
- writeStore(data);
276
- }
277
-
278
- /** Remove a provider's entry. Returns false (and leaves the file alone) when absent.
279
- * Removing the last entry deletes the file rather than leaving an empty {} around. */
280
- export function removeCredential(provider: string): boolean {
281
- const data = readRaw();
282
- if (!(provider in data)) return false;
283
- delete data[provider];
284
- if (Object.keys(data).length === 0) {
285
- try {
286
- rmSync(credentialsPath());
287
- } catch {
288
- writeStore(data);
289
- }
290
- return true;
291
- }
292
- writeStore(data);
293
- return true;
294
- }
295
-
296
- /** First 4 chars + "…" (bar wording). Secrets of 8 chars or fewer collapse to "…" alone —
297
- * half of a short key is most of the key, so nothing of it is shown. */
298
- export function redactSecret(secret: string): string {
299
- return secret.length > 8 ? secret.slice(0, 4) + "…" : "…";
300
- }
301
-
302
- export interface ProviderListing {
303
- provider: string;
304
- /** the record's `type` — api (`auth set`) or oauth (`auth login`; also the mcp-oauth records of port #76) */
305
- kind: "api" | "oauth";
306
- /** api: the env var the key stands in for; oauth: the literal "oauth token"; mcp-oauth: "mcp server token" */
307
- keyName: string;
308
- redacted: string;
309
- /** oauth only: access-token expiry, ms since the epoch (Number.MAX_SAFE_INTEGER = never) */
310
- expires?: number;
311
- }
312
-
313
- /** Redacted listing for `rovecode auth list`: provider + kind + key NAME + redacted prefix (+ expiry
314
- * for OAuth entries). No secret value — key, access token or refresh token — appears in the records. */
315
- export function listProviders(): ProviderListing[] {
316
- const api: ProviderListing[] = Object.entries(loadCredentials()).map(([provider, cred]) => ({
317
- provider, kind: "api", keyName: cred.keyName ?? keyNameFor(provider), redacted: redactSecret(cred.key),
318
- }));
319
- const oauth: ProviderListing[] = Object.entries(loadOAuthCredentials()).map(([provider, cred]) => ({
320
- provider, kind: "oauth", keyName: "oauth token", redacted: redactSecret(cred.access), expires: cred.expires,
321
- }));
322
- // port #76: MCP server tokens (`mcp:<name>`) — listed and removable like any entry, never a chat provider
323
- const mcp: ProviderListing[] = Object.entries(readRaw()).flatMap(([provider, value]) => isMcpOAuthRecord(value)
324
- ? [{ provider, kind: "oauth" as const, keyName: "mcp server token", redacted: redactSecret(value.access), expires: value.expires }]
325
- : []);
326
- return [...api, ...oauth, ...mcp].sort((a, b) => a.provider.localeCompare(b.provider));
327
- }
328
-
329
- /** rovecode provider id -> models.dev provider key, mirroring catalog.ts PROVIDER_MAP for the
330
- * non-identity ids (together -> "togetherai", fireworks -> "fireworks-ai") plus an
331
- * auth-only alias: moonshot -> "moonshotai" (models.dev has no bare "moonshot" key — the
332
- * catalog reaches it via VENDOR_PREFIX_MAP on model ids instead, which auth cannot use).
333
- * Identity ids (anthropic/openai/deepseek/openrouter/...) need no entry: keyNameFor tries
334
- * the id itself against the snapshot first. */
335
- const AUTH_PROVIDER_MAP: Record<string, string> = {
336
- together: "togetherai",
337
- fireworks: "fireworks-ai",
338
- moonshot: "moonshotai",
339
- };
340
-
341
- /** Which env var / key name a provider expects. models.dev drives this (snapshot
342
- * Provider.env, e.g. anthropic -> ANTHROPIC_API_KEY — same source opencode's provider
343
- * loader reads at provider.ts:1583 @ ebece6e); providers absent from models.dev (kaesra,
344
- * ollama, moondream, vllm) fall back to <ID>_API_KEY, which matches every envKey in
345
- * stream.ts builtinProviders by construction. */
346
- export function keyNameFor(providerId: string): string {
347
- const key = AUTH_PROVIDER_MAP[providerId] ?? providerId;
348
- const env = snapshotProvidersLazy()[key]?.env;
349
- if (env !== undefined && env.length > 0 && env[0]) return env[0];
350
- return providerId.toUpperCase().replace(/[^A-Z0-9]+/g, "_") + "_API_KEY";
351
- }
352
-
353
- // ---------- secret prompt (`rovecode auth set`) ----------
354
-
355
- type SecretInput = NodeJS.ReadableStream & { isTTY?: boolean; isRaw?: boolean };
356
- type SecretOutput = NodeJS.WritableStream & { columns?: number };
357
- /** Streams readSecret talks to — injectable so tests can drive a fake TTY in-process. */
358
- export interface SecretPromptIO { input?: SecretInput; output?: SecretOutput }
359
-
360
- /** Cursor-control sequence for the cooked-mode fallback: after Enter the terminal has echoed
361
- * prompt+line and moved to the next row, so erase ONE row per wrapped row the echo occupied
362
- * (cursor-up + erase-line each), then "\r". A single row is not enough — a 33-col prompt plus
363
- * a 108-char Anthropic key wraps on any terminal narrower than 141 columns. Pure: pinned by
364
- * the headless-xterm test at 80 and 120 columns. Unknown/zero width assumes 80. */
365
- export function echoScrubSequence(promptLen: number, lineLen: number, columns: number): string {
366
- const cols = columns > 0 ? Math.floor(columns) : 80;
367
- const rows = Math.max(1, Math.ceil((promptLen + lineLen) / cols));
368
- return "\x1b[1A\x1b[2K".repeat(rows) + "\r";
369
- }
370
-
371
- /** Read one secret line for `rovecode auth set`. What is guaranteed:
372
- * - TTY stdin: the prompt goes to stderr and readline runs in terminal mode, which calls
373
- * setRawMode(true): the terminal driver's echo is OFF and readline's own echo goes to a
374
- * sink, so the keystrokes are never written to the terminal at all. Verified on a real
375
- * Windows console under Bun 1.3.14 — the console input mode drops ENABLE_ECHO_INPUT and
376
- * ENABLE_LINE_INPUT, the screen buffer stays clean, and the mode is restored on close.
377
- * - Fallback, only if raw mode did not take (no/failing setRawMode, input.isRaw stays false):
378
- * the driver echoed, so after Enter every row the prompt+echo wrapped onto is erased
379
- * (echoScrubSequence). A terminal that ignores VT cursor sequences keeps that echo.
380
- * - Piped stdin (scripts, tests): one line, no prompt, nothing written to any stream.
381
- * The value is returned trimmed and is never logged or embedded in an error. */
382
- export function readSecret(promptText: string, io: SecretPromptIO = {}): Promise<string> {
383
- const input = io.input ?? process.stdin;
384
- const output = io.output ?? process.stderr;
385
- const tty = input.isTTY === true;
386
- if (tty) output.write(promptText);
387
- const rl = tty
388
- ? createInterface({ input, output: new Writable({ write: (_c, _e, cb) => cb() }), terminal: true })
389
- : createInterface({ input });
390
- const raw = tty && input.isRaw === true;
391
- return new Promise((resolve) => {
392
- let settled = false; // rl.close() emits "close" SYNCHRONOUSLY — guard the race
393
- rl.once("line", (line) => {
394
- if (settled) return;
395
- settled = true;
396
- // raw: nothing was echoed, just end the prompt line; not raw: erase the echoed rows
397
- if (tty) output.write(raw ? "\n" : echoScrubSequence(promptText.length, line.length, output.columns ?? 80));
398
- rl.close();
399
- resolve(line.trim());
400
- });
401
- rl.once("close", () => { // EOF / Ctrl+C / Ctrl+D without a line
402
- if (settled) return;
403
- settled = true;
404
- if (tty) output.write("\n");
405
- resolve("");
406
- });
407
- });
408
- }
@@ -1,223 +0,0 @@
1
- /** Anthropic prompt-cache boundary placement — pure transform for the /messages request body.
2
- *
3
- * Prompt caching is a PREFIX match: the API render order is tools → system → messages, and a
4
- * `cache_control: {type:"ephemeral"}` marker on a content block caches everything from the start
5
- * of the request up to and including that block. Any byte change before a marker invalidates it.
6
- * Therefore boundaries only pay off on STABLE prefixes — a boundary after content that changes
7
- * every turn is a wasted breakpoint (Anthropic allows at most 4 per request).
8
- *
9
- * Pattern reference (hermes-agent snapshot, research/source_snapshots/hermes-agent):
10
- * - agent/prompt_caching.py:1-8 — "The default layout uses 4 cache_control breakpoints: the static
11
- * system prefix, the end of the system prompt, and the last 2 non-system messages."
12
- * - agent/prompt_caching.py:157-162 (_build_marker) — the marker is `{"type": "ephemeral"}`.
13
- * - agent/prompt_caching.py:113-121 (_apply_cache_marker) — string content is converted to a
14
- * single text block carrying the marker; list content gets the marker on its LAST block.
15
- * - agent/prompt_caching.py:99-103, 331-333 — volatile tails ride UNMARKED ("a changed ticket ID
16
- * or timestamp no longer invalidates the whole skill body"), and empty text blocks are never
17
- * emitted (HTTP 400 on native Anthropic).
18
- * - agent/prompt_caching.py:567-573 (apply_anthropic_cache_control) — idempotence contract:
19
- * re-application "can never accumulate past 4 markers".
20
- * - agent/system_prompt.py:911-924 and agent/turn_context.py:214-217 — the stability principle:
21
- * volatile per-turn facts are kept OUT of / AFTER the cached band so the prefix stays
22
- * byte-stable turn-over-turn.
23
- *
24
- * Placement policy implemented here (deliberately simpler than Hermes' 4-marker layout):
25
- * (1) SYSTEM boundary — `system: string` is converted to
26
- * `[{type:"text", text, cache_control:{type:"ephemeral"}}]` when it is ≥ minChunkChars.
27
- * A system-block array already provided by the caller gets the marker on its last block
28
- * (unless any block is already marked). Because caching is prefix-based, this single marker
29
- * also covers the `tools` array rendered before `system` — no separate tools marker needed.
30
- * (2) CONVERSATION-PREFIX boundary — the marker goes on the LAST content block of the most
31
- * recent message that has ≥ 2 messages after it (index length-3): an agent turn appends
32
- * the trailing exchange (assistant tool_use + tool_result, or user + assistant), so the
33
- * tail churns every turn while the prefix up to there is CANONICALLY stable — not
34
- * byte-identical. Two rewrites make consecutive requests differ in raw bytes: the marker
35
- * moves forward each turn (dropped from the old anchor, added at the new one), and the
36
- * marked message's string content is rewritten to [{type:"text",...}] by
37
- * withMarkerOnLastBlock below. Anthropic's cache key normalizes exactly those two
38
- * (cache_control is placement metadata, and string content is equivalent to a single
39
- * text block), so the previous request's prefix still cache-hits. Pinned by the
40
- * consecutive-turn stability test in test/unit/cache.test.ts.
41
- * If the anchor message cannot carry a marker (empty content — cf. Hermes _can_carry_marker,
42
- * prompt_caching.py:127-140, "so the breakpoints land on messages that count"), the
43
- * boundary reallocates to the nearest earlier eligible message. String content is
44
- * converted to a one-block array, preserving the text byte-for-byte.
45
- * (3) BUDGET — never exceed maxBreakpoints total, counting markers already present in
46
- * system/messages/tools before placing new ones. System wins over conversation prefix.
47
- * (4) IDEMPOTENT — a position that already carries a marker is left untouched, existing markers
48
- * count toward the budget, and untouched inputs are returned by reference, so
49
- * apply(apply(body)) deep-equals apply(body).
50
- * (5) Bodies without messages/system pass through untouched (same reference), as does anything
51
- * whose shapes don't match — this is a best-effort decorator, never a validator.
52
- *
53
- * minChunkChars default: 4096 chars ≈ 1024 tokens (~4 chars/token) — the documented minimum
54
- * cacheable prefix for Sonnet/Opus-class models (model-dependent, 512–4096 tokens per the API
55
- * docs). Prefixes below the model minimum are silently NOT cached, so spending a breakpoint on
56
- * them is pure waste. For the message boundary the gate compares the CUMULATIVE prefix
57
- * (system + messages[0..anchor]) against minChunkChars, since that whole span is what the
58
- * marker would cache (tools text is not counted — a conservative undercount).
59
- *
60
- * Pure: the input body is never mutated; modified paths are copied copy-on-write.
61
- */
62
-
63
- export interface CacheOptions {
64
- /** Maximum cache_control markers allowed in the request. Anthropic's limit — and the default — is 4. */
65
- maxBreakpoints?: number;
66
- /** Don't spend a breakpoint on a prefix shorter than this many characters. Default 4096 (≈1024 tokens). */
67
- minChunkChars?: number;
68
- }
69
-
70
- export interface CacheControl { type: "ephemeral" }
71
-
72
- /** Loose ("-ish") view of the body anthropicStream builds (src/providers/stream.ts:152-160):
73
- * `{ model, max_tokens, system?: string, messages: [...], tools?: [...] }`. Every field is
74
- * unknown-typed so the adapter's `Record<string, unknown>` is directly assignable; this module
75
- * narrows at runtime and passes through anything it does not recognize. */
76
- export interface AnthropicishBody {
77
- system?: unknown;
78
- messages?: unknown;
79
- tools?: unknown;
80
- [key: string]: unknown;
81
- }
82
-
83
- export const DEFAULT_MAX_BREAKPOINTS = 4;
84
- export const DEFAULT_MIN_CHUNK_CHARS = 4096;
85
-
86
- const EPHEMERAL: CacheControl = { type: "ephemeral" };
87
-
88
- type Rec = Record<string, unknown>;
89
-
90
- function isRec(v: unknown): v is Rec {
91
- return typeof v === "object" && v !== null && !Array.isArray(v);
92
- }
93
-
94
- /** Marker presence on a content block / tool entry (block-level only — the shapes this module emits). */
95
- function hasMarker(block: unknown): boolean {
96
- return isRec(block) && block["cache_control"] !== undefined;
97
- }
98
-
99
- function blockText(block: unknown): string {
100
- if (!isRec(block)) return "";
101
- return typeof block["text"] === "string" ? block["text"] : "";
102
- }
103
-
104
- function systemChars(system: unknown): number {
105
- if (typeof system === "string") return system.length;
106
- if (Array.isArray(system)) return system.reduce((n: number, b) => n + blockText(b).length, 0);
107
- return 0;
108
- }
109
-
110
- function contentChars(content: unknown): number {
111
- if (typeof content === "string") return content.length;
112
- if (!Array.isArray(content)) return 0;
113
- let n = 0;
114
- for (const b of content) {
115
- n += blockText(b).length;
116
- // tool_result blocks carry their payload under "content" (string form only; arrays undercount)
117
- if (isRec(b) && typeof b["content"] === "string") n += b["content"].length;
118
- }
119
- return n;
120
- }
121
-
122
- function messageMarkerCount(msg: unknown): number {
123
- if (!isRec(msg)) return 0;
124
- const content = msg["content"];
125
- return Array.isArray(content) ? content.filter(hasMarker).length : 0;
126
- }
127
-
128
- function countExistingMarkers(system: unknown, messages: readonly unknown[] | null, tools: unknown): number {
129
- let n = 0;
130
- if (Array.isArray(system)) n += system.filter(hasMarker).length;
131
- for (const m of messages ?? []) n += messageMarkerCount(m);
132
- if (Array.isArray(tools)) n += tools.filter(hasMarker).length;
133
- return n;
134
- }
135
-
136
- /** A marker on this message would actually land on the wire: non-empty string content (an empty
137
- * string would convert to an empty text block — HTTP 400), or a non-empty block array whose last
138
- * element is an object. Mirrors Hermes _can_carry_marker (prompt_caching.py:127-154). */
139
- function canCarryMarker(msg: unknown): boolean {
140
- if (!isRec(msg)) return false;
141
- const content = msg["content"];
142
- if (typeof content === "string") return content.length > 0;
143
- if (Array.isArray(content) && content.length > 0) return isRec(content[content.length - 1]);
144
- return false;
145
- }
146
-
147
- /** Copy of `msg` with the ephemeral marker on its last content block; string content becomes a
148
- * one-block array with the text preserved byte-for-byte (Hermes prompt_caching.py:113-121). */
149
- function withMarkerOnLastBlock(msg: Rec): Rec {
150
- const content = msg["content"];
151
- if (typeof content === "string") {
152
- return { ...msg, content: [{ type: "text", text: content, cache_control: EPHEMERAL }] };
153
- }
154
- if (Array.isArray(content) && content.length > 0) {
155
- const last = content[content.length - 1];
156
- if (isRec(last)) {
157
- const blocks = content.slice();
158
- blocks[blocks.length - 1] = { ...last, cache_control: EPHEMERAL };
159
- return { ...msg, content: blocks };
160
- }
161
- }
162
- return msg; // callers gate on canCarryMarker(); unreachable in practice
163
- }
164
-
165
- export function applyAnthropicCacheBoundaries(body: AnthropicishBody, opts?: CacheOptions): AnthropicishBody {
166
- const maxBreakpoints = opts?.maxBreakpoints ?? DEFAULT_MAX_BREAKPOINTS;
167
- const minChunkChars = opts?.minChunkChars ?? DEFAULT_MIN_CHUNK_CHARS;
168
-
169
- const system = body.system;
170
- const messages: readonly unknown[] | null = Array.isArray(body.messages) ? body.messages : null;
171
-
172
- const hasSystem = (typeof system === "string" && system.length > 0) || (Array.isArray(system) && system.length > 0);
173
- const hasMessages = messages !== null && messages.length > 0;
174
- if (!hasSystem && !hasMessages) return body; // policy (5): pass through untouched
175
-
176
- let used = countExistingMarkers(system, messages, body.tools);
177
- let out = body;
178
- const copyOnWrite = (): AnthropicishBody => (out === body ? (out = { ...body }) : out);
179
-
180
- // ---- policy (1): system boundary ------------------------------------------------------------
181
- if (used < maxBreakpoints && hasSystem) {
182
- if (typeof system === "string") {
183
- if (system.length >= minChunkChars) {
184
- copyOnWrite().system = [{ type: "text", text: system, cache_control: EPHEMERAL }];
185
- used += 1;
186
- }
187
- } else if (Array.isArray(system) && !system.some(hasMarker)) {
188
- const last = system[system.length - 1];
189
- if (isRec(last) && systemChars(system) >= minChunkChars) {
190
- const blocks = system.slice();
191
- blocks[blocks.length - 1] = { ...last, cache_control: EPHEMERAL };
192
- copyOnWrite().system = blocks;
193
- used += 1;
194
- }
195
- }
196
- }
197
-
198
- // ---- policy (2): conversation-prefix boundary ------------------------------------------------
199
- // Anchor = most recent message with ≥2 messages after it (index length-3); the trailing two
200
- // messages are this turn's churn. Reallocate backwards past marker-incapable messages.
201
- if (used < maxBreakpoints && messages !== null && messages.length >= 3) {
202
- let anchor = -1;
203
- for (let i = messages.length - 3; i >= 0; i -= 1) {
204
- if (canCarryMarker(messages[i])) { anchor = i; break; }
205
- }
206
- const msg = anchor >= 0 ? messages[anchor] : undefined;
207
- if (isRec(msg) && messageMarkerCount(msg) === 0) {
208
- let prefixChars = systemChars(system);
209
- for (let i = 0; i <= anchor; i += 1) {
210
- const m = messages[i];
211
- if (isRec(m)) prefixChars += contentChars(m["content"]);
212
- }
213
- if (prefixChars >= minChunkChars) {
214
- const next = messages.slice();
215
- next[anchor] = withMarkerOnLastBlock(msg);
216
- copyOnWrite().messages = next;
217
- used += 1;
218
- }
219
- }
220
- }
221
-
222
- return out;
223
- }