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,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
- }