rovecode 0.3.2 → 0.4.0-beta.2

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