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,169 +0,0 @@
1
- /** Sextant renderer-local commands (port #43): the slash lines and palette actions the renderer
2
- * answers itself — /help /theme /open /diff /focus /agents — the /undo /permissions /mode notes
3
- * (ALIAS_NOTE) and the state-then-hook effect helpers keys.ts shares with them. Ported from the
4
- * user's own sextant v0.4.0 prototype app.js:1024-1029 (runSlash) and :944 (openFile); split out
5
- * of keys.ts for the line budget — also the home for #44's further renderer-local commands.
6
- * Pure over (state, ctx): no clock, no timers, no process access. A state field is written BEFORE
7
- * the matching KeyCtx.local hook fires, so the renderer only loads content / rebuilds the palette.
8
- * Everything that is not renderer-local (built-ins, custom commands, `!cmd`, free text, unknown
9
- * `/x`) reaches ctx.hooks.onSubmit unchanged — app.ts handleSlash owns the rest. */
10
-
11
- import type { KeyCtx } from "./keys.ts";
12
- import { parseInput, resolveFile } from "./overlays.ts";
13
- import { expandMentions } from "./mentions.ts";
14
- import { THEME_ORDER, type CodeMode, type Focus, type SextantState, type ThemeName } from "./types.ts";
15
- import { openHelp, openNotices } from "./overlays.ts";
16
-
17
- /** prototype commands with an rovecode equivalent: a toast instead of a submission — unless
18
- * setCommands lists a custom command of that name, which then runs like any other.
19
- * `undo` is GONE from here since port #65: /undo is a real built-in that reaches onSubmit
20
- * (tui/git-cmds.ts restores the previous checkpoint), no longer an alias note. */
21
- export const ALIAS_NOTE: Record<string, string> = {
22
- permissions: "/permissions → use /yolo",
23
- mode: "/mode → use /plan or /act",
24
- };
25
-
26
- const isTheme = (v: string): v is ThemeName => (THEME_ORDER as readonly string[]).includes(v);
27
-
28
- // ------------------------------------------------------------------ effects (state first, then the hook)
29
-
30
- export function setTheme(s: SextantState, ctx: KeyCtx, name: ThemeName): void {
31
- s.theme = name;
32
- ctx.local.setTheme(name);
33
- }
34
-
35
- export function setMode(s: SextantState, ctx: KeyCtx, mode: CodeMode): void {
36
- s.code.mode = mode;
37
- s.code.scroll = 0;
38
- ctx.local.setMode(mode);
39
- }
40
-
41
- /** the crew board in the code panel (⌃a, /agents): lane closed, focus follows */
42
- export function showAgents(s: SextantState, ctx: KeyCtx): void {
43
- setMode(s, ctx, "agents");
44
- s.code.laneOpen = false;
45
- s.focus = "code";
46
- }
47
-
48
- /** app.js:944 — parent dirs expand, the code panel shows the file; focus is the caller's call */
49
- export function openFile(s: SextantState, ctx: KeyCtx, path: string): void {
50
- const parts = path.split("/");
51
- for (let i = 1; i < parts.length; i++) s.files.expanded.add(parts.slice(0, i).join("/"));
52
- s.code.file = path;
53
- s.code.mode = "code";
54
- s.code.scroll = 0;
55
- ctx.local.openFile(path);
56
- }
57
-
58
- /** the files panel only takes focus while the layout shows it (≥ 140 columns) */
59
- export function setFocus(s: SextantState, ctx: KeyCtx, f: Focus): void {
60
- // a narrow terminal has no files column: page files into the main slot (draw-tabs.ts) instead of
61
- // refusing — "the files panel needs ≥ 140 columns" was the old answer, and it left files unreachable
62
- if (f === "files" && !ctx.layout.files) s.page = "files";
63
- s.focus = f;
64
- }
65
-
66
- // ------------------------------------------------------------------ dispatch
67
-
68
- /** renderer-local slash commands run here; everything else (built-ins, custom, `!cmd`, free
69
- * text, unknown `/x`) reaches onSubmit unchanged — app.ts handleSlash owns the rest */
70
- export function dispatch(s: SextantState, text: string, ctx: KeyCtx): void {
71
- // the connect wizard IS this surface's /connect — THE connect command (Berkay, 2026-09-10: setup
72
- // is gone from the table; /setup still opens the wizard as a soft alias for muscle memory). With
73
- // an argument, /connect keeps its one-line contract: the answers are on the line
74
- if (text === "/setup" || text === "/connect") { ctx.local.openWizard(); return; }
75
- const p = parseInput(text);
76
- // `@file` in free text: the file goes with the message as a `read` result (mentions.ts) — this is the one
77
- // consumer of parseInput's `mentions`, and what the footer's "@ mentions attach files" has meant since
78
- if (p.kind === "text" && p.mentions.length > 0) {
79
- // port #54: an IMAGE mention takes the /attach seam (local.attachImage → the store's stage) instead of
80
- // dying as "a binary file" — it reaches the model as an image part and spends none of the text budget
81
- const r = expandMentions(text, { cwd: s.cwd, mentions: p.mentions, resolve: (m) => resolveFile(m, s.files.paths), attachImage: ctx.local.attachImage?.bind(ctx.local) });
82
- for (const n of r.notes) ctx.local.toast(n);
83
- ctx.hooks.onSubmit(r.text);
84
- return;
85
- }
86
- if (p.kind !== "slash" || !runLocal(s, p.cmd ?? "", p.arg ?? "", ctx)) ctx.hooks.onSubmit(text);
87
- }
88
-
89
- /** true when the line was answered here and must not reach onSubmit */
90
- export function runLocal(s: SextantState, cmd: string, arg: string, ctx: KeyCtx): boolean {
91
- switch (cmd) {
92
- case "help":
93
- openHelp(s);
94
- return false; // the card opens AND /help reaches the transcript
95
- case "notices":
96
- openNotices(s);
97
- return true;
98
- case "market":
99
- // the overlay opens empty with a "loading" status; the renderer fills it when the catalog answers
100
- ctx.local.openMarket();
101
- return true;
102
- case "context":
103
- // counting is synchronous but not free on a long transcript, so the renderer does it off the frame
104
- ctx.local.openContext();
105
- case "wizard":
106
- // /connect and /setup on the sextant surface: the columned connect wizard (draw-wizard.ts),
107
- // reached here so the classic surface keeps its own pickOne chain untouched
108
- ctx.local.openWizard();
109
- return true;
110
- return true;
111
- case "theme":
112
- if (isTheme(arg)) setTheme(s, ctx, arg);
113
- else ctx.local.toast(`unknown theme "${arg}" · night, ember or contrast`);
114
- return true;
115
- case "open": {
116
- const p = resolveFile(arg, s.files.paths, ctx.fuzzy);
117
- if (!p) { ctx.local.toast(arg ? `no file matches "${arg}"` : "usage: /open <file>"); return true; }
118
- openFile(s, ctx, p);
119
- s.focus = "code";
120
- return true;
121
- }
122
- case "diff": {
123
- if (arg) {
124
- const p = resolveFile(arg, s.files.paths, ctx.fuzzy);
125
- if (!p) { ctx.local.toast(`no file matches "${arg}"`); return true; }
126
- s.code.file = p;
127
- }
128
- setMode(s, ctx, "diff");
129
- return true;
130
- }
131
- case "focus":
132
- if (arg === "messages" || arg === "code" || arg === "files") setFocus(s, ctx, arg);
133
- else ctx.local.toast("focus is messages, code or files");
134
- return true;
135
- case "agents":
136
- if (arg) return false; // port #62: `/agents list` (any argument) reaches app.ts handleSlash — the definitions list; the BARE /agents is this board
137
- showAgents(s, ctx);
138
- return true;
139
- default: {
140
- const note = ALIAS_NOTE[cmd];
141
- // a custom command of that name (listed by setCommands) wins over the note
142
- if (note && !s.commands.some((c) => c.name === cmd)) { ctx.local.toast(note); return true; }
143
- return false;
144
- }
145
- }
146
- }
147
-
148
- /** palette actions (overlays.ts paletteItems): a slash line, or theme: / mode: / focus: / open: */
149
- export function runAction(s: SextantState, action: string, ctx: KeyCtx): void {
150
- if (action.startsWith("/")) {
151
- const p = parseInput(action), needsArg = ["theme", "open", "focus"].includes(p.cmd ?? "");
152
- if (needsArg && !p.arg) { // the argument is still missing: park the line in the prompt, options open
153
- s.input.text = action + " ";
154
- s.input.cur = s.input.text.length;
155
- s.input.sgSel = 0;
156
- s.focus = "messages";
157
- return;
158
- }
159
- dispatch(s, action, ctx);
160
- return;
161
- }
162
- const i = action.indexOf(":"), kind = action.slice(0, i), rest = action.slice(i + 1);
163
- if (kind === "theme" && isTheme(rest)) setTheme(s, ctx, rest);
164
- else if (kind === "mode") {
165
- setMode(s, ctx, rest as CodeMode);
166
- if (rest === "agents") s.code.laneOpen = false;
167
- } else if (kind === "focus") setFocus(s, ctx, rest as Focus);
168
- else if (kind === "open") { openFile(s, ctx, rest); s.focus = "code"; }
169
- }
@@ -1,287 +0,0 @@
1
- /** The seam between the market overlay and the market module.
2
- *
3
- * draw-market.ts is pure over (state, screen): it takes rows and a status and never fetches. This file is
4
- * the only place that knows src/market/ exists — it lazily imports the module (the boot rule: nothing in
5
- * the market's dependency tree is loaded until someone opens /market), maps src/market/types.ts onto the
6
- * overlay's flattened view rows, and turns every failure into a status the overlay can draw.
7
- *
8
- * Nothing here throws for a data problem, because the module it wraps does not either: a source that fails
9
- * comes back as `SourceStatus {ok:false, reason}` and is shown as the error state, a source answered from
10
- * the cache is shown as the offline state with its age, and an install that fails is a `{ok:false, error}`
11
- * outcome printed on the plan card. The one thing this file DOES guard is the module not being there yet:
12
- * while src/market/ is being written (types.ts landed first), an absent registry.ts must read as "the
13
- * market module is not wired yet", not as a crash in the middle of a frame. */
14
-
15
- import type { MarketDocLine, MarketPlan, MarketStatus, MarketViewRow } from "./draw-market.ts";
16
- import { npxPackage } from "../mcp/local-package.ts";
17
- import type { MarketInstall } from "../mcp/market.ts";
18
-
19
- /** what the overlay needs to open: the rows, why they are what they are, and anything worth saying once */
20
- export interface MarketLoad {
21
- rows: MarketViewRow[];
22
- status: MarketStatus;
23
- notes: string[];
24
- }
25
-
26
- /** src/market/types.ts, structurally — declared here so this file compiles before the module lands and so
27
- * the overlay never imports the market's types directly */
28
- interface Env { name: string; description?: string; required: boolean; secret: boolean; default?: string }
29
- interface Item {
30
- id: string; kind: "mcp" | "skill" | "plugin"; title: string; publisher: string; description: string;
31
- version?: string; repository?: string; homepage?: string; tags: string[]; status?: string;
32
- env: Env[]; install: unknown; planNote?: string[];
33
- /** the item's own documentation, as the catalog carries it (nimbus-24's writer): third-party markdown */
34
- docs?: { source: string; format: string; bytes: number; truncated: boolean; body: string };
35
- }
36
- interface Row extends Item {
37
- installed?: { path: string; scope: "user" | "project"; version?: string; updateAvailable?: boolean; trusted?: boolean };
38
- }
39
- type Status =
40
- | { ok: true; from: "live" }
41
- | { ok: true; from: "cache"; ageMs: number }
42
- | { ok: true; from: "curated" }
43
- /** the source was not consulted at all (an empty query never asks the registry, --offline skips it) */
44
- | { ok: true; from: "skipped"; why: string }
45
- | { ok: false; reason: string };
46
- interface Result { items: Item[]; sources: Record<string, Status>; notes: string[] }
47
- interface PlanView {
48
- item: Item; target: string; scope: "user" | "project"; preview: string[];
49
- asks: Env[]; pending: string[]; replaces?: string;
50
- }
51
- type Outcome =
52
- | { ok: true; item: Item; target: string; scope: "user" | "project"; envNames: string[]; trusted?: boolean; next?: string;
53
- /** install-once: what npm put on disk (src/market/types.ts InstallOutcome.package) */
54
- package?: { name: string; version: string; prefix: string; integrity?: string; missing?: string[] } }
55
- | { ok: false; error: string };
56
- /** the plan/install options the overlay passes through; `local` is the chooser's answer (mcp/local-package.ts) */
57
- interface Ctx { scope: "user" | "project"; cwd: string; home: string; local?: boolean }
58
-
59
- /** the module's public surface, as much of it as the overlay uses (src/market/registry.ts + install.ts;
60
- * there is no barrel file, so the two are imported separately) */
61
- interface RegistryModule {
62
- searchMarket(query: string, deps?: { offline?: boolean; withDocs?: boolean }): Promise<Result>;
63
- /** one item WITH its documentation body: search and list deliberately leave the bodies out (they are
64
- * hundreds of kilobytes the list never reads), so the docs pane asks for the row it is about to show */
65
- findItem(kind: "mcp" | "skill" | "plugin", id: string, deps?: { offline?: boolean }): Promise<{ item?: Item; notes: string[] }>;
66
- }
67
- interface InstallModule {
68
- planInstall(item: Item, opts: Ctx): PlanView | { error: string };
69
- runInstall(plan: PlanView, answers: Record<string, string>, opts: Ctx, deps?: unknown): Promise<Outcome>;
70
- withInstalled(items: readonly Item[], cwd: string, home: string): Row[];
71
- }
72
-
73
- /** "the market module is not wired yet" rather than an exception mid-frame */
74
- const NOT_WIRED = "the market module is not available in this build (src/market/ is still landing)";
75
-
76
- async function load(): Promise<{ registry: RegistryModule; install: InstallModule } | null> {
77
- try {
78
- // two lazy imports, resolved at call time so a missing file is a null, not a boot failure
79
- const [registry, install] = await Promise.all([
80
- import("../market/registry.ts") as Promise<Partial<RegistryModule>>,
81
- import("../market/install.ts") as Promise<Partial<InstallModule>>,
82
- ]);
83
- if (typeof registry.searchMarket !== "function" || typeof install.planInstall !== "function") return null;
84
- return { registry: registry as RegistryModule, install: install as InstallModule };
85
- } catch {
86
- return null;
87
- }
88
- }
89
-
90
- /** what an item runs once installed, in one line — the install spec's own words, per arm */
91
- function runsLine(item: Item): string {
92
- const spec = item.install as { kind?: string; entry?: { installs?: { kind?: string; url?: string; command?: string; args?: string[]; runtime?: string }[] }; source?: unknown; git?: boolean; files?: { path: string }[] } | undefined;
93
- if (!spec) return "";
94
- if (spec.kind === "mcp") {
95
- const first = spec.entry?.installs?.[0];
96
- if (!first) return "";
97
- return first.kind === "http" ? `remote ${first.url ?? ""}` : [first.command, ...(first.args ?? [])].join(" ");
98
- }
99
- if (spec.kind === "plugin") return `${spec.git ? "clone" : "copy"} ${String(spec.source ?? "")}`;
100
- if (spec.kind === "skill") {
101
- if (spec.files?.length) return `${spec.files.length} file(s), written verbatim — runs nothing`;
102
- const src = spec.source as { git?: string; subfolder?: string } | undefined;
103
- return src?.git ? `clone ${src.git}${src.subfolder ? ` (${src.subfolder})` : ""}` : "a SKILL.md the model reads when it matches";
104
- }
105
- return "";
106
- }
107
-
108
- /** the other ways in an MCP entry offers (a remote endpoint beside a local runtime) */
109
- function alternatives(item: Item): string[] {
110
- const spec = item.install as { kind?: string; entry?: { installs?: { kind?: string; url?: string; command?: string; args?: string[]; runtime?: string }[] } } | undefined;
111
- if (spec?.kind !== "mcp") return [];
112
- return (spec.entry?.installs ?? []).slice(1).map((i) => (i.kind === "http" ? `remote ${i.url ?? ""}` : `${i.runtime ?? "stdio"}: ${[i.command, ...(i.args ?? [])].join(" ")}`));
113
- }
114
-
115
- /** the npm package an mcp row's first form would run through npx — the install-once offer's subject */
116
- function localOfferOf(item: Item): string | undefined {
117
- const spec = item.install as { kind?: string; entry?: { installs?: unknown[] } } | undefined;
118
- if (spec?.kind !== "mcp") return undefined;
119
- const first = spec.entry?.installs?.[0];
120
- if (first === undefined || typeof first !== "object" || first === null) return undefined;
121
- return npxPackage(first as MarketInstall)?.spec;
122
- }
123
-
124
- export function toViewRow(row: Row): MarketViewRow {
125
- const localOffer = localOfferOf(row);
126
- return {
127
- ...(localOffer !== undefined ? { localOffer } : {}),
128
- id: row.id,
129
- kind: row.kind,
130
- title: row.title,
131
- publisher: row.publisher,
132
- description: row.description,
133
- ...(row.version !== undefined ? { version: row.version } : {}),
134
- runs: runsLine(row),
135
- env: row.env.map((v) => ({ name: v.name, required: v.required, secret: v.secret, ...(v.description !== undefined ? { description: v.description } : {}) })),
136
- alternatives: alternatives(row),
137
- pending: row.planNote ?? [],
138
- ...(row.installed ? { installed: row.installed } : {}),
139
- // search carries the metadata, not the body: the pane fills `lines` from docsFor() when it opens
140
- ...(row.docs ? { docs: { source: row.docs.source, truncated: row.docs.truncated === true, lines: typeof row.docs.body === "string" && row.docs.body !== "" ? docLines(row.docs.body) : [] } } : {}),
141
- };
142
- }
143
-
144
- /** ESC and the rest of C0 (tab and newline excepted), DEL, and the C1 range some terminals still read
145
- * as CSI — a document must not be able to move the cursor, change a colour or clear the screen */
146
- // eslint-disable-next-line no-control-regex
147
- const CONTROL = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/g;
148
-
149
- /** A document, flattened to lines a cockpit can draw and stripped of everything a terminal would obey.
150
- * The body is third-party text, so escape sequences and control characters go before anything else; then
151
- * markdown is flattened rather than rendered — headings and fenced code keep their shape, and the rest is
152
- * prose wrapped to a column. A README must not be able to move the cursor or repaint the screen. */
153
- export function docLines(body: string, width = 96): MarketDocLine[] {
154
- const safe = body
155
- .replace(/\r\n?/g, "\n")
156
- .replace(/<(script|style|iframe|object|embed|template|noscript)\b[\s\S]*?<\/\1\s*>/gi, "")
157
- .replace(/<\/?[a-zA-Z][^>]*>/g, "")
158
- .replace(CONTROL, "")
159
- .replace(/\t/g, " ")
160
- .replace(/^---\n[\s\S]*?\n---\n/, "");
161
- const out: MarketDocLine[] = [];
162
- let fence = false;
163
- for (const raw of safe.split("\n")) {
164
- if (/^\s*```/.test(raw)) { fence = !fence; out.push({ kind: "rule", text: "" }); continue; }
165
- if (fence) { out.push({ kind: "code", text: raw.slice(0, width) }); continue; }
166
- const h = /^(#{1,6})\s+(.*)$/.exec(raw);
167
- if (h) { out.push({ kind: "head", text: h[2]!.replace(/[`*]/g, "").slice(0, width) }); continue; }
168
- const text = raw.replace(/\[([^\]]+)\]\([^)]*\)/g, "$1").replace(/[`*]/g, "");
169
- if (text.trim() === "") { out.push({ kind: "blank", text: "" }); continue; }
170
- let line = "";
171
- for (const word of text.split(/\s+/).filter(Boolean)) {
172
- const piece = word.length > width ? word.slice(0, width) : word;
173
- if (!line) { line = piece; continue; }
174
- if (line.length + 1 + piece.length <= width) line += ` ${piece}`;
175
- else { out.push({ kind: "text", text: line }); line = piece; }
176
- }
177
- if (line) out.push({ kind: "text", text: line });
178
- }
179
- return out;
180
- }
181
-
182
- /** the three drawn states, decided from the sources the module consulted — never from an empty list */
183
- export function statusFrom(sources: Record<string, Status>): MarketStatus {
184
- const failed = Object.entries(sources).filter(([, v]) => !v.ok) as [string, { ok: false; reason: string }][];
185
- if (failed.length) {
186
- const [name, s] = failed[0]!;
187
- return { kind: "error", reason: failed.length === 1 ? `${name}: ${s.reason}` : `${name}: ${s.reason} (+${failed.length - 1} more)` };
188
- }
189
- const cached = Object.values(sources).find((v) => v.ok && v.from === "cache") as { ok: true; from: "cache"; ageMs: number } | undefined;
190
- if (cached) {
191
- const mins = Math.round(cached.ageMs / 60000);
192
- return { kind: "offline", note: `showing cached results${mins > 0 ? `, ${mins}m old` : ""}` };
193
- }
194
- // "skipped" is not a failure and not staleness: the source was left out on purpose, and `why` says so.
195
- // It is worth one line because a reader who does not see a registry row deserves to know it was not asked.
196
- const skipped = Object.values(sources).find((v) => v.ok && v.from === "skipped") as { ok: true; from: "skipped"; why: string } | undefined;
197
- if (skipped) return { kind: "offline", note: skipped.why };
198
- return { kind: "ready" };
199
- }
200
-
201
- /** Everything the overlay opens with. `offline` forces the no-network path (the curated shelf and the
202
- * repository catalogs), which is also what a failed network falls back to. */
203
- export async function loadMarket(cwd: string, home: string, opts: { offline?: boolean } = {}): Promise<MarketLoad> {
204
- const mod = await load();
205
- if (!mod) return { rows: [], status: { kind: "error", reason: NOT_WIRED }, notes: [] };
206
- const result = await mod.registry.searchMarket("", opts.offline === true ? { offline: true } : {});
207
- // installed state is disk truth, joined onto the catalog by withInstalled (never read from a catalog)
208
- let rows: Row[] = result.items as Row[];
209
- try { rows = mod.install.withInstalled(result.items, cwd, home); } catch { /* disk unreadable: the rows still list */ }
210
- return { rows: rows.map(toViewRow), status: statusFrom(result.sources), notes: result.notes };
211
- }
212
-
213
- /** The documentation for one row, fetched when the reader asks for it.
214
- *
215
- * `searchMarket` carries docs METADATA but not the bodies — they are ~400 KB nobody reads while browsing —
216
- * so the list cannot fill this in advance. `findItem` reads the one row with its body, and the body is
217
- * flattened to terminal-safe lines here, once, at the moment the pane opens. */
218
- export async function docsFor(row: MarketViewRow): Promise<{ source: string; truncated: boolean; lines: MarketDocLine[] } | null> {
219
- const mod = await load();
220
- if (!mod || typeof mod.registry.findItem !== "function") return null;
221
- const { item } = await mod.registry.findItem(row.kind, row.id, { offline: true });
222
- const docs = item?.docs;
223
- if (!docs || typeof docs.body !== "string" || docs.body.trim() === "") return null;
224
- return { source: docs.source, truncated: docs.truncated === true, lines: docLines(docs.body) };
225
- }
226
-
227
- /** the plan for one row, or the reason there is none. Writes nothing. `local` is the chooser's answer for an npx
228
- * row: true draws the install-once plan (`node <bin>`, the `installs`/`records` rows), false or absent the npx
229
- * line as today — and the plan remembers it, so the install runs the plan that was approved. */
230
- export async function planFor(row: MarketViewRow, ctx: Ctx, local?: boolean): Promise<MarketPlan | { error: string }> {
231
- const mod = await load();
232
- if (!mod) return { error: NOT_WIRED };
233
- const result = await mod.registry.searchMarket(row.id, { offline: true });
234
- const item = result.items.find((i) => i.kind === row.kind && i.id === row.id);
235
- if (!item) return { error: `${row.kind}:${row.id} is not in the catalog any more` };
236
- const plan = mod.install.planInstall(item, { ...ctx, ...(local === true ? { local: true } : {}) });
237
- if ("error" in plan) return { error: plan.error };
238
- return {
239
- row,
240
- title: `${plan.item.kind}:${plan.item.id} — ${plan.item.title}`,
241
- target: plan.target,
242
- scope: plan.scope,
243
- preview: plan.preview,
244
- asks: plan.asks.map((a) => ({ name: a.name, required: a.required, secret: a.secret })),
245
- pending: plan.pending,
246
- ...(plan.replaces !== undefined ? { replaces: plan.replaces } : {}),
247
- ...(local === true ? { local: true } : {}),
248
- };
249
- }
250
-
251
- /** Run a plan the human has just confirmed. The overlay only ever sees the outcome sentence. `opts.local` must be
252
- * the approved plan's own `local` — npm runs here, and only here, after the card said yes. `opts.deps` is the
253
- * installer's seam (a fake `npm` in tests). */
254
- export async function install(row: MarketViewRow, ctx: Ctx, opts: { local?: boolean; deps?: unknown } = {}): Promise<{ ok: boolean; text: string }> {
255
- const mod = await load();
256
- if (!mod) return { ok: false, text: NOT_WIRED };
257
- const result = await mod.registry.searchMarket(row.id, { offline: true });
258
- const item = result.items.find((i) => i.kind === row.kind && i.id === row.id);
259
- if (!item) return { ok: false, text: `${row.kind}:${row.id} is not in the catalog any more` };
260
- const withLocal: Ctx = { ...ctx, ...(opts.local === true ? { local: true } : {}) };
261
- const plan = mod.install.planInstall(item, withLocal);
262
- if ("error" in plan) return { ok: false, text: plan.error };
263
- // A SECRET cannot be finished inside the overlay: it has to be typed on a shell, masked, not into a
264
- // query line that echoes. The overlay says so and hands the exact command over.
265
- //
266
- // `pending` is not that, and treating it as if it were made six of the sixteen curated MCP servers
267
- // uninstallable from the cockpit — including `filesystem`, which is the first one anybody tries.
268
- // Nothing is asked for a pending value: it is a placeholder the installer writes into the config
269
- // (`<directory the server may touch>`) for the human to replace afterwards, and the CLI installs it
270
- // exactly that way. Refusing here was the overlay inventing a requirement the installer does not have.
271
- if (plan.asks.length > 0) {
272
- const what = plan.asks.map((a) => a.name).join(", ");
273
- return { ok: false, text: `${what} must be typed where it can be masked — run: rovecode market install ${item.kind}:${item.id}` };
274
- }
275
- const outcome = await mod.install.runInstall(plan, {}, withLocal, opts.deps);
276
- if (!outcome.ok) return { ok: false, text: outcome.error };
277
- const bits = [`installed into ${outcome.target}`];
278
- if (outcome.package) {
279
- bits.push(`${outcome.package.name} ${outcome.package.version} installed once → ${outcome.package.prefix}${outcome.package.integrity !== undefined ? " (integrity recorded)" : ""}`);
280
- if (outcome.package.missing?.length) bits.push(`record incomplete: ${outcome.package.missing.join("; ")}`);
281
- }
282
- if (outcome.envNames.length) bits.push(`export ${outcome.envNames.join(", ")}`);
283
- // the placeholder is the one thing between this install and a working server, so it leads
284
- for (const p of plan.pending) bits.push(`fill in ${p}`);
285
- if (outcome.next) bits.push(outcome.next);
286
- return { ok: true, text: bits.join(" · ") };
287
- }
@@ -1,200 +0,0 @@
1
- /** `@file` mentions, made true. overlays.ts has parsed `@path` into `mentions[]` (with a fuzzy resolveFile) since
2
- * the port, and the input footer promised "@ mentions attach files" — but nothing ever read `.mentions`, so the
3
- * promise did nothing. This module is the consumer: on submit, each mention that resolves to a workspace file is
4
- * appended to the message exactly as the `read` tool would return it (hashline `path#TAG` header, `N#hash|text`
5
- * lines), so the model has the contents — and valid edit anchors — without spending a tool round-trip.
6
- *
7
- * Why here and not in tui/commands.ts: that file's note ("no `!shell` / `@file` injection … the template reaches
8
- * the model as plain text") is about CUSTOM COMMAND TEMPLATES — files a repo or a plugin ships, where injecting
9
- * files or shell output from a template is a way for a project to read the user's disk. That reason stands and
10
- * is untouched. A `@file` the human types into their own prompt is the human choosing to show a file; the only
11
- * risk is cost, so the caps below exist and every cap is SAID, in the message and as a toast.
12
- *
13
- * What is refused, and said: a mention no workspace file matches; a directory; a file outside the workspace
14
- * (resolveFile only knows the scanned list, and the absolute path is checked against cwd again); a binary; a
15
- * file past MAX_BYTES. What is capped, and said: lines per file (MAX_LINES — the footer names the offset that
16
- * continues), files per message (MAX_FILES), characters per message (MAX_CHARS). Never silent: `@` must not be a
17
- * way to spend a context window without knowing.
18
- *
19
- * Three widenings (2026-09-07), each because the promise was narrower than the words: `@~/notes.md` and an
20
- * ABSOLUTE mention name a file outright and are attached wherever they point (the person typed the whole path —
21
- * that IS the choice; a RELATIVE mention still has to land in the workspace); a relative mention that resolves
22
- * nowhere under cwd is tried against the `--add-dir` roots, without which a file in an added root could not be
23
- * mentioned at all; and an IMAGE mention takes the /attach seam so it reaches the model as an image part instead
24
- * of being refused as "a binary file". The token class carries `~`, `:` and `\` for the first of those, which is
25
- * what lets `@C:\Users\me\notes.md` tokenize on Windows — the cost is that a prose `@foo:bar` is read as a path
26
- * and reported as not found, which is a note, not a loss. */
27
-
28
- import { existsSync, openSync, closeSync, readSync, readFileSync, statSync } from "node:fs";
29
- import { homedir } from "node:os";
30
- import { isAbsolute, resolve, sep } from "node:path";
31
- import { sniffImageMime } from "../core/images.ts";
32
- import { fileTag, lineHash, readAnchored, renderAnchored, type AnchoredFile } from "../coding/hashline.ts";
33
-
34
- /** `@path` tokens in free text: at the start or after whitespace, so `me@example.com` is not one. The one
35
- * regex both surfaces and overlays.ts parseInput read — this module imports nothing from the sextant, so the
36
- * classic renderer can use it without loading the cockpit's module graph. */
37
- export const MENTION_RE = /(?:^|\s)@([\w./\\:~-]+)/g;
38
- /** the mentions of a typed line — none for a `/command` or a `!shell` line, which are never expanded */
39
- export function mentionsIn(text: string): string[] {
40
- const t = text.trim();
41
- if (t[0] === "/" || t[0] === "!") return [];
42
- return [...t.matchAll(MENTION_RE)].map((m) => m[1]!);
43
- }
44
-
45
- /** the most lines one mention contributes — the rest is one `read` with an offset away */
46
- export const MENTION_MAX_LINES = 400;
47
- /** mentions expanded per message; the rest are named and left for the model to read */
48
- export const MENTION_MAX_FILES = 8;
49
- /** characters all expansions together may add to one message (~15k tokens) */
50
- export const MENTION_MAX_CHARS = 60_000;
51
- /** a file larger than this is not read at all — name it, let the model ask for a window */
52
- export const MENTION_MAX_BYTES = 2 * 1024 * 1024;
53
- /** with less than this left of the character budget, a further file is named rather than squeezed to a
54
- * line or two — a three-line fragment of a file is worse context than "read it yourself" */
55
- export const MENTION_MIN_BLOCK = 500;
56
-
57
- /** the line that opens the attached section — what userRow cuts the transcript at */
58
- export const MENTION_FRAME = "(files attached by @mention — each block is what `read` returns for the file; its edit anchors are valid)";
59
- /** one per attached file, right above its read block: `[@src/x.ts — attached: 120 lines]` */
60
- export const MENTION_HEAD = /^\[@(\S+) — attached: (\d+)(?: of (\d+))? lines(, capped[^\]]*)?\]$/;
61
-
62
- export interface AttachedFile { path: string; shown: number; total: number; capped: boolean }
63
- export interface MentionExpansion {
64
- /** the message as submitted: the typed text, then the attached section (unchanged when nothing attached) */
65
- text: string;
66
- attached: AttachedFile[];
67
- /** what was refused or capped, one sentence each — the renderer toasts them */
68
- notes: string[];
69
- }
70
-
71
- export interface ExpandOptions {
72
- cwd: string;
73
- /** a mention → the cwd-relative posix path it names, or null. The sextant ranks over its scanned file list
74
- * (overlays.ts resolveFile: exact, unique basename, fuzzy); the classic renderer, which has no list, takes
75
- * the exact path only — what its own `@` autocomplete inserts. */
76
- resolve: (mention: string) => string | null;
77
- /** already parsed mentions (default: mentionsIn(text)) */
78
- mentions?: readonly string[];
79
- /** `--add-dir` roots (core/workspace.ts): a RELATIVE mention that resolves nowhere under cwd is tried against
80
- * each of these in order, so a file in an added root can be mentioned at all. cwd always wins a tie. */
81
- roots?: readonly string[];
82
- /** an image mention takes the /attach seam instead of being refused as binary — it reaches the model as an
83
- * image part, and costs nothing against the text budget. Without this the old "binary file" refusal stands. */
84
- attachImage?: (abs: string) => void;
85
- /** seams for tests */
86
- stat?: (abs: string) => { isFile(): boolean; size: number };
87
- read?: (abs: string) => string;
88
- exists?: (abs: string) => boolean;
89
- }
90
-
91
- /** Where a mention points, and by what right. `~`/`~/x` and an ABSOLUTE mention are the person naming a file
92
- * outright — that is the choice, so they are allowed wherever they point; a RELATIVE mention must land inside the
93
- * cwd or one of the `--add-dir` roots, which is the containment rule the workspace already enforces everywhere
94
- * else. `label` is what the notes and the attached header show. */
95
- export function locateMention(m: string, opts: ExpandOptions): { abs: string; label: string; root?: string } | null {
96
- const exists = opts.exists ?? existsSync;
97
- if (m === "~" || m.startsWith("~/") || m.startsWith("~\\")) { const abs = resolve(homedir(), m.slice(2)); return { abs, label: m }; }
98
- if (isAbsolute(m)) return { abs: resolve(m), label: m };
99
- const rel = opts.resolve(m);
100
- if (rel !== null) return { abs: resolve(opts.cwd, rel), label: rel };
101
- for (const root of opts.roots ?? []) {
102
- const abs = resolve(root, m);
103
- if ((abs === resolve(root) || abs.startsWith(resolve(root) + sep)) && exists(abs)) return { abs, label: m, root };
104
- }
105
- return null;
106
- }
107
-
108
- const BINARY_PROBE = 8 * 1024;
109
-
110
- /** the file's image MIME by magic bytes (core/images.ts), reading only the first bytes — an image mention must not
111
- * pull a 4 MB photo into memory just to learn it is a photo. null when it is not an image we accept. */
112
- function sniffImage(abs: string, opts: ExpandOptions): string | null {
113
- try {
114
- if (opts.read) { const head = Buffer.from(opts.read(abs).slice(0, 32), "binary"); return sniffImageMime(head) ?? null; }
115
- const fd = openSync(abs, "r");
116
- try { const buf = Buffer.alloc(32); const n = readSync(fd, buf, 0, 32, 0); return sniffImageMime(buf.subarray(0, n)) ?? null; }
117
- finally { closeSync(fd); }
118
- } catch { return null; }
119
- }
120
-
121
- /** Expand every `@mention` in a typed line. Pure apart from the reads; never throws — a file that cannot be
122
- * read becomes a note and the message goes out without it. */
123
- export function expandMentions(text: string, opts: ExpandOptions): MentionExpansion {
124
- const mentions = [...new Set(opts.mentions ?? mentionsIn(text))];
125
- const notes: string[] = [];
126
- const attached: AttachedFile[] = [];
127
- const blocks: string[] = [];
128
- if (mentions.length === 0) return { text, attached, notes };
129
- const root = resolve(opts.cwd);
130
- const stat = opts.stat ?? ((p: string) => statSync(p));
131
- let budget = MENTION_MAX_CHARS;
132
- for (const m of mentions) {
133
- if (attached.length >= MENTION_MAX_FILES) { notes.push(`@${m}: not attached — ${MENTION_MAX_FILES} files per message is the cap; ask me to read it`); continue; }
134
- const found = locateMention(m, opts);
135
- if (found === null) { notes.push(`@${m}: no file in the workspace matches`); continue; }
136
- const { abs } = found;
137
- const rel = found.label;
138
- // a relative mention resolved through the workspace list stays inside it; `~`/absolute and the added roots
139
- // carry their own right (locateMention), so only the cwd-relative case is re-checked here
140
- if (found.root === undefined && !isAbsolute(m) && !m.startsWith("~") && abs !== root && !abs.startsWith(root + sep)) { notes.push(`@${rel}: outside the workspace — not attached`); continue; }
141
- let st: { isFile(): boolean; size: number };
142
- try { st = stat(abs); } catch { notes.push(`@${rel}: cannot be read — not attached`); continue; }
143
- if (!st.isFile()) { notes.push(`@${rel}: a directory — name a file in it`); continue; }
144
- // an IMAGE takes the /attach seam BEFORE the size and budget checks: it reaches the model as an image part
145
- // and spends none of the text budget, so refusing it as "binary" was the wrong answer to the right question
146
- if (opts.attachImage) {
147
- const mime = sniffImage(abs, opts);
148
- if (mime !== null) {
149
- opts.attachImage(abs);
150
- notes.push(`@${rel}: attached as an image (${mime})${found.root ? ` (from ${found.root})` : ""}`);
151
- continue;
152
- }
153
- }
154
- if (st.size > MENTION_MAX_BYTES) { notes.push(`@${rel}: ${(st.size / 1048576).toFixed(1)} MB is too large to attach — ask me to read a window of it`); continue; }
155
- let content: string;
156
- try { content = opts.read ? opts.read(abs) : readFileSync(abs, "utf8"); }
157
- catch { notes.push(`@${rel}: cannot be read — not attached`); continue; }
158
- if (content.slice(0, BINARY_PROBE).includes("\0")) { notes.push(`@${rel}: a binary file — not attached`); continue; }
159
- if (budget < MENTION_MIN_BLOCK) { notes.push(`@${rel}: not attached — this message already carries ${MENTION_MAX_CHARS.toLocaleString()} characters of files; ask me to read it`); continue; }
160
- const file = opts.read ? anchoredFrom(abs, content) : readAnchored(abs);
161
- const total = file.lines.length;
162
- // the line cap first, then the character budget: whichever is hit, the footer says where to continue
163
- let shown = Math.min(total, MENTION_MAX_LINES);
164
- let body = renderAnchored({ ...file, lines: file.lines.slice(0, shown) }).replace(/\n$/, "");
165
- while (body.length > budget && shown > 1) {
166
- shown = Math.max(1, Math.floor(shown * budget / body.length));
167
- body = renderAnchored({ ...file, lines: file.lines.slice(0, shown) }).replace(/\n$/, "");
168
- }
169
- const capped = shown < total;
170
- budget -= body.length;
171
- const head = capped
172
- ? `[@${rel} — attached: ${shown} of ${total} lines, capped: read it with offset ${shown + 1} for the rest]`
173
- : `[@${rel} — attached: ${total} lines]`;
174
- blocks.push(`${head}\n${body}\n(showing lines ${total === 0 ? 0 : 1}-${shown} of ${total})`);
175
- attached.push({ path: rel, shown, total, capped });
176
- if (capped) notes.push(`@${rel}: ${total} lines — attached the first ${shown}; the rest is a read away`);
177
- }
178
- if (blocks.length === 0) return { text, attached, notes };
179
- return { text: `${text.trimEnd()}\n\n${MENTION_FRAME}\n\n${blocks.join("\n\n")}`, attached, notes };
180
- }
181
-
182
- /** an AnchoredFile from content already in hand (the `read` seam), split and hashed exactly as readAnchored does —
183
- * including the empty last line a trailing newline yields, so the counts match what the read tool reports */
184
- function anchoredFrom(abs: string, content: string): AnchoredFile {
185
- return { path: abs, tag: fileTag(content), lines: content.split("\n").map((t, i) => ({ n: i + 1, hash: lineHash(t), text: t })) };
186
- }
187
-
188
- /** The transcript's view of a submitted line: the typed text, and one chip per attached file — never the file
189
- * bodies, which would flood the messages panel (they are in the session, where the model reads them). */
190
- export function splitAttached(text: string): { text: string; files: string[] } {
191
- const lines = text.split("\n");
192
- const at = lines.indexOf(MENTION_FRAME);
193
- if (at < 0) return { text, files: [] };
194
- const files: string[] = [];
195
- for (const l of lines.slice(at + 1)) {
196
- const m = MENTION_HEAD.exec(l);
197
- if (m) files.push(m[3] !== undefined ? `${m[1]} · ${m[2]}/${m[3]} lines, capped` : `${m[1]} · ${m[2]} lines`);
198
- }
199
- return { text: lines.slice(0, at).join("\n").trimEnd(), files };
200
- }