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,159 @@
1
+ /** Sextant plan + usage painters (port #41). Ported from the user's sextant v0.4.0 app.js:675-744
2
+ * (drawPlan steps/crew/next, drawUsage tokens/context bar/cost); the scenario phases are gone — the
3
+ * steps ARE the session's todos (tools/todo.ts loadTodos), the crew rows are TaskManager TaskInfo,
4
+ * usage comes from the run's real token/cost accounting. Pure: `now` only drives the crew spinner. */
5
+
6
+ import { ATTR } from "./types.ts";
7
+ import type { Rect, ScreenLike, Seg, SextantState, Theme } from "./types.ts";
8
+ import type { TaskInfo } from "../core/tasks.ts";
9
+ import { EMPTY } from "../core/voice.ts";
10
+ import type { TodoItem } from "../tools/todo.ts";
11
+ import { fmtK, planCounts } from "./model.ts";
12
+ import { crewSummary, laneGlyph, laneStatus, laneTone } from "./draw-agents.ts";
13
+ import { panel } from "./layout.ts";
14
+ import { st } from "./theme.ts";
15
+ import { driftRow } from "./draw-context.ts";
16
+
17
+ const CREW_MAX = 5;
18
+ export const STEP_GLYPH: Record<TodoItem["status"], string> = { completed: "◆", in_progress: "◈", pending: "◇" };
19
+
20
+ const oneLine = (text: string): string => text.replace(/\s+/g, " ").trim();
21
+ /** greedy word wrap (app.js wrap) — long words are split at the width */
22
+ export function wrapText(text: string, width: number): string[] {
23
+ const out: string[] = [];
24
+ if (width <= 0) return out;
25
+ for (const para of oneLine(text).split("\n")) {
26
+ let line = "";
27
+ for (const w of para.split(" ")) {
28
+ if (!line) { line = w; continue; }
29
+ if ([...line].length + 1 + [...w].length <= width) line += " " + w; else { out.push(line); line = w; }
30
+ }
31
+ while ([...line].length > width) { const cps = [...line]; out.push(cps.slice(0, width).join("")); line = cps.slice(width).join(""); }
32
+ out.push(line);
33
+ }
34
+ return out;
35
+ }
36
+
37
+ /** `next` = the in_progress item, then the first pending one */
38
+ export function nextSteps(todos: readonly TodoItem[]): TodoItem[] {
39
+ const out: TodoItem[] = [];
40
+ const cur = todos.find((t) => t.status === "in_progress"); if (cur) out.push(cur);
41
+ const pend = todos.find((t) => t.status === "pending"); if (pend) out.push(pend);
42
+ return out;
43
+ }
44
+ /** non-terminal tasks = "working" (queued + running) — crewSummary(crew).working agrees */
45
+ export const crewWorking = (crew: readonly TaskInfo[]): number => crewSummary(crew).working;
46
+ /** the crew board's glyph + tone (#46 draw-agents): ◇ queued · spinner running · ◆ done · × failed · ▪ cancelled */
47
+ export function crewGlyph(t: TaskInfo, theme: Theme, now: number): [string, number] {
48
+ return [laneGlyph(t.status, now), laneTone(t.status, theme)];
49
+ }
50
+ /** the crew board's status word (#46 laneStatus): `working mm:ss` while running, else the status */
51
+ export const crewStatus = (t: TaskInfo, now = 0): string => laneStatus(t, now);
52
+
53
+ /** Plan panel: `plan done/total` title, todo steps (◆ done · ◈ in_progress · ◇ pending, priority hint
54
+ * at the right), then — anchored to the bottom — `crew k working` (when any) and `next`. */
55
+ export function drawPlan(scr: ScreenLike, R: Rect, s: SextantState, theme: Theme, now = 0): void {
56
+ const todos = s.plan.todos, c = planCounts(s);
57
+ const B = panel(scr, R, "plan", false, todos.length ? [[`${c.completed}/${c.total}`, st(theme.muted)]] : [], theme);
58
+ const yEnd = B.y + B.h;
59
+ let y = B.y;
60
+ if (s.plan.note) for (const line of wrapText(s.plan.note, B.w).slice(0, 2)) if (y < yEnd) scr.put(B.x, y++, line, st(theme.warn), B.w);
61
+ const next = nextSteps(todos), crew = s.crew.slice(-CREW_MAX);
62
+ let nextRows = next.length ? next.length + 1 : 0, crewRows = crew.length ? crew.length + 1 : 0;
63
+ let nextY = yEnd - nextRows;
64
+ let crewY = (nextRows ? nextY - 1 : yEnd) - crewRows;
65
+ const minY = y + 2; // keep the steps header + one row above the anchored blocks
66
+ if (crewRows && crewY < minY) { crewRows = 0; crewY = yEnd; }
67
+ if (nextRows && nextY < minY) { nextRows = 0; nextY = yEnd; }
68
+ const stepsEnd = crewRows ? crewY - 1 : nextRows ? nextY - 1 : yEnd;
69
+ scr.text(B.x, y++, [["steps", st(theme.muted)], [todos.length ? ` ${c.completed}/${c.total}` : "", st(theme.dim)]], B.w);
70
+ if (!todos.length && y < stepsEnd) { // empty plan: the hint's second line only when there is room
71
+ scr.put(B.x, y++, EMPTY.plan[0], st(theme.dim), B.w);
72
+ if (y < stepsEnd) scr.put(B.x, y++, EMPTY.plan[1], st(theme.dim), B.w);
73
+ }
74
+ // a long plan windows around the step in progress instead of always showing its head: the person
75
+ // wants to see what is being done now, not steps 1-8 of 30. One row above says how many are earlier.
76
+ const room = Math.max(0, stepsEnd - y), curIdx = todos.findIndex((t) => t.status === "in_progress");
77
+ let start = 0;
78
+ if (todos.length > room && curIdx >= room - 1) { // the current step would be hidden (or be the marker row)
79
+ const visible = Math.max(1, room - 2); // minus the "earlier" row and the "+N more" row
80
+ start = Math.max(0, Math.min(curIdx - Math.floor(visible / 2), todos.length - visible));
81
+ }
82
+ if (start > 0 && y < stepsEnd) scr.put(B.x, y++, ` …${start} earlier`, st(theme.dim), B.w);
83
+ let shown = start;
84
+ for (const t of todos.slice(start)) {
85
+ if (y >= stepsEnd) break;
86
+ const cur = t.status === "in_progress", done = t.status === "completed";
87
+ const pr = t.priority === "high" ? "high" : t.priority === "low" ? "low" : "";
88
+ scr.put(B.x, y, STEP_GLYPH[t.status], st(done ? theme.okDim : cur ? theme.accent : theme.dim));
89
+ scr.clip(B.x + 3, y, oneLine(t.content), st(done ? theme.fg2 : cur ? theme.fg : theme.muted, -1, cur ? ATTR.BOLD : 0), B.w - 3 - (pr ? pr.length + 1 : 0));
90
+ if (pr) scr.put(B.x + B.w - pr.length, y, pr, st(t.priority === "high" ? theme.warn : theme.dim));
91
+ y++; shown++;
92
+ }
93
+ if (shown < todos.length && shown > 0) { y--; scr.fill(B.x, y, B.w, 1, " "); scr.put(B.x, y, ` +${todos.length - shown + 1} more`, st(theme.dim), B.w); }
94
+ if (crewRows) {
95
+ let cy = crewY;
96
+ scr.text(B.x, cy++, [["crew", st(theme.muted)], [` ${crewSummary(s.crew).text}`, st(theme.dim)]], B.w);
97
+ for (const t of crew) {
98
+ const [g, gc] = crewGlyph(t, theme, now), status = crewStatus(t, now);
99
+ scr.text(B.x, cy, [[g + " ", st(gc)], [t.label, st(t.status === "done" ? theme.fg2 : t.status === "failed" ? theme.err : theme.fg)]], B.w - status.length - 1);
100
+ scr.put(B.x + B.w - status.length, cy, status, st(t.status === "failed" ? theme.err : theme.dim));
101
+ cy++;
102
+ }
103
+ }
104
+ if (nextRows) {
105
+ let ny = nextY;
106
+ scr.put(B.x, ny++, "next", st(theme.muted));
107
+ for (const t of next) {
108
+ const cur = t.status === "in_progress";
109
+ scr.text(B.x, ny++, [[STEP_GLYPH[t.status] + " ", st(cur ? theme.accent : theme.dim)], [oneLine(t.content), st(cur ? theme.fg : theme.muted)]], B.w);
110
+ }
111
+ }
112
+ }
113
+
114
+ /** filled cells of a `width`-wide context bar at `pct` (0..100) */
115
+ export const barFilled = (pct: number, width: number): number => Math.round((Math.max(0, Math.min(100, pct)) / 100) * width);
116
+ /** the first candidate that fits `width` cells, else the last one clipped with an ellipsis */
117
+ export function fitText(candidates: readonly string[], width: number): string {
118
+ for (const c of candidates) if ([...c].length <= width) return c;
119
+ const last = candidates[candidates.length - 1];
120
+ if (last === undefined || width <= 0) return "";
121
+ return width === 1 ? "…" : [...last].slice(0, width - 1).join("") + "…";
122
+ }
123
+
124
+ /** Usage panel (3 inner rows): `tokens 4.2k` (+ in/out split when it fits), `context ━━──── 12%`
125
+ * (or `context ?` when the window is unknown), `cost $0.030` (or `cost —` when unpriced) +
126
+ * provider/model (model alone, then clipped, when narrow). */
127
+ export function drawUsage(scr: ScreenLike, R: Rect, s: SextantState, theme: Theme): void {
128
+ const B = panel(scr, R, "usage", false, [], theme);
129
+ const u = s.usage;
130
+ const label = (t: string): Seg => [t.padEnd(10), st(theme.muted)];
131
+ const total = fmtK(u.tokensIn + u.tokensOut);
132
+ const split = fitText([` ${fmtK(u.tokensIn)} in · ${fmtK(u.tokensOut)} out`, ` ${fmtK(u.tokensIn)}/${fmtK(u.tokensOut)}`, ""], B.w - 10 - total.length);
133
+ if (u.tokensIn + u.tokensOut === 0) scr.text(B.x, B.y, [label("tokens"), [EMPTY.usage, st(theme.dim)]], B.w); // nothing spent yet
134
+ else scr.text(B.x, B.y, [label("tokens"), [total, st(theme.fg)], [split, st(theme.dim)]], B.w);
135
+ scr.text(B.x, B.y + 1, [label("context")]);
136
+ if (u.contextPct === null) scr.put(B.x + 10, B.y + 1, "?", st(theme.dim));
137
+ else {
138
+ const barW = Math.max(6, B.w - 10 - 7), filled = barFilled(u.contextPct, barW);
139
+ for (let i = 0; i < barW; i++) scr.put(B.x + 10 + i, B.y + 1, i < filled ? "━" : "─", st(i < filled ? theme.accent : theme.rule2));
140
+ scr.put(B.x + 10 + barW + 2, B.y + 1, `${Math.round(u.contextPct)}%`.padStart(4), st(u.contextPct > 80 ? theme.warn : theme.fg));
141
+ }
142
+ const cost = u.costUsd === null ? "—" : `$${u.costUsd.toFixed(3)}`;
143
+ const model = u.model ? fitText([u.provider ? ` ${u.provider}/${u.model}` : ` ${u.model}`, ` ${u.model}`], B.w - 10 - cost.length) : "";
144
+ scr.text(B.x, B.y + 2, [label("cost"), [cost, st(theme.fg)], [model, st(theme.dim)]], B.w);
145
+
146
+ // How far the bar above is from what the provider actually counted. It matters here rather than only
147
+ // inside /context because compaction fires on OUR estimate: a model whose real prompt is a fifth bigger
148
+ // than the bar says gets compacted late, and the bar is the thing the human is looking at when they
149
+ // decide whether one more turn fits.
150
+ //
151
+ // Drawn only once /context has been opened, and silent otherwise. The number comes from comparing our
152
+ // count with a turn that reported usage, which the cockpit does not do on its own — and inventing a
153
+ // placeholder ("drift —") would take a row to say nothing. `driftRow` returns undefined when there is
154
+ // nothing honest to say, so this is one condition rather than a special case.
155
+ const d = s.context ? driftRow(s.context.drift, s.context.tolerance) : undefined;
156
+ if (d && B.h > 3) {
157
+ scr.text(B.x, B.y + 3, [label("drift"), [fitText([d.text, ""], B.w - 10), st(d.warn ? theme.warn : theme.dim)]], B.w);
158
+ }
159
+ }
@@ -0,0 +1,85 @@
1
+ /** The tab strip: paging between panels the terminal is too narrow to show at once.
2
+ *
3
+ * layout.ts hides the files panel under 140 columns and the plan/usage column under 110, and until
4
+ * now a hidden panel was simply gone — ⌃e answered "the files panel needs ≥ 140 columns" and that was
5
+ * that. Berkay asked for the panels to behave like tabs instead: on a narrow terminal the MAIN slot
6
+ * (the code panel's rect) shows whichever panel `s.page` names, and a strip on that slot's top border
7
+ * says which panels exist and which one is showing. Clicking a tab pages to it; ⌃e pages to files,
8
+ * ⌃o cycles.
9
+ *
10
+ * Deliberately absent on a wide terminal: when every panel is on screen there is nothing to page,
11
+ * and a strip would be one more thing to read. tabsFor() returns [] and nothing is drawn.
12
+ *
13
+ * Geometry and painting live together here so the hit zones the frame loop registers are the cells
14
+ * that were actually painted — the same rule card-hits.ts and panel-hits.ts follow. The strip
15
+ * overpaints the panel title on the top border (panel() draws the title at x+2); the active tab IS
16
+ * the title. */
17
+
18
+ import { st } from "./draw-util.ts";
19
+ import { strWidth } from "./screen.ts";
20
+ import { ATTR, PAGES, type Layout, type Page, type Rect, type ScreenLike, type SextantState, type Theme } from "./types.ts";
21
+
22
+ export interface Tab {
23
+ page: Page;
24
+ /** the cells the label occupies on the border row */
25
+ rect: Rect;
26
+ active: boolean;
27
+ }
28
+
29
+ const LABEL: Record<Page, string> = { code: "code", files: "files", plan: "plan" };
30
+ const SEP = " · ";
31
+
32
+ /** is this page's own panel hidden by the layout, so it needs a tab to be reachable? */
33
+ export function hiddenByLayout(L: Layout, page: Page): boolean {
34
+ return page === "files" ? L.files === null : page === "plan" ? L.plan === null : false;
35
+ }
36
+
37
+ /** the page actually showing in the main slot: `s.page` if that panel is hidden by the layout (so
38
+ * paging is what makes it visible), else code — a page pointing at a panel that is already on
39
+ * screen is inert, never a duplicate */
40
+ export function mainPage(L: Layout, s: SextantState): Page {
41
+ return hiddenByLayout(L, s.page) ? s.page : "code";
42
+ }
43
+
44
+ /** The tabs to show, positioned on the main slot's top border; [] on a wide terminal. */
45
+ export function tabsFor(L: Layout, s: SextantState): Tab[] {
46
+ const hidden = PAGES.filter((p) => hiddenByLayout(L, p));
47
+ if (hidden.length === 0) return [];
48
+ const pages: Page[] = ["code", ...hidden];
49
+ const active = mainPage(L, s);
50
+ const y = L.code.y;
51
+ const right = L.code.x + L.code.w - 2; // the border's last usable cell before the corner
52
+ let x = L.code.x + 2;
53
+ const out: Tab[] = [];
54
+ for (const [i, page] of pages.entries()) {
55
+ const w = strWidth(LABEL[page]) + 2; // ` label `
56
+ if (x + w > right) break; // a strip the border cannot hold stops, never wraps
57
+ out.push({ page, rect: { x, y, w, h: 1 }, active: page === active });
58
+ x += w + (i < pages.length - 1 ? strWidth(SEP) : 0);
59
+ }
60
+ return out;
61
+ }
62
+
63
+ /** Paint the strip over the main slot's top border. Pure; returns what it painted. */
64
+ export function drawTabs(scr: ScreenLike, L: Layout, s: SextantState, theme: Theme): Tab[] {
65
+ const tabs = tabsFor(L, s);
66
+ if (tabs.length === 0) return tabs;
67
+ const focused = s.focus === "code" || s.focus === "files";
68
+ for (const [i, t] of tabs.entries()) {
69
+ const style = t.active
70
+ ? st(theme.bg, focused ? theme.accent : theme.fg2, ATTR.BOLD)
71
+ : st(theme.muted);
72
+ scr.text(t.rect.x, t.rect.y, [[` ${LABEL[t.page]} `, style]], t.rect.w);
73
+ const next = tabs[i + 1];
74
+ if (next) scr.text(t.rect.x + t.rect.w, t.rect.y, [[SEP, st(theme.frame)]], next.rect.x - (t.rect.x + t.rect.w));
75
+ }
76
+ return tabs;
77
+ }
78
+
79
+ /** the page after `page` among the tabs on screen (⌃o); `page` itself when there is nothing to cycle */
80
+ export function nextPage(L: Layout, s: SextantState): Page {
81
+ const tabs = tabsFor(L, s);
82
+ if (tabs.length < 2) return s.page;
83
+ const i = tabs.findIndex((t) => t.active);
84
+ return tabs[(i + 1) % tabs.length]!.page;
85
+ }
@@ -0,0 +1,65 @@
1
+ /** Sextant surface (port #42) — small pure drawing helpers shared by the code and messages panels.
2
+ * Ported from the user's own sextant v0.4.0 prototype: src/app.js:37 (st), :66-78 (wrap), :210-220
3
+ * (panel). Pure: no clock, no timers, no process access — `now` arrives as a parameter.
4
+ * #44 dedupe: `st` and `panel` are the shared theme.ts / layout.ts ones, re-exported here so the
5
+ * panels' imports stay put (layout.ts panel() = this file's former one: interior cleared to the
6
+ * theme background, extras clipped with an ellipsis before they are dropped). */
7
+
8
+ import { SPIN, type Rect } from "./types.ts";
9
+
10
+ export { st } from "./theme.ts";
11
+ export { panel } from "./layout.ts";
12
+
13
+ /** the four-glyph diamond spinner, phase from the frame clock (140 ms per step) */
14
+ export const spinner = (now: number): string => SPIN[Math.floor(now / 140) % SPIN.length] ?? "◇";
15
+
16
+ /** split file content into lines; a trailing newline does not produce a phantom empty line */
17
+ export function splitLines(s: string): string[] {
18
+ const l = s.split("\n");
19
+ if (l.length && l[l.length - 1] === "") l.pop();
20
+ return l;
21
+ }
22
+
23
+ /** 1-slot cache: splitLines is called twice per frame (drawFileView + codeScrollTop/rowCount).
24
+ * File content is a stable string reference within a frame; reference equality is the cheapest guard. */
25
+ let _splitCache: { src: string; lines: string[] } | null = null;
26
+ export function splitLinesCached(s: string): string[] {
27
+ if (_splitCache && _splitCache.src === s) return _splitCache.lines;
28
+ const lines = splitLines(s);
29
+ _splitCache = { src: s, lines };
30
+ return lines;
31
+ }
32
+
33
+ /** Word-wrap to `width` cells — one cell per code point (types.ts), so an emoji counts once and a
34
+ * surrogate pair never straddles two rows — keeping explicit newlines (a blank paragraph stays a
35
+ * blank row); a word longer than the width is hard-split wherever it starts. Never returns []. */
36
+ export function wrap(text: string, width: number): string[] {
37
+ const w = Math.max(1, width);
38
+ const out: string[] = [];
39
+ for (const para of String(text).split("\n")) {
40
+ let line = "", len = 0;
41
+ for (const word of para.split(" ")) {
42
+ const cps = [...word];
43
+ if (line && len + 1 + cps.length <= w) { line += " " + word; len += 1 + cps.length; continue; }
44
+ if (line) out.push(line);
45
+ while (cps.length > w) out.push(cps.splice(0, w).join(""));
46
+ line = cps.join(""); len = cps.length;
47
+ }
48
+ out.push(line);
49
+ }
50
+ return out;
51
+ }
52
+
53
+ /** hard-split a line into `width`-cell chunks (terminal-style, no word boundaries; one cell per
54
+ * code point — an astral glyph moves to the next row whole); "" → [""] */
55
+ export function hardWrap(line: string, width: number): string[] {
56
+ const w = Math.max(1, width);
57
+ const cps = [...line];
58
+ if (cps.length <= w) return [line];
59
+ const out: string[] = [];
60
+ for (let i = 0; i < cps.length; i += w) out.push(cps.slice(i, i + w).join(""));
61
+ return out;
62
+ }
63
+
64
+ /** inner rect of a panel drawn at `r` (2 cells of horizontal padding, 1 row for each border) */
65
+ export const inner = (r: Rect): Rect => ({ x: r.x + 2, y: r.y + 1, w: r.w - 4, h: r.h - 2 });
@@ -0,0 +1,230 @@
1
+ /** ported from the user's sextant v0.4.0 prototype, src/engine.js (+ wrap() from app.js lines 66-78) */
2
+ /* Pure helpers for the panels: syntax tokenizer, diff hunks/line marks over jsdiff output, fuzzy
3
+ match, word wrap, small formatters. The prototype's O(n·m) LCS diffLines() is NOT ported —
4
+ jsdiff (Myers, already a dependency) produces the ops, which then feed the same hunk/mark code. */
5
+
6
+ import { diffLines, type ChangeObject, type StructuredPatch } from "diff";
7
+ import type { DiffHunk } from "./types.ts";
8
+
9
+ export type Lang = "ts" | "json" | "md" | "text";
10
+ export const LANG_NAME: Readonly<Record<Lang, string>> = { ts: "TypeScript", json: "JSON", md: "Markdown", text: "Plain Text" };
11
+
12
+ /** split on \n, dropping the empty tail a trailing newline leaves */
13
+ export const splitLines = (s: string): string[] => { const l = s.split("\n"); if (l.length && l[l.length - 1] === "") l.pop(); return l; };
14
+ export const basename = (p: string): string => p.slice(p.lastIndexOf("/") + 1);
15
+ export const dirname = (p: string): string => (p.includes("/") ? p.slice(0, p.lastIndexOf("/")) : "");
16
+
17
+ /** language by extension; the TS tokenizer serves the whole JS/TS family (the prototype knew .ts/.tsx only) */
18
+ export function langOf(p: string): Lang {
19
+ if (/\.[cm]?[jt]sx?$/.test(p)) return "ts";
20
+ if (/\.jsonc?$/.test(p)) return "json";
21
+ if (/\.(md|markdown)$/.test(p)) return "md";
22
+ return "text";
23
+ }
24
+
25
+ /* ------------------------------------------------------------ tokenizer */
26
+
27
+ export type TokenClass = "kw" | "str" | "num" | "cm" | "fn" | "ty" | "pu" | "op" | "prop" | "id" | "key" | "dec" | "plain";
28
+ export type Token = [string, TokenClass];
29
+
30
+ const KW = new Set("import export from const let var function return if else async await new type interface extends implements class throw try catch finally for while of in as default void typeof instanceof null undefined true false this readonly declare satisfies keyof enum switch case break continue do yield static private public protected".split(" "));
31
+ const TS_RE = /(\/\/.*)|("(?:[^"\\\n]|\\.)*"|'(?:[^'\\\n]|\\.)*'|`(?:[^`\\]|\\.)*`)|(\b\d[\d_]*(?:\.\d+)?\b)|(@?[A-Za-z_$][\w$]*)|([{}()[\]])|([;,.:])|(=>|\?\?|\?\.|[<>=!+\-*/%&|^~?]+)/g;
32
+
33
+ /** one source line → [text, class] runs; "text" is one plain run, "md" goes through tokenizeMd */
34
+ export function tokenize(line: string, lang: string): Token[] {
35
+ if (lang === "md") return tokenizeMd(line);
36
+ if (lang === "text") return [[line, "plain"]];
37
+ const out: Token[] = [];
38
+ let last = 0;
39
+ let m: RegExpExecArray | null;
40
+ TS_RE.lastIndex = 0;
41
+ while ((m = TS_RE.exec(line))) {
42
+ if (m.index > last) out.push([line.slice(last, m.index), "plain"]);
43
+ const tok = m[0];
44
+ let cls: TokenClass;
45
+ if (m[1]) cls = "cm";
46
+ else if (m[2]) cls = lang === "json" && /^\s*:/.test(line.slice(m.index + tok.length)) ? "key" : "str";
47
+ else if (m[3]) cls = "num";
48
+ else if (m[4]) {
49
+ const before = line[m.index - 1];
50
+ const after = line.slice(m.index + tok.length);
51
+ if (tok[0] === "@") cls = "dec";
52
+ else if (KW.has(tok)) cls = "kw";
53
+ else if (before === ".") cls = "prop";
54
+ else if (/^\s*\(/.test(after) || (/^</.test(after) && /^[a-z]/.test(tok))) cls = "fn";
55
+ else if (/^[A-Z]/.test(tok)) cls = "ty";
56
+ else if (/^\??:/.test(after)) cls = "prop";
57
+ else cls = "id";
58
+ } else if (m[5] || m[6]) cls = "pu";
59
+ else cls = "op";
60
+ out.push([tok, cls]);
61
+ last = m.index + tok.length;
62
+ }
63
+ if (last < line.length) out.push([line.slice(last), "plain"]);
64
+ return out;
65
+ }
66
+
67
+ export function tokenizeMd(line: string): Token[] {
68
+ if (/^#{1,6}\s/.test(line)) return [[line, "kw"]];
69
+ if (/^\s{4}/.test(line)) return [[line, "str"]];
70
+ const out: Token[] = [];
71
+ let last = 0;
72
+ let m: RegExpExecArray | null;
73
+ const re = /`[^`]+`/g;
74
+ while ((m = re.exec(line))) {
75
+ if (m.index > last) out.push([line.slice(last, m.index), "plain"]);
76
+ out.push([m[0], "str"]);
77
+ last = m.index + m[0].length;
78
+ }
79
+ if (last < line.length) out.push([line.slice(last), "plain"]);
80
+ return out;
81
+ }
82
+
83
+ /* ------------------------------------------------------------ diff */
84
+
85
+ export type OpKind = "eq" | "del" | "ins";
86
+ /** one diff line; an/bn are the 1-based old/new line numbers (absent on the side the line is not on) */
87
+ export interface DiffOp { t: OpKind; text: string; an?: number; bn?: number }
88
+ /** a run of ops with its @@ header numbers: old start/count, new start/count */
89
+ export interface Hunk { ops: DiffOp[]; as: number; bs: number; ac: number; bc: number }
90
+ export type LineMark = "added" | "changed";
91
+
92
+ /** jsdiff diffLines() change objects → the prototype's op list with line numbers */
93
+ export function opsFromChanges(changes: readonly ChangeObject<string>[]): DiffOp[] {
94
+ const ops: DiffOp[] = [];
95
+ let an = 0, bn = 0;
96
+ for (const c of changes) {
97
+ const lines = c.value.split("\n");
98
+ if (lines.length > c.count) lines.pop(); // the value ends with its last line's newline
99
+ for (const text of lines) {
100
+ if (c.added) ops.push({ t: "ins", text, bn: ++bn });
101
+ else if (c.removed) ops.push({ t: "del", text, an: ++an });
102
+ else ops.push({ t: "eq", text, an: ++an, bn: ++bn });
103
+ }
104
+ }
105
+ return ops;
106
+ }
107
+
108
+ /** line-diff two texts (jsdiff Myers) → ops; replaces the prototype's O(n·m) LCS table */
109
+ export function diffOps(a: string, b: string): DiffOp[] {
110
+ return opsFromChanges(diffLines(a, b));
111
+ }
112
+
113
+ /** added/deleted line counts */
114
+ export const opStats = (ops: readonly DiffOp[]): { a: number; d: number } => {
115
+ let a = 0, d = 0;
116
+ for (const o of ops) { if (o.t === "ins") a++; else if (o.t === "del") d++; }
117
+ return { a, d };
118
+ };
119
+
120
+ function hunkOf(ops: readonly DiffOp[], s: number, e: number): Hunk {
121
+ const slice = ops.slice(s, e);
122
+ let as: number | null = null, bs: number | null = null, ac = 0, bc = 0;
123
+ for (const o of slice) {
124
+ if (o.an) { if (as == null) as = o.an; ac++; }
125
+ if (o.bn) { if (bs == null) bs = o.bn; bc++; }
126
+ }
127
+ return { ops: slice, as: as ?? 0, bs: bs ?? 0, ac, bc };
128
+ }
129
+
130
+ /** group changes into hunks with `ctx` context lines; changes closer than 2·ctx share a hunk; Infinity = one hunk */
131
+ export function buildHunks(ops: readonly DiffOp[], ctx: number): Hunk[] {
132
+ if (!isFinite(ctx)) return ops.length ? [hunkOf(ops, 0, ops.length)] : [];
133
+ const hunks: Hunk[] = [];
134
+ let i = 0;
135
+ while (i < ops.length) {
136
+ if (ops[i]!.t === "eq") { i++; continue; }
137
+ const start = Math.max(0, i - ctx);
138
+ let end = i + 1, j = i + 1;
139
+ while (j < ops.length) {
140
+ if (ops[j]!.t !== "eq") { end = j + 1; j++; continue; }
141
+ let k = j;
142
+ while (k < ops.length && ops[k]!.t === "eq") k++;
143
+ if (k < ops.length && k - j <= ctx * 2) { j = k; continue; }
144
+ break;
145
+ }
146
+ end = Math.min(ops.length, end + ctx);
147
+ hunks.push(hunkOf(ops, start, end));
148
+ i = end;
149
+ }
150
+ return hunks;
151
+ }
152
+
153
+ /** new-line-number → "added" (pure insertion) | "changed" (insertion next to a deletion) */
154
+ export function lineMarks(ops: readonly DiffOp[]): Map<number, LineMark> {
155
+ const marks = new Map<number, LineMark>();
156
+ let i = 0;
157
+ while (i < ops.length) {
158
+ if (ops[i]!.t === "eq") { i++; continue; }
159
+ let j = i, hasDel = false;
160
+ while (j < ops.length && ops[j]!.t !== "eq") { if (ops[j]!.t === "del") hasDel = true; j++; }
161
+ for (let k = i; k < j; k++) { const o = ops[k]!; if (o.t === "ins" && o.bn) marks.set(o.bn, hasDel ? "changed" : "added"); }
162
+ i = j;
163
+ }
164
+ return marks;
165
+ }
166
+
167
+ /** a prototype hunk → the contract's DiffHunk rows */
168
+ export function toDiffHunk(h: Hunk): DiffHunk {
169
+ return { rows: h.ops.map((o) => ({ op: o.t === "ins" ? "+" : o.t === "del" ? "-" : " ", text: o.text })), oldStart: h.as, newStart: h.bs };
170
+ }
171
+
172
+ /** jsdiff structuredPatch() hunks → the contract's DiffHunk rows ("" stays a context row) */
173
+ export function hunksFromPatch(patch: StructuredPatch): DiffHunk[] {
174
+ return patch.hunks.map((h) => ({
175
+ oldStart: h.oldStart,
176
+ newStart: h.newStart,
177
+ rows: h.lines.map((l) => {
178
+ const c = l[0];
179
+ const op = c === "+" ? "+" : c === "-" ? "-" : " ";
180
+ return { op, text: c === "+" || c === "-" || c === " " ? l.slice(1) : l };
181
+ }),
182
+ }));
183
+ }
184
+
185
+ /* ------------------------------------------------------------ fuzzy */
186
+
187
+ export interface FuzzyMatch { score: number; idx: number[] }
188
+
189
+ /** subsequence match, case-insensitive: +3 for a run, +1 otherwise, +2 at a word/path start; null when q is not in s */
190
+ export function fuzzy(q: string, s: string): FuzzyMatch | null {
191
+ q = q.toLowerCase(); s = s.toLowerCase();
192
+ let qi = 0, score = 0, last = -2;
193
+ const idx: number[] = [];
194
+ for (let i = 0; i < s.length && qi < q.length; i++) {
195
+ if (s[i] === q[qi]) {
196
+ idx.push(i);
197
+ score += (last === i - 1 ? 3 : 1) + (i === 0 || s[i - 1] === " " || s[i - 1] === "/" ? 2 : 0);
198
+ last = i; qi++;
199
+ }
200
+ }
201
+ return qi === q.length ? { score, idx } : null;
202
+ }
203
+
204
+ /* ------------------------------------------------------------ text */
205
+
206
+ /** greedy word wrap per paragraph (\n); a word longer than width is hard-split (app.js wrap) */
207
+ export function wrap(text: string, width: number): string[] {
208
+ width = Math.max(1, width); // width ≤ 0 never advanced the hard-split loop below (prototype quirk)
209
+ const out: string[] = [];
210
+ for (const para of String(text).split("\n")) {
211
+ let line = "";
212
+ for (const w of para.split(" ")) {
213
+ if (!line) { line = w; continue; }
214
+ if (line.length + 1 + w.length <= width) line += " " + w; else { out.push(line); line = w; }
215
+ }
216
+ while (line.length > width) { out.push(line.slice(0, width)); line = line.slice(width); }
217
+ out.push(line);
218
+ }
219
+ return out;
220
+ }
221
+
222
+ /** mm:ss.t run clock */
223
+ export const fmtClock = (ms: number): string => {
224
+ const s = Math.max(0, ms) / 1000;
225
+ const m = Math.floor(s / 60);
226
+ const r = s - m * 60;
227
+ return `${String(m).padStart(2, "0")}:${r.toFixed(1).padStart(4, "0")}`;
228
+ };
229
+ export const fmtDur = (ms: number): string => (ms < 1000 ? `${Math.round(ms)}ms` : `${(ms / 1000).toFixed(1)}s`);
230
+ export const fmtK = (n: number): string => (n >= 1000 ? `${(n / 1000).toFixed(1)}k` : String(n));
@@ -0,0 +1,25 @@
1
+ /** Click zones on the outer frame's two border rows (draw-frame.ts headerRight / footerRight): the
2
+ * unread badge `◆ N` opens the notices (⌃b), the theme name in the footer tag cycles the theme (⌃t),
3
+ * and `effort X` prefills `/effort ` so the levels appear as suggestions. The words are located by
4
+ * the same segment math the painter uses, so a zone is exactly the word it reads. */
5
+ import { footerRight, headerRight } from "./draw-frame.ts";
6
+ import { segWidth } from "./layout.ts";
7
+ import type { HitZone, Rect, SextantState, Theme } from "./types.ts";
8
+
9
+ export function frameHits(F: Rect, s: SextantState, theme: Theme, now: number): HitZone[] {
10
+ const out: HitZone[] = [];
11
+ const head = headerRight(s, theme, now);
12
+ const hx = F.x + F.w - 2 - segWidth(head.segs);
13
+ if (head.badge) out.push({ rect: { x: hx + head.badge[0], y: F.y, w: head.badge[1], h: 1 }, onClick: () => {}, key: { type: "key", name: "b", ctrl: true } });
14
+ const foot = footerRight(s, theme);
15
+ const fx = F.x + F.w - 2 - segWidth(foot.segs), fy = F.y + F.h - 1;
16
+ if (foot.effort) {
17
+ out.push({
18
+ rect: { x: fx + foot.effort[0], y: fy, w: foot.effort[1], h: 1 },
19
+ // the prompt gets `/effort ` with the cursor at the end; the suggestion strip then lists the levels
20
+ onClick: () => { s.input.text = "/effort "; s.input.cur = s.input.text.length; s.input.sgSel = 0; s.focus = "messages"; },
21
+ });
22
+ }
23
+ out.push({ rect: { x: fx + foot.theme[0], y: fy, w: foot.theme[1], h: 1 }, onClick: () => {}, key: { type: "key", name: "t", ctrl: true } });
24
+ return out;
25
+ }