rovecode 0.4.0-beta.2 → 0.4.0

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 (428) hide show
  1. package/README.md +67 -69
  2. package/THIRD_PARTY_NOTICES.md +0 -44
  3. package/bin/rovecode.ts +21 -0
  4. package/package.json +16 -37
  5. package/src/account/keys.ts +97 -0
  6. package/src/account/login.ts +158 -0
  7. package/src/account/provision.ts +47 -0
  8. package/src/account/store.ts +63 -0
  9. package/src/acp/server.ts +373 -0
  10. package/src/cli/account-cmd.ts +116 -0
  11. package/src/cli/connect.ts +244 -0
  12. package/src/cli/context-cmd.ts +199 -0
  13. package/src/cli/dispatch.ts +109 -0
  14. package/src/cli/doctor.ts +324 -0
  15. package/src/cli/export.ts +278 -0
  16. package/src/cli/help.ts +240 -0
  17. package/src/cli/is-tui-invocation.ts +8 -0
  18. package/src/cli/main.ts +599 -0
  19. package/src/cli/market-cmd.ts +658 -0
  20. package/src/cli/mcp-market-cmd.ts +299 -0
  21. package/src/cli/output.ts +382 -0
  22. package/src/cli/repl.ts +172 -0
  23. package/src/cli/resume.ts +32 -0
  24. package/src/cli/run-limits.ts +78 -0
  25. package/src/cli/runtime.ts +792 -0
  26. package/src/cli/setup.ts +187 -0
  27. package/src/cli/update-cmd.ts +78 -0
  28. package/src/cli/workflow-cmd.ts +100 -0
  29. package/src/coding/checkpoints.ts +270 -0
  30. package/src/coding/diff.ts +136 -0
  31. package/src/coding/files.ts +339 -0
  32. package/src/coding/hashline.ts +319 -0
  33. package/src/coding/lsp.ts +406 -0
  34. package/src/coding/repomap-cache.ts +99 -0
  35. package/src/coding/repomap-files.ts +110 -0
  36. package/src/coding/repomap.ts +392 -0
  37. package/src/core/compaction.ts +399 -0
  38. package/src/core/config.ts +289 -0
  39. package/src/core/context-report.ts +228 -0
  40. package/src/core/context.ts +60 -0
  41. package/src/core/count-remote.ts +107 -0
  42. package/src/core/execpolicy-rules.ts +196 -0
  43. package/src/core/execpolicy.ts +385 -0
  44. package/src/core/executor.ts +397 -0
  45. package/src/core/guardrails.ts +400 -0
  46. package/src/core/hooks.ts +398 -0
  47. package/src/core/images.ts +230 -0
  48. package/src/core/intro.ts +236 -0
  49. package/src/core/loop.ts +621 -0
  50. package/src/core/modes.ts +372 -0
  51. package/src/core/orchestrator.ts +207 -0
  52. package/src/core/reflection.ts +165 -0
  53. package/src/core/sandbox-config.ts +167 -0
  54. package/src/core/session-images.ts +73 -0
  55. package/src/core/session.ts +398 -0
  56. package/src/core/settings.ts +98 -0
  57. package/src/core/stuck-detector.ts +273 -0
  58. package/src/core/tasks.ts +374 -0
  59. package/src/core/token-scale.ts +108 -0
  60. package/src/core/tool-output-budget.ts +166 -0
  61. package/src/core/tools.ts +288 -0
  62. package/src/core/types.ts +330 -0
  63. package/src/core/update-check.ts +171 -0
  64. package/src/core/update.ts +158 -0
  65. package/src/core/usage.ts +204 -0
  66. package/src/core/validate.ts +121 -0
  67. package/src/core/verify-gate.ts +159 -0
  68. package/src/core/verify.ts +237 -0
  69. package/src/core/voice.ts +158 -0
  70. package/src/core/win-job.ts +183 -0
  71. package/src/design/audit.ts +797 -0
  72. package/src/design/direction.ts +190 -0
  73. package/src/design/rules.ts +157 -0
  74. package/src/eval/bench.ts +150 -0
  75. package/src/eval/gauntlet-runner.ts +218 -0
  76. package/src/eval/gauntlet.ts +226 -0
  77. package/src/eval/grader.ts +186 -0
  78. package/src/eval/record.ts +202 -0
  79. package/src/eval/redact.ts +141 -0
  80. package/src/eval/replay.ts +147 -0
  81. package/src/eval/trajectory.ts +373 -0
  82. package/src/index.ts +17 -0
  83. package/src/market/catalogs/mcp-docs.json +111 -0
  84. package/src/market/catalogs/plugins.json +111 -0
  85. package/src/market/catalogs/skills.json +478 -0
  86. package/src/market/clone.ts +72 -0
  87. package/src/market/context-cost.ts +121 -0
  88. package/src/market/digest.ts +106 -0
  89. package/src/market/index.ts +22 -0
  90. package/src/market/install.ts +578 -0
  91. package/src/market/manifest.ts +187 -0
  92. package/src/market/prereq.ts +145 -0
  93. package/src/market/registry.ts +363 -0
  94. package/src/market/resolve.ts +111 -0
  95. package/src/market/types.ts +236 -0
  96. package/src/market/validate.ts +227 -0
  97. package/src/mcp/client.ts +431 -0
  98. package/src/mcp/config.ts +239 -0
  99. package/src/mcp/local-package.ts +211 -0
  100. package/src/mcp/market-catalog.ts +84 -0
  101. package/src/mcp/market-install.ts +289 -0
  102. package/src/mcp/market.ts +0 -0
  103. package/src/mcp/tools.ts +131 -0
  104. package/src/mcp/trust.ts +49 -0
  105. package/src/memory/blocks.ts +175 -0
  106. package/src/memory/recall.ts +355 -0
  107. package/src/memory/store.ts +105 -0
  108. package/src/memory/tools.ts +99 -0
  109. package/src/plugins/cli.ts +123 -0
  110. package/src/plugins/discover.ts +108 -0
  111. package/src/plugins/index.ts +50 -0
  112. package/src/plugins/init.ts +140 -0
  113. package/src/plugins/install.ts +184 -0
  114. package/src/plugins/load.ts +149 -0
  115. package/src/plugins/manifest.ts +106 -0
  116. package/src/plugins/state.ts +83 -0
  117. package/src/providers/auth.ts +293 -0
  118. package/src/providers/cache.ts +223 -0
  119. package/src/providers/catalog-local.ts +160 -0
  120. package/src/providers/catalog.ts +408 -0
  121. package/src/providers/middleware-context.ts +86 -0
  122. package/src/providers/middleware.ts +373 -0
  123. package/src/providers/profile-glm53.ts +111 -0
  124. package/src/providers/profile-sonnet5-persona.ts +65 -0
  125. package/src/providers/profile-sonnet5-voice.ts +23 -0
  126. package/src/providers/profiles.ts +156 -0
  127. package/src/providers/provider-config.ts +311 -0
  128. package/src/providers/registry.ts +302 -0
  129. package/src/providers/response-validation.ts +80 -0
  130. package/src/providers/retry.ts +234 -0
  131. package/src/providers/router.ts +294 -0
  132. package/src/providers/sse.ts +26 -0
  133. package/src/providers/stream-errors.ts +117 -0
  134. package/src/providers/stream.ts +569 -0
  135. package/src/providers/thinking.ts +189 -0
  136. package/src/providers/wire-messages.ts +129 -0
  137. package/src/sdk/client.ts +225 -0
  138. package/src/sdk/index.ts +3 -0
  139. package/src/server/dashboard.ts +144 -0
  140. package/src/server/http.ts +343 -0
  141. package/src/server/openapi.ts +246 -0
  142. package/src/sextant/card-hits.ts +102 -0
  143. package/src/sextant/card-keys.ts +55 -0
  144. package/src/sextant/context-source.ts +157 -0
  145. package/src/sextant/draw-agents.ts +273 -0
  146. package/src/sextant/draw-code.ts +388 -0
  147. package/src/sextant/draw-context.ts +222 -0
  148. package/src/sextant/draw-frame.ts +164 -0
  149. package/src/sextant/draw-market.ts +573 -0
  150. package/src/sextant/draw-messages.ts +386 -0
  151. package/src/sextant/draw-pet.ts +230 -0
  152. package/src/sextant/draw-plan.ts +159 -0
  153. package/src/sextant/draw-tabs.ts +85 -0
  154. package/src/sextant/draw-util.ts +65 -0
  155. package/src/sextant/engine.ts +230 -0
  156. package/src/sextant/frame-hits.ts +25 -0
  157. package/src/sextant/frame.ts +101 -0
  158. package/src/sextant/git-status.ts +197 -0
  159. package/src/sextant/grid.ts +59 -0
  160. package/src/sextant/input.ts +119 -0
  161. package/src/sextant/keys.ts +488 -0
  162. package/src/sextant/layout.ts +86 -0
  163. package/src/sextant/local-commands.ts +156 -0
  164. package/src/sextant/market-source.ts +287 -0
  165. package/src/sextant/mentions.ts +141 -0
  166. package/src/sextant/message-hits.ts +26 -0
  167. package/src/sextant/model.ts +387 -0
  168. package/src/sextant/overlays.ts +451 -0
  169. package/src/sextant/panel-hits.ts +38 -0
  170. package/src/sextant/pet.ts +399 -0
  171. package/src/sextant/screen.ts +324 -0
  172. package/src/sextant/scroll-hits.ts +66 -0
  173. package/src/sextant/scrollbar.ts +82 -0
  174. package/src/sextant/selection.ts +123 -0
  175. package/src/sextant/sextant-bridge.ts +174 -0
  176. package/src/sextant/sextant-cards.ts +142 -0
  177. package/src/sextant/sextant-diff-base.ts +63 -0
  178. package/src/sextant/sextant-files.ts +154 -0
  179. package/src/sextant/sextant-frame-loop.ts +314 -0
  180. package/src/sextant/sextant-renderer.ts +478 -0
  181. package/src/sextant/sextant-repo.ts +131 -0
  182. package/src/sextant/theme.ts +66 -0
  183. package/src/sextant/tool-rows.ts +189 -0
  184. package/src/sextant/types.ts +473 -0
  185. package/src/skills/index.ts +306 -0
  186. package/src/skills/tools.ts +69 -0
  187. package/src/skills/versioned.ts +227 -0
  188. package/src/telemetry/otel.ts +353 -0
  189. package/src/telemetry/otlp.ts +68 -0
  190. package/src/tools/ask-user.ts +156 -0
  191. package/src/tools/design.ts +151 -0
  192. package/src/tools/evalcell.ts +338 -0
  193. package/src/tools/html-text.ts +139 -0
  194. package/src/tools/provider.ts +149 -0
  195. package/src/tools/task.ts +216 -0
  196. package/src/tools/todo.ts +320 -0
  197. package/src/tools/webfetch.ts +331 -0
  198. package/src/tui/app.ts +608 -0
  199. package/src/tui/attach.ts +127 -0
  200. package/src/tui/checkpoints-cmd.ts +70 -0
  201. package/src/tui/clipboard-image.ts +81 -0
  202. package/src/tui/commands.ts +277 -0
  203. package/src/tui/cost.ts +108 -0
  204. package/src/tui/info-cmd.ts +144 -0
  205. package/src/tui/mcp-cmd.ts +128 -0
  206. package/src/tui/modes-cmd.ts +45 -0
  207. package/src/tui/overlays.ts +97 -0
  208. package/src/tui/pi-renderer.ts +424 -0
  209. package/src/tui/providers-cmd.ts +366 -0
  210. package/src/tui/renderer.ts +101 -0
  211. package/src/tui/replay-marker.ts +29 -0
  212. package/src/tui/session-cmd.ts +146 -0
  213. package/src/tui/sextant-attach.ts +68 -0
  214. package/src/tui/sextant-io.ts +184 -0
  215. package/src/tui/sextant-smoke.ts +110 -0
  216. package/src/tui/smoke.ts +72 -0
  217. package/src/tui/theme.ts +59 -0
  218. package/src/tui/todo-label.ts +7 -0
  219. package/src/workflow/engine.ts +266 -0
  220. package/tsconfig.json +30 -0
  221. package/vendor/pi-tui/LICENSE +21 -0
  222. package/vendor/pi-tui/PATCHES.md +12 -0
  223. package/vendor/pi-tui/PROVENANCE.md +12 -0
  224. package/vendor/pi-tui/README.upstream.md +854 -0
  225. package/vendor/pi-tui/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
  226. package/vendor/pi-tui/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
  227. package/vendor/pi-tui/src/alt-screen-search.ts +158 -0
  228. package/vendor/pi-tui/src/autocomplete.ts +827 -0
  229. package/vendor/pi-tui/src/components/alt-screen-flash.ts +52 -0
  230. package/vendor/pi-tui/src/components/box.ts +138 -0
  231. package/vendor/pi-tui/src/components/cancellable-loader.ts +41 -0
  232. package/vendor/pi-tui/src/components/editor.ts +2364 -0
  233. package/vendor/pi-tui/src/components/h-stack.ts +45 -0
  234. package/vendor/pi-tui/src/components/image.ts +128 -0
  235. package/vendor/pi-tui/src/components/input.ts +448 -0
  236. package/vendor/pi-tui/src/components/loader.ts +93 -0
  237. package/vendor/pi-tui/src/components/markdown.ts +1016 -0
  238. package/vendor/pi-tui/src/components/scroll-view.ts +217 -0
  239. package/vendor/pi-tui/src/components/select-list.ts +230 -0
  240. package/vendor/pi-tui/src/components/settings-list.ts +277 -0
  241. package/vendor/pi-tui/src/components/spacer.ts +29 -0
  242. package/vendor/pi-tui/src/components/stack.ts +155 -0
  243. package/vendor/pi-tui/src/components/text.ts +108 -0
  244. package/vendor/pi-tui/src/components/truncated-text.ts +66 -0
  245. package/vendor/pi-tui/src/components/v-stack.ts +34 -0
  246. package/vendor/pi-tui/src/editor-component.ts +75 -0
  247. package/vendor/pi-tui/src/fuzzy.ts +138 -0
  248. package/vendor/pi-tui/src/index.ts +149 -0
  249. package/vendor/pi-tui/src/keybindings.ts +321 -0
  250. package/vendor/pi-tui/src/keys.ts +1402 -0
  251. package/vendor/pi-tui/src/kill-ring.ts +47 -0
  252. package/vendor/pi-tui/src/latex.ts +1381 -0
  253. package/vendor/pi-tui/src/layout-node.ts +52 -0
  254. package/vendor/pi-tui/src/layout.ts +411 -0
  255. package/vendor/pi-tui/src/native-modifiers.ts +60 -0
  256. package/vendor/pi-tui/src/native-module-path.ts +32 -0
  257. package/vendor/pi-tui/src/stdin-buffer.ts +445 -0
  258. package/vendor/pi-tui/src/terminal-colors.ts +74 -0
  259. package/vendor/pi-tui/src/terminal-image.ts +701 -0
  260. package/vendor/pi-tui/src/terminal.ts +554 -0
  261. package/vendor/pi-tui/src/tui-alt-screen.ts +1379 -0
  262. package/vendor/pi-tui/src/tui-main-screen.ts +655 -0
  263. package/vendor/pi-tui/src/tui.ts +1264 -0
  264. package/vendor/pi-tui/src/undo-stack.ts +29 -0
  265. package/vendor/pi-tui/src/utils.ts +1327 -0
  266. package/vendor/pi-tui/src/word-navigation.ts +118 -0
  267. package/vendor/pi-tui/test/test-themes.ts +39 -0
  268. package/vendor/pi-tui/test/virtual-terminal.ts +219 -0
  269. package/CHANGELOG.md +0 -512
  270. package/bin/rovecode.js +0 -24
  271. package/dist/cli/app-dybnr56b.js +0 -2
  272. package/dist/cli/ask-user-p8hq4xgj.js +0 -2
  273. package/dist/cli/auth-login-ewpgw5sm.js +0 -2
  274. package/dist/cli/auth-m8p9grty.js +0 -2
  275. package/dist/cli/bench-xv3ypwev.js +0 -9
  276. package/dist/cli/catalog-737wb2s0.js +0 -2
  277. package/dist/cli/cli-arhg40m0.js +0 -2
  278. package/dist/cli/client-cf2pxx8q.js +0 -2
  279. package/dist/cli/commands-3p7e4xxs.js +0 -2
  280. package/dist/cli/connect-3q93d7cb.js +0 -2
  281. package/dist/cli/context-cmd-eqxmxhzq.js +0 -2
  282. package/dist/cli/context-report-hbw9zfes.js +0 -2
  283. package/dist/cli/count-remote-mby98cd0.js +0 -2
  284. package/dist/cli/design-122y0axd.js +0 -2
  285. package/dist/cli/dispatch-b4egzvvh.js +0 -2
  286. package/dist/cli/doctor-x4jkv72e.js +0 -3
  287. package/dist/cli/executor-ftvg6tsy.js +0 -2
  288. package/dist/cli/export-pdgdhkch.js +0 -2
  289. package/dist/cli/files-cez9a96p.js +0 -2
  290. package/dist/cli/gauntlet-r3xxaszc.js +0 -2
  291. package/dist/cli/gauntlet-runner-r515m7kk.js +0 -10
  292. package/dist/cli/gauntlet-wave3-bnkjk2v2.js +0 -5
  293. package/dist/cli/gauntlet-wave4-acs9s60q.js +0 -14
  294. package/dist/cli/hashline-ewg5hbe3.js +0 -2
  295. package/dist/cli/http-n0kehsk8.js +0 -5
  296. package/dist/cli/index-z5qt1s76.js +0 -2
  297. package/dist/cli/install-80mp63kx.js +0 -2
  298. package/dist/cli/loop-12twjcat.js +0 -2
  299. package/dist/cli/main-01pv9206.js +0 -4
  300. package/dist/cli/main-0jys2ccn.js +0 -3
  301. package/dist/cli/main-1ztz6fkj.js +0 -10
  302. package/dist/cli/main-23q7cmww.js +0 -9
  303. package/dist/cli/main-2rzbexn2.js +0 -3
  304. package/dist/cli/main-2wyax8k9.js +0 -9
  305. package/dist/cli/main-2yeveeve.js +0 -6
  306. package/dist/cli/main-2z3dek0b.js +0 -3
  307. package/dist/cli/main-2zgsknth.js +0 -3
  308. package/dist/cli/main-45ejth3a.js +0 -4
  309. package/dist/cli/main-45rn3trk.js +0 -22
  310. package/dist/cli/main-4p4e2w7x.js +0 -4
  311. package/dist/cli/main-4y0tnfpa.js +0 -16
  312. package/dist/cli/main-5py0rkmc.js +0 -4
  313. package/dist/cli/main-6dtqmbt6.js +0 -7
  314. package/dist/cli/main-6h9x282m.js +0 -4
  315. package/dist/cli/main-6vjeds42.js +0 -3
  316. package/dist/cli/main-78gq4bt9.js +0 -6
  317. package/dist/cli/main-7jd5vh3x.js +0 -4
  318. package/dist/cli/main-7kt6r53y.js +0 -4
  319. package/dist/cli/main-8c1tbazx.js +0 -58
  320. package/dist/cli/main-9a9rnh47.js +0 -19
  321. package/dist/cli/main-9ht36z12.js +0 -3
  322. package/dist/cli/main-a2yfvcy9.js +0 -7
  323. package/dist/cli/main-a3f51n0x.js +0 -5
  324. package/dist/cli/main-b8zq261k.js +0 -3
  325. package/dist/cli/main-bxtvnf6d.js +0 -13
  326. package/dist/cli/main-edxc3yzt.js +0 -4
  327. package/dist/cli/main-evgz4mp5.js +0 -21
  328. package/dist/cli/main-f33fc5je.js +0 -9
  329. package/dist/cli/main-fvnpq46y.js +0 -12
  330. package/dist/cli/main-gbbty4d4.js +0 -3
  331. package/dist/cli/main-gth53dnt.js +0 -25
  332. package/dist/cli/main-hqbz10aw.js +0 -9
  333. package/dist/cli/main-hrrvcfan.js +0 -38
  334. package/dist/cli/main-hzwtsb2m.js +0 -5
  335. package/dist/cli/main-j7ttv0sd.js +0 -34
  336. package/dist/cli/main-jak598k9.js +0 -5
  337. package/dist/cli/main-kba6zeyd.js +0 -6
  338. package/dist/cli/main-kwwsz6rq.js +0 -3
  339. package/dist/cli/main-m8vm17zq.js +0 -3
  340. package/dist/cli/main-mg4f96e1.js +0 -3
  341. package/dist/cli/main-mg9b20ac.js +0 -18
  342. package/dist/cli/main-mgb9ccnx.js +0 -3
  343. package/dist/cli/main-mjt2p7aj.js +0 -3
  344. package/dist/cli/main-n6qrdbmy.js +0 -3
  345. package/dist/cli/main-na7wse0x.js +0 -5
  346. package/dist/cli/main-nqveez48.js +0 -4
  347. package/dist/cli/main-ntqef02r.js +0 -10
  348. package/dist/cli/main-nvc3yjay.js +0 -136
  349. package/dist/cli/main-p0cfn6nr.js +0 -16
  350. package/dist/cli/main-qj2djy17.js +0 -19
  351. package/dist/cli/main-qsevpgsv.js +0 -3
  352. package/dist/cli/main-qvarybsp.js +0 -3
  353. package/dist/cli/main-rebtt91r.js +0 -5
  354. package/dist/cli/main-rpg7h8mb.js +0 -3
  355. package/dist/cli/main-rsy72qmw.js +0 -15
  356. package/dist/cli/main-rvetps99.js +0 -18
  357. package/dist/cli/main-s4bb0jav.js +0 -3
  358. package/dist/cli/main-s9v8k74e.js +0 -3
  359. package/dist/cli/main-tjvwmscs.js +0 -3
  360. package/dist/cli/main-tkgarpjj.js +0 -4
  361. package/dist/cli/main-v8y60bb2.js +0 -3
  362. package/dist/cli/main-vhrrq337.js +0 -3
  363. package/dist/cli/main-vp2dfb7s.js +0 -4
  364. package/dist/cli/main-vqbr22sz.js +0 -8
  365. package/dist/cli/main-vxnwe5xx.js +0 -18
  366. package/dist/cli/main-wgph00xf.js +0 -5
  367. package/dist/cli/main-wk2csfnj.js +0 -5
  368. package/dist/cli/main-wm997zjx.js +0 -3
  369. package/dist/cli/main-wpkyraxh.js +0 -3
  370. package/dist/cli/main-wqt32p5x.js +0 -4
  371. package/dist/cli/main-x9ct6y1a.js +0 -3
  372. package/dist/cli/main-xfekqh9m.js +0 -7
  373. package/dist/cli/main-xt9zc3n6.js +0 -7
  374. package/dist/cli/main-xx2z3zh5.js +0 -4
  375. package/dist/cli/main-y5c82rxr.js +0 -3
  376. package/dist/cli/main-yrjt2sqt.js +0 -14
  377. package/dist/cli/main-ys6zj3yr.js +0 -3
  378. package/dist/cli/main-ywbxshqc.js +0 -8
  379. package/dist/cli/main-z13755t8.js +0 -25
  380. package/dist/cli/main-zc7pyrbj.js +0 -4
  381. package/dist/cli/main.js +0 -279
  382. package/dist/cli/market-cmd-bm5xvn9f.js +0 -5
  383. package/dist/cli/mcp-login-bthtfpt7.js +0 -2
  384. package/dist/cli/mcp-market-cmd-mbeshfyd.js +0 -2
  385. package/dist/cli/notify-54v5z9dz.js +0 -2
  386. package/dist/cli/oauth-g5gme95c.js +0 -2
  387. package/dist/cli/output-satndjap.js +0 -16
  388. package/dist/cli/profiles-sfhpbq3m.js +0 -2
  389. package/dist/cli/provider-config-hv3xtdt4.js +0 -2
  390. package/dist/cli/provider-kwzq6g84.js +0 -2
  391. package/dist/cli/registry-fh0hdnyn.js +0 -2
  392. package/dist/cli/registry-y1y8e94r.js +0 -2
  393. package/dist/cli/repl-t4z03mqq.js +0 -11
  394. package/dist/cli/resume-fqt4chg8.js +0 -2
  395. package/dist/cli/run-flags-rysbag9t.js +0 -2
  396. package/dist/cli/runtime-j19fjbsa.js +0 -2
  397. package/dist/cli/sandbox-config-g4qxd7y5.js +0 -2
  398. package/dist/cli/server-r0b6bksk.js +0 -5
  399. package/dist/cli/session-arg-txmn5g4x.js +0 -2
  400. package/dist/cli/session-ed250d9j.js +0 -2
  401. package/dist/cli/sessions-cmd-adw7svfn.js +0 -7
  402. package/dist/cli/settings-y9rzcqx8.js +0 -2
  403. package/dist/cli/setup-jmbr11j0.js +0 -2
  404. package/dist/cli/sextant-smoke-tcth0vea.js +0 -5
  405. package/dist/cli/skills-cmd-zbdy99v6.js +0 -2
  406. package/dist/cli/smoke-1bg937kx.js +0 -8
  407. package/dist/cli/start-chat-p01cdks3.js +0 -12
  408. package/dist/cli/stream-4wmyaypz.js +0 -2
  409. package/dist/cli/task-eg4s093s.js +0 -2
  410. package/dist/cli/tasks-12v9rr9k.js +0 -2
  411. package/dist/cli/thinking-a5ngvqyh.js +0 -2
  412. package/dist/cli/todo-1wxpcecx.js +0 -2
  413. package/dist/cli/tools-2ftsya7w.js +0 -2
  414. package/dist/cli/tools-x1tj4fxm.js +0 -2
  415. package/dist/cli/trust-cmd-hccxehzb.js +0 -2
  416. package/dist/cli/update-check-ygt3vd7m.js +0 -2
  417. package/dist/cli/update-cmd-v23qhr8c.js +0 -2
  418. package/dist/cli/voice-g1gtck92.js +0 -2
  419. package/dist/cli/webfetch-0nnrjgb5.js +0 -2
  420. package/dist/cli/websearch-f0vr2p7d.js +0 -2
  421. package/dist/cli/workspace-9rq1w4ta.js +0 -2
  422. package/dist/lib/index.js +0 -62
  423. package/dist/lib/models-index.json +0 -1
  424. package/dist/lib/plugins.js +0 -6
  425. package/dist/lib/providers.js +0 -17
  426. package/dist/lib/public-api.js +0 -20
  427. package/dist/rovecode.exe +0 -4
  428. /package/{dist/cli → src/providers}/models-index.json +0 -0
@@ -0,0 +1,324 @@
1
+ /** ported from the user's sextant v0.4.0 prototype, src/term.js (Screen, sgr) — cell buffer + diff flush + SGR */
2
+ /* Pure over an injected Sink: no process access, no clock. The renderer (#44) owns stdout, resize
3
+ events and the frame interval; this module only turns a cell buffer into the minimal byte stream
4
+ that repaints what changed. Additions over the prototype: truecolor → xterm-256 quantization,
5
+ East-Asian width measurement (a width-2 glyph occupies two cells; a later write over either half
6
+ breaks the glyph into spaces so borders stay put), zero-width code points (combining marks, VS16,
7
+ ZWJ and the glyph it joins) ride on the cell before them so the terminal cursor never ends left of
8
+ the buffer, frame glyphs measure 1 cell even under ambiguousAsWide, and control characters never
9
+ reach a cell (toText()/flush cannot leak stray escape bytes). */
10
+
11
+ import { eastAsianWidth } from "get-east-asian-width";
12
+ import { hex } from "./theme.ts";
13
+ import { ATTR, type ScreenLike, type Seg, type Style } from "./types.ts";
14
+
15
+ export const ESC = "\x1b";
16
+ export const CSI = ESC + "[";
17
+ /** the prototype's floor (Screen.resize); layout() clamps to the same numbers */
18
+ export const MIN_COLS = 40;
19
+ export const MIN_ROWS = 12;
20
+
21
+ export interface Sink { write(s: string): void }
22
+ export interface Cursor { x: number; y: number }
23
+ export interface ScreenOptions {
24
+ /** emit 38;2/48;2 truecolor SGR (default true); false quantizes to the xterm 256 cube (38;5/48;5) */
25
+ truecolor?: boolean;
26
+ /** count East-Asian-ambiguous text (◆, ¡ …) as 2 cells; default false. Frame glyphs (box drawing,
27
+ * block elements, …) stay 1 cell either way — the option measures content, never the frame */
28
+ ambiguousAsWide?: boolean;
29
+ }
30
+ /** one buffer cell for tests/panels: width 2 = a wide glyph, 0 = the continuation half of one;
31
+ * ch holds the glyph plus any zero-width code points that joined it (e + U+0301, ZWJ sequences) */
32
+ export interface Cell { ch: string; fg: number; bg: number; a: number; width: number }
33
+
34
+ /* ------------------------------------------------------------ width */
35
+
36
+ /** zero-width code points join the glyph before them: combining marks (Mn/Me), format characters
37
+ * (Cf — ZWSP/ZWNJ/ZWJ U+200B–D, BOM, tags…) and variation selectors U+FE00–0F. Nothing below
38
+ * U+0300 qualifies (U+00AD soft hyphen stays 1 as in wcwidth), which keeps Latin text off the regex. */
39
+ const ZERO_WIDTH_RE = /^[\p{Mn}\p{Me}\p{Cf}\u200B-\u200D\uFE00-\uFE0F]$/u;
40
+ const isZeroWidth = (cp: number): boolean => cp >= 0x300 && ZERO_WIDTH_RE.test(String.fromCodePoint(cp));
41
+ /** Box Drawing + Block Elements (U+2500–259F) and the ellipsis: a frame is 1 cell whatever ambiguousAsWide says */
42
+ const isFrameGlyph = (cp: number): boolean => (cp >= 0x2500 && cp < 0x25a0) || cp === 0x2026;
43
+ const ZWJ = 0x200d;
44
+ const CONTROL_RE = /[\x00-\x1f\x7f-\x9f]/g;
45
+
46
+ /** display width of one code point: 0 (joins the previous glyph), else 1 or 2 via get-east-asian-width;
47
+ * no wide/ambiguous code point exists below U+1100/U+00A1 */
48
+ export function charWidth(cp: number, ambiguousAsWide = false): 0 | 1 | 2 {
49
+ if (cp < 0x80) return 1;
50
+ if (isZeroWidth(cp)) return 0;
51
+ if (ambiguousAsWide ? isFrameGlyph(cp) : cp < 0x1100) return 1;
52
+ return eastAsianWidth(cp, { ambiguousAsWide });
53
+ }
54
+
55
+ /** a string as cells, [text, width] per glyph: zero-width code points — and the code point a ZWJ
56
+ * joins — ride on the glyph before them; a leading one has nothing to join and is dropped */
57
+ export function* glyphs(s: string, ambiguousAsWide = false): Generator<[string, 1 | 2], void> {
58
+ let cur = "", cw: 1 | 2 = 1, join = false;
59
+ for (const c of s) {
60
+ const cp = c.codePointAt(0)!;
61
+ const w = charWidth(cp, ambiguousAsWide);
62
+ if (cur && (w === 0 || (join && cp >= 0x80))) { cur += c; join = cp === ZWJ; continue; }
63
+ if (w === 0) continue;
64
+ if (cur) yield [cur, cw];
65
+ cur = c; cw = w; join = false;
66
+ }
67
+ if (cur) yield [cur, cw];
68
+ }
69
+
70
+ /** display width of a string in cells (control characters count as the space they become) */
71
+ export function strWidth(s: string, ambiguousAsWide = false): number {
72
+ let w = 0;
73
+ for (const [, cw] of glyphs(s, ambiguousAsWide)) w += cw;
74
+ return w;
75
+ }
76
+
77
+ /* ------------------------------------------------------------ colors */
78
+
79
+ const CUBE = [0, 95, 135, 175, 215, 255] as const;
80
+ /** xterm's default 16 ANSI colors — so color256() covers every index; sgr() never emits 0-15 */
81
+ const ANSI16 = [0x000000, 0x800000, 0x008000, 0x808000, 0x000080, 0x800080, 0x008080, 0xc0c0c0, 0x808080, 0xff0000, 0x00ff00, 0xffff00, 0x0000ff, 0xff00ff, 0x00ffff, 0xffffff];
82
+ /** nearest cube level for one channel (the 0/95/135/175/215/255 ramp) */
83
+ const cubeLevel = (v: number): number => (v < 48 ? 0 : v < 115 ? 1 : Math.min(5, Math.floor((v - 35) / 40)));
84
+ function dist2(a: number, b: number): number {
85
+ const dr = ((a >> 16) & 255) - ((b >> 16) & 255), dg = ((a >> 8) & 255) - ((b >> 8) & 255), db = (a & 255) - (b & 255);
86
+ return dr * dr + dg * dg + db * db;
87
+ }
88
+
89
+ /** packed rgb of an xterm-256 palette index */
90
+ export function color256(i: number): number {
91
+ if (i < 16) return ANSI16[i] ?? 0;
92
+ if (i < 232) { const k = i - 16; return (CUBE[Math.floor(k / 36)]! << 16) | (CUBE[Math.floor(k / 6) % 6]! << 8) | CUBE[k % 6]!; }
93
+ const g = 8 + 10 * Math.min(23, i - 232);
94
+ return (g << 16) | (g << 8) | g;
95
+ }
96
+
97
+ /** nearest xterm-256 index (16..255) for a packed rgb: the 6×6×6 cube or the 24-step gray ramp, whichever is closer */
98
+ export function quantize256(c: number): number {
99
+ const r = (c >> 16) & 255, g = (c >> 8) & 255, b = c & 255;
100
+ const cube = 16 + 36 * cubeLevel(r) + 6 * cubeLevel(g) + cubeLevel(b);
101
+ const gray = 232 + Math.max(0, Math.min(23, Math.round(((r + g + b) / 3 - 8) / 10)));
102
+ return dist2(c, color256(gray)) < dist2(c, color256(cube)) ? gray : cube;
103
+ }
104
+
105
+ /** SGR for a cell style: reset + attribute bits + fg/bg (truecolor 38;2 or quantized 38;5); -1 = default color */
106
+ export function sgr(fg: number, bg: number, a: number, truecolor = true): string {
107
+ let s = CSI + "0";
108
+ if (a & ATTR.BOLD) s += ";1";
109
+ if (a & ATTR.DIM) s += ";2";
110
+ if (a & ATTR.ITALIC) s += ";3";
111
+ if (a & ATTR.UNDERLINE) s += ";4";
112
+ if (a & ATTR.INVERSE) s += ";7";
113
+ if (a & ATTR.STRIKE) s += ";9";
114
+ if (fg >= 0) s += truecolor ? `;38;2;${(fg >> 16) & 255};${(fg >> 8) & 255};${fg & 255}` : `;38;5;${quantize256(fg)}`;
115
+ if (bg >= 0) s += truecolor ? `;48;2;${(bg >> 16) & 255};${(bg >> 8) & 255};${bg & 255}` : `;48;5;${quantize256(bg)}`;
116
+ return s + "m";
117
+ }
118
+
119
+ /* ------------------------------------------------------------ screen */
120
+
121
+ interface Prev { ch: (string | null)[]; fg: Int32Array; bg: Int32Array; at: Uint8Array }
122
+
123
+ export class Screen implements ScreenLike {
124
+ w = 0;
125
+ h = 0;
126
+ private ch: string[] = [];
127
+ /** cell widths: 1, 2 (wide glyph) or 0 (continuation half of the wide glyph to its left) */
128
+ private wd = new Uint8Array(0);
129
+ private fg = new Int32Array(0);
130
+ private bg = new Int32Array(0);
131
+ private at = new Uint8Array(0);
132
+ private prev: Prev | null = null;
133
+ private readonly truecolor: boolean;
134
+ private readonly ambiguousAsWide: boolean;
135
+
136
+ constructor(private readonly sink: Sink, cols: number, rows: number, opts: ScreenOptions = {}) {
137
+ this.truecolor = opts.truecolor ?? true;
138
+ this.ambiguousAsWide = opts.ambiguousAsWide ?? false;
139
+ this.resize(cols, rows);
140
+ }
141
+
142
+ /** (re)allocate the buffer at ≥ 40×12; the next flush repaints everything */
143
+ resize(cols: number, rows: number): void {
144
+ this.w = Math.max(MIN_COLS, Number.isFinite(cols) ? Math.floor(cols) : 0);
145
+ this.h = Math.max(MIN_ROWS, Number.isFinite(rows) ? Math.floor(rows) : 0);
146
+ const n = this.w * this.h;
147
+ this.ch = new Array<string>(n).fill(" ");
148
+ this.wd = new Uint8Array(n).fill(1);
149
+ this.fg = new Int32Array(n).fill(-1);
150
+ this.bg = new Int32Array(n).fill(-1);
151
+ this.at = new Uint8Array(n);
152
+ this.prev = null;
153
+ }
154
+
155
+ /** start a frame: every cell becomes a plain space over the given background */
156
+ begin(bg?: number): void {
157
+ this.ch.fill(" "); this.wd.fill(1); this.fg.fill(-1); this.bg.fill(bg == null ? -1 : bg); this.at.fill(0);
158
+ }
159
+
160
+ cellAt(x: number, y: number): Cell | null {
161
+ if (x < 0 || y < 0 || x >= this.w || y >= this.h) return null;
162
+ const i = y * this.w + x;
163
+ return { ch: this.ch[i]!, fg: this.fg[i]!, bg: this.bg[i]!, a: this.at[i]!, width: this.wd[i]! };
164
+ }
165
+
166
+ /** a write over either half of a wide glyph turns its other half into a space */
167
+ private breakWide(i: number): void {
168
+ const w = this.wd[i];
169
+ if (w === 0 && i > 0) { this.ch[i - 1] = " "; this.wd[i - 1] = 1; }
170
+ else if (w === 2 && i + 1 < this.wd.length) { this.ch[i + 1] = " "; this.wd[i + 1] = 1; }
171
+ }
172
+
173
+ private set(i: number, c: string, w: 1 | 2, fg: number, bg: number, a: number): void {
174
+ this.breakWide(i);
175
+ if (w === 2) this.breakWide(i + 1);
176
+ this.ch[i] = c; this.wd[i] = w; this.fg[i] = fg; if (bg >= 0) this.bg[i] = bg; this.at[i] = a;
177
+ if (w === 2) { this.ch[i + 1] = ""; this.wd[i + 1] = 0; this.fg[i + 1] = fg; if (bg >= 0) this.bg[i + 1] = bg; this.at[i + 1] = a; }
178
+ }
179
+
180
+ /** the longest prefix of s that fits in w cells */
181
+ private head(s: string, w: number): string {
182
+ let out = "", used = 0;
183
+ for (const [g, cw] of glyphs(s, this.ambiguousAsWide)) {
184
+ if (used + cw > w) break;
185
+ out += g; used += cw;
186
+ }
187
+ return out;
188
+ }
189
+
190
+ /** write a string; returns the x after the last cell written. bg < 0 keeps the cell's background. */
191
+ put(x: number, y: number, str: string, st?: Style, maxW = Infinity): number {
192
+ if (y < 0 || y >= this.h || !str) return x;
193
+ const fg = st ? st.fg : -1, bg = st ? st.bg : -1, a = st ? st.a : 0;
194
+ let cx = x;
195
+ const end = Math.min(this.w, x + maxW);
196
+ for (const [g, cw] of glyphs(str.replace(CONTROL_RE, " "), this.ambiguousAsWide)) {
197
+ if (cx >= end) break;
198
+ if (cw === 2 && cx + 2 > end) break; // a wide glyph that does not fit is dropped, never half-drawn
199
+ if (cx >= 0) this.set(y * this.w + cx, g, cw, fg, bg, a);
200
+ cx += cw;
201
+ }
202
+ return cx;
203
+ }
204
+
205
+ /** write segments clipped to maxW; returns the x after the last cell */
206
+ text(x: number, y: number, segs: readonly Seg[], maxW = Infinity): number {
207
+ let cx = x;
208
+ const end = x + maxW;
209
+ for (const [s, st] of segs) {
210
+ if (cx >= end) break;
211
+ cx = this.put(cx, y, s, st, end - cx);
212
+ }
213
+ return cx;
214
+ }
215
+
216
+ /** clipped text with a trailing ellipsis when it does not fit (measured in cells) */
217
+ clip(x: number, y: number, str: string, st: Style | undefined, maxW: number): number {
218
+ if (maxW <= 0) return x;
219
+ if (strWidth(str, this.ambiguousAsWide) > maxW) str = maxW > 1 ? this.head(str, maxW - 1) + "…" : this.head(str, maxW);
220
+ return this.put(x, y, str, st, maxW);
221
+ }
222
+
223
+ fill(x: number, y: number, w: number, h: number, ch: string, st?: Style): void {
224
+ const fg = st ? st.fg : -1, bg = st ? st.bg : -1, a = st ? st.a : 0;
225
+ let cp = ch.replace(CONTROL_RE, " ").codePointAt(0) ?? 0x20;
226
+ if (isZeroWidth(cp)) cp = 0x20; // a lone zero-width glyph has nothing to join (and a 0-cell step would never advance)
227
+ const g = String.fromCodePoint(cp), cw = charWidth(cp, this.ambiguousAsWide) || 1;
228
+ for (let yy = y; yy < y + h; yy++) {
229
+ if (yy < 0 || yy >= this.h) continue;
230
+ for (let xx = x; xx < x + w; xx += cw) {
231
+ if (xx < 0 || xx + cw > this.w || xx + cw > x + w) continue;
232
+ this.set(yy * this.w + xx, g, cw, fg, bg, a);
233
+ }
234
+ }
235
+ }
236
+
237
+ /** recolor a rectangle's background only */
238
+ tint(x: number, y: number, w: number, h: number, bg: number): void {
239
+ for (let yy = y; yy < y + h; yy++) {
240
+ if (yy < 0 || yy >= this.h) continue;
241
+ for (let xx = x; xx < x + w; xx++) {
242
+ if (xx < 0 || xx >= this.w) continue;
243
+ this.bg[yy * this.w + xx] = bg;
244
+ }
245
+ }
246
+ }
247
+
248
+ hline(x: number, y: number, w: number, st?: Style, ch = "─"): void { for (let i = 0; i < w; i++) this.put(x + i, y, ch, st); }
249
+ vline(x: number, y: number, h: number, st?: Style, ch = "│"): void { for (let i = 0; i < h; i++) this.put(x, y + i, ch, st); }
250
+
251
+ /** rounded box; bgFill paints the interior first when given */
252
+ box(x: number, y: number, w: number, h: number, st?: Style, bgFill?: number): void {
253
+ if (bgFill != null) this.fill(x, y, w, h, " ", { fg: -1, bg: bgFill, a: 0 });
254
+ this.put(x, y, "╭", st); this.put(x + w - 1, y, "╮", st);
255
+ this.put(x, y + h - 1, "╰", st); this.put(x + w - 1, y + h - 1, "╯", st);
256
+ this.hline(x + 1, y, w - 2, st); this.hline(x + 1, y + h - 1, w - 2, st);
257
+ this.vline(x, y + 1, h - 2, st); this.vline(x + w - 1, y + 1, h - 2, st);
258
+ }
259
+
260
+ /** emit the difference to the previous flush: full paint (ESC[2J) on the first frame or after
261
+ * resize, otherwise only changed cells — a cursor move only where the run is discontiguous, an
262
+ * SGR only when the style changes, and never the bottom-right cell (terminals may scroll on it). */
263
+ flush(cursor?: Cursor | null): void {
264
+ const n = this.w * this.h;
265
+ let full = false;
266
+ if (!this.prev || this.prev.ch.length !== n) {
267
+ this.prev = { ch: new Array<string | null>(n).fill(null), fg: new Int32Array(n).fill(-2), bg: new Int32Array(n).fill(-2), at: new Uint8Array(n).fill(255) };
268
+ full = true;
269
+ }
270
+ const p = this.prev;
271
+ let out = full ? CSI + "2J" : "";
272
+ let cfg: number | null = null, cbg: number | null = null, cat: number | null = null, lx = -1, ly = -1;
273
+ const last = n - 1;
274
+ for (let y = 0; y < this.h; y++) {
275
+ for (let x = 0; x < this.w; x++) {
276
+ const i = y * this.w + x;
277
+ const wd = this.wd[i]!;
278
+ if (wd === 0) continue; // continuation half: painted together with its glyph
279
+ if (i + wd - 1 >= last) continue; // scroll guard: the last cell (or a glyph spilling into it) is never written
280
+ if (!full && this.ch[i] === p.ch[i] && this.fg[i] === p.fg[i] && this.bg[i] === p.bg[i] && this.at[i] === p.at[i]) continue;
281
+ if (!(ly === y && lx === x)) out += CSI + (y + 1) + ";" + (x + 1) + "H";
282
+ const fg = this.fg[i]!, bg = this.bg[i]!, at = this.at[i]!;
283
+ if (fg !== cfg || bg !== cbg || at !== cat) { out += sgr(fg, bg, at, this.truecolor); cfg = fg; cbg = bg; cat = at; }
284
+ out += this.ch[i]!;
285
+ lx = x + wd; ly = y;
286
+ p.ch[i] = this.ch[i]!; p.fg[i] = fg; p.bg[i] = bg; p.at[i] = at;
287
+ if (wd === 2) { p.ch[i + 1] = ""; p.fg[i + 1] = fg; p.bg[i + 1] = bg; p.at[i + 1] = at; }
288
+ }
289
+ }
290
+ out += cursor ? CSI + (cursor.y + 1) + ";" + (cursor.x + 1) + "H" + CSI + "?25h" : CSI + "?25l";
291
+ this.sink.write(out);
292
+ }
293
+
294
+ /** plain-text dump: one line per row, trailing whitespace removed, no escape bytes ever */
295
+ toText(): string {
296
+ const lines: string[] = [];
297
+ for (let y = 0; y < this.h; y++) lines.push(this.ch.slice(y * this.w, (y + 1) * this.w).join("").replace(/\s+$/, ""));
298
+ return lines.join("\n");
299
+ }
300
+
301
+ /** html dump with colors (debug/screenshots) */
302
+ toHTML(font = "Cascadia Mono"): string {
303
+ let html = "";
304
+ for (let y = 0; y < this.h; y++) {
305
+ let row = "", run = "";
306
+ let cur: Style | null = null;
307
+ const flushRun = (): void => {
308
+ if (!run || !cur) return;
309
+ const s = cur;
310
+ row += `<span style="color:${s.fg >= 0 ? hex(s.fg) : "inherit"};background:${s.bg >= 0 ? hex(s.bg) : "transparent"};${s.a & ATTR.BOLD ? "font-weight:600;" : ""}${s.a & ATTR.ITALIC ? "font-style:italic;" : ""}${s.a & ATTR.STRIKE ? "text-decoration:line-through;" : ""}">${run.replace(/&/g, "&amp;").replace(/</g, "&lt;")}</span>`;
311
+ run = "";
312
+ };
313
+ for (let x = 0; x < this.w; x++) {
314
+ const i = y * this.w + x;
315
+ const s: Style = { fg: this.fg[i]!, bg: this.bg[i]!, a: this.at[i]! };
316
+ if (!cur || cur.fg !== s.fg || cur.bg !== s.bg || cur.a !== s.a) { flushRun(); cur = s; }
317
+ run += this.ch[i]!;
318
+ }
319
+ flushRun();
320
+ html += row + "\n";
321
+ }
322
+ return `<!doctype html><meta charset="utf-8"><body style="margin:0;background:#000"><pre style="margin:0;font:14px/1.25 '${font}',Consolas,monospace;display:inline-block">${html}</pre></body>`;
323
+ }
324
+ }
@@ -0,0 +1,66 @@
1
+ /** Scrollbar thumbs as grab zones (types.ts HitZone.onDrag). Each painter draws its bar from a
2
+ * handful of numbers — track x/y/h, content rows, viewport rows, offset (draw-messages.ts:338,
3
+ * draw-frame.ts:111, draw-code.ts:385). The same numbers rebuilt here give the thumb's rect; a drag
4
+ * slides the thumb by `dy` rows from where it was painted and maps that back onto an offset, so the
5
+ * thumb stays under the finger wherever it was grabbed. */
6
+ import { codeScrollTop, RAIL_W, rowCount } from "./draw-code.ts";
7
+ import { areas, messagesScroll } from "./draw-messages.ts";
8
+ import { inner } from "./draw-util.ts";
9
+ import { scrollbarGeom, thumbRect, type ScrollbarGeom } from "./scrollbar.ts";
10
+ import type { HitZone, Layout, Page, SextantState, Theme, TreeRow } from "./types.ts";
11
+
12
+ /** the content offset after sliding the thumb `dy` rows from where it was painted (clamped) */
13
+ export function slideOffset(g: ScrollbarGeom, dy: number, total: number, viewport: number): number {
14
+ const range = Math.max(0, total - viewport), track = g.trackH - g.thumbH;
15
+ if (track <= 0 || range === 0) return 0;
16
+ const top = Math.max(g.trackY, Math.min(g.trackY + track, g.thumbY + dy));
17
+ return Math.round((top - g.trackY) / track * range);
18
+ }
19
+
20
+ function thumbZone(g: ScrollbarGeom, total: number, viewport: number, set: (offset: number) => void): HitZone | null {
21
+ if (!g.visible) return null;
22
+ return { rect: thumbRect(g), onClick: () => {}, onDrag: (_y, dy) => set(slideOffset(g, dy, total, viewport)) };
23
+ }
24
+
25
+ /** Wheeling the messages up unsticks the view from the tail; once the person scrolls back down to the end
26
+ * (wheel, drag, keys) the view follows the tail again — otherwise a conversation that has been scrolled
27
+ * once never follows new text until pgdn/end. Called by the frame loop after the paint. */
28
+ export function followTailIfAtEnd(L: Layout, s: SextantState, theme: Theme, now: number): void {
29
+ if (s.stick) return;
30
+ const { max } = messagesScroll(L.messages, s, theme, now);
31
+ if (s.msgScroll >= max) { s.stick = true; s.msgScroll = max; }
32
+ }
33
+
34
+ /** one zone per visible scrollbar thumb: messages, the files tree (in its panel or paged into the
35
+ * main slot), and the code panel in a scrolling mode (file / diff / search; run and agents tail) */
36
+ export function scrollThumbHits(L: Layout, main: Page, s: SextantState, theme: Theme, now: number, rows: TreeRow[]): HitZone[] {
37
+ const out: HitZone[] = [];
38
+ {
39
+ const B = inner(L.messages);
40
+ const { msgH } = areas(B, s);
41
+ const { offset, max } = messagesScroll(L.messages, s, theme, now);
42
+ const total = max + msgH;
43
+ const z = thumbZone(scrollbarGeom(B.x + B.w - 1, B.y, msgH, total, msgH, offset), total, msgH,
44
+ (o) => { s.msgScroll = o; s.stick = o >= max; }); // dragged to the bottom = follow the tail again
45
+ if (z) out.push(z);
46
+ }
47
+ // the files painter re-clamps scroll around the cursor, so the cursor is pulled into the new window
48
+ const filesRect = L.files ?? (main === "files" ? L.code : null);
49
+ if (filesRect) {
50
+ const B = inner(filesRect);
51
+ const z = thumbZone(scrollbarGeom(B.x + B.w - 1, B.y, B.h, rows.length, B.h, s.files.scroll), rows.length, B.h,
52
+ (o) => { s.files.scroll = o; s.files.cursor = Math.max(o, Math.min(s.files.cursor, o + B.h - 1)); });
53
+ if (z) out.push(z);
54
+ }
55
+ const c = s.code;
56
+ if (main === "code" && (c.mode === "code" || c.mode === "diff" || c.mode === "search")) {
57
+ const B = inner(L.code);
58
+ if (B.w >= RAIL_W + 2) {
59
+ const total = rowCount(s, B.w - RAIL_W - 2), top = codeScrollTop(L.code, s);
60
+ const z = thumbZone(scrollbarGeom(B.x + B.w - RAIL_W - 1, B.y, B.h, total, B.h, top), total, B.h,
61
+ (o) => { c.scroll = o; });
62
+ if (z) out.push(z);
63
+ }
64
+ }
65
+ return out;
66
+ }
@@ -0,0 +1,82 @@
1
+ /** Sextant surface — shared one-column scrollbar painter.
2
+ * Pure: no I/O, no timers, no state mutation. All geometry is exported so a
3
+ * caller can hang a mouse hit-zone on the thumb rectangle. */
4
+
5
+ import type { Rect, ScreenLike, Theme } from "./types.ts";
6
+ import { st } from "./theme.ts";
7
+
8
+ /** Geometry for a vertical scrollbar track.
9
+ * When `visible` is false, no other fields are meaningful. */
10
+ export interface ScrollbarGeom {
11
+ /** column the bar occupies */
12
+ x: number;
13
+ /** first row of the track */
14
+ trackY: number;
15
+ /** number of rows in the track */
16
+ trackH: number;
17
+ /** first row of the thumb (absolute) */
18
+ thumbY: number;
19
+ /** number of rows the thumb spans (≥ 1 when visible) */
20
+ thumbH: number;
21
+ visible: boolean;
22
+ }
23
+
24
+ /** Compute scrollbar geometry without drawing.
25
+ * @param x - column for the bar
26
+ * @param trackY - first row of the track
27
+ * @param trackH - track height (rows)
28
+ * @param total - total content rows
29
+ * @param viewport - visible rows (same unit as total)
30
+ * @param offset - first visible row (0-based)
31
+ */
32
+ export function scrollbarGeom(
33
+ x: number,
34
+ trackY: number,
35
+ trackH: number,
36
+ total: number,
37
+ viewport: number,
38
+ offset: number,
39
+ ): ScrollbarGeom {
40
+ const visible = total > viewport && trackH >= 2;
41
+ if (!visible) return { x, trackY, trackH, thumbY: trackY, thumbH: 0, visible: false };
42
+ const thumbH = Math.max(1, Math.round(trackH * viewport / total));
43
+ const scrollRange = Math.max(1, total - viewport);
44
+ const trackRange = Math.max(0, trackH - thumbH);
45
+ const rawThumbY = trackY + Math.round(trackRange * Math.max(0, offset) / scrollRange);
46
+ const thumbY = Math.min(rawThumbY, trackY + trackH - thumbH);
47
+ return { x, trackY, trackH, thumbY, thumbH, visible: true };
48
+ }
49
+
50
+ /** Paint the scrollbar described by `geom`.
51
+ * Track: thin `│` in theme.rule; thumb: `▌` in theme.dim. */
52
+ export function drawScrollbar(scr: ScreenLike, geom: ScrollbarGeom, theme: Theme): void {
53
+ if (!geom.visible) return;
54
+ const trackSt = st(theme.rule);
55
+ const thumbSt = st(theme.dim);
56
+ for (let i = 0; i < geom.trackH; i++) {
57
+ const y = geom.trackY + i;
58
+ const inThumb = y >= geom.thumbY && y < geom.thumbY + geom.thumbH;
59
+ scr.put(geom.x, y, inThumb ? "▌" : "│", inThumb ? thumbSt : trackSt);
60
+ }
61
+ }
62
+
63
+ /** Convenience: compute + draw in one call; returns the geometry (for hit-zone export). */
64
+ export function scrollbar(
65
+ scr: ScreenLike,
66
+ theme: Theme,
67
+ x: number,
68
+ trackY: number,
69
+ trackH: number,
70
+ total: number,
71
+ viewport: number,
72
+ offset: number,
73
+ ): ScrollbarGeom {
74
+ const geom = scrollbarGeom(x, trackY, trackH, total, viewport, offset);
75
+ drawScrollbar(scr, geom, theme);
76
+ return geom;
77
+ }
78
+
79
+ /** A Rect covering the thumb — useful for registering a mouse hit-zone. */
80
+ export function thumbRect(geom: ScrollbarGeom): Rect {
81
+ return { x: geom.x, y: geom.thumbY, w: 1, h: geom.thumbH };
82
+ }
@@ -0,0 +1,123 @@
1
+ /** Chat text selection: drag over the messages panel, release, and the dragged text is on the
2
+ * clipboard. The chat must be copyable the way any terminal is — no mode, no permission, no
3
+ * /mouse off ritual first (Berkay, 2026-09-20). The app owns the mouse (clicks, wheel and the
4
+ * scrollbars need mouse reporting, and reporting is exactly what kills the terminal's native
5
+ * selection), so the gesture lives here instead:
6
+ *
7
+ * - a left press inside the messages panel is HELD, not forwarded;
8
+ * - released without moving, it is replayed verbatim, so every click keeps working — it just
9
+ * fires on release, the way a terminal fires a link click;
10
+ * - dragged, it becomes a screen-space selection over the panel, painted as a tint rect;
11
+ * - on release the selected cells are read back from the last painted frame and written to the
12
+ * clipboard as OSC 52 — the escape every modern terminal (Windows Terminal included) reads as
13
+ * "put this base64 on the clipboard". A toast says what happened; the transcript is untouched.
14
+ *
15
+ * Deliberately screen-space: what you see is what you copy (clipped to the panel's inner rect,
16
+ * trailing blanks trimmed, blank head/tail rows dropped). Modal cards and open overlays keep
17
+ * their own clicks (the gesture stands down), and a scrollbar-thumb press stays keys.ts's grab. */
18
+
19
+ import { mouseKind } from "./input.ts";
20
+ import { notify } from "./model.ts";
21
+ import type { HitZone, MouseEvent, Rect, SextantState } from "./types.ts";
22
+
23
+ export interface SelectionDeps {
24
+ state: SextantState;
25
+ /** the messages panel rect of the current layout */
26
+ messagesRect(): Rect;
27
+ /** the frame's hit zones (a press on a grabbable one — a scrollbar thumb — is not ours) */
28
+ hits(): readonly HitZone[];
29
+ /** the last painted frame as text, or null before the first paint */
30
+ screenText(): string | null;
31
+ /** raw terminal write (the OSC 52 sequence) */
32
+ write(seq: string): void;
33
+ markDirty(): void;
34
+ /** replay a held press through the normal input pipeline (a click that never became a drag) */
35
+ forward(ev: MouseEvent, now: number): void;
36
+ }
37
+
38
+ interface Sel { x0: number; y0: number; x1: number; y1: number }
39
+
40
+ const inside = (x: number, y: number, r: Rect): boolean => x >= r.x && x < r.x + r.w && y >= r.y && y < r.y + r.h;
41
+
42
+ /** normalized (x0≤x1, y0≤y1), clamped to the panel's inner rect (inside the border) */
43
+ function clampSel(a: { x: number; y: number }, b: { x: number; y: number }, r: Rect): Sel {
44
+ const ix0 = r.x + 1, ix1 = r.x + r.w - 2, iy0 = r.y + 1, iy1 = r.y + r.h - 2;
45
+ const cx = (x: number): number => Math.min(ix1, Math.max(ix0, x));
46
+ const cy = (y: number): number => Math.min(iy1, Math.max(iy0, y));
47
+ const x0 = cx(Math.min(a.x, b.x)), x1 = cx(Math.max(a.x, b.x));
48
+ const y0 = cy(Math.min(a.y, b.y)), y1 = cy(Math.max(a.y, b.y));
49
+ return { x0, y0, x1, y1 };
50
+ }
51
+
52
+ export class TextSelection {
53
+ private sel: Sel | null = null;
54
+ private pending: MouseEvent | null = null;
55
+ /** the press point a live drag is anchored to (screen coords); sel is re-normalized from it on every move */
56
+ private anchor: { x: number; y: number } | null = null;
57
+
58
+ constructor(private readonly d: SelectionDeps) {}
59
+
60
+ /** a selection is on screen (the frame loop cancels it on any key) */
61
+ get active(): boolean { return this.sel !== null; }
62
+
63
+ cancel(): void { this.sel = null; this.pending = null; this.anchor = null; }
64
+
65
+ /** true = the gesture owns this mouse event; the caller must NOT forward it to keys.ts */
66
+ onMouseEvent(ev: MouseEvent, now: number): boolean {
67
+ const kind = mouseKind(ev);
68
+ if (kind === "wheel-up" || kind === "wheel-down") { this.pending = null; return false; }
69
+ if (this.pending === null && this.sel === null) {
70
+ const r = this.d.messagesRect();
71
+ if (kind !== "click" || !inside(ev.x, ev.y, r)) return false;
72
+ const s = this.d.state;
73
+ // modals and overlays keep their own clicks; a thumb grab stays the scrollbar's
74
+ if (s.card !== null || s.palette !== null || s.market !== null || s.context !== null || s.help) return false;
75
+ if (this.d.hits().some((z) => z.onDrag !== undefined && inside(ev.x, ev.y, z.rect))) return false;
76
+ this.pending = ev;
77
+ return true;
78
+ }
79
+ if (kind === "drag") {
80
+ const r = this.d.messagesRect();
81
+ if (this.pending !== null) { this.anchor = { x: this.pending.x, y: this.pending.y }; this.pending = null; }
82
+ if (this.anchor === null) return false; // a drag belonging to a grab keys.ts owns (a scrollbar thumb)
83
+ this.sel = clampSel(this.anchor, ev, r);
84
+ this.d.markDirty();
85
+ return true;
86
+ }
87
+ if (kind === "release") {
88
+ if (this.pending !== null) { const p = this.pending; this.pending = null; this.d.forward(p, now); return true; }
89
+ if (this.sel !== null) { this.copy(now); this.sel = null; this.anchor = null; this.d.markDirty(); return true; }
90
+ }
91
+ // any other press (right button, modifiers) mid-gesture: abandon it, own nothing
92
+ this.pending = null;
93
+ return false;
94
+ }
95
+
96
+ /** the tint rect for this frame (clipped to the panel interior), null = nothing selected */
97
+ highlight(): { x: number; y: number; w: number; h: number } | null {
98
+ const sel = this.sel;
99
+ if (sel === null) return null;
100
+ return { x: sel.x0, y: sel.y0, w: sel.x1 - sel.x0 + 1, h: sel.y1 - sel.y0 + 1 };
101
+ }
102
+
103
+ /** the selected cells of the last painted frame, one line per row, blanks trimmed */
104
+ selectedText(): string {
105
+ const sel = this.sel, text = this.d.screenText();
106
+ if (sel === null || text === null) return "";
107
+ const lines = text.split("\n");
108
+ const out: string[] = [];
109
+ for (let y = sel.y0; y <= sel.y1; y++) {
110
+ out.push([...(lines[y] ?? "")].slice(sel.x0, sel.x1 + 1).join("").replace(/\s+$/, ""));
111
+ }
112
+ while (out.length > 0 && out[0]!.trim() === "") out.shift();
113
+ while (out.length > 0 && out[out.length - 1]!.trim() === "") out.pop();
114
+ return out.join("\n");
115
+ }
116
+
117
+ private copy(now: number): void {
118
+ const text = this.selectedText();
119
+ if (text === "") return;
120
+ this.d.write(`\x1b]52;c;${Buffer.from(text, "utf8").toString("base64")}\x07`);
121
+ notify(this.d.state, `copied ${[...text].length} characters to the clipboard`, now);
122
+ }
123
+ }