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