@pellux/goodvibes-tui 0.27.0 → 0.29.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.
- package/CHANGELOG.md +124 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/core/context-usage.ts +53 -0
- package/src/core/conversation.ts +4 -6
- package/src/core/session-recovery.ts +2 -0
- package/src/core/turn-event-wiring.ts +1 -0
- package/src/input/commands/cost-runtime.ts +49 -0
- package/src/input/commands/eval.ts +4 -3
- package/src/input/commands/operator-runtime.ts +5 -1
- package/src/input/commands.ts +2 -0
- package/src/input/handler-feed-routes.ts +60 -26
- package/src/input/handler-feed.ts +17 -8
- package/src/input/handler-picker-routes.ts +18 -13
- package/src/input/handler-shortcuts.ts +51 -0
- package/src/input/handler-ui-state.ts +4 -0
- package/src/input/handler.ts +43 -6
- package/src/input/keybindings.ts +53 -8
- package/src/input/model-picker.ts +5 -0
- package/src/input/panel-integration-actions.ts +117 -1
- package/src/main.ts +5 -2
- package/src/panels/agent-inspector-panel.ts +139 -45
- package/src/panels/agent-inspector-shared.ts +147 -3
- package/src/panels/approval-panel.ts +204 -102
- package/src/panels/automation-control-panel.ts +370 -103
- package/src/panels/base-panel.ts +26 -2
- package/src/panels/builtin/agent.ts +28 -37
- package/src/panels/builtin/development.ts +40 -32
- package/src/panels/builtin/knowledge.ts +15 -6
- package/src/panels/builtin/operations.ts +178 -117
- package/src/panels/builtin/session.ts +35 -9
- package/src/panels/builtin/shared.ts +98 -6
- package/src/panels/cockpit-panel.ts +253 -75
- package/src/panels/cockpit-read-model.ts +2 -1
- package/src/panels/communication-panel.ts +159 -56
- package/src/panels/confirm-state.ts +8 -1
- package/src/panels/control-plane-panel.ts +354 -95
- package/src/panels/cost-tracker-panel.ts +243 -57
- package/src/panels/debug-panel.ts +347 -159
- package/src/panels/diff-panel.ts +257 -82
- package/src/panels/docs-panel.ts +166 -64
- package/src/panels/eval-panel.ts +336 -109
- package/src/panels/expandable-list-panel.ts +189 -0
- package/src/panels/file-explorer-panel.ts +238 -159
- package/src/panels/file-preview-panel.ts +141 -59
- package/src/panels/git-panel.ts +360 -191
- package/src/panels/hooks-panel.ts +181 -19
- package/src/panels/incident-review-panel.ts +252 -43
- package/src/panels/index.ts +0 -4
- package/src/panels/intelligence-panel.ts +310 -103
- package/src/panels/knowledge-graph-panel.ts +480 -58
- package/src/panels/local-auth-panel.ts +251 -33
- package/src/panels/marketplace-panel.ts +219 -33
- package/src/panels/memory-panel.ts +83 -81
- package/src/panels/ops-control-panel.ts +211 -32
- package/src/panels/ops-strategy-panel.ts +131 -45
- package/src/panels/orchestration-panel.ts +259 -67
- package/src/panels/panel-list-panel.ts +184 -136
- package/src/panels/panel-manager.ts +120 -13
- package/src/panels/plan-dashboard-panel.ts +507 -74
- package/src/panels/plugins-panel.ts +227 -34
- package/src/panels/policy-panel.ts +264 -55
- package/src/panels/polish-core.ts +162 -0
- package/src/panels/polish-tables.ts +258 -0
- package/src/panels/polish.ts +67 -149
- package/src/panels/project-planning-answer-actions.ts +134 -0
- package/src/panels/project-planning-panel.ts +84 -113
- package/src/panels/provider-health-panel.ts +438 -480
- package/src/panels/provider-health-routes.ts +203 -0
- package/src/panels/provider-health-tracker.ts +194 -6
- package/src/panels/provider-health-views.ts +560 -0
- package/src/panels/qr-panel.ts +136 -38
- package/src/panels/remote-panel.ts +123 -38
- package/src/panels/routes-panel.ts +87 -24
- package/src/panels/sandbox-panel.ts +232 -65
- package/src/panels/scrollable-list-panel.ts +143 -145
- package/src/panels/security-panel.ts +204 -52
- package/src/panels/services-panel.ts +174 -76
- package/src/panels/session-browser-panel.ts +101 -6
- package/src/panels/session-maintenance.ts +4 -122
- package/src/panels/settings-sync-panel.ts +346 -68
- package/src/panels/skills-panel.ts +157 -89
- package/src/panels/subscription-panel.ts +105 -34
- package/src/panels/symbol-outline-panel.ts +287 -154
- package/src/panels/system-messages-panel.ts +172 -38
- package/src/panels/tasks-panel.ts +348 -134
- package/src/panels/thinking-panel.ts +66 -32
- package/src/panels/token-budget-panel.ts +233 -46
- package/src/panels/tool-inspector-panel.ts +170 -54
- package/src/panels/types.ts +65 -4
- package/src/panels/work-plan-panel.ts +393 -39
- package/src/panels/worktree-panel.ts +239 -61
- package/src/panels/wrfc-panel-format.ts +133 -0
- package/src/panels/wrfc-panel.ts +146 -150
- package/src/renderer/compaction-preview.ts +2 -1
- package/src/renderer/compositor.ts +46 -43
- package/src/renderer/conversation-overlays.ts +25 -11
- package/src/renderer/footer-tips.ts +41 -0
- package/src/renderer/fullscreen-primitives.ts +22 -16
- package/src/renderer/help-overlay.ts +91 -14
- package/src/renderer/hint-grammar.ts +52 -0
- package/src/renderer/layout.ts +7 -7
- package/src/renderer/modal-factory.ts +23 -15
- package/src/renderer/model-picker-overlay.ts +30 -11
- package/src/renderer/model-workspace.ts +2 -3
- package/src/renderer/overlay-box.ts +16 -10
- package/src/renderer/panel-composite.ts +7 -20
- package/src/renderer/panel-workspace-bar.ts +14 -8
- package/src/renderer/process-indicator.ts +3 -1
- package/src/renderer/progress.ts +8 -6
- package/src/renderer/search-overlay.ts +27 -6
- package/src/renderer/session-picker-modal.ts +6 -4
- package/src/renderer/settings-modal.ts +70 -10
- package/src/renderer/shell-surface.ts +41 -15
- package/src/renderer/status-glyphs.ts +11 -9
- package/src/renderer/tab-strip.ts +148 -34
- package/src/renderer/theme.ts +60 -3
- package/src/renderer/ui-factory.ts +45 -36
- package/src/renderer/ui-primitives.ts +23 -2
- package/src/runtime/bootstrap-shell.ts +35 -18
- package/src/runtime/diagnostics/panels/index.ts +0 -3
- package/src/runtime/ui/model-picker/health-enrichment.ts +3 -0
- package/src/runtime/ui/model-picker/types.ts +4 -0
- package/src/shell/ui-openers.ts +14 -22
- package/src/utils/format-duration.ts +55 -0
- package/src/utils/format-number.ts +71 -0
- package/src/utils/splash-lines.ts +44 -3
- package/src/version.ts +1 -1
- package/src/work-plans/work-plan-store.ts +13 -0
- package/src/panels/agent-logs-panel.ts +0 -666
- package/src/panels/agent-logs-shared.ts +0 -129
- package/src/panels/context-visualizer-panel.ts +0 -214
- package/src/panels/forensics-panel.ts +0 -364
- package/src/panels/panel-picker.ts +0 -106
- package/src/panels/provider-account-snapshot.ts +0 -259
- package/src/panels/provider-accounts-panel.ts +0 -218
- package/src/panels/provider-stats-panel.ts +0 -366
- package/src/panels/schedule-panel.ts +0 -342
- package/src/panels/watchers-panel.ts +0 -193
- package/src/renderer/panel-picker-overlay.ts +0 -202
- package/src/renderer/panel-tab-bar.ts +0 -69
- package/src/runtime/diagnostics/panels/panel-resources.ts +0 -118
- package/src/runtime/diagnostics/panels/policy.ts +0 -177
|
@@ -1,40 +1,58 @@
|
|
|
1
1
|
import type { Line } from '../types/grid.ts';
|
|
2
2
|
import { createEmptyLine } from '../types/grid.ts';
|
|
3
|
+
import { truncateDisplay } from '../utils/terminal-width.ts';
|
|
3
4
|
import { ScrollableListPanel } from './scrollable-list-panel.ts';
|
|
4
|
-
import type {
|
|
5
|
+
import type { PanelIntegrationContext } from './types.ts';
|
|
6
|
+
import type { PluginManager, PluginManagerObserver, PluginStatus } from '@pellux/goodvibes-sdk/platform/plugins';
|
|
5
7
|
import {
|
|
8
|
+
type ConfirmState,
|
|
9
|
+
handleConfirmInput,
|
|
10
|
+
renderConfirmLines,
|
|
11
|
+
} from './confirm-state.ts';
|
|
12
|
+
import {
|
|
13
|
+
buildDetailBlock,
|
|
6
14
|
buildEmptyState,
|
|
15
|
+
buildKeyValueLine,
|
|
16
|
+
buildKeyboardHints,
|
|
7
17
|
buildPanelLine,
|
|
8
18
|
buildPanelWorkspace,
|
|
9
19
|
DEFAULT_PANEL_PALETTE,
|
|
10
20
|
type PanelPalette,
|
|
11
21
|
} from './polish.ts';
|
|
12
22
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
// Base chrome only — title band, state colors, and text tokens all come
|
|
24
|
+
// straight from DEFAULT_PANEL_PALETTE (WO-002).
|
|
25
|
+
const C = DEFAULT_PANEL_PALETTE;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* WO-134: PluginsPanel drives real plugin-manager mutations (enable/disable/
|
|
29
|
+
* verify/lift-quarantine) from the keyboard, not just read-only inspection.
|
|
30
|
+
* `PluginManagerObserver` alone (the read-only surface most panels need) has
|
|
31
|
+
* no mutation methods, so the constructor param is widened to also require
|
|
32
|
+
* the handful of `PluginManager` methods this panel actually calls. The real
|
|
33
|
+
* runtime object passed at bootstrap (`services.pluginManager`, a full
|
|
34
|
+
* `PluginManager` instance — see src/runtime/services.ts) already satisfies
|
|
35
|
+
* this widened shape.
|
|
36
|
+
*/
|
|
37
|
+
export type PluginManagerControls = PluginManagerObserver
|
|
38
|
+
& Pick<PluginManager, 'enable' | 'disable' | 'verify' | 'liftQuarantine'>;
|
|
39
|
+
|
|
40
|
+
type PluginConfirmSubject = { readonly kind: 'disable' | 'lift-quarantine'; readonly name: string };
|
|
23
41
|
|
|
24
42
|
function trustColor(tier: PluginStatus['trustTier']): string {
|
|
25
43
|
switch (tier) {
|
|
26
44
|
case 'trusted':
|
|
27
|
-
return C.
|
|
45
|
+
return C.good;
|
|
28
46
|
case 'limited':
|
|
29
47
|
return C.warn;
|
|
30
48
|
case 'untrusted':
|
|
31
|
-
return C.
|
|
49
|
+
return C.bad;
|
|
32
50
|
}
|
|
33
51
|
}
|
|
34
52
|
|
|
35
53
|
function statusColor(status: PluginStatus): string {
|
|
36
|
-
if (status.quarantined) return C.
|
|
37
|
-
if (status.active) return C.
|
|
54
|
+
if (status.quarantined) return C.bad;
|
|
55
|
+
if (status.active) return C.good;
|
|
38
56
|
if (status.enabled) return C.warn;
|
|
39
57
|
return C.dim;
|
|
40
58
|
}
|
|
@@ -47,25 +65,158 @@ function statusLabel(status: PluginStatus): string {
|
|
|
47
65
|
}
|
|
48
66
|
|
|
49
67
|
export class PluginsPanel extends ScrollableListPanel<PluginStatus> {
|
|
50
|
-
private readonly manager:
|
|
68
|
+
private readonly manager: PluginManagerControls;
|
|
51
69
|
private readonly unsub: (() => void) | null;
|
|
70
|
+
/** Pending destructive/state-changing confirm for 'd' disable and 'q' lift-quarantine. */
|
|
71
|
+
private confirmAction: ConfirmState<PluginConfirmSubject> | null = null;
|
|
72
|
+
/** Result of the last 'v' verify press, rendered in the detail block until the selection or a new verify changes it. */
|
|
73
|
+
private _verifyResult: { name: string; valid: boolean; fingerprint?: string | undefined; reason?: string | undefined } | null = null;
|
|
52
74
|
|
|
53
|
-
public constructor(manager:
|
|
54
|
-
super('plugins', 'Plugins', '
|
|
75
|
+
public constructor(manager: PluginManagerControls) {
|
|
76
|
+
super('plugins', 'Plugins', '◐', 'automation-control');
|
|
55
77
|
this.showSelectionGutter = true; // I5: non-color selection affordance
|
|
78
|
+
this.filterEnabled = true;
|
|
79
|
+
this.filterLabel = 'Filter plugins';
|
|
56
80
|
this.manager = manager;
|
|
57
81
|
this.unsub = manager.subscribe(() => this.markDirty());
|
|
58
82
|
}
|
|
59
83
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
84
|
+
protected override filterMatches(plugin: PluginStatus, q: string): boolean {
|
|
85
|
+
return plugin.name.toLowerCase().includes(q)
|
|
86
|
+
|| plugin.trustTier.toLowerCase().includes(q)
|
|
87
|
+
|| plugin.version.toLowerCase().includes(q);
|
|
63
88
|
}
|
|
64
89
|
|
|
90
|
+
// WO-134: selection is preserved across onActivate — BasePanel's default
|
|
91
|
+
// (mark dirty, no index reset) is exactly what we want, so no override.
|
|
92
|
+
|
|
65
93
|
public override onDestroy(): void {
|
|
66
94
|
this.unsub?.();
|
|
67
95
|
}
|
|
68
96
|
|
|
97
|
+
// -------------------------------------------------------------------------
|
|
98
|
+
// Input — e=enable, d=disable (confirm), v=verify, q=lift quarantine (confirm)
|
|
99
|
+
// -------------------------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
public handleInput(key: string): boolean {
|
|
102
|
+
if (this.lastError !== null) this.clearError();
|
|
103
|
+
|
|
104
|
+
if (this.confirmAction) {
|
|
105
|
+
const result = handleConfirmInput(this.confirmAction, key);
|
|
106
|
+
if (result === 'confirmed') {
|
|
107
|
+
this._executeConfirmed(this.confirmAction.subject);
|
|
108
|
+
this.confirmAction = null;
|
|
109
|
+
this.markDirty();
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
if (result === 'cancelled') {
|
|
113
|
+
this.confirmAction = null;
|
|
114
|
+
this.markDirty();
|
|
115
|
+
}
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!this.filterActive) {
|
|
120
|
+
switch (key) {
|
|
121
|
+
case 'e':
|
|
122
|
+
this._enableSelected();
|
|
123
|
+
return true;
|
|
124
|
+
case 'd':
|
|
125
|
+
this._requestDisable();
|
|
126
|
+
return true;
|
|
127
|
+
case 'v':
|
|
128
|
+
this._verifySelected();
|
|
129
|
+
return true;
|
|
130
|
+
case 'q': {
|
|
131
|
+
const plugin = this.getSelectedItem();
|
|
132
|
+
if (!plugin?.quarantined) return false;
|
|
133
|
+
this._requestLiftQuarantine();
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
case 'm': {
|
|
137
|
+
// Consumed here only when there is a quarantined plugin selected;
|
|
138
|
+
// the actual /recall dispatch happens in handlePanelIntegrationAction
|
|
139
|
+
// below, which needs the executeCommand bridge from the router.
|
|
140
|
+
const plugin = this.getSelectedItem();
|
|
141
|
+
return Boolean(plugin?.quarantined);
|
|
142
|
+
}
|
|
143
|
+
default:
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return super.handleInput(key);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* WO-134: optional one-key capture-to-memory for a quarantined plugin — 'm'
|
|
153
|
+
* dispatches `/recall capture plugin <name>` (recall-capture.ts:146 pattern)
|
|
154
|
+
* instead of requiring the operator to type the command by hand.
|
|
155
|
+
*/
|
|
156
|
+
public handlePanelIntegrationAction(key: string, ctx: PanelIntegrationContext): boolean {
|
|
157
|
+
if (key !== 'm' || !ctx.executeCommand) return false;
|
|
158
|
+
const plugin = this.getSelectedItem();
|
|
159
|
+
if (!plugin?.quarantined) return false;
|
|
160
|
+
void ctx.executeCommand('recall', ['capture', 'plugin', plugin.name]).catch(() => {});
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private _enableSelected(): void {
|
|
165
|
+
const plugin = this.getSelectedItem();
|
|
166
|
+
if (!plugin) return;
|
|
167
|
+
void this.manager.enable(plugin.name).then((result) => {
|
|
168
|
+
if (!result.ok) this.setError(`Enable failed: ${result.error ?? 'unknown error'}`);
|
|
169
|
+
this.markDirty();
|
|
170
|
+
}).catch((err) => {
|
|
171
|
+
this.setError(`Enable failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
172
|
+
this.markDirty();
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private _requestDisable(): void {
|
|
177
|
+
const plugin = this.getSelectedItem();
|
|
178
|
+
if (!plugin) return;
|
|
179
|
+
this.confirmAction = { subject: { kind: 'disable', name: plugin.name }, label: plugin.name, verb: 'Disable' };
|
|
180
|
+
this.markDirty();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private _requestLiftQuarantine(): void {
|
|
184
|
+
const plugin = this.getSelectedItem();
|
|
185
|
+
if (!plugin?.quarantined) return;
|
|
186
|
+
this.confirmAction = { subject: { kind: 'lift-quarantine', name: plugin.name }, label: plugin.name, verb: 'Lift quarantine on' };
|
|
187
|
+
this.markDirty();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
private _verifySelected(): void {
|
|
191
|
+
const plugin = this.getSelectedItem();
|
|
192
|
+
if (!plugin) return;
|
|
193
|
+
const result = this.manager.verify(plugin.name);
|
|
194
|
+
if (!result.ok && result.reason?.toLowerCase().includes('not found')) {
|
|
195
|
+
this._verifyResult = null;
|
|
196
|
+
this.setError(`Verify failed: ${result.reason}`);
|
|
197
|
+
this.markDirty();
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
this._verifyResult = { name: plugin.name, valid: result.valid, fingerprint: result.fingerprint, reason: result.reason };
|
|
201
|
+
this.markDirty();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private _executeConfirmed(subject: PluginConfirmSubject): void {
|
|
205
|
+
if (subject.kind === 'disable') {
|
|
206
|
+
void this.manager.disable(subject.name).then((result) => {
|
|
207
|
+
if (!result.ok) this.setError(`Disable failed: ${result.error ?? 'unknown error'}`);
|
|
208
|
+
this.markDirty();
|
|
209
|
+
}).catch((err) => {
|
|
210
|
+
this.setError(`Disable failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
211
|
+
this.markDirty();
|
|
212
|
+
});
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const result = this.manager.liftQuarantine(subject.name);
|
|
216
|
+
if (!result.ok) this.setError(`Lift quarantine failed: ${result.error ?? 'unknown error'}`);
|
|
217
|
+
this.markDirty();
|
|
218
|
+
}
|
|
219
|
+
|
|
69
220
|
protected override getPalette(): PanelPalette {
|
|
70
221
|
return C;
|
|
71
222
|
}
|
|
@@ -122,15 +273,30 @@ export class PluginsPanel extends ScrollableListPanel<PluginStatus> {
|
|
|
122
273
|
return workspace;
|
|
123
274
|
}
|
|
124
275
|
|
|
276
|
+
// Provenance/error posture header — surface trust + quarantine pressure first.
|
|
277
|
+
const quarantined = plugins.filter((p) => p.quarantined).length;
|
|
278
|
+
const untrusted = plugins.filter((p) => p.trustTier === 'untrusted').length;
|
|
279
|
+
const active = plugins.filter((p) => p.active).length;
|
|
280
|
+
const headerLines: Line[] = [
|
|
281
|
+
buildKeyValueLine(width, [
|
|
282
|
+
{ label: 'plugins', value: String(plugins.length), valueColor: C.info },
|
|
283
|
+
{ label: 'active', value: String(active), valueColor: active > 0 ? C.good : C.dim },
|
|
284
|
+
{ label: 'untrusted', value: String(untrusted), valueColor: untrusted > 0 ? C.warn : C.dim },
|
|
285
|
+
{ label: 'quarantined', value: String(quarantined), valueColor: quarantined > 0 ? C.bad : C.dim },
|
|
286
|
+
], C),
|
|
287
|
+
];
|
|
288
|
+
|
|
125
289
|
this.clampSelection();
|
|
126
|
-
const selected =
|
|
290
|
+
const selected = this.getSelectedItem()!;
|
|
127
291
|
const selectedCaps = this.manager.capabilities(selected.name);
|
|
128
292
|
const trustRecord = this.manager.getTrustRecord(selected.name);
|
|
129
293
|
const quarantineRecord = this.manager.getQuarantineRecord(selected.name);
|
|
130
|
-
const
|
|
294
|
+
const detailRows: Line[] = [
|
|
131
295
|
buildPanelLine(width, [
|
|
132
296
|
[' Plugin: ', C.label],
|
|
133
297
|
[selected.name, C.value],
|
|
298
|
+
[' v', C.label],
|
|
299
|
+
[selected.version, C.dim],
|
|
134
300
|
[' State: ', C.label],
|
|
135
301
|
[statusLabel(selected), statusColor(selected)],
|
|
136
302
|
[' Trust: ', C.label],
|
|
@@ -138,41 +304,68 @@ export class PluginsPanel extends ScrollableListPanel<PluginStatus> {
|
|
|
138
304
|
]),
|
|
139
305
|
buildPanelLine(width, [
|
|
140
306
|
[' Description: ', C.label],
|
|
141
|
-
[selected.description
|
|
307
|
+
[truncateDisplay(selected.description, Math.max(0, width - 15)), C.dim],
|
|
142
308
|
]),
|
|
143
309
|
];
|
|
144
310
|
|
|
145
311
|
if (selectedCaps) {
|
|
146
|
-
|
|
312
|
+
detailRows.push(buildPanelLine(width, [
|
|
147
313
|
[' Capabilities: ', C.label],
|
|
148
314
|
[String(selectedCaps.requested.length), C.value],
|
|
149
315
|
[' High-risk: ', C.label],
|
|
150
|
-
[String(selectedCaps.highRisk.length), selectedCaps.highRisk.length > 0 ? C.warn : C.
|
|
316
|
+
[String(selectedCaps.highRisk.length), selectedCaps.highRisk.length > 0 ? C.warn : C.good],
|
|
151
317
|
[' Blocked: ', C.label],
|
|
152
|
-
[String(selectedCaps.blocked.length), selectedCaps.blocked.length > 0 ? C.
|
|
318
|
+
[String(selectedCaps.blocked.length), selectedCaps.blocked.length > 0 ? C.bad : C.good],
|
|
153
319
|
]));
|
|
154
320
|
}
|
|
155
321
|
|
|
156
322
|
if (trustRecord?.signatureFingerprint) {
|
|
157
|
-
|
|
323
|
+
detailRows.push(buildPanelLine(width, [
|
|
158
324
|
[' Signature: ', C.label],
|
|
159
|
-
[trustRecord.signatureFingerprint, C.info],
|
|
325
|
+
[truncateDisplay(trustRecord.signatureFingerprint, Math.max(0, width - 14)), C.info],
|
|
160
326
|
]));
|
|
327
|
+
} else {
|
|
328
|
+
detailRows.push(buildPanelLine(width, [[' Signature: unsigned (no provenance fingerprint on record)', C.warn]]));
|
|
161
329
|
}
|
|
162
330
|
|
|
163
331
|
if (quarantineRecord) {
|
|
164
|
-
|
|
332
|
+
detailRows.push(buildPanelLine(width, [
|
|
165
333
|
[' Quarantine: ', C.label],
|
|
166
|
-
[quarantineRecord.reason
|
|
334
|
+
[truncateDisplay(quarantineRecord.reason, Math.max(0, width - 14)), C.bad],
|
|
167
335
|
]));
|
|
168
336
|
}
|
|
169
337
|
|
|
170
|
-
|
|
171
|
-
|
|
338
|
+
if (this._verifyResult && this._verifyResult.name === selected.name) {
|
|
339
|
+
const vr = this._verifyResult;
|
|
340
|
+
const verifySegments: Array<[string, string, string?]> = [
|
|
341
|
+
[' Verify: ', C.label],
|
|
342
|
+
[vr.valid ? 'VALID' : 'INVALID', vr.valid ? C.good : C.bad],
|
|
343
|
+
];
|
|
344
|
+
if (vr.fingerprint) verifySegments.push([` fp=${truncateDisplay(vr.fingerprint, 20)}`, C.info]);
|
|
345
|
+
if (!vr.valid && vr.reason) verifySegments.push([` ${truncateDisplay(vr.reason, Math.max(0, width - 40))}`, C.warn]);
|
|
346
|
+
detailRows.push(buildPanelLine(width, verifySegments));
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (this.confirmAction) detailRows.push(...renderConfirmLines(width, this.confirmAction));
|
|
350
|
+
|
|
351
|
+
const hints = this.filterActive
|
|
352
|
+
? [{ keys: 'type', label: 'filter' }, { keys: 'Enter', label: 'apply' }, { keys: 'Esc', label: 'clear' }]
|
|
353
|
+
: [
|
|
354
|
+
{ keys: 'Up/Down', label: 'move' },
|
|
355
|
+
{ keys: 'e', label: 'enable' },
|
|
356
|
+
{ keys: 'd', label: 'disable' },
|
|
357
|
+
{ keys: 'v', label: 'verify' },
|
|
358
|
+
...(selected.quarantined ? [{ keys: 'q', label: 'lift quarantine' }, { keys: 'm', label: 'capture to memory' }] : []),
|
|
359
|
+
{ keys: '/', label: 'filter' },
|
|
360
|
+
];
|
|
172
361
|
|
|
173
362
|
return this.renderList(width, height, {
|
|
174
363
|
title: 'Plugin Control Room',
|
|
175
|
-
|
|
364
|
+
header: headerLines,
|
|
365
|
+
footer: [
|
|
366
|
+
...buildDetailBlock(width, `Plugin · ${selected.name}`, detailRows, C),
|
|
367
|
+
buildKeyboardHints(width, hints, C),
|
|
368
|
+
],
|
|
176
369
|
});
|
|
177
370
|
}
|
|
178
371
|
}
|