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,386 +0,0 @@
1
- /** Sextant surface (port #42) — the MESSAGES panel: user / assistant / tool / steer / compaction /
2
- * system rows, the ONE modal card (approval or ask_user question) pinned above the ╌ rule, and the
3
- * prompt line with its cursor cell. Ported from the user's own sextant v0.4.0 prototype,
4
- * src/app.js:561-672 (toolRow, buildRows, drawMessages); the mock welcome/"try" rows, permission
5
- * items and click hit-boxes are gone — rows come from SextantState.messages, the card from
6
- * SextantState.card, the agent label reads "rovecode". Pure painter: no clock, no timers, no state
7
- * mutation; the prototype's msgScroll write-back became the `messagesScroll` seam. */
8
-
9
- import type { CardState, Rect, ScreenLike, Seg, SextantState, Theme, ToolRow } from "./types.ts";
10
- import { ATTR } from "./types.ts";
11
- import { moreMarker } from "../coding/diff.ts";
12
- import { inner, panel, spinner, st, wrap } from "./draw-util.ts";
13
- import { clipText } from "./tool-rows.ts";
14
- import { segWidth } from "./layout.ts";
15
- import { fmtElapsed, fmtK } from "./model.ts";
16
- import { thinkingWord } from "./pet.ts";
17
- import { scrollbar } from "./scrollbar.ts";
18
-
19
- /** one painted line of the transcript; `path` marks a tool row that names a file (message-hits.ts opens it on click) */
20
- export interface Row { segs: Seg[]; indent?: number; path?: string }
21
-
22
- /** 1-slot cache: buildRows is called twice per frame (once in drawMessages, once in messagesScroll).
23
- * Within a single frame now/w/theme are constant and messages is the same array, so the second call
24
- * is always a cache hit. Keyed by array identity + length (cheap mutation guard) + w + theme.name + now. */
25
- let _rowsCache: { msgs: readonly unknown[]; len: number; w: number; name: string; now: number; rows: Row[] } | null = null;
26
- export function cachedBuildRows(s: SextantState, w: number, theme: Theme, now: number): Row[] {
27
- if (_rowsCache && _rowsCache.msgs === s.messages && _rowsCache.len === s.messages.length
28
- && _rowsCache.w === w && _rowsCache.name === theme.name && _rowsCache.now === now) {
29
- return _rowsCache.rows;
30
- }
31
- const rows = buildRows(s, w, theme, now);
32
- _rowsCache = { msgs: s.messages, len: s.messages.length, w, name: theme.name, now, rows };
33
- return rows;
34
- }
35
-
36
- /** the attachment cap the chip row quotes (core/images.ts MAX_IMAGES_PER_MESSAGE; a literal here keeps this painter free of node:fs) */
37
- const MAX_STAGED_HINT = 8;
38
- /** prompt placeholder when the input is empty */
39
- export const PLACEHOLDER = "ask rovecode — e.g. fix the failing test";
40
- /** button label per verdict — `all edits` reads as the mode it turns on, not as a third yes */
41
- export const VERDICT_LABEL: Record<"once" | "always" | "all-edits" | "deny", string> =
42
- { once: "allow", always: "always", "all-edits": "all edits", deny: "deny" };
43
- /** the default row, for a card that offers no extra door */
44
- export const VERDICTS: readonly string[] = ["allow", "always", "deny"];
45
- export const FREE_TEXT_HINT = "type an answer…";
46
- export const SKIP_LABEL = "skip this question";
47
- /** the longest question shown in the card (wrapped rows) before it is elided */
48
- const MAX_QUESTION_ROWS = 6;
49
-
50
- const VERB_GLYPH: Record<string, string> = { read: "·", edit: "~", write: "+", remove: "−", run: "$", search: "⌕", fetch: "↗", task: "»" };
51
- /** the label shrinks to this many cells before a detail that would still fit whole is touched (app.js) */
52
- const LABEL_MIN = 8;
53
- /** when the detail must be clipped instead, the label keeps up to this many cells (all of a shorter one) */
54
- const LABEL_KEEP = 24;
55
- /** a text detail that would keep fewer cells than this says nothing — dropped instead of clipped */
56
- const DETAIL_MIN = 6;
57
-
58
- /** cells a string takes: one per code point (types.ts) — `.length` would count an emoji twice */
59
- const cells = (s: string): number => [...s].length;
60
- const segCells = (segs: readonly Seg[]): number => segs.reduce((n, [s]) => n + cells(s), 0);
61
-
62
- /** `<glyph> <verb> <label>` with the detail right-aligned: `· read x … N lines`, `~ edit x +a −b`,
63
- * `$ run cmd … last line`; the spinner replaces the glyph while the call runs (app.js:561-571).
64
- * A failed edit shows its rejection detail, never the +a −b it did not apply. When label and detail
65
- * do not both fit, the label yields first (down to LABEL_MIN cells, the prototype's rule); when even
66
- * that leaves no room, a TEXT detail is clipped with `…` beside a label kept at up to LABEL_KEEP cells
67
- * — a rejection's reason stays on the row at the frame's 160×44 message width instead of vanishing;
68
- * the `+a −b` pair is dropped whole when it cannot fit. */
69
- export function toolRow(t: ToolRow, w: number, theme: Theme, now: number): Seg[] {
70
- const failed = !t.running && t.ok === false;
71
- const g = t.running ? spinner(now) : VERB_GLYPH[t.verb] ?? "·";
72
- const gc = t.running ? theme.accent : failed ? theme.err : "~+−".includes(g) ? theme.fg2 : theme.dim;
73
- const verb = ((t.verb === "other" ? t.tool : t.verb) + " ").padEnd(7); // always ≥1 space before the label (ask_user, todo_write, MCP names)
74
- const detail: Seg[] = [];
75
- if (t.verb === "edit" && !failed && (t.add || t.del)) {
76
- if (t.add) detail.push([`+${t.add}`, st(theme.ok)]);
77
- if (t.del) detail.push([`${t.add ? " " : ""}−${t.del}`, st(theme.err)]);
78
- } else if (t.detail) detail.push([(t.verb === "edit" ? "" : "… ") + t.detail, st(failed ? theme.err : theme.muted)]);
79
- let dw = segCells(detail);
80
- let label = t.label;
81
- const room = w - 2 - verb.length; // cells for label + gap + detail
82
- let labelMax = room;
83
- if (dw) {
84
- const keep = Math.min(cells(label), LABEL_KEEP);
85
- if (room - dw - 2 >= Math.min(cells(label), LABEL_MIN)) labelMax = room - dw - 2; // both fit once the label is clipped
86
- else if (detail.length === 1 && room - keep - 2 >= DETAIL_MIN) { // clip the text detail beside the kept label
87
- const [text, style] = detail[0]!;
88
- detail[0] = [clipText(text, room - keep - 2), style];
89
- labelMax = keep; dw = segCells(detail);
90
- } else { detail.length = 0; dw = 0; }
91
- }
92
- if (cells(label) > labelMax) label = labelMax > 1 ? [...label].slice(0, labelMax - 1).join("") + "…" : "";
93
- const segs: Seg[] = [[g + " ", st(gc)], [verb, st(t.running ? theme.fg2 : theme.muted)], [label, st(t.running ? theme.fg : failed ? theme.err : theme.fg2)]];
94
- if (detail.length) segs.push([" ".repeat(Math.max(1, w - 2 - verb.length - cells(label) - dw)), st(-1)], ...detail);
95
- return segs;
96
- }
97
-
98
- /** the status word after `◆ rovecode ·` for the current run: `needs you` while a card waits (the frame
99
- * header's word, draw-frame.ts), else the live activity while running, else the outcome */
100
- export function activityLabel(s: SextantState, now?: number): string {
101
- if (s.card) return "needs you";
102
- const a = s.activity;
103
- // a provider turn with no text yet: the word rotates with the clock so the eye can tell live from frozen
104
- if (s.running) return now !== undefined && a.turnAt !== undefined && a.state === "THINKING" ? thinkingWord(now - a.turnAt) : a.label || a.state.toLowerCase();
105
- switch (a.state) {
106
- case "SUCCESS": return "done";
107
- case "ERROR": return "error";
108
- case "WAITING": return "needs you";
109
- case "IDLE": return "";
110
- default: return a.label || a.state.toLowerCase();
111
- }
112
- }
113
-
114
- /** ` 14s · 1.2k tokens` for the provider turn in flight — proof the run is alive while the model
115
- * is silent (a reasoning phase runs 15 s and more at high effort). The clock starts at turn_start,
116
- * the count is reasoning plus answer tokens so far. Degrades in steps to fit `room`: the token count
117
- * goes first, then the clock, so a narrow panel never overflows. Empty when no turn is in flight. */
118
- export function liveTail(s: SextantState, now: number, room: number): string {
119
- const a = s.activity;
120
- if (a.turnAt === undefined) return "";
121
- const clock = fmtElapsed(now - a.turnAt);
122
- const full = a.tokens ? ` ${clock} · ${fmtK(a.tokens)} tokens` : ` ${clock}`;
123
- if ([...full].length <= room) return full;
124
- const short = ` ${clock}`;
125
- return [...short].length <= room ? short : "";
126
- }
127
-
128
- /** `@path` mentions in a user line get the accent (app.js:596) */
129
- function mentionSegs(line: string, theme: Theme): Seg[] {
130
- return line.split(/(@[\w./\\-]+)/).filter(Boolean).map((part): Seg => [part, part[0] === "@" ? st(theme.accent, -1, ATTR.BOLD) : st(theme.fg, -1, ATTR.BOLD)]);
131
- }
132
-
133
- /** Flatten s.messages into drawable rows for a panel `w` cells wide (app.js:572-637). Each run
134
- * (the rows after a user row) opens with a `◆ rovecode · <status>` header before its first row of any
135
- * kind — a permission refusal can be the first thing a run says; the current run carries the live
136
- * status, earlier ones just the diamond. System/compaction rows outside a run (idle notes before
137
- * any user row) get no header. */
138
- export function buildRows(s: SextantState, w: number, theme: Theme, now: number): Row[] {
139
- const rows: Row[] = [];
140
- const blank = (): void => { rows.push({ segs: [] }); };
141
- const iw = Math.max(1, w - 2);
142
- let lastUser = -1;
143
- s.messages.forEach((m, i) => { if (m.kind === "user") lastUser = i; });
144
- let prev: SextantState["messages"][number] | null = null;
145
- let headerDue = true;
146
- let inRun = s.running; // a live run, or the rows after a user row
147
- const outcome = s.activity.state === "SUCCESS" ? theme.ok : s.activity.state === "ERROR" ? theme.err : theme.accentDim;
148
- const header = (i: number): void => {
149
- const current = i > lastUser;
150
- const label = current ? activityLabel(s, now) : "";
151
- const dc = !current ? theme.accentDim : s.running ? theme.accent : outcome;
152
- const segs: Seg[] = [["◆ ", st(dc)], ["rovecode", st(theme.fg, -1, ATTR.BOLD)], [label ? " · " + label : "", st(s.running ? theme.accent : theme.muted)]];
153
- if (current && s.running && !s.card) { const tail = liveTail(s, now, w - segWidth(segs)); if (tail) segs.push([tail, st(theme.muted)]); }
154
- rows.push({ segs });
155
- headerDue = false;
156
- };
157
- s.messages.forEach((m, i) => {
158
- switch (m.kind) {
159
- case "user": {
160
- if (prev) blank();
161
- const last = i === lastUser;
162
- rows.push({ segs: [["you", st(last ? theme.accent : theme.muted)], [last ? " · sent" : "", st(theme.accentDim)]] });
163
- for (const l of wrap(m.text, iw)) rows.push({ segs: mentionSegs(l, theme), indent: 2 });
164
- if (m.images?.length) rows.push({ segs: m.images.flatMap((name): Seg[] => [[` ▣ ${name} `, st(theme.fg2, theme.selBg)], [" ", st(-1)]]), indent: 2 });
165
- // one chip per @file attached (mentions.ts): the read block itself is in the session, not on screen
166
- if (m.files?.length) for (const f of m.files) rows.push({ segs: [[` ▤ ${f} `, st(theme.fg2, theme.selBg)]], indent: 2 });
167
- headerDue = true; inRun = true;
168
- break;
169
- }
170
- case "assistant": {
171
- if (headerDue) { blank(); header(i); } else if (prev && prev.kind !== "assistant") blank();
172
- const lines = m.text ? wrap(m.text, iw) : [];
173
- lines.forEach((l, k) => {
174
- const segs: Seg[] = [[l, st(theme.fg2)]];
175
- if (m.streaming && k === lines.length - 1) segs.push(["▌", st(theme.accent)]);
176
- rows.push({ segs, indent: 2 });
177
- });
178
- if (m.streaming && !lines.length) rows.push({ segs: [["▌", st(theme.accent)]], indent: 2 });
179
- break;
180
- }
181
- case "tool":
182
- if (headerDue) { blank(); header(i); } else if (prev && prev.kind === "assistant") blank();
183
- rows.push({ segs: toolRow(m, iw, theme, now), indent: 2, ...(m.path ? { path: m.path } : {}) });
184
- break;
185
- case "steer":
186
- if (headerDue) { blank(); header(i); } else blank();
187
- wrap(m.text, iw).forEach((l, k) => rows.push({ segs: [[k === 0 ? "» " : " ", st(theme.accent)], [l, st(theme.fg)]] }));
188
- break;
189
- case "compaction":
190
- if (headerDue && inRun) { blank(); header(i); } else blank();
191
- for (const l of wrap(m.text, iw)) rows.push({ segs: [["▸ ", st(theme.accentDim)], [l, st(theme.muted, -1, ATTR.ITALIC)]] });
192
- break;
193
- case "system": {
194
- if (headerDue && inRun) { blank(); header(i); } else if (prev && prev.kind !== "system") blank();
195
- const err = m.tone === "error";
196
- const gc = err ? theme.err : m.tone === "warn" ? theme.warn : theme.accentDim;
197
- for (const l of wrap(m.text, iw)) rows.push({ segs: [[err ? "× " : "▸ ", st(gc)], [l, st(err ? theme.err : theme.muted)]] });
198
- break;
199
- }
200
- }
201
- prev = m;
202
- });
203
- // a run that has not said anything yet still shows it is alive: the header with the live line
204
- // is the ONLY thing between the sent prompt and a first token that may be 15 s away. Not under a
205
- // card — the card is the status then, and the message rows above it are budgeted to the row.
206
- if (headerDue && s.running && inRun && !s.card) { blank(); header(s.messages.length); }
207
- return rows;
208
- }
209
-
210
- /** row skeleton of the card: total rows and the free-text row index (question card) — the shared
211
- * geometry behind `cardRows` and `promptCursor` */
212
- export function cardShape(card: CardState, w: number, maxDetail: number): { total: number; freeText: number | null } {
213
- if (card.kind === "approval") {
214
- const n = card.detail ? detailLines(card.detail).length : 0;
215
- return { total: 3 + Math.min(n, Math.max(1, maxDetail)), freeText: null };
216
- }
217
- const { q, opts, freeRows } = questionRows(card, w);
218
- return { total: 1 + q + opts + freeRows + 2, freeText: freeRows > 0 ? 1 + q + opts : null };
219
- }
220
-
221
- /** the longest free-text answer shown (wrapped rows) before the head scrolls off — the tail keeps
222
- * the caret and the last words in view, the way a chat box behaves */
223
- export const MAX_FREE_TEXT_ROWS = 6;
224
-
225
- /** Row arithmetic the question card shares between its shape, its rows, the caret and the click
226
- * zones (card-hits.ts): `q` question rows (capped), `opts` option rows, and `freeRows` — the typed
227
- * answer WRAPPED to the card width (Berkay: a long answer used to be clipped to one row with `…`,
228
- * so the text you were typing vanished; it belongs on the next line). 0 when free text is off. */
229
- export function questionRows(card: Extract<CardState, { kind: "question" }>, w: number): { q: number; opts: number; freeRows: number; freeLines: string[] } {
230
- const q = Math.min(MAX_QUESTION_ROWS, wrap(card.prompt.question, Math.max(1, w - 2)).length);
231
- const opts = card.prompt.options?.length ?? 0;
232
- if (card.prompt.allowFreeText === false) return { q, opts, freeRows: 0, freeLines: [] };
233
- // the hint occupies the single empty row; typed text wraps at the row width minus the `▌ ` marker
234
- const all = card.freeText.length === 0 ? [""] : wrap(card.freeText, Math.max(1, w - 4));
235
- const freeLines = all.length > MAX_FREE_TEXT_ROWS ? all.slice(all.length - MAX_FREE_TEXT_ROWS) : all;
236
- return { q, opts, freeRows: freeLines.length, freeLines };
237
- }
238
-
239
- /** the previewDiff lines worth a card row: the leading `--- a/x` / `+++ b/x` pair is dropped (the
240
- * title's `<tool> <args>` already names the file); hunk headers, signed lines and markers stay */
241
- function detailLines(detail: string): string[] {
242
- const all = detail.split("\n");
243
- return all[0]?.startsWith("--- ") && all[1]?.startsWith("+++ ") ? all.slice(2) : all;
244
- }
245
-
246
- /** color of one previewDiff line inside the approval card */
247
- function diffLineStyle(l: string, theme: Theme): Seg {
248
- if (l.startsWith("+++") || l.startsWith("---") || l.startsWith("@@") || l.startsWith("…") || l.startsWith("\\")) return [l, st(theme.dim)];
249
- if (l[0] === "+") return [l, st(theme.ok)];
250
- if (l[0] === "-") return [l, st(theme.err)];
251
- return [l, st(theme.fg2)];
252
- }
253
-
254
- /** The modal card rows (app.js:607-616): approval = `◆ needs your permission <tool> <args>`, the
255
- * previewDiff detail bounded to `maxDetail` rows (clip marker), the verdict buttons
256
- * `allow · always · deny` with the selected one inverted; question = the question, its options,
257
- * the free-text row (when allowed) and `skip this question`, selection inverted the same way. */
258
- export function cardRows(card: CardState, w: number, maxDetail: number, theme: Theme): Row[] {
259
- const rows: Row[] = [{ segs: [] }];
260
- const button = (label: string, sel: boolean): Seg => [` ${label} `, sel ? st(theme.bg, theme.accent, ATTR.BOLD) : st(theme.muted)];
261
- if (card.kind === "approval") {
262
- rows.push({ segs: [["◆ ", st(theme.warn)], ["needs your permission", st(theme.fg, -1, ATTR.BOLD)], [` ${card.tool} ${card.argsPreview}`.trimEnd(), st(theme.fg2)]] });
263
- if (card.detail) {
264
- const all = detailLines(card.detail);
265
- const max = Math.max(1, maxDetail);
266
- const lines = all.length > max ? [...all.slice(0, max - 1), moreMarker(all.length - max + 1)] : all;
267
- for (const l of lines) rows.push({ segs: [diffLineStyle(l, theme)], indent: 2 });
268
- }
269
- const segs: Seg[] = [];
270
- card.verdicts.forEach((v, i) => { segs.push(button(VERDICT_LABEL[v], i === card.selected), [" ", st(-1)]); });
271
- segs.push([" ⏎ confirm ←→ choose esc deny", st(theme.dim)]);
272
- rows.push({ segs, indent: 2 });
273
- return rows;
274
- }
275
- const q = wrap(card.prompt.question, Math.max(1, w - 2));
276
- const shown = q.length > MAX_QUESTION_ROWS ? [...q.slice(0, MAX_QUESTION_ROWS - 1), (q[MAX_QUESTION_ROWS - 1] ?? "").slice(0, Math.max(0, w - 4)) + "…"] : q;
277
- shown.forEach((l, k) => rows.push({ segs: [[k === 0 ? "◆ " : " ", st(theme.warn)], [l, st(theme.fg, -1, ATTR.BOLD)]] }));
278
- const opts = card.prompt.options ?? [];
279
- opts.forEach((o, i) => rows.push({ segs: [button(o, i === card.selected)], indent: 2 }));
280
- const { freeRows, freeLines } = questionRows(card, w);
281
- const free = freeRows > 0;
282
- if (free) {
283
- const sel = card.selected === opts.length;
284
- // the answer wraps: `▌ ` marks the first row, continuation rows align under the text
285
- freeLines.forEach((line, k) => {
286
- const marker: Seg = [k === 0 ? "▌ " : " ", st(sel ? theme.accent : theme.accentDim)];
287
- const empty = card.freeText.length === 0;
288
- rows.push({ segs: [marker, [empty ? FREE_TEXT_HINT : line, empty ? st(theme.dim) : st(theme.fg, -1, ATTR.BOLD)]], indent: 2 });
289
- });
290
- }
291
- rows.push({ segs: [button(SKIP_LABEL, card.selected === opts.length + (free ? 1 : 0))], indent: 2 });
292
- rows.push({ segs: [["⏎ confirm ↑↓ choose", st(theme.dim)]], indent: 2 });
293
- return rows;
294
- }
295
-
296
- /** detail rows an approval card may take inside a message area `h` rows tall: all but the card's
297
- * blank + title + buttons and two message rows (the 160×44 frame's h = 10 → 5: a small hunk whole) */
298
- const detailBudget = (h: number): number => Math.max(2, h - 5);
299
-
300
- /** message rows above the rule, card rows (bounded so ≥1 message row survives), rows per message area */
301
- export function areas(B: Rect, s: SextantState): { h: number; cardH: number; msgH: number; maxDetail: number } {
302
- const h = Math.max(0, B.h - 2);
303
- const maxDetail = detailBudget(h);
304
- const cardH = s.card ? Math.min(cardShape(s.card, B.w, maxDetail).total, Math.max(0, h - 1)) : 0;
305
- return { h, cardH, msgH: h - cardH, maxDetail };
306
- }
307
-
308
- /** Scroll geometry for the message rows: `max` = rows that do not fit, `offset` = the row drawn
309
- * first — the tail when `stick`, else s.msgScroll clamped. The renderer writes `offset` back into
310
- * s.msgScroll after a frame (the prototype mutated S.msgScroll in place; a pure painter cannot). */
311
- export function messagesScroll(rect: Rect, s: SextantState, theme: Theme, now: number): { offset: number; max: number } {
312
- const B = inner(rect);
313
- const { msgH } = areas(B, s);
314
- const rows = cachedBuildRows(s, B.w, theme, now);
315
- const max = Math.max(0, rows.length - msgH);
316
- return { offset: s.stick ? max : Math.max(0, Math.min(s.msgScroll, max)), max };
317
- }
318
-
319
- /** Where the terminal cursor belongs for the prompt (or the question card's free-text row when it
320
- * is selected); null when the messages panel is not taking text (other focus, palette/help, approval). */
321
- export function promptCursor(rect: Rect, s: SextantState): { x: number; y: number } | null {
322
- if (s.palette || s.help) return null;
323
- const B = inner(rect);
324
- if (B.w < 3 || B.h < 3) return null;
325
- const { cardH, msgH, maxDetail } = areas(B, s);
326
- if (s.card) {
327
- if (s.card.kind !== "question") return null;
328
- const shape = cardShape(s.card, B.w, maxDetail);
329
- if (shape.freeText === null || s.card.selected !== (s.card.prompt.options?.length ?? 0)) return null; // selection index = options.length is the free-text row
330
- // the caret sits at the end of the LAST wrapped row of the answer (questionRows keeps the tail)
331
- const { freeRows, freeLines } = questionRows(s.card, B.w);
332
- const row = shape.freeText + freeRows - 1 - (shape.total - cardH); // rows hidden when the card is clipped scroll off the top
333
- if (row < 0) return null;
334
- const last = freeLines[freeLines.length - 1] ?? "";
335
- return { x: Math.min(B.x + 4 + [...last].length, B.x + B.w - 1), y: B.y + msgH + row };
336
- }
337
- if (s.focus !== "messages") return null;
338
- const inW = B.w - 2;
339
- const off = Math.max(0, s.input.cur - inW + 1);
340
- return { x: B.x + 2 + s.input.cur - off, y: B.y + B.h - 1 };
341
- }
342
-
343
- /** Paint the messages panel into `rect`: frame + count, rows (scrolled), the pinned card, the ╌
344
- * rule, the prompt line `▌ <text>` (placeholder when empty). Pure over its inputs. */
345
- export function drawMessages(scr: ScreenLike, rect: Rect, s: SextantState, theme: Theme, now: number): void {
346
- if (rect.w < 4 || rect.h < 3) return;
347
- const count = s.messages.filter((m) => m.kind === "user").length;
348
- const focused = s.focus === "messages";
349
- const B = panel(scr, rect, "messages", focused, count ? [[String(count), st(theme.muted)]] : [], theme);
350
- const { h, cardH, msgH, maxDetail } = areas(B, s);
351
- if (h > 0) {
352
- const rows = cachedBuildRows(s, B.w, theme, now);
353
- const max = Math.max(0, rows.length - msgH);
354
- const offset = s.stick ? max : Math.max(0, Math.min(s.msgScroll, max));
355
- scrollbar(scr, theme, B.x + B.w - 1, B.y, msgH, rows.length, msgH, offset); // scroll-hits.ts rebuilds this geometry for the drag zone
356
- for (let i = 0; i < msgH; i++) {
357
- const r = rows[offset + i];
358
- if (!r) break;
359
- const cx = B.x + (r.indent ?? 0);
360
- scr.text(cx, B.y + i, r.segs, B.x + B.w - cx);
361
- }
362
- if (s.card && cardH > 0) {
363
- const all = cardRows(s.card, B.w, maxDetail, theme);
364
- const shown = all.slice(all.length - cardH); // when clipped keep the tail: the buttons must stay reachable
365
- shown.forEach((r, i) => { const cx = B.x + (r.indent ?? 0); scr.text(cx, B.y + msgH + i, r.segs, B.x + B.w - cx); });
366
- }
367
- }
368
- const py = B.y + B.h - 1;
369
- if (B.h >= 2) scr.hline(B.x, py - 1, B.w, st(theme.frameDim), "╌");
370
- // staged images ride ON the rule row as chips — visible before the message is sent, and no row is
371
- // taken from the transcript for them (Berkay: keep the surface simple). Clipped, oldest dropped first.
372
- if (B.h >= 2 && s.staged.length > 0) {
373
- const segs: Seg[] = [[" ", st(-1)]];
374
- for (const name of s.staged) segs.push([` ▣ ${name} `, st(theme.fg2, theme.selBg)], [" ", st(-1)]);
375
- segs.push([`${s.staged.length}/${MAX_STAGED_HINT} · enter sends · /attach clear`, st(theme.dim)]);
376
- scr.text(B.x, py - 1, segs, B.w);
377
- }
378
- const hasText = s.input.text.length > 0;
379
- scr.put(B.x, py, "▌", st(hasText || focused ? theme.accent : theme.accentDim));
380
- const inW = B.w - 2;
381
- if (inW <= 0) return;
382
- if (hasText) {
383
- const off = Math.max(0, s.input.cur - inW + 1);
384
- scr.put(B.x + 2, py, s.input.text.slice(off), st(theme.fg, -1, ATTR.BOLD), inW);
385
- } else scr.put(B.x + 2, py, PLACEHOLDER, st(theme.dim), inW);
386
- }
@@ -1,230 +0,0 @@
1
- /** Port #45 — paints rovecode into its panel: a rounded frame titled `<name> lv N <mood>`, the cloud
2
- * sprite on the prototype's 1.8 s step sway (pet.js:196), weather (code drizzle while editing, lightning while running/testing,
3
- * sun on success, zzz when sleepy), the storm (dark filled body, furrowed brows, red eyes, rain + bolts,
4
- * panel flash, reddened border), sparkles/hearts, and a ≤2-line speech bubble — everything clipped to
5
- * the panel rect. Ported from the user's own sextant v0.4.0 prototype `src/pet.js` draw() + `app.js`
6
- * drawPet()/panel() (user-owned). PURE: `now` is a parameter; no timers, no unseeded randomness. */
7
-
8
- import { ATTR, type Rect, type ScreenLike, type Seg, type SextantState, type Style, type Theme } from "./types.ts";
9
- import { CODE_RAIN, FACE, INNER, SPRITE, moodCtxFrom, type Mood, type MoodCtx, type Pet } from "./pet.ts";
10
- import { mix, st } from "./theme.ts";
11
-
12
- /** term.js mix() lives in theme.ts (port #40); re-exported for the pet tests/fixtures that import it here */
13
- export { mix };
14
-
15
- /** the sprite shifts one row every SWAY_MS — pet.js:196 `Math.floor(t / 1800) % 2`, the README's "1.8 s salınım"
16
- * (full period 3.6 s); the prototype's ±1-column drift (pet.js:197) is deliberately not ported: "titreme yok" */
17
- export const SWAY_MS = 1800;
18
- /** a double-flicker strike every 2.6 s; the panel flashes for the first 260 ms */
19
- export const STORM_PERIOD_MS = 2600;
20
- export const STORM_FLASH_MS = 260;
21
- export const SPRITE_W = 18;
22
-
23
- /** the interior shading, per sprite row: light at the top, heaviest along the base (see the fill in
24
- * drawPet). Rows past the end fall back to ░, so a re-drawn sprite cannot crash the painter. */
25
- export const FILL_GLYPH: readonly string[] = ["░", "░", "░", "▒", "▓", "▓"];
26
- export const FILL_MIX: readonly number[] = [0.10, 0.12, 0.14, 0.18, 0.24, 0.26];
27
-
28
- /** the click zone the input layer registers (a click anywhere in the panel pokes); null when hidden */
29
- export function petHit(rect: Rect | null): Rect | null { return rect; }
30
-
31
- export function moodColor(m: Mood, theme: Theme): number {
32
- if (m === "sunny") return theme.ok;
33
- if (m === "furious") return theme.err;
34
- if (m === "patient") return theme.warn;
35
- if (m === "focused" || m === "zapping" || m === "conducting") return theme.accent;
36
- return theme.muted;
37
- }
38
-
39
- /** vertical sway phase: a step that flips every SWAY_MS (frames within one step are identical — no per-frame jitter) */
40
- export function swayBob(now: number): 0 | 1 { return (Math.floor(now / SWAY_MS) % 2) as 0 | 1; }
41
-
42
- /** word wrap to w cells (code points); a word longer than a row — a host, a path — is hard-split, so a
43
- * bubble line is never wider than the row and the closing ” survives put()'s clip (it used to be left
44
- * whole and the quote fell off the edge) */
45
- export function wrapText(text: string, w: number): string[] {
46
- const out: string[] = [];
47
- const width = Math.max(1, w);
48
- let line = "", len = 0;
49
- for (const raw of text.split(" ")) {
50
- const cps = [...raw];
51
- while (cps.length > width) { if (line) { out.push(line); line = ""; len = 0; } out.push(cps.splice(0, width).join("")); }
52
- if (!cps.length) continue;
53
- const word = cps.join("");
54
- if (!line) { line = word; len = cps.length; continue; }
55
- if (len + 1 + cps.length <= width) { line += " " + word; len += 1 + cps.length; }
56
- else { out.push(line); line = word; len = cps.length; }
57
- }
58
- if (line) out.push(line);
59
- return out;
60
- }
61
-
62
- /** no-op when the layout hides the pet (rect null below 34 rows, or ROVECODE_PET=0) */
63
- export function drawPet(scr: ScreenLike, rect: Rect | null, pet: Pet, s: SextantState, theme: Theme, now: number): void {
64
- if (!rect || rect.w < 8 || rect.h < 4) return;
65
- const ctx = moodCtxFrom(s, now);
66
- pet.tick(now, ctx);
67
- const m = pet.mood(ctx, now);
68
- const B = panel(scr, rect, pet.state.name, pet.level(ctx.tokens), m, theme);
69
- drawCloud(scr, B, pet, ctx, m, theme, now);
70
- }
71
-
72
- /** app.js panel(): rounded box, bold title in the top border, right-aligned extras; returns the inner rect.
73
- * When the row is too narrow for `lv N mood` the level is dropped first, then the mood. */
74
- function panel(scr: ScreenLike, R: Rect, name: string, lvl: number, m: Mood, theme: Theme): Rect {
75
- const furious = m === "furious";
76
- const col = furious ? mix(theme.bg, theme.err, 0.6) : theme.frame;
77
- scr.box(R.x, R.y, R.w, R.h, st(col));
78
- scr.text(R.x + 2, R.y, [[" ", st(-1)], [name, st(furious ? col : theme.fg2, -1, ATTR.BOLD)], [" ", st(-1)]], R.w - 3);
79
- const moodSeg: Seg = [m, st(moodColor(m, theme), -1, furious ? ATTR.BOLD : 0)];
80
- const variants: Seg[][] = [[[`lv ${lvl}`, st(theme.muted)], [" ", st(-1)], moodSeg], [moodSeg]];
81
- for (const extra of variants) {
82
- const ew = extra.reduce((n, [t]) => n + t.length, 0);
83
- const ex = R.x + R.w - 3 - ew;
84
- if (ex > R.x + 4 + name.length) { scr.text(ex, R.y, [[" ", st(-1)], ...extra, [" ", st(-1)]]); break; }
85
- }
86
- return { x: R.x + 2, y: R.y + 1, w: R.w - 4, h: R.h - 2 };
87
- }
88
-
89
- function drawCloud(scr: ScreenLike, B: Rect, pet: Pet, ctx: MoodCtx, m: Mood, theme: Theme, t: number): void {
90
- const P = pet.state;
91
- const has = (k: string) => P.fx.find((f) => f.kind === k);
92
- const inB = (x: number, y: number) => x >= B.x && x < B.x + B.w && y >= B.y && y < B.y + B.h;
93
- const putSafe = (x: number, y: number, ch: string, s: Style) => { if (inB(x, y)) scr.put(x, y, ch, s); };
94
- const sleeping = m === "sleepy", stormy = m === "furious", sunny = m === "sunny";
95
- const zapping = ctx.state === "RUNNING" || ctx.state === "TESTING";
96
- // storm timing: a double-flicker strike every 2.6 s; the panel lights up for the first 260 ms
97
- let flash = false, boltCols: number[] = [];
98
- if (stormy) {
99
- const cyc = Math.floor(t / STORM_PERIOD_MS);
100
- flash = t % STORM_PERIOD_MS < STORM_FLASH_MS;
101
- boltCols = [3 + ((cyc * 7919) % 11), 3 + ((cyc * 104729 + 5) % 11)];
102
- if (flash) scr.tint(B.x, B.y, B.w, B.h, mix(theme.bg, theme.warn, 0.07));
103
- }
104
- // body colour: dark red outline over a ░-filled body in a storm, dim while asleep, bright while busy
105
- let body = theme.fg2;
106
- if (sunny) body = theme.fg;
107
- else if (stormy) body = flash ? theme.warn : mix(theme.bg, theme.err, 0.7);
108
- else if (sleeping) body = theme.mixDim;
109
- else if (ctx.state === "EDITING" || ctx.running) body = theme.fg;
110
- // the interior is always painted now: ░ across the top rows, ▒ through the middle, ▓ along the base —
111
- // two shading steps that read as volume without touching the outline. The tone is mixed from the body
112
- // colour, so the cloud dims asleep and brightens while busy with the rest of the sprite. A storm keeps
113
- // its own flat, brighter fill (and its flash), which is what makes it read as a different thing.
114
- const stormFill = stormy ? mix(theme.bg, theme.fg, flash ? 0.35 : 0.22) : null;
115
- const faceFg = stormy ? theme.err : sleeping ? theme.muted : theme.fg;
116
- const mouthFg = stormy ? theme.fg2 : sleeping ? theme.muted : theme.fg;
117
- // motion: bounce fx hops every 200 ms, shiver fx jitters sideways, a strike shakes the cloud,
118
- // otherwise the calm 1.8 s sway (asleep: settled low)
119
- const bob = has("bounce") ? Math.floor(t / 200) % 2 : sleeping ? 1 : swayBob(t);
120
- const drift = has("shiver") ? Math.floor(t / 160) % 2 : flash ? 1 : 0;
121
- const sx = B.x + Math.max(0, Math.floor((B.w - SPRITE_W) / 2)) + drift, sy = B.y + 2 - bob;
122
- // face
123
- let eyes = "••", mouth = "◡", eyeShift = 0;
124
- const blink = !sleeping && t % 5200 < 170;
125
- if (ctx.booting) eyes = "──";
126
- else if (stormy) { eyes = flash ? "▪▪" : "••"; mouth = "∩"; }
127
- else if (sleeping) eyes = "──";
128
- else if (sunny) eyes = "◠◠";
129
- else if (m === "patient") mouth = "○";
130
- else if (zapping) mouth = "─";
131
- else if (ctx.state === "READING") eyeShift = [-1, 0, 1, 0][Math.floor(t / 900) % 4] ?? 0;
132
- else if (ctx.running) eyeShift = 1;
133
- if (P.glance && P.glance.until > t && !sleeping && !stormy) eyeShift = P.glance.dir;
134
- if (ctx.typing && !ctx.running) eyeShift = 1;
135
- if (blink && !stormy) eyes = "──";
136
- for (let r = 0; r < SPRITE.length; r++) {
137
- const y = sy + r, row = SPRITE[r] ?? "", [a, b] = INNER[r] ?? [0, 0];
138
- for (let i = 0; i < row.length; i++) {
139
- const ch = row[i] ?? " ";
140
- if (ch !== " ") putSafe(sx + i, y, ch, st(body));
141
- else if (i > a && i < b) {
142
- putSafe(sx + i, y, stormFill != null ? "░" : (FILL_GLYPH[r] ?? "░"),
143
- st(stormFill ?? mix(theme.bg, body, FILL_MIX[r] ?? 0.14)));
144
- }
145
- }
146
- if (r === FACE.EYE_ROW) {
147
- putSafe(sx + FACE.EYE_L + eyeShift, y, eyes[0] ?? "•", st(faceFg, -1, ATTR.BOLD));
148
- putSafe(sx + FACE.EYE_R + eyeShift, y, eyes[1] ?? "•", st(faceFg, -1, ATTR.BOLD));
149
- }
150
- if (r === FACE.EYE_ROW - 1 && stormy) { // furrowed brows
151
- putSafe(sx + FACE.EYE_L + eyeShift, y, "╲", st(theme.err, -1, ATTR.BOLD));
152
- putSafe(sx + FACE.EYE_R + eyeShift, y, "╱", st(theme.err, -1, ATTR.BOLD));
153
- }
154
- if (r === FACE.MOUTH_ROW) putSafe(sx + FACE.MOUTH + (eyeShift > 0 ? 1 : 0), y, mouth, st(mouthFg));
155
- }
156
- // weather + extras
157
- const wy = sy + SPRITE.length; // first row under the cloud
158
- if (sunny) {
159
- putSafe(sx + 15, sy - 1, "☼", st(theme.warn, -1, ATTR.BOLD));
160
- putSafe(sx + 17, sy - 2, "·", st(theme.warn));
161
- putSafe(sx + 13, sy - 2, "·", st(theme.warn));
162
- putSafe(sx + 18, sy, "·", st(theme.warn));
163
- }
164
- if (sleeping) {
165
- const ph = Math.floor(t / 1200) % 3;
166
- putSafe(sx + 13, sy - (ph >= 1 ? 1 : 0), "z", st(theme.muted));
167
- if (ph >= 1) putSafe(sx + 15, sy - 1 - (ph === 2 ? 1 : 0), "Z", st(theme.muted));
168
- }
169
- if (ctx.running && !stormy && m !== "patient" && ctx.state !== "EDITING" && !zapping) { // thinking
170
- const ph = Math.floor(t / 800) % 4;
171
- putSafe(sx + 17, sy + 1, "◌", st(theme.muted));
172
- if (ph >= 1) putSafe(sx + 18, sy, "○", st(theme.muted));
173
- if (ph >= 2) putSafe(sx + 20, sy - 1, ["…", "·", "?", "…"][Math.floor(t / 3000) % 4] ?? "…", st(theme.fg2));
174
- }
175
- if (m === "patient") putSafe(sx + 17, sy + (Math.floor(t / 900) % 2 ? 0 : 1), "?", st(theme.warn, -1, ATTR.BOLD)); // slow blink
176
- if (ctx.state === "EDITING") { // code drizzle
177
- for (let row = 0; row < 2; row++) {
178
- const phase = Math.floor(t / 420) + row * 2;
179
- for (let x = sx + 3; x < sx + 16; x++) {
180
- if ((x + phase) % 4 !== 0) continue;
181
- const ch = CODE_RAIN[(x * 7 + Math.floor(t / 1400) + row) % CODE_RAIN.length] ?? ".";
182
- putSafe(x, wy + row, ch, st(row ? theme.dim : theme.accentDim));
183
- }
184
- }
185
- }
186
- if (zapping && Math.floor(t / 700) % 2 === 0) { // lightning
187
- const k = sx + 4 + (Math.floor(t / 2100) % 3) * 5;
188
- putSafe(k, wy, "╲", st(theme.warn, -1, ATTR.BOLD));
189
- putSafe(k, wy + 1, "╱", st(theme.warn, -1, ATTR.BOLD));
190
- }
191
- if (stormy) {
192
- const rows = Math.max(1, Math.min(3, B.y + B.h - 2 - wy));
193
- for (let row = 0; row < rows; row++) {
194
- for (let x = sx + 1; x < sx + 17; x++) {
195
- if ((x * 3 + Math.floor(t / 330) + row * 2) % 4 === 0) putSafe(x, wy + row, row % 2 ? "╷" : "│", st(flash ? theme.fg2 : theme.info));
196
- }
197
- }
198
- if (flash) {
199
- for (const k of boltCols) for (let r = 0; r < rows; r++) putSafe(sx + k, wy + r, r % 2 ? "╱" : "╲", st(theme.warn, -1, ATTR.BOLD));
200
- putSafe(sx - 1, sy + 2, "╲", st(theme.warn, -1, ATTR.BOLD));
201
- putSafe(sx + 18, sy + 1, "╱", st(theme.warn, -1, ATTR.BOLD));
202
- }
203
- }
204
- const sp = has("sparkle");
205
- if (sp) {
206
- for (let i = 0; i < 7; i++) {
207
- const h = (sp.seed * (i + 3) * 2654435761) >>> 0;
208
- const x = B.x + (h % B.w), y = B.y + ((h >>> 8) % Math.max(1, wy + 1 - B.y));
209
- const on = Math.floor((t + (h % 500)) / 520) % 2 === 0;
210
- if (on && !(y >= sy && y < wy && x >= sx + 1 && x < sx + 18)) putSafe(x, y, ["*", "·", "+"][i % 3] ?? "*", st(i % 2 ? theme.warn : theme.ok));
211
- }
212
- }
213
- const hearts = has("hearts");
214
- if (hearts) {
215
- const age = t - (hearts.until - 1800);
216
- const rise = Math.min(2, Math.floor(Math.max(0, age) / 700));
217
- putSafe(sx + 16, sy + 1 - rise, "♥", st(theme.err));
218
- if (rise >= 1) putSafe(sx + 19, sy + 2 - rise, "♥", st(theme.err));
219
- }
220
- // speech bubble: at most two lines at the bottom of the panel, clipped to its width
221
- const q = P.quip && P.quip.until > t ? P.quip.text : null;
222
- if (q) {
223
- const qy = B.y + B.h - 2;
224
- // B.w - 3: the two quote marks share the line and put() clips at B.w - 1, so a full line keeps its closing ”
225
- const lines = wrapText(q, B.w - 3).slice(0, 2);
226
- lines.forEach((l, i) => {
227
- if (qy + i < B.y + B.h) scr.put(B.x + 1, qy + i, (i === 0 ? "“" : " ") + l + (i === lines.length - 1 ? "”" : ""), st(theme.fg2, -1, ATTR.ITALIC), B.w - 1);
228
- });
229
- }
230
- }