@pellux/goodvibes-agent 0.1.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 (398) hide show
  1. package/.goodvibes/GOODVIBES.md +35 -0
  2. package/.goodvibes/agents/reviewer.md +48 -0
  3. package/.goodvibes/skills/add-provider/SKILL.md +199 -0
  4. package/CHANGELOG.md +25 -0
  5. package/README.md +74 -0
  6. package/bin/goodvibes-agent.ts +2 -0
  7. package/docs/README.md +23 -0
  8. package/docs/deployment-and-services.md +57 -0
  9. package/docs/getting-started.md +53 -0
  10. package/docs/release-and-publishing.md +46 -0
  11. package/package.json +134 -0
  12. package/scripts/check-bun.sh +20 -0
  13. package/src/audio/player.ts +156 -0
  14. package/src/audio/spoken-turn-controller.ts +203 -0
  15. package/src/audio/spoken-turn-model-routing.ts +117 -0
  16. package/src/audio/spoken-turn-wiring.ts +44 -0
  17. package/src/audio/text-chunker.ts +110 -0
  18. package/src/cli/bundle-command.ts +227 -0
  19. package/src/cli/completion.ts +90 -0
  20. package/src/cli/config-overrides.ts +159 -0
  21. package/src/cli/endpoints.ts +63 -0
  22. package/src/cli/entrypoint.ts +172 -0
  23. package/src/cli/help.ts +299 -0
  24. package/src/cli/index.ts +11 -0
  25. package/src/cli/management-commands.ts +426 -0
  26. package/src/cli/management.ts +744 -0
  27. package/src/cli/network-posture.ts +46 -0
  28. package/src/cli/package-verification.ts +123 -0
  29. package/src/cli/parser.ts +369 -0
  30. package/src/cli/provider-auth-routes.ts +22 -0
  31. package/src/cli/provider-classification.ts +107 -0
  32. package/src/cli/redaction.ts +105 -0
  33. package/src/cli/service-command.ts +26 -0
  34. package/src/cli/service-posture.ts +482 -0
  35. package/src/cli/status.ts +383 -0
  36. package/src/cli/surface-command.ts +247 -0
  37. package/src/cli/tui-startup.ts +32 -0
  38. package/src/cli/types.ts +69 -0
  39. package/src/cli-flags.ts +21 -0
  40. package/src/config/goodvibes-home-audit.ts +465 -0
  41. package/src/config/index.ts +57 -0
  42. package/src/config/provider-model.ts +23 -0
  43. package/src/config/secret-config.ts +119 -0
  44. package/src/config/secrets.ts +71 -0
  45. package/src/config/surface.ts +1 -0
  46. package/src/core/composer-state.ts +61 -0
  47. package/src/core/conversation-rendering.ts +359 -0
  48. package/src/core/conversation.ts +551 -0
  49. package/src/core/history.ts +45 -0
  50. package/src/core/orchestrator.ts +7 -0
  51. package/src/core/system-message-router.ts +171 -0
  52. package/src/daemon/cli.ts +55 -0
  53. package/src/daemon/safe-serve.ts +61 -0
  54. package/src/input/agent-workspace.ts +428 -0
  55. package/src/input/autocomplete.ts +96 -0
  56. package/src/input/bookmark-modal.ts +115 -0
  57. package/src/input/command-args-hint.ts +36 -0
  58. package/src/input/command-registry.ts +329 -0
  59. package/src/input/commands/agent-externalized-tui.ts +73 -0
  60. package/src/input/commands/agent-workspace-runtime.ts +17 -0
  61. package/src/input/commands/branch-runtime.ts +72 -0
  62. package/src/input/commands/cloudflare-runtime.ts +370 -0
  63. package/src/input/commands/config.ts +18 -0
  64. package/src/input/commands/control-room-runtime.ts +255 -0
  65. package/src/input/commands/conversation-runtime.ts +207 -0
  66. package/src/input/commands/discovery-runtime.ts +52 -0
  67. package/src/input/commands/eval.ts +204 -0
  68. package/src/input/commands/experience-runtime.ts +278 -0
  69. package/src/input/commands/guidance-runtime.ts +106 -0
  70. package/src/input/commands/health-runtime.ts +434 -0
  71. package/src/input/commands/hooks-runtime.ts +148 -0
  72. package/src/input/commands/incident-runtime.ts +95 -0
  73. package/src/input/commands/integration-runtime.ts +394 -0
  74. package/src/input/commands/intelligence-runtime.ts +223 -0
  75. package/src/input/commands/knowledge.ts +531 -0
  76. package/src/input/commands/local-auth-runtime.ts +105 -0
  77. package/src/input/commands/local-provider-runtime.ts +170 -0
  78. package/src/input/commands/local-runtime.ts +392 -0
  79. package/src/input/commands/local-setup-review.ts +199 -0
  80. package/src/input/commands/local-setup-transfer.ts +135 -0
  81. package/src/input/commands/local-setup.ts +282 -0
  82. package/src/input/commands/managed-runtime.ts +209 -0
  83. package/src/input/commands/marketplace-runtime.ts +290 -0
  84. package/src/input/commands/mcp-runtime.ts +432 -0
  85. package/src/input/commands/memory-product-runtime.ts +111 -0
  86. package/src/input/commands/memory.ts +151 -0
  87. package/src/input/commands/notify-runtime.ts +83 -0
  88. package/src/input/commands/onboarding-runtime.ts +14 -0
  89. package/src/input/commands/operator-panel-runtime.ts +146 -0
  90. package/src/input/commands/operator-runtime.ts +392 -0
  91. package/src/input/commands/planning-runtime.ts +205 -0
  92. package/src/input/commands/platform-access-runtime.ts +422 -0
  93. package/src/input/commands/platform-services-runtime.ts +246 -0
  94. package/src/input/commands/policy-dispatch.ts +339 -0
  95. package/src/input/commands/policy.ts +17 -0
  96. package/src/input/commands/product-runtime.ts +351 -0
  97. package/src/input/commands/profile-sync-runtime.ts +99 -0
  98. package/src/input/commands/provider-accounts-runtime.ts +113 -0
  99. package/src/input/commands/provider.ts +363 -0
  100. package/src/input/commands/qrcode-runtime.ts +20 -0
  101. package/src/input/commands/quit-shared.ts +162 -0
  102. package/src/input/commands/recall-bundle.ts +132 -0
  103. package/src/input/commands/recall-capture.ts +152 -0
  104. package/src/input/commands/recall-query.ts +229 -0
  105. package/src/input/commands/recall-review.ts +98 -0
  106. package/src/input/commands/recall-shared.ts +22 -0
  107. package/src/input/commands/remote-runtime-pool.ts +106 -0
  108. package/src/input/commands/remote-runtime-setup.ts +199 -0
  109. package/src/input/commands/remote-runtime.ts +431 -0
  110. package/src/input/commands/replay-runtime.ts +18 -0
  111. package/src/input/commands/runtime-services.ts +291 -0
  112. package/src/input/commands/schedule-runtime.ts +91 -0
  113. package/src/input/commands/services-runtime.ts +209 -0
  114. package/src/input/commands/session-content.ts +408 -0
  115. package/src/input/commands/session-workflow.ts +464 -0
  116. package/src/input/commands/session.ts +375 -0
  117. package/src/input/commands/settings-sync-runtime.ts +174 -0
  118. package/src/input/commands/share-runtime.ts +119 -0
  119. package/src/input/commands/shell-core.ts +307 -0
  120. package/src/input/commands/skills-runtime.ts +221 -0
  121. package/src/input/commands/subscription-runtime.ts +434 -0
  122. package/src/input/commands/tasks-runtime.ts +230 -0
  123. package/src/input/commands/teamwork-runtime.ts +339 -0
  124. package/src/input/commands/teleport-runtime.ts +57 -0
  125. package/src/input/commands/tts-runtime.ts +29 -0
  126. package/src/input/commands/work-plan-runtime.ts +169 -0
  127. package/src/input/commands.ts +131 -0
  128. package/src/input/feed-context-factory.ts +254 -0
  129. package/src/input/file-picker.ts +192 -0
  130. package/src/input/handler-command-route.ts +180 -0
  131. package/src/input/handler-content-actions.ts +497 -0
  132. package/src/input/handler-feed-routes.ts +648 -0
  133. package/src/input/handler-feed.ts +452 -0
  134. package/src/input/handler-interactions.ts +281 -0
  135. package/src/input/handler-modal-routes.ts +418 -0
  136. package/src/input/handler-modal-stack.ts +263 -0
  137. package/src/input/handler-modal-token-routes.ts +329 -0
  138. package/src/input/handler-onboarding-cloudflare.ts +391 -0
  139. package/src/input/handler-onboarding.ts +620 -0
  140. package/src/input/handler-picker-routes.ts +472 -0
  141. package/src/input/handler-prompt-buffer.ts +320 -0
  142. package/src/input/handler-shortcuts.ts +213 -0
  143. package/src/input/handler-ui-state.ts +372 -0
  144. package/src/input/handler.ts +729 -0
  145. package/src/input/input-history.ts +297 -0
  146. package/src/input/keybindings.ts +292 -0
  147. package/src/input/mcp-workspace.ts +554 -0
  148. package/src/input/model-picker-provider-filter.ts +28 -0
  149. package/src/input/model-picker-types.ts +137 -0
  150. package/src/input/model-picker.ts +797 -0
  151. package/src/input/onboarding/handler-onboarding-routes.ts +125 -0
  152. package/src/input/onboarding/onboarding-runtime-status.ts +87 -0
  153. package/src/input/onboarding/onboarding-wizard-apply.ts +277 -0
  154. package/src/input/onboarding/onboarding-wizard-cloudflare-step.ts +494 -0
  155. package/src/input/onboarding/onboarding-wizard-cloudflare.ts +204 -0
  156. package/src/input/onboarding/onboarding-wizard-constants.ts +158 -0
  157. package/src/input/onboarding/onboarding-wizard-external-surface-extra-specs.ts +130 -0
  158. package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +762 -0
  159. package/src/input/onboarding/onboarding-wizard-helpers.ts +167 -0
  160. package/src/input/onboarding/onboarding-wizard-rules.ts +256 -0
  161. package/src/input/onboarding/onboarding-wizard-state.ts +365 -0
  162. package/src/input/onboarding/onboarding-wizard-steps.ts +798 -0
  163. package/src/input/onboarding/onboarding-wizard-types.ts +195 -0
  164. package/src/input/onboarding/onboarding-wizard.ts +711 -0
  165. package/src/input/panel-integration-actions.ts +78 -0
  166. package/src/input/profile-picker-modal.ts +222 -0
  167. package/src/input/search.ts +100 -0
  168. package/src/input/selection-modal.ts +163 -0
  169. package/src/input/selection.ts +135 -0
  170. package/src/input/session-picker-modal.ts +136 -0
  171. package/src/input/settings-modal-behavior.ts +37 -0
  172. package/src/input/settings-modal-secrets.ts +41 -0
  173. package/src/input/settings-modal-subscriptions.ts +95 -0
  174. package/src/input/settings-modal-types.ts +91 -0
  175. package/src/input/settings-modal.ts +793 -0
  176. package/src/input/submission-intent.ts +17 -0
  177. package/src/input/submission-router.ts +59 -0
  178. package/src/input/tts-settings-actions.ts +100 -0
  179. package/src/main.ts +792 -0
  180. package/src/mcp/runtime-reload.ts +81 -0
  181. package/src/panels/agent-inspector-panel.ts +521 -0
  182. package/src/panels/agent-inspector-shared.ts +94 -0
  183. package/src/panels/agent-logs-panel.ts +559 -0
  184. package/src/panels/agent-logs-shared.ts +129 -0
  185. package/src/panels/approval-panel.ts +150 -0
  186. package/src/panels/automation-control-panel.ts +212 -0
  187. package/src/panels/base-panel.ts +254 -0
  188. package/src/panels/builtin/agent.ts +117 -0
  189. package/src/panels/builtin/development.ts +31 -0
  190. package/src/panels/builtin/knowledge.ts +26 -0
  191. package/src/panels/builtin/operations.ts +349 -0
  192. package/src/panels/builtin/session.ts +129 -0
  193. package/src/panels/builtin/shared.ts +274 -0
  194. package/src/panels/builtin-panels.ts +23 -0
  195. package/src/panels/cockpit-panel.ts +183 -0
  196. package/src/panels/communication-panel.ts +153 -0
  197. package/src/panels/confirm-state.ts +61 -0
  198. package/src/panels/context-visualizer-panel.ts +204 -0
  199. package/src/panels/control-plane-panel.ts +211 -0
  200. package/src/panels/cost-tracker-panel.ts +444 -0
  201. package/src/panels/debug-panel.ts +432 -0
  202. package/src/panels/diff-panel.ts +520 -0
  203. package/src/panels/docs-panel.ts +283 -0
  204. package/src/panels/eval-panel.ts +399 -0
  205. package/src/panels/file-explorer-panel.ts +584 -0
  206. package/src/panels/file-preview-panel.ts +434 -0
  207. package/src/panels/forensics-panel.ts +364 -0
  208. package/src/panels/git-panel.ts +638 -0
  209. package/src/panels/hooks-panel.ts +239 -0
  210. package/src/panels/incident-review-panel.ts +197 -0
  211. package/src/panels/index.ts +46 -0
  212. package/src/panels/intelligence-panel.ts +176 -0
  213. package/src/panels/knowledge-panel.ts +345 -0
  214. package/src/panels/local-auth-panel.ts +130 -0
  215. package/src/panels/marketplace-panel.ts +212 -0
  216. package/src/panels/memory-panel.ts +225 -0
  217. package/src/panels/ops-control-panel.ts +150 -0
  218. package/src/panels/ops-strategy-panel.ts +235 -0
  219. package/src/panels/orchestration-panel.ts +273 -0
  220. package/src/panels/panel-list-panel.ts +509 -0
  221. package/src/panels/panel-manager.ts +570 -0
  222. package/src/panels/panel-picker.ts +106 -0
  223. package/src/panels/plan-dashboard-panel.ts +274 -0
  224. package/src/panels/plugins-panel.ts +178 -0
  225. package/src/panels/policy-panel.ts +308 -0
  226. package/src/panels/polish.ts +717 -0
  227. package/src/panels/project-planning-panel.ts +711 -0
  228. package/src/panels/provider-account-snapshot.ts +259 -0
  229. package/src/panels/provider-accounts-panel.ts +218 -0
  230. package/src/panels/provider-health-domains.ts +215 -0
  231. package/src/panels/provider-health-panel.ts +727 -0
  232. package/src/panels/provider-health-tracker.ts +115 -0
  233. package/src/panels/provider-stats-panel.ts +366 -0
  234. package/src/panels/qr-panel.ts +182 -0
  235. package/src/panels/remote-panel.ts +449 -0
  236. package/src/panels/routes-panel.ts +178 -0
  237. package/src/panels/sandbox-panel.ts +283 -0
  238. package/src/panels/schedule-panel.ts +329 -0
  239. package/src/panels/scrollable-list-panel.ts +491 -0
  240. package/src/panels/search-focus.ts +32 -0
  241. package/src/panels/security-panel.ts +295 -0
  242. package/src/panels/services-panel.ts +231 -0
  243. package/src/panels/session-browser-panel.ts +400 -0
  244. package/src/panels/session-maintenance.ts +125 -0
  245. package/src/panels/settings-sync-panel.ts +120 -0
  246. package/src/panels/skills-panel.ts +431 -0
  247. package/src/panels/subscription-panel.ts +263 -0
  248. package/src/panels/symbol-outline-panel.ts +486 -0
  249. package/src/panels/system-messages-panel.ts +230 -0
  250. package/src/panels/tasks-panel.ts +399 -0
  251. package/src/panels/thinking-panel.ts +304 -0
  252. package/src/panels/token-budget-panel.ts +475 -0
  253. package/src/panels/tool-inspector-panel.ts +429 -0
  254. package/src/panels/types.ts +54 -0
  255. package/src/panels/watchers-panel.ts +193 -0
  256. package/src/panels/work-plan-panel.ts +175 -0
  257. package/src/panels/worktree-panel.ts +182 -0
  258. package/src/panels/wrfc-panel.ts +609 -0
  259. package/src/permissions/prompt.ts +165 -0
  260. package/src/planning/project-planning-coordinator.ts +543 -0
  261. package/src/plugins/loader.ts +15 -0
  262. package/src/renderer/agent-detail-modal.ts +331 -0
  263. package/src/renderer/agent-workspace.ts +238 -0
  264. package/src/renderer/ansi-sanitize.ts +76 -0
  265. package/src/renderer/autocomplete-overlay.ts +154 -0
  266. package/src/renderer/block-actions.ts +76 -0
  267. package/src/renderer/bookmark-modal.ts +101 -0
  268. package/src/renderer/bottom-bar.ts +58 -0
  269. package/src/renderer/buffer.ts +113 -0
  270. package/src/renderer/code-block.ts +373 -0
  271. package/src/renderer/compositor.ts +283 -0
  272. package/src/renderer/context-inspector.ts +219 -0
  273. package/src/renderer/conversation-layout.ts +67 -0
  274. package/src/renderer/conversation-overlays.ts +140 -0
  275. package/src/renderer/conversation-surface.ts +260 -0
  276. package/src/renderer/diff-view.ts +132 -0
  277. package/src/renderer/diff.ts +130 -0
  278. package/src/renderer/file-picker-overlay.ts +101 -0
  279. package/src/renderer/file-tree.ts +153 -0
  280. package/src/renderer/fullscreen-primitives.ts +130 -0
  281. package/src/renderer/fullscreen-workspace.ts +199 -0
  282. package/src/renderer/git-status.ts +89 -0
  283. package/src/renderer/help-overlay.ts +267 -0
  284. package/src/renderer/history-search-overlay.ts +73 -0
  285. package/src/renderer/layout-engine.ts +97 -0
  286. package/src/renderer/layout.ts +32 -0
  287. package/src/renderer/live-tail-modal.ts +156 -0
  288. package/src/renderer/markdown.ts +635 -0
  289. package/src/renderer/mcp-workspace.ts +237 -0
  290. package/src/renderer/modal-factory.ts +467 -0
  291. package/src/renderer/modal-utils.ts +24 -0
  292. package/src/renderer/model-picker-overlay.ts +473 -0
  293. package/src/renderer/model-workspace.ts +488 -0
  294. package/src/renderer/onboarding/onboarding-wizard.ts +615 -0
  295. package/src/renderer/overlay-box.ts +146 -0
  296. package/src/renderer/overlay-viewport.ts +104 -0
  297. package/src/renderer/panel-composite.ts +158 -0
  298. package/src/renderer/panel-picker-overlay.ts +202 -0
  299. package/src/renderer/panel-tab-bar.ts +69 -0
  300. package/src/renderer/panel-workspace-bar.ts +42 -0
  301. package/src/renderer/process-indicator.ts +96 -0
  302. package/src/renderer/process-modal.ts +656 -0
  303. package/src/renderer/process-summary.ts +67 -0
  304. package/src/renderer/profile-picker-modal.ts +129 -0
  305. package/src/renderer/progress.ts +98 -0
  306. package/src/renderer/qr-renderer.ts +120 -0
  307. package/src/renderer/search-overlay.ts +54 -0
  308. package/src/renderer/selection-modal-overlay.ts +214 -0
  309. package/src/renderer/semantic-diff.ts +369 -0
  310. package/src/renderer/session-picker-modal.ts +127 -0
  311. package/src/renderer/settings-modal-helpers.ts +193 -0
  312. package/src/renderer/settings-modal.ts +537 -0
  313. package/src/renderer/shell-surface.ts +88 -0
  314. package/src/renderer/status-glyphs.ts +21 -0
  315. package/src/renderer/status-token.ts +67 -0
  316. package/src/renderer/surface-layout.ts +101 -0
  317. package/src/renderer/syntax-highlighter.ts +542 -0
  318. package/src/renderer/system-message.ts +83 -0
  319. package/src/renderer/tab-strip.ts +108 -0
  320. package/src/renderer/text-layout.ts +31 -0
  321. package/src/renderer/thinking.ts +17 -0
  322. package/src/renderer/tool-call.ts +234 -0
  323. package/src/renderer/ui-factory.ts +524 -0
  324. package/src/renderer/ui-primitives.ts +96 -0
  325. package/src/runtime/bootstrap-command-context.ts +278 -0
  326. package/src/runtime/bootstrap-command-parts.ts +386 -0
  327. package/src/runtime/bootstrap-core.ts +540 -0
  328. package/src/runtime/bootstrap-hook-bridge.ts +112 -0
  329. package/src/runtime/bootstrap-shell.ts +283 -0
  330. package/src/runtime/bootstrap.ts +575 -0
  331. package/src/runtime/cloudflare-control-plane.ts +349 -0
  332. package/src/runtime/context.ts +142 -0
  333. package/src/runtime/diagnostics/panels/index.ts +24 -0
  334. package/src/runtime/diagnostics/panels/ops.ts +156 -0
  335. package/src/runtime/diagnostics/panels/panel-resources.ts +118 -0
  336. package/src/runtime/diagnostics/panels/policy.ts +177 -0
  337. package/src/runtime/index.ts +662 -0
  338. package/src/runtime/onboarding/apply.ts +642 -0
  339. package/src/runtime/onboarding/derivation.ts +534 -0
  340. package/src/runtime/onboarding/index.ts +7 -0
  341. package/src/runtime/onboarding/markers.ts +148 -0
  342. package/src/runtime/onboarding/snapshot.ts +406 -0
  343. package/src/runtime/onboarding/state.ts +141 -0
  344. package/src/runtime/onboarding/types.ts +404 -0
  345. package/src/runtime/onboarding/verify.ts +171 -0
  346. package/src/runtime/operator-token-cleanup.ts +27 -0
  347. package/src/runtime/perf/panel-contracts.ts +32 -0
  348. package/src/runtime/perf/panel-health-monitor.ts +18 -0
  349. package/src/runtime/sandbox-public-gaps.ts +358 -0
  350. package/src/runtime/services.ts +670 -0
  351. package/src/runtime/store/domains/domain-read-matrix.ts +15 -0
  352. package/src/runtime/store/domains/index.ts +222 -0
  353. package/src/runtime/store/domains/panels.ts +117 -0
  354. package/src/runtime/store/domains/ui-perf.ts +103 -0
  355. package/src/runtime/store/index.ts +305 -0
  356. package/src/runtime/store/selectors/index.ts +359 -0
  357. package/src/runtime/store/state.ts +145 -0
  358. package/src/runtime/surface-feature-flags.ts +65 -0
  359. package/src/runtime/terminal-output-guard.ts +228 -0
  360. package/src/runtime/ui/index.ts +39 -0
  361. package/src/runtime/ui/model-picker/data-provider.ts +182 -0
  362. package/src/runtime/ui/model-picker/health-enrichment.ts +228 -0
  363. package/src/runtime/ui/model-picker/index.ts +59 -0
  364. package/src/runtime/ui/model-picker/types.ts +149 -0
  365. package/src/runtime/ui/provider-health/data-provider.ts +244 -0
  366. package/src/runtime/ui/provider-health/fallback-visualizer.ts +71 -0
  367. package/src/runtime/ui/provider-health/index.ts +46 -0
  368. package/src/runtime/ui/provider-health/types.ts +146 -0
  369. package/src/runtime/ui-events.ts +1 -0
  370. package/src/runtime/ui-read-model-helpers.ts +1 -0
  371. package/src/runtime/ui-read-models-observability-maintenance.ts +1 -0
  372. package/src/runtime/ui-read-models-observability-options.ts +1 -0
  373. package/src/runtime/ui-read-models-observability-remote.ts +1 -0
  374. package/src/runtime/ui-read-models-observability-security.ts +1 -0
  375. package/src/runtime/ui-read-models-observability-system.ts +1 -0
  376. package/src/runtime/ui-read-models-observability.ts +1 -0
  377. package/src/runtime/ui-read-models.ts +61 -0
  378. package/src/runtime/ui-service-queries.ts +1 -0
  379. package/src/runtime/ui-services.ts +190 -0
  380. package/src/scripts/process-messages.ts +42 -0
  381. package/src/shell/blocking-input.ts +98 -0
  382. package/src/shell/service-settings-sync.ts +273 -0
  383. package/src/shell/ui-openers.ts +352 -0
  384. package/src/tools/index.ts +1 -0
  385. package/src/tools/wrfc-agent-guard.ts +49 -0
  386. package/src/types/grid.ts +48 -0
  387. package/src/types/sql-js.d.ts +15 -0
  388. package/src/utils/clipboard.ts +22 -0
  389. package/src/utils/splash-lines.ts +46 -0
  390. package/src/utils/terminal-width.ts +185 -0
  391. package/src/verification/live-verifier.ts +430 -0
  392. package/src/verification/verification-ledger.ts +242 -0
  393. package/src/version.ts +17 -0
  394. package/src/widget/index.ts +2 -0
  395. package/src/widget/types.ts +9 -0
  396. package/src/widget/widget.ts +8 -0
  397. package/src/work-plans/work-plan-store.ts +374 -0
  398. package/tsconfig.json +18 -0
@@ -0,0 +1,717 @@
1
+ import type { Line } from '../types/grid.ts';
2
+ import { createEmptyLine, createStyledCell } from '../types/grid.ts';
3
+ import { getDisplayWidth, wrapText } from '../utils/terminal-width.ts';
4
+ import { getSurfaceContentRows, getTrackedVisibleWindow, getVisibleWindow, type VisibleWindow } from '../renderer/surface-layout.ts';
5
+ import { GLYPHS, UI_TONES } from '../renderer/ui-primitives.ts';
6
+ import { type StatusState, STATE_GLYPHS } from '../renderer/status-glyphs.ts';
7
+
8
+ export interface PanelPalette {
9
+ readonly label: string;
10
+ readonly value: string;
11
+ readonly dim: string;
12
+ readonly info: string;
13
+ readonly good?: string;
14
+ readonly warn?: string;
15
+ readonly bad?: string;
16
+ readonly empty: string;
17
+ readonly header?: string;
18
+ readonly headerBg?: string;
19
+ readonly surfaceBg?: string;
20
+ readonly sectionBg?: string;
21
+ readonly summaryBg?: string;
22
+ readonly inputBg?: string;
23
+ readonly accent?: string;
24
+ readonly selectBg?: string;
25
+ }
26
+
27
+ export const DEFAULT_PANEL_PALETTE: Readonly<Required<PanelPalette>> = {
28
+ header: UI_TONES.fg.primary,
29
+ headerBg: UI_TONES.bg.title,
30
+ label: UI_TONES.fg.muted,
31
+ value: UI_TONES.fg.primary,
32
+ dim: UI_TONES.fg.dim,
33
+ info: UI_TONES.state.info,
34
+ good: UI_TONES.state.good,
35
+ warn: UI_TONES.state.warn,
36
+ bad: UI_TONES.state.bad,
37
+ empty: '#334155',
38
+ surfaceBg: UI_TONES.bg.surface,
39
+ sectionBg: UI_TONES.bg.section,
40
+ summaryBg: UI_TONES.bg.summary,
41
+ inputBg: UI_TONES.bg.input,
42
+ accent: UI_TONES.fg.secondary,
43
+ selectBg: UI_TONES.bg.selected,
44
+ } as const;
45
+
46
+ /**
47
+ * Extend the base panel palette with domain-specific colors.
48
+ *
49
+ * Convention: raw hex colors may only live inside a palette constant declared
50
+ * at the top of a panel file, not inline in render calls.
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * const C = extendPalette(DEFAULT_PANEL_PALETTE, {
55
+ * decision: '#38bdf8',
56
+ * incident: '#ef4444',
57
+ * });
58
+ * ```
59
+ */
60
+ export function extendPalette<T extends Record<string, string>>(
61
+ base: typeof DEFAULT_PANEL_PALETTE,
62
+ extras: T,
63
+ ): typeof DEFAULT_PANEL_PALETTE & T {
64
+ return { ...base, ...extras };
65
+ }
66
+
67
+ export function buildPanelLine(
68
+ width: number,
69
+ segments: Array<StyledPanelSegment | [string, string, string?]>,
70
+ ): Line {
71
+ return buildStyledPanelLine(
72
+ width,
73
+ segments.map((seg) =>
74
+ Array.isArray(seg) ? { text: seg[0], fg: seg[1], bg: seg[2] } : seg,
75
+ ),
76
+ );
77
+ }
78
+
79
+ export interface StyledPanelSegment {
80
+ readonly text: string;
81
+ readonly fg: string;
82
+ readonly bg?: string;
83
+ readonly bold?: boolean;
84
+ readonly dim?: boolean;
85
+ }
86
+
87
+ export function buildSelectablePanelLine(
88
+ width: number,
89
+ segments: ReadonlyArray<StyledPanelSegment>,
90
+ options: { selected?: boolean; selectedBg?: string; fillFg?: string; fillBg?: string; leadingMarker?: string } = {},
91
+ ): Line {
92
+ const selected = options.selected ?? false;
93
+ const selectedBg = selected ? (options.selectedBg ?? DEFAULT_PANEL_PALETTE.selectBg) : '';
94
+ const fillBg = selectedBg || options.fillBg || '';
95
+ const fillFg = options.fillFg ?? '';
96
+ const cells = createEmptyLine(width);
97
+ if (fillBg) {
98
+ for (let col = 0; col < width; col++) {
99
+ cells[col] = createStyledCell(' ', { bg: fillBg, fg: fillFg });
100
+ }
101
+ }
102
+
103
+ let col = 0;
104
+ if (selected && options.leadingMarker) {
105
+ for (const ch of options.leadingMarker) {
106
+ const charWidth = getDisplayWidth(ch);
107
+ if (charWidth <= 0 || col + charWidth > width) break;
108
+ cells[col] = createStyledCell(ch, { fg: DEFAULT_PANEL_PALETTE.info, bg: selectedBg, bold: true });
109
+ if (charWidth > 1 && col + 1 < width) cells[col + 1] = createStyledCell(' ', { fg: DEFAULT_PANEL_PALETTE.info, bg: selectedBg, bold: true });
110
+ col += charWidth;
111
+ }
112
+ }
113
+ for (const segment of segments) {
114
+ const fg = segment.fg;
115
+ const bg = segment.bg ?? fillBg;
116
+ for (const ch of segment.text) {
117
+ const charWidth = getDisplayWidth(ch);
118
+ if (charWidth <= 0) continue;
119
+ if (col + charWidth > width) return cells;
120
+ cells[col] = createStyledCell(ch, {
121
+ fg,
122
+ bg,
123
+ bold: segment.bold ?? false,
124
+ dim: segment.dim ?? false,
125
+ });
126
+ if (charWidth > 1 && col + 1 < width) {
127
+ cells[col + 1] = createStyledCell(' ', {
128
+ fg,
129
+ bg,
130
+ bold: segment.bold ?? false,
131
+ dim: segment.dim ?? false,
132
+ });
133
+ }
134
+ col += charWidth;
135
+ }
136
+ }
137
+
138
+ while (col < width) {
139
+ cells[col++] = createStyledCell(' ', { bg: fillBg, fg: fillFg });
140
+ }
141
+ return cells;
142
+ }
143
+
144
+ export function buildStyledPanelLine(
145
+ width: number,
146
+ segments: ReadonlyArray<StyledPanelSegment>,
147
+ options: { fillBg?: string; fillFg?: string } = {},
148
+ ): Line {
149
+ return buildSelectablePanelLine(width, segments, options);
150
+ }
151
+
152
+ export function buildGuidanceLine(
153
+ width: number,
154
+ command: string,
155
+ summary: string,
156
+ palette: PanelPalette,
157
+ ): Line {
158
+ return buildPanelLine(width, [
159
+ [` ${GLYPHS.status.info} `, palette.info],
160
+ [command, palette.info],
161
+ [' - ', palette.dim],
162
+ [summary, palette.dim],
163
+ ]);
164
+ }
165
+
166
+ export function buildPanelTitle(
167
+ width: number,
168
+ title: string,
169
+ palette: PanelPalette,
170
+ ): Line {
171
+ const titleText = fitDisplayText(` ${title}`, width);
172
+ return buildStyledPanelLine(width, [
173
+ { text: titleText, fg: palette.header ?? palette.accent ?? palette.value, bg: palette.headerBg, bold: true },
174
+ ], { fillBg: palette.headerBg });
175
+ }
176
+
177
+ export function buildBodyText(
178
+ width: number,
179
+ text: string,
180
+ palette: PanelPalette,
181
+ fg: string = palette.value,
182
+ ): Line[] {
183
+ const wrapped = wrapText(text, Math.max(10, width - 2));
184
+ return wrapped.map((line) => buildStyledPanelLine(width, [{ text: ` ${line}`, fg, bg: palette.surfaceBg }], { fillBg: palette.surfaceBg }));
185
+ }
186
+
187
+ export function buildSectionHeader(
188
+ width: number,
189
+ title: string,
190
+ palette: PanelPalette,
191
+ ): Line {
192
+ const prefix = `${GLYPHS.status.dualPane} ${title}`;
193
+ const divider = GLYPHS.frame.horizontal.repeat(Math.max(0, width - getDisplayWidth(prefix) - 2));
194
+ return buildStyledPanelLine(width, [
195
+ { text: ' ', fg: palette.label, bg: palette.sectionBg },
196
+ { text: prefix, fg: palette.label, bg: palette.sectionBg, bold: true },
197
+ { text: ' ', fg: palette.dim, bg: palette.sectionBg },
198
+ { text: divider, fg: palette.dim, bg: palette.sectionBg },
199
+ ], { fillBg: palette.sectionBg });
200
+ }
201
+
202
+ export function buildKeyValueLine(
203
+ width: number,
204
+ entries: ReadonlyArray<{ label: string; value: string; valueColor?: string }>,
205
+ palette: PanelPalette,
206
+ ): Line {
207
+ const segments: Array<[string, string, string?]> = [];
208
+ for (const entry of entries) {
209
+ segments.push([' ', palette.label]);
210
+ segments.push([`${entry.label} `, palette.label]);
211
+ segments.push([entry.value, entry.valueColor ?? palette.value]);
212
+ segments.push([' ', palette.dim]);
213
+ }
214
+ return buildStyledPanelLine(width, segments.map(([text, fg, bg]) => ({ text, fg, bg: bg ?? palette.summaryBg })), { fillBg: palette.summaryBg });
215
+ }
216
+
217
+ export function buildShortcutLine(
218
+ width: number,
219
+ keys: string,
220
+ summary: string,
221
+ palette: PanelPalette,
222
+ ): Line {
223
+ return buildPanelLine(width, [
224
+ [' ', palette.label],
225
+ [keys, palette.info],
226
+ [' ', palette.dim],
227
+ [summary, palette.dim],
228
+ ]);
229
+ }
230
+
231
+ export function buildPanelListRow(
232
+ width: number,
233
+ segments: ReadonlyArray<StyledPanelSegment>,
234
+ palette: PanelPalette,
235
+ options: {
236
+ readonly selected?: boolean;
237
+ readonly selectedBg?: string;
238
+ readonly marker?: string;
239
+ readonly markerColor?: string;
240
+ readonly fillBg?: string;
241
+ } = {},
242
+ ): Line {
243
+ const selected = options.selected ?? false;
244
+ const selectedBg = options.selectedBg ?? palette.selectBg;
245
+ const fillBg = selected ? selectedBg : options.fillBg;
246
+ const marker = selected ? `${options.marker ?? GLYPHS.navigation.selected} ` : ' ';
247
+ const markerFg = selected ? (options.markerColor ?? palette.info) : palette.dim;
248
+ return buildStyledPanelLine(width, [
249
+ { text: marker, fg: markerFg, bg: fillBg, bold: selected },
250
+ ...segments.map((segment) => ({
251
+ ...segment,
252
+ bg: segment.bg ?? fillBg,
253
+ })),
254
+ ], { fillBg });
255
+ }
256
+
257
+ export function buildSearchInputLine(
258
+ width: number,
259
+ label: string,
260
+ value: string,
261
+ palette: PanelPalette,
262
+ options: { active?: boolean; bg?: string; emptyLabel?: string; valueColor?: string } = {},
263
+ ): Line {
264
+ const active = options.active ?? false;
265
+ const normalizedValue = active && value.endsWith('_')
266
+ ? `${value.slice(0, -1)}${GLYPHS.surface.cursor}`
267
+ : value;
268
+ const hasValue = normalizedValue.trim().length > 0;
269
+ const content = hasValue ? normalizedValue : (options.emptyLabel ?? '(none)');
270
+ const bg = options.bg ?? (active ? palette.inputBg : palette.sectionBg);
271
+ const fg = active
272
+ ? options.valueColor ?? palette.info
273
+ : hasValue
274
+ ? options.valueColor ?? palette.value
275
+ : palette.dim;
276
+ return buildStyledPanelLine(width, [
277
+ { text: ' ', fg: palette.label, bg },
278
+ { text: `${label}`, fg: palette.label, bg },
279
+ { text: content, fg, bg, bold: active },
280
+ ], { fillBg: bg });
281
+ }
282
+
283
+ /**
284
+ * Build a status pill segment (glyph + label) for use in buildPanelLine.
285
+ *
286
+ * Returns StyledPanelSegment[] — spread directly into a buildPanelLine segments
287
+ * array:
288
+ * buildPanelLine(width, [[' count ', C.label], ...buildStatusPill('bad', '3')])
289
+ *
290
+ * Convention: raw hex colors may only live inside a palette constant declared at
291
+ * the top of a panel file; buildStatusPill derives its color from the palette.
292
+ */
293
+ export function buildStatusPill(
294
+ state: StatusState,
295
+ label: string,
296
+ opts?: { glyph?: string; bg?: string; count?: number },
297
+ ): StyledPanelSegment[] {
298
+ const glyph = opts?.glyph ?? STATE_GLYPHS[state];
299
+ const color = state === 'good' ? DEFAULT_PANEL_PALETTE.good
300
+ : state === 'warn' ? DEFAULT_PANEL_PALETTE.warn
301
+ : state === 'bad' ? DEFAULT_PANEL_PALETTE.bad
302
+ : DEFAULT_PANEL_PALETTE.info;
303
+ const bg = opts?.bg;
304
+ const text = opts?.count !== undefined ? `${glyph} ${label} (${opts.count})` : `${glyph} ${label}`;
305
+ return [{ text, fg: color, bg }];
306
+ }
307
+
308
+ export function buildStatPill(
309
+ label: string,
310
+ value: string,
311
+ labelColor: string,
312
+ valueColor: string,
313
+ bg = '',
314
+ ): Array<[string, string, string?]> {
315
+ return [
316
+ [' ', labelColor, bg],
317
+ [label, labelColor, bg],
318
+ [' ', labelColor, bg],
319
+ [value, valueColor, bg],
320
+ [' ', labelColor, bg],
321
+ ];
322
+ }
323
+
324
+ export function buildMeterLine(
325
+ width: number,
326
+ filled: number,
327
+ total: number,
328
+ colors: { filled: string; empty: string; label?: string },
329
+ options: { prefix?: string; suffix?: string; filledChar?: string; emptyChar?: string } = {},
330
+ ): Line {
331
+ const prefix = options.prefix ?? ' ';
332
+ const suffix = options.suffix ?? ' ';
333
+ const emptyChar = options.emptyChar ?? GLYPHS.meter.empty;
334
+ const normalizedFilledChar = options.filledChar ?? GLYPHS.meter.filled;
335
+ const barWidth = Math.max(1, total);
336
+ const clampedFilled = Math.max(0, Math.min(barWidth, filled));
337
+ const segments: StyledPanelSegment[] = [{ text: prefix, fg: colors.label ?? colors.filled }];
338
+ if (clampedFilled > 0) {
339
+ segments.push({ text: normalizedFilledChar.repeat(clampedFilled), fg: colors.filled });
340
+ }
341
+ if (clampedFilled < barWidth) {
342
+ segments.push({ text: emptyChar.repeat(barWidth - clampedFilled), fg: colors.empty });
343
+ }
344
+ segments.push({ text: suffix, fg: colors.label ?? colors.filled });
345
+ return buildStyledPanelLine(width, segments);
346
+ }
347
+
348
+ export function buildEmptyState(
349
+ width: number,
350
+ title: string,
351
+ body: string,
352
+ actions: ReadonlyArray<{ command: string; summary: string }>,
353
+ palette: PanelPalette,
354
+ ): Line[] {
355
+ const lines: Line[] = [];
356
+ lines.push(buildPanelLine(width, [[` ${title}`, palette.empty]]));
357
+ lines.push(...buildBodyText(width, body, palette, palette.dim));
358
+ if (actions.length > 0) {
359
+ lines.push(buildPanelLine(width, [[' Suggested next steps', palette.label]]));
360
+ for (const action of actions) {
361
+ lines.push(buildGuidanceLine(width, action.command, action.summary, palette));
362
+ }
363
+ }
364
+ return lines;
365
+ }
366
+
367
+ export function buildSummaryBlock(
368
+ width: number,
369
+ title: string,
370
+ rows: readonly Line[],
371
+ palette: PanelPalette,
372
+ ): Line[] {
373
+ const lines: Line[] = [
374
+ buildStyledPanelLine(width, [
375
+ { text: ` ${GLYPHS.status.active} ${title}`, fg: palette.header ?? palette.value, bg: palette.summaryBg, bold: true },
376
+ ], { fillBg: palette.summaryBg }),
377
+ ];
378
+ for (const row of rows) lines.push(row);
379
+ return lines;
380
+ }
381
+
382
+ export function buildDetailBlock(
383
+ width: number,
384
+ title: string,
385
+ rows: readonly Line[],
386
+ palette: PanelPalette,
387
+ ): Line[] {
388
+ return [
389
+ buildStyledPanelLine(width, [
390
+ { text: ` ${GLYPHS.status.review} ${title}`, fg: palette.header ?? palette.value, bg: palette.sectionBg, bold: true },
391
+ ], { fillBg: palette.sectionBg }),
392
+ ...rows.map((row) => row.map((cell) => (
393
+ cell.bg
394
+ ? cell
395
+ : createStyledCell(cell.char, {
396
+ fg: cell.fg,
397
+ bg: palette.surfaceBg,
398
+ bold: cell.bold,
399
+ dim: cell.dim,
400
+ underline: cell.underline,
401
+ })
402
+ ))),
403
+ ];
404
+ }
405
+
406
+ function fitDisplayText(text: string, width: number): string {
407
+ let col = 0;
408
+ let out = '';
409
+ for (const ch of text) {
410
+ const w = getDisplayWidth(ch);
411
+ if (w <= 0 || col + w > width) break;
412
+ out += ch;
413
+ col += w;
414
+ }
415
+ return out;
416
+ }
417
+
418
+ export interface PanelWorkspaceSection {
419
+ readonly title?: string;
420
+ readonly lines: readonly Line[];
421
+ }
422
+
423
+ export interface PrimaryScrollablePanelSectionConfig {
424
+ readonly title?: string;
425
+ readonly fixedLines?: readonly Line[];
426
+ readonly scrollableLines: readonly Line[];
427
+ readonly selectedIndex: number;
428
+ readonly scrollOffset: number;
429
+ readonly guardRows?: number;
430
+ readonly minRows?: number;
431
+ readonly appendWindowSummary?: {
432
+ readonly dimColor?: string;
433
+ readonly formatter?: (window: VisibleWindow) => Line;
434
+ };
435
+ }
436
+
437
+ export interface ScrollablePanelSectionConfig {
438
+ readonly title?: string;
439
+ readonly fixedLines?: readonly Line[];
440
+ readonly scrollableLines: readonly Line[];
441
+ readonly scrollOffset: number;
442
+ readonly selectedIndex?: number;
443
+ readonly guardRows?: number;
444
+ readonly minRows?: number;
445
+ readonly appendWindowSummary?: {
446
+ readonly dimColor?: string;
447
+ readonly formatter?: (window: VisibleWindow) => Line;
448
+ };
449
+ }
450
+
451
+ export interface StackedScrollablePanelSectionConfig extends ScrollablePanelSectionConfig {
452
+ readonly weight?: number;
453
+ }
454
+
455
+ export interface PanelWorkspaceConfig {
456
+ readonly title: string;
457
+ readonly intro?: string;
458
+ readonly sections: readonly PanelWorkspaceSection[];
459
+ readonly footerLines?: readonly Line[];
460
+ readonly palette: PanelPalette;
461
+ }
462
+
463
+ function getPanelWorkspaceIntroRows(
464
+ width: number,
465
+ intro: string | undefined,
466
+ palette: PanelPalette,
467
+ ): number {
468
+ return intro ? buildBodyText(width, intro, palette, palette.dim).length : 0;
469
+ }
470
+
471
+ export function getPanelWorkspaceContentBudget(
472
+ width: number,
473
+ height: number,
474
+ config: Pick<PanelWorkspaceConfig, 'intro' | 'footerLines' | 'palette'>,
475
+ ): number {
476
+ const footerLines = [...(config.footerLines ?? [])];
477
+ const chromeRows = 1 + getPanelWorkspaceIntroRows(width, config.intro, config.palette) + footerLines.length;
478
+ return Math.max(
479
+ 1,
480
+ getSurfaceContentRows({
481
+ viewportHeight: height,
482
+ chromeRows,
483
+ minContentRows: 4,
484
+ maxContentRows: Math.max(4, height - chromeRows),
485
+ minTotalRows: 8,
486
+ maxTotalRows: Math.max(8, height),
487
+ targetRatio: 1,
488
+ }),
489
+ );
490
+ }
491
+
492
+ export function getPanelWorkspaceSectionRows(section: PanelWorkspaceSection): number {
493
+ return (section.title ? 1 : 0) + section.lines.length;
494
+ }
495
+
496
+ export function getPanelScrollableSectionBudget(
497
+ width: number,
498
+ height: number,
499
+ options: {
500
+ readonly intro?: string;
501
+ readonly footerLines?: readonly Line[];
502
+ readonly palette: PanelPalette;
503
+ readonly beforeSections?: readonly PanelWorkspaceSection[];
504
+ readonly currentSectionTitle?: string;
505
+ readonly currentSectionFixedRows?: number;
506
+ readonly afterSections?: readonly PanelWorkspaceSection[];
507
+ readonly minRows?: number;
508
+ },
509
+ ): number {
510
+ const contentBudget = getPanelWorkspaceContentBudget(width, height, options);
511
+ const beforeRows = (options.beforeSections ?? []).reduce((sum, section) => sum + getPanelWorkspaceSectionRows(section), 0);
512
+ const afterRows = (options.afterSections ?? []).reduce((sum, section) => sum + getPanelWorkspaceSectionRows(section), 0);
513
+ const currentHeaderRows = options.currentSectionTitle ? 1 : 0;
514
+ const currentFixedRows = Math.max(0, options.currentSectionFixedRows ?? 0);
515
+ return Math.max(options.minRows ?? 1, contentBudget - beforeRows - currentHeaderRows - currentFixedRows - afterRows);
516
+ }
517
+
518
+ export function resolvePrimaryScrollableSection(
519
+ width: number,
520
+ height: number,
521
+ options: {
522
+ readonly intro?: string;
523
+ readonly footerLines?: readonly Line[];
524
+ readonly palette: PanelPalette;
525
+ readonly beforeSections?: readonly PanelWorkspaceSection[];
526
+ readonly section: PrimaryScrollablePanelSectionConfig;
527
+ readonly afterSections?: readonly PanelWorkspaceSection[];
528
+ },
529
+ ): {
530
+ readonly section: PanelWorkspaceSection;
531
+ readonly scrollOffset: number;
532
+ readonly window: VisibleWindow;
533
+ } {
534
+ return resolveScrollablePanelSection(width, height, {
535
+ intro: options.intro,
536
+ footerLines: options.footerLines,
537
+ palette: options.palette,
538
+ beforeSections: options.beforeSections,
539
+ section: options.section,
540
+ afterSections: options.afterSections,
541
+ });
542
+ }
543
+
544
+ export function resolveScrollablePanelSection(
545
+ width: number,
546
+ height: number,
547
+ options: {
548
+ readonly intro?: string;
549
+ readonly footerLines?: readonly Line[];
550
+ readonly palette: PanelPalette;
551
+ readonly beforeSections?: readonly PanelWorkspaceSection[];
552
+ readonly section: ScrollablePanelSectionConfig;
553
+ readonly afterSections?: readonly PanelWorkspaceSection[];
554
+ },
555
+ ): {
556
+ readonly section: PanelWorkspaceSection;
557
+ readonly scrollOffset: number;
558
+ readonly window: VisibleWindow;
559
+ } {
560
+ const fixedLines = [...(options.section.fixedLines ?? [])];
561
+ const budget = getPanelScrollableSectionBudget(width, height, {
562
+ intro: options.intro,
563
+ footerLines: options.footerLines,
564
+ palette: options.palette,
565
+ beforeSections: options.beforeSections,
566
+ currentSectionTitle: options.section.title,
567
+ currentSectionFixedRows: fixedLines.length,
568
+ afterSections: options.afterSections,
569
+ minRows: options.section.minRows ?? 1,
570
+ });
571
+ const window = options.section.selectedIndex === undefined
572
+ ? getVisibleWindow(
573
+ options.section.scrollableLines.length,
574
+ options.section.scrollOffset,
575
+ budget,
576
+ )
577
+ : getTrackedVisibleWindow(
578
+ options.section.scrollableLines.length,
579
+ options.section.selectedIndex,
580
+ budget,
581
+ options.section.scrollOffset,
582
+ options.section.guardRows ?? 1,
583
+ );
584
+ const lines = [
585
+ ...fixedLines,
586
+ ...options.section.scrollableLines.slice(window.start, window.end),
587
+ ];
588
+ if (options.section.appendWindowSummary && options.section.scrollableLines.length > window.count) {
589
+ const summary = options.section.appendWindowSummary.formatter
590
+ ? options.section.appendWindowSummary.formatter(window)
591
+ : buildPanelLine(width, [[` showing ${window.start + 1}-${window.end} of ${window.total}`, options.section.appendWindowSummary.dimColor ?? options.palette.dim]]);
592
+ lines.push(summary);
593
+ }
594
+ return {
595
+ section: {
596
+ title: options.section.title,
597
+ lines,
598
+ },
599
+ scrollOffset: window.start,
600
+ window,
601
+ };
602
+ }
603
+
604
+ export function resolveStackedScrollableSections(
605
+ width: number,
606
+ height: number,
607
+ options: {
608
+ readonly intro?: string;
609
+ readonly footerLines?: readonly Line[];
610
+ readonly palette: PanelPalette;
611
+ readonly beforeSections?: readonly PanelWorkspaceSection[];
612
+ readonly sections: readonly StackedScrollablePanelSectionConfig[];
613
+ readonly afterSections?: readonly PanelWorkspaceSection[];
614
+ },
615
+ ): ReadonlyArray<{
616
+ readonly section: PanelWorkspaceSection;
617
+ readonly scrollOffset: number;
618
+ readonly window: VisibleWindow;
619
+ }> {
620
+ if (options.sections.length === 0) return [];
621
+ const contentBudget = getPanelWorkspaceContentBudget(width, height, options);
622
+ const beforeRows = (options.beforeSections ?? []).reduce((sum, section) => sum + getPanelWorkspaceSectionRows(section), 0);
623
+ const afterRows = (options.afterSections ?? []).reduce((sum, section) => sum + getPanelWorkspaceSectionRows(section), 0);
624
+ const fixedChrome = options.sections.reduce((sum, section) => (
625
+ sum
626
+ + (section.title ? 1 : 0)
627
+ + (section.fixedLines?.length ?? 0)
628
+ ), 0);
629
+ const allocatable = Math.max(1, contentBudget - beforeRows - afterRows - fixedChrome);
630
+ const mins = options.sections.map((section) => Math.max(1, section.minRows ?? 1));
631
+ const weights = options.sections.map((section) => Math.max(1, section.weight ?? 1));
632
+ const totalMin = mins.reduce((sum, value) => sum + value, 0);
633
+ const budgets = [...mins];
634
+ let remaining = Math.max(0, allocatable - totalMin);
635
+ const totalWeight = weights.reduce((sum, value) => sum + value, 0);
636
+ for (let i = 0; i < budgets.length; i++) {
637
+ if (remaining <= 0) break;
638
+ const share = i === budgets.length - 1
639
+ ? remaining
640
+ : Math.floor((remaining * weights[i]!) / totalWeight);
641
+ budgets[i]! += share;
642
+ remaining -= share;
643
+ }
644
+ let cursor = 0;
645
+ while (remaining > 0 && budgets.length > 0) {
646
+ budgets[cursor % budgets.length]! += 1;
647
+ remaining--;
648
+ cursor++;
649
+ }
650
+
651
+ return options.sections.map((section, index) => {
652
+ const fixedLines = [...(section.fixedLines ?? [])];
653
+ const window = section.selectedIndex === undefined
654
+ ? getVisibleWindow(section.scrollableLines.length, section.scrollOffset, budgets[index]!)
655
+ : getTrackedVisibleWindow(
656
+ section.scrollableLines.length,
657
+ section.selectedIndex,
658
+ budgets[index]!,
659
+ section.scrollOffset,
660
+ section.guardRows ?? 1,
661
+ );
662
+ const lines = [
663
+ ...fixedLines,
664
+ ...section.scrollableLines.slice(window.start, window.end),
665
+ ];
666
+ if (section.appendWindowSummary && section.scrollableLines.length > window.count) {
667
+ const summary = section.appendWindowSummary.formatter
668
+ ? section.appendWindowSummary.formatter(window)
669
+ : buildPanelLine(width, [[` showing ${window.start + 1}-${window.end} of ${window.total}`, section.appendWindowSummary.dimColor ?? options.palette.dim]]);
670
+ lines.push(summary);
671
+ }
672
+ return {
673
+ section: {
674
+ title: section.title,
675
+ lines,
676
+ },
677
+ scrollOffset: window.start,
678
+ window,
679
+ };
680
+ });
681
+ }
682
+
683
+ export function buildPanelWorkspace(
684
+ width: number,
685
+ height: number,
686
+ config: PanelWorkspaceConfig,
687
+ ): Line[] {
688
+ const lines: Line[] = [];
689
+ lines.push(buildPanelTitle(width, config.title, config.palette));
690
+ if (config.intro) {
691
+ lines.push(...buildBodyText(width, config.intro, config.palette, config.palette.dim));
692
+ }
693
+ const footerLines = [...(config.footerLines ?? [])];
694
+ const contentBudget = getPanelWorkspaceContentBudget(width, height, config);
695
+
696
+ let consumed = 0;
697
+ for (let sectionIndex = 0; sectionIndex < config.sections.length; sectionIndex++) {
698
+ const section = config.sections[sectionIndex]!;
699
+ if (consumed >= contentBudget) break;
700
+ if (section.title) {
701
+ if (consumed >= contentBudget) break;
702
+ lines.push(buildSectionHeader(width, section.title, config.palette));
703
+ consumed++;
704
+ }
705
+ for (const line of section.lines) {
706
+ if (consumed >= contentBudget) break;
707
+ lines.push(line);
708
+ consumed++;
709
+ }
710
+ }
711
+
712
+ const contentAndChromeTarget = Math.max(0, height - footerLines.length);
713
+ while (lines.length < contentAndChromeTarget) lines.push(createEmptyLine(width));
714
+ lines.push(...footerLines);
715
+ while (lines.length < height) lines.push(createEmptyLine(width));
716
+ return lines.slice(0, height);
717
+ }