rovecode 0.3.3 → 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 -48
  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 +83 -72
  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,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
- }