@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,524 @@
1
+ import { type Line, type Cell, createEmptyLine, createEmptyCell } from '../types/grid.ts';
2
+ import { LAYOUT } from './layout.ts';
3
+ import { VERSION } from '../version.ts';
4
+ import { fitDisplay, getDisplayWidth, truncateDisplay, wrapText, interpolateColor } from '../utils/terminal-width.ts';
5
+ import type { GitHeaderInfo } from './git-status.ts';
6
+ import { renderConversationFragment, renderConversationStatusLine, type ConversationStatusSegment } from './conversation-surface.ts';
7
+ import { GLYPHS } from './ui-primitives.ts';
8
+
9
+ /** Number of frames before the animated gradient completes one full cycle. */
10
+ const GRADIENT_CYCLE_FRAMES = 50;
11
+ /** Number of frames before rotating to the next thinking phrase (~30 seconds at 80ms/frame). */
12
+ const PHRASE_ROTATION_FRAMES = 375;
13
+
14
+ /** Build the git segment string and its display width. Single source of truth for header layout. */
15
+ function buildGitSegment(gitInfo: GitHeaderInfo): { text: string; width: number } {
16
+ const branch = ` git:${gitInfo.branch}`;
17
+ if (gitInfo.dirty) {
18
+ const text = `${branch} * `;
19
+ return { text, width: getDisplayWidth(text) };
20
+ }
21
+ if (gitInfo.ahead > 0 || gitInfo.behind > 0) {
22
+ const arrows = (gitInfo.ahead > 0 ? ` +${gitInfo.ahead}` : '') + (gitInfo.behind > 0 ? ` -${gitInfo.behind}` : '');
23
+ const text = `${branch}${arrows} `;
24
+ return { text, width: getDisplayWidth(text) };
25
+ }
26
+ const text = `${branch} `;
27
+ return { text, width: getDisplayWidth(text) };
28
+ }
29
+
30
+ /** Format a number: up to 999, then 1.0k, 1.0M, 1.0B, 1.0T */
31
+ function fmtNum(n: number): string {
32
+ if (n < 1000) return String(n);
33
+ if (n < 1_000_000) return (n / 1000).toFixed(1) + 'k';
34
+ if (n < 1_000_000_000) return (n / 1_000_000).toFixed(1) + 'M';
35
+ if (n < 1_000_000_000_000) return (n / 1_000_000_000).toFixed(1) + 'B';
36
+ return (n / 1_000_000_000_000).toFixed(1) + 'T';
37
+ }
38
+
39
+ /**
40
+ * UIFactory - Generates standard UI fragments without needing Ink/React overhead.
41
+ */
42
+ export class UIFactory {
43
+ public static createHeader(width: number, model: string, provider: string, title?: string, gitInfo?: GitHeaderInfo): Line[] {
44
+ const lines: Line[] = [];
45
+ const CYAN = '#00ffff';
46
+ const GREY = '244';
47
+ const TITLE_COLOR = '250';
48
+ const brand = ` GoodVibes `;
49
+ const ver = `v${VERSION} `;
50
+ const stats = ` ${model} `;
51
+ const prov = `(${provider}) `;
52
+ const line = createEmptyLine(width);
53
+ let curX = 0;
54
+ for (const char of brand) { line[curX++] = { char, fg: CYAN, bg: '', bold: true, dim: false, underline: false, italic: false, strikethrough: false }; }
55
+ for (const char of ver) { line[curX++] = { char, fg: GREY, bg: '', bold: false, dim: true, underline: false, italic: false, strikethrough: false }; }
56
+ // Optional conversation title — shown after brand/ver, truncated to fit
57
+ if (title) {
58
+ const titleStr = `│ ${title} `;
59
+ // Reserve space for git info (if present) + model/provider on the right
60
+ const gitReserved = gitInfo ? buildGitSegment(gitInfo).width : 0;
61
+ const rightReserved = getDisplayWidth(stats + prov) + gitReserved;
62
+ const maxTitleW = width - curX - rightReserved - 1;
63
+ let displayTitle: string;
64
+ if (getDisplayWidth(titleStr) <= maxTitleW) {
65
+ displayTitle = titleStr;
66
+ } else {
67
+ let truncated = '';
68
+ let w = 0;
69
+ for (const ch of titleStr) {
70
+ const cw = getDisplayWidth(ch);
71
+ if (w + cw > maxTitleW - 3) { truncated += '...'; break; }
72
+ truncated += ch;
73
+ w += cw;
74
+ }
75
+ displayTitle = truncated;
76
+ }
77
+ for (const char of displayTitle) { if (curX < width) line[curX++] = { char, fg: TITLE_COLOR, bg: '', bold: false, dim: true, underline: false, italic: false, strikethrough: false }; }
78
+ }
79
+ // Build git info segment
80
+ let gitStr = '';
81
+ let gitFg = '238';
82
+ if (gitInfo) {
83
+ gitStr = buildGitSegment(gitInfo).text;
84
+ if (gitInfo.dirty || gitInfo.ahead > 0 || gitInfo.behind > 0) {
85
+ gitFg = '220'; // yellow when dirty or out-of-sync
86
+ }
87
+ }
88
+ const rightSideText = stats + prov;
89
+ const rightSideW = getDisplayWidth(rightSideText) + getDisplayWidth(gitStr);
90
+ let rightX = width - rightSideW;
91
+ for (const char of gitStr) { if (rightX >= 0 && rightX < width) line[rightX++] = { char, fg: gitFg, bg: '', bold: false, dim: !gitInfo?.dirty && !(gitInfo?.ahead || gitInfo?.behind), underline: false, italic: false, strikethrough: false }; }
92
+ for (const char of stats) { if (rightX < width) line[rightX++] = { char, fg: CYAN, bg: '', bold: true, dim: false, underline: false, italic: false, strikethrough: false }; }
93
+ for (const char of prov) { if (rightX < width) line[rightX++] = { char, fg: GREY, bg: '', bold: false, dim: true, underline: false, italic: false, strikethrough: false }; }
94
+ lines.push(line);
95
+ lines.push(this.stringToLine('━'.repeat(width), width, { fg: '244' }));
96
+ return lines;
97
+ }
98
+
99
+ /**
100
+ * createMessageBar - Renders a historical user message.
101
+ * Logic: Calculates the longest line to create a "hugging" block.
102
+ */
103
+ public static createMessageBar(
104
+ width: number, text: string,
105
+ bgColor = '#2a2a2a', textColor = '252', prefixStr = ' › ',
106
+ strikethrough = false
107
+ ): Line[] {
108
+ return renderConversationFragment(text, width, {
109
+ prefix: prefixStr,
110
+ prefixFg: '135',
111
+ text: textColor,
112
+ bodyBg: bgColor,
113
+ strikethrough,
114
+ });
115
+ }
116
+
117
+ /**
118
+ * createQueuedMessageFragment - Renders a dimmed message bar for queued prompts.
119
+ */
120
+ public static createQueuedMessageFragment(width: number, text: string): Line[] {
121
+ return renderConversationFragment(text, width, {
122
+ prefix: ' (...) ',
123
+ prefixFg: '135',
124
+ text: '240',
125
+ bodyBg: '#1a1a1a',
126
+ dim: true,
127
+ });
128
+ }
129
+
130
+ public static createFooter(
131
+ width: number,
132
+ prompt: string,
133
+ usage: { up: number; down: number; max?: number },
134
+ showExitNotice: boolean,
135
+ lastCopyTime: number,
136
+ model?: string,
137
+ toolCount?: number,
138
+ cursorPos?: number,
139
+ workingDir?: string,
140
+ provider?: string,
141
+ contextWindow?: number,
142
+ compactThreshold?: number,
143
+ dangerMode?: boolean,
144
+ lastInputTokens?: number,
145
+ commandArgsHint?: string,
146
+ hitlMode?: string,
147
+ promptFocused: boolean = true,
148
+ composerMode?: string,
149
+ composerStatus?: string,
150
+ composerFlags?: readonly string[],
151
+ composerPendingRisk?: 'none' | 'approval-wait' | 'shell' | 'command' | 'remote',
152
+ ): Line[] {
153
+ const lines: Line[] = [];
154
+ const promptLines = prompt.split('\n');
155
+ const TEXT_COLOR = promptFocused ? '252' : '246';
156
+ const BG_COLOR = promptFocused ? '#2a2a2a' : '#1f2430';
157
+ const BORDER_COLOR = BG_COLOR;
158
+ const boxMargin = 2; const boxWidth = width - (boxMargin * 2); const boxStartX = boxMargin;
159
+ const createBaseLine = () => {
160
+ const l = createEmptyLine(width);
161
+ for (let x = 0; x < width; x++) l[x].bg = '';
162
+ return l;
163
+ };
164
+ const topLine = createBaseLine();
165
+ for (let x = 0; x < boxWidth; x++) topLine[boxStartX + x] = { char: GLYPHS.surface.top, fg: BORDER_COLOR, bg: '', bold: false, dim: false, underline: false, italic: false, strikethrough: false };
166
+ lines.push(topLine);
167
+ promptLines.forEach((text, i) => {
168
+ const contentW = boxWidth - 4;
169
+ const prefix = i === 0 ? ' › ' : ' ';
170
+ // Render text without cursor insertion — cursor is overlaid after
171
+ const rawText = `${prefix}${text}`;
172
+ const paddedText = fitDisplay(rawText, contentW);
173
+ const contentLine = createBaseLine();
174
+ for (let x = 0; x < boxWidth; x++) {
175
+ const char = (x >= 2 && x < boxWidth - 2) ? paddedText[x - 2] || ' ' : ' ';
176
+ contentLine[boxStartX + x] = {
177
+ char,
178
+ fg: (x < 5 && i === 0) ? (promptFocused ? '135' : '244') : TEXT_COLOR,
179
+ bg: BG_COLOR,
180
+ bold: false,
181
+ dim: !promptFocused,
182
+ underline: false,
183
+ italic: false,
184
+ strikethrough: false,
185
+ };
186
+ }
187
+
188
+ // Overlay cursor only while the prompt owns focus.
189
+ if (promptFocused && cursorPos !== undefined) {
190
+ let lineStart = 0;
191
+ for (let li = 0; li < i; li++) lineStart += promptLines[li].length + 1;
192
+ const posInLine = cursorPos - lineStart;
193
+ if (posInLine >= 0 && posInLine <= text.length) {
194
+ // Cursor column in cell coordinates: prefix width (3) + posInLine + box padding (2)
195
+ const cursorX = boxStartX + 2 + prefix.length + posInLine;
196
+ if (cursorX < boxStartX + boxWidth - 2) {
197
+ const cell = contentLine[cursorX];
198
+ // Invert: bright fg on the text bg, swap to make cursor visible
199
+ contentLine[cursorX] = {
200
+ char: cell.char === ' ' ? GLYPHS.surface.cursor : cell.char,
201
+ fg: cell.char === ' ' ? '252' : '#000000',
202
+ bg: cell.char === ' ' ? (promptFocused ? BG_COLOR : '#334155') : '#ffffff',
203
+ bold: false, dim: false, underline: false, italic: false, strikethrough: false
204
+ };
205
+ }
206
+ }
207
+ } else if (promptFocused && i === promptLines.length - 1) {
208
+ // No cursorPos provided — show block at end (fallback)
209
+ const endX = boxStartX + 2 + prefix.length + text.length;
210
+ if (endX < boxStartX + boxWidth - 2) {
211
+ contentLine[endX] = { char: GLYPHS.surface.cursor, fg: '252', bg: promptFocused ? BG_COLOR : '#334155', bold: false, dim: false, underline: false, italic: false, strikethrough: false };
212
+ }
213
+ }
214
+
215
+ // Overlay args hint: dim grey text after cursor on the last prompt line.
216
+ // Only shown when a commandArgsHint is provided and cursor is at the end of input.
217
+ if (commandArgsHint && i === promptLines.length - 1) {
218
+ // Determine where the cursor sits on this line
219
+ let cursorColOnLine: number;
220
+ if (cursorPos !== undefined) {
221
+ let lineStart = 0;
222
+ for (let li = 0; li < i; li++) lineStart += promptLines[li].length + 1;
223
+ cursorColOnLine = cursorPos - lineStart;
224
+ } else {
225
+ cursorColOnLine = text.length;
226
+ }
227
+ // Only show hint when cursor is at end of the last line (no args typed yet)
228
+ if (cursorColOnLine >= text.length) {
229
+ // Hint starts one cell after the cursor block
230
+ const hintStartX = boxStartX + 2 + prefix.length + text.length + 1;
231
+ const hintText = ' ' + commandArgsHint;
232
+ let hx = hintStartX;
233
+ for (const ch of hintText) {
234
+ if (hx >= boxStartX + boxWidth - 2) break;
235
+ contentLine[hx] = { char: ch, fg: '238', bg: BG_COLOR, bold: false, dim: true, underline: false, italic: false, strikethrough: false };
236
+ hx++;
237
+ }
238
+ }
239
+ }
240
+
241
+ lines.push(contentLine);
242
+ });
243
+ const bottomLine = createBaseLine();
244
+ for (let x = 0; x < boxWidth; x++) bottomLine[boxStartX + x] = { char: GLYPHS.surface.bottom, fg: BORDER_COLOR, bg: '', bold: false, dim: false, underline: false, italic: false, strikethrough: false };
245
+ lines.push(bottomLine);
246
+ lines.push(createBaseLine());
247
+ const composerTokens: Array<{ text: string; fg: string; bold?: boolean; dim?: boolean }> = [];
248
+ if (composerMode) composerTokens.push({ text: ` ${GLYPHS.status.active} ${composerMode} `, fg: '#38bdf8', bold: true });
249
+ if (composerPendingRisk && composerPendingRisk !== 'none') {
250
+ const riskColor = composerPendingRisk === 'approval-wait'
251
+ ? '#f59e0b'
252
+ : composerPendingRisk === 'shell'
253
+ ? '#ef4444'
254
+ : composerPendingRisk === 'remote'
255
+ ? '#a78bfa'
256
+ : '#f59e0b';
257
+ composerTokens.push({ text: ` risk:${composerPendingRisk} `, fg: riskColor, bold: true });
258
+ }
259
+ if (composerStatus && composerStatus !== 'idle') composerTokens.push({ text: ` state:${composerStatus} `, fg: '244', dim: true });
260
+ if (composerFlags && composerFlags.length > 0) composerTokens.push({ text: ` flags:${composerFlags.join(',')} `, fg: '244', dim: true });
261
+ if (composerTokens.length > 0) {
262
+ const postureLine = createBaseLine();
263
+ let px = 2;
264
+ for (const token of composerTokens) {
265
+ for (const ch of token.text) {
266
+ if (px >= width) break;
267
+ postureLine[px] = {
268
+ char: ch,
269
+ fg: token.fg,
270
+ bg: '',
271
+ bold: token.bold ?? false,
272
+ dim: token.dim ?? false,
273
+ underline: false,
274
+ italic: false,
275
+ strikethrough: false,
276
+ };
277
+ px += getDisplayWidth(ch);
278
+ }
279
+ if (px >= width) break;
280
+ }
281
+ lines.push(postureLine);
282
+ lines.push(createBaseLine());
283
+ }
284
+ const isRecentlyCopied = Date.now() - lastCopyTime < 2000;
285
+ // Token usage line
286
+ const u = usage as { input?: number; output?: number; cacheRead?: number; cacheWrite?: number; up?: number; down?: number };
287
+ const inp = u.input ?? u.up ?? 0;
288
+ const out = u.output ?? u.down ?? 0;
289
+ const cr = u.cacheRead ?? 0;
290
+ const cw = u.cacheWrite ?? 0;
291
+ const total = inp + out + cr + cw;
292
+ const tokenSep = ` ${GLYPHS.navigation.pipeSeparator} `;
293
+ const tokenLine = ` Token Usage [ Input: ${fmtNum(inp)}${tokenSep}Output: ${fmtNum(out)}${tokenSep}Cache Read: ${fmtNum(cr)}${tokenSep}Cache Write: ${fmtNum(cw)}${tokenSep}Total: ${fmtNum(total)} ]`;
294
+ const copiedNotice = isRecentlyCopied ? ` [COPIED] ` : '';
295
+ const statsLine = ' ' + tokenLine + ' '.repeat(Math.max(0, width - 4 - getDisplayWidth(tokenLine) - getDisplayWidth(copiedNotice))) + copiedNotice;
296
+ lines.push(this.stringToLine(statsLine, width, { fg: isRecentlyCopied ? '81' : '244', bold: isRecentlyCopied }));
297
+ // Context usage progress bar
298
+ if (contextWindow && contextWindow > 0) {
299
+ const ctxTokens = lastInputTokens ?? 0;
300
+ const label = ' Context Usage: ';
301
+ const suffix = ` [ ${fmtNum(ctxTokens)} / ${fmtNum(contextWindow)} ]`;
302
+ const barWidth = Math.max(10, Math.min(30, width - getDisplayWidth(label) - getDisplayWidth(suffix) - 8));
303
+ const ctxPct = Math.min(1, ctxTokens / contextWindow);
304
+ lines.push(createBaseLine());
305
+ lines.push(this.createProgressBarLine(label, ctxPct, barWidth, width, suffix));
306
+ }
307
+ // Context info line (working dir, model+provider, tools)
308
+ if (workingDir || model) {
309
+ const home = typeof process !== 'undefined' ? process.env.HOME ?? '' : '';
310
+ const displayDir = workingDir && home && workingDir.startsWith(home)
311
+ ? '~' + workingDir.slice(home.length)
312
+ : workingDir ?? '';
313
+ const ctxParts: string[] = [];
314
+ if (displayDir) ctxParts.push(displayDir);
315
+ if (model) {
316
+ ctxParts.push(model + (provider ? ` (${provider})` : ''));
317
+ }
318
+ if (toolCount) ctxParts.push(`${toolCount} tools`);
319
+ if (hitlMode) ctxParts.push(`hitl:${hitlMode}`);
320
+ if (composerMode) ctxParts.push(`mode:${composerMode}`);
321
+ if (composerStatus && composerStatus !== 'idle') ctxParts.push(`status:${composerStatus}`);
322
+ if (composerFlags && composerFlags.length > 0) ctxParts.push(composerFlags.join(','));
323
+ const ctxLine = ' ' + ctxParts.join(` ${GLYPHS.navigation.pipeSeparator} `);
324
+ lines.push(createBaseLine());
325
+ lines.push(this.stringToLine(truncateDisplay(ctxLine, width), width, { fg: '240', dim: true }));
326
+ lines.push(createBaseLine());
327
+ }
328
+ if (showExitNotice) {
329
+ const notice = ` !!! Press Ctrl+C again to exit !!! `;
330
+ lines.push(this.stringToLine(fitDisplay(notice, width), width, { fg: '196', bold: true }));
331
+ } else {
332
+ const help = ` /help for commands - Ctrl+C to quit `;
333
+ const dangerWarn = dangerMode ? `! DANGER MODE - ALL CHANGES AUTO-APPROVED ` : '';
334
+ const helpW = getDisplayWidth(help);
335
+ const dangerW = getDisplayWidth(dangerWarn);
336
+ const spacerW = Math.max(0, width - helpW - dangerW);
337
+ const combinedLine = help + ' '.repeat(spacerW) + dangerWarn;
338
+ const line = this.stringToLine(truncateDisplay(combinedLine, width), width, { fg: '240', dim: true });
339
+ // Overlay the danger warning in red bold
340
+ if (dangerMode && dangerW > 0) {
341
+ let col = helpW + spacerW;
342
+ for (const ch of dangerWarn) {
343
+ if (col >= width) break;
344
+ const cw = getDisplayWidth(ch);
345
+ line[col] = { char: ch, fg: '#ef4444', bg: '', bold: true, dim: false, underline: false, italic: false, strikethrough: false };
346
+ if (cw === 2 && col + 1 < width) line[col + 1] = { ...line[col], char: '' };
347
+ col += cw;
348
+ }
349
+ }
350
+ lines.push(line);
351
+ }
352
+ lines.push(createBaseLine());
353
+ return lines;
354
+ }
355
+
356
+ /** Rotating thinking phrases — vaporwave / good vibes themed. */
357
+ private static readonly THINKING_PHRASES = [
358
+ 'Thinking...',
359
+ 'Vibing...',
360
+ 'Manifesting...',
361
+ 'Channeling energy...',
362
+ 'Tuning frequencies...',
363
+ 'Riding the wave...',
364
+ 'Aligning chakras...',
365
+ 'Entering flow state...',
366
+ 'Consulting the void...',
367
+ 'Absorbing aesthetics...',
368
+ 'Synthesizing vibes...',
369
+ 'Transcending...',
370
+ 'Dreaming in neon...',
371
+ 'Parsing the cosmos...',
372
+ 'Loading good vibes...',
373
+ 'Meditating...',
374
+ 'Catching a vibe...',
375
+ 'Harmonizing...',
376
+ 'Feeling it...',
377
+ 'In the zone...',
378
+ ];
379
+
380
+ /** Gradient colors for thinking text — cyan to purple (matches splash). */
381
+ private static readonly THINK_GRADIENT_START = '#00ffff';
382
+ private static readonly THINK_GRADIENT_END = '#d000ff';
383
+
384
+ public static createThinkingFragment(width: number, spinner: string, frame: number = 0, tokenSpeed?: number, toolPreview?: string, inputTokens?: number, outputTokens?: number): Line[] {
385
+ // Rotate phrase every ~30 seconds (frame ticks at 80ms, so ~375 frames)
386
+ const phraseIndex = Math.floor(frame / PHRASE_ROTATION_FRAMES) % this.THINKING_PHRASES.length;
387
+ const phrase = this.THINKING_PHRASES[phraseIndex];
388
+ const speedSuffix = (tokenSpeed !== undefined && tokenSpeed > 0) ? ` (${Math.round(tokenSpeed)} tok/s)` : '';
389
+ const text = ` ${spinner} ${phrase}${speedSuffix} `;
390
+
391
+ const textWidth = Math.max(1, getDisplayWidth(text) - 1);
392
+ const segments: ConversationStatusSegment[] = Array.from(text).map((char, index) => {
393
+ const rawUnwrapped = (index / textWidth) - (frame % GRADIENT_CYCLE_FRAMES) * 0.02;
394
+ const raw = ((rawUnwrapped % 1.0) + 1.0) % 1.0;
395
+ const gradientPos = raw <= 0.5 ? raw * 2 : (1 - raw) * 2;
396
+ return {
397
+ text: char,
398
+ fg: interpolateColor(this.THINK_GRADIENT_START, this.THINK_GRADIENT_END, gradientPos),
399
+ bold: true,
400
+ };
401
+ });
402
+ if (inputTokens !== undefined || outputTokens !== undefined) {
403
+ const inTok = inputTokens ?? 0;
404
+ const outTok = outputTokens ?? 0;
405
+ segments.push({ text: ` in ${fmtNum(inTok)} `, fg: '243', dim: true });
406
+ segments.push({ text: `out ${fmtNum(outTok)}`, fg: '#00ffff' });
407
+ }
408
+ const line = createEmptyLine(width);
409
+ let col = 1;
410
+ for (const segment of segments) {
411
+ for (const char of segment.text) {
412
+ if (col >= width) break;
413
+ const charWidth = getDisplayWidth(char);
414
+ if (charWidth <= 0 || col + charWidth > width) break;
415
+ line[col] = {
416
+ char,
417
+ fg: segment.fg,
418
+ bg: '',
419
+ bold: segment.bold ?? false,
420
+ dim: segment.dim ?? false,
421
+ underline: false,
422
+ italic: segment.italic ?? false,
423
+ strikethrough: false,
424
+ };
425
+ if (charWidth === 2 && col + 1 < width) {
426
+ line[col + 1] = { ...line[col], char: '' };
427
+ }
428
+ col += charWidth;
429
+ }
430
+ if (col >= width) break;
431
+ }
432
+
433
+ const lines: Line[] = [
434
+ this.stringToLine(' '.repeat(width), width),
435
+ line,
436
+ ];
437
+
438
+ if (toolPreview) {
439
+ const previewLine = createEmptyLine(width);
440
+ const label = ' tool: ';
441
+ let px = 0;
442
+ for (const ch of label) {
443
+ if (px >= width) break;
444
+ previewLine[px] = {
445
+ char: ch,
446
+ fg: '#38bdf8',
447
+ bg: '',
448
+ bold: true,
449
+ dim: false,
450
+ underline: false,
451
+ italic: false,
452
+ strikethrough: false,
453
+ };
454
+ px += getDisplayWidth(ch);
455
+ }
456
+ for (const ch of toolPreview) {
457
+ if (px >= width) break;
458
+ const charWidth = getDisplayWidth(ch);
459
+ if (charWidth <= 0 || px + charWidth > width) break;
460
+ previewLine[px] = {
461
+ char: ch,
462
+ fg: '243',
463
+ bg: '',
464
+ bold: false,
465
+ dim: true,
466
+ underline: false,
467
+ italic: false,
468
+ strikethrough: false,
469
+ };
470
+ if (charWidth === 2 && px + 1 < width) {
471
+ previewLine[px + 1] = { ...previewLine[px], char: '' };
472
+ }
473
+ px += charWidth;
474
+ }
475
+ lines.push(previewLine);
476
+ }
477
+
478
+ lines.push(this.stringToLine(' '.repeat(width), width));
479
+ return lines;
480
+ }
481
+
482
+ /**
483
+ * createProgressBarLine - Renders a labeled progress bar line.
484
+ * @param label - Left-side label string (padded as-is)
485
+ * @param pct - Fill fraction 0..1
486
+ * @param barWidth - Number of bar characters
487
+ * @param lineWidth - Total terminal width to slice to
488
+ */
489
+ private static createProgressBarLine(label: string, pct: number, barWidth: number, lineWidth: number, suffix?: string): Line {
490
+ const pctDisplay = Math.round(pct * 100);
491
+ const filled = Math.round(pct * barWidth);
492
+ const color = pct < 0.6 ? '82' : pct < 0.85 ? '220' : '196';
493
+ const bar = GLYPHS.meter.filled.repeat(filled) + GLYPHS.meter.empty.repeat(barWidth - filled);
494
+ const pctStr = ` ${pctDisplay}%`;
495
+ const full = label + bar + pctStr + (suffix ?? '');
496
+ return this.stringToLine(truncateDisplay(full, lineWidth), lineWidth, { fg: color, dim: true });
497
+ }
498
+
499
+ public static stringToLine(text: string, width: number, style: Partial<Cell> = {}): Line {
500
+ const line = createEmptyLine(width);
501
+ let currentColumn = 0;
502
+ for (const char of text) {
503
+ if (currentColumn >= width) break;
504
+ const code = char.codePointAt(0) ?? 0;
505
+ if (code < 32 || code === 127) continue;
506
+ const charWidth = getDisplayWidth(char);
507
+ line[currentColumn] = {
508
+ char,
509
+ fg: style.fg || '',
510
+ bg: style.bg || '',
511
+ bold: style.bold || false,
512
+ dim: style.dim || false,
513
+ underline: style.underline || false,
514
+ italic: style.italic || false,
515
+ strikethrough: style.strikethrough || false
516
+ };
517
+ if (charWidth === 2 && currentColumn + 1 < width) {
518
+ line[currentColumn + 1] = { ...line[currentColumn], char: '' };
519
+ }
520
+ currentColumn += charWidth;
521
+ }
522
+ return line;
523
+ }
524
+ }
@@ -0,0 +1,96 @@
1
+ export const GLYPHS = {
2
+ frame: {
3
+ topLeft: '┌',
4
+ topRight: '┐',
5
+ bottomLeft: '└',
6
+ bottomRight: '┘',
7
+ horizontal: '─',
8
+ vertical: '│',
9
+ teeLeft: '├',
10
+ teeRight: '┤',
11
+ teeTop: '┬',
12
+ teeBottom: '┴',
13
+ cross: '┼',
14
+ },
15
+ surface: {
16
+ top: '▄',
17
+ bottom: '▀',
18
+ cursor: '█',
19
+ altCursor: '▌',
20
+ },
21
+ navigation: {
22
+ selected: '▸',
23
+ collapsed: '▸',
24
+ expanded: '▾',
25
+ up: '↑',
26
+ down: '↓',
27
+ moreAbove: '▲',
28
+ moreBelow: '▼',
29
+ next: '→',
30
+ back: '←',
31
+ pipeSeparator: '│',
32
+ },
33
+ status: {
34
+ success: '✓',
35
+ failure: '✕',
36
+ pending: '•',
37
+ active: '●',
38
+ idle: '○',
39
+ info: '•',
40
+ blocked: '⊘',
41
+ skipped: '◇',
42
+ review: '◈',
43
+ retry: '↻',
44
+ handoff: '⇢',
45
+ reference: '↗',
46
+ partial: '◐',
47
+ dualPane: '◆',
48
+ star: '★',
49
+ },
50
+ meter: {
51
+ filled: '█',
52
+ medium: '▓',
53
+ light: '▒',
54
+ empty: '░',
55
+ spark: ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'],
56
+ },
57
+ } as const;
58
+
59
+ export const UI_TONES = {
60
+ fg: {
61
+ primary: '#e2e8f0',
62
+ secondary: '#cbd5e1',
63
+ muted: '#94a3b8',
64
+ dim: '#475569',
65
+ inverse: '#0f172a',
66
+ },
67
+ bg: {
68
+ base: '#11131a',
69
+ surface: '#161a22',
70
+ title: '#0f172a',
71
+ section: '#18202b',
72
+ summary: '#1b2430',
73
+ selected: '#223049',
74
+ input: '#1e293b',
75
+ warning: '#2b2116',
76
+ error: '#2a161b',
77
+ success: '#14241b',
78
+ },
79
+ state: {
80
+ info: '#38bdf8',
81
+ good: '#22c55e',
82
+ warn: '#f59e0b',
83
+ bad: '#ef4444',
84
+ blocked: '#f97316',
85
+ active: '#60a5fa',
86
+ },
87
+ accent: {
88
+ browser: '#7dd3fc',
89
+ control: '#22d3ee',
90
+ inspector: '#c4b5fd',
91
+ workflow: '#fbbf24',
92
+ conversation: '#93c5fd',
93
+ },
94
+ } as const;
95
+
96
+ export type UiGlyphRegistry = typeof GLYPHS;