@pellux/goodvibes-agent 0.1.116 → 1.0.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 +48 -661
- package/README.md +64 -57
- package/bin/goodvibes-agent.ts +16 -2
- package/dist/package/main.js +199180 -192185
- package/docs/README.md +15 -8
- package/docs/channels-remote-and-api.md +50 -0
- package/docs/connected-host.md +59 -0
- package/docs/getting-started.md +61 -50
- package/docs/knowledge-artifacts-and-multimodal.md +91 -0
- package/docs/project-planning.md +79 -0
- package/docs/providers-and-routing.md +46 -0
- package/docs/release-and-publishing.md +48 -11
- package/docs/tools-and-commands.md +123 -0
- package/docs/voice-and-live-tts.md +51 -0
- package/package.json +3 -6
- package/src/agent/behavior-discovery-summary.ts +1 -3
- package/src/agent/channel-delivery.ts +201 -0
- package/src/agent/media-generation.ts +159 -0
- package/src/agent/memory-prompt.ts +0 -1
- package/src/agent/note-registry.ts +329 -0
- package/src/agent/operator-actions.ts +343 -0
- package/src/agent/persona-discovery.ts +1 -3
- package/src/agent/persona-registry.ts +19 -16
- package/src/agent/record-labels.ts +107 -0
- package/src/agent/reminder-schedule-format.ts +33 -24
- package/src/agent/reminder-schedule.ts +27 -26
- package/src/agent/routine-registry.ts +54 -14
- package/src/agent/routine-schedule-args.ts +2 -1
- package/src/agent/routine-schedule-format.ts +77 -53
- package/src/agent/routine-schedule-promotion.ts +35 -33
- package/src/agent/routine-schedule-receipts.ts +29 -27
- package/src/agent/runtime-profile-starters.ts +5 -5
- package/src/agent/runtime-profile.ts +91 -12
- package/src/agent/skill-registry.ts +210 -32
- package/src/cli/agent-knowledge-args.ts +5 -1
- package/src/cli/agent-knowledge-command.ts +40 -34
- package/src/cli/agent-knowledge-format.ts +80 -67
- package/src/cli/agent-knowledge-methods.ts +1 -1
- package/src/cli/agent-knowledge-runtime.ts +32 -26
- package/src/cli/bundle-command.ts +14 -9
- package/src/cli/config-overrides.ts +37 -36
- package/src/cli/entrypoint.ts +16 -7
- package/src/cli/external-runtime.ts +10 -4
- package/src/cli/help.ts +54 -29
- package/src/cli/local-library-command.ts +203 -83
- package/src/cli/management-commands.ts +105 -68
- package/src/cli/management.ts +55 -74
- package/src/cli/memory-command.ts +66 -32
- package/src/cli/parser.ts +37 -3
- package/src/cli/profiles-command.ts +166 -31
- package/src/cli/provider-auth-routes.ts +2 -1
- package/src/cli/routines-command.ts +176 -40
- package/src/cli/service-posture.ts +16 -16
- package/src/cli/status.ts +56 -135
- package/src/core/conversation-message-snapshot.ts +131 -0
- package/src/input/agent-workspace-access-command-editor-submission.ts +149 -0
- package/src/input/agent-workspace-access-command-editors.ts +170 -0
- package/src/input/agent-workspace-activation.ts +35 -8
- package/src/input/agent-workspace-basic-command-editor-submission.ts +351 -94
- package/src/input/agent-workspace-basic-command-editors.ts +502 -9
- package/src/input/agent-workspace-categories.ts +423 -51
- package/src/input/agent-workspace-channel-command-editor-submission.ts +83 -0
- package/src/input/agent-workspace-channel-command-editors.ts +65 -0
- package/src/input/agent-workspace-channels.ts +35 -2
- package/src/input/agent-workspace-command-editor.ts +41 -3
- package/src/input/agent-workspace-config-reader.ts +16 -0
- package/src/input/agent-workspace-delegation-editor-submission.ts +55 -0
- package/src/input/agent-workspace-editors.ts +166 -1
- package/src/input/agent-workspace-knowledge-command-editor-submission.ts +132 -0
- package/src/input/agent-workspace-knowledge-command-editors.ts +110 -0
- package/src/input/agent-workspace-knowledge-query-editor.ts +1 -1
- package/src/input/agent-workspace-learned-behavior.ts +67 -0
- package/src/input/agent-workspace-library-command-editor-submission.ts +50 -0
- package/src/input/agent-workspace-library-command-editors.ts +35 -0
- package/src/input/agent-workspace-local-operations.ts +218 -0
- package/src/input/agent-workspace-local-selection.ts +75 -0
- package/src/input/agent-workspace-mcp-command-editor-submission.ts +95 -0
- package/src/input/agent-workspace-media-command-editor-submission.ts +62 -0
- package/src/input/agent-workspace-media-command-editors.ts +27 -0
- package/src/input/agent-workspace-memory-command-editor-submission.ts +232 -0
- package/src/input/agent-workspace-memory-command-editors.ts +180 -0
- package/src/input/agent-workspace-memory-editor.ts +2 -2
- package/src/input/agent-workspace-navigation.ts +81 -0
- package/src/input/agent-workspace-notify-editor-submission.ts +121 -0
- package/src/input/agent-workspace-operations-command-editor-submission.ts +208 -0
- package/src/input/agent-workspace-operations-command-editors.ts +196 -0
- package/src/input/agent-workspace-panel-route.ts +43 -0
- package/src/input/agent-workspace-provider-command-editor-submission.ts +155 -0
- package/src/input/agent-workspace-provider-command-editors.ts +93 -0
- package/src/input/agent-workspace-reminder-schedule-editor.ts +1 -1
- package/src/input/agent-workspace-requirements.ts +11 -0
- package/src/input/agent-workspace-routine-schedule-editor.ts +2 -2
- package/src/input/agent-workspace-search.ts +169 -0
- package/src/input/agent-workspace-secret-editor-submission.ts +153 -0
- package/src/input/agent-workspace-session-command-editor-submission.ts +199 -0
- package/src/input/agent-workspace-session-command-editors.ts +249 -0
- package/src/input/agent-workspace-setup.ts +37 -16
- package/src/input/agent-workspace-skill-bundle-command-editor-submission.ts +101 -0
- package/src/input/agent-workspace-skill-bundle-command-editors.ts +110 -0
- package/src/input/agent-workspace-snapshot.ts +162 -21
- package/src/input/agent-workspace-task-command-editor-submission.ts +61 -0
- package/src/input/agent-workspace-task-command-editors.ts +47 -0
- package/src/input/agent-workspace-token.ts +18 -2
- package/src/input/agent-workspace-types.ts +212 -4
- package/src/input/agent-workspace-voice-media.ts +4 -7
- package/src/input/agent-workspace-web-research-editor.ts +104 -0
- package/src/input/agent-workspace-workplan-editor-submission.ts +153 -0
- package/src/input/agent-workspace.ts +178 -240
- package/src/input/command-registry.ts +5 -2
- package/src/input/commands/agent-runtime-profile-runtime.ts +126 -37
- package/src/input/commands/agent-skills-runtime.ts +149 -72
- package/src/input/commands/agent-workspace-runtime.ts +23 -6
- package/src/input/commands/brief-runtime.ts +55 -25
- package/src/input/commands/channels-runtime.ts +147 -33
- package/src/input/commands/compat-runtime.ts +32 -0
- package/src/input/commands/delegation-runtime.ts +29 -23
- package/src/input/commands/experience-runtime.ts +6 -4
- package/src/input/commands/guidance-runtime.ts +4 -4
- package/src/input/commands/health-runtime.ts +149 -126
- package/src/input/commands/knowledge.ts +82 -60
- package/src/input/commands/local-provider-runtime.ts +43 -21
- package/src/input/commands/local-runtime.ts +138 -16
- package/src/input/commands/local-setup-review.ts +10 -20
- package/src/input/commands/mcp-runtime.ts +63 -46
- package/src/input/commands/notify-runtime.ts +38 -9
- package/src/input/commands/operator-actions-runtime.ts +138 -0
- package/src/input/commands/operator-runtime.ts +6 -76
- package/src/input/commands/personas-runtime.ts +46 -31
- package/src/input/commands/planning-runtime.ts +1 -1
- package/src/input/commands/platform-access-runtime.ts +35 -98
- package/src/input/commands/product-runtime.ts +2 -2
- package/src/input/commands/provider-accounts-runtime.ts +60 -39
- package/src/input/commands/qrcode-runtime.ts +50 -9
- package/src/input/commands/recall-bundle.ts +11 -11
- package/src/input/commands/recall-capture.ts +13 -11
- package/src/input/commands/recall-query.ts +29 -21
- package/src/input/commands/recall-review.ts +2 -1
- package/src/input/commands/routines-runtime.ts +99 -45
- package/src/input/commands/runtime-services.ts +1 -14
- package/src/input/commands/schedule-runtime.ts +33 -20
- package/src/input/commands/security-runtime.ts +6 -11
- package/src/input/commands/session-content.ts +80 -78
- package/src/input/commands/session-workflow.ts +134 -104
- package/src/input/commands/session.ts +3 -174
- package/src/input/commands/shell-core.ts +38 -17
- package/src/input/commands/subscription-runtime.ts +54 -185
- package/src/input/commands/support-bundle-runtime.ts +53 -0
- package/src/input/commands/tasks-runtime.ts +21 -21
- package/src/input/commands/work-plan-runtime.ts +33 -8
- package/src/input/commands.ts +6 -2
- package/src/input/feed-context-factory.ts +2 -2
- package/src/input/file-picker.ts +3 -2
- package/src/input/handler-command-route.ts +4 -7
- package/src/input/handler-content-actions.ts +89 -26
- package/src/input/handler-feed-routes.ts +19 -12
- package/src/input/handler-feed.ts +6 -5
- package/src/input/handler-interactions.ts +9 -36
- package/src/input/handler-modal-stack.ts +3 -3
- package/src/input/handler-onboarding.ts +52 -91
- package/src/input/handler-shortcuts.ts +16 -7
- package/src/input/handler.ts +50 -23
- package/src/input/input-history.ts +5 -6
- package/src/input/keybindings.ts +27 -16
- package/src/input/mcp-workspace.ts +90 -26
- package/src/input/onboarding/handler-onboarding-routes.ts +3 -1
- package/src/input/onboarding/onboarding-wizard-apply.ts +62 -0
- package/src/input/onboarding/onboarding-wizard-constants.ts +1 -0
- package/src/input/onboarding/onboarding-wizard-helpers.ts +1 -1
- package/src/input/onboarding/onboarding-wizard-operator-steps.ts +223 -55
- package/src/input/onboarding/onboarding-wizard-steps.ts +59 -35
- package/src/input/onboarding/onboarding-wizard-types.ts +4 -1
- package/src/input/onboarding/onboarding-wizard.ts +87 -2
- package/src/input/profile-picker-modal.ts +31 -12
- package/src/input/session-picker-modal.ts +21 -4
- package/src/input/settings-modal-agent-policy.ts +6 -6
- package/src/input/settings-modal-behavior.ts +0 -3
- package/src/input/settings-modal-subscriptions.ts +3 -3
- package/src/input/settings-modal-types.ts +2 -13
- package/src/input/settings-modal.ts +20 -66
- package/src/input/submission-intent.ts +0 -1
- package/src/input/submission-router.ts +3 -3
- package/src/main.ts +18 -10
- package/src/panels/approval-panel.ts +8 -8
- package/src/panels/automation-control-panel.ts +4 -4
- package/src/panels/base-panel.ts +1 -1
- package/src/panels/builtin/agent.ts +1 -1
- package/src/panels/builtin/operations.ts +3 -12
- package/src/panels/builtin/session.ts +32 -24
- package/src/panels/builtin/shared.ts +7 -7
- package/src/panels/cost-tracker-panel.ts +1 -1
- package/src/panels/docs-panel.ts +5 -3
- package/src/panels/index.ts +0 -1
- package/src/panels/knowledge-panel.ts +6 -5
- package/src/panels/memory-panel.ts +7 -6
- package/src/panels/panel-list-panel.ts +36 -80
- package/src/panels/project-planning-panel.ts +19 -12
- package/src/panels/provider-account-snapshot.ts +51 -25
- package/src/panels/provider-accounts-panel.ts +33 -18
- package/src/panels/provider-health-domains.ts +48 -7
- package/src/panels/provider-health-panel.ts +5 -4
- package/src/panels/qr-panel.ts +44 -20
- package/src/panels/schedule-panel.ts +9 -17
- package/src/panels/security-panel.ts +8 -8
- package/src/panels/session-browser-panel.ts +10 -10
- package/src/panels/subscription-panel.ts +6 -6
- package/src/panels/system-messages-panel.ts +3 -3
- package/src/panels/tasks-panel.ts +21 -14
- package/src/panels/tool-inspector-panel.ts +19 -12
- package/src/panels/work-plan-panel.ts +5 -5
- package/src/planning/project-planning-coordinator.ts +3 -3
- package/src/provider-auth-route-display.ts +9 -0
- package/src/renderer/agent-workspace-style.ts +34 -0
- package/src/renderer/agent-workspace.ts +254 -57
- package/src/renderer/autocomplete-overlay.ts +25 -6
- package/src/renderer/block-actions.ts +1 -3
- package/src/renderer/bookmark-modal.ts +19 -4
- package/src/renderer/buffer.ts +4 -2
- package/src/renderer/context-inspector.ts +50 -13
- package/src/renderer/diff.ts +1 -1
- package/src/renderer/file-picker-overlay.ts +19 -6
- package/src/renderer/help-overlay.ts +112 -34
- package/src/renderer/history-search-overlay.ts +19 -4
- package/src/renderer/live-tail-modal.ts +27 -5
- package/src/renderer/mcp-workspace.ts +176 -43
- package/src/renderer/model-picker-overlay.ts +58 -3
- package/src/renderer/model-workspace.ts +104 -22
- package/src/renderer/onboarding/onboarding-wizard.ts +20 -4
- package/src/renderer/process-modal.ts +27 -6
- package/src/renderer/profile-picker-modal.ts +21 -5
- package/src/renderer/search-overlay.ts +25 -5
- package/src/renderer/selection-modal-overlay.ts +46 -14
- package/src/renderer/session-picker-modal.ts +18 -1
- package/src/renderer/settings-modal-helpers.ts +2 -40
- package/src/renderer/settings-modal.ts +88 -55
- package/src/renderer/system-message.ts +1 -1
- package/src/renderer/tool-call.ts +20 -11
- package/src/runtime/agent-runtime-events.ts +129 -0
- package/src/runtime/bootstrap-command-context.ts +7 -1
- package/src/runtime/bootstrap-command-parts.ts +11 -8
- package/src/runtime/bootstrap-core.ts +23 -65
- package/src/runtime/bootstrap-hook-bridge.ts +7 -18
- package/src/runtime/bootstrap-shell.ts +46 -8
- package/src/runtime/bootstrap.ts +36 -46
- package/src/runtime/connected-host-auth.ts +47 -0
- package/src/runtime/diagnostics/panels/index.ts +2 -2
- package/src/runtime/diagnostics/panels/panel-resources.ts +1 -1
- package/src/runtime/diagnostics/panels/policy.ts +7 -7
- package/src/runtime/index.ts +2 -5
- package/src/runtime/onboarding/apply-file-helpers.ts +66 -0
- package/src/runtime/onboarding/apply.ts +288 -74
- package/src/runtime/onboarding/derivation.ts +10 -13
- package/src/runtime/onboarding/snapshot.ts +2 -17
- package/src/runtime/onboarding/types.ts +34 -20
- package/src/runtime/onboarding/verify.ts +105 -4
- package/src/runtime/operator-token-cleanup.ts +3 -3
- package/src/runtime/services.ts +95 -11
- package/src/runtime/store/selectors/index.ts +3 -3
- package/src/runtime/store/state.ts +1 -4
- package/src/runtime/surface-feature-flags.ts +41 -6
- package/src/runtime/ui-read-models.ts +3 -4
- package/src/runtime/ui-services.ts +6 -6
- package/src/shell/blocking-input.ts +2 -1
- package/src/shell/service-settings-sync.ts +7 -7
- package/src/shell/ui-openers.ts +17 -38
- package/src/tools/agent-analysis-registry-policy.ts +0 -1
- package/src/tools/agent-channel-send-tool.ts +133 -0
- package/src/tools/agent-context-policy.ts +1 -1
- package/src/tools/agent-knowledge-ingest-tool.ts +405 -0
- package/src/tools/agent-knowledge-tool.ts +170 -0
- package/src/tools/agent-local-registry-tool.ts +279 -73
- package/src/tools/agent-media-generate-tool.ts +133 -0
- package/src/tools/agent-notify-tool.ts +143 -0
- package/src/tools/agent-operator-action-tool.ts +137 -0
- package/src/tools/agent-operator-briefing-tool.ts +217 -0
- package/src/tools/agent-reminder-schedule-tool.ts +237 -0
- package/src/tools/agent-tool-policy-guard.ts +13 -15
- package/src/tools/agent-work-plan-tool.ts +256 -0
- package/src/version.ts +1 -1
- package/docs/connected-services.md +0 -51
- package/src/cli/package-verification.ts +0 -274
- package/src/input/commands/policy-dispatch.ts +0 -339
- package/src/input/commands/policy.ts +0 -17
- package/src/panels/panel-picker.ts +0 -105
- package/src/panels/policy-panel.ts +0 -308
- package/src/renderer/panel-picker-overlay.ts +0 -202
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import type { PanelRegistration, PanelCategory } from './types.ts';
|
|
2
|
-
|
|
3
|
-
/** Display order for panel categories. */
|
|
4
|
-
const CATEGORY_ORDER: PanelCategory[] = ['agent', 'monitoring', 'session', 'ai'];
|
|
5
|
-
|
|
6
|
-
/** Human-readable labels for each category. */
|
|
7
|
-
const CATEGORY_LABELS: Record<PanelCategory, string> = {
|
|
8
|
-
agent: 'Agent',
|
|
9
|
-
monitoring: 'Monitoring',
|
|
10
|
-
session: 'Session',
|
|
11
|
-
ai: 'AI',
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Modal state for browsing and selecting panel registrations.
|
|
16
|
-
* The filtered list is a flat list of PanelRegistration items matching the
|
|
17
|
-
* current search query. Category headers are rendered by the overlay, not
|
|
18
|
-
* stored here — keeping this class pure data/logic.
|
|
19
|
-
*/
|
|
20
|
-
export class PanelPicker {
|
|
21
|
-
public active: boolean = false;
|
|
22
|
-
public selectedIndex: number = 0;
|
|
23
|
-
public searchQuery: string = '';
|
|
24
|
-
private items: PanelRegistration[] = [];
|
|
25
|
-
private filtered: PanelRegistration[] = [];
|
|
26
|
-
|
|
27
|
-
/** Open the picker with the given set of registered panels. */
|
|
28
|
-
open(registrations: PanelRegistration[]): void {
|
|
29
|
-
this.items = registrations;
|
|
30
|
-
this.searchQuery = '';
|
|
31
|
-
this.filtered = this._applyFilter('');
|
|
32
|
-
this.selectedIndex = 0;
|
|
33
|
-
this.active = true;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/** Close and reset the picker. */
|
|
37
|
-
close(): void {
|
|
38
|
-
this.active = false;
|
|
39
|
-
this.searchQuery = '';
|
|
40
|
-
this.selectedIndex = 0;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Filter the list by name or description (case-insensitive).
|
|
45
|
-
* Resets selectedIndex to 0.
|
|
46
|
-
*/
|
|
47
|
-
search(query: string): void {
|
|
48
|
-
this.searchQuery = query;
|
|
49
|
-
this.filtered = this._applyFilter(query);
|
|
50
|
-
this.selectedIndex = 0;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** Move selection up by one, wrapping at top. */
|
|
54
|
-
moveUp(): void {
|
|
55
|
-
if (this.filtered.length === 0) return;
|
|
56
|
-
this.selectedIndex = this.selectedIndex <= 0
|
|
57
|
-
? this.filtered.length - 1
|
|
58
|
-
: this.selectedIndex - 1;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** Move selection down by one, wrapping at bottom. */
|
|
62
|
-
moveDown(): void {
|
|
63
|
-
if (this.filtered.length === 0) return;
|
|
64
|
-
this.selectedIndex = this.selectedIndex >= this.filtered.length - 1
|
|
65
|
-
? 0
|
|
66
|
-
: this.selectedIndex + 1;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Returns the filtered list, sorted by category order then name.
|
|
71
|
-
* The overlay is responsible for inserting category headers between groups.
|
|
72
|
-
*/
|
|
73
|
-
getVisible(): PanelRegistration[] {
|
|
74
|
-
return this.filtered;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/** Returns the currently selected registration, or null if the list is empty. */
|
|
78
|
-
getSelected(): PanelRegistration | null {
|
|
79
|
-
if (this.filtered.length === 0) return null;
|
|
80
|
-
return this.filtered[this.selectedIndex] ?? null;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// ── Private ──────────────────────────────────────────────────────────────
|
|
84
|
-
|
|
85
|
-
private _applyFilter(query: string): PanelRegistration[] {
|
|
86
|
-
const q = query.trim().toLowerCase();
|
|
87
|
-
const source = q
|
|
88
|
-
? this.items.filter(
|
|
89
|
-
r =>
|
|
90
|
-
r.name.toLowerCase().includes(q) ||
|
|
91
|
-
r.description.toLowerCase().includes(q),
|
|
92
|
-
)
|
|
93
|
-
: [...this.items];
|
|
94
|
-
|
|
95
|
-
// Sort by category order, then alphabetically within each category
|
|
96
|
-
return source.sort((a, b) => {
|
|
97
|
-
const ai = CATEGORY_ORDER.indexOf(a.category);
|
|
98
|
-
const bi = CATEGORY_ORDER.indexOf(b.category);
|
|
99
|
-
if (ai !== bi) return ai - bi;
|
|
100
|
-
return a.name.localeCompare(b.name);
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export { CATEGORY_LABELS, CATEGORY_ORDER };
|
|
@@ -1,308 +0,0 @@
|
|
|
1
|
-
import type { Line } from '../types/grid.ts';
|
|
2
|
-
import { createEmptyLine } from '../types/grid.ts';
|
|
3
|
-
import { BasePanel } from './base-panel.ts';
|
|
4
|
-
import type { PolicyRuntimeState } from '@/runtime/index.ts';
|
|
5
|
-
import type { PolicyPanelSnapshot } from '../runtime/diagnostics/panels/policy.ts';
|
|
6
|
-
import {
|
|
7
|
-
buildPanelLine,
|
|
8
|
-
buildPanelWorkspace,
|
|
9
|
-
resolveScrollablePanelSection,
|
|
10
|
-
DEFAULT_PANEL_PALETTE,
|
|
11
|
-
} from './polish.ts';
|
|
12
|
-
|
|
13
|
-
const C = {
|
|
14
|
-
...DEFAULT_PANEL_PALETTE,
|
|
15
|
-
header: '#94a3b8',
|
|
16
|
-
headerBg: '#1e293b',
|
|
17
|
-
label: '#64748b',
|
|
18
|
-
value: '#e2e8f0',
|
|
19
|
-
dim: '#475569',
|
|
20
|
-
ok: '#22c55e',
|
|
21
|
-
warn: '#eab308',
|
|
22
|
-
error: '#ef4444',
|
|
23
|
-
info: '#38bdf8',
|
|
24
|
-
empty: '#334155',
|
|
25
|
-
} as const;
|
|
26
|
-
|
|
27
|
-
function fmtTime(value: string | undefined): string {
|
|
28
|
-
if (!value) return 'n/a';
|
|
29
|
-
return value.replace('T', ' ').slice(0, 19);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function fmtRate(value: number | undefined): string {
|
|
33
|
-
if (value === undefined) return 'n/a';
|
|
34
|
-
return `${(value * 100).toFixed(1)}%`;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function gateColor(status: string | undefined): string {
|
|
38
|
-
switch (status) {
|
|
39
|
-
case 'allowed':
|
|
40
|
-
return C.ok;
|
|
41
|
-
case 'blocked':
|
|
42
|
-
return C.error;
|
|
43
|
-
case 'no_data':
|
|
44
|
-
return C.warn;
|
|
45
|
-
default:
|
|
46
|
-
return C.dim;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export class PolicyPanel extends BasePanel {
|
|
51
|
-
private readonly _state: PolicyRuntimeState;
|
|
52
|
-
private readonly _unsub: (() => void) | null;
|
|
53
|
-
private _scrollOffset = 0;
|
|
54
|
-
|
|
55
|
-
public constructor(state: PolicyRuntimeState) {
|
|
56
|
-
super('policy', 'Policy', 'U', 'monitoring');
|
|
57
|
-
this._state = state;
|
|
58
|
-
this._unsub = state.subscribe(() => this.markDirty());
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
public override onActivate(): void {
|
|
62
|
-
super.onActivate();
|
|
63
|
-
this._scrollOffset = 0;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
public override onDestroy(): void {
|
|
67
|
-
this._unsub?.();
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
public handleInput(key: string): boolean {
|
|
71
|
-
if (key === 'up' || key === 'k') {
|
|
72
|
-
this._scrollOffset = Math.max(0, this._scrollOffset - 1);
|
|
73
|
-
this.markDirty();
|
|
74
|
-
return true;
|
|
75
|
-
}
|
|
76
|
-
if (key === 'down' || key === 'j') {
|
|
77
|
-
this._scrollOffset++;
|
|
78
|
-
this.markDirty();
|
|
79
|
-
return true;
|
|
80
|
-
}
|
|
81
|
-
if (key === 'r') {
|
|
82
|
-
this._state.recordTrendEntry();
|
|
83
|
-
this.markDirty();
|
|
84
|
-
return true;
|
|
85
|
-
}
|
|
86
|
-
return false;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
public render(width: number, height: number): Line[] {
|
|
90
|
-
this.needsRender = false;
|
|
91
|
-
const snapshot = this._state.getSnapshot();
|
|
92
|
-
const content = this._buildContent(width, snapshot);
|
|
93
|
-
const governanceSection = resolveScrollablePanelSection(width, height, {
|
|
94
|
-
footerLines: [buildPanelLine(width, [[' Up/Down scroll r record divergence trend snapshot', C.dim]])],
|
|
95
|
-
palette: C,
|
|
96
|
-
section: {
|
|
97
|
-
title: 'Governance',
|
|
98
|
-
scrollableLines: content,
|
|
99
|
-
scrollOffset: this._scrollOffset,
|
|
100
|
-
minRows: 4,
|
|
101
|
-
},
|
|
102
|
-
});
|
|
103
|
-
this._scrollOffset = governanceSection.scrollOffset;
|
|
104
|
-
const lines = buildPanelWorkspace(width, height, {
|
|
105
|
-
title: 'Policy And Governance',
|
|
106
|
-
sections: [governanceSection.section],
|
|
107
|
-
footerLines: [buildPanelLine(width, [[' Up/Down scroll r record divergence trend snapshot', C.dim]])],
|
|
108
|
-
palette: C,
|
|
109
|
-
});
|
|
110
|
-
while (lines.length < height) {
|
|
111
|
-
lines.push(createEmptyLine(width));
|
|
112
|
-
}
|
|
113
|
-
return lines.slice(0, height);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
private _buildContent(width: number, snapshot: PolicyPanelSnapshot): Line[] {
|
|
117
|
-
const lines: Line[] = [];
|
|
118
|
-
const current = snapshot.current;
|
|
119
|
-
const candidate = snapshot.candidate;
|
|
120
|
-
const divergence = snapshot.divergence;
|
|
121
|
-
const permissionAudit = snapshot.recentPermissionAudit;
|
|
122
|
-
const lintFindings = snapshot.lintFindings;
|
|
123
|
-
const simulationSummary = snapshot.lastSimulationSummary;
|
|
124
|
-
const preflightReview = snapshot.lastPreflightReview;
|
|
125
|
-
|
|
126
|
-
if (!current && !candidate) {
|
|
127
|
-
lines.push(buildPanelLine(width, [[' No policy bundles loaded. Use /policy load to begin.', C.empty]]));
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if (current) {
|
|
131
|
-
lines.push(buildPanelLine(width, [[' Current', C.label]]));
|
|
132
|
-
lines.push(buildPanelLine(width, [
|
|
133
|
-
[' Bundle: ', C.label],
|
|
134
|
-
[current.bundle.bundleId, C.value],
|
|
135
|
-
[' State: ', C.label],
|
|
136
|
-
[current.state, C.info],
|
|
137
|
-
]));
|
|
138
|
-
lines.push(buildPanelLine(width, [
|
|
139
|
-
[' Loaded: ', C.label],
|
|
140
|
-
[fmtTime(current.loadedAt), C.dim],
|
|
141
|
-
[' Active: ', C.label],
|
|
142
|
-
[fmtTime(current.activatedAt), C.dim],
|
|
143
|
-
]));
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
if (candidate) {
|
|
147
|
-
lines.push(buildPanelLine(width, [[' Candidate', C.label]]));
|
|
148
|
-
lines.push(buildPanelLine(width, [
|
|
149
|
-
[' Bundle: ', C.label],
|
|
150
|
-
[candidate.bundle.bundleId, C.value],
|
|
151
|
-
[' State: ', C.label],
|
|
152
|
-
[candidate.state, C.info],
|
|
153
|
-
]));
|
|
154
|
-
lines.push(buildPanelLine(width, [
|
|
155
|
-
[' Loaded: ', C.label],
|
|
156
|
-
[fmtTime(candidate.loadedAt), C.dim],
|
|
157
|
-
[' Rules: ', C.label],
|
|
158
|
-
[String(candidate.rules.length), C.value],
|
|
159
|
-
]));
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
if (snapshot.diff) {
|
|
163
|
-
lines.push(buildPanelLine(width, [[' Diff', C.label]]));
|
|
164
|
-
lines.push(buildPanelLine(width, [
|
|
165
|
-
[' Changes: ', C.label],
|
|
166
|
-
[String(snapshot.diff.totalChanges), C.value],
|
|
167
|
-
[' Added: ', C.label],
|
|
168
|
-
[String(snapshot.diff.added.length), C.ok],
|
|
169
|
-
[' Removed: ', C.label],
|
|
170
|
-
[String(snapshot.diff.removed.length), C.error],
|
|
171
|
-
[' Changed: ', C.label],
|
|
172
|
-
[String(snapshot.diff.changed.length), C.warn],
|
|
173
|
-
]));
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
if (divergence) {
|
|
177
|
-
lines.push(buildPanelLine(width, [[' Governance Gate', C.label]]));
|
|
178
|
-
lines.push(buildPanelLine(width, [
|
|
179
|
-
[' Mode: ', C.label],
|
|
180
|
-
[divergence.mode, C.info],
|
|
181
|
-
[' Gate: ', C.label],
|
|
182
|
-
[divergence.gate.status, gateColor(divergence.gate.status)],
|
|
183
|
-
[' Divergence: ', C.label],
|
|
184
|
-
[fmtRate(divergence.gate.divergenceRate ?? divergence.report.overall.divergenceRate), C.value],
|
|
185
|
-
]));
|
|
186
|
-
lines.push(buildPanelLine(width, [
|
|
187
|
-
[' Evaluations: ', C.label],
|
|
188
|
-
[String(divergence.report.overall.totalEvaluations), C.value],
|
|
189
|
-
[' Trend points: ', C.label],
|
|
190
|
-
[String(divergence.trend.length), C.value],
|
|
191
|
-
]));
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
if (snapshot.history.length > 0) {
|
|
195
|
-
lines.push(buildPanelLine(width, [[' History', C.label]]));
|
|
196
|
-
for (const version of snapshot.history.slice(0, 5)) {
|
|
197
|
-
lines.push(buildPanelLine(width, [
|
|
198
|
-
[' ', C.label],
|
|
199
|
-
[version.bundle.bundleId, C.value],
|
|
200
|
-
[' ', C.label],
|
|
201
|
-
[version.state, C.dim],
|
|
202
|
-
[' ', C.label],
|
|
203
|
-
[fmtTime(version.activatedAt ?? version.loadedAt), C.dim],
|
|
204
|
-
]));
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
if (permissionAudit.length > 0) {
|
|
209
|
-
lines.push(buildPanelLine(width, [[' Permission Audit', C.label]]));
|
|
210
|
-
for (const entry of permissionAudit.slice(0, 5)) {
|
|
211
|
-
const outcome = entry.approved === undefined ? 'pending' : entry.approved ? 'approved' : 'denied';
|
|
212
|
-
const outcomeColor = entry.approved === undefined ? C.warn : entry.approved ? C.ok : C.error;
|
|
213
|
-
lines.push(buildPanelLine(width, [
|
|
214
|
-
[' ', C.label],
|
|
215
|
-
[entry.tool, C.value],
|
|
216
|
-
[' ', C.label],
|
|
217
|
-
[entry.riskLevel.toUpperCase(), outcomeColor],
|
|
218
|
-
[' ', C.label],
|
|
219
|
-
[outcome, outcomeColor],
|
|
220
|
-
]));
|
|
221
|
-
lines.push(buildPanelLine(width, [
|
|
222
|
-
[' ', C.label],
|
|
223
|
-
[entry.summary.slice(0, Math.max(0, width - 6)), C.dim],
|
|
224
|
-
]));
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
if (lintFindings.length > 0) {
|
|
229
|
-
lines.push(buildPanelLine(width, [[' Policy Lint', C.label]]));
|
|
230
|
-
for (const finding of lintFindings.slice(0, 5)) {
|
|
231
|
-
const color = finding.severity === 'error' ? C.error : finding.severity === 'warn' ? C.warn : C.info;
|
|
232
|
-
lines.push(buildPanelLine(width, [
|
|
233
|
-
[' ', C.label],
|
|
234
|
-
[finding.severity.toUpperCase(), color],
|
|
235
|
-
[' ', C.label],
|
|
236
|
-
[finding.message.slice(0, Math.max(0, width - 14)), color],
|
|
237
|
-
]));
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
if (!preflightReview) {
|
|
242
|
-
if (!current && !candidate && !divergence && snapshot.history.length === 0 && permissionAudit.length === 0 && lintFindings.length === 0 && !simulationSummary) {
|
|
243
|
-
lines.push(buildPanelLine(width, [[' Preflight Review', C.label]]));
|
|
244
|
-
lines.push(buildPanelLine(width, [[' No proactive preflight review recorded yet.', C.empty]]));
|
|
245
|
-
}
|
|
246
|
-
} else {
|
|
247
|
-
lines.push(buildPanelLine(width, [[' Preflight Review', C.label]]));
|
|
248
|
-
const statusColor =
|
|
249
|
-
preflightReview.status === 'pass'
|
|
250
|
-
? C.ok
|
|
251
|
-
: preflightReview.status === 'warn'
|
|
252
|
-
? C.warn
|
|
253
|
-
: C.error;
|
|
254
|
-
lines.push(buildPanelLine(width, [
|
|
255
|
-
[' Status: ', C.label],
|
|
256
|
-
[preflightReview.status.toUpperCase(), statusColor],
|
|
257
|
-
[' Issues: ', C.label],
|
|
258
|
-
[String(preflightReview.issueCount), C.value],
|
|
259
|
-
[' Generated: ', C.label],
|
|
260
|
-
[fmtTime(preflightReview.generatedAt), C.dim],
|
|
261
|
-
]));
|
|
262
|
-
lines.push(buildPanelLine(width, [
|
|
263
|
-
[' ', C.label],
|
|
264
|
-
[preflightReview.summary.slice(0, Math.max(0, width - 2)), C.dim],
|
|
265
|
-
]));
|
|
266
|
-
for (const issue of preflightReview.issues.slice(0, 4)) {
|
|
267
|
-
const issueColor = issue.severity === 'error' ? C.error : issue.severity === 'warn' ? C.warn : C.info;
|
|
268
|
-
lines.push(buildPanelLine(width, [
|
|
269
|
-
[' ', C.label],
|
|
270
|
-
[issue.severity.toUpperCase(), issueColor],
|
|
271
|
-
[' ', C.label],
|
|
272
|
-
[issue.message.slice(0, Math.max(0, width - 14)), issueColor],
|
|
273
|
-
]));
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
if (!simulationSummary) {
|
|
278
|
-
if (!current && !candidate && !divergence && snapshot.history.length === 0 && permissionAudit.length === 0 && lintFindings.length === 0 && !preflightReview) {
|
|
279
|
-
lines.push(buildPanelLine(width, [[' Simulation Samples', C.label]]));
|
|
280
|
-
lines.push(buildPanelLine(width, [[' No concrete simulation samples recorded yet.', C.empty]]));
|
|
281
|
-
}
|
|
282
|
-
} else {
|
|
283
|
-
lines.push(buildPanelLine(width, [[' Simulation Samples', C.label]]));
|
|
284
|
-
lines.push(buildPanelLine(width, [
|
|
285
|
-
[' Mode: ', C.label],
|
|
286
|
-
[simulationSummary.mode, C.info],
|
|
287
|
-
[' Diverged: ', C.label],
|
|
288
|
-
[`${simulationSummary.divergentScenarios}/${simulationSummary.totalScenarios}`, simulationSummary.divergentScenarios > 0 ? C.warn : C.ok],
|
|
289
|
-
[' Allowed(actual/sim): ', C.label],
|
|
290
|
-
[`${simulationSummary.allowedByActual}/${simulationSummary.allowedBySimulated}`, C.value],
|
|
291
|
-
]));
|
|
292
|
-
for (const result of simulationSummary.results.slice(0, 4)) {
|
|
293
|
-
const color = result.diverged ? C.warn : (result.authoritativeDecision.allowed ? C.ok : C.error);
|
|
294
|
-
lines.push(buildPanelLine(width, [
|
|
295
|
-
[' ', C.label],
|
|
296
|
-
[result.scenario.label.slice(0, Math.max(0, width - 40)), C.value],
|
|
297
|
-
[' ', C.label],
|
|
298
|
-
[(result.authoritativeDecision.allowed ? 'allow' : 'deny').toUpperCase(), color],
|
|
299
|
-
[' ', C.label],
|
|
300
|
-
[result.diverged ? (result.divergenceType ?? 'diverged') : 'aligned', color],
|
|
301
|
-
]));
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
lines.push(buildPanelLine(width, [[' /policy opens this panel. Press r to record a divergence trend snapshot.', C.dim]]));
|
|
306
|
-
return lines;
|
|
307
|
-
}
|
|
308
|
-
}
|
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
import { type Line } from '../types/grid.ts';
|
|
2
|
-
import { fitDisplay, getDisplayWidth, truncateDisplay } from '../utils/terminal-width.ts';
|
|
3
|
-
import type { PanelPicker } from '../panels/panel-picker.ts';
|
|
4
|
-
import { CATEGORY_LABELS, CATEGORY_ORDER } from '../panels/panel-picker.ts';
|
|
5
|
-
import type { PanelCategory, PanelRegistration } from '../panels/types.ts';
|
|
6
|
-
import {
|
|
7
|
-
createOverlayBorderLine,
|
|
8
|
-
createOverlayBoxLayout,
|
|
9
|
-
createOverlayContentLine,
|
|
10
|
-
DEFAULT_OVERLAY_PALETTE,
|
|
11
|
-
putOverlayText,
|
|
12
|
-
} from './overlay-box.ts';
|
|
13
|
-
import { getOverlaySurfaceMetrics } from './overlay-viewport.ts';
|
|
14
|
-
|
|
15
|
-
const TITLE_FG = '#cbd5e1';
|
|
16
|
-
const CATEGORY_FG = '#94a3b8';
|
|
17
|
-
const SELECTED_FG = '#e2e8f0';
|
|
18
|
-
const SELECTED_BG = '#1e293b';
|
|
19
|
-
const BODY_FG = '252';
|
|
20
|
-
const BORDER_FG = DEFAULT_OVERLAY_PALETTE.borderFg;
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Render the panel picker modal as Line[] for overlay in the viewport.
|
|
24
|
-
* Panels are grouped by category. Category headers are inserted between groups.
|
|
25
|
-
* When a search query is active a search-bar row is shown beneath the title.
|
|
26
|
-
*/
|
|
27
|
-
export function renderPanelPickerOverlay(
|
|
28
|
-
picker: PanelPicker,
|
|
29
|
-
width: number,
|
|
30
|
-
viewportHeight = 24,
|
|
31
|
-
): Line[] {
|
|
32
|
-
if (!picker.active) return [];
|
|
33
|
-
|
|
34
|
-
const lines: Line[] = [];
|
|
35
|
-
const metrics = getOverlaySurfaceMetrics(width, viewportHeight, {
|
|
36
|
-
margin: 4,
|
|
37
|
-
maxWidth: 72,
|
|
38
|
-
chromeRows: picker.searchQuery.length > 0 ? 7 : 6,
|
|
39
|
-
minContentRows: 6,
|
|
40
|
-
maxContentRows: 10,
|
|
41
|
-
});
|
|
42
|
-
const layout = createOverlayBoxLayout(width, metrics.margin, metrics.boxWidth);
|
|
43
|
-
const contentW = layout.innerWidth;
|
|
44
|
-
const titleFg = TITLE_FG;
|
|
45
|
-
const borderFg = BORDER_FG;
|
|
46
|
-
|
|
47
|
-
// ── Title bar ──────────────────────────────────────────────────────────────
|
|
48
|
-
const titleLine = createOverlayBorderLine(width, layout, '┌', '─', '┐', borderFg);
|
|
49
|
-
putOverlayText(titleLine, layout.margin + 2, layout.width - 4, 'Open Panel Workspace', { fg: titleFg, bold: true });
|
|
50
|
-
lines.push(titleLine);
|
|
51
|
-
|
|
52
|
-
// ── Search bar (shown when query is non-empty) ──────────────────────────────
|
|
53
|
-
if (picker.searchQuery.length > 0) {
|
|
54
|
-
const searchLine = createOverlayContentLine(width, layout, borderFg);
|
|
55
|
-
const searchLabel = '\u2315 ';
|
|
56
|
-
const queryAvail = contentW - getDisplayWidth(searchLabel);
|
|
57
|
-
const queryText = fitDisplay(picker.searchQuery, queryAvail);
|
|
58
|
-
putOverlayText(searchLine, layout.margin + 2, getDisplayWidth(searchLabel), searchLabel, { fg: titleFg });
|
|
59
|
-
putOverlayText(searchLine, layout.margin + 2 + getDisplayWidth(searchLabel), queryAvail, queryText, { fg: titleFg });
|
|
60
|
-
lines.push(searchLine);
|
|
61
|
-
|
|
62
|
-
lines.push(createOverlayBorderLine(width, layout, '├', '─', '┤', borderFg));
|
|
63
|
-
} else {
|
|
64
|
-
const placeholder = ' Browse by category or start typing to filter by panel name, purpose, or category.';
|
|
65
|
-
const row = createOverlayContentLine(width, layout, borderFg);
|
|
66
|
-
putOverlayText(row, layout.margin + 2, contentW, fitDisplay(placeholder, contentW), { fg: '244', dim: true });
|
|
67
|
-
lines.push(row);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const visible = picker.getVisible();
|
|
71
|
-
|
|
72
|
-
if (visible.length === 0) {
|
|
73
|
-
const noResults = 'No panels match your search';
|
|
74
|
-
const noRow = createOverlayContentLine(width, layout, borderFg);
|
|
75
|
-
putOverlayText(noRow, layout.margin + 2, contentW, fitDisplay(noResults, contentW), { fg: '244', dim: true });
|
|
76
|
-
lines.push(noRow);
|
|
77
|
-
} else {
|
|
78
|
-
// Build a flat render list of { type: 'header' | 'item', ... } entries
|
|
79
|
-
// so we can compute the correct scroll window over all rows.
|
|
80
|
-
type HeaderEntry = { type: 'header'; category: PanelCategory };
|
|
81
|
-
type ItemEntry = { type: 'item'; reg: PanelRegistration; flatIndex: number };
|
|
82
|
-
type RenderEntry = HeaderEntry | ItemEntry;
|
|
83
|
-
|
|
84
|
-
const renderEntries: RenderEntry[] = [];
|
|
85
|
-
|
|
86
|
-
// When no search query, group by category; when searching, show flat list
|
|
87
|
-
// with a single "Results" header.
|
|
88
|
-
if (picker.searchQuery.length === 0) {
|
|
89
|
-
const byCategory = new Map<PanelCategory, PanelRegistration[]>();
|
|
90
|
-
for (const reg of visible) {
|
|
91
|
-
const group = byCategory.get(reg.category) ?? [];
|
|
92
|
-
group.push(reg);
|
|
93
|
-
byCategory.set(reg.category, group);
|
|
94
|
-
}
|
|
95
|
-
let flatIndex = 0;
|
|
96
|
-
for (const cat of CATEGORY_ORDER) {
|
|
97
|
-
const group = byCategory.get(cat);
|
|
98
|
-
if (!group || group.length === 0) continue;
|
|
99
|
-
renderEntries.push({ type: 'header', category: cat });
|
|
100
|
-
for (const reg of group) {
|
|
101
|
-
renderEntries.push({ type: 'item', reg, flatIndex });
|
|
102
|
-
flatIndex++;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
} else {
|
|
106
|
-
for (let i = 0; i < visible.length; i++) {
|
|
107
|
-
renderEntries.push({ type: 'item', reg: visible[i], flatIndex: i });
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Compute scroll window: ensure the selected item row stays visible.
|
|
112
|
-
// Find the render-entry index of the selected item.
|
|
113
|
-
const selectedEntryIdx = renderEntries.findIndex(
|
|
114
|
-
e => e.type === 'item' && e.flatIndex === picker.selectedIndex,
|
|
115
|
-
);
|
|
116
|
-
const total = renderEntries.length;
|
|
117
|
-
const maxVisible = metrics.contentRows;
|
|
118
|
-
let startEntry = 0;
|
|
119
|
-
if (total > maxVisible && selectedEntryIdx >= 0) {
|
|
120
|
-
startEntry = Math.max(
|
|
121
|
-
0,
|
|
122
|
-
Math.min(
|
|
123
|
-
selectedEntryIdx - Math.floor(maxVisible / 2),
|
|
124
|
-
total - maxVisible,
|
|
125
|
-
),
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
const endEntry = Math.min(startEntry + maxVisible, total);
|
|
129
|
-
|
|
130
|
-
for (let i = startEntry; i < endEntry; i++) {
|
|
131
|
-
const entry = renderEntries[i];
|
|
132
|
-
|
|
133
|
-
if (entry.type === 'header') {
|
|
134
|
-
// Category header row
|
|
135
|
-
const label = CATEGORY_LABELS[entry.category].toUpperCase();
|
|
136
|
-
const headerRow = createOverlayContentLine(width, layout, borderFg);
|
|
137
|
-
putOverlayText(headerRow, layout.margin + 2, contentW, fitDisplay(` ${label}`, contentW), { fg: CATEGORY_FG, dim: true });
|
|
138
|
-
lines.push(headerRow);
|
|
139
|
-
} else {
|
|
140
|
-
const { reg, flatIndex } = entry;
|
|
141
|
-
const isSelected = flatIndex === picker.selectedIndex;
|
|
142
|
-
const indicator = isSelected ? '\u25b6 ' : ' ';
|
|
143
|
-
|
|
144
|
-
// icon (1 char) + 2 spaces + name
|
|
145
|
-
const iconStr = reg.icon + ' ';
|
|
146
|
-
const iconW = getDisplayWidth(iconStr);
|
|
147
|
-
|
|
148
|
-
// separator between name and description: ' \u2014 '
|
|
149
|
-
const sep = ' \u2014 ';
|
|
150
|
-
const sepW = getDisplayWidth(sep);
|
|
151
|
-
|
|
152
|
-
// Allocate space: indicator(2) + icon(iconW) + name + sep + desc fills contentW
|
|
153
|
-
const nameMaxW = Math.max(8, Math.floor((contentW - 2 - iconW - sepW) * 0.35));
|
|
154
|
-
const descMaxW = contentW - 2 - iconW - nameMaxW - sepW;
|
|
155
|
-
|
|
156
|
-
const nameRaw = reg.name;
|
|
157
|
-
const nameStr = fitDisplay(nameRaw, nameMaxW);
|
|
158
|
-
|
|
159
|
-
const descRaw = reg.description;
|
|
160
|
-
const descStr = fitDisplay(descRaw, descMaxW);
|
|
161
|
-
|
|
162
|
-
const row = createOverlayContentLine(width, layout, borderFg, isSelected ? SELECTED_BG : '');
|
|
163
|
-
const rowText = indicator + iconStr + nameStr + sep + descStr;
|
|
164
|
-
putOverlayText(row, layout.margin + 2, contentW, fitDisplay(truncateDisplay(rowText, contentW), contentW), {
|
|
165
|
-
fg: isSelected ? SELECTED_FG : BODY_FG,
|
|
166
|
-
bg: isSelected ? SELECTED_BG : '',
|
|
167
|
-
bold: isSelected,
|
|
168
|
-
});
|
|
169
|
-
lines.push(row);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const selected = picker.getSelected();
|
|
174
|
-
if (selected) {
|
|
175
|
-
lines.push(createOverlayBorderLine(width, layout, '├', '─', '┤', borderFg));
|
|
176
|
-
const categoryLabel = CATEGORY_LABELS[selected.category].toUpperCase();
|
|
177
|
-
const selectedLine = createOverlayContentLine(width, layout, borderFg);
|
|
178
|
-
putOverlayText(selectedLine, layout.margin + 2, contentW, fitDisplay(`${selected.icon} ${selected.name} [${categoryLabel}]`, contentW), { fg: SELECTED_FG });
|
|
179
|
-
lines.push(selectedLine);
|
|
180
|
-
const desc = fitDisplay(truncateDisplay(selected.description, contentW), contentW);
|
|
181
|
-
const descRow = createOverlayContentLine(width, layout, borderFg);
|
|
182
|
-
putOverlayText(descRow, layout.margin + 2, contentW, desc, { fg: '244', dim: true });
|
|
183
|
-
lines.push(descRow);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// ── Scroll indicator ───────────────────────────────────────────────────
|
|
187
|
-
if (total > maxVisible) {
|
|
188
|
-
const scrollInfo = `${picker.selectedIndex + 1}/${visible.length}`;
|
|
189
|
-
const scrollRow = createOverlayContentLine(width, layout, borderFg);
|
|
190
|
-
putOverlayText(scrollRow, layout.margin + 2 + Math.max(0, contentW - getDisplayWidth(scrollInfo)), getDisplayWidth(scrollInfo), scrollInfo, { fg: '240', dim: true });
|
|
191
|
-
lines.push(scrollRow);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// ── Bottom border with hints ───────────────────────────────────────────────
|
|
196
|
-
const hints = '[Up/Down] Navigate [Enter] Open [/] Filter [Esc] Cancel';
|
|
197
|
-
const bottomLine = createOverlayBorderLine(width, layout, '└', '─', '┘', borderFg);
|
|
198
|
-
putOverlayText(bottomLine, layout.margin + 2, layout.width - 4, truncateDisplay(hints, layout.width - 4), { fg: '240', dim: true });
|
|
199
|
-
lines.push(bottomLine);
|
|
200
|
-
|
|
201
|
-
return lines;
|
|
202
|
-
}
|