@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,78 @@
1
+ import type { CommandContext } from './command-registry.ts';
2
+ import { logger } from '@pellux/goodvibes-sdk/platform/utils';
3
+ import type { Panel } from '../panels/types.ts';
4
+ import type { PanelManager } from '../panels/panel-manager.ts';
5
+ import { FileExplorerPanel } from '../panels/file-explorer-panel.ts';
6
+ import { FilePreviewPanel } from '../panels/file-preview-panel.ts';
7
+ import { SymbolOutlinePanel } from '../panels/symbol-outline-panel.ts';
8
+ import { ApprovalPanel } from '../panels/approval-panel.ts';
9
+
10
+ function ensurePreviewPanel(panelManager: PanelManager): FilePreviewPanel | null {
11
+ const existing = panelManager.getPanel('preview');
12
+ if (existing instanceof FilePreviewPanel) {
13
+ const pane = panelManager.getPaneOf('preview');
14
+ panelManager.activateById('preview');
15
+ if (pane) panelManager.focusPane(pane);
16
+ return existing;
17
+ }
18
+ const targetPane: 'top' | 'bottom' = panelManager.isBottomPaneVisible()
19
+ ? (panelManager.getFocusedPane() === 'top' ? 'bottom' : 'top')
20
+ : 'bottom';
21
+ const opened = panelManager.open('preview', targetPane);
22
+ panelManager.show();
23
+ panelManager.focusPane(targetPane);
24
+ return opened instanceof FilePreviewPanel ? opened : null;
25
+ }
26
+
27
+ function syncSymbolOutlineFromPreview(panelManager: PanelManager, previewPanel: FilePreviewPanel): void {
28
+ const symbols = panelManager.getPanel('symbols');
29
+ const filePath = previewPanel.getCurrentFilePath();
30
+ const source = previewPanel.getSource();
31
+ if (symbols instanceof SymbolOutlinePanel && filePath && source !== null) {
32
+ symbols.loadFile(filePath, source);
33
+ }
34
+ }
35
+
36
+ export function handlePanelIntegrationAction(
37
+ panelManager: PanelManager,
38
+ activePanel: Panel | null,
39
+ key: string,
40
+ commandContext?: CommandContext,
41
+ ): boolean {
42
+ if (!activePanel) return false;
43
+
44
+ if ((key === 'enter' || key === 'return' || key === 'right') && activePanel instanceof FileExplorerPanel) {
45
+ const filePath = activePanel.getFocusedFilePath();
46
+ if (!filePath) return false;
47
+ const previewPanel = ensurePreviewPanel(panelManager);
48
+ if (!previewPanel) return false;
49
+ previewPanel.openFile(filePath);
50
+ syncSymbolOutlineFromPreview(panelManager, previewPanel);
51
+ return true;
52
+ }
53
+
54
+ if ((key === 'enter' || key === 'return') && activePanel instanceof SymbolOutlinePanel) {
55
+ const location = activePanel.getSelectedLocation();
56
+ if (!location) return false;
57
+ const previewPanel = ensurePreviewPanel(panelManager);
58
+ if (!previewPanel) return false;
59
+ if (previewPanel.getCurrentFilePath() !== location.path) {
60
+ previewPanel.openFile(location.path);
61
+ syncSymbolOutlineFromPreview(panelManager, previewPanel);
62
+ }
63
+ previewPanel.goToLine(location.line);
64
+ return true;
65
+ }
66
+
67
+ if ((key === 'enter' || key === 'return') && activePanel instanceof ApprovalPanel) {
68
+ const command = activePanel.getSelectedCommand();
69
+ if (!command || !commandContext?.executeCommand) return false;
70
+ const parts = command.replace(/^\//, '').split(/\s+/).filter(Boolean);
71
+ const [name, ...args] = parts;
72
+ if (!name) return false;
73
+ void commandContext.executeCommand(name, args).catch((err) => { logger.debug('approval panel command dispatch failed', { err }); });
74
+ return true;
75
+ }
76
+
77
+ return false;
78
+ }
@@ -0,0 +1,222 @@
1
+ /**
2
+ * ProfilePickerModal — state management for the /profiles picker modal.
3
+ *
4
+ * Lists profiles from ProfileManager.list(), tracks selected index,
5
+ * and handles load/delete/save actions.
6
+ */
7
+
8
+ import type { ProfileInfo, ProfileData, ProfileManager } from '@pellux/goodvibes-sdk/platform/profiles';
9
+ import { logger } from '@pellux/goodvibes-sdk/platform/utils';
10
+ import type { ConfigManager } from '@pellux/goodvibes-sdk/platform/config';
11
+ import type { ConfigKey } from '@pellux/goodvibes-sdk/platform/config';
12
+ import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
13
+
14
+ /** Known display setting keys (subset of ConfigKey that maps to display.*). */
15
+ const DISPLAY_KEYS: ConfigKey[] = [
16
+ 'display.stream', 'display.lineNumbers', 'display.collapseThreshold',
17
+ 'display.theme', 'display.showThinking', 'display.showReasoningSummary',
18
+ 'display.showTokenSpeed', 'display.showToolPreview',
19
+ ] as const;
20
+
21
+ /** Known behavior setting keys (subset of ConfigKey that maps to behavior.*). */
22
+ const BEHAVIOR_KEYS: ConfigKey[] = [
23
+ 'behavior.autoApprove', 'behavior.autoCompactThreshold',
24
+ 'behavior.saveHistory', 'behavior.notifyOnComplete',
25
+ ] as const;
26
+
27
+ /**
28
+ * Apply a profile data category to the config manager.
29
+ * Iterates only known/valid keys rather than open-ended Object.entries.
30
+ */
31
+ function applyProfileCategory(
32
+ cm: ConfigManager,
33
+ data: Record<string, unknown>,
34
+ keys: ConfigKey[],
35
+ ): void {
36
+ for (const key of keys) {
37
+ const field = key.split('.')[1];
38
+ if (field && Object.prototype.hasOwnProperty.call(data, field)) {
39
+ try {
40
+ cm.setDynamic(key, data[field]);
41
+ } catch (e) { logger.debug('applyProfileCategory: key set failed', { key, error: summarizeError(e) }); }
42
+ }
43
+ }
44
+ }
45
+
46
+ // ---------------------------------------------------------------------------
47
+ // ProfilePickerModal
48
+ // ---------------------------------------------------------------------------
49
+
50
+ export class ProfilePickerModal {
51
+ public active = false;
52
+ public profiles: ProfileInfo[] = [];
53
+ public selectedIndex = 0;
54
+ public scrollOffset = 0;
55
+ public visibleRows = 8;
56
+ public deleteConfirmationTarget: string | null = null;
57
+
58
+ /** Last status message (success/error feedback). */
59
+ public statusMessage = '';
60
+
61
+ public constructor(private readonly profileManager: ProfileManager) {}
62
+
63
+ /**
64
+ * Open the modal, loading profiles from ProfileManager.
65
+ */
66
+ open(): void {
67
+ this.profiles = this.profileManager.list();
68
+ this.selectedIndex = 0;
69
+ this.scrollOffset = 0;
70
+ this.statusMessage = '';
71
+ this.deleteConfirmationTarget = null;
72
+ this.active = true;
73
+ }
74
+
75
+ close(): void {
76
+ this.active = false;
77
+ this.statusMessage = '';
78
+ this.deleteConfirmationTarget = null;
79
+ }
80
+
81
+ moveUp(): void {
82
+ if (this.profiles.length === 0) return;
83
+ this.selectedIndex = (this.selectedIndex - 1 + this.profiles.length) % this.profiles.length;
84
+ this._clampScroll();
85
+ this.deleteConfirmationTarget = null;
86
+ }
87
+
88
+ moveDown(): void {
89
+ if (this.profiles.length === 0) return;
90
+ this.selectedIndex = (this.selectedIndex + 1) % this.profiles.length;
91
+ this._clampScroll();
92
+ this.deleteConfirmationTarget = null;
93
+ }
94
+
95
+ setVisibleRows(rows: number): void {
96
+ this.visibleRows = Math.max(3, rows);
97
+ this._clampScroll();
98
+ }
99
+
100
+ getSelected(): ProfileInfo | null {
101
+ return this.profiles[this.selectedIndex] ?? null;
102
+ }
103
+
104
+ /**
105
+ * Load the selected profile into configManager.
106
+ * Returns true on success, false on error.
107
+ */
108
+ loadSelected(configManager: ConfigManager): boolean {
109
+ const profile = this.getSelected();
110
+ if (!profile) return false;
111
+
112
+ try {
113
+ const { data } = this.profileManager.load(profile.name);
114
+
115
+ // Apply display settings using validated key list
116
+ if (data.display) {
117
+ applyProfileCategory(configManager, data.display as Record<string, unknown>, DISPLAY_KEYS);
118
+ }
119
+
120
+ // Apply provider settings (model + reasoningEffort only)
121
+ if (data.provider) {
122
+ if (data.provider.model !== undefined) {
123
+ try { configManager.set('provider.model', data.provider.model); } catch (e) { logger.debug('profile: model set failed', { error: summarizeError(e) }); }
124
+ }
125
+ if (data.provider.reasoningEffort !== undefined) {
126
+ try { configManager.set('provider.reasoningEffort', data.provider.reasoningEffort); } catch (e) { logger.debug('profile: reasoningEffort set failed', { error: summarizeError(e) }); }
127
+ }
128
+ }
129
+
130
+ // Apply behavior settings using validated key list
131
+ if (data.behavior) {
132
+ applyProfileCategory(configManager, data.behavior as Record<string, unknown>, BEHAVIOR_KEYS);
133
+ }
134
+
135
+ configManager.save();
136
+ this.statusMessage = `Loaded profile: ${profile.name}`;
137
+ return true;
138
+ } catch (e) {
139
+ this.statusMessage = `Error: ${summarizeError(e)}`;
140
+ return false;
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Delete the selected profile from disk.
146
+ * Refreshes the list after deletion.
147
+ */
148
+ deleteSelected(): boolean {
149
+ const profile = this.getSelected();
150
+ if (!profile) return false;
151
+ if (this.deleteConfirmationTarget !== profile.name) {
152
+ this.deleteConfirmationTarget = profile.name;
153
+ this.statusMessage = `Press delete again to remove profile: ${profile.name}`;
154
+ return false;
155
+ }
156
+
157
+ try {
158
+ const deleted = this.profileManager.delete(profile.name);
159
+ if (!deleted) {
160
+ this.statusMessage = `Profile not found: ${profile.name}`;
161
+ this.deleteConfirmationTarget = null;
162
+ return false;
163
+ }
164
+ this.profiles = this.profileManager.list();
165
+ if (this.selectedIndex >= this.profiles.length) {
166
+ this.selectedIndex = Math.max(0, this.profiles.length - 1);
167
+ }
168
+ this._clampScroll();
169
+ this.deleteConfirmationTarget = null;
170
+ this.statusMessage = `Deleted: ${profile.name}`;
171
+ return true;
172
+ } catch (e) {
173
+ this.deleteConfirmationTarget = null;
174
+ this.statusMessage = `Error: ${summarizeError(e)}`;
175
+ return false;
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Save the current config settings as a new profile under `name`.
181
+ */
182
+ saveCurrentAs(name: string, configManager: ConfigManager): boolean {
183
+ if (!name || !name.trim()) {
184
+ this.statusMessage = 'Profile name cannot be empty';
185
+ return false;
186
+ }
187
+
188
+ try {
189
+ const all = configManager.getAll();
190
+ const data: ProfileData = {
191
+ display: { ...all.display },
192
+ provider: {
193
+ model: all.provider.model,
194
+ reasoningEffort: all.provider.reasoningEffort,
195
+ },
196
+ behavior: { ...all.behavior },
197
+ };
198
+
199
+ this.profileManager.save(name, data);
200
+
201
+ // Reload list
202
+ this.profiles = this.profileManager.list();
203
+ this.statusMessage = `Saved profile: ${name}`;
204
+ this._clampScroll();
205
+ return true;
206
+ } catch (e) {
207
+ this.statusMessage = `Error: ${summarizeError(e)}`;
208
+ return false;
209
+ }
210
+ }
211
+
212
+ private _clampScroll(): void {
213
+ const visRows = Math.max(3, this.visibleRows);
214
+ if (this.selectedIndex < this.scrollOffset) {
215
+ this.scrollOffset = this.selectedIndex;
216
+ } else if (this.selectedIndex >= this.scrollOffset + visRows) {
217
+ this.scrollOffset = this.selectedIndex - visRows + 1;
218
+ }
219
+ const maxOffset = Math.max(0, this.profiles.length - visRows);
220
+ this.scrollOffset = Math.max(0, Math.min(this.scrollOffset, maxOffset));
221
+ }
222
+ }
@@ -0,0 +1,100 @@
1
+ import type { InfiniteBuffer } from '../core/history.ts';
2
+
3
+ export interface SearchMatch {
4
+ line: number;
5
+ col: number;
6
+ length: number;
7
+ }
8
+
9
+ /**
10
+ * SearchManager - Handles search-within-output state.
11
+ * Tracks active query, matches, and current match navigation.
12
+ */
13
+ export class SearchManager {
14
+ public active = false;
15
+ /** When true, the query is locked and arrow/comma/period navigate matches. */
16
+ public locked = false;
17
+ public query = '';
18
+ public matches: SearchMatch[] = [];
19
+ public currentMatch = 0;
20
+
21
+ /** Open search mode. */
22
+ open(): void {
23
+ this.active = true;
24
+ this.locked = false;
25
+ this.query = '';
26
+ this.matches = [];
27
+ this.currentMatch = 0;
28
+ }
29
+
30
+ /** Lock the query — switches from typing mode to navigation mode. */
31
+ lock(): void {
32
+ this.locked = true;
33
+ }
34
+
35
+ /** Unlock — return to typing mode. */
36
+ unlock(): void {
37
+ this.locked = false;
38
+ }
39
+
40
+ /** Close search mode. */
41
+ close(): void {
42
+ this.active = false;
43
+ this.locked = false;
44
+ }
45
+
46
+ /** Update query and find matches in the history buffer. */
47
+ search(query: string, history: InfiniteBuffer): void {
48
+ this.query = query;
49
+ this.matches = [];
50
+ this.currentMatch = 0;
51
+
52
+ if (query.length === 0) return;
53
+
54
+ const lowerQuery = query.toLowerCase();
55
+ const lines = history.getAllLines();
56
+
57
+ for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {
58
+ const line = lines[lineIdx];
59
+ // Build text from cells
60
+ const text = line.map(c => c.char).join('').toLowerCase();
61
+ let col = 0;
62
+ while (col <= text.length - lowerQuery.length) {
63
+ const idx = text.indexOf(lowerQuery, col);
64
+ if (idx === -1) break;
65
+ this.matches.push({ line: lineIdx, col: idx, length: query.length });
66
+ col = idx + 1;
67
+ }
68
+ }
69
+ }
70
+
71
+ /** Jump to next match. */
72
+ nextMatch(): void {
73
+ if (this.matches.length === 0) return;
74
+ this.currentMatch = (this.currentMatch + 1) % this.matches.length;
75
+ }
76
+
77
+ /** Jump to previous match. */
78
+ prevMatch(): void {
79
+ if (this.matches.length === 0) return;
80
+ this.currentMatch = (this.currentMatch - 1 + this.matches.length) % this.matches.length;
81
+ }
82
+
83
+ /** Get the line number of the current match (for scroll). */
84
+ getCurrentMatchLine(): number {
85
+ if (this.matches.length === 0) return -1;
86
+ return this.matches[this.currentMatch]?.line ?? -1;
87
+ }
88
+
89
+ /** Get matches on a given line. */
90
+ getMatchesOnLine(lineIdx: number): SearchMatch[] {
91
+ return this.matches.filter(m => m.line === lineIdx);
92
+ }
93
+
94
+ /** Is the current match on the given line at the given col? */
95
+ isCurrentMatch(lineIdx: number, col: number): boolean {
96
+ if (this.matches.length === 0) return false;
97
+ const m = this.matches[this.currentMatch];
98
+ return m !== undefined && m.line === lineIdx && m.col === col;
99
+ }
100
+ }
@@ -0,0 +1,163 @@
1
+ /**
2
+ * SelectionModal - Generic reusable selection modal with fuzzy search.
3
+ * Used by /template, /sessions, /bookmarks, /tools, and focused pickers.
4
+ */
5
+
6
+ export interface SelectionItem {
7
+ id: string;
8
+ label: string;
9
+ detail?: string; // shown to the right of label
10
+ category?: string; // optional grouping header
11
+ actions?: string; // hint text for available actions (e.g., "[d] delete")
12
+ fg?: string; // optional foreground color override for this item
13
+ primaryAction?: SelectionAction; // default Enter/Space behavior for this row
14
+ adjustable?: boolean; // supports left/right adjustment without leaving the modal
15
+ adjustStep?: number; // base step for left/right adjustments
16
+ adjustMin?: number; // minimum numeric value when adjustable
17
+ adjustMax?: number; // maximum numeric value when adjustable
18
+ adjustPrecision?: number; // decimal places to preserve for numeric adjustments
19
+ }
20
+
21
+ export type SelectionAction = 'select' | 'delete' | 'edit' | 'toggle' | 'increment' | 'decrement';
22
+
23
+ export interface SelectionResult {
24
+ item: SelectionItem;
25
+ action: SelectionAction;
26
+ step?: number;
27
+ }
28
+
29
+ export class SelectionModal {
30
+ public active = false;
31
+ public title = '';
32
+ public query = ''; // fuzzy search query
33
+ public searchFocused = false;
34
+ public items: SelectionItem[] = [];
35
+ public filteredItems: SelectionItem[] = [];
36
+ public selectedIndex = 0;
37
+ public allowSearch = true;
38
+ public customActions: Map<string, SelectionAction> = new Map();
39
+
40
+ /** Open the modal with items and title */
41
+ open(
42
+ title: string,
43
+ items: SelectionItem[],
44
+ opts?: {
45
+ preSelectId?: string;
46
+ allowSearch?: boolean;
47
+ customActions?: Map<string, SelectionAction>;
48
+ }
49
+ ): void {
50
+ this.title = title;
51
+ this.items = items;
52
+ this.query = '';
53
+ this.allowSearch = opts?.allowSearch ?? true;
54
+ this.searchFocused = false;
55
+ this.customActions = opts?.customActions ?? new Map();
56
+ this.active = true;
57
+ this.filterItems();
58
+
59
+ // Pre-select by id if provided
60
+ if (opts?.preSelectId) {
61
+ const idx = this.filteredItems.findIndex(it => it.id === opts.preSelectId);
62
+ this.selectedIndex = idx >= 0 ? idx : 0;
63
+ } else {
64
+ this.selectedIndex = 0;
65
+ }
66
+ }
67
+
68
+ close(): void {
69
+ this.active = false;
70
+ this.title = '';
71
+ this.query = '';
72
+ this.searchFocused = false;
73
+ this.items = [];
74
+ this.filteredItems = [];
75
+ this.selectedIndex = 0;
76
+ this.customActions = new Map();
77
+ }
78
+
79
+ moveUp(): void {
80
+ // Skip category headers (items with no id-based content — filtered items include only selectable ones)
81
+ if (this.filteredItems.length === 0) return;
82
+ this.selectedIndex = this.selectedIndex > 0
83
+ ? this.selectedIndex - 1
84
+ : this.filteredItems.length - 1;
85
+ }
86
+
87
+ moveDown(): void {
88
+ if (this.filteredItems.length === 0) return;
89
+ this.selectedIndex = this.selectedIndex < this.filteredItems.length - 1
90
+ ? this.selectedIndex + 1
91
+ : 0;
92
+ }
93
+
94
+ /** Update fuzzy search filter */
95
+ setQuery(query: string): void {
96
+ this.query = query;
97
+ this.selectedIndex = 0;
98
+ this.filterItems();
99
+ }
100
+
101
+ canFocusSearch(): boolean {
102
+ return this.allowSearch;
103
+ }
104
+
105
+ focusSearch(): void {
106
+ if (this.allowSearch) this.searchFocused = true;
107
+ }
108
+
109
+ blurSearch(): void {
110
+ this.searchFocused = false;
111
+ }
112
+
113
+ /** Get currently highlighted item */
114
+ getSelected(): SelectionItem | null {
115
+ if (this.filteredItems.length === 0) return null;
116
+ return this.filteredItems[this.selectedIndex] ?? null;
117
+ }
118
+
119
+ /** Fuzzy match items against query — resets filteredItems */
120
+ private filterItems(): void {
121
+ if (this.query.length === 0) {
122
+ this.filteredItems = this.items.slice();
123
+ return;
124
+ }
125
+
126
+ const scored = this.items
127
+ .map(item => ({ item, ...this.fuzzyMatch(this.query, item.label + ' ' + (item.detail ?? '') + ' ' + (item.category ?? '')) }))
128
+ .filter(r => r.match)
129
+ .sort((a, b) => b.score - a.score);
130
+
131
+ this.filteredItems = scored.map(r => r.item);
132
+ if (this.selectedIndex >= this.filteredItems.length) {
133
+ this.selectedIndex = Math.max(0, this.filteredItems.length - 1);
134
+ }
135
+ }
136
+
137
+ /** Fuzzy match: does the query match the candidate? */
138
+ private fuzzyMatch(query: string, candidate: string): { match: boolean; score: number } {
139
+ if (query.length === 0) return { match: true, score: 0 };
140
+ const lowerQuery = query.toLowerCase();
141
+ const lowerCandidate = candidate.toLowerCase();
142
+
143
+ // Substring match (highest priority)
144
+ const subIdx = lowerCandidate.indexOf(lowerQuery);
145
+ if (subIdx !== -1) {
146
+ return { match: true, score: 100 - subIdx };
147
+ }
148
+
149
+ // Character-by-character fuzzy match
150
+ let qi = 0;
151
+ let score = 0;
152
+ for (let ci = 0; ci < lowerCandidate.length && qi < lowerQuery.length; ci++) {
153
+ if (lowerCandidate[ci] === lowerQuery[qi]) {
154
+ qi++;
155
+ score += 1;
156
+ }
157
+ }
158
+ if (qi === lowerQuery.length) {
159
+ return { match: true, score };
160
+ }
161
+ return { match: false, score: 0 };
162
+ }
163
+ }
@@ -0,0 +1,135 @@
1
+ import type { InfiniteBuffer } from '../core/history.ts';
2
+ import type { Cell } from '../types/grid.ts';
3
+
4
+ export interface SelectionPoint {
5
+ col: number;
6
+ row: number;
7
+ }
8
+
9
+ /**
10
+ * SelectionManager - Owns text selection state.
11
+ * Extracted from StateManager. Requires access to the history buffer
12
+ * and scroll state (scrollTop, viewportHeight) at call time.
13
+ */
14
+ export class SelectionManager {
15
+ public anchor: SelectionPoint | null = null;
16
+ public focus: SelectionPoint | null = null;
17
+ public isDragging = false;
18
+
19
+ private screenToAbsoluteRow(viewportRow: number, scrollTop: number, vHeight: number, lineCount: number): number {
20
+ const offset = Math.max(0, vHeight - lineCount);
21
+ return scrollTop + (viewportRow - offset);
22
+ }
23
+
24
+ public startSelection(col: number, viewportRow: number, scrollTop: number, vHeight: number, lineCount: number): void {
25
+ const absoluteRow = this.screenToAbsoluteRow(viewportRow, scrollTop, vHeight, lineCount);
26
+ this.anchor = { col, row: absoluteRow };
27
+ this.focus = { col, row: absoluteRow };
28
+ this.isDragging = true;
29
+ }
30
+
31
+ public extendSelection(col: number, viewportRow: number, scrollTop: number, vHeight: number, lineCount: number): void {
32
+ if (!this.isDragging) return;
33
+ const absoluteRow = this.screenToAbsoluteRow(viewportRow, scrollTop, vHeight, lineCount);
34
+ this.focus = { col, row: absoluteRow };
35
+ }
36
+
37
+ public endSelection(): void {
38
+ this.isDragging = false;
39
+ }
40
+
41
+ public clearSelection(): void {
42
+ this.anchor = null;
43
+ this.focus = null;
44
+ this.isDragging = false;
45
+ }
46
+
47
+ public hasSelection(): boolean {
48
+ if (!this.anchor || !this.focus) return false;
49
+ return this.anchor.row !== this.focus.row || this.anchor.col !== this.focus.col;
50
+ }
51
+
52
+ public getSelectedText(history: InfiniteBuffer): string {
53
+ if (!this.anchor || !this.focus) return '';
54
+
55
+ const start =
56
+ this.anchor.row < this.focus.row ||
57
+ (this.anchor.row === this.focus.row && this.anchor.col <= this.focus.col)
58
+ ? this.anchor
59
+ : this.focus;
60
+ const end = start === this.anchor ? this.focus : this.anchor;
61
+
62
+ const lines: string[] = [];
63
+ const allLines = history.getAllLines();
64
+
65
+ for (let r = Math.max(0, start.row); r <= Math.min(allLines.length - 1, end.row); r++) {
66
+ const line = allLines[r];
67
+ if (!line) continue;
68
+
69
+ const startCol = r === start.row ? start.col : 0;
70
+ const endCol = r === end.row ? end.col : line.length;
71
+ const gutterEnd = this.findLineNumberGutterEnd(line);
72
+ const effectiveStartCol = startCol < gutterEnd ? gutterEnd : startCol;
73
+
74
+ let lineText = '';
75
+ for (let c = Math.max(0, effectiveStartCol); c < Math.min(line.length, endCol); c++) {
76
+ const cell = line[c];
77
+ if (cell && cell.char !== '') lineText += cell.char;
78
+ }
79
+
80
+ lines.push(this.stripDecorativePrefix(lineText));
81
+ }
82
+
83
+ return lines.join('\n');
84
+ }
85
+
86
+ private findLineNumberGutterEnd(line: Cell[]): number {
87
+ let i = 0;
88
+ while (i < line.length) {
89
+ const cell = line[i];
90
+ if (cell?.char === ' ' && !cell?.dim && (cell?.bg ?? '') === '') {
91
+ i++;
92
+ continue;
93
+ }
94
+ break;
95
+ }
96
+ const start = i;
97
+ let sawDigit = false;
98
+ while (i < line.length) {
99
+ const cell = line[i];
100
+ const ch = cell?.char ?? '';
101
+ if (cell?.dim && ch !== '' && /[0-9 │]/.test(ch)) {
102
+ if (/[0-9]/.test(ch)) sawDigit = true;
103
+ i++;
104
+ continue;
105
+ }
106
+ break;
107
+ }
108
+ if (!sawDigit) return start;
109
+ if (i < line.length && line[i]?.char === ' ') i++;
110
+ return i > start ? i : start;
111
+ }
112
+
113
+ private stripDecorativePrefix(text: string): string {
114
+ return text
115
+ .replace(/^(\s*)(?:[▸▾▶►●○◆▲△▼▽•✕✓▌]\s+)+/u, '$1')
116
+ .replace(/^(\s*)\[(?:x|~| )\]\s+/u, '$1')
117
+ .trimEnd();
118
+ }
119
+
120
+ public isCellSelected(col: number, absoluteRow: number): boolean {
121
+ if (!this.anchor || !this.focus) return false;
122
+ const start =
123
+ this.anchor.row < this.focus.row ||
124
+ (this.anchor.row === this.focus.row && this.anchor.col <= this.focus.col)
125
+ ? this.anchor
126
+ : this.focus;
127
+ const end = start === this.anchor ? this.focus : this.anchor;
128
+ if (absoluteRow < start.row || absoluteRow > end.row) return false;
129
+ if (absoluteRow === start.row && absoluteRow === end.row)
130
+ return col >= start.col && col < end.col;
131
+ if (absoluteRow === start.row) return col >= start.col;
132
+ if (absoluteRow === end.row) return col < end.col;
133
+ return true;
134
+ }
135
+ }