@pellux/goodvibes-tui 1.1.0 → 1.9.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 (267) hide show
  1. package/CHANGELOG.md +85 -21
  2. package/README.md +28 -18
  3. package/docs/foundation-artifacts/operator-contract.json +4045 -1763
  4. package/package.json +2 -2
  5. package/src/audio/spoken-turn-controller.ts +12 -2
  6. package/src/audio/spoken-turn-wiring.ts +2 -1
  7. package/src/cli/help.ts +8 -1
  8. package/src/cli/service-posture.ts +2 -1
  9. package/src/cli/status.ts +2 -1
  10. package/src/cli/surface-command.ts +2 -2
  11. package/src/core/alert-gating.ts +67 -0
  12. package/src/core/approval-alert.ts +72 -0
  13. package/src/core/budget-breach-notifier.ts +106 -0
  14. package/src/core/composer-state.ts +11 -3
  15. package/src/core/conversation-line-cache.ts +27 -3
  16. package/src/core/conversation-rendering.ts +90 -14
  17. package/src/core/conversation.ts +18 -0
  18. package/src/core/focus-tracker.ts +41 -0
  19. package/src/core/long-task-notifier.ts +33 -6
  20. package/src/core/stream-event-wiring.ts +20 -1
  21. package/src/core/system-message-noise.ts +87 -0
  22. package/src/core/system-message-router.ts +104 -51
  23. package/src/core/turn-cancellation.ts +25 -0
  24. package/src/core/turn-event-wiring.ts +72 -3
  25. package/src/daemon/cli.ts +29 -2
  26. package/src/daemon/handlers/register.ts +8 -1
  27. package/src/daemon/service-commands.ts +329 -0
  28. package/src/export/cost-utils.ts +36 -0
  29. package/src/input/autocomplete.ts +27 -1
  30. package/src/input/command-registry.ts +84 -5
  31. package/src/input/commands/checkpoint-runtime.ts +280 -0
  32. package/src/input/commands/codebase-runtime.ts +232 -0
  33. package/src/input/commands/config.ts +43 -3
  34. package/src/input/commands/eval.ts +1 -1
  35. package/src/input/commands/health-runtime.ts +10 -2
  36. package/src/input/commands/image-runtime.ts +112 -0
  37. package/src/input/commands/intelligence-runtime.ts +11 -1
  38. package/src/input/commands/local-auth-runtime.ts +4 -1
  39. package/src/input/commands/local-runtime.ts +9 -3
  40. package/src/input/commands/marketplace-runtime.ts +2 -2
  41. package/src/input/commands/memory.ts +73 -35
  42. package/src/input/commands/operator-panel-runtime.ts +71 -31
  43. package/src/input/commands/planning-runtime.ts +116 -4
  44. package/src/input/commands/platform-sandbox-runtime.ts +1 -6
  45. package/src/input/commands/plugin-runtime.ts +2 -2
  46. package/src/input/commands/policy-dispatch.ts +21 -0
  47. package/src/input/commands/provider-accounts-runtime.ts +1 -1
  48. package/src/input/commands/qrcode-runtime.ts +26 -6
  49. package/src/input/commands/recall-review.ts +35 -0
  50. package/src/input/commands/remote-runtime-setup.ts +5 -3
  51. package/src/input/commands/remote-runtime.ts +3 -3
  52. package/src/input/commands/runtime-services.ts +54 -13
  53. package/src/input/commands/services-runtime.ts +1 -1
  54. package/src/input/commands/session-content.ts +20 -9
  55. package/src/input/commands/session-workflow.ts +16 -1
  56. package/src/input/commands/settings-sync-runtime.ts +16 -4
  57. package/src/input/commands/shell-core.ts +25 -8
  58. package/src/input/commands/skills-runtime.ts +2 -8
  59. package/src/input/commands/subscription-runtime.ts +2 -2
  60. package/src/input/commands/test-runtime.ts +277 -0
  61. package/src/input/commands/websearch-runtime.ts +101 -0
  62. package/src/input/commands/work-plan-runtime.ts +36 -10
  63. package/src/input/commands/workstream-runtime.ts +488 -0
  64. package/src/input/commands.ts +12 -0
  65. package/src/input/config-modal-types.ts +175 -0
  66. package/src/input/config-modal.ts +592 -0
  67. package/src/input/feed-context-factory.ts +10 -8
  68. package/src/input/handler-command-route.ts +37 -20
  69. package/src/input/handler-content-actions.ts +17 -2
  70. package/src/input/handler-feed-routes.ts +74 -114
  71. package/src/input/handler-feed.ts +79 -27
  72. package/src/input/handler-interactions.ts +1 -3
  73. package/src/input/handler-modal-routes.ts +136 -6
  74. package/src/input/handler-modal-stack.ts +14 -7
  75. package/src/input/handler-modal-token-routes.ts +16 -49
  76. package/src/input/handler-onboarding-daemon-adopt.ts +149 -0
  77. package/src/input/handler-onboarding.ts +71 -59
  78. package/src/input/handler-picker-routes.ts +26 -102
  79. package/src/input/handler-shortcuts.ts +74 -14
  80. package/src/input/handler-types.ts +2 -6
  81. package/src/input/handler-ui-state.ts +10 -22
  82. package/src/input/handler.ts +12 -29
  83. package/src/input/keybindings.ts +23 -8
  84. package/src/input/model-picker-types.ts +24 -6
  85. package/src/input/model-picker.ts +77 -2
  86. package/src/input/onboarding/onboarding-runtime-status.ts +10 -1
  87. package/src/input/onboarding/onboarding-wizard-apply.ts +8 -1
  88. package/src/input/onboarding/onboarding-wizard-constants.ts +4 -1
  89. package/src/input/onboarding/onboarding-wizard-network-adopt.ts +136 -0
  90. package/src/input/onboarding/onboarding-wizard-steps.ts +9 -6
  91. package/src/input/onboarding/onboarding-wizard-types.ts +3 -1
  92. package/src/input/panel-integration-actions.ts +9 -170
  93. package/src/input/panel-mouse-geometry.ts +97 -0
  94. package/src/input/panel-paste-flood-guard.ts +86 -0
  95. package/src/input/selection-modal.ts +11 -0
  96. package/src/input/session-picker-modal.ts +44 -4
  97. package/src/input/settings-modal-data.ts +146 -2
  98. package/src/input/settings-modal-mutations.ts +6 -4
  99. package/src/input/settings-modal-types.ts +4 -2
  100. package/src/main.ts +49 -51
  101. package/src/panels/agent-inspector-shared.ts +2 -1
  102. package/src/panels/base-panel.ts +22 -1
  103. package/src/panels/builtin/agent.ts +21 -107
  104. package/src/panels/builtin/development.ts +15 -61
  105. package/src/panels/builtin/knowledge.ts +8 -32
  106. package/src/panels/builtin/operations.ts +84 -428
  107. package/src/panels/builtin/session.ts +21 -112
  108. package/src/panels/builtin/shared.ts +18 -46
  109. package/src/panels/builtin-modals.ts +218 -0
  110. package/src/panels/builtin-panels.ts +5 -0
  111. package/src/panels/cost-tracker-panel.ts +36 -10
  112. package/src/panels/diff-panel.ts +12 -43
  113. package/src/panels/eval-registry.ts +60 -0
  114. package/src/panels/fleet-deep-link.ts +31 -0
  115. package/src/panels/fleet-panel-format.ts +62 -0
  116. package/src/panels/fleet-panel-worktree-detail.ts +48 -0
  117. package/src/panels/fleet-panel.ts +758 -0
  118. package/src/panels/fleet-read-model.ts +520 -0
  119. package/src/panels/fleet-steer.ts +125 -0
  120. package/src/panels/fleet-stop.ts +153 -0
  121. package/src/panels/fleet-tabs.ts +230 -0
  122. package/src/panels/fleet-transcript.ts +356 -0
  123. package/src/panels/index.ts +7 -31
  124. package/src/panels/modals/hooks-modal.ts +187 -0
  125. package/src/panels/modals/keybindings-modal.ts +166 -0
  126. package/src/panels/modals/knowledge-modal.ts +158 -0
  127. package/src/panels/modals/local-auth-modal.ts +132 -0
  128. package/src/panels/modals/marketplace-modal.ts +218 -0
  129. package/src/panels/modals/memory-modal.ts +180 -0
  130. package/src/panels/modals/modal-surface-helpers.ts +54 -0
  131. package/src/panels/modals/modal-theme.ts +42 -0
  132. package/src/panels/modals/pairing-modal.ts +163 -0
  133. package/src/panels/modals/planning-modal.ts +304 -0
  134. package/src/panels/modals/plugins-modal.ts +174 -0
  135. package/src/panels/modals/policy-modal.ts +256 -0
  136. package/src/panels/modals/provider-health-modal.ts +136 -0
  137. package/src/panels/modals/remote-modal.ts +115 -0
  138. package/src/panels/modals/sandbox-modal.ts +150 -0
  139. package/src/panels/modals/security-modal.ts +217 -0
  140. package/src/panels/modals/services-modal.ts +179 -0
  141. package/src/panels/modals/settings-sync-modal.ts +142 -0
  142. package/src/panels/modals/skills-modal.ts +189 -0
  143. package/src/panels/modals/subscription-modal.ts +172 -0
  144. package/src/panels/modals/work-plan-modal.ts +177 -0
  145. package/src/panels/panel-confirm-overlay.ts +72 -0
  146. package/src/panels/panel-manager.ts +123 -9
  147. package/src/panels/polish-core.ts +38 -25
  148. package/src/panels/project-planning-answer-actions.ts +8 -11
  149. package/src/panels/skills-panel.ts +7 -51
  150. package/src/panels/types.ts +37 -0
  151. package/src/permissions/hunk-selection.ts +179 -0
  152. package/src/permissions/prompt.ts +216 -13
  153. package/src/renderer/autocomplete-overlay.ts +28 -2
  154. package/src/renderer/compaction-history-modal.ts +19 -3
  155. package/src/renderer/compaction-preview.ts +13 -2
  156. package/src/renderer/compaction-quality.ts +100 -0
  157. package/src/renderer/compositor.ts +2 -3
  158. package/src/renderer/config-modal.ts +95 -0
  159. package/src/renderer/conversation-overlays.ts +10 -17
  160. package/src/renderer/fleet-tab-strip.ts +56 -0
  161. package/src/renderer/footer-tips.ts +15 -3
  162. package/src/renderer/fullscreen-primitives.ts +32 -22
  163. package/src/renderer/git-status.ts +43 -1
  164. package/src/renderer/help-overlay.ts +2 -2
  165. package/src/renderer/layout.ts +0 -4
  166. package/src/renderer/markdown.ts +7 -3
  167. package/src/renderer/modal-factory.ts +25 -20
  168. package/src/renderer/model-workspace.ts +62 -4
  169. package/src/renderer/overlay-box.ts +21 -17
  170. package/src/renderer/panel-workspace-bar.ts +8 -2
  171. package/src/renderer/process-indicator.ts +14 -3
  172. package/src/renderer/selection-modal-overlay.ts +6 -1
  173. package/src/renderer/session-picker-modal.ts +196 -3
  174. package/src/renderer/settings-modal-helpers.ts +2 -0
  175. package/src/renderer/settings-modal.ts +7 -0
  176. package/src/renderer/shell-surface.ts +8 -1
  177. package/src/renderer/status-glyphs.ts +14 -15
  178. package/src/renderer/system-message.ts +15 -3
  179. package/src/renderer/terminal-bg-probe.ts +339 -0
  180. package/src/renderer/terminal-escapes.ts +20 -0
  181. package/src/renderer/theme-mode-config.ts +67 -0
  182. package/src/renderer/theme.ts +91 -1
  183. package/src/renderer/thinking.ts +11 -3
  184. package/src/renderer/tool-call.ts +15 -9
  185. package/src/renderer/tool-result-summary.ts +148 -0
  186. package/src/renderer/turn-injection.ts +133 -0
  187. package/src/renderer/ui-factory.ts +154 -85
  188. package/src/renderer/ui-primitives.ts +30 -129
  189. package/src/runtime/bootstrap-command-context.ts +27 -1
  190. package/src/runtime/bootstrap-command-parts.ts +41 -10
  191. package/src/runtime/bootstrap-core.ts +47 -15
  192. package/src/runtime/bootstrap-hook-bridge.ts +7 -0
  193. package/src/runtime/bootstrap-shell.ts +48 -17
  194. package/src/runtime/bootstrap.ts +129 -22
  195. package/src/runtime/code-index-services.ts +135 -0
  196. package/src/runtime/legacy-daemon-migration.ts +516 -0
  197. package/src/runtime/memory-fold.ts +26 -0
  198. package/src/runtime/onboarding/derivation.ts +7 -2
  199. package/src/runtime/onboarding/snapshot.ts +27 -1
  200. package/src/runtime/onboarding/types.ts +31 -1
  201. package/src/runtime/operator-token-cleanup.ts +82 -1
  202. package/src/runtime/orchestrator-core-services.ts +59 -0
  203. package/src/runtime/process-lifecycle.ts +3 -1
  204. package/src/runtime/resume-notice.ts +209 -0
  205. package/src/runtime/services.ts +98 -46
  206. package/src/runtime/session-inbound-inputs.ts +252 -0
  207. package/src/runtime/session-spine-transport.ts +64 -0
  208. package/src/runtime/terminal-output-guard.ts +15 -8
  209. package/src/runtime/ui-services.ts +27 -3
  210. package/src/runtime/workstream-services.ts +327 -0
  211. package/src/runtime/wrfc-persistence.ts +124 -17
  212. package/src/shell/blocking-input.ts +68 -4
  213. package/src/shell/recovery-input-helpers.ts +170 -1
  214. package/src/shell/ui-openers.ts +171 -26
  215. package/src/utils/format-duration.ts +8 -18
  216. package/src/utils/splash-lines.ts +1 -1
  217. package/src/utils/terminal-width.ts +52 -0
  218. package/src/version.ts +1 -1
  219. package/src/panels/agent-inspector-panel.ts +0 -786
  220. package/src/panels/approval-panel.ts +0 -252
  221. package/src/panels/automation-control-panel.ts +0 -479
  222. package/src/panels/cockpit-panel.ts +0 -481
  223. package/src/panels/cockpit-read-model.ts +0 -238
  224. package/src/panels/communication-panel.ts +0 -256
  225. package/src/panels/control-plane-panel.ts +0 -470
  226. package/src/panels/debug-panel.ts +0 -615
  227. package/src/panels/docs-panel.ts +0 -384
  228. package/src/panels/eval-panel.ts +0 -627
  229. package/src/panels/file-explorer-panel.ts +0 -673
  230. package/src/panels/file-preview-panel.ts +0 -517
  231. package/src/panels/hooks-panel.ts +0 -401
  232. package/src/panels/incident-review-panel.ts +0 -406
  233. package/src/panels/intelligence-panel.ts +0 -383
  234. package/src/panels/knowledge-graph-panel.ts +0 -515
  235. package/src/panels/marketplace-panel.ts +0 -399
  236. package/src/panels/memory-panel.ts +0 -558
  237. package/src/panels/ops-control-panel.ts +0 -329
  238. package/src/panels/ops-strategy-panel.ts +0 -321
  239. package/src/panels/orchestration-panel.ts +0 -465
  240. package/src/panels/panel-list-panel.ts +0 -566
  241. package/src/panels/plan-dashboard-panel.ts +0 -707
  242. package/src/panels/policy-panel.ts +0 -517
  243. package/src/panels/project-planning-panel.ts +0 -731
  244. package/src/panels/provider-health-panel.ts +0 -678
  245. package/src/panels/provider-health-tracker.ts +0 -310
  246. package/src/panels/provider-health-views.ts +0 -567
  247. package/src/panels/qr-panel.ts +0 -280
  248. package/src/panels/remote-panel.ts +0 -534
  249. package/src/panels/routes-panel.ts +0 -241
  250. package/src/panels/sandbox-panel.ts +0 -456
  251. package/src/panels/security-panel.ts +0 -447
  252. package/src/panels/services-panel.ts +0 -329
  253. package/src/panels/session-browser-panel.ts +0 -496
  254. package/src/panels/settings-sync-panel.ts +0 -398
  255. package/src/panels/subscription-panel.ts +0 -342
  256. package/src/panels/symbol-outline-panel.ts +0 -619
  257. package/src/panels/system-messages-panel.ts +0 -364
  258. package/src/panels/tasks-panel.ts +0 -608
  259. package/src/panels/thinking-panel.ts +0 -333
  260. package/src/panels/tool-inspector-panel.ts +0 -537
  261. package/src/panels/work-plan-panel.ts +0 -540
  262. package/src/panels/worktree-panel.ts +0 -360
  263. package/src/panels/wrfc-panel.ts +0 -790
  264. package/src/renderer/agent-detail-modal.ts +0 -466
  265. package/src/renderer/live-tail-modal.ts +0 -156
  266. package/src/renderer/process-modal.ts +0 -671
  267. package/src/renderer/qr-renderer.ts +0 -120
@@ -1,399 +0,0 @@
1
- import type { Line } from '../types/grid.ts';
2
- import { createEmptyLine } from '../types/grid.ts';
3
- import { fitDisplay, truncateDisplay } from '../utils/terminal-width.ts';
4
- import { ScrollableListPanel } from './scrollable-list-panel.ts';
5
- import { type ConfirmState, handleConfirmInput, renderConfirmLines } from './confirm-state.ts';
6
- import {
7
- buildEmptyState,
8
- buildGuidanceLine,
9
- buildKeyValueLine,
10
- buildKeyboardHints,
11
- buildPanelLine,
12
- buildPanelWorkspace,
13
- DEFAULT_PANEL_PALETTE,
14
- } from './polish.ts';
15
- import {
16
- type EcosystemCatalogPathOptions,
17
- installEcosystemCatalogEntry,
18
- listInstalledEcosystemEntries,
19
- loadEcosystemCatalog,
20
- reviewEcosystemCatalogEntry,
21
- uninstallEcosystemCatalogEntry,
22
- type EcosystemCatalogEntry,
23
- type EcosystemEntryKind,
24
- } from '@/runtime/index.ts';
25
- import type { UiMarketplaceSnapshot, UiReadModel } from '../runtime/ui-read-models.ts';
26
- import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
27
-
28
- // Base chrome only — title band and text tokens come straight from
29
- // DEFAULT_PANEL_PALETTE (WO-002).
30
- const C = DEFAULT_PANEL_PALETTE;
31
-
32
- type MarketplaceReview = ReturnType<typeof reviewEcosystemCatalogEntry>;
33
-
34
- type MarketplaceRow = {
35
- kind: EcosystemEntryKind;
36
- entry: EcosystemCatalogEntry;
37
- installed: boolean;
38
- /**
39
- * Computed once per refresh() (not per render()) so that render() never
40
- * touches disk — see the class-level note on the render()/refresh() split.
41
- */
42
- review: MarketplaceReview;
43
- };
44
-
45
- /** What a pending install/uninstall confirm will do once the user confirms. */
46
- interface MarketplaceConfirmSubject {
47
- readonly kind: EcosystemEntryKind;
48
- readonly entryId: string;
49
- readonly action: 'install' | 'uninstall';
50
- }
51
-
52
- function statusColor(installed: boolean): string {
53
- return installed ? C.good : C.dim;
54
- }
55
-
56
- export class MarketplacePanel extends ScrollableListPanel<MarketplaceRow> {
57
- private rows: MarketplaceRow[] = [];
58
- private readonly unsub: (() => void) | null;
59
- // I1: confirm state for install/uninstall (destructive-adjacent — mutates disk)
60
- private confirm: ConfirmState<MarketplaceConfirmSubject> | null = null;
61
- // Entry id whose full review detail is expanded (Enter toggles); cleared on refresh.
62
- private expandedEntryId: string | null = null;
63
-
64
- public constructor(
65
- private readonly readModel?: UiReadModel<UiMarketplaceSnapshot>,
66
- private readonly ecosystemPaths?: EcosystemCatalogPathOptions,
67
- ) {
68
- super('marketplace', 'Marketplace', '◩', 'automation-control');
69
- this.filterEnabled = true;
70
- this.filterLabel = 'Filter marketplace';
71
- this.unsub = readModel ? readModel.subscribe(() => this.markDirty()) : null;
72
- }
73
-
74
- protected override filterMatches(row: MarketplaceRow, q: string): boolean {
75
- return row.kind.toLowerCase().includes(q)
76
- || row.entry.name.toLowerCase().includes(q)
77
- || (row.entry.provenance ?? 'local').toLowerCase().includes(q);
78
- }
79
-
80
- public override onDestroy(): void {
81
- this.unsub?.();
82
- }
83
-
84
- // Disk reload happens here (activate) and on explicit 'r' refresh — never
85
- // inside render(), which used to reload all four catalogs every frame.
86
- public override onActivate(): void {
87
- super.onActivate();
88
- this.refresh();
89
- }
90
-
91
- protected override onSelect(row: MarketplaceRow): void {
92
- // Enter toggles the full review-detail block for the selected entry.
93
- this.expandedEntryId = this.expandedEntryId === row.entry.id ? null : row.entry.id;
94
- this.markDirty();
95
- }
96
-
97
- // ---------------------------------------------------------------------------
98
- // ScrollableListPanel implementation
99
- // ---------------------------------------------------------------------------
100
-
101
- protected getItems(): readonly MarketplaceRow[] {
102
- return this.rows;
103
- }
104
-
105
- protected renderItem(row: MarketplaceRow, index: number, selected: boolean, width: number): Line {
106
- const bg = selected ? C.selectBg : undefined;
107
- const provenance = row.entry.provenance ?? 'local';
108
- return buildPanelLine(width, [
109
- [' ', C.label, bg],
110
- [row.kind.padEnd(11), C.info, bg],
111
- [fitDisplay(row.entry.name, 20), C.value, bg],
112
- [` ${fitDisplay(provenance, 16)}`, provenance === 'local' ? C.dim : C.info, bg],
113
- [` ${(row.installed ? 'INSTALLED' : 'CURATED').padEnd(9)} `, statusColor(row.installed), bg],
114
- [` ${row.entry.version ?? 'n/a'}`, C.dim, bg],
115
- ]);
116
- }
117
-
118
- protected override getPalette() { return C; }
119
- protected override getEmptyStateMessage() {
120
- return this.ecosystemPaths
121
- ? ' No curated marketplace entries found yet.'
122
- : ' Marketplace catalog paths are not wired into this panel yet.';
123
- }
124
- protected override getEmptyStateActions() {
125
- return [
126
- { command: '/marketplace bundle import <path>', summary: 'import a curated marketplace bundle' },
127
- { command: '/marketplace catalog review', summary: 'inspect the current local catalog posture' },
128
- { command: '/marketplace publish <kind> <path>', summary: 'publish local ecosystem entries back into the curated catalog' },
129
- ];
130
- }
131
-
132
- public override handleInput(key: string): boolean {
133
- if (this.lastError !== null) this.clearError();
134
-
135
- const confirmResult = handleConfirmInput(this.confirm, key);
136
- if (confirmResult === 'confirmed') {
137
- const subject = this.confirm!.subject;
138
- this.confirm = null;
139
- this._applyConfirmedAction(subject);
140
- this.markDirty();
141
- return true;
142
- }
143
- if (confirmResult === 'cancelled') {
144
- this.confirm = null;
145
- this.markDirty();
146
- return true;
147
- }
148
- if (confirmResult === 'absorbed') return true;
149
-
150
- if (!this.filterActive) {
151
- if (key === 'r') {
152
- this.refresh();
153
- this.markDirty();
154
- return true;
155
- }
156
-
157
- // selectedIndex is relative to the filtered view, so the acted-on row
158
- // must come from getVisibleItems() — this.rows would desync under '/'.
159
- const selectedRow = this.getSelectedItem();
160
- if (key === 'i' && selectedRow && !selectedRow.installed && this.ecosystemPaths) {
161
- this.confirm = {
162
- subject: { kind: selectedRow.kind, entryId: selectedRow.entry.id, action: 'install' },
163
- label: `${selectedRow.kind} ${selectedRow.entry.name}`,
164
- verb: 'Install',
165
- };
166
- this.markDirty();
167
- return true;
168
- }
169
- if (key === 'u' && selectedRow && selectedRow.installed && this.ecosystemPaths) {
170
- this.confirm = {
171
- subject: { kind: selectedRow.kind, entryId: selectedRow.entry.id, action: 'uninstall' },
172
- label: `${selectedRow.kind} ${selectedRow.entry.name}`,
173
- verb: 'Uninstall',
174
- };
175
- this.markDirty();
176
- return true;
177
- }
178
-
179
- // Recommendations become actionable jumps: pressing the digit shown next
180
- // to a recommendation row jumps the selection to (and expands) that
181
- // catalog entry, rather than only printing a static suggestion.
182
- if (/^[1-9]$/.test(key)) {
183
- const recommendations = this.readModel?.getSnapshot()?.recommendations ?? [];
184
- const recommendation = recommendations[Number(key) - 1];
185
- if (recommendation) {
186
- // Search the visible (possibly filtered) list: selectedIndex is
187
- // visible-relative, so a this.rows index would land on the wrong row.
188
- const targetIndex = this.getVisibleItems().findIndex((row) => row.kind === recommendation.kind && row.entry.id === recommendation.entry.id);
189
- if (targetIndex >= 0) {
190
- this.selectedIndex = targetIndex;
191
- this.expandedEntryId = recommendation.entry.id;
192
- this.markDirty();
193
- return true;
194
- }
195
- }
196
- }
197
- }
198
-
199
- return super.handleInput(key);
200
- }
201
-
202
- private _applyConfirmedAction(subject: MarketplaceConfirmSubject): void {
203
- if (!this.ecosystemPaths) return;
204
- const verb = subject.action === 'install' ? 'Install' : 'Uninstall';
205
- try {
206
- const result = subject.action === 'install'
207
- ? installEcosystemCatalogEntry(subject.kind, subject.entryId, this.ecosystemPaths)
208
- : uninstallEcosystemCatalogEntry(subject.kind, subject.entryId, this.ecosystemPaths);
209
- if (!result.ok) {
210
- this.setError(`${verb} failed: ${result.error}`);
211
- return;
212
- }
213
- this.clearError();
214
- this.refresh();
215
- } catch (e) {
216
- this.setError(`${verb} failed: ${summarizeError(e)}`);
217
- }
218
- }
219
-
220
- private refresh(): void {
221
- if (!this.ecosystemPaths) {
222
- this.rows = [];
223
- this.clampSelection();
224
- return;
225
- }
226
- const ecosystemPaths = this.ecosystemPaths;
227
- try {
228
- const installedPlugins = new Set(listInstalledEcosystemEntries('plugin', ecosystemPaths).map((receipt) => receipt.entry.id));
229
- const installedSkills = new Set(listInstalledEcosystemEntries('skill', ecosystemPaths).map((receipt) => receipt.entry.id));
230
- const installedHookPacks = new Set(listInstalledEcosystemEntries('hook-pack', ecosystemPaths).map((receipt) => receipt.entry.id));
231
- const installedPolicyPacks = new Set(listInstalledEcosystemEntries('policy-pack', ecosystemPaths).map((receipt) => receipt.entry.id));
232
- const build = (kind: EcosystemEntryKind, installed: Set<string>): MarketplaceRow[] =>
233
- loadEcosystemCatalog(kind, ecosystemPaths).map((entry) => ({
234
- kind,
235
- entry,
236
- installed: installed.has(entry.id),
237
- review: reviewEcosystemCatalogEntry(entry, ecosystemPaths),
238
- }));
239
- const rows: MarketplaceRow[] = [
240
- ...build('plugin', installedPlugins),
241
- ...build('skill', installedSkills),
242
- ...build('hook-pack', installedHookPacks),
243
- ...build('policy-pack', installedPolicyPacks),
244
- ];
245
- this.rows = rows.sort((a, b) => a.entry.name.localeCompare(b.entry.name));
246
- this.clampSelection();
247
- // I2: clear any previous catalog load error on successful refresh
248
- this.clearError();
249
- } catch (e) {
250
- // I2: surface catalog load failure
251
- this.setError(`Catalog load failed: ${summarizeError(e)}`);
252
- }
253
- }
254
-
255
- public render(width: number, height: number): Line[] {
256
- this.clampSelection();
257
-
258
- if (this.confirm) {
259
- const lines = buildPanelWorkspace(width, height, {
260
- title: 'Marketplace Control Room',
261
- sections: [{ title: 'Confirmation', lines: renderConfirmLines(width, this.confirm) }],
262
- palette: C,
263
- });
264
- while (lines.length < height) lines.push(createEmptyLine(width));
265
- return lines.slice(0, height);
266
- }
267
-
268
- const intro = 'Curated local-first ecosystem with provenance, compatibility, rollback history, and receipt-aware lifecycle review.';
269
- const installedCount = this.rows.filter((row) => row.installed).length;
270
- const snapshot = this.readModel?.getSnapshot();
271
- const recommendations = snapshot?.recommendations ?? [];
272
- const startupIssues = snapshot?.startupIssues ?? [];
273
-
274
- if (this.rows.length === 0) {
275
- return buildPanelWorkspace(width, height, {
276
- title: 'Marketplace Control Room',
277
- intro,
278
- sections: [{
279
- lines: buildEmptyState(
280
- width,
281
- this.ecosystemPaths ? ' No curated marketplace entries found yet.' : ' Marketplace catalog paths are not wired into this panel yet.',
282
- this.ecosystemPaths
283
- ? 'The marketplace is ready, but no plugin, skill, hook-pack, or policy-pack catalogs are available in this workspace.'
284
- : 'The shell needs explicit marketplace catalog roots before this panel can inspect curated plugin, skill, hook-pack, or policy-pack entries.',
285
- [
286
- { command: '/marketplace bundle import <path>', summary: 'import a curated marketplace bundle' },
287
- { command: '/marketplace catalog review', summary: 'inspect the current local catalog posture' },
288
- { command: '/marketplace publish <kind> <path>', summary: 'publish local ecosystem entries back into the curated catalog' },
289
- ],
290
- C,
291
- ),
292
- }],
293
- palette: C,
294
- });
295
- }
296
-
297
- const postureLines = [
298
- buildKeyValueLine(width, [
299
- { label: 'curated', value: String(this.rows.length), valueColor: C.value },
300
- { label: 'installed', value: String(installedCount), valueColor: installedCount > 0 ? C.good : C.dim },
301
- { label: 'plugins', value: String(this.rows.filter((row) => row.kind === 'plugin').length), valueColor: C.info },
302
- { label: 'skills', value: String(this.rows.filter((row) => row.kind === 'skill').length), valueColor: C.info },
303
- { label: 'hooks', value: String(this.rows.filter((row) => row.kind === 'hook-pack').length), valueColor: C.info },
304
- { label: 'policies', value: String(this.rows.filter((row) => row.kind === 'policy-pack').length), valueColor: C.info },
305
- ], C),
306
- ];
307
-
308
- // Recommendations become actionable jumps: each row is numbered and the
309
- // matching digit key jumps the main selection straight to that entry
310
- // (see handleInput) instead of only printing a static suggestion.
311
- const recommendationLines = recommendations.length > 0
312
- ? recommendations.slice(0, 4).map((recommendation, index) => buildPanelLine(width, [
313
- [` [${index + 1}] `, C.info],
314
- [fitDisplay(`${recommendation.kind} ${recommendation.entry.id}`, 26), C.info],
315
- [truncateDisplay(` ${recommendation.title}`, Math.max(0, width - 33)), C.dim],
316
- ]))
317
- : [buildPanelLine(width, [[' No contextual marketplace recommendations right now.', C.dim]])];
318
-
319
- const startupIssueLines = startupIssues.length > 0
320
- ? startupIssues.slice(0, 4).map((issue) => buildPanelLine(width, [[' ', C.label], [truncateDisplay(issue, Math.max(0, width - 2)), C.warn]]))
321
- : [buildPanelLine(width, [[' No startup or lifecycle issues are currently pushing marketplace repair recommendations.', C.dim]])];
322
-
323
- const selectedRow = this.getSelectedItem();
324
- const selectedLines: Line[] = [];
325
- if (selectedRow) {
326
- // review is computed once in refresh() (not here) so render() never
327
- // touches disk.
328
- const review = selectedRow.review;
329
- selectedLines.push(buildPanelLine(width, [
330
- [' Provenance: ', C.label],
331
- [truncateDisplay(selectedRow.entry.provenance ?? '(none)', Math.max(0, width - 15)), selectedRow.entry.provenance ? C.info : C.dim],
332
- ]));
333
- selectedLines.push(buildPanelLine(width, [
334
- [' Source: ', C.label],
335
- [truncateDisplay(selectedRow.entry.source, Math.max(0, width - 11)), C.value],
336
- ]));
337
- selectedLines.push(buildKeyValueLine(width, [
338
- { label: 'Compatibility', value: review.compatibility.status, valueColor: review.compatibility.status === 'supported' ? C.good : C.warn },
339
- { label: 'Risk', value: review.riskLevel, valueColor: review.riskLevel === 'low' ? C.good : C.warn },
340
- { label: 'State', value: selectedRow.installed ? 'installed' : 'curated', valueColor: statusColor(selectedRow.installed) },
341
- ], C));
342
-
343
- if (this.expandedEntryId === selectedRow.entry.id) {
344
- // Enter = full review detail: every field reviewEcosystemCatalogEntry
345
- // returns, not just the compact compatibility/risk/state summary above.
346
- selectedLines.push(buildPanelLine(width, [
347
- [' Source path: ', C.label],
348
- [truncateDisplay(review.sourcePath, Math.max(0, width - 16)), review.sourceExists ? C.good : C.warn],
349
- ]));
350
- selectedLines.push(buildKeyValueLine(width, [
351
- { label: 'Source kind', value: review.sourceKind, valueColor: C.info },
352
- { label: 'Source exists', value: review.sourceExists ? 'yes' : 'no', valueColor: review.sourceExists ? C.good : C.warn },
353
- { label: 'Recommended scope', value: review.recommendedScope, valueColor: C.info },
354
- ], C));
355
- selectedLines.push(buildPanelLine(width, [
356
- [' Runtime fit: ', C.label],
357
- [review.runtimeFit.status, review.runtimeFit.status === 'supported' ? C.good : C.warn],
358
- [review.runtimeFit.reasons.length > 0 ? ` (${review.runtimeFit.reasons.join('; ')})` : '', C.dim],
359
- ]));
360
- if (review.compatibility.reasons.length > 0) {
361
- selectedLines.push(buildPanelLine(width, [
362
- [' Compatibility notes: ', C.label],
363
- [truncateDisplay(review.compatibility.reasons.join('; '), Math.max(0, width - 24)), C.warn],
364
- ]));
365
- }
366
- } else {
367
- selectedLines.push(buildGuidanceLine(width, 'Enter', 'expand full compatibility, receipt, and runtime-fit detail for the selected entry', C));
368
- }
369
- }
370
-
371
- // Context-aware hints: filter mode vs. browse mode (install/uninstall only
372
- // make sense given the selected entry's current install state).
373
- const hints = this.filterActive
374
- ? [{ keys: 'type', label: 'filter' }, { keys: 'Enter', label: 'apply' }, { keys: 'Esc', label: 'clear' }]
375
- : [
376
- { keys: 'Up/Down', label: 'move' },
377
- { keys: 'Enter', label: 'detail' },
378
- ...(selectedRow && !selectedRow.installed ? [{ keys: 'i', label: 'install' }] : []),
379
- ...(selectedRow && selectedRow.installed ? [{ keys: 'u', label: 'uninstall' }] : []),
380
- ...(recommendations.length > 0 ? [{ keys: '1-9', label: 'jump to recommendation' }] : []),
381
- { keys: 'r', label: 'refresh' },
382
- { keys: '/', label: 'filter' },
383
- ];
384
-
385
- const footer: Line[] = selectedLines.length > 0 && height >= 20
386
- ? [...selectedLines, buildKeyboardHints(width, hints, C)]
387
- : [buildKeyboardHints(width, hints, C)];
388
-
389
- return this.renderList(width, height, {
390
- title: 'Marketplace Control Room',
391
- header: [
392
- ...postureLines,
393
- ...startupIssueLines,
394
- ...recommendationLines,
395
- ],
396
- footer,
397
- });
398
- }
399
- }