@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,199 @@
1
+ import type { Line } from '../types/grid.ts';
2
+ import { GLYPHS } from './ui-primitives.ts';
3
+ import {
4
+ borderLine,
5
+ clamp,
6
+ contentLine,
7
+ drawHorizontalRule,
8
+ drawVerticalRule,
9
+ fillRange,
10
+ FULLSCREEN_PALETTE,
11
+ makeLine,
12
+ writeText,
13
+ } from './fullscreen-primitives.ts';
14
+
15
+ export {
16
+ borderLine,
17
+ clamp,
18
+ contentLine,
19
+ fillRange,
20
+ makeLine,
21
+ padDisplay,
22
+ stableWindow,
23
+ writeText,
24
+ } from './fullscreen-primitives.ts';
25
+ export { FULLSCREEN_PALETTE as WORKSPACE_PALETTE } from './fullscreen-primitives.ts';
26
+
27
+ const WORKSPACE_PALETTE = FULLSCREEN_PALETTE;
28
+
29
+ export interface WorkspaceRow {
30
+ readonly text: string;
31
+ readonly selected?: boolean;
32
+ readonly bold?: boolean;
33
+ readonly dim?: boolean;
34
+ readonly fg?: string;
35
+ readonly bg?: string;
36
+ readonly kind?: 'group' | 'item' | 'more' | 'empty';
37
+ }
38
+
39
+ export interface FullscreenWorkspaceRenderOptions {
40
+ readonly width: number;
41
+ readonly height: number;
42
+ readonly title: string;
43
+ readonly stateLabel?: string;
44
+ readonly leftHeader: string;
45
+ readonly mainHeader: string;
46
+ readonly leftRows: readonly WorkspaceRow[];
47
+ readonly contextRows: readonly WorkspaceRow[];
48
+ readonly controlRows: readonly WorkspaceRow[];
49
+ readonly footer: string;
50
+ readonly leftWidth?: number;
51
+ readonly contextRatio?: number;
52
+ readonly minContextRows?: number;
53
+ }
54
+
55
+ export interface FullscreenWorkspaceMetrics {
56
+ readonly safeWidth: number;
57
+ readonly safeHeight: number;
58
+ readonly leftWidth: number;
59
+ readonly centerWidth: number;
60
+ readonly bodyRows: number;
61
+ readonly contextWidth: number;
62
+ readonly contextRows: number;
63
+ readonly controlRows: number;
64
+ }
65
+
66
+ export function drawVertical(line: Line, x: number, bg = ''): void {
67
+ if (x <= 0 || x >= line.length - 1) return;
68
+ drawVerticalRule(line, x, WORKSPACE_PALETTE.border, bg);
69
+ }
70
+
71
+ export function drawHorizontalRange(line: Line, startX: number, endX: number, bg = ''): void {
72
+ drawHorizontalRule(line, Math.max(1, startX), Math.min(line.length - 2, endX), WORKSPACE_PALETTE.border, bg);
73
+ }
74
+
75
+ function leftWidthFor(width: number, explicit?: number): number {
76
+ if (explicit !== undefined) return clamp(explicit, 14, Math.max(14, width - 24));
77
+ return width < 80
78
+ ? clamp(Math.round(width * 0.32), 14, Math.max(14, width - 24))
79
+ : clamp(Math.round(width * 0.22), 24, 34);
80
+ }
81
+
82
+ export function getFullscreenWorkspaceMetrics(options: Pick<
83
+ FullscreenWorkspaceRenderOptions,
84
+ 'width' | 'height' | 'leftWidth' | 'contextRatio' | 'minContextRows'
85
+ >): FullscreenWorkspaceMetrics {
86
+ const safeWidth = Math.max(1, options.width);
87
+ const safeHeight = Math.max(12, options.height);
88
+ const leftWidth = leftWidthFor(safeWidth, options.leftWidth);
89
+ const centerWidth = Math.max(20, safeWidth - leftWidth - 3);
90
+ const bodyTop = 3;
91
+ const footerY = safeHeight - 2;
92
+ const bodyRows = Math.max(4, footerY - bodyTop);
93
+ const contextWidth = Math.max(10, centerWidth - 2);
94
+ const maxContextRows = Math.max(3, bodyRows - 4);
95
+ const minContextRows = clamp(options.minContextRows ?? 10, 3, maxContextRows);
96
+ const contextRows = clamp(
97
+ Math.round(bodyRows * (options.contextRatio ?? 0.4)),
98
+ Math.min(minContextRows, maxContextRows),
99
+ maxContextRows,
100
+ );
101
+ const controlRows = Math.max(3, bodyRows - contextRows - 1);
102
+ return { safeWidth, safeHeight, leftWidth, centerWidth, bodyRows, contextWidth, contextRows, controlRows };
103
+ }
104
+
105
+ function rowFg(row: WorkspaceRow, fallback: string): string {
106
+ if (row.fg) return row.fg;
107
+ if (row.kind === 'group') return WORKSPACE_PALETTE.subtitle;
108
+ if (row.kind === 'more') return WORKSPACE_PALETTE.dim;
109
+ return fallback;
110
+ }
111
+
112
+ export function renderFullscreenWorkspace(options: FullscreenWorkspaceRenderOptions): Line[] {
113
+ const { safeWidth, safeHeight, leftWidth, centerWidth, bodyRows, contextWidth, contextRows } = getFullscreenWorkspaceMetrics(options);
114
+ const leftStart = 1;
115
+ const dividerX = leftWidth + 1;
116
+ const centerStart = dividerX + 1;
117
+ const centerEnd = safeWidth - 2;
118
+ const bodyTop = 3;
119
+ const separatorY = bodyTop + contextRows;
120
+ const lines: Line[] = [];
121
+
122
+ const top = borderLine(safeWidth, GLYPHS.frame.topLeft, GLYPHS.frame.horizontal, GLYPHS.frame.topRight);
123
+ writeText(top, 2, safeWidth - 4, ` ${options.title} `, { fg: WORKSPACE_PALETTE.title, bold: true });
124
+ if (options.stateLabel) {
125
+ writeText(top, Math.max(2, safeWidth - options.stateLabel.length - 4), options.stateLabel.length, options.stateLabel, {
126
+ fg: WORKSPACE_PALETTE.subtitle,
127
+ });
128
+ }
129
+ lines.push(top);
130
+
131
+ const header = contentLine(safeWidth, WORKSPACE_PALETTE.footerBg);
132
+ drawVertical(header, dividerX, WORKSPACE_PALETTE.footerBg);
133
+ writeText(header, leftStart + 1, leftWidth - 2, options.leftHeader, {
134
+ fg: WORKSPACE_PALETTE.subtitle,
135
+ bold: true,
136
+ bg: WORKSPACE_PALETTE.footerBg,
137
+ });
138
+ writeText(header, centerStart + 1, centerWidth - 2, options.mainHeader, {
139
+ fg: WORKSPACE_PALETTE.subtitle,
140
+ bold: true,
141
+ bg: WORKSPACE_PALETTE.footerBg,
142
+ });
143
+ lines.push(header);
144
+
145
+ const headerSep = contentLine(safeWidth, '');
146
+ drawVertical(headerSep, dividerX);
147
+ drawHorizontalRange(headerSep, 1, safeWidth - 2);
148
+ lines.push(headerSep);
149
+
150
+ for (let row = 0; row < bodyRows; row += 1) {
151
+ const y = bodyTop + row;
152
+ const inContext = y < separatorY;
153
+ const inSeparator = y === separatorY;
154
+ const bg = inSeparator ? '' : inContext ? WORKSPACE_PALETTE.contextBg : WORKSPACE_PALETTE.controlsBg;
155
+ const line = contentLine(safeWidth, bg);
156
+ fillRange(line, 1, dividerX - 1, WORKSPACE_PALETTE.categoryBg);
157
+ drawVertical(line, dividerX, bg);
158
+
159
+ const leftRow = options.leftRows[row] ?? { text: '', kind: 'empty' as const };
160
+ if (leftRow.selected) fillRange(line, leftStart, dividerX - 1, WORKSPACE_PALETTE.selectedBg);
161
+ writeText(line, leftStart + 1, leftWidth - 3, leftRow.text, {
162
+ fg: leftRow.selected ? WORKSPACE_PALETTE.text : rowFg(leftRow, WORKSPACE_PALETTE.muted),
163
+ bg: leftRow.selected ? WORKSPACE_PALETTE.selectedBg : WORKSPACE_PALETTE.categoryBg,
164
+ bold: leftRow.bold ?? (leftRow.selected || leftRow.kind === 'group'),
165
+ dim: leftRow.dim,
166
+ });
167
+
168
+ if (inSeparator) {
169
+ drawHorizontalRange(line, centerStart, centerEnd);
170
+ } else if (inContext) {
171
+ const contextRow = options.contextRows[row] ?? { text: '', kind: 'empty' as const };
172
+ writeText(line, centerStart + 1, contextWidth, contextRow.text, {
173
+ fg: rowFg(contextRow, WORKSPACE_PALETTE.text),
174
+ bg,
175
+ bold: contextRow.bold,
176
+ dim: contextRow.dim ?? contextRow.text.length === 0,
177
+ });
178
+ } else {
179
+ const controlRow = options.controlRows[row - contextRows - 1] ?? { text: '', kind: 'empty' as const };
180
+ if (controlRow.selected) fillRange(line, centerStart, centerEnd, WORKSPACE_PALETTE.selectedBg);
181
+ writeText(line, centerStart + 1, contextWidth, controlRow.text, {
182
+ fg: controlRow.selected ? WORKSPACE_PALETTE.text : rowFg(controlRow, WORKSPACE_PALETTE.text),
183
+ bg: controlRow.selected ? WORKSPACE_PALETTE.selectedBg : bg,
184
+ bold: controlRow.bold ?? controlRow.selected,
185
+ dim: controlRow.dim ?? controlRow.text.length === 0,
186
+ });
187
+ }
188
+
189
+ lines.push(line);
190
+ }
191
+
192
+ const footer = contentLine(safeWidth, WORKSPACE_PALETTE.footerBg);
193
+ writeText(footer, 2, safeWidth - 4, options.footer, { fg: WORKSPACE_PALETTE.muted, bg: WORKSPACE_PALETTE.footerBg });
194
+ lines.push(footer);
195
+ lines.push(borderLine(safeWidth, GLYPHS.frame.bottomLeft, GLYPHS.frame.horizontal, GLYPHS.frame.bottomRight));
196
+
197
+ while (lines.length < safeHeight) lines.unshift(makeLine(safeWidth));
198
+ return lines.slice(-safeHeight);
199
+ }
@@ -0,0 +1,89 @@
1
+ import { GitService } from '@pellux/goodvibes-sdk/platform/git';
2
+ import { logger } from '@pellux/goodvibes-sdk/platform/utils';
3
+ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
4
+
5
+ /** Git state shown in the header bar. */
6
+ export interface GitHeaderInfo {
7
+ branch: string;
8
+ dirty: boolean;
9
+ ahead: number;
10
+ behind: number;
11
+ }
12
+
13
+ const FALLBACK: GitHeaderInfo = { branch: '?', dirty: false, ahead: 0, behind: 0 };
14
+
15
+ /**
16
+ * GitStatusProvider — Fetches git state for the header bar.
17
+ *
18
+ * Results are cached for 2 seconds (TTL). The next call after expiry triggers
19
+ * a fresh fetch and returns the cached value immediately (stale-while-revalidate).
20
+ * Never throws — returns FALLBACK on any error.
21
+ */
22
+ export class GitStatusProvider {
23
+ private cache: GitHeaderInfo = { ...FALLBACK };
24
+ private lastFetch = 0;
25
+ private readonly ttlMs = 2000;
26
+ private fetching = false;
27
+
28
+ constructor(private readonly workingDirectory: string) {}
29
+
30
+ /** Returns cached info immediately; refreshes in background if TTL expired. */
31
+ async getStatus(): Promise<GitHeaderInfo> {
32
+ const now = Date.now();
33
+ if (now - this.lastFetch < this.ttlMs) {
34
+ return this.cache;
35
+ }
36
+ // Fetch synchronously on first call (no cache yet), otherwise return stale
37
+ if (this.lastFetch === 0) {
38
+ await this._fetch().catch(() => {
39
+ // Ensure fallback is set if _fetch failed before setting lastFetch
40
+ if (this.lastFetch === 0) {
41
+ this.lastFetch = Date.now();
42
+ }
43
+ });
44
+ } else if (!this.fetching) {
45
+ this._fetch().catch(err => { logger.debug('GitStatusProvider: background refresh failed', { error: summarizeError(err) }); });
46
+ }
47
+ return this.cache;
48
+ }
49
+
50
+ /** Force a fresh fetch and update the cache. Returns updated info. */
51
+ async refresh(): Promise<GitHeaderInfo> {
52
+ await this._fetch();
53
+ return this.cache;
54
+ }
55
+
56
+ private async _fetch(): Promise<void> {
57
+ if (this.fetching) return;
58
+ this.fetching = true;
59
+ try {
60
+ const git = new GitService(this.workingDirectory);
61
+ const [statusResult, branchResult] = await Promise.all([
62
+ git.status(),
63
+ git.branch(),
64
+ ]);
65
+ const dirty =
66
+ statusResult.modified.length > 0 ||
67
+ statusResult.created.length > 0 ||
68
+ statusResult.deleted.length > 0 ||
69
+ statusResult.renamed.length > 0 ||
70
+ statusResult.conflicted.length > 0 ||
71
+ statusResult.not_added.length > 0;
72
+ this.cache = {
73
+ branch: branchResult.current || '?',
74
+ dirty,
75
+ ahead: statusResult.ahead ?? 0,
76
+ behind: statusResult.behind ?? 0,
77
+ };
78
+ this.lastFetch = Date.now();
79
+ } catch {
80
+ // Never throw — return fallback
81
+ if (this.lastFetch === 0) {
82
+ this.cache = { ...FALLBACK };
83
+ this.lastFetch = Date.now();
84
+ }
85
+ } finally {
86
+ this.fetching = false;
87
+ }
88
+ }
89
+ }
@@ -0,0 +1,267 @@
1
+ /**
2
+ * renderHelpOverlay — renders the help overlay with keyboard shortcuts and slash commands.
3
+ *
4
+ * Toggle with `?` key or `/help` command.
5
+ */
6
+
7
+ import { type Line } from '../types/grid.ts';
8
+ import { ModalFactory } from './modal-factory.ts';
9
+ import type { SlashCommand } from '../input/command-registry.ts';
10
+ import type { KeybindingsManager } from '../input/keybindings.ts';
11
+ import { getOverlaySurfaceMetrics } from './overlay-viewport.ts';
12
+ import { getVisibleWindow } from './surface-layout.ts';
13
+ import { logger } from '@pellux/goodvibes-sdk/platform/utils';
14
+
15
+ function toModalSections(rows: readonly string[]): import('./modal-factory.ts').ModalSection[] {
16
+ return rows.map((row) => {
17
+ if (row === '') return { type: 'spacer' as const };
18
+ if (row.startsWith(' ') && !row.slice(2).includes(' ')) {
19
+ return { type: 'title' as const, content: row.trim() };
20
+ }
21
+ if (row.startsWith(' \u2500')) return { type: 'separator' as const };
22
+ return { type: 'text' as const, content: row };
23
+ });
24
+ }
25
+
26
+ /**
27
+ * Render the help overlay as Line[].
28
+ * Shows keyboard shortcuts summary and slash commands.
29
+ *
30
+ * @param width Terminal width.
31
+ * @param commands List of registered slash commands.
32
+ * @param scrollOffset Number of lines scrolled (for navigation).
33
+ */
34
+ export function renderHelpOverlay(
35
+ width: number,
36
+ keybindingsManager: KeybindingsManager,
37
+ commands?: SlashCommand[],
38
+ scrollOffset = 0,
39
+ viewportHeight = process.stdout.rows || 24,
40
+ ): Line[] {
41
+ const kb = (action: Parameters<typeof keybindingsManager.getComboLabel>[0]) => keybindingsManager.getComboLabel(action);
42
+
43
+ const hasCommand = (name: string): boolean => Boolean(commands?.some((command) => command.name === name || (command.aliases ?? []).includes(name)));
44
+
45
+ // Keyboard shortcut sections
46
+ const shortcutRows: string[] = [
47
+ ' Core Navigation',
48
+ ' ' + '\u2500'.repeat(40),
49
+ ` ${'Up / Down'.padEnd(20)} Scroll / history recall`,
50
+ ` ${'PageUp / PageDn'.padEnd(20)} Scroll by full page`,
51
+ ` ${kb('search').padEnd(20)} Search conversation (Ctrl+F)`,
52
+ '',
53
+ ' Prompt And Editing',
54
+ ' ' + '\u2500'.repeat(40),
55
+ ` ${'Enter'.padEnd(20)} Submit message`,
56
+ ` ${'Shift+Enter'.padEnd(20)} Insert newline`,
57
+ ` ${kb('paste').padEnd(20)} Paste (image priority)`,
58
+ ` ${(kb('undo') + ' / ' + kb('redo')).padEnd(20)} Undo / redo`,
59
+ '',
60
+ ' Overlays And Panels',
61
+ ' ' + '\u2500'.repeat(40),
62
+ ` ${'?'.padEnd(20)} Toggle help`,
63
+ ` ${'/shortcuts'.padEnd(20)} Full keyboard shortcuts`,
64
+ ` ${kb('panel-picker').padEnd(20)} Open or focus the panel workspace`,
65
+ '',
66
+ ];
67
+
68
+ // Featured commands shown in the Quick Start section.
69
+ // Each entry is [commandName, subcommandOrArgHint, description].
70
+ // Commands not registered in the live registry are omitted at render time.
71
+ const FEATURED_COMMANDS: Array<[name: string, argHint: string, desc: string]> = [
72
+ ['onboarding', '', 'Open the onboarding wizard with current settings preloaded'],
73
+ ['cockpit', '', 'Unified runtime control room'],
74
+ ['settings', '', 'Settings and config browser'],
75
+ ['provider', '', 'Choose provider or model family'],
76
+ ['subscription', '', 'Review provider logins and subscriptions'],
77
+ ['marketplace', 'open', 'Browse plugins, skills, and packs'],
78
+ ['remote', 'setup', 'Review remote, bridge, and tunnel flows'],
79
+ ['sandbox', 'review', 'Inspect secure execution posture'],
80
+ ['security', '', 'Security review workspace'],
81
+ ['policy', '', 'Simulation, lint, and preflight review'],
82
+ ['incident', '', 'Incident workspace and export flows'],
83
+ ['knowledge', '', 'Durable knowledge and review queue'],
84
+ ['hooks', '', 'Hook workbench and runtime activity'],
85
+ ['orchestration','', 'Graph and recursive-agent control room'],
86
+ ['communication','', 'Structured agent communication workspace'],
87
+ ['tasks', '', 'Task surface for list/show/pause/resume/output'],
88
+ ];
89
+
90
+ // Build command rows from featured list, filtering out unregistered commands.
91
+ function featuredRow(name: string, argHint: string, desc: string): string {
92
+ const invocation = argHint ? `/${name} ${argHint}` : `/${name}`;
93
+ return ` ${invocation.padEnd(23)} ${desc}`;
94
+ }
95
+
96
+ const quickStartRows: string[] = [];
97
+ try {
98
+ for (const [name, argHint, desc] of FEATURED_COMMANDS) {
99
+ if (!hasCommand(name)) continue; // omit if not in live registry
100
+ quickStartRows.push(featuredRow(name, argHint, desc));
101
+ }
102
+ } catch (err) {
103
+ // A plugin command getter threw during registry traversal. Fall back to an
104
+ // unfiltered quick-start list so /help remains reachable.
105
+ logger.warn(`[help-overlay] registry traversal error during command filter; using unfiltered list: ${err}`);
106
+ quickStartRows.length = 0;
107
+ for (const [name, argHint, desc] of FEATURED_COMMANDS) {
108
+ quickStartRows.push(featuredRow(name, argHint, desc));
109
+ }
110
+ }
111
+
112
+ const commandRows: string[] = [];
113
+ if (quickStartRows.length > 0) {
114
+ commandRows.push(' Quick Start', ' ' + '\u2500'.repeat(40), ...quickStartRows, '');
115
+ }
116
+
117
+ if (commands && commands.length > 0) {
118
+ commandRows.push('', ' Available Slash Commands', ' ' + '\u2500'.repeat(40));
119
+ const preferred = ['setup', 'cockpit', 'settings', 'provider', 'subscription', 'marketplace', 'remote', 'sandbox', 'security', 'policy', 'incident', 'knowledge', 'hooks', 'orchestration', 'communication', 'tasks'];
120
+ const seen = new Set<string>();
121
+ for (const name of preferred) {
122
+ const cmd = commands.find((entry) => entry.name === name);
123
+ if (!cmd) continue;
124
+ seen.add(cmd.name);
125
+ const nameCol = `/${cmd.name}`.padEnd(18);
126
+ commandRows.push(` ${nameCol} ${cmd.description}`);
127
+ }
128
+ const remainder = [...commands]
129
+ .filter((cmd) => !seen.has(cmd.name))
130
+ .sort((a, b) => a.name.localeCompare(b.name))
131
+ .slice(0, 24);
132
+ if (remainder.length > 0) {
133
+ commandRows.push('', ' More Commands', ' ' + '\u2500'.repeat(40));
134
+ for (const cmd of remainder) {
135
+ const nameCol = `/${cmd.name}`.padEnd(18);
136
+ commandRows.push(` ${nameCol} ${cmd.description}`);
137
+ }
138
+ }
139
+ } else if (!hasCommand('help')) {
140
+ commandRows.push('', ' Essentials', ' ' + '\u2500'.repeat(40));
141
+ commandRows.push(' /help Show this help overlay');
142
+ commandRows.push(' /shortcuts Keyboard shortcut reference');
143
+ commandRows.push(' /model Select LLM model');
144
+ commandRows.push(' /clear Clear conversation');
145
+ }
146
+
147
+ const allRows = [...shortcutRows, ...commandRows];
148
+
149
+ // Apply scroll offset — show a window of rows
150
+ const metrics = getOverlaySurfaceMetrics(width, viewportHeight, {
151
+ chromeRows: 4,
152
+ minContentRows: 8,
153
+ maxContentRows: 12,
154
+ });
155
+ const maxVisible = metrics.contentRows;
156
+ const clampedOffset = Math.max(0, Math.min(scrollOffset, Math.max(0, allRows.length - maxVisible)));
157
+ const visibleRows = allRows.slice(clampedOffset, clampedOffset + maxVisible);
158
+ const window = getVisibleWindow(allRows.length, clampedOffset, maxVisible);
159
+
160
+ return ModalFactory.createModal(
161
+ {
162
+ title: 'Help',
163
+ width: metrics.boxWidth,
164
+ margin: metrics.margin,
165
+ targetContentRows: metrics.contentRows,
166
+ tabs: [
167
+ { label: 'Overview', active: true },
168
+ { label: 'Commands' },
169
+ ],
170
+ sections: toModalSections(visibleRows),
171
+ helpers: allRows.length > maxVisible
172
+ ? [{ content: `[${window.start + 1}-${Math.min(allRows.length, clampedOffset + visibleRows.length)} of ${allRows.length}]` }]
173
+ : undefined,
174
+ hints: ['? or Esc Close', 'Up/Down Scroll'],
175
+ },
176
+ width,
177
+ );
178
+ }
179
+
180
+ /**
181
+ * renderShortcutsOverlay — renders keyboard shortcuts as Line[].
182
+ * Accessed via /shortcuts command. Reflects live keybindings (user overrides included).
183
+ */
184
+ export function renderShortcutsOverlay(
185
+ width: number,
186
+ keybindingsManager: KeybindingsManager,
187
+ scrollOffset = 0,
188
+ viewportHeight = process.stdout.rows || 24,
189
+ ): Line[] {
190
+ function row(key: string, desc: string): string {
191
+ const keyCol = key.length > 20 ? key.slice(0, 19) + '\u2026' : key.padEnd(20);
192
+ return ` ${keyCol} ${desc}`;
193
+ }
194
+
195
+ // Helper: get the label for a bindable action, falling back to literal string.
196
+ const kb = (action: Parameters<typeof keybindingsManager.getComboLabel>[0]) => keybindingsManager.getComboLabel(action);
197
+
198
+ const allRows: string[] = [
199
+ ' Navigation',
200
+ ' ' + '\u2500'.repeat(40),
201
+ row('Up / Down', 'Scroll / history recall'),
202
+ row('PageUp / PageDn', 'Scroll by full page'),
203
+ row('Home / End', 'Jump to start / end of line'),
204
+ row(kb('search'), 'Search conversation'),
205
+ row('Mouse wheel', 'Scroll conversation or hovered panel'),
206
+ '',
207
+ ' Editing',
208
+ ' ' + '\u2500'.repeat(40),
209
+ row('Enter', 'Submit message'),
210
+ row('Shift+Enter', 'Insert newline'),
211
+ row('@', 'Open file picker'),
212
+ row('/', 'Slash command mode'),
213
+ row(kb('paste'), 'Paste (image priority)'),
214
+ row(`${kb('undo')} / ${kb('redo')}`, 'Undo / redo'),
215
+ row(kb('clear-prompt'), 'Clear prompt'),
216
+ row(kb('delete-word'), 'Delete word backward'),
217
+ row(kb('kill-line'), 'Kill to end of line'),
218
+ row(kb('apply-diff-line-start'), 'Apply diff / line start'),
219
+ row(kb('next-error-line-end'), 'Next error / line end'),
220
+ '',
221
+ ' Actions',
222
+ ' ' + '\u2500'.repeat(40),
223
+ row('Tab', 'Collapse/expand block'),
224
+ row(kb('bookmark'), 'Bookmark block'),
225
+ row(kb('block-copy'), 'Copy block to clipboard'),
226
+ row(kb('block-save'), 'Save block to file'),
227
+ row(kb('copy-selection'), 'Copy selection'),
228
+ row('F2', 'Process monitor'),
229
+ row('?', 'Help overlay'),
230
+ row(`${kb('clear-cancel')} x2`, 'Exit'),
231
+ '',
232
+ ' Panels',
233
+ ' ' + '\u2500'.repeat(40),
234
+ row('Tab', 'Swap focus between input and panel workspace'),
235
+ row(kb('panel-picker'), 'Open / focus / hide panel workspace'),
236
+ row(kb('panel-tab-next'), 'Next workspace panel tab'),
237
+ row(kb('panel-tab-prev'), 'Previous workspace panel tab'),
238
+ '',
239
+ ` Config: /keybindings to list and customize`,
240
+ ];
241
+
242
+ const metrics = getOverlaySurfaceMetrics(width, viewportHeight, {
243
+ chromeRows: 4,
244
+ minContentRows: 8,
245
+ maxContentRows: 12,
246
+ });
247
+ const maxVisible = metrics.contentRows;
248
+ const clampedOffset = Math.max(0, Math.min(scrollOffset, Math.max(0, allRows.length - maxVisible)));
249
+ const visibleRows = allRows.slice(clampedOffset, clampedOffset + maxVisible);
250
+ const window = getVisibleWindow(allRows.length, clampedOffset, maxVisible);
251
+
252
+ return ModalFactory.createModal(
253
+ {
254
+ title: 'Keyboard Shortcuts',
255
+ width: metrics.boxWidth,
256
+ margin: metrics.margin,
257
+ targetContentRows: metrics.contentRows,
258
+ tabs: [{ label: 'Shortcuts', active: true }],
259
+ sections: toModalSections(visibleRows),
260
+ helpers: allRows.length > maxVisible
261
+ ? [{ content: `[${window.start + 1}-${Math.min(allRows.length, clampedOffset + visibleRows.length)} of ${allRows.length}]` }]
262
+ : undefined,
263
+ hints: ['Esc Close', 'Up/Down Scroll'],
264
+ },
265
+ width,
266
+ );
267
+ }
@@ -0,0 +1,73 @@
1
+ import type { Line } from '../types/grid.ts';
2
+ import { getDisplayWidth } from '../utils/terminal-width.ts';
3
+ import type { HistorySearch } from '../input/input-history.ts';
4
+ import { createBottomBarLine, writeBottomBarText } from './bottom-bar.ts';
5
+
6
+ /**
7
+ * Truncate `text` to at most `maxWidth` display columns, then pad with spaces
8
+ * to exactly `maxWidth` columns. CJK/emoji wide characters count as 2 columns.
9
+ */
10
+ function truncateToWidth(text: string, maxWidth: number): string {
11
+ let usedWidth = 0;
12
+ let result = '';
13
+ let i = 0;
14
+ while (i < text.length) {
15
+ const code = text.codePointAt(i)!;
16
+ const charLen = code > 0xFFFF ? 2 : 1;
17
+ const charWidth = getDisplayWidth(text.slice(i, i + charLen));
18
+ if (usedWidth + charWidth > maxWidth) break;
19
+ result += text.slice(i, i + charLen);
20
+ usedWidth += charWidth;
21
+ i += charLen;
22
+ }
23
+ // Pad to exactly maxWidth columns with spaces
24
+ return result + ' '.repeat(maxWidth - usedWidth);
25
+ }
26
+
27
+ /**
28
+ * Render the reverse-i-search bar as a single Line[] overlay at the bottom of the viewport.
29
+ * Format: (reverse-i-search)`query': matched-command-text
30
+ *
31
+ * - The matched command text is shown in dim grey over the teal bar.
32
+ * - If no match, shows "(failed reverse-i-search)" prefix.
33
+ */
34
+ export function renderHistorySearchOverlay(
35
+ historySearch: HistorySearch,
36
+ width: number
37
+ ): Line[] {
38
+ if (width <= 0) return [];
39
+
40
+ const match = historySearch.currentMatch;
41
+ const hasMatch = match !== null && historySearch.query.length > 0;
42
+ const noMatch = historySearch.query.length > 0 && !hasMatch;
43
+
44
+ const prefix = noMatch
45
+ ? '(failed reverse-i-search)`'
46
+ : '(reverse-i-search)`';
47
+ const queryPart = historySearch.query + "': ";
48
+ const matchText = hasMatch ? match?.entry ?? '' : '';
49
+
50
+ // Build the display string
51
+ const label = prefix + queryPart;
52
+ const full = truncateToWidth(label + matchText, width);
53
+
54
+ const line = createBottomBarLine(width, { fg: '#000000', bg: '#00ffcc' });
55
+ writeBottomBarText(line, 0, width, full, { fg: '#000000', bg: '#00ffcc' });
56
+
57
+ // Highlight the matched region in the match text with dim styling
58
+ if (hasMatch && match) {
59
+ const labelW = getDisplayWidth(label);
60
+ const matchStartCol = labelW + match.matchStart;
61
+ const matchEndCol = matchStartCol + match.matchLength;
62
+ const highlightWidth = Math.max(0, matchEndCol - matchStartCol);
63
+ const matchedSlice = truncateToWidth(match.entry.slice(match.matchStart, match.matchStart + match.matchLength), highlightWidth);
64
+ writeBottomBarText(line, matchStartCol, highlightWidth, matchedSlice, {
65
+ fg: '#000000',
66
+ bg: '#00ffcc',
67
+ bold: true,
68
+ underline: true,
69
+ });
70
+ }
71
+
72
+ return [line];
73
+ }