rovecode 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (334) hide show
  1. package/LICENSE +662 -0
  2. package/README.md +737 -0
  3. package/THIRD_PARTY_NOTICES.md +268 -0
  4. package/bin/rovecode.ts +21 -0
  5. package/package.json +56 -0
  6. package/src/acp/server.ts +374 -0
  7. package/src/cli/auth-login.ts +122 -0
  8. package/src/cli/connect.ts +244 -0
  9. package/src/cli/context-cmd.ts +199 -0
  10. package/src/cli/dispatch.ts +82 -0
  11. package/src/cli/doctor.ts +362 -0
  12. package/src/cli/export.ts +276 -0
  13. package/src/cli/help.ts +293 -0
  14. package/src/cli/is-tui-invocation.ts +8 -0
  15. package/src/cli/main.ts +583 -0
  16. package/src/cli/market-cmd.ts +658 -0
  17. package/src/cli/mcp-login.ts +141 -0
  18. package/src/cli/mcp-market-cmd.ts +302 -0
  19. package/src/cli/output.ts +382 -0
  20. package/src/cli/repl.ts +250 -0
  21. package/src/cli/repomap-root.ts +14 -0
  22. package/src/cli/resume.ts +57 -0
  23. package/src/cli/run-flags.ts +43 -0
  24. package/src/cli/run-limits.ts +78 -0
  25. package/src/cli/runtime.ts +931 -0
  26. package/src/cli/session-arg.ts +30 -0
  27. package/src/cli/sessions-cmd.ts +145 -0
  28. package/src/cli/setup.ts +153 -0
  29. package/src/cli/skills-cmd.ts +194 -0
  30. package/src/cli/start-chat.ts +65 -0
  31. package/src/cli/trust-cmd.ts +52 -0
  32. package/src/coding/bash.ts +148 -0
  33. package/src/coding/checkpoints.ts +327 -0
  34. package/src/coding/diff.ts +138 -0
  35. package/src/coding/files.ts +341 -0
  36. package/src/coding/hashline.ts +274 -0
  37. package/src/coding/lsp-gate.ts +254 -0
  38. package/src/coding/lsp-servers.ts +147 -0
  39. package/src/coding/lsp.ts +283 -0
  40. package/src/coding/repomap-cache.ts +99 -0
  41. package/src/coding/repomap-files.ts +192 -0
  42. package/src/coding/repomap.ts +481 -0
  43. package/src/core/agents.ts +255 -0
  44. package/src/core/compaction.ts +259 -0
  45. package/src/core/config.ts +289 -0
  46. package/src/core/context-report.ts +228 -0
  47. package/src/core/context.ts +60 -0
  48. package/src/core/count-remote.ts +107 -0
  49. package/src/core/execpolicy-rules.ts +196 -0
  50. package/src/core/execpolicy.ts +385 -0
  51. package/src/core/executor.ts +454 -0
  52. package/src/core/guardrails.ts +400 -0
  53. package/src/core/hooks.ts +411 -0
  54. package/src/core/images.ts +230 -0
  55. package/src/core/intro.ts +266 -0
  56. package/src/core/loop.ts +567 -0
  57. package/src/core/modes.ts +372 -0
  58. package/src/core/orchestrator.ts +245 -0
  59. package/src/core/proc-group.ts +48 -0
  60. package/src/core/project-trust.ts +98 -0
  61. package/src/core/reflection.ts +165 -0
  62. package/src/core/sandbox-config.ts +186 -0
  63. package/src/core/session-id.ts +24 -0
  64. package/src/core/session-images.ts +73 -0
  65. package/src/core/session-ops.ts +183 -0
  66. package/src/core/session-text.ts +29 -0
  67. package/src/core/session.ts +469 -0
  68. package/src/core/settings.ts +170 -0
  69. package/src/core/tasks.ts +646 -0
  70. package/src/core/token-scale.ts +108 -0
  71. package/src/core/tools.ts +309 -0
  72. package/src/core/trust.ts +104 -0
  73. package/src/core/types.ts +330 -0
  74. package/src/core/update-check.ts +171 -0
  75. package/src/core/usage.ts +204 -0
  76. package/src/core/validate.ts +121 -0
  77. package/src/core/verify-gate.ts +159 -0
  78. package/src/core/verify.ts +236 -0
  79. package/src/core/voice.ts +158 -0
  80. package/src/core/win-job.ts +183 -0
  81. package/src/core/workspace.ts +184 -0
  82. package/src/design/audit.ts +797 -0
  83. package/src/design/direction.ts +190 -0
  84. package/src/design/rules.ts +157 -0
  85. package/src/eval/bench.ts +150 -0
  86. package/src/eval/gauntlet-runner.ts +215 -0
  87. package/src/eval/gauntlet-support.ts +84 -0
  88. package/src/eval/gauntlet-wave3.ts +269 -0
  89. package/src/eval/gauntlet-wave4.ts +217 -0
  90. package/src/eval/gauntlet.ts +253 -0
  91. package/src/index.ts +17 -0
  92. package/src/lanes/agy.ts +95 -0
  93. package/src/lanes/approval.ts +24 -0
  94. package/src/lanes/claude.ts +129 -0
  95. package/src/lanes/codex.ts +127 -0
  96. package/src/lanes/events.ts +130 -0
  97. package/src/lanes/job.ts +142 -0
  98. package/src/lanes/opencode.ts +122 -0
  99. package/src/lanes/process.ts +184 -0
  100. package/src/lanes/progress.ts +183 -0
  101. package/src/lanes/registry.ts +178 -0
  102. package/src/lanes/runner.ts +124 -0
  103. package/src/lanes/types.ts +112 -0
  104. package/src/market/catalogs/mcp-docs.json +111 -0
  105. package/src/market/catalogs/plugins.json +111 -0
  106. package/src/market/catalogs/skills.json +478 -0
  107. package/src/market/clone.ts +72 -0
  108. package/src/market/context-cost.ts +121 -0
  109. package/src/market/digest.ts +106 -0
  110. package/src/market/index.ts +22 -0
  111. package/src/market/install.ts +578 -0
  112. package/src/market/manifest.ts +187 -0
  113. package/src/market/prereq.ts +145 -0
  114. package/src/market/registry.ts +363 -0
  115. package/src/market/resolve.ts +111 -0
  116. package/src/market/types.ts +236 -0
  117. package/src/market/validate.ts +227 -0
  118. package/src/mcp/client.ts +449 -0
  119. package/src/mcp/config.ts +252 -0
  120. package/src/mcp/local-package.ts +211 -0
  121. package/src/mcp/market-catalog.ts +84 -0
  122. package/src/mcp/market-install.ts +289 -0
  123. package/src/mcp/market.ts +362 -0
  124. package/src/mcp/oauth.ts +251 -0
  125. package/src/mcp/prompts-resources.ts +249 -0
  126. package/src/mcp/shared.ts +149 -0
  127. package/src/mcp/status.ts +67 -0
  128. package/src/mcp/tools.ts +275 -0
  129. package/src/mcp/transport.ts +122 -0
  130. package/src/mcp/trust.ts +25 -0
  131. package/src/memory/blocks.ts +278 -0
  132. package/src/memory/recall.ts +355 -0
  133. package/src/memory/scope.ts +182 -0
  134. package/src/memory/store.ts +105 -0
  135. package/src/memory/tools.ts +99 -0
  136. package/src/plugins/cli.ts +119 -0
  137. package/src/plugins/discover.ts +108 -0
  138. package/src/plugins/index.ts +50 -0
  139. package/src/plugins/install.ts +184 -0
  140. package/src/plugins/load.ts +124 -0
  141. package/src/plugins/manifest.ts +92 -0
  142. package/src/plugins/state.ts +83 -0
  143. package/src/providers/auth.ts +408 -0
  144. package/src/providers/cache.ts +223 -0
  145. package/src/providers/catalog-local.ts +160 -0
  146. package/src/providers/catalog.ts +421 -0
  147. package/src/providers/middleware-context.ts +86 -0
  148. package/src/providers/middleware.ts +373 -0
  149. package/src/providers/model-list.ts +23 -0
  150. package/src/providers/models-index.json +1 -0
  151. package/src/providers/oauth/common.ts +105 -0
  152. package/src/providers/oauth/device-code.ts +107 -0
  153. package/src/providers/oauth/github-copilot.ts +146 -0
  154. package/src/providers/oauth/loopback.ts +158 -0
  155. package/src/providers/oauth/openai.ts +163 -0
  156. package/src/providers/oauth/openrouter.ts +89 -0
  157. package/src/providers/oauth/pkce.ts +45 -0
  158. package/src/providers/oauth/registry.ts +39 -0
  159. package/src/providers/oauth/seam.ts +89 -0
  160. package/src/providers/profile-glm53.ts +111 -0
  161. package/src/providers/profile-sonnet5-persona.ts +65 -0
  162. package/src/providers/profile-sonnet5-voice.ts +23 -0
  163. package/src/providers/profiles.ts +156 -0
  164. package/src/providers/provider-config.ts +311 -0
  165. package/src/providers/registry.ts +333 -0
  166. package/src/providers/responses.ts +209 -0
  167. package/src/providers/retry.ts +234 -0
  168. package/src/providers/router.ts +294 -0
  169. package/src/providers/sse.ts +26 -0
  170. package/src/providers/stream-errors.ts +117 -0
  171. package/src/providers/stream.ts +566 -0
  172. package/src/providers/thinking.ts +189 -0
  173. package/src/providers/wire-messages.ts +129 -0
  174. package/src/providers/wire-responses.ts +79 -0
  175. package/src/providers/wire-select.ts +53 -0
  176. package/src/server/http.ts +291 -0
  177. package/src/server/openapi.ts +246 -0
  178. package/src/sextant/card-hits.ts +102 -0
  179. package/src/sextant/card-keys.ts +55 -0
  180. package/src/sextant/context-source.ts +157 -0
  181. package/src/sextant/crew-cards.ts +350 -0
  182. package/src/sextant/draw-agents.ts +273 -0
  183. package/src/sextant/draw-code.ts +388 -0
  184. package/src/sextant/draw-context.ts +222 -0
  185. package/src/sextant/draw-frame.ts +164 -0
  186. package/src/sextant/draw-market.ts +573 -0
  187. package/src/sextant/draw-messages.ts +386 -0
  188. package/src/sextant/draw-pet.ts +230 -0
  189. package/src/sextant/draw-plan.ts +187 -0
  190. package/src/sextant/draw-tabs.ts +85 -0
  191. package/src/sextant/draw-util.ts +65 -0
  192. package/src/sextant/draw-wizard.ts +378 -0
  193. package/src/sextant/engine.ts +230 -0
  194. package/src/sextant/frame-hits.ts +25 -0
  195. package/src/sextant/frame.ts +101 -0
  196. package/src/sextant/git-status.ts +197 -0
  197. package/src/sextant/grid.ts +59 -0
  198. package/src/sextant/input.ts +119 -0
  199. package/src/sextant/keys.ts +521 -0
  200. package/src/sextant/layout.ts +86 -0
  201. package/src/sextant/local-commands.ts +169 -0
  202. package/src/sextant/market-source.ts +287 -0
  203. package/src/sextant/mentions.ts +200 -0
  204. package/src/sextant/message-hits.ts +26 -0
  205. package/src/sextant/model.ts +387 -0
  206. package/src/sextant/overlays.ts +456 -0
  207. package/src/sextant/panel-hits.ts +38 -0
  208. package/src/sextant/pet.ts +399 -0
  209. package/src/sextant/screen.ts +324 -0
  210. package/src/sextant/scroll-hits.ts +66 -0
  211. package/src/sextant/scrollbar.ts +82 -0
  212. package/src/sextant/sextant-bridge.ts +174 -0
  213. package/src/sextant/sextant-cards.ts +142 -0
  214. package/src/sextant/sextant-diff-base.ts +63 -0
  215. package/src/sextant/sextant-files.ts +154 -0
  216. package/src/sextant/sextant-frame-loop.ts +335 -0
  217. package/src/sextant/sextant-renderer.ts +574 -0
  218. package/src/sextant/sextant-repo.ts +140 -0
  219. package/src/sextant/theme.ts +66 -0
  220. package/src/sextant/tool-rows.ts +189 -0
  221. package/src/sextant/types.ts +493 -0
  222. package/src/skills/index.ts +387 -0
  223. package/src/skills/pack.ts +220 -0
  224. package/src/skills/spec.ts +162 -0
  225. package/src/skills/tools.ts +69 -0
  226. package/src/skills/versioned.ts +227 -0
  227. package/src/telemetry/otel-export.ts +122 -0
  228. package/src/telemetry/otel-lanes.ts +89 -0
  229. package/src/telemetry/otel-logs.ts +131 -0
  230. package/src/telemetry/otel-metrics.ts +136 -0
  231. package/src/telemetry/otel.ts +397 -0
  232. package/src/telemetry/otlp.ts +76 -0
  233. package/src/tools/ask-user.ts +156 -0
  234. package/src/tools/bash-bg.ts +94 -0
  235. package/src/tools/bash-jobs.ts +237 -0
  236. package/src/tools/design.ts +151 -0
  237. package/src/tools/evalcell.ts +338 -0
  238. package/src/tools/html-text.ts +139 -0
  239. package/src/tools/provider.ts +149 -0
  240. package/src/tools/task.ts +250 -0
  241. package/src/tools/todo.ts +320 -0
  242. package/src/tools/webfetch.ts +332 -0
  243. package/src/tools/websearch.ts +359 -0
  244. package/src/tui/agents-cmd.ts +41 -0
  245. package/src/tui/app.ts +749 -0
  246. package/src/tui/attach.ts +127 -0
  247. package/src/tui/boot-notes.ts +41 -0
  248. package/src/tui/builtin-prompts.ts +59 -0
  249. package/src/tui/checkpoints-cmd.ts +70 -0
  250. package/src/tui/clipboard-image.ts +81 -0
  251. package/src/tui/clipboard.ts +78 -0
  252. package/src/tui/commands.ts +283 -0
  253. package/src/tui/config-view.ts +53 -0
  254. package/src/tui/context-cmds.ts +282 -0
  255. package/src/tui/cost.ts +108 -0
  256. package/src/tui/crash-guard.ts +173 -0
  257. package/src/tui/focus-terminal.ts +34 -0
  258. package/src/tui/git-cmds.ts +273 -0
  259. package/src/tui/git-plain.ts +58 -0
  260. package/src/tui/info-cmd.ts +150 -0
  261. package/src/tui/input-plain.ts +76 -0
  262. package/src/tui/mcp-cmd.ts +128 -0
  263. package/src/tui/memory-note.ts +77 -0
  264. package/src/tui/modes-cmd.ts +45 -0
  265. package/src/tui/notify-seq.ts +100 -0
  266. package/src/tui/notify.ts +318 -0
  267. package/src/tui/overlays.ts +97 -0
  268. package/src/tui/pi-renderer.ts +428 -0
  269. package/src/tui/providers-cmd.ts +377 -0
  270. package/src/tui/reasoning-view.ts +56 -0
  271. package/src/tui/renderer.ts +128 -0
  272. package/src/tui/replay-marker.ts +29 -0
  273. package/src/tui/session-cmd.ts +148 -0
  274. package/src/tui/session-manage.ts +95 -0
  275. package/src/tui/sextant-attach.ts +102 -0
  276. package/src/tui/sextant-io.ts +202 -0
  277. package/src/tui/sextant-smoke.ts +110 -0
  278. package/src/tui/shell-cmd.ts +158 -0
  279. package/src/tui/smoke.ts +72 -0
  280. package/src/tui/staged-terminal.ts +50 -0
  281. package/src/tui/startup.ts +12 -0
  282. package/src/tui/theme.ts +59 -0
  283. package/src/tui/todo-label.ts +7 -0
  284. package/src/tui/trust-card.ts +107 -0
  285. package/src/tui/tui-commands.ts +87 -0
  286. package/tsconfig.json +30 -0
  287. package/vendor/pi-tui/LICENSE +21 -0
  288. package/vendor/pi-tui/PATCHES.md +12 -0
  289. package/vendor/pi-tui/PROVENANCE.md +12 -0
  290. package/vendor/pi-tui/README.upstream.md +854 -0
  291. package/vendor/pi-tui/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
  292. package/vendor/pi-tui/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
  293. package/vendor/pi-tui/src/alt-screen-search.ts +158 -0
  294. package/vendor/pi-tui/src/autocomplete.ts +827 -0
  295. package/vendor/pi-tui/src/components/alt-screen-flash.ts +52 -0
  296. package/vendor/pi-tui/src/components/box.ts +138 -0
  297. package/vendor/pi-tui/src/components/cancellable-loader.ts +41 -0
  298. package/vendor/pi-tui/src/components/editor.ts +2364 -0
  299. package/vendor/pi-tui/src/components/h-stack.ts +45 -0
  300. package/vendor/pi-tui/src/components/image.ts +128 -0
  301. package/vendor/pi-tui/src/components/input.ts +448 -0
  302. package/vendor/pi-tui/src/components/loader.ts +93 -0
  303. package/vendor/pi-tui/src/components/markdown.ts +1016 -0
  304. package/vendor/pi-tui/src/components/scroll-view.ts +217 -0
  305. package/vendor/pi-tui/src/components/select-list.ts +230 -0
  306. package/vendor/pi-tui/src/components/settings-list.ts +277 -0
  307. package/vendor/pi-tui/src/components/spacer.ts +29 -0
  308. package/vendor/pi-tui/src/components/stack.ts +155 -0
  309. package/vendor/pi-tui/src/components/text.ts +108 -0
  310. package/vendor/pi-tui/src/components/truncated-text.ts +66 -0
  311. package/vendor/pi-tui/src/components/v-stack.ts +34 -0
  312. package/vendor/pi-tui/src/editor-component.ts +75 -0
  313. package/vendor/pi-tui/src/fuzzy.ts +138 -0
  314. package/vendor/pi-tui/src/index.ts +149 -0
  315. package/vendor/pi-tui/src/keybindings.ts +321 -0
  316. package/vendor/pi-tui/src/keys.ts +1402 -0
  317. package/vendor/pi-tui/src/kill-ring.ts +47 -0
  318. package/vendor/pi-tui/src/latex.ts +1381 -0
  319. package/vendor/pi-tui/src/layout-node.ts +52 -0
  320. package/vendor/pi-tui/src/layout.ts +411 -0
  321. package/vendor/pi-tui/src/native-modifiers.ts +60 -0
  322. package/vendor/pi-tui/src/native-module-path.ts +32 -0
  323. package/vendor/pi-tui/src/stdin-buffer.ts +445 -0
  324. package/vendor/pi-tui/src/terminal-colors.ts +74 -0
  325. package/vendor/pi-tui/src/terminal-image.ts +701 -0
  326. package/vendor/pi-tui/src/terminal.ts +554 -0
  327. package/vendor/pi-tui/src/tui-alt-screen.ts +1379 -0
  328. package/vendor/pi-tui/src/tui-main-screen.ts +655 -0
  329. package/vendor/pi-tui/src/tui.ts +1264 -0
  330. package/vendor/pi-tui/src/undo-stack.ts +29 -0
  331. package/vendor/pi-tui/src/utils.ts +1327 -0
  332. package/vendor/pi-tui/src/word-navigation.ts +118 -0
  333. package/vendor/pi-tui/test/test-themes.ts +39 -0
  334. package/vendor/pi-tui/test/virtual-terminal.ts +219 -0
@@ -0,0 +1,164 @@
1
+ /** Sextant frame + files + toasts painters (port #41). Ported from the user's sextant v0.4.0
2
+ * app.js:150-161 (glyph/stateColor/stateLabel), :209-220 (panel), :245-269 (drawFrame header +
3
+ * footer), :300-330 (GUIDE/drawFiles) and :1090-1096 (drawToasts); the mock repo/branch/model
4
+ * constants are read from SextantState. Pure: `now` is a parameter — no clocks, no timers. */
5
+
6
+ import { ATTR, SPIN } from "./types.ts";
7
+ import type { ActivityState, FileStatus, Rect, ScreenLike, Seg, SextantState, Theme } from "./types.ts";
8
+ import { elapsed, fileCount, fmtClock, repoModified, treeGuides, treeRows, type Guide } from "./model.ts";
9
+ import { EMPTY } from "../core/voice.ts";
10
+ import { panel, segWidth } from "./layout.ts";
11
+ import { st } from "./theme.ts";
12
+ import { thinkingWord } from "./pet.ts";
13
+ import { scrollbar } from "./scrollbar.ts";
14
+ import { unreadNotices } from "./model.ts";
15
+
16
+ const gap: Seg = [" ", undefined];
17
+
18
+ type Mode = "idle" | "thinking" | "active" | "waiting" | "complete" | "error";
19
+ const MODE: Record<ActivityState, Mode> = {
20
+ IDLE: "idle", THINKING: "thinking", WRITING: "thinking", READING: "thinking",
21
+ EDITING: "active", RUNNING: "active", TESTING: "active", DELEGATING: "active",
22
+ WAITING: "waiting", SUCCESS: "complete", ERROR: "error",
23
+ };
24
+ const modeOf = (s: SextantState): Mode => (s.card ? "waiting" : MODE[s.activity.state]);
25
+ export const spin = (now: number, period = 140): string => SPIN[Math.floor(Math.max(0, now) / period) % SPIN.length] ?? "◆";
26
+
27
+ /** header glyph + color (app.js glyph()): spinner while working, blinking ◆ while waiting,
28
+ * ok/err ◆ when done, a slow accent pulse when idle */
29
+ export function activityGlyph(s: SextantState, theme: Theme, now: number): [string, number] {
30
+ switch (modeOf(s)) {
31
+ case "thinking": case "active": return [spin(now), theme.accent];
32
+ case "waiting": return ["◆", Math.floor(now / 450) % 2 ? theme.warn : theme.mixDim];
33
+ case "complete": return ["◆", theme.ok];
34
+ case "error": return ["◆", theme.err];
35
+ default: return ["◆", [theme.accent, theme.accentDim, theme.mixDim, theme.accentDim][Math.floor(now / 500) % 4] ?? theme.accent];
36
+ }
37
+ }
38
+ export function activityColor(s: SextantState, theme: Theme): number {
39
+ const m = modeOf(s);
40
+ return m === "idle" ? theme.muted : m === "waiting" ? theme.warn : m === "complete" ? theme.ok : m === "error" ? theme.err : theme.accent;
41
+ }
42
+ /** what the header says: "needs you" over a card, the rotating thinking word while a provider turn
43
+ * is silent (the same word the messages header shows — one word per screen), else the activity label */
44
+ export const activityLabel = (s: SextantState, now?: number): string =>
45
+ s.card ? "needs you"
46
+ : now !== undefined && s.activity.turnAt !== undefined && s.activity.state === "THINKING" ? thinkingWord(now - s.activity.turnAt)
47
+ : s.activity.label || s.activity.state.toLowerCase();
48
+
49
+ /** Outer frame: `◆ rovecode · repo · branch · n modified` left, glyph + activity + run clock right,
50
+ * key hints bottom-left, mode/yolo markers + `model · theme · vX` bottom-right. Without git the
51
+ * header shows the cwd basename only. */
52
+ /** the header's right-hand run: unread badge · activity glyph + label · run clock. `badge` is the
53
+ * badge's [cell offset, width] inside the run (null once everything is read) — frame-hits.ts makes
54
+ * it a click zone that opens the notices, the same as ⌃b. */
55
+ export function headerRight(s: SextantState, theme: Theme, now: number): { segs: Seg[]; badge: [number, number] | null } {
56
+ const [g, gc] = activityGlyph(s, theme, now);
57
+ // unread notices (model.ts notify) sit left of the activity: `◆ 3` in warn, gone when read (⌃b)
58
+ const unread = unreadNotices(s);
59
+ const badge = unread > 0 ? `◆ ${unread}` : null;
60
+ const segs: Seg[] = [gap, ...(badge ? [[badge, st(theme.warn)] as Seg, [" ", st(-1)] as Seg] : []), [g, st(gc)], [" " + activityLabel(s, now), st(activityColor(s, theme))]];
61
+ if (s.activity.startedAt !== null) segs.push([" " + fmtClock(elapsed(s, now)), st(theme.muted)]);
62
+ segs.push(gap);
63
+ return { segs, badge: badge ? [1, segWidth([[badge, undefined]])] : null };
64
+ }
65
+
66
+ const DOT = (theme: Theme): Seg => [" · ", st(theme.dim)];
67
+
68
+ /** the footer's right-hand run: plan-mode / permission markers, then `model · effort X · theme · vX`.
69
+ * `effort` and `theme` are the [cell offset, width] of those words inside the run — frame-hits.ts
70
+ * makes them click zones (effort → prefill `/effort `, theme → next theme like ⌃t). */
71
+ export function footerRight(s: SextantState, theme: Theme): { segs: Seg[]; effort: [number, number] | null; theme: [number, number] } {
72
+ const dot = DOT(theme);
73
+ // the thinking dial rides next to the model ("effort high") — same word as the /effort command
74
+ const effortWord = s.usage.effort ? `effort ${s.usage.effort}` : "";
75
+ const parts = [s.usage.model, effortWord, s.theme, `v${s.version}`].filter(Boolean);
76
+ // one marker for the permission tier: auto wins over accept edits (it already covers writes)
77
+ const perm: Seg[] = s.yolo ? [["auto", st(theme.warn)], dot] : s.acceptEdits ? [["accept edits", st(theme.warn)], dot] : [];
78
+ const lead: Seg[] = [gap, ...(s.mode === "plan" ? [["plan mode", st(theme.warn)] as Seg, dot] : []), ...perm];
79
+ const segs: Seg[] = [...lead, [parts.join(" · "), st(theme.dim)], gap];
80
+ // where each part starts inside the run: the lead, then the parts with their " · " joints (3 cells)
81
+ const starts: number[] = [];
82
+ let off = segWidth(lead);
83
+ for (const p of parts) { starts.push(off); off += segWidth([[p, undefined]]) + 3; }
84
+ const at = (i: number): [number, number] => [starts[i]!, segWidth([[parts[i]!, undefined]])];
85
+ return { segs, effort: effortWord ? at(parts.indexOf(effortWord)) : null, theme: at(parts.length - 2) };
86
+ }
87
+
88
+ export function drawFrame(scr: ScreenLike, L: { frame: Rect }, s: SextantState, theme: Theme, now: number): void {
89
+ const F = L.frame;
90
+ scr.box(F.x, F.y, F.w, F.h, st(theme.frameDim));
91
+ const right = headerRight(s, theme, now).segs;
92
+ const rw = segWidth(right);
93
+ scr.text(F.x + F.w - 2 - rw, F.y, right);
94
+ const dot = DOT(theme);
95
+ const short: Seg[] = [gap, ["◆ ", st(theme.accent)], ["rovecode", st(theme.fg, -1, ATTR.BOLD)], dot, [s.repo.name, st(theme.fg2)], gap];
96
+ const full: Seg[] = s.repo.branch === null ? short
97
+ : [...short.slice(0, -1), dot, [s.repo.branch, st(theme.fg2)], dot, [`${repoModified(s)} modified`, st(theme.fg2)], gap];
98
+ const avail = F.w - 4 - rw - 2;
99
+ scr.text(F.x + 2, F.y, segWidth(full) > avail ? short : full, avail);
100
+ // bottom: mode/yolo markers + `model · effort · theme · vX` right (footerRight); key hints left, whole
101
+ // hints dropped (theme, diff, focus first) rather than clipped mid-word when the row is narrow
102
+ const ver = footerRight(s, theme).segs;
103
+ const vw = segWidth(ver);
104
+ const armed = now < s.escUntil;
105
+ const key = (k: string, what: string, kc = theme.fg2): Seg[] => [[k, st(kc)], [what, st(theme.muted)]];
106
+ const hints: Seg[][] = s.running
107
+ ? [key("esc", armed ? " again to stop" : " stop", armed ? theme.accent : theme.fg2), key("⌃k", " commands")]
108
+ : [key("⏎", " send"), key("tab", " focus"), key("⌃k", " commands"), key("⌃d", " diff"), key("⌃t", " theme"), key("⌃c", " quit")];
109
+ const hintsAvail = F.w - 4 - vw - 2;
110
+ const join = (hs: Seg[][]): Seg[] => [gap, ...hs.flatMap((h, i) => (i ? [[" ", undefined] as Seg, ...h] : h)), gap];
111
+ for (const drop of ["⌃t", "⌃d", "tab", "⌃k"]) {
112
+ if (segWidth(join(hints)) <= hintsAvail) break;
113
+ const i = hints.findIndex((h) => h[0]?.[0] === drop);
114
+ if (i >= 0) hints.splice(i, 1);
115
+ }
116
+ scr.text(F.x + 2, F.y + F.h - 1, join(hints), hintsAvail);
117
+ scr.text(F.x + F.w - 2 - vw, F.y + F.h - 1, ver);
118
+ }
119
+
120
+ const GUIDE: Record<Guide, string> = { bar: "│ ", tee: "├ ", end: "└ ", blank: " " };
121
+ const statusColor = (s: FileStatus, theme: Theme): number => (s === "M" ? theme.warn : s === "A" ? theme.ok : theme.err);
122
+
123
+ /** Files tree: ▾/▸ dirs with GUIDE connectors, M/A/D at the right edge, a diamond spinner beside
124
+ * a touched file, `·` on a collapsed dir with changes, cursor tint when focused; scrolls to the cursor. */
125
+ export function drawFiles(scr: ScreenLike, R: Rect, s: SextantState, theme: Theme, now: number): void {
126
+ const focused = s.focus === "files";
127
+ const B = panel(scr, R, "files", focused, [[String(fileCount(s)), st(theme.muted)]], theme);
128
+ const rows = treeRows(s, now), guides = treeGuides(rows), f = s.files;
129
+ f.cursor = Math.max(0, Math.min(f.cursor, rows.length - 1));
130
+ if (f.cursor < f.scroll) f.scroll = f.cursor;
131
+ if (f.cursor >= f.scroll + B.h) f.scroll = f.cursor - B.h + 1;
132
+ f.scroll = Math.max(0, Math.min(f.scroll, Math.max(0, rows.length - B.h)));
133
+ if (rows.length === 0) { // empty tree: one dim hint (a second line when the panel has room)
134
+ scr.put(B.x, B.y, EMPTY.files[0], st(theme.dim), B.w);
135
+ if (B.h > 1) scr.put(B.x, B.y + 1, EMPTY.files[1], st(theme.dim), B.w);
136
+ return;
137
+ }
138
+ // draw scrollbar before the row loop; status glyphs (M/A/D) are written after and overlay it
139
+ scrollbar(scr, theme, B.x + B.w - 1, B.y, B.h, rows.length, B.h, f.scroll); // scroll-hits.ts rebuilds this geometry for the drag zone
140
+ for (let i = 0; i < B.h; i++) {
141
+ const idx = f.scroll + i, r = rows[idx];
142
+ if (!r) break;
143
+ const y = B.y + i, isCur = idx === f.cursor, isSel = r.path === s.code.file;
144
+ const bg = isCur && focused ? theme.selBg : -1;
145
+ if (bg >= 0) scr.tint(B.x, y, B.w, 1, bg);
146
+ let cx = B.x;
147
+ for (const g of guides[idx] ?? []) cx = scr.put(cx, y, GUIDE[g], st(theme.rule2, bg));
148
+ if (r.dir) cx = scr.put(cx, y, r.expanded ? "▾ " : "▸ ", st(theme.dim, bg));
149
+ const nameSt = r.dir ? st(theme.fg2, bg) : r.status === "D" ? st(theme.dim, bg, ATTR.STRIKE) : isSel ? st(theme.accent, bg, ATTR.BOLD) : r.status ? st(theme.fg, bg) : st(theme.muted, bg);
150
+ cx = scr.clip(cx, y, r.name + (r.dir ? "/" : ""), nameSt, B.x + B.w - 2 - cx - 1);
151
+ if (!r.dir && r.touchedUntil !== undefined && r.touchedUntil > now) scr.put(cx + 1, y, spin(now), st(theme.accent, bg));
152
+ if (!r.dir && r.status) scr.put(B.x + B.w - 1, y, r.status, st(statusColor(r.status, theme), bg));
153
+ else if (r.dir && !r.expanded && r.hasChanges) scr.put(B.x + B.w - 1, y, "·", st(theme.dim, bg));
154
+ }
155
+ }
156
+
157
+ /** the last three live toasts, top-right inside the frame */
158
+ export function drawToasts(scr: ScreenLike, s: SextantState, theme: Theme, now: number): void {
159
+ s.toasts.filter((t) => t.until > now).slice(-3).forEach((t, i) => {
160
+ const col = t.tone === "error" ? theme.err : t.tone === "warn" ? theme.warn : theme.accent;
161
+ const x = scr.w - 4 - [...` ◆ ${t.text} `].length;
162
+ scr.text(x, 2 + i, [[" ◆ ", st(col, theme.bg2)], [t.text + " ", st(theme.fg, theme.bg2)]]);
163
+ });
164
+ }