vgxness 1.2.0 → 1.3.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 (70) hide show
  1. package/README.md +7 -6
  2. package/dist/cli/cli-help.js +8 -7
  3. package/dist/cli/commands/index.js +1 -1
  4. package/dist/cli/commands/interactive-entrypoint-dispatcher.js +150 -0
  5. package/dist/cli/commands/setup-dispatcher.js +7 -4
  6. package/dist/cli/dispatcher.js +10 -8
  7. package/dist/cli/index.js +0 -0
  8. package/dist/cli/setup-wizard-renderer.js +1 -1
  9. package/dist/cli/tui/main-menu/index.js +0 -1
  10. package/dist/cli/tui/main-menu/main-menu-controller.js +0 -2
  11. package/dist/cli/tui/main-menu/main-menu-read-model.js +2 -8
  12. package/dist/cli/tui/main-menu/main-menu-render-shape.js +5 -1
  13. package/dist/cli/tui/main-menu/main-menu-state.js +1 -1
  14. package/dist/cli/tui/opentui/code/index.js +210 -0
  15. package/dist/cli/tui/opentui/code/screen.js +107 -0
  16. package/dist/cli/tui/opentui/code/smoke.js +32 -0
  17. package/dist/cli/tui/opentui/main-menu/index.js +3 -0
  18. package/dist/cli/tui/opentui/main-menu/renderer.js +68 -0
  19. package/dist/cli/tui/opentui/main-menu/screen.js +62 -0
  20. package/dist/cli/tui/opentui/main-menu/smoke.js +17 -0
  21. package/dist/cli/tui/opentui/main-menu/view.js +8 -0
  22. package/dist/cli/tui/opentui/setup/index.js +3 -0
  23. package/dist/cli/tui/opentui/setup/renderer.js +87 -0
  24. package/dist/cli/tui/opentui/setup/screen.js +170 -0
  25. package/dist/cli/tui/opentui/setup/smoke.js +42 -0
  26. package/dist/cli/tui/opentui/setup/view.js +12 -0
  27. package/dist/cli/tui/setup/setup-tui-input.js +43 -0
  28. package/dist/cli/tui/setup/setup-tui-read-model.js +1 -1
  29. package/dist/cli/tui/setup/setup-tui-render-shape.js +1 -1
  30. package/dist/cli/tui/setup/setup-tui-view-helpers.js +46 -0
  31. package/dist/cli/tui/visual/index.js +0 -2
  32. package/dist/code/tui/approval-actions.js +33 -0
  33. package/dist/code/tui/prompt-mode.js +11 -0
  34. package/dist/code/tui/runtime-events.js +320 -0
  35. package/dist/sdd/sdd-workflow-service.js +0 -24
  36. package/dist/setup/providers/antigravity-setup-adapter.js +1 -1
  37. package/dist/setup/providers/claude-setup-adapter.js +2 -2
  38. package/dist/setup/providers/custom-setup-adapter.js +1 -1
  39. package/dist/setup/providers/opencode-setup-adapter.js +3 -3
  40. package/dist/setup/setup-lifecycle-service.js +1 -1
  41. package/docs/architecture.md +4 -4
  42. package/docs/cli.md +11 -10
  43. package/docs/funcionamiento-del-sistema.md +6 -7
  44. package/docs/prd.md +4 -4
  45. package/docs/vgxcode.md +76 -0
  46. package/package.json +5 -6
  47. package/dist/cli/commands/dashboard-dispatcher.js +0 -560
  48. package/dist/cli/dashboard-operational-read-models.js +0 -428
  49. package/dist/cli/dashboard-renderer.js +0 -158
  50. package/dist/cli/dashboard-screen-renderers.js +0 -256
  51. package/dist/cli/dashboard-tui-read-model.js +0 -73
  52. package/dist/cli/dashboard-tui-state.js +0 -314
  53. package/dist/cli/interactive-dashboard.js +0 -34
  54. package/dist/cli/tui/dashboard/dashboard-adapter.js +0 -4
  55. package/dist/cli/tui/main-menu/main-menu-app.js +0 -28
  56. package/dist/cli/tui/render-ink-app.js +0 -10
  57. package/dist/cli/tui/setup/screens/applying-screen.js +0 -6
  58. package/dist/cli/tui/setup/screens/cancellation-screen.js +0 -6
  59. package/dist/cli/tui/setup/screens/error-recovery-screen.js +0 -6
  60. package/dist/cli/tui/setup/screens/final-confirmation-screen.js +0 -6
  61. package/dist/cli/tui/setup/screens/opencode-details-screen.js +0 -10
  62. package/dist/cli/tui/setup/screens/plan-review-screen.js +0 -6
  63. package/dist/cli/tui/setup/screens/project-database-screen.js +0 -6
  64. package/dist/cli/tui/setup/screens/provider-screen.js +0 -7
  65. package/dist/cli/tui/setup/screens/result-screen.js +0 -16
  66. package/dist/cli/tui/setup/screens/screen-components.js +0 -103
  67. package/dist/cli/tui/setup/screens/welcome-screen.js +0 -6
  68. package/dist/cli/tui/setup/setup-tui-app.js +0 -113
  69. package/dist/cli/tui/visual/choice-list.js +0 -10
  70. package/dist/cli/tui/visual/layout.js +0 -10
@@ -1,256 +0,0 @@
1
- import { classifySetupForProject, copyOnly, isSetupReady, } from './dashboard-tui-read-model.js';
2
- import { dashboardScreens } from './dashboard-tui-state.js';
3
- import { renderSetupStatus } from './setup-status-renderer.js';
4
- import { joinSections, renderFooterGroups, renderPanel, renderTabRow, styleFocusedRow } from './tui-render-helpers.js';
5
- const screenLabels = dashboardScreens;
6
- const menuLabelColumnWidth = Math.max(...screenLabels.map((screen) => screen.label.length));
7
- const menuFocusColumnWidth = '[focused]'.length;
8
- const menuDescriptionColumnWidth = Math.max(...screenLabels.map((screen) => menuPurpose(screen.id).length));
9
- export function renderDashboardFrame(state, options = {}) {
10
- if (state.viewMode === 'menu') {
11
- return joinSections([
12
- renderDashboardHeader(state, options),
13
- renderErrorsPanel(state, options),
14
- renderDashboardMenuView(state, options),
15
- renderDashboardFooter(state, options),
16
- ]);
17
- }
18
- return joinSections([
19
- renderDashboardHeader(state, options),
20
- renderTabRow(screenLabels.map((screen) => ({
21
- key: screen.key,
22
- label: screen.label,
23
- selected: screen.id === state.activeScreen,
24
- focused: screen.id === state.focusedScreen,
25
- })), options),
26
- renderErrorsPanel(state, options),
27
- renderBackControl(options),
28
- renderPanel(`Content · ${activeLabel(state.activeScreen)}`, renderActiveScreen(state, options).split('\n'), options),
29
- renderActionPreviewPanel(state.actionPreview, options),
30
- renderDashboardFooter(state, options),
31
- ]);
32
- }
33
- function renderDashboardHeader(state, options) {
34
- if (state.viewMode === 'menu') {
35
- return renderPanel('Header', ['VGXNESS Interactive Dashboard', `Project: ${state.project ?? 'none'} · Change: ${state.change ?? 'none'}`], options);
36
- }
37
- return renderPanel('Header', [
38
- 'VGXNESS Interactive Dashboard',
39
- `Context: Project: ${state.project ?? 'none'} · Change: ${state.change ?? 'none'} · View: ${state.viewMode} · Screen: ${activeLabel(state.activeScreen)} · Menu focus: ${activeLabel(state.focusedScreen)}`,
40
- `Menu order: ${screenLabels.map((screen) => screen.label).join(', ')}`,
41
- ], options);
42
- }
43
- function renderDashboardMenuView(state, options) {
44
- return renderPanel('Menu', ['Choose a section to open:', ...screenLabels.map((screen) => renderMenuRow(screen, screen.id === state.focusedScreen, options))], options);
45
- }
46
- function renderMenuRow(screen, focused, options) {
47
- const marker = focused ? '›' : ' ';
48
- const label = screen.label.padEnd(menuLabelColumnWidth);
49
- const focusMarker = (focused ? '[focused]' : '').padEnd(menuFocusColumnWidth);
50
- const description = menuPurpose(screen.id).padEnd(menuDescriptionColumnWidth);
51
- const row = `${marker} [${screen.key}] ${label} ${focusMarker} ${description}`;
52
- return focused ? styleFocusedRow(row, options) : row;
53
- }
54
- function menuPurpose(id) {
55
- if (id === 'installation')
56
- return 'Read-only install plan';
57
- if (id === 'status')
58
- return 'Provider status and doctor';
59
- if (id === 'runs')
60
- return 'Run status';
61
- if (id === 'approvals')
62
- return 'Approval queue';
63
- if (id === 'agents')
64
- return 'Agent registry';
65
- if (id === 'skills')
66
- return 'Skill registry';
67
- if (id === 'memory')
68
- return 'Memory observations';
69
- if (id === 'sdd')
70
- return 'Artifact status';
71
- if (id === 'permissions')
72
- return 'Permission policy';
73
- return 'Workspace context';
74
- }
75
- function renderBackControl(options) {
76
- return renderPanel('Back', ['[ b / Esc / Backspace ] Back or close detail', 'Direct shortcuts 1-9/0 open another section.'], options);
77
- }
78
- function renderErrorsPanel(state, options) {
79
- const errors = [state.error === undefined ? undefined : `Error: ${state.error}`].filter((value) => value !== undefined);
80
- if (errors.length === 0)
81
- return undefined;
82
- return renderPanel('Recovery', [...errors, 'Recovery: press r to refresh.'], { ...options, status: 'error' });
83
- }
84
- function renderDashboardFooter(state, options) {
85
- const modeGroups = state.viewMode === 'menu'
86
- ? [{ label: 'Menu', items: ['↑/↓ or j/k move', 'Enter open', '1-9/0 open'] }]
87
- : [{ label: 'Section', items: ['j/k or ↑/↓ move', 'Enter detail only', '[a] Action preview', 'b/Esc/Backspace back'] }];
88
- return renderPanel('Footer/help', [
89
- ...renderFooterGroups([...modeGroups, { label: 'Global', items: ['r refresh', '? help', 'q quit'] }], options),
90
- 'Safety: read-only dashboard; actions are copy-only and never apply/install/approve.',
91
- ], options);
92
- }
93
- function renderActionPreviewPanel(preview, options) {
94
- if (preview === undefined)
95
- return undefined;
96
- if (!preview.available)
97
- return renderPanel('Action preview', [
98
- `Source: ${preview.screen}`,
99
- `State: unavailable`,
100
- preview.message,
101
- 'Safety: display-only; does not run in TUI.',
102
- ], options);
103
- const descriptor = preview.descriptor;
104
- return renderPanel('Action preview', [
105
- `Label: ${descriptor.label}`,
106
- `Source: ${descriptor.source.screen} · ${descriptor.source.rowTitle} (${descriptor.source.rowId})`,
107
- `Mode: ${descriptor.mode}`,
108
- `Safety: ${descriptor.safety}; copyOnly=${String(descriptor.copyOnly)}; executesInTui=${String(descriptor.executesInTui)}`,
109
- `Command: ${descriptor.command}`,
110
- `Guidance: ${descriptor.guidance}`,
111
- 'This preview does not run in TUI.',
112
- ], options);
113
- }
114
- function renderActiveScreen(state, options) {
115
- if (state.activeScreen === 'installation')
116
- return renderInstallationScreen(state, options);
117
- if (state.activeScreen === 'status')
118
- return renderStatusScreen(state, options);
119
- if (state.activeScreen === 'agents')
120
- return renderManagementScreen(state.management?.agents, state, options);
121
- if (state.activeScreen === 'skills')
122
- return renderManagementScreen(state.management?.skills, state, options);
123
- if (state.activeScreen === 'memory')
124
- return renderManagementScreen(state.management?.memory, state, options);
125
- if (state.activeScreen === 'sdd')
126
- return renderManagementScreen(state.management?.sdd, state, options);
127
- if (state.activeScreen === 'runs')
128
- return renderManagementScreen(state.management?.runs, state, options);
129
- if (state.activeScreen === 'approvals')
130
- return renderManagementScreen(state.management?.approvals, state, options);
131
- if (state.activeScreen === 'permissions')
132
- return renderManagementScreen(state.management?.permissions, state, options);
133
- return state.management?.settings === undefined ? renderSettingsScreen(state) : renderManagementScreen(state.management.settings, state, options);
134
- }
135
- function renderInstallationScreen(state, options) {
136
- const status = classifySetupForProject(state.project, state.setup, state.sectionErrors.setup);
137
- if (state.installation !== undefined)
138
- return renderInstallationReadModel(state, options);
139
- if (status === 'ready-without-project' || status === 'loading')
140
- return renderInstallationReadModel(state, options);
141
- if (status === 'error')
142
- return [
143
- 'Installation',
144
- 'State: error',
145
- `Setup status unavailable: ${state.sectionErrors.setup ?? state.error ?? 'unknown error'}`,
146
- copyOnly('Inspect setup status', 'npm run cli -- setup status'),
147
- ].join('\n');
148
- if (state.setup === undefined)
149
- return ['Installation', 'State: empty', 'No setup status data is available yet.', copyOnly('Inspect setup status', 'npm run cli -- setup status')].join('\n');
150
- const setupReady = isSetupReady(state.setup);
151
- return [
152
- 'Installation',
153
- `State: ${setupReady ? 'ready' : 'blocked'}`,
154
- `Next action: ${setupReady ? 'Continue to Status with key 2.' : state.setup.nextAction.reason}`,
155
- 'Readiness checklist',
156
- checklist('Project store', state.setup.store.status, state.setup.store.blocker),
157
- checklist('Default manager/profile', state.setup.defaults.status, state.setup.defaults.blocker),
158
- checklist('MCP visibility/setup', state.setup.mcp.status, state.setup.mcp.nextAction),
159
- checklist('Doctor guidance', setupReady ? 'ready' : 'unknown', setupReady ? undefined : 'Run diagnostics outside the TUI.'),
160
- renderSetupStatus(state.setup),
161
- 'Read-only guidance: choose AI tool integration in vgx init only; this dashboard only previews and copies commands.',
162
- copyOnly('Preview setup', 'npm run cli -- mcp setup --provider opencode --preview'),
163
- copyOnly('Plan install', 'npm run cli -- mcp install opencode --plan'),
164
- copyOnly('Inspect doctor', 'npm run cli -- mcp doctor opencode'),
165
- ].join('\n');
166
- }
167
- function renderStatusScreen(state, options) {
168
- return ['Status', 'State: read-only', renderInstallationReadModel(state, options)].join('\n');
169
- }
170
- function renderInstallationReadModel(state, options) {
171
- const model = state.installation;
172
- if (model === undefined) {
173
- const doctorLines = state.doctor === undefined
174
- ? ['Doctor: not executed by dashboard; use copy-only command below.']
175
- : [state.doctor.summary, ...state.doctor.warnings.map((warning) => `Warning: ${warning}`)];
176
- return [
177
- 'Lifecycle',
178
- `State: ${state.setup === undefined ? 'loading' : isSetupReady(state.setup) ? 'ready' : 'blocked'}`,
179
- state.setup?.nextAction.reason ?? 'Waiting for setup lifecycle evidence.',
180
- 'OpenCode config',
181
- 'Provider status: unavailable in this load; dashboard did not write config.',
182
- 'Doctor',
183
- ...doctorLines,
184
- 'Change plan',
185
- 'Preview-only. Use explicit CLI setup/init flows for any future writes.',
186
- copyOnly('Preview OpenCode setup', 'npm run cli -- mcp setup --provider opencode --preview'),
187
- copyOnly('Inspect OpenCode doctor', 'npm run cli -- mcp doctor opencode'),
188
- ].join('\n');
189
- }
190
- const status = model.providerStatus;
191
- const doctor = model.providerDoctor;
192
- const plan = model.providerChangePlan;
193
- const lines = [
194
- 'Lifecycle',
195
- `State: ${model.lifecycle.status}`,
196
- model.lifecycle.summary,
197
- ...(model.lifecycle.nextAction === undefined ? [] : [`Next: ${model.lifecycle.nextAction}`]),
198
- 'OpenCode config',
199
- status === undefined ? 'Status: unavailable' : `${status.summary} (${status.overallStatus})`,
200
- ...(status?.config.paths.slice(0, 4).map((path) => `- ${path.label}: ${path.status} · ${path.detail}`) ?? []),
201
- 'Doctor',
202
- doctor === undefined ? 'Doctor: unavailable' : `${doctor.summary} (${doctor.passedCount}/${doctor.checkCount} passing)`,
203
- ...(doctor?.checks.slice(0, 6).map((check) => `- ${check.id}: ${check.status} · ${check.detail}`) ?? []),
204
- 'Change plan',
205
- plan === undefined ? 'Plan: unavailable' : `${plan.summary} · effects=${plan.previewEffects.action} · writes=${String(plan.safety.willWrite)}`,
206
- ...(plan === undefined ? [] : [`Confirmation: ${plan.confirmations.message}`]),
207
- ...renderCopyActions(model.copyActions),
208
- ];
209
- void options;
210
- return lines.join('\n');
211
- }
212
- function renderManagementScreen(model, state, options) {
213
- void options;
214
- const section = activeLabel(state.activeScreen);
215
- if (model === undefined)
216
- return [
217
- section,
218
- 'State: empty',
219
- 'No read model is available yet; press r to refresh.',
220
- copyOnly(`Inspect ${section}`, `npm run cli -- ${state.activeScreen} --help`),
221
- ].join('\n');
222
- const selectedIndex = state.selectedByScreen[state.activeScreen] ?? 0;
223
- const selected = model.rows[selectedIndex];
224
- const lines = [section, `State: ${model.status}`, model.summary];
225
- if (model.errorState !== undefined)
226
- lines.push(`Error: ${model.errorState.message}`, ...renderCopyActions(model.errorState.nextActions));
227
- else if (model.rows.length === 0)
228
- lines.push(model.emptyState?.message ?? 'No rows are available.', ...renderCopyActions(model.emptyState?.nextActions ?? model.copyActions));
229
- else {
230
- for (const [index, row] of model.rows.entries())
231
- lines.push(`${index === selectedIndex ? '›' : ' '} ${row.title} (${row.id})${row.status === undefined ? '' : ` [${row.status}]`} — ${row.summary}`);
232
- if (state.detailVisibleByScreen[state.activeScreen] === true && selected !== undefined)
233
- lines.push('Detail', ...selected.detail.map((line) => ` ${line}`), ...renderCopyActions(selected.copyActions).map((line) => ` ${line}`));
234
- else
235
- lines.push('Press Enter to show selected row detail. Enter never runs commands.');
236
- }
237
- return lines.join('\n');
238
- }
239
- function renderSettingsScreen(state) {
240
- return [
241
- 'Settings',
242
- `State: ${state.project === undefined ? 'ready-without-project' : 'ready'}`,
243
- `Workspace: ${state.context.workspaceRoot}`,
244
- `Database: ${state.context.databasePath ?? 'default'}`,
245
- 'Safety: provider config writes are external/copy-only and require explicit confirmation.',
246
- ].join('\n');
247
- }
248
- function renderCopyActions(actions) {
249
- return actions.map((action) => copyOnly(action.label, action.command));
250
- }
251
- function checklist(label, status, detail) {
252
- return `${status === 'ready' ? '[x]' : '[ ]'} ${label} — ${status}${detail === undefined ? '' : ` — ${detail}`}`;
253
- }
254
- function activeLabel(id) {
255
- return screenLabels.find((screen) => screen.id === id)?.label ?? id;
256
- }
@@ -1,73 +0,0 @@
1
- import { sddPhases } from '../sdd/schema.js';
2
- export function sanitizeDashboardError(error) {
3
- const message = error instanceof Error ? error.message : typeof error === 'string' ? error : 'Unavailable';
4
- return message.replace(/\s+/g, ' ').trim().slice(0, 180) || 'Unavailable';
5
- }
6
- export function isSetupReady(setup) {
7
- return setup?.store.status === 'ready' && setup.verification.status === 'ready' && setup.agents.deferred !== true && setup.defaults.status === 'ready';
8
- }
9
- export function classifySetup(setup, error) {
10
- if (error !== undefined)
11
- return 'error';
12
- if (setup === undefined)
13
- return 'loading';
14
- return isSetupReady(setup) ? 'ready' : 'blocked';
15
- }
16
- export function classifySetupForProject(project, setup, error) {
17
- if (project === undefined && error === undefined && setup === undefined)
18
- return 'ready-without-project';
19
- if (project === undefined && error === undefined && setup !== undefined)
20
- return setup.store.status === 'ready' ? 'ready-without-project' : 'blocked';
21
- return classifySetup(setup, error);
22
- }
23
- export function classifySdd(sdd, change, error) {
24
- if (error !== undefined)
25
- return sdd === undefined ? 'error' : 'blocked';
26
- if (change === undefined)
27
- return 'empty';
28
- if (sdd === undefined || sdd.status.phases.every((phase) => !phase.present))
29
- return 'empty';
30
- if (sdd.readiness !== undefined && !sdd.readiness.ready)
31
- return 'blocked';
32
- return 'ready';
33
- }
34
- export function classifyProjectScoped(project, status, error) {
35
- if (error !== undefined)
36
- return 'error';
37
- if (project === undefined)
38
- return 'project-required';
39
- return status ?? 'empty';
40
- }
41
- export function createNoProjectDashboardSections() {
42
- return {
43
- installation: 'ready-without-project',
44
- status: 'ready-without-project',
45
- agents: 'project-required',
46
- skills: 'project-required',
47
- memory: 'project-required',
48
- sdd: 'project-required',
49
- runs: 'project-required',
50
- approvals: 'project-required',
51
- permissions: 'project-required',
52
- settings: 'ready-without-project',
53
- };
54
- }
55
- export function classifyAgentsSkills(agentsSkills, error) {
56
- if (error !== undefined)
57
- return agentsSkills === undefined ? 'error' : agentsSkills.status;
58
- return agentsSkills?.status ?? 'empty';
59
- }
60
- export function classifyDoctor(doctor, error) {
61
- if (error !== undefined)
62
- return doctor === undefined ? 'error' : doctor.status;
63
- return doctor?.status ?? 'empty';
64
- }
65
- export function canonicalSddPhasePresence(sdd) {
66
- return sddPhases.map((phase) => {
67
- const status = sdd?.status.phases.find((item) => item.phase === phase);
68
- return { phase, present: status?.present ?? false, topicKey: status?.topicKey ?? `sdd/${sdd?.change ?? '<change>'}/${phase}` };
69
- });
70
- }
71
- export function copyOnly(label, command) {
72
- return `${label} (copy-only, Run outside TUI): ${command}`;
73
- }
@@ -1,314 +0,0 @@
1
- import { classifySetup, } from './dashboard-tui-read-model.js';
2
- import { buildSetupWizardReadModel } from './setup-wizard-read-model.js';
3
- import { createSetupWizardState, setupWizardKeyFromInput } from './setup-wizard-state.js';
4
- export const dashboardScreens = [
5
- { id: 'installation', key: '1', label: 'Installation' },
6
- { id: 'status', key: '2', label: 'Status' },
7
- { id: 'agents', key: '3', label: 'Agents' },
8
- { id: 'skills', key: '4', label: 'Skills' },
9
- { id: 'memory', key: '5', label: 'Memory' },
10
- { id: 'sdd', key: '6', label: 'SDD' },
11
- { id: 'runs', key: '7', label: 'Runs' },
12
- { id: 'approvals', key: '8', label: 'Approvals' },
13
- { id: 'permissions', key: '9', label: 'Permissions' },
14
- { id: 'settings', key: '0', label: 'Settings' },
15
- ];
16
- export function createDashboardState(input) {
17
- void input.setupOnboardingDismissed;
18
- const sectionErrors = input.sectionErrors ?? {};
19
- const activeScreen = input.activeScreen ?? initialDashboardScreen(input.setup, sectionErrors.setup);
20
- const focusedScreen = input.focusedScreen ?? activeScreen;
21
- const project = input.project ?? input.context?.project;
22
- const change = input.change ?? input.context?.change;
23
- const databasePath = input.databasePath ?? input.context?.databasePath;
24
- const context = {
25
- workspaceRoot: input.workspaceRoot ?? input.context?.workspaceRoot ?? process.cwd(),
26
- ...(project === undefined ? {} : { project }),
27
- ...(change === undefined ? {} : { change }),
28
- ...(databasePath === undefined ? {} : { databasePath }),
29
- };
30
- const state = {
31
- context,
32
- ...(project === undefined ? {} : { project }),
33
- activeScreen,
34
- focusedScreen,
35
- viewMode: input.viewMode ?? 'menu',
36
- runs: input.runs,
37
- selectedIndex: clampSelection(input.selectedIndex ?? 0, input.runs),
38
- selectedByScreen: input.selectedByScreen ?? {},
39
- detailVisibleByScreen: input.detailVisibleByScreen ?? {},
40
- shouldExit: input.shouldExit ?? false,
41
- helpVisible: false,
42
- setupWizard: input.setupWizard ??
43
- createSetupWizardState({ projectMode: project === undefined ? 'none' : 'current', ...(project === undefined ? {} : { selectedProject: project }) }),
44
- sectionErrors,
45
- };
46
- if (change !== undefined)
47
- state.change = change;
48
- if (input.sdd !== undefined)
49
- state.sdd = input.sdd;
50
- if (input.setup !== undefined)
51
- state.setup = input.setup;
52
- if (input.setupWizardReadModel !== undefined)
53
- state.setupWizardReadModel = input.setupWizardReadModel;
54
- else if (input.setup !== undefined)
55
- state.setupWizardReadModel = buildSetupWizardReadModel(input.setup);
56
- if (input.profiles !== undefined)
57
- state.profiles = input.profiles;
58
- if (input.agentsSkills !== undefined)
59
- state.agentsSkills = input.agentsSkills;
60
- if (input.workflows !== undefined)
61
- state.workflows = input.workflows;
62
- if (input.operationalRuns !== undefined)
63
- state.operationalRuns = input.operationalRuns;
64
- if (input.approvals !== undefined)
65
- state.approvals = input.approvals;
66
- if (input.doctor !== undefined)
67
- state.doctor = input.doctor;
68
- if (input.installation !== undefined)
69
- state.installation = input.installation;
70
- if (input.management !== undefined)
71
- state.management = input.management;
72
- if (input.runInsights !== undefined)
73
- state.runInsights = input.runInsights;
74
- if (input.actionPreview !== undefined)
75
- state.actionPreview = input.actionPreview;
76
- if (input.navigationNotice !== undefined)
77
- state.navigationNotice = input.navigationNotice;
78
- if (input.error !== undefined)
79
- state.error = input.error;
80
- return state;
81
- }
82
- export function initialDashboardScreen(setup, setupError) {
83
- return classifySetup(setup, setupError) === 'ready' || setupError !== undefined ? 'status' : 'installation';
84
- }
85
- export function reduceDashboardKey(state, key) {
86
- const screen = screenForKey(key);
87
- if (screen !== undefined) {
88
- const { actionPreview: _actionPreview, navigationNotice: _navigationNotice, ...rest } = state;
89
- return { ...rest, viewMode: 'screen', activeScreen: screen, focusedScreen: screen };
90
- }
91
- if (key === 'menu-back')
92
- return state.viewMode === 'screen' ? backFromScreen(state) : state;
93
- if (key === 'menu-left')
94
- return moveMenuFocus(clearActionPreview(state), -1);
95
- if (key === 'menu-right')
96
- return moveMenuFocus(clearActionPreview(state), 1);
97
- if (key === 'menu-open') {
98
- if (state.viewMode === 'screen')
99
- return showDetail(state);
100
- const { actionPreview: _actionPreview, navigationNotice: _navigationNotice, ...rest } = state;
101
- return { ...rest, viewMode: 'screen', activeScreen: state.focusedScreen };
102
- }
103
- if (isSetupKey(key))
104
- return state;
105
- if (key === 'up')
106
- return state.viewMode === 'menu' ? moveMenuFocus(state, -1) : navigateScreenRows(state, -1);
107
- if (key === 'down')
108
- return state.viewMode === 'menu' ? moveMenuFocus(state, 1) : navigateScreenRows(state, 1);
109
- if (key === 'action-preview')
110
- return state.viewMode === 'screen' ? showActionPreview(state) : state;
111
- if (key === 'help')
112
- return { ...state, helpVisible: !state.helpVisible };
113
- if (key === 'refresh')
114
- return clearActionPreview(state);
115
- if (key === 'quit')
116
- return { ...state, shouldExit: true };
117
- return state;
118
- }
119
- export function dashboardKeyFromInput(input) {
120
- if (input === '1')
121
- return 'screen-installation';
122
- if (input === '2')
123
- return 'screen-status';
124
- if (input === '3')
125
- return 'screen-agents';
126
- if (input === '4')
127
- return 'screen-skills';
128
- if (input === '5')
129
- return 'screen-memory';
130
- if (input === '6')
131
- return 'screen-sdd';
132
- if (input === '7')
133
- return 'screen-runs';
134
- if (input === '8')
135
- return 'screen-approvals';
136
- if (input === '9')
137
- return 'screen-permissions';
138
- if (input === '0')
139
- return 'screen-settings';
140
- if (input === '' || input === 'h')
141
- return 'menu-left';
142
- if (input === '' || input === 'l')
143
- return 'menu-right';
144
- if (input === '\r' || input === '\n')
145
- return 'menu-open';
146
- if (input === 'a')
147
- return 'action-preview';
148
- if (input === 'b' || input === '\u001B' || input === '\u007F')
149
- return 'menu-back';
150
- if (input === 'd')
151
- return 'setup-details';
152
- if (input === '?')
153
- return 'help';
154
- const setupKey = setupWizardKeyFromInput(input);
155
- if (setupKey === 'next')
156
- return 'setup-next';
157
- if (setupKey === 'back')
158
- return 'setup-back';
159
- if (setupKey === 'toggle')
160
- return 'setup-toggle';
161
- if (setupKey === 'details')
162
- return 'setup-details';
163
- if (setupKey === 'escape')
164
- return 'setup-escape';
165
- if (input === 'k' || input === '\u001B[A')
166
- return 'up';
167
- if (input === 'j' || input === '\u001B[B')
168
- return 'down';
169
- if (input === 'r')
170
- return 'refresh';
171
- if (input === 'q' || input === '\u0003')
172
- return 'quit';
173
- return undefined;
174
- }
175
- export function refreshDashboardState(previous, loaded) {
176
- return createDashboardState({
177
- ...(previous.project === undefined ? {} : { project: previous.project }),
178
- context: previous.context,
179
- ...(previous.change === undefined ? {} : { change: previous.change }),
180
- ...loaded,
181
- activeScreen: previous.activeScreen,
182
- focusedScreen: previous.focusedScreen,
183
- viewMode: previous.viewMode,
184
- selectedIndex: previous.selectedIndex,
185
- selectedByScreen: previous.selectedByScreen,
186
- detailVisibleByScreen: previous.detailVisibleByScreen,
187
- shouldExit: previous.shouldExit,
188
- setupWizard: previous.setupWizard,
189
- });
190
- }
191
- function moveMenuFocus(state, delta) {
192
- const currentIndex = Math.max(0, dashboardScreens.findIndex((screen) => screen.id === state.focusedScreen));
193
- const nextIndex = (currentIndex + delta + dashboardScreens.length) % dashboardScreens.length;
194
- const focusedScreen = dashboardScreens[nextIndex]?.id ?? state.focusedScreen;
195
- return focusedScreen === state.focusedScreen ? state : { ...state, focusedScreen };
196
- }
197
- function screenForKey(key) {
198
- if (key === 'screen-installation')
199
- return 'installation';
200
- if (key === 'screen-setup')
201
- return 'installation';
202
- if (key === 'screen-status')
203
- return 'status';
204
- if (key === 'screen-workflows')
205
- return 'status';
206
- if (key === 'screen-runs')
207
- return 'runs';
208
- if (key === 'screen-approvals')
209
- return 'approvals';
210
- if (key === 'screen-agents')
211
- return 'agents';
212
- if (key === 'screen-skills')
213
- return 'skills';
214
- if (key === 'screen-memory')
215
- return 'memory';
216
- if (key === 'screen-sdd')
217
- return 'sdd';
218
- if (key === 'screen-permissions')
219
- return 'permissions';
220
- if (key === 'screen-doctor')
221
- return 'status';
222
- if (key === 'screen-settings')
223
- return 'settings';
224
- return undefined;
225
- }
226
- function backFromScreen(state) {
227
- if (state.actionPreview !== undefined)
228
- return clearActionPreview(state);
229
- if (state.detailVisibleByScreen[state.activeScreen] === true)
230
- return { ...clearActionPreview(state), detailVisibleByScreen: { ...state.detailVisibleByScreen, [state.activeScreen]: false } };
231
- return { ...clearActionPreview(state), viewMode: 'menu' };
232
- }
233
- function showDetail(state) {
234
- return { ...clearActionPreview(state), detailVisibleByScreen: { ...state.detailVisibleByScreen, [state.activeScreen]: true } };
235
- }
236
- function navigateScreenRows(state, delta) {
237
- const rows = isManagementScreen(state.activeScreen) ? (state.management?.[state.activeScreen]?.rows.length ?? 0) : 0;
238
- if (rows === 0 && state.activeScreen === 'runs')
239
- return navigateRuns(clearActionPreview(state), delta);
240
- if (rows === 0)
241
- return state;
242
- const current = state.selectedByScreen[state.activeScreen] ?? 0;
243
- const next = (current + delta + rows) % rows;
244
- return { ...clearActionPreview(state), selectedByScreen: { ...state.selectedByScreen, [state.activeScreen]: next } };
245
- }
246
- function showActionPreview(state) {
247
- if (!isManagementScreen(state.activeScreen))
248
- return { ...state, actionPreview: { available: false, screen: state.activeScreen, message: 'No action preview is available for this screen.' } };
249
- const model = state.management?.[state.activeScreen];
250
- const row = model?.rows[state.selectedByScreen[state.activeScreen] ?? 0];
251
- if (row === undefined)
252
- return { ...state, actionPreview: { available: false, screen: state.activeScreen, message: 'No selected row has an action to preview.' } };
253
- const action = row.copyActions[0];
254
- if (action === undefined)
255
- return { ...state, actionPreview: { available: false, screen: state.activeScreen, message: 'The selected row has no copy-only action.' } };
256
- return { ...state, actionPreview: { available: true, descriptor: actionDescriptor(state.activeScreen, row.id, row.title, action) } };
257
- }
258
- function actionDescriptor(screen, rowId, rowTitle, action) {
259
- return {
260
- id: `${screen}:${rowId}:copy-action:0`,
261
- label: action.label,
262
- source: { screen, rowId, rowTitle },
263
- mode: 'copy-command',
264
- safety: action.safety,
265
- command: action.command,
266
- guidance: 'Copy and run outside the TUI only after manual review. This dashboard does not execute the action.',
267
- copyOnly: true,
268
- executesInTui: false,
269
- };
270
- }
271
- function clearActionPreview(state) {
272
- if (state.actionPreview === undefined)
273
- return state;
274
- const { actionPreview: _actionPreview, ...rest } = state;
275
- return rest;
276
- }
277
- function isManagementScreen(screen) {
278
- return (screen === 'agents' ||
279
- screen === 'skills' ||
280
- screen === 'memory' ||
281
- screen === 'sdd' ||
282
- screen === 'runs' ||
283
- screen === 'approvals' ||
284
- screen === 'permissions' ||
285
- screen === 'settings');
286
- }
287
- function isSetupKey(key) {
288
- return key.startsWith('setup-');
289
- }
290
- function _toSetupWizardKey(key) {
291
- if (key === 'setup-next')
292
- return 'next';
293
- if (key === 'setup-back')
294
- return 'back';
295
- if (key === 'setup-toggle')
296
- return 'toggle';
297
- if (key === 'setup-details')
298
- return 'details';
299
- return 'escape';
300
- }
301
- function clampSelection(selectedIndex, runs) {
302
- if (runs.length === 0)
303
- return 0;
304
- return Math.max(0, Math.min(selectedIndex, runs.length - 1));
305
- }
306
- function navigateRuns(state, delta) {
307
- if (state.runs.length === 0)
308
- return { ...state, selectedIndex: 0, navigationNotice: 'No runs are available to select yet.' };
309
- if (state.runs.length === 1)
310
- return { ...state, selectedIndex: 0, navigationNotice: 'Only one run is available; selection stayed on the only run.' };
311
- const selectedIndex = clampSelection(state.selectedIndex + delta, state.runs);
312
- const { navigationNotice: _navigationNotice, ...rest } = state;
313
- return { ...rest, selectedIndex };
314
- }
@@ -1,34 +0,0 @@
1
- import { renderDashboardFrame } from './dashboard-screen-renderers.js';
2
- import { sanitizeDashboardError } from './dashboard-tui-read-model.js';
3
- import { createDashboardState as createState, dashboardKeyFromInput, reduceDashboardKey, refreshDashboardState, } from './dashboard-tui-state.js';
4
- export function createDashboardState(input) {
5
- return createState(input);
6
- }
7
- export async function loadInitialDashboardState(project, loader) {
8
- const loaded = await loader();
9
- if (!loaded.ok)
10
- return createDashboardState({
11
- ...(project === undefined ? {} : { project }),
12
- runs: [],
13
- activeScreen: project === undefined ? 'installation' : 'status',
14
- error: sanitizeDashboardError(loaded.error.message),
15
- });
16
- return createDashboardState({ ...(project === undefined ? {} : { project }), ...loaded.value });
17
- }
18
- export { dashboardKeyFromInput, reduceDashboardKey };
19
- export async function refreshDashboard(state, loader) {
20
- const loaded = await loader();
21
- if (!loaded.ok)
22
- return { ...state, error: sanitizeDashboardError(loaded.error.message) };
23
- return refreshDashboardState(state, loaded.value);
24
- }
25
- export function renderDashboard(state, options = {}) {
26
- return renderDashboardFrame(state, options);
27
- }
28
- export function resolveDashboardRenderStyle(input) {
29
- if (input.isTTY !== true)
30
- return 'plain';
31
- if (input.env?.NO_COLOR !== undefined || input.env?.CI !== undefined)
32
- return 'plain';
33
- return 'ansi';
34
- }