@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,345 @@
1
+ import type { Line } from '../types/grid.ts';
2
+ import { ScrollableListPanel } from './scrollable-list-panel.ts';
3
+ import { type ConfirmState, handleConfirmInput, renderConfirmLines } from './confirm-state.ts';
4
+ import type { MemoryClass, MemoryRecord, MemoryRegistry, MemoryReviewState } from '@pellux/goodvibes-sdk/platform/state';
5
+ import {
6
+ buildBodyText,
7
+ buildEmptyState,
8
+ buildGuidanceLine,
9
+ buildKeyValueLine,
10
+ buildPanelLine,
11
+ buildPanelWorkspace,
12
+ DEFAULT_PANEL_PALETTE,
13
+ } from './polish.ts';
14
+
15
+ function summarize(records: MemoryRecord[], cls: MemoryClass): MemoryRecord[] {
16
+ return records.filter((record) => record.cls === cls).slice(0, 3);
17
+ }
18
+
19
+ const C = {
20
+ ...DEFAULT_PANEL_PALETTE,
21
+ header: '#94a3b8',
22
+ headerBg: '#1e293b',
23
+ } as const;
24
+
25
+ function reviewStateColor(state: MemoryReviewState): string {
26
+ switch (state) {
27
+ case 'reviewed':
28
+ return C.good;
29
+ case 'stale':
30
+ return C.warn;
31
+ case 'contradicted':
32
+ return C.bad;
33
+ case 'fresh':
34
+ default:
35
+ return C.info;
36
+ }
37
+ }
38
+
39
+ function formatConfidence(confidence: number): string {
40
+ return `${confidence.toString().padStart(3, ' ')}%`;
41
+ }
42
+
43
+ export class KnowledgePanel extends ScrollableListPanel<MemoryRecord> {
44
+ private readonly registry: MemoryRegistry;
45
+ private unsubscribe?: () => void;
46
+ private records: MemoryRecord[] = [];
47
+ // I1: confirm for destructive review-state mutations
48
+ private confirm: ConfirmState<{ id: string; action: 'stale' | 'contradicted' }> | null = null;
49
+
50
+ public constructor(registry: MemoryRegistry) {
51
+ super('knowledge', 'Knowledge', 'K', 'agent');
52
+ this.registry = registry;
53
+ }
54
+
55
+ public override onActivate(): void {
56
+ super.onActivate();
57
+ this.refresh();
58
+ this.unsubscribe = this.registry.subscribe(() => {
59
+ this.refresh();
60
+ this.markDirty();
61
+ });
62
+ }
63
+
64
+ public override onDeactivate(): void {
65
+ super.onDeactivate();
66
+ }
67
+
68
+ public override onDestroy(): void {
69
+ this.unsubscribe?.();
70
+ this.unsubscribe = undefined;
71
+ }
72
+
73
+ // ---------------------------------------------------------------------------
74
+ // ScrollableListPanel implementation
75
+ // ---------------------------------------------------------------------------
76
+
77
+ protected getItems(): readonly MemoryRecord[] {
78
+ return this.records;
79
+ }
80
+
81
+ protected renderItem(record: MemoryRecord, index: number, selected: boolean, width: number): Line {
82
+ const bg = selected ? C.selectBg : undefined;
83
+ return buildPanelLine(width, [
84
+ [' ', C.label, bg],
85
+ [record.reviewState.padEnd(13), reviewStateColor(record.reviewState), bg],
86
+ [` ${formatConfidence(record.confidence)} `, C.value, bg],
87
+ [record.summary.slice(0, Math.max(0, width - 26)), C.value, bg],
88
+ ]);
89
+ }
90
+
91
+ protected override getPalette() { return C; }
92
+ protected override getEmptyStateMessage() { return 'No durable project knowledge'; }
93
+ protected override getEmptyStateActions() {
94
+ return [
95
+ { command: '/recall add fact <summary>', summary: 'capture a durable fact directly' },
96
+ { command: '/recall capture incident latest', summary: 'promote the latest incident into project memory' },
97
+ { command: '/recall capture policy', summary: 'store the current policy posture as durable evidence' },
98
+ ];
99
+ }
100
+
101
+ // ---------------------------------------------------------------------------
102
+ // Input
103
+ // ---------------------------------------------------------------------------
104
+
105
+ public handleInput(key: string): boolean {
106
+ // I1: y/n confirm for stale/contradict
107
+ if (this.confirm) {
108
+ const result = handleConfirmInput(this.confirm, key);
109
+ if (result === 'confirmed') {
110
+ const { id, action } = this.confirm.subject;
111
+ this.confirm = null;
112
+ const selected = this.records.find((r) => r.id === id);
113
+ if (selected) {
114
+ try {
115
+ if (action === 'stale') {
116
+ this.registry.review(id, {
117
+ state: 'stale',
118
+ confidence: Math.min(selected.confidence, 40),
119
+ reviewedBy: 'operator',
120
+ staleReason: 'marked stale from the knowledge panel',
121
+ });
122
+ } else {
123
+ this.registry.review(id, {
124
+ state: 'contradicted',
125
+ confidence: 0,
126
+ reviewedBy: 'operator',
127
+ staleReason: 'marked contradicted from the knowledge panel',
128
+ });
129
+ }
130
+ } catch (e) {
131
+ // I2: surface async failure
132
+ this.setError(`Review update failed: ${e instanceof Error ? e.message : String(e)}`);
133
+ }
134
+ }
135
+ this.refresh();
136
+ this.markDirty();
137
+ return true;
138
+ }
139
+ if (result === 'cancelled') {
140
+ this.confirm = null;
141
+ this.markDirty();
142
+ return true;
143
+ }
144
+ if (result === 'absorbed') return true;
145
+ }
146
+
147
+ // I2: auto-clear error on next keypress (inherited via super.handleInput)
148
+ if (this.records.length === 0) return super.handleInput(key);
149
+
150
+ const selected = this.records[this.selectedIndex];
151
+
152
+ if (key === 'Enter' || key === 'return' || key === 'r') {
153
+ if (!selected) return false;
154
+ this.registry.review(selected.id, {
155
+ state: 'reviewed',
156
+ confidence: Math.max(selected.confidence, 85),
157
+ reviewedBy: 'operator',
158
+ });
159
+ this.refresh();
160
+ this.markDirty();
161
+ return true;
162
+ }
163
+ if (key === 's') {
164
+ if (!selected) return false;
165
+ // I1: prompt confirm before marking stale
166
+ this.confirm = { subject: { id: selected.id, action: 'stale' }, label: selected.summary.slice(0, 40) };
167
+ this.markDirty();
168
+ return true;
169
+ }
170
+ if (key === 'c') {
171
+ if (!selected) return false;
172
+ // I1: prompt confirm before marking contradicted
173
+ this.confirm = { subject: { id: selected.id, action: 'contradicted' }, label: selected.summary.slice(0, 40) };
174
+ this.markDirty();
175
+ return true;
176
+ }
177
+ if (key === 'f') {
178
+ if (!selected) return false;
179
+ this.registry.review(selected.id, {
180
+ state: 'fresh',
181
+ confidence: Math.max(selected.confidence, 60),
182
+ reviewedBy: 'operator',
183
+ });
184
+ this.refresh();
185
+ this.markDirty();
186
+ return true;
187
+ }
188
+
189
+ // Normalize arrow keys to base class format
190
+ if (key === 'ArrowUp') return super.handleInput('up');
191
+ if (key === 'ArrowDown') return super.handleInput('down');
192
+ return super.handleInput(key);
193
+ }
194
+
195
+ private refresh(): void {
196
+ const queue = this.registry.reviewQueue(24);
197
+ this.records = queue.length > 0 ? queue : this.registry.search({ limit: 24 });
198
+ this.clampSelection();
199
+ }
200
+
201
+ public render(width: number, height: number): Line[] {
202
+ this.clampSelection();
203
+
204
+ // I1: show confirm dialog in place of normal content
205
+ if (this.confirm) {
206
+ return buildPanelWorkspace(width, height, {
207
+ title: 'Knowledge Control Room',
208
+ intro: '',
209
+ sections: [{ title: 'Confirmation', lines: renderConfirmLines(width, this.confirm) }],
210
+ footerLines: [buildPanelLine(width, [[' y confirm n / Esc cancel', C.dim]])],
211
+ palette: C,
212
+ });
213
+ }
214
+
215
+ if (this.records.length === 0) this.refresh();
216
+
217
+ const intro = 'Typed project knowledge, reviewed evidence, and operator-governed memory across session, project, and team scopes.';
218
+ const records = this.registry.search({ limit: 200 });
219
+
220
+ if (records.length === 0) {
221
+ return this.renderList(width, height, {
222
+ title: 'Knowledge Control Room',
223
+ footer: [buildPanelLine(width, [[' Review keys: Up/Down move r/Enter review s stale c contradicted f fresh', C.dim]])],
224
+ });
225
+ }
226
+
227
+ const queue = this.registry.reviewQueue(24);
228
+ const byClass = new Map<MemoryClass, number>();
229
+ const byReview = new Map<MemoryReviewState, number>();
230
+ const byScope = new Map<string, number>();
231
+ for (const record of records) {
232
+ byClass.set(record.cls, (byClass.get(record.cls) ?? 0) + 1);
233
+ byReview.set(record.reviewState, (byReview.get(record.reviewState) ?? 0) + 1);
234
+ byScope.set(record.scope, (byScope.get(record.scope) ?? 0) + 1);
235
+ }
236
+
237
+ const classLines: Line[] = [
238
+ buildPanelLine(width, [
239
+ [' facts ', C.label], [String(byClass.get('fact') ?? 0), C.good],
240
+ [' risks ', C.label], [String(byClass.get('risk') ?? 0), (byClass.get('risk') ?? 0) > 0 ? C.warn : C.good],
241
+ [' runbooks ', C.label], [String(byClass.get('runbook') ?? 0), C.info],
242
+ [' architecture ', C.label], [String(byClass.get('architecture') ?? 0), C.info],
243
+ [' incidents ', C.label], [String(byClass.get('incident') ?? 0), (byClass.get('incident') ?? 0) > 0 ? C.bad : C.good],
244
+ ]),
245
+ buildPanelLine(width, [
246
+ [' decisions ', C.label], [String(byClass.get('decision') ?? 0), C.value],
247
+ [' constraints ', C.label], [String(byClass.get('constraint') ?? 0), C.value],
248
+ [' ownership ', C.label], [String(byClass.get('ownership') ?? 0), C.value],
249
+ [' patterns ', C.label], [String(byClass.get('pattern') ?? 0), C.value],
250
+ [' total ', C.label], [String(records.length), C.value],
251
+ ]),
252
+ ];
253
+
254
+ const reviewLines: Line[] = [
255
+ buildPanelLine(width, [
256
+ [' reviewed ', C.label], [String(byReview.get('reviewed') ?? 0), C.good],
257
+ [' fresh ', C.label], [String(byReview.get('fresh') ?? 0), C.info],
258
+ [' stale ', C.label], [String(byReview.get('stale') ?? 0), C.warn],
259
+ [' contradicted ', C.label], [String(byReview.get('contradicted') ?? 0), C.bad],
260
+ [' Review Queue ', C.label], [String(queue.length), queue.length > 0 ? C.warn : C.good],
261
+ ]),
262
+ buildPanelLine(width, [
263
+ [' session ', C.label], [String(byScope.get('session') ?? 0), C.info],
264
+ [' project ', C.label], [String(byScope.get('project') ?? 0), C.value],
265
+ [' team ', C.label], [String(byScope.get('team') ?? 0), C.good],
266
+ ]),
267
+ buildGuidanceLine(width, '/recall review', 'work the stale and contradicted queue from the command surface', C),
268
+ ];
269
+
270
+ const recentSummaryLines: Line[] = [];
271
+ for (const [title, items, color] of [
272
+ ['Recent Risks', summarize(records, 'risk'), C.warn],
273
+ ['Runbooks', summarize(records, 'runbook'), C.info],
274
+ ['Architecture Notes', summarize(records, 'architecture'), C.info],
275
+ ['Recent Incidents', summarize(records, 'incident'), C.bad],
276
+ ] as const) {
277
+ if (recentSummaryLines.length > 0) {
278
+ recentSummaryLines.push(buildPanelLine(width, [['', C.dim]]));
279
+ }
280
+ recentSummaryLines.push(buildPanelLine(width, [[` ${title}`, C.label]]));
281
+ if (items.length === 0) {
282
+ recentSummaryLines.push(buildPanelLine(width, [[' none recorded', C.dim]]));
283
+ continue;
284
+ }
285
+ for (const item of items) {
286
+ recentSummaryLines.push(buildPanelLine(width, [
287
+ [' ', C.label],
288
+ [item.summary.slice(0, Math.max(0, width - 2)), color],
289
+ ]));
290
+ }
291
+ }
292
+
293
+ const selectedRecord = this.records[this.selectedIndex];
294
+ const selectedLines: Line[] = [];
295
+ if (selectedRecord) {
296
+ selectedLines.push(buildPanelLine(width, [[' Selected', C.label]]));
297
+ selectedLines.push(buildKeyValueLine(width, [
298
+ { label: 'Class', value: selectedRecord.cls, valueColor: C.value },
299
+ { label: 'Scope', value: selectedRecord.scope, valueColor: C.info },
300
+ { label: 'Review', value: selectedRecord.reviewState, valueColor: reviewStateColor(selectedRecord.reviewState) },
301
+ { label: 'Confidence', value: formatConfidence(selectedRecord.confidence), valueColor: C.value },
302
+ ], C));
303
+ selectedLines.push(...buildBodyText(width, `Summary: ${selectedRecord.summary}`, C, C.value));
304
+ if (selectedRecord.detail) selectedLines.push(...buildBodyText(width, `Detail: ${selectedRecord.detail}`, C, C.dim));
305
+ if (selectedRecord.provenance.length) {
306
+ selectedLines.push(...buildBodyText(
307
+ width,
308
+ `Provenance: ${selectedRecord.provenance.map((p) => `${p.kind}:${p.ref}`).join(', ')}`,
309
+ C,
310
+ C.dim,
311
+ ));
312
+ }
313
+ if (selectedRecord.staleReason) {
314
+ selectedLines.push(...buildBodyText(
315
+ width,
316
+ `Stale reason: ${selectedRecord.staleReason}`,
317
+ C,
318
+ selectedRecord.reviewState === 'contradicted' ? C.bad : C.warn,
319
+ ));
320
+ }
321
+ if (selectedRecord.reviewedAt) {
322
+ selectedLines.push(buildPanelLine(width, [
323
+ [' Reviewed: ', C.label],
324
+ [new Date(selectedRecord.reviewedAt).toLocaleString(), C.dim],
325
+ ]));
326
+ if (selectedRecord.reviewedBy) {
327
+ selectedLines.push(buildPanelLine(width, [
328
+ [' Reviewer: ', C.label],
329
+ [selectedRecord.reviewedBy, C.dim],
330
+ ]));
331
+ }
332
+ }
333
+ }
334
+
335
+ return this.renderList(width, height, {
336
+ title: 'Knowledge Control Room',
337
+ header: [...classLines, ...reviewLines],
338
+ footer: [
339
+ ...(selectedLines.length > 0 ? selectedLines : []),
340
+ ...recentSummaryLines,
341
+ buildPanelLine(width, [[' Up/Down move r/Enter reviewed s stale c contradicted f fresh', C.dim]]),
342
+ ],
343
+ });
344
+ }
345
+ }
@@ -0,0 +1,130 @@
1
+ import type { Line } from '../types/grid.ts';
2
+ import { createEmptyLine } from '../types/grid.ts';
3
+ import { ScrollableListPanel } from './scrollable-list-panel.ts';
4
+ import {
5
+ buildDetailBlock,
6
+ buildGuidanceLine,
7
+ buildPanelListRow,
8
+ buildPanelLine,
9
+ buildSummaryBlock,
10
+ buildPanelWorkspace,
11
+ DEFAULT_PANEL_PALETTE,
12
+ type PanelPalette,
13
+ } from './polish.ts';
14
+ import type { LocalAuthSnapshot } from '@pellux/goodvibes-sdk/platform/security';
15
+ import type { LocalAuthInspectionQuery } from '../runtime/ui-service-queries.ts';
16
+
17
+ const C = {
18
+ ...DEFAULT_PANEL_PALETTE,
19
+ info: '#38bdf8',
20
+ warn: '#eab308',
21
+ error: '#ef4444',
22
+ selectBg: '#1e293b',
23
+ } as const;
24
+
25
+ function formatRoles(roles: readonly string[]): string {
26
+ return roles.length > 0 ? roles.join(', ') : '(none)';
27
+ }
28
+
29
+ type LocalAuthUser = LocalAuthSnapshot['users'][number];
30
+
31
+ export class LocalAuthPanel extends ScrollableListPanel<LocalAuthUser> {
32
+ private readonly authManager: LocalAuthInspectionQuery;
33
+
34
+ public constructor(authManager: LocalAuthInspectionQuery) {
35
+ super('local-auth', 'Local Auth', 'U', 'monitoring');
36
+ this.showSelectionGutter = true; // I5: non-color selection affordance
37
+ this.authManager = authManager;
38
+ }
39
+
40
+ protected override getPalette(): PanelPalette {
41
+ return C;
42
+ }
43
+
44
+ protected getItems(): readonly LocalAuthUser[] {
45
+ return this.authManager.inspect().users;
46
+ }
47
+
48
+ protected renderItem(user: LocalAuthUser, _index: number, selected: boolean, width: number): Line {
49
+ return buildPanelListRow(width, [
50
+ { text: user.username.padEnd(20), fg: C.value },
51
+ { text: ` roles=${formatRoles(user.roles)}`.slice(0, Math.max(0, width - 24)), fg: C.info },
52
+ ], C, { selected });
53
+ }
54
+
55
+ protected override getEmptyStateMessage(): string {
56
+ return ' No local auth users configured.';
57
+ }
58
+
59
+ public render(width: number, height: number): Line[] {
60
+ const intro = 'Manage local daemon and HTTP-listener auth users, bootstrap state, and active sessions.';
61
+ const snapshot = this.authManager.inspect();
62
+ const users = this.getItems();
63
+
64
+ const issueMessages: string[] = [];
65
+ if (snapshot.bootstrapCredentialPresent) issueMessages.push('Bootstrap credential file still exists and should be cleared after password rotation.');
66
+ if (snapshot.userCount <= 1) issueMessages.push('Only one local auth user is configured.');
67
+ if (snapshot.sessionCount === 0) issueMessages.push('No active local auth sessions are currently tracked.');
68
+
69
+ const headerLines: Line[] = [
70
+ ...buildSummaryBlock(width, 'Local auth posture', [
71
+ buildPanelLine(width, [
72
+ [' users ', C.label],
73
+ [String(snapshot.userCount), C.value],
74
+ [' sessions ', C.label],
75
+ [String(snapshot.sessionCount), snapshot.sessionCount > 0 ? C.info : C.dim],
76
+ [' bootstrap ', C.label],
77
+ [snapshot.bootstrapCredentialPresent ? 'present' : 'cleared', snapshot.bootstrapCredentialPresent ? C.warn : C.good],
78
+ ]),
79
+ buildPanelLine(width, [[' user store ', C.label], [snapshot.userStorePath.slice(0, Math.max(0, width - 13)), C.dim]]),
80
+ buildPanelLine(width, [[' bootstrap file ', C.label], [snapshot.bootstrapCredentialPath.slice(0, Math.max(0, width - 18)), C.dim]]),
81
+ ...(issueMessages.length > 0
82
+ ? issueMessages.map((issue) => buildPanelLine(width, [[` issue: ${issue}`.slice(0, Math.max(0, width)), C.warn]]))
83
+ : [buildPanelLine(width, [[' local auth posture looks healthy.', C.good]])]),
84
+ buildGuidanceLine(width, '/auth local rotate-password <user> <password>', 'rotate bootstrap/default credentials and revoke older sessions as needed', C),
85
+ ], C),
86
+ ];
87
+
88
+ if (users.length === 0) {
89
+ const workspace = buildPanelWorkspace(width, height, {
90
+ title: 'Local Auth Control Room',
91
+ intro,
92
+ sections: [{ lines: headerLines }],
93
+ palette: C,
94
+ });
95
+ while (workspace.length < height) workspace.push(createEmptyLine(width));
96
+ return workspace;
97
+ }
98
+
99
+ this.clampSelection();
100
+ const selected = users[this.selectedIndex];
101
+
102
+ const footerLines: Line[] = [];
103
+ if (selected) {
104
+ footerLines.push(
105
+ ...buildDetailBlock(width, 'Selected user', [
106
+ buildPanelLine(width, [[' username ', C.label], [selected.username, C.value], [' roles ', C.label], [formatRoles(selected.roles).slice(0, Math.max(0, width - 23)), C.info]]),
107
+ buildPanelLine(width, [[` next: /auth local rotate-password ${selected.username} <password>`.slice(0, Math.max(0, width)), C.dim]]),
108
+ buildPanelLine(width, [[` next: /auth local delete-user ${selected.username}`.slice(0, Math.max(0, width)), C.dim]]),
109
+ ], C),
110
+ );
111
+ }
112
+
113
+ if (snapshot.sessions.length > 0) {
114
+ footerLines.push(
115
+ ...snapshot.sessions.slice(0, 8).map((session) => buildPanelLine(width, [
116
+ [' ', C.label],
117
+ [session.username.padEnd(18), C.value],
118
+ [` expires ${new Date(session.expiresAt).toLocaleString()}`.slice(0, Math.max(0, width - 20)), C.dim],
119
+ ])),
120
+ );
121
+ }
122
+ footerLines.push(buildPanelLine(width, [[' /auth local review /auth local add-user /auth local rotate-password /auth local revoke-session ', C.dim]]));
123
+
124
+ return this.renderList(width, height, {
125
+ title: 'Local Auth Control Room',
126
+ header: headerLines,
127
+ footer: footerLines,
128
+ });
129
+ }
130
+ }
@@ -0,0 +1,212 @@
1
+ import type { Line } from '../types/grid.ts';
2
+ import { ScrollableListPanel } from './scrollable-list-panel.ts';
3
+ import {
4
+ buildEmptyState,
5
+ buildGuidanceLine,
6
+ buildKeyValueLine,
7
+ buildPanelLine,
8
+ buildPanelWorkspace,
9
+ DEFAULT_PANEL_PALETTE,
10
+ type PanelWorkspaceSection,
11
+ } from './polish.ts';
12
+ import {
13
+ type EcosystemCatalogPathOptions,
14
+ listInstalledEcosystemEntries,
15
+ loadEcosystemCatalog,
16
+ reviewEcosystemCatalogEntry,
17
+ type EcosystemCatalogEntry,
18
+ type EcosystemEntryKind,
19
+ } from '@/runtime/index.ts';
20
+ import type { UiMarketplaceSnapshot, UiReadModel } from '../runtime/ui-read-models.ts';
21
+
22
+ const C = {
23
+ ...DEFAULT_PANEL_PALETTE,
24
+ header: '#e2e8f0',
25
+ headerBg: '#1f2937',
26
+ } as const;
27
+
28
+ type MarketplaceRow = {
29
+ kind: EcosystemEntryKind;
30
+ entry: EcosystemCatalogEntry;
31
+ installed: boolean;
32
+ };
33
+
34
+ function statusColor(installed: boolean): string {
35
+ return installed ? C.good : C.dim;
36
+ }
37
+
38
+ export class MarketplacePanel extends ScrollableListPanel<MarketplaceRow> {
39
+ private rows: MarketplaceRow[] = [];
40
+ private readonly unsub: (() => void) | null;
41
+
42
+ public constructor(
43
+ private readonly readModel?: UiReadModel<UiMarketplaceSnapshot>,
44
+ private readonly ecosystemPaths?: EcosystemCatalogPathOptions,
45
+ ) {
46
+ super('marketplace', 'Marketplace', 'M', 'monitoring');
47
+ this.unsub = readModel ? readModel.subscribe(() => this.markDirty()) : null;
48
+ }
49
+
50
+ public override onDestroy(): void {
51
+ this.unsub?.();
52
+ }
53
+
54
+ public override onActivate(): void {
55
+ super.onActivate();
56
+ this.refresh();
57
+ }
58
+
59
+ // ---------------------------------------------------------------------------
60
+ // ScrollableListPanel implementation
61
+ // ---------------------------------------------------------------------------
62
+
63
+ protected getItems(): readonly MarketplaceRow[] {
64
+ return this.rows;
65
+ }
66
+
67
+ protected renderItem(row: MarketplaceRow, index: number, selected: boolean, width: number): Line {
68
+ const bg = selected ? C.selectBg : undefined;
69
+ const provenance = row.entry.provenance ?? 'local';
70
+ return buildPanelLine(width, [
71
+ [' ', C.label, bg],
72
+ [row.kind.padEnd(11), C.info, bg],
73
+ [row.entry.name.slice(0, 20).padEnd(20), C.value, bg],
74
+ [` ${provenance.slice(0, 16).padEnd(16)}`, provenance === 'local' ? C.dim : C.info, bg],
75
+ [` ${(row.installed ? 'INSTALLED' : 'CURATED').padEnd(9)} `, statusColor(row.installed), bg],
76
+ [` ${row.entry.version ?? 'n/a'}`, C.dim, bg],
77
+ ]);
78
+ }
79
+
80
+ protected override getPalette() { return C; }
81
+ protected override getEmptyStateMessage() {
82
+ return this.ecosystemPaths
83
+ ? ' No curated marketplace entries found yet.'
84
+ : ' Marketplace catalog paths are not wired into this panel yet.';
85
+ }
86
+ protected override getEmptyStateActions() {
87
+ return [
88
+ { command: '/marketplace bundle import <path>', summary: 'import a curated marketplace bundle' },
89
+ { command: '/marketplace catalog review', summary: 'inspect the current local catalog posture' },
90
+ { command: '/marketplace publish <kind> <path>', summary: 'publish local ecosystem entries back into the curated catalog' },
91
+ ];
92
+ }
93
+
94
+ private refresh(): void {
95
+ if (!this.ecosystemPaths) {
96
+ this.rows = [];
97
+ this.clampSelection();
98
+ return;
99
+ }
100
+ try {
101
+ const installedPlugins = new Set(listInstalledEcosystemEntries('plugin', this.ecosystemPaths).map((receipt) => receipt.entry.id));
102
+ const installedSkills = new Set(listInstalledEcosystemEntries('skill', this.ecosystemPaths).map((receipt) => receipt.entry.id));
103
+ const installedHookPacks = new Set(listInstalledEcosystemEntries('hook-pack', this.ecosystemPaths).map((receipt) => receipt.entry.id));
104
+ const installedPolicyPacks = new Set(listInstalledEcosystemEntries('policy-pack', this.ecosystemPaths).map((receipt) => receipt.entry.id));
105
+ const rows: MarketplaceRow[] = [
106
+ ...loadEcosystemCatalog('plugin', this.ecosystemPaths).map((entry) => ({ kind: 'plugin' as const, entry, installed: installedPlugins.has(entry.id) })),
107
+ ...loadEcosystemCatalog('skill', this.ecosystemPaths).map((entry) => ({ kind: 'skill' as const, entry, installed: installedSkills.has(entry.id) })),
108
+ ...loadEcosystemCatalog('hook-pack', this.ecosystemPaths).map((entry) => ({ kind: 'hook-pack' as const, entry, installed: installedHookPacks.has(entry.id) })),
109
+ ...loadEcosystemCatalog('policy-pack', this.ecosystemPaths).map((entry) => ({ kind: 'policy-pack' as const, entry, installed: installedPolicyPacks.has(entry.id) })),
110
+ ];
111
+ this.rows = rows.sort((a, b) => a.entry.name.localeCompare(b.entry.name));
112
+ this.clampSelection();
113
+ // I2: clear any previous catalog load error on successful refresh
114
+ this.clearError();
115
+ } catch (e) {
116
+ // I2: surface catalog load failure
117
+ this.setError(`Catalog load failed: ${e instanceof Error ? e.message : String(e)}`);
118
+ }
119
+ }
120
+
121
+ public render(width: number, height: number): Line[] {
122
+ this.clampSelection();
123
+ this.refresh();
124
+
125
+ const intro = 'Curated local-first ecosystem with provenance, compatibility, rollback history, and receipt-aware lifecycle review.';
126
+ const installedCount = this.rows.filter((row) => row.installed).length;
127
+ const snapshot = this.readModel?.getSnapshot();
128
+ const recommendations = snapshot?.recommendations ?? [];
129
+ const startupIssues = snapshot?.startupIssues ?? [];
130
+
131
+ if (this.rows.length === 0) {
132
+ return buildPanelWorkspace(width, height, {
133
+ title: 'Marketplace Control Room',
134
+ intro,
135
+ sections: [{
136
+ lines: buildEmptyState(
137
+ width,
138
+ this.ecosystemPaths ? ' No curated marketplace entries found yet.' : ' Marketplace catalog paths are not wired into this panel yet.',
139
+ this.ecosystemPaths
140
+ ? 'The marketplace is ready, but no plugin, skill, hook-pack, or policy-pack catalogs are available in this workspace.'
141
+ : 'The shell needs explicit marketplace catalog roots before this panel can inspect curated plugin, skill, hook-pack, or policy-pack entries.',
142
+ [
143
+ { command: '/marketplace bundle import <path>', summary: 'import a curated marketplace bundle' },
144
+ { command: '/marketplace catalog review', summary: 'inspect the current local catalog posture' },
145
+ { command: '/marketplace publish <kind> <path>', summary: 'publish local ecosystem entries back into the curated catalog' },
146
+ ],
147
+ C,
148
+ ),
149
+ }],
150
+ palette: C,
151
+ });
152
+ }
153
+
154
+ const postureLines = [
155
+ buildKeyValueLine(width, [
156
+ { label: 'curated', value: String(this.rows.length), valueColor: C.value },
157
+ { label: 'installed', value: String(installedCount), valueColor: installedCount > 0 ? C.good : C.dim },
158
+ { label: 'plugins', value: String(this.rows.filter((row) => row.kind === 'plugin').length), valueColor: C.info },
159
+ { label: 'skills', value: String(this.rows.filter((row) => row.kind === 'skill').length), valueColor: C.info },
160
+ { label: 'hooks', value: String(this.rows.filter((row) => row.kind === 'hook-pack').length), valueColor: C.info },
161
+ { label: 'policies', value: String(this.rows.filter((row) => row.kind === 'policy-pack').length), valueColor: C.info },
162
+ ], C),
163
+ buildGuidanceLine(width, '/marketplace open', 'browse curated entries and inspect compatibility, provenance, and receipts', C),
164
+ ];
165
+
166
+ const recommendationLines = recommendations.length > 0
167
+ ? recommendations.slice(0, 4).map((recommendation) => buildPanelLine(width, [
168
+ [' ', C.label],
169
+ [`${recommendation.kind} ${recommendation.entry.id}`.slice(0, 28).padEnd(28), C.info],
170
+ [` ${recommendation.title}`.slice(0, Math.max(0, width - 31)), C.dim],
171
+ ]))
172
+ : [buildPanelLine(width, [[' No contextual marketplace recommendations right now.', C.dim]])];
173
+
174
+ const startupIssueLines = startupIssues.length > 0
175
+ ? startupIssues.slice(0, 4).map((issue) => buildPanelLine(width, [[' ', C.label], [issue.slice(0, Math.max(0, width - 2)), C.warn]]))
176
+ : [buildPanelLine(width, [[' No startup or lifecycle issues are currently pushing marketplace repair recommendations.', C.dim]])];
177
+
178
+ const selectedRow = this.rows[this.selectedIndex];
179
+ const selectedLines: Line[] = [];
180
+ if (selectedRow) {
181
+ const review = reviewEcosystemCatalogEntry(selectedRow.entry, this.ecosystemPaths!);
182
+ selectedLines.push(buildPanelLine(width, [
183
+ [' Provenance: ', C.label],
184
+ [(selectedRow.entry.provenance ?? '(none)').slice(0, Math.max(0, width - 15)), selectedRow.entry.provenance ? C.info : C.dim],
185
+ ]));
186
+ selectedLines.push(buildPanelLine(width, [
187
+ [' Source: ', C.label],
188
+ [selectedRow.entry.source.slice(0, Math.max(0, width - 11)), C.value],
189
+ ]));
190
+ selectedLines.push(buildKeyValueLine(width, [
191
+ { label: 'Compatibility', value: review.compatibility.status, valueColor: review.compatibility.status === 'supported' ? C.good : C.warn },
192
+ { label: 'Risk', value: review.riskLevel, valueColor: review.riskLevel === 'low' ? C.good : C.warn },
193
+ { label: 'State', value: selectedRow.installed ? 'installed' : 'curated', valueColor: statusColor(selectedRow.installed) },
194
+ ], C));
195
+ selectedLines.push(buildGuidanceLine(width, '/marketplace review <id>', 'inspect full compatibility and receipt detail for the selected entry', C));
196
+ }
197
+
198
+ const postureSection: PanelWorkspaceSection = { title: 'Marketplace posture', lines: postureLines };
199
+ const startupIssuesSection: PanelWorkspaceSection = { title: 'Startup Issues', lines: startupIssueLines };
200
+ const recommendationsSection: PanelWorkspaceSection = { title: 'Recommendations', lines: recommendationLines };
201
+
202
+ return this.renderList(width, height, {
203
+ title: 'Marketplace Control Room',
204
+ header: [
205
+ ...postureLines,
206
+ ...startupIssueLines,
207
+ ...recommendationLines,
208
+ ],
209
+ footer: selectedLines.length > 0 && height >= 20 ? selectedLines : [],
210
+ });
211
+ }
212
+ }