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,58 +0,0 @@
1
- /** `/commit` and `/undo` for `rovecode --plain` (port #65): the readline REPL has no Renderer, so this adapter maps
2
- * the handful of Renderer calls git-cmds.ts makes onto console lines and the REPL's own y/n question — the SAME
3
- * cmdCommit / cmdUndo run (one implementation, ADR-002), the same bash-tool dispatch and approval chain. A denied
4
- * card cannot prefill a readline prompt, so the draft is printed for copy-and-resubmit instead. */
5
-
6
- import type { Runtime } from "../cli/runtime.ts";
7
- import { cmdCommit, cmdUndo, type GitCmdCtx } from "./git-cmds.ts";
8
- import type { ApprovalAnswer, AssistantView, Renderer, RendererHooks, StatusInfo } from "./renderer.ts";
9
- import type { ApprovalFn } from "../core/types.ts";
10
-
11
- export interface PlainGitDeps {
12
- rt: GitCmdCtx["rt"] & Pick<Runtime, "store">;
13
- /** the REPL's own approver (its y/n asker) — what a card would have asked, asked on the line */
14
- approve: ApprovalFn | undefined;
15
- yolo: boolean;
16
- /** the REPL's readline question (answers arrive trimmed + lowercased) */
17
- ask(question: string): Promise<string>;
18
- /** console.log by default; tests capture */
19
- out?: (line: string) => void;
20
- /** Share the REPL's busy/interrupt state with shell and model turns. */
21
- busy?: () => boolean;
22
- bindAbort?: (ac: AbortController | null) => void;
23
- }
24
-
25
- /** the console Renderer: notes, tool card lines, the approval question; everything else inert */
26
- export function plainRenderer(ask: PlainGitDeps["ask"], out: (line: string) => void): Renderer {
27
- const oneLine = (s: string): string => s.replace(/\n/g, " ⏎ ");
28
- return {
29
- start(_h: RendererHooks): void {}, stop(): void {}, setCommands(): void {},
30
- addUser(text: string): void { out(text); },
31
- addSystemNote(text: string, tone: "info" | "warn" | "error" = "info"): void { out(` ${tone === "info" ? "" : `${tone}: `}${text}`); },
32
- beginAssistant(): AssistantView { return { append() {}, done() {} }; },
33
- toolStart(_id: string, tool: string, args: string): void { out(`\n → ${tool} ${args}`); },
34
- toolUpdate(): void {},
35
- toolEnd(_id: string, ok: boolean, preview: string): void { out(` ← ${ok ? "ok" : "FAIL"} ${oneLine(preview)}`); },
36
- async askApproval(tool: string, argsPreview: string, detail?: string): Promise<ApprovalAnswer> {
37
- out(`\n approval needed: ${tool} ${argsPreview}`);
38
- if (detail) for (const l of detail.split("\n")) out(` ${l}`);
39
- const a = await ask(" allow? [y]es / [n]o: ");
40
- return a === "y" || a === "yes" ? "once" : "deny";
41
- },
42
- async askQuestion(): Promise<null> { return null; },
43
- async pickOne(): Promise<null> { return null; },
44
- clearTranscript(): void {},
45
- prefillEditor(text: string): void { out(` resubmit with: ${oneLine(text)}`); }, // no editor to prefill in --plain
46
- setBusy(_b: boolean, _label?: string): void {},
47
- setStatus(_i: StatusInfo): void {},
48
- };
49
- }
50
-
51
- /** `/commit [message]` or `/undo` typed at the `--plain` prompt (the REPL gates a busy run before calling this) */
52
- export async function runPlainGitCommand(deps: PlainGitDeps, text: string): Promise<void> {
53
- const renderer = plainRenderer(deps.ask, deps.out ?? console.log);
54
- const ctx: GitCmdCtx = { renderer, rt: deps.rt, store: () => deps.rt.store, approve: () => deps.approve, yolo: () => deps.yolo, busy: deps.busy ?? (() => false), setBusy(): void {}, bindAbort: deps.bindAbort ?? (() => {}) };
55
- const line = text.trim();
56
- if (line === "/undo") await cmdUndo(ctx);
57
- else if (line === "/commit" || line.startsWith("/commit ")) await cmdCommit(ctx, line.slice("/commit".length));
58
- }
@@ -1,150 +0,0 @@
1
- /** TUI info commands, extracted from app.ts for the ADR-002 cap (wiring pass): /help /status
2
- * /cost /skills /memory /export read state and print a note; /todos (port #32) and /tasks
3
- * (port #26) surface the agent-maintained lists. Nothing here moves the session leaf — the
4
- * CALLER owns the swappable store/blocks and the status slice, so every read runs through
5
- * the injected ctx (same shape as session-cmd / checkpoints-cmd). */
6
-
7
- import type { Runtime } from "../cli/runtime.ts";
8
- import { exportSession } from "../cli/export.ts";
9
- import { describeSandbox } from "../core/sandbox-config.ts";
10
- import type { SessionStore } from "../core/session.ts";
11
- import { formatTaskList, isTerminal } from "../core/tasks.ts";
12
- import type { BlockStore } from "../memory/blocks.ts";
13
- import type { ModelCatalog } from "../providers/catalog.ts";
14
- import { loadTodos, renderTodos, todoStatusLabel } from "../tools/todo.ts";
15
- import { helpForCommands, type CustomCommand } from "./commands.ts";
16
- import { memoryCommand } from "./memory-note.ts";
17
- import { buildCostNote } from "./cost.ts";
18
- import type { Renderer, SlashCommand } from "./renderer.ts";
19
- import { join } from "node:path";
20
-
21
- export interface InfoStateSlice { provider: string; model: string; turns: number; tokensIn: number; tokensOut: number }
22
-
23
- export interface InfoCmdCtx {
24
- renderer: Renderer;
25
- /** the runtime slices the info commands read: cwd, config provenance, sandbox rung, skills, tasks */
26
- rt: Pick<Runtime, "cwd" | "projectContext" | "sandbox" | "skillStore" | "tasks">;
27
- /** <cwd>/.rovecode/sessions — todos.json lives under <sessionsDir>/<session id> */
28
- sessionsDir: string;
29
- /** the ACTIVE session store, read live (/sessions and a root /rewind swap it) */
30
- store(): SessionStore;
31
- /** the ACTIVE memory block store, read live (swapped with the session) */
32
- blocks(): BlockStore;
33
- /** provider/model/turns/tokens exactly as the status line shows them */
34
- state: InfoStateSlice;
35
- commands: { builtin: readonly SlashCommand[]; custom: readonly CustomCommand[] };
36
- /** /cost pricing + context window (offline snapshot; `/cost refresh` is the TUI's one fetch) */
37
- catalog: ModelCatalog;
38
- }
39
-
40
- /** /help topics in display order; a command with an unknown or missing group lands under "more" at the end */
41
- export const HELP_GROUP_ORDER: readonly string[] = ["start here", "session", "model & provider", "modes & safety", "files & history", "info"];
42
-
43
- /** /help — the built-ins grouped by topic (SlashCommand.group), one plain line each, then the "custom:" tail (port #30). */
44
- export function cmdHelp(ctx: InfoCmdCtx): void {
45
- ctx.renderer.addSystemNote(groupedHelp(ctx.commands.builtin) + helpForCommands(ctx.commands.custom));
46
- }
47
-
48
- /** pure: the grouped body /help prints (tests read it without a renderer) */
49
- export function groupedHelp(builtin: readonly SlashCommand[]): string {
50
- const groups = new Map<string, SlashCommand[]>();
51
- for (const c of builtin) { const g = c.group !== undefined && HELP_GROUP_ORDER.includes(c.group) ? c.group : "more"; const l = groups.get(g) ?? []; l.push(c); groups.set(g, l); }
52
- const order = [...HELP_GROUP_ORDER, "more"].filter((g) => groups.has(g));
53
- return order.map((g) => `${g}\n` + groups.get(g)!.map((c) => ` /${c.name} — ${c.description}`).join("\n")).join("\n");
54
- }
55
-
56
- /** /status — provider/model/turns/tokens, the active executor rung + its origin (port #27), and
57
- * config provenance (port #8 HIGH-2: dropped/truncated sources must be visible). */
58
- export function cmdStatus(ctx: InfoCmdCtx): void {
59
- const pc = ctx.rt.projectContext;
60
- const cfgBits = pc.sources.map((s) => s.chars === 0 ? `${s.path} (dropped)` : s.truncated ? `${s.path} (truncated)` : s.path);
61
- if (pc.skippedFiles > 0) cfgBits.push(`+${pc.skippedFiles} skipped (file cap)`);
62
- const s = ctx.state;
63
- ctx.renderer.addSystemNote(
64
- `provider=${s.provider} model=${s.model} turns=${s.turns} tokens=${s.tokensIn}in/${s.tokensOut}out` +
65
- `\nsandbox: ${describeSandbox(ctx.rt.sandbox)}` +
66
- `\nconfig: ${cfgBits.length > 0 ? cfgBits.join(", ") : "(none)"}`,
67
- );
68
- }
69
-
70
- /** /cost [refresh] — ports #5+#6: normalized usage (incl. cache traffic) priced per message at its
71
- * origin model; `refresh` re-fetches models.dev pricing (24h disk cache) — the live half is
72
- * user-invoked only, so the TUI stays network-free unless asked. */
73
- export function cmdCost(ctx: InfoCmdCtx, arg: string): void {
74
- if (arg === "refresh") {
75
- void ctx.catalog.refresh().then((ok) => ctx.renderer.addSystemNote(
76
- ok ? "model catalog refreshed from models.dev" : "catalog refresh failed — using the offline snapshot",
77
- ok ? "info" : "warn",
78
- ));
79
- return;
80
- }
81
- ctx.renderer.addSystemNote(buildCostNote(ctx.store().messages(), ctx.catalog, { provider: ctx.state.provider, model: ctx.state.model }));
82
- }
83
-
84
- /** /skills — installed skills, one `name — description` row each. */
85
- export function cmdSkills(ctx: InfoCmdCtx): void {
86
- const rows = ctx.rt.skillStore.list().map((s) => `${s.name} — ${s.description}`);
87
- ctx.renderer.addSystemNote(rows.length ? rows.join("\n") : "(no skills installed)");
88
- }
89
-
90
- /** `/memory` — both blocks as the store holds them NOW, each with the file it lives in; `/memory --user` the USER
91
- * block alone; `/memory <text>` appends to MEMORY and `/memory --user <text>` to USER (tui/memory-note.ts, the
92
- * same append path as `#<text>` and the memory_edit tool). It shows the live text rather than renderForPrompt()
93
- * so an edit made this run is visible immediately, with a hint that the prompt picks it up next run — and it
94
- * names the paths, because the bug this replaced was a note landing somewhere the person did not mean. */
95
- export function cmdMemory(ctx: InfoCmdCtx, arg = ""): void {
96
- const r = memoryCommand(ctx.blocks(), arg);
97
- ctx.renderer.addSystemNote(r.text, r.tone);
98
- }
99
-
100
- /** /export [--json] [path] [--force] — port #38: write THIS session as markdown (raw JSONL with
101
- * --json), local only. Read-only over the store (exportSession re-reads from disk), so no busy
102
- * gate; a spaced path stays whole (every non-flag word joins the path). */
103
- export function cmdExport(ctx: InfoCmdCtx, arg: string): void {
104
- try {
105
- const words = arg.split(/\s+/).filter(Boolean);
106
- const res = exportSession(ctx.sessionsDir, ctx.store().id, {
107
- json: words.includes("--json"), force: words.includes("--force"),
108
- out: words.filter((w) => !w.startsWith("-")).join(" ") || undefined, cwd: ctx.rt.cwd,
109
- });
110
- ctx.renderer.addSystemNote(`exported ${res.format} → ${res.path}`);
111
- } catch (e) {
112
- ctx.renderer.addSystemNote(e instanceof Error ? e.message : String(e), "error");
113
- }
114
- }
115
-
116
- /** /todos — port #32: the agent-maintained list at <session>/todos.json as checkbox rows. A
117
- * corrupt file is a warning above the (empty) list, never a throw (loadTodos contract). */
118
- export function cmdTodos(ctx: InfoCmdCtx): void {
119
- const { items, note } = loadTodos(join(ctx.sessionsDir, ctx.store().id));
120
- if (note) ctx.renderer.addSystemNote(note, "warn");
121
- ctx.renderer.addSystemNote(items.length > 0 ? renderTodos(items) : "(no todos — the agent maintains the list with todo_write)");
122
- }
123
-
124
- /** Status-bar label for the session's list ("todos 1/3"); undefined when empty, so the app omits
125
- * the key and plain surfaces never see a blank segment. */
126
- export function todoLabel(sessionDir: string): string | undefined {
127
- return todoStatusLabel(loadTodos(sessionDir).items) || undefined;
128
- }
129
-
130
- /** /tasks [cancel <id>|cancel all] — port #26: the runtime TaskManager's snapshot (the same rows
131
- * as the tool's `list` action). Cancel acknowledges the REQUEST here; the "cancelled" line itself
132
- * arrives through the app's task subscription when the child run has actually settled (a queued
133
- * task settles at once, a running one when its aborted run returns) — one terminal note per task. */
134
- export function cmdTasks(ctx: InfoCmdCtx, arg: string): void {
135
- const words = arg.split(/\s+/).filter(Boolean);
136
- const tasks = ctx.rt.tasks;
137
- if (words.length === 0) { ctx.renderer.addSystemNote(formatTaskList(tasks.list())); return; }
138
- if (words[0] !== "cancel" || words.length !== 2) { ctx.renderer.addSystemNote("usage: /tasks [cancel <id>|cancel all]", "warn"); return; }
139
- const id = words[1]!;
140
- if (id === "all") {
141
- const n = tasks.cancelAll();
142
- ctx.renderer.addSystemNote(n === 0 ? "no queued or running tasks to cancel" : `cancelling ${n} task${n === 1 ? "" : "s"}`);
143
- return;
144
- }
145
- const before = tasks.status(id);
146
- if (!before) { ctx.renderer.addSystemNote(`unknown task '${id}' — list with /tasks`, "warn"); return; }
147
- if (isTerminal(before.status)) { ctx.renderer.addSystemNote(`task ${id} already ${before.status}`); return; }
148
- tasks.cancel(id);
149
- ctx.renderer.addSystemNote(`cancelling task ${id} (${before.label})`);
150
- }
@@ -1,76 +0,0 @@
1
- /** Port #78: --plain shell/mention parity over rovecode's existing dispatch and hashline attachments.
2
- * No second executor, classifier, policy or file reader. Unlike aion, rovecode's approval contract has
3
- * once/always/deny (no persistent-save verdict), and mentions carry read-tool anchors, not XML file blocks.
4
- * This console renderer shares the REPL's once/always/deny approval ladder; git confirmations stay yes/no. */
5
- import { existsSync } from "node:fs";
6
- import { resolve } from "node:path";
7
- import type { Runtime } from "../cli/runtime.ts";
8
- import type { ApprovalFn } from "../core/types.ts";
9
- import { expandMentions } from "../sextant/mentions.ts";
10
- import { cmdAttach, type AttachCtx } from "./attach.ts";
11
- import type { Renderer } from "./renderer.ts";
12
- import { runShellLine, type ShellCtx } from "./shell-cmd.ts";
13
-
14
- export const PLAIN_APPROVAL_QUESTION = " allow? [y]es / [a]lways / [n]o: ";
15
- type Ask = (question: string) => Promise<string>;
16
- type Out = (line: string) => void;
17
- const NL = String.fromCharCode(10);
18
-
19
- /** The existing REPL ladder, shared by model calls and shell calls; always is session-only. */
20
- export async function askPlainApproval(ask: Ask, tool: string, args: string, detail?: string, out: Out = console.log): ReturnType<ApprovalFn> {
21
- out(`${NL} approval needed: ${tool} ${args}`);
22
- if (detail) for (const line of detail.split(NL)) out(` ${line}`);
23
- const answer = await ask(PLAIN_APPROVAL_QUESTION);
24
- return answer === "a" ? "always" : answer === "n" || answer === "" ? "deny" : "once";
25
- }
26
-
27
- export interface PlainInputDeps {
28
- rt: ShellCtx["rt"] & Pick<Runtime, "store" | "roots">;
29
- yolo(): boolean;
30
- busy(): boolean;
31
- bindAbort(ac: AbortController | null): void;
32
- ask: Ask;
33
- modelRef(): { provider: string; model: string };
34
- out?: Out;
35
- }
36
-
37
- /** Readline already echoed the typed input; only the tool cards and notes are printed here. */
38
- export function plainInputRenderer(ask: Ask, out: Out): Renderer {
39
- return {
40
- start() {}, stop() {}, setCommands() {}, addUser() {},
41
- addSystemNote(text, tone = "info") { out(` ${tone === "info" ? "" : `${tone}: `}${text}`); },
42
- beginAssistant() { return { append() {}, done() {} }; },
43
- toolStart(_id, tool, args) { out(`${NL} → ${tool} ${args}`); },
44
- toolUpdate() {},
45
- toolEnd(_id, ok, preview) { out(` ← ${ok ? "ok" : "FAIL"} ${preview.split(NL).join(" ⏎ ")}`); },
46
- askApproval: (tool, args, detail) => askPlainApproval(ask, tool, args, detail, out),
47
- async askQuestion() { return null; }, async pickOne() { return null; },
48
- clearTranscript() {}, prefillEditor() {}, setBusy() {}, setStatus() {},
49
- };
50
- }
51
-
52
- export async function runPlainShellLine(deps: PlainInputDeps, line: string): Promise<void> {
53
- const out = deps.out ?? console.log;
54
- const ctx: ShellCtx = {
55
- renderer: plainInputRenderer(deps.ask, out), rt: deps.rt, store: () => deps.rt.store,
56
- level: () => deps.yolo() ? "auto" : "ask",
57
- approve: () => deps.yolo() ? undefined : (req) => askPlainApproval(deps.ask, req.tool, JSON.stringify(req.revisedArgs).slice(0, 140), undefined, out),
58
- busy: () => deps.busy(), setBusy() {}, bindAbort: (ac) => deps.bindAbort(ac),
59
- };
60
- await runShellLine(ctx, line);
61
- }
62
-
63
- /** LAST transform before agentLoop: exact paths (like the classic renderer), added roots, existing
64
- * containment/caps, valid edit anchors, and images staged on the same SessionStore through cmdAttach. */
65
- export function expandPlainInput(deps: PlainInputDeps, text: string): string {
66
- const renderer = plainInputRenderer(deps.ask, deps.out ?? console.log);
67
- const attach: AttachCtx = { renderer, cwd: deps.rt.cwd, store: () => deps.rt.store, modelRef: () => deps.modelRef() };
68
- const result = expandMentions(text, {
69
- cwd: deps.rt.cwd, roots: deps.rt.roots.dirs,
70
- resolve: (path) => existsSync(resolve(deps.rt.cwd, path)) ? path : null,
71
- attachImage: (path) => cmdAttach(attach, path),
72
- });
73
- for (const note of result.notes) renderer.addSystemNote(note, note.includes(": attached as an image (") ? "info" : "warn");
74
- if (result.attached.length > 0) renderer.addSystemNote(`attached: ${result.attached.map((file) => `${file.path} (${file.shown} lines${file.capped ? ", capped" : ""})`).join(" · ")}`);
75
- return result.text;
76
- }
@@ -1,128 +0,0 @@
1
- /** /mcp — the MCP market inside the TUI, through the two cards the surface already has. `/mcp [query]`
2
- * opens the palette (Renderer.pickOne: the same box, keys and fuzzy filter as ⌃k) over the curated
3
- * shelf plus the registry's matches; Enter on a row → if the server has several launch forms, one more
4
- * pick; then the APPROVAL card (Renderer.askApproval) whose detail is the exact plan — command + args or
5
- * URL, source, publisher, version, the env NAMES, the file — and only a yes writes. Kept out of app.ts
6
- * (ADR-002 cap) like providers-cmd.ts.
7
- *
8
- * Secrets: the TUI has no masked input, so nothing is ever asked here. An install that wants a key is
9
- * written with `${NAME}` (config.ts fills it from the environment at launch) and the closing note says
10
- * which names to export — or to run `rovecode mcp add <name>` on a shell, where the prompt is masked. */
11
-
12
- import type { Renderer, PickItem } from "./renderer.ts";
13
- import { installLabel, searchMarket, type MarketDeps, type MarketEntry } from "../mcp/market.ts";
14
- import { describePlan, fillPlan, namesWritten, planInstall, serverLine, writeServer, type McpScope } from "../mcp/market-install.ts";
15
- import { parseConfigFile } from "../mcp/config.ts";
16
- import { mcpTrustStatus, projectMcpFiles, trustMcpFile } from "../mcp/trust.ts";
17
- import { installLocalPackage, localLaunch, npxPackage } from "../mcp/local-package.ts";
18
- import { buildRecord, recordInstall } from "../market/manifest.ts";
19
- import { rovecodeHome } from "../providers/auth.ts";
20
-
21
- /** the subcommands the sextant offers after `/mcp ` (Enter completes the word, the name comes next) */
22
- export const MCP_SUBCOMMANDS = ["search", "info", "add", "remove", "list", "show", "trust", "untrust"] as const;
23
- export const MCP_COMMAND = { name: "mcp", choices: MCP_SUBCOMMANDS, choicesThen: "complete" as const, description: "Find and install an MCP server: /mcp [query] [--project] — the curated shelf, then the registry · /mcp trust approves this repo's MCP files", group: "modes & safety" };
24
-
25
- /** `/mcp trust` — one approval card per project MCP file: the file as preview, its servers as detail; a yes
26
- * records the file's current bytes as trusted (mcp/trust.ts), so the NEXT launch loads it */
27
- async function trustProjectFiles(ctx: McpCmdCtx, home: string): Promise<void> {
28
- const files = projectMcpFiles(ctx.cwd);
29
- if (files.length === 0) { ctx.renderer.addSystemNote("mcp: no project MCP files here (.rovecode/mcp.json, .mcp.json)"); return; }
30
- const anySet = new Proxy({}, { get: () => "set" }) as Record<string, string>;
31
- for (const file of files) {
32
- if (mcpTrustStatus(home, file) === "trusted") { ctx.renderer.addSystemNote(`mcp: ${file} is already trusted as it is now`); continue; }
33
- const warnings: string[] = [];
34
- const lines = parseConfigFile(file, warnings, anySet).map(serverLine).concat(warnings.map((w) => `! ${w}`));
35
- const answer = await ctx.renderer.askApproval("mcp trust", file, lines.join("\n") || "(no servers in it)");
36
- if (answer === "deny") { ctx.renderer.addSystemNote(`mcp: ${file} stays untrusted — nothing in it loads`); continue; }
37
- const r = trustMcpFile(home, file);
38
- ctx.renderer.addSystemNote(r.ok ? `mcp: trusted ${file} — restart me to connect; an edit asks again` : `mcp: ${r.reason}`, r.ok ? "info" : "warn");
39
- }
40
- }
41
-
42
- export interface McpCmdCtx {
43
- renderer: Renderer;
44
- cwd: string;
45
- home?: string;
46
- /** registry access — tests inject a fixture fetch or `offline` */
47
- market?: MarketDeps;
48
- /** how `npm install` runs for the install-once pick — tests inject one that writes a fake node_modules */
49
- spawn?: import("../mcp/local-package.ts").Spawn;
50
- }
51
-
52
- const clip = (s: string, n: number): string => (s.length > n ? s.slice(0, n - 1) + "…" : s);
53
-
54
- function pickItem(e: MarketEntry): PickItem {
55
- const src = e.source === "curated" ? "curated" : `registry · ${e.publisher ?? "?"}`;
56
- return { value: e.key, label: e.title ?? e.key, description: `${src}${e.status ? ` · ${e.status}` : ""} · ${clip(e.description, 60)}` };
57
- }
58
-
59
- export async function cmdMcp(ctx: McpCmdCtx, arg: string): Promise<void> {
60
- const { renderer } = ctx;
61
- const words = arg.split(/\s+/).filter(Boolean);
62
- const scope: McpScope = words.includes("--project") ? "project" : "user";
63
- const query = words.filter((w) => !w.startsWith("--")).join(" ");
64
- const home = ctx.home ?? rovecodeHome();
65
- if (words[0] === "trust") { await trustProjectFiles(ctx, home); return; }
66
- const market: MarketDeps = { home, ...ctx.market };
67
- const found = await searchMarket(query, market);
68
- for (const n of found.notes) renderer.addSystemNote(`mcp: ${n}`, "warn");
69
- if (found.entries.length === 0) { renderer.addSystemNote(`mcp: nothing matches "${query}" — the registry matches on the server's name`, "warn"); return; }
70
- const key = await renderer.pickOne(found.entries.map(pickItem), query ? `mcp market · ${query}` : "mcp market");
71
- if (key === null) return;
72
- const entry = found.entries.find((e) => e.key === key);
73
- if (!entry) return;
74
- let pick = 0;
75
- if (entry.installs.length > 1) {
76
- const how = await renderer.pickOne(entry.installs.map((i, ix) => ({ value: String(ix), label: i.kind === "stdio" ? `run ${installLabel(i)}` : `connect ${i.url}` })), `${entry.title ?? entry.key} · how`);
77
- if (how === null) return;
78
- pick = Number(how);
79
- }
80
- // the install-once offer (mcp/local-package.ts) as one more pick, BEFORE the plan: the card the human
81
- // approves is then the plan that runs. Esc here writes nothing; "as today" is the npx line unchanged.
82
- const chosen = entry.installs[pick];
83
- const offer = chosen !== undefined ? npxPackage(chosen) : undefined;
84
- let local = false;
85
- if (offer !== undefined && scope === "user") { // a project file is shared: install-once's absolute path has no place in it (planInstall refuses it too)
86
- const how = await renderer.pickOne([
87
- { value: "local", label: `install once — node starts it in ~0.4 s`, description: `runs npm install now: ${offer.spec}'s code lands under ~/.rovecode/mcp (typically 20–30 MB, one time); no network needed to start` },
88
- { value: "npx", label: `run through npx at every start — as today`, description: `~2 s per start, re-resolves the package and asks the npm registry each time; nothing installed now` },
89
- ], `${entry.title ?? entry.key} · how to start it`);
90
- if (how === null) return;
91
- local = how === "local";
92
- }
93
- const plan = planInstall(entry, { scope, cwd: ctx.cwd, home, pick, ...(local ? { local: true } : {}) });
94
- if ("error" in plan) { renderer.addSystemNote(`mcp: ${plan.error}`, "warn"); return; }
95
- // the approval card: title = what is being done, preview = the one line that runs, detail = the whole plan
96
- // (for install-once the detail says, in words, that npm runs and code lands on this machine)
97
- const answer = await renderer.askApproval("mcp add", `${plan.name} ← ${plan.local ? `node ${plan.local.pkg.spec} (installed once)` : installLabel(plan.install)}`, describePlan(plan, "env").join("\n"));
98
- if (answer === "deny") { renderer.addSystemNote("mcp: nothing written"); return; }
99
- // install-once: npm first; only its success reaches the file, and what landed goes on record
100
- let launch: { command: string; args: string[] } | undefined;
101
- let pkgRecord: NonNullable<Parameters<typeof buildRecord>[1]["package"]> | undefined;
102
- if (plan.local) {
103
- renderer.addSystemNote(`mcp: npm install ${plan.local.pkg.spec} → ${plan.local.prefix} …`);
104
- const lr = await installLocalPackage(plan.local.pkg, plan.local.prefix, ctx.spawn ? { spawn: ctx.spawn } : {});
105
- if (!lr.ok) { renderer.addSystemNote(`mcp: ${lr.error} — nothing written`, "error"); return; }
106
- launch = localLaunch(lr.pkg, plan.local.pkg.rest);
107
- pkgRecord = { name: lr.pkg.name, version: lr.pkg.version, prefix: plan.local.prefix, bin: lr.pkg.bin, missing: lr.pkg.missing,
108
- ...(lr.pkg.integrity !== undefined ? { integrity: lr.pkg.integrity } : {}), ...(lr.pkg.resolved !== undefined ? { resolved: lr.pkg.resolved } : {}) };
109
- }
110
- let trusted: boolean | undefined;
111
- const raw = fillPlan(plan, {}, launch); // no answers: required asks become ${NAME}, optional ones are left out — the file works without them
112
- try {
113
- // the card just approved this exact content: a project file is trusted as written (mcp/trust.ts)
114
- trusted = writeServer(plan.file, plan.name, raw, scope === "project" ? { trustHome: home } : {}).trusted;
115
- } catch (e) { renderer.addSystemNote(`mcp: ${e instanceof Error ? e.message : String(e)}`, "error"); return; }
116
- renderer.addSystemNote(`mcp: added "${plan.name}" → ${plan.file}${trusted === true ? " (trusted as written)" : ""} — restart me to connect (servers are read once per process)`);
117
- if (pkgRecord) {
118
- recordInstall(buildRecord({ kind: "mcp", id: entry.key, source: entry.source, ...(entry.version !== undefined ? { version: entry.version } : {}) },
119
- { scope, target: plan.file, package: pkgRecord, installedBy: "mcp add" }), { cwd: ctx.cwd, home });
120
- renderer.addSystemNote(`mcp: installed ${pkgRecord.name} ${pkgRecord.version} once → ${plan.local!.prefix}${pkgRecord.integrity !== undefined ? " (integrity recorded in installed.json)" : ""}`);
121
- if (pkgRecord.missing?.length) renderer.addSystemNote(`mcp: record incomplete: ${pkgRecord.missing.join("; ")}`, "warn");
122
- }
123
- if (trusted === false) renderer.addSystemNote("mcp: that file already held servers you have not approved, so it is NOT trusted yet — /mcp trust shows them", "warn");
124
- // only the names the FILE now refers to: an optional ask that was left out is not something to go and set
125
- const named = namesWritten(plan, raw);
126
- if (named.length) renderer.addSystemNote(`mcp: set ${named.join(", ")} in your environment before the restart — or run \`rovecode mcp add ${entry.key}\` on a shell, which asks for them masked`, "warn");
127
- if (plan.pending.length) renderer.addSystemNote(`mcp: fill in ${plan.pending.join(", ")} in that file's args before use`, "warn");
128
- }
@@ -1,77 +0,0 @@
1
- /** `#<text>` memory shortcut + `/memory [--user] [text]`. A prompt LINE that starts with `#` appends the rest of
2
- * the line to the MEMORY block (the project store, memory/scope.ts) — no model turn, one transcript note — the way
3
- * `!cmd` short-circuits the prompt (tui/shell-cmd.ts). Pattern source: Claude Code's `#` "add to memory" shortcut
4
- * (proprietary — PATTERN ONLY, no code copied).
5
- *
6
- * The classifier is deliberately narrow, because the obvious version swallows pasted code: a note is ONE line
7
- * whose trimmed text is `#` followed by a letter or digit (`#always use bun`, `#1 is flaky`). Everything else is a
8
- * prompt for the model — any submission with a newline (a pasted `#include …` block, a shebang script), `#`,
9
- * `# heading`, `##x`, `#!…`, `#[…]`, `#-flag`, and a `#` INSIDE a line (`fix the #1 bug`).
10
- *
11
- * The edit lands on disk and in the live store at once (memory_edit and /memory see it); the prompt keeps its boot
12
- * snapshot and picks the note up on the NEXT run (memory/blocks.ts). Both surfaces share this module. No store is
13
- * constructed here — the ACTIVE runtime store is injected, so a session switch cannot leave this writing to a
14
- * store nobody reads. */
15
-
16
- import type { BlockName, BlockStore } from "../memory/blocks.ts";
17
- import type { Renderer } from "./renderer.ts";
18
-
19
- /** the classifier's opener: `#` then a letter or digit (any script) */
20
- const NOTE_OPENER = /^#[\p{L}\p{N}]/u;
21
-
22
- /** The note of a `#<text>` line, or null when the submission is a prompt for the model (header rule). */
23
- export function memoryNoteLine(text: string): string | null {
24
- if (/[\r\n]/.test(text)) return null; // a multi-line submission is pasted content, never a note
25
- const t = text.trim();
26
- if (!NOTE_OPENER.test(t)) return null;
27
- return t.slice(1).trim();
28
- }
29
-
30
- export type Tone = "info" | "warn";
31
-
32
- const LABEL: Readonly<Record<BlockName, string>> = { memory: "MEMORY", user: "USER" };
33
-
34
- /** Append `text` to `block` through the store (cap, trust and ledger guarded); the line says WHERE it landed —
35
- * the path is the point, since the whole bug this replaced was a note landing somewhere the person did not mean. */
36
- export function appendMemory(store: BlockStore, block: BlockName, text: string): { ok: boolean; text: string; tone: Tone } {
37
- const r = store.add(block, text);
38
- if (!r.ok) return { ok: false, tone: "warn", text: `memory: not saved to ${LABEL[block]} — ${r.reason ?? "edit failed"}` };
39
- return { ok: true, tone: "info", text: `memory: noted in ${LABEL[block]} (${r.current}/${r.limit} chars, ${store.path(block)}) — in the prompt from the next run` };
40
- }
41
-
42
- export interface MemoryNoteCtx {
43
- renderer: Pick<Renderer, "addUser" | "addSystemNote">;
44
- /** the ACTIVE block store, read live */
45
- blocks(): BlockStore;
46
- }
47
-
48
- /** Run one `#<text>` line: echo it, append to MEMORY, note the outcome. false = not a note line (nothing done). */
49
- export function runMemoryNote(ctx: MemoryNoteCtx, line: string): boolean {
50
- const note = memoryNoteLine(line);
51
- if (note === null) return false;
52
- ctx.renderer.addUser(line.trim());
53
- const r = appendMemory(ctx.blocks(), "memory", note);
54
- ctx.renderer.addSystemNote(r.text, r.tone);
55
- return true;
56
- }
57
-
58
- /** the /memory hints under a block: an edit this run, a block withheld from the prompt, a block over its cap */
59
- function blockHints(store: BlockStore, b: BlockName): string {
60
- const over = store.overCap(b);
61
- return (store.edited(b) ? "\n(edited this run — in the prompt from the next run)" : "")
62
- + (store.isWithheld(b) ? "\n(not in the prompt and not writable — it came with this repository; rovecode trust show)" : "")
63
- + (over ? `\n(over the ${over.cap}-char cap — the prompt shows the first ${over.cap}; trim the file)` : "");
64
- }
65
-
66
- /** `/memory` → both blocks as the store holds them NOW (live text, path, and a hint when an edit this run is not
67
- * yet in the prompt); `/memory --user` → the USER block alone; `/memory <text>` appends to MEMORY and
68
- * `/memory --user <text>` to USER (the same append path as `#<text>` and memory_edit add). */
69
- export function memoryCommand(store: BlockStore, arg: string): { text: string; tone: Tone } {
70
- const a = arg.trim();
71
- const user = a === "--user" || a.startsWith("--user ");
72
- const text = user ? a.slice("--user".length).trim() : a;
73
- if (text) { const r = appendMemory(store, user ? "user" : "memory", text); return { text: r.text, tone: r.tone }; }
74
- const blocks: BlockName[] = user ? ["user"] : ["memory", "user"];
75
- const shown = blocks.map((b) => `# ${LABEL[b]} — ${store.path(b)}\n${store.liveText(b) || "(empty)"}${blockHints(store, b)}`);
76
- return { text: shown.join("\n\n"), tone: "info" };
77
- }
@@ -1,45 +0,0 @@
1
- /** TUI glue for plan/act modes (port #20), extracted from app.ts for the ADR-002 cap.
2
- * Pure functions over the ModeManager + the app's mutable state slice. */
3
-
4
- import { ModeManager, applyModeRules, buildModeChangeEntry, modeSwitchOf, planModePromptSection, type AgentMode } from "../core/modes.ts";
5
- import type { SessionStore } from "../core/session.ts";
6
- import type { AgentDefinition, RunConfig } from "../core/types.ts";
7
- import type { Renderer } from "./renderer.ts";
8
-
9
- export interface ModeStateSlice { provider: string; model: string; mode: AgentMode; busy: boolean }
10
-
11
- /** /plan and /act — busy-gated toggle; updates the state slice from the mode's model slot. */
12
- export function togglePlanAct(modes: ModeManager, cmd: AgentMode, state: ModeStateSlice, renderer: Renderer, pushStatus: () => void): void {
13
- if (state.busy) { renderer.addSystemNote("finish or interrupt the run first (Esc)", "warn"); return; }
14
- const sw = modes.toggle(cmd);
15
- if (!sw) { renderer.addSystemNote(`already in ${cmd} mode`); return; }
16
- const cur = modes.modelFor();
17
- state.mode = modes.mode; state.model = cur.model; state.provider = cur.provider;
18
- renderer.addSystemNote(cmd === "plan" ? "plan mode: read-only tools — writes/shell/spawn denied by policy" : "act mode: full toolset restored");
19
- pushStatus();
20
- }
21
-
22
- /** Run-start enforcement: plan mode appends read-only rules AFTER the base set
23
- * (last-match-wins overrides even yolo) and steers the system prompt. */
24
- export function applyModeToRun(modes: ModeManager, cfg: RunConfig, def: AgentDefinition): void {
25
- cfg.permissionRules = applyModeRules(modes.mode, cfg.permissionRules);
26
- if (modes.mode !== "plan") return;
27
- delete cfg.verify; // plan mode changes nothing and runs nothing: the verify gate (core/verify-gate.ts) is off here, not merely idle
28
- const base = def.systemPrompt;
29
- def.systemPrompt = (v) => (typeof base === "function" ? base(v) : base) + "\n\n" + planModePromptSection();
30
- }
31
-
32
- /** MED-2: a pending mode switch must survive quit and session swaps, not just the
33
- * next submit — flush it as a durable entry so resume restores the last mode.
34
- * Round-trip cancellations have no pending switch, so nothing lands for them. */
35
- export function flushModeSwitch(modes: ModeManager, store: SessionStore): void {
36
- const sw = modes.consumeSwitchNotice();
37
- if (sw) store.append(buildModeChangeEntry(sw, store.messages().at(-1)?.id ?? null));
38
- }
39
-
40
- /** LOW-3: replay label for a system entry — mode switches render as a human line
41
- * ("mode → plan"), never the raw <mode_notice> XML the entry carries. */
42
- export function replayLabel(entry: unknown, text: string): string {
43
- const sw = modeSwitchOf(entry);
44
- return sw ? `mode → ${sw.to}` : text;
45
- }
@@ -1,100 +0,0 @@
1
- /** Notification sequences + focus tracking for the interactive TUI — PURE: no I/O, no env reads, no timers, no settings.
2
- *
3
- * Pattern source: openai/codex (Apache-2.0, pattern level; no code copied) — codex-rs/tui/src/notifications/mod.rs (the
4
- * `auto` backend chooser: OSC 9 on Ghostty / iTerm2 / kitty / Warp / WezTerm, else BEL; the not-OSC-9 list incl. Windows
5
- * Terminal, VsCode, AppleTerminal, Alacritty, Vte, Konsole), codex-rs/tui/src/notifications/osc9.rs (the tmux DCS
6
- * passthrough: `ESC P tmux ;` + the OSC with every ESC doubled + `ESC \`, and its three test vectors),
7
- * codex-rs/terminal-detection/src/lib.rs (the env markers each terminal sets; TERM_PROGRAM masks nothing here — any
8
- * marker counts), codex-rs/tui/src/chatwidget/notifications.rs (the whitespace-normalised, capped preview). The
9
- * focus-report idiom (DECSET 1004 → CSI I / CSI O) is xterm's. OSC 777 (rxvt-unicode / VTE `notify;title;body`), the
10
- * control-byte scrub and the argv parser are rovecode's own.
11
- *
12
- * Why a scrub at all: the model's last answer rides inside an OSC payload whose own terminator is BEL — an embedded BEL
13
- * or ESC would end the toast early or start a second sequence. Nothing a model wrote reaches the wire unfiltered. */
14
-
15
- export type Env = Readonly<Record<string, string | undefined>>;
16
- export type NotifyMethod = "bell" | "osc9" | "osc777";
17
- export type NotifyKind = "run_end" | "approval" | "question";
18
- export interface NotifyDetail { tool?: string; argsPreview?: string; question?: string; lastText?: string }
19
-
20
- export const BEL = "\x07";
21
- /** DECSET 1004: the terminal reports focus changes as CSI I (in) / CSI O (out) */
22
- export const FOCUS_ON = "\x1b[?1004h";
23
- export const FOCUS_OFF = "\x1b[?1004l";
24
- export const FOCUS_IN = "\x1b[I";
25
- export const FOCUS_OUT = "\x1b[O";
26
-
27
- /** strip every C0 / C1 control byte (tab, newline, CR count as whitespace first, so words never fuse), collapse
28
- * whitespace, trim, and cut to `max` characters with a closing `…` */
29
- export function scrub(text: string, max: number): string {
30
- const flat = text.replace(/[\x00-\x1f\x7f-\x9f]/g, (c) => (/\s/.test(c) ? " " : "")).replace(/\s+/g, " ").trim();
31
- if (max <= 0) return "";
32
- return flat.length > max ? `${flat.slice(0, max - 1)}…` : flat;
33
- }
34
-
35
- const has = (env: Env, key: string): boolean => (env[key] ?? "") !== "";
36
- /** codex terminal-detection normalisation of TERM_PROGRAM: spaces, `-`, `_`, `.` dropped, lower-cased */
37
- const program = (env: Env): string => (env.TERM_PROGRAM ?? "").replace(/[\s_.-]/g, "").toLowerCase();
38
-
39
- /** `auto`: OSC 9 only where codex lists a toast-capable terminal (Ghostty, iTerm2, kitty, Warp, WezTerm), else the bell
40
- * (Windows Terminal, vscode, Apple_Terminal, alacritty, VTE, unknown). OSC 777 is never picked automatically. */
41
- export function autoMethod(env: Env): NotifyMethod {
42
- const p = program(env), term = env.TERM ?? "";
43
- const ghostty = p === "ghostty" || term === "xterm-ghostty" || has(env, "GHOSTTY_RESOURCES_DIR");
44
- const iterm = p === "itermapp" || p === "iterm" || p === "iterm2" || has(env, "ITERM_SESSION_ID");
45
- const kitty = p === "kitty" || term === "xterm-kitty" || has(env, "KITTY_WINDOW_ID");
46
- const warp = p === "warpterminal" || p === "warp";
47
- const wezterm = p === "wezterm" || has(env, "WEZTERM_VERSION");
48
- return ghostty || iterm || kitty || warp || wezterm ? "osc9" : "bell";
49
- }
50
-
51
- /** inside tmux (a non-empty TMUX): OSC sequences must ride a DCS passthrough to reach the outer terminal */
52
- export const isTmux = (env: Env): boolean => has(env, "TMUX");
53
-
54
- /** the byte-exact sequence for one notification (`msg` already scrubbed by the caller): bell `\x07`; osc9 `ESC ] 9 ; msg BEL`;
55
- * osc777 `ESC ] 777 ; notify ; rovecode ; msg BEL`; under tmux the two OSC forms become `ESC P tmux ;` + the sequence with
56
- * every ESC doubled + `ESC \` (BEL needs no wrap, tmux passes it through) */
57
- export function sequenceFor(method: NotifyMethod, msg: string, tmux: boolean): string {
58
- if (method === "bell") return BEL;
59
- const osc = method === "osc9" ? `\x1b]9;${msg}\x07` : `\x1b]777;notify;rovecode;${msg}\x07`;
60
- return tmux ? `\x1bPtmux;${osc.replace(/\x1b/g, "\x1b\x1b")}\x1b\\` : osc;
61
- }
62
-
63
- /** the human line a toast shows — every part scrubbed and capped (approval: ≤ 40 chars of args; question ≤ 60; answer ≤ 120) */
64
- export function message(kind: NotifyKind, d: NotifyDetail): string {
65
- if (kind === "approval") return `approval needed: ${scrub(d.tool ?? "", 40)} ${scrub(d.argsPreview ?? "", 40)}`.trimEnd();
66
- if (kind === "question") return `question: ${scrub(d.question ?? "", 60)}`;
67
- const last = scrub(d.lastText ?? "", 120);
68
- return last ? `run finished: ${last}` : "run finished";
69
- }
70
-
71
- export type Argv = string[] | { error: string };
72
- export const isArgvError = (a: Argv): a is { error: string } => !Array.isArray(a);
73
-
74
- /** `notify_command` text → argv: a JSON string array when it starts with `[` (`["notify-send","rovecode"]`), else split on
75
- * whitespace; NEVER handed to a shell (a Windows path with `\` or an argument holding spaces needs the JSON form). `{ error }`
76
- * for a `[`-prefixed value that is not a non-empty array of non-empty strings, and for an empty argv. */
77
- export function parseArgv(value: string): Argv {
78
- const text = value.trim();
79
- if (text.startsWith("[")) {
80
- let parsed: unknown;
81
- try { parsed = JSON.parse(text); } catch (e) { return { error: `not a JSON array (${e instanceof Error ? e.message : String(e)})` }; }
82
- if (!Array.isArray(parsed) || parsed.length === 0 || !parsed.every((x) => typeof x === "string" && x.length > 0)) return { error: "a JSON array must hold one or more non-empty strings" };
83
- return parsed as string[];
84
- }
85
- const words = text.split(/\s+/).filter((w) => w.length > 0);
86
- return words.length === 0 ? { error: "empty command" } : words;
87
- }
88
-
89
- /** Consumes CSI I / CSI O (focus in / out) from a raw input chunk BEFORE the surface's parser sees it. `focused` starts
90
- * true: a terminal that never reports focus reads as focused — watched — so `notify_when: unfocused` stays silent there
91
- * instead of ringing on every run. No carry across chunks — the classic surface feeds StdinBuffer-reassembled sequences
92
- * and the sextant's parseInput degrades a split CSI to a harmless unknown key. */
93
- export class FocusTracker {
94
- focused = true;
95
- /** the chunk with every focus report removed (the flag follows the LAST one in it); "" when nothing else was there */
96
- feed(chunk: string): string {
97
- if (!chunk.includes("\x1b[")) return chunk;
98
- return chunk.replace(/\x1b\[([IO])/g, (_m, c: string) => { this.focused = c === "I"; return ""; });
99
- }
100
- }