vgxness 1.14.1 → 1.16.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/dist/agents/agent-selector-resolver.js +23 -0
- package/dist/agents/canonical-agent-manifest.js +27 -25
- package/dist/cli/cli-help.js +10 -4
- package/dist/cli/commands/agent-skill-dispatcher.js +26 -1
- package/dist/cli/commands/index.js +1 -1
- package/dist/cli/commands/interactive-entrypoint-dispatcher.js +2 -2
- package/dist/cli/commands/setup-dispatcher.js +63 -0
- package/dist/cli/dispatcher.js +16 -4
- package/dist/cli/home-tui-app.js +387 -0
- package/dist/cli/home-tui-controller.js +296 -0
- package/dist/cli/sdd-renderer.js +7 -0
- package/dist/cli/setup-plan-renderer.js +60 -36
- package/dist/cli/setup-tui-app.js +130 -0
- package/dist/cli/setup-tui-controller.js +89 -0
- package/dist/cli/tui/ink/components.js +38 -0
- package/dist/cli/tui/ink/theme.js +36 -0
- package/dist/mcp/control-plane.js +7 -0
- package/dist/mcp/opencode-handoff-preview.js +41 -14
- package/dist/mcp/schema.js +25 -0
- package/dist/mcp/stdio-server.js +4 -2
- package/dist/mcp/validation.js +14 -0
- package/dist/providers/opencode/manager-payload.js +21 -12
- package/dist/runs/execution-planning.js +67 -0
- package/dist/runs/run-service.js +10 -3
- package/dist/sdd/sdd-continuation-plan.js +46 -0
- package/dist/setup/setup-plan.js +4 -2
- package/dist/skills/skill-export-service.js +34 -0
- package/dist/skills/skill-index-service.js +115 -0
- package/dist/skills/skill-payload.js +1 -0
- package/dist/skills/skill-resolver.js +17 -13
- package/dist/workspace-strategy/diagnostics.js +34 -0
- package/dist/workspace-strategy/index.js +3 -0
- package/dist/workspace-strategy/recommendation-policy.js +196 -0
- package/dist/workspace-strategy/schema.js +1 -0
- package/docs/architecture.md +13 -2
- package/docs/cli.md +24 -4
- package/docs/glossary.md +5 -1
- package/docs/mcp.md +10 -3
- package/docs/roadmap.md +2 -2
- package/package.json +4 -1
- package/seeds/skills/skill-seed-v1.json +4 -4
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box } from 'ink';
|
|
3
|
+
import { renderSddAdviceLines } from './sdd-renderer.js';
|
|
4
|
+
import { TuiCard, TuiFooter, TuiHeader, TuiSummaryBar, TuiTabs } from './tui/ink/components.js';
|
|
5
|
+
import { inkTuiTheme, normalizeInkTuiWidth } from './tui/ink/theme.js';
|
|
6
|
+
export const homeTuiTabs = ['setup', 'status', 'runs', 'sdd', 'skills', 'provider'];
|
|
7
|
+
const homeTuiTabItems = homeTuiTabs.map((tab) => ({ key: tab, label: labelForHomeTab(tab) }));
|
|
8
|
+
export function HomeTuiApp({ plan, width, selectedTab, view = 'overview', runsReadModel = { state: 'not-loaded' }, skillsReadModel = { state: 'not-loaded' }, sddInput = { change: '' }, sddReadModel = { state: 'not-loaded' } }) {
|
|
9
|
+
const cardWidth = normalizeInkTuiWidth(width);
|
|
10
|
+
if (view === 'status-focus')
|
|
11
|
+
return _jsx(FocusedStatusView, { plan: plan, width: cardWidth });
|
|
12
|
+
if (view === 'runs-focus')
|
|
13
|
+
return _jsx(FocusedRunsView, { plan: plan, width: cardWidth, runs: runsReadModel });
|
|
14
|
+
if (view === 'sdd-focus')
|
|
15
|
+
return _jsx(FocusedSddView, { plan: plan, width: cardWidth, sddInput: sddInput, sdd: sddReadModel });
|
|
16
|
+
if (view === 'skills-focus')
|
|
17
|
+
return _jsx(FocusedSkillsView, { plan: plan, width: cardWidth, skills: skillsReadModel });
|
|
18
|
+
if (view === 'provider-focus')
|
|
19
|
+
return _jsx(FocusedProviderView, { plan: plan, width: cardWidth });
|
|
20
|
+
return (_jsxs(Box, { flexDirection: "column", width: cardWidth, paddingX: 1, paddingY: 1, children: [_jsx(TuiHeader, { title: inkTuiTheme.copy.homeTitle, subtitle: inkTuiTheme.copy.homeSubtitle, project: plan.project }), _jsx(TuiTabs, { items: homeTuiTabItems, selectedKey: selectedTab }), _jsx(TuiSummaryBar, { items: summaryItems(plan) }), _jsx(Box, { marginTop: 1, children: _jsx(HomePanel, { plan: plan, selectedTab: selectedTab }) }), _jsx(TuiFooter, { text: "Keys: \u2191/\u2193 or j/k move \u00B7 Tab/Shift+Tab next/back \u00B7 Enter opens focused area \u00B7 q exits \u00B7 read-only" })] }));
|
|
21
|
+
}
|
|
22
|
+
function FocusedSkillsView({ plan, width, skills }) {
|
|
23
|
+
const badge = skills.state === 'ready' ? 'read-only' : skills.state === 'unavailable' ? 'unavailable' : 'loading';
|
|
24
|
+
const tone = skills.state === 'unavailable' ? 'warning' : 'info';
|
|
25
|
+
return (_jsxs(Box, { flexDirection: "column", width: width, paddingX: 1, paddingY: 1, children: [_jsx(TuiHeader, { title: "VGXNESS Skills", subtitle: "Registry guidance cockpit \u00B7 read-only", project: plan.project }), _jsx(TuiSummaryBar, { items: summaryItems(plan) }), _jsx(Box, { marginTop: 1, children: _jsx(TuiCard, { title: "Skills", badge: badge, tone: tone, lines: focusedSkillsLines(plan, skills) }) }), _jsx(TuiFooter, { text: "Keys: Esc/b back to Home \u00B7 q exits \u00B7 read-only \u00B7 no provider skill writes" })] }));
|
|
26
|
+
}
|
|
27
|
+
function FocusedRunsView({ plan, width, runs }) {
|
|
28
|
+
const badge = runs.state === 'ready' ? 'read-only' : runs.state === 'unavailable' ? 'unavailable' : 'loading';
|
|
29
|
+
const tone = runs.state === 'unavailable' ? 'warning' : 'info';
|
|
30
|
+
const title = runs.state === 'ready' && runs.selected !== undefined ? 'Run detail' : 'Runs';
|
|
31
|
+
return (_jsxs(Box, { flexDirection: "column", width: width, paddingX: 1, paddingY: 1, children: [_jsx(TuiHeader, { title: "VGXNESS Runs", subtitle: "Run recovery cockpit \u00B7 read-only", project: plan.project }), _jsx(TuiSummaryBar, { items: summaryItems(plan) }), _jsx(Box, { marginTop: 1, children: _jsx(TuiCard, { title: title, badge: badge, tone: tone, lines: focusedRunsLines(plan, runs) }) }), _jsx(TuiFooter, { text: "Keys: Enter opens first run detail \u00B7 Esc/b back to Home \u00B7 q exits \u00B7 read-only \u00B7 no writes" })] }));
|
|
32
|
+
}
|
|
33
|
+
function FocusedSddView({ plan, width, sddInput, sdd }) {
|
|
34
|
+
const badge = sddInput.change.length === 0 ? 'change required' : sdd.state === 'ready' ? sdd.next.status : sdd.state === 'unavailable' ? 'unavailable' : 'change selected';
|
|
35
|
+
const tone = sdd.state === 'unavailable' ? 'warning' : 'info';
|
|
36
|
+
return (_jsxs(Box, { flexDirection: "column", width: width, paddingX: 1, paddingY: 1, children: [_jsx(TuiHeader, { title: "VGXNESS SDD", subtitle: "Spec-driven development cockpit \u00B7 change required \u00B7 read-only", project: plan.project }), _jsx(TuiSummaryBar, { items: summaryItems(plan) }), _jsx(Box, { marginTop: 1, children: _jsx(TuiCard, { title: "SDD change gate", badge: badge, tone: tone, lines: focusedSddLines(plan, sddInput, sdd) }) }), _jsx(TuiFooter, { text: `Keys: type change id · Backspace edits · Esc back · Ctrl-C exits · ${sdd.state === 'ready' ? 'SDD state read-only' : 'local SDD state not opened'}` })] }));
|
|
37
|
+
}
|
|
38
|
+
function FocusedStatusView({ plan, width }) {
|
|
39
|
+
return (_jsxs(Box, { flexDirection: "column", width: width, paddingX: 1, paddingY: 1, children: [_jsx(TuiHeader, { title: "VGXNESS Status", subtitle: "Focused setup readiness \u00B7 read-only", project: plan.project }), _jsx(TuiSummaryBar, { items: summaryItems(plan) }), _jsx(Box, { marginTop: 1, children: _jsx(TuiCard, { title: "Current setup status", badge: plan.status === 'ready' ? 'ready' : statusTitle(plan.status), tone: plan.status === 'ready' ? 'success' : plan.status === 'conflict' ? 'danger' : 'warning', lines: focusedStatusLines(plan) }) }), _jsx(TuiFooter, { text: "Keys: Esc/b back to Home \u00B7 q exits \u00B7 read-only \u00B7 no provider config writes" })] }));
|
|
40
|
+
}
|
|
41
|
+
function FocusedProviderView({ plan, width }) {
|
|
42
|
+
return (_jsxs(Box, { flexDirection: "column", width: width, paddingX: 1, paddingY: 1, children: [_jsx(TuiHeader, { title: "VGXNESS Provider", subtitle: "Provider setup plan \u00B7 read-only", project: plan.project }), _jsx(TuiSummaryBar, { items: summaryItems(plan) }), _jsx(Box, { marginTop: 1, children: _jsx(TuiCard, { title: "Provider plan", badge: providerTitle(plan.provider), tone: plan.provider === 'opencode' ? 'info' : 'warning', lines: focusedProviderLines(plan) }) }), _jsx(TuiFooter, { text: "Keys: Esc/b back to Home \u00B7 q exits \u00B7 read-only \u00B7 providers were not executed" })] }));
|
|
43
|
+
}
|
|
44
|
+
function HomePanel({ plan, selectedTab }) {
|
|
45
|
+
if (selectedTab === 'setup')
|
|
46
|
+
return _jsx(TuiCard, { title: "Setup", badge: "preview-only", tone: "warning", lines: setupLines(plan) });
|
|
47
|
+
if (selectedTab === 'status')
|
|
48
|
+
return _jsx(TuiCard, { title: "Status", badge: plan.status === 'ready' ? 'ready' : 'blocked', tone: plan.status === 'ready' ? 'success' : 'danger', lines: statusLines(plan) });
|
|
49
|
+
if (selectedTab === 'runs')
|
|
50
|
+
return _jsx(TuiCard, { title: "Runs", badge: "coming-soon", tone: "neutral", lines: runsLines() });
|
|
51
|
+
if (selectedTab === 'sdd')
|
|
52
|
+
return _jsx(TuiCard, { title: "SDD", badge: "manual", tone: "info", lines: sddLines() });
|
|
53
|
+
if (selectedTab === 'skills')
|
|
54
|
+
return _jsx(TuiCard, { title: "Skills", badge: "read-only", tone: "info", lines: skillsLines() });
|
|
55
|
+
return _jsx(TuiCard, { title: "Provider", badge: plan.opencode === undefined ? 'manual' : plan.status === 'ready' ? 'ready' : 'blocked', tone: plan.status === 'ready' ? 'success' : 'warning', lines: providerLines(plan) });
|
|
56
|
+
}
|
|
57
|
+
function setupLines(plan) {
|
|
58
|
+
return [
|
|
59
|
+
'Setup preview',
|
|
60
|
+
'',
|
|
61
|
+
field('Project', plan.project),
|
|
62
|
+
field('Status', plan.status),
|
|
63
|
+
field('Provider', plan.provider),
|
|
64
|
+
field('Database', `${plan.db.mode} (${plan.db.source})`),
|
|
65
|
+
field('Safety', 'read-only preview'),
|
|
66
|
+
'',
|
|
67
|
+
'Press Enter to open the focused setup flow.',
|
|
68
|
+
'CLI fallback: `vgxness init`.',
|
|
69
|
+
'Use `vgxness setup plan` for a copyable text preview.',
|
|
70
|
+
];
|
|
71
|
+
}
|
|
72
|
+
function statusLines(plan) {
|
|
73
|
+
return [
|
|
74
|
+
field('Setup', plan.status),
|
|
75
|
+
field('Provider', plan.provider),
|
|
76
|
+
field('Actions', plan.actions.length === 0 ? 'none planned' : `${plan.actions.length} preview action(s)`),
|
|
77
|
+
field('Conflicts', plan.conflicts.length === 0 ? 'none' : `${plan.conflicts.length} item(s)`),
|
|
78
|
+
'',
|
|
79
|
+
'This home view is intentionally read-only and does not open provider processes.',
|
|
80
|
+
nextSetupStep(plan),
|
|
81
|
+
];
|
|
82
|
+
}
|
|
83
|
+
function focusedStatusLines(plan) {
|
|
84
|
+
return [
|
|
85
|
+
field('Project', plan.project),
|
|
86
|
+
field('Setup', plan.status),
|
|
87
|
+
field('Provider', providerTitle(plan.provider)),
|
|
88
|
+
field('Database', `${plan.db.mode} (${plan.db.source})`),
|
|
89
|
+
field('Actions', plan.actions.length === 0 ? 'none planned' : `${plan.actions.length} preview action(s)`),
|
|
90
|
+
field('Conflicts', plan.conflicts.length === 0 ? 'none' : `${plan.conflicts.length} item(s)`),
|
|
91
|
+
field('Backups', plan.backupsPlanned.length === 0 ? 'none planned' : `${plan.backupsPlanned.length} planned for future apply`),
|
|
92
|
+
field('Provider writes', String(plan.safety.writesProviderConfig)),
|
|
93
|
+
'',
|
|
94
|
+
nextSetupStep(plan),
|
|
95
|
+
...(plan.conflicts.length === 0 ? [] : ['', 'Blocking conflicts:', ...plan.conflicts.map((conflict) => `- ${conflict.message}`)]),
|
|
96
|
+
'',
|
|
97
|
+
'This focused panel is still a setup-readiness view; it does not open local run/SDD state.',
|
|
98
|
+
];
|
|
99
|
+
}
|
|
100
|
+
function focusedRunsLines(plan, runs) {
|
|
101
|
+
if (runs.state === 'unavailable') {
|
|
102
|
+
return [
|
|
103
|
+
field('Project', plan.project),
|
|
104
|
+
field('Store', 'unavailable'),
|
|
105
|
+
field('Database', runs.databasePath),
|
|
106
|
+
field('Safety', 'read-only'),
|
|
107
|
+
'',
|
|
108
|
+
runs.message,
|
|
109
|
+
'',
|
|
110
|
+
'Use `vgxness resume --project <name>` to inspect interrupted runs from a Bun-backed runtime.',
|
|
111
|
+
'Use `vgxness runs list --project <name> --status needs-human` for actionable blockers.',
|
|
112
|
+
];
|
|
113
|
+
}
|
|
114
|
+
if (runs.state === 'ready') {
|
|
115
|
+
if (runs.selected !== undefined)
|
|
116
|
+
return focusedRunDetailLines(plan, runs.databasePath, runs.selected);
|
|
117
|
+
return [
|
|
118
|
+
field('Project', plan.project),
|
|
119
|
+
field('Store', 'opened read-only'),
|
|
120
|
+
field('Database', runs.databasePath),
|
|
121
|
+
field('Recent runs', String(runs.recent.length)),
|
|
122
|
+
field('Interrupted', String(runs.interrupted.length)),
|
|
123
|
+
'',
|
|
124
|
+
...runSummarySection('Interrupted runs', runs.interrupted),
|
|
125
|
+
'',
|
|
126
|
+
...runSummarySection('Recent runs', runs.recent),
|
|
127
|
+
'',
|
|
128
|
+
runs.details.length === 0 ? 'No run is available for detail inspection.' : 'Press Enter to inspect the first interrupted/recent run here.',
|
|
129
|
+
'CLI fallback: `vgxness runs get --id <run-id>`.',
|
|
130
|
+
];
|
|
131
|
+
}
|
|
132
|
+
return [
|
|
133
|
+
field('Project', plan.project),
|
|
134
|
+
field('Loaded state', 'not opened'),
|
|
135
|
+
field('Safety', 'read-only placeholder'),
|
|
136
|
+
'',
|
|
137
|
+
'This panel does not open the local SQLite store yet.',
|
|
138
|
+
'Use `vgxness resume --project <name>` to inspect interrupted runs.',
|
|
139
|
+
'Use `vgxness runs list --project <name> --status needs-human` for actionable blockers.',
|
|
140
|
+
'',
|
|
141
|
+
'Next implementation slice can wire this panel to the run read model.',
|
|
142
|
+
];
|
|
143
|
+
}
|
|
144
|
+
function focusedRunDetailLines(plan, databasePath, run) {
|
|
145
|
+
return [
|
|
146
|
+
field('Project', plan.project),
|
|
147
|
+
field('Store', 'opened read-only'),
|
|
148
|
+
field('Database', databasePath),
|
|
149
|
+
field('Run', shortRunId(run.id)),
|
|
150
|
+
field('Status', run.status),
|
|
151
|
+
field('Workflow/phase', `${run.workflow}/${run.phase}`),
|
|
152
|
+
field('Events', String(run.events)),
|
|
153
|
+
field('Checkpoints', String(run.checkpoints)),
|
|
154
|
+
field('Approvals', String(run.approvals)),
|
|
155
|
+
field('Attempts', String(run.attempts)),
|
|
156
|
+
...(run.latestCheckpointLabel === undefined ? [] : [field('Latest checkpoint', run.latestCheckpointLabel)]),
|
|
157
|
+
...(run.latestEventTitle === undefined ? [] : [field('Latest event', run.latestEventTitle)]),
|
|
158
|
+
...(run.outcome === undefined ? [] : [field('Outcome', run.outcome)]),
|
|
159
|
+
...(run.outcomeReason === undefined ? [] : [field('Outcome reason', run.outcomeReason)]),
|
|
160
|
+
...(run.userIntent === undefined ? [] : ['', `Intent: ${run.userIntent}`]),
|
|
161
|
+
'',
|
|
162
|
+
`Inspect JSON: vgxness runs get --id ${run.id}`,
|
|
163
|
+
'No retry, approval, or execution is performed from this TUI detail.',
|
|
164
|
+
];
|
|
165
|
+
}
|
|
166
|
+
function runSummarySection(title, runs) {
|
|
167
|
+
if (runs.length === 0)
|
|
168
|
+
return [title, '- none'];
|
|
169
|
+
return [title, ...runs.map((run) => `- ${shortRunId(run.id)} ${run.status} ${run.workflow}/${run.phase}${run.userIntent === undefined ? '' : ` — ${run.userIntent}`}`)];
|
|
170
|
+
}
|
|
171
|
+
function shortRunId(id) {
|
|
172
|
+
return id.length <= 8 ? id : id.slice(0, 8);
|
|
173
|
+
}
|
|
174
|
+
function focusedSddLines(plan, input, sdd) {
|
|
175
|
+
const change = input.change.length === 0 ? '<id>' : input.change;
|
|
176
|
+
if (input.change.length > 0 && sdd.state === 'unavailable') {
|
|
177
|
+
return [
|
|
178
|
+
field('Project', plan.project),
|
|
179
|
+
field('Change', input.change),
|
|
180
|
+
field('Input', `${input.change}_`),
|
|
181
|
+
field('Store', 'unavailable'),
|
|
182
|
+
field('Database', sdd.databasePath),
|
|
183
|
+
field('Acceptance', 'human-only'),
|
|
184
|
+
field('Safety', 'read-only'),
|
|
185
|
+
'',
|
|
186
|
+
sdd.message,
|
|
187
|
+
'',
|
|
188
|
+
`Continue: vgxness sdd continue --project ${plan.project} --change ${change}`,
|
|
189
|
+
`Status: vgxness sdd status --project ${plan.project} --change ${change}`,
|
|
190
|
+
'Human acceptance remains explicit; artifact presence is not acceptance.',
|
|
191
|
+
];
|
|
192
|
+
}
|
|
193
|
+
if (input.change.length > 0 && sdd.state === 'ready') {
|
|
194
|
+
return [
|
|
195
|
+
field('Project', plan.project),
|
|
196
|
+
field('Change', input.change),
|
|
197
|
+
field('Input', `${input.change}_`),
|
|
198
|
+
field('Store', 'opened read-only'),
|
|
199
|
+
field('Database', sdd.databasePath),
|
|
200
|
+
field('Status', sdd.next.status),
|
|
201
|
+
field('Next phase', sdd.next.nextPhase ?? 'none'),
|
|
202
|
+
field('Accepted', `${sdd.cockpit.acceptedCount}/${sdd.cockpit.phases.length}`),
|
|
203
|
+
field('Artifacts', String(sdd.cockpit.artifacts.length)),
|
|
204
|
+
field('Blockers', String(sdd.cockpit.aggregateBlockers.length)),
|
|
205
|
+
field('Recommended', sdd.cockpit.recommendedAction),
|
|
206
|
+
field('Acceptance', sdd.cockpit.gates?.requiresHumanAcceptance === true ? 'human review needed' : 'human-only'),
|
|
207
|
+
field('Safety', 'read-only'),
|
|
208
|
+
'',
|
|
209
|
+
sdd.next.reason,
|
|
210
|
+
'',
|
|
211
|
+
...sddBlockerLines(sdd.cockpit.aggregateBlockers),
|
|
212
|
+
...(sdd.continuation.advice.length === 0 ? [] : ['', ...renderSddAdviceLines(sdd.continuation.advice)]),
|
|
213
|
+
'',
|
|
214
|
+
`Inspect: ${sdd.continuation.inspectCommand}`,
|
|
215
|
+
`Continue: vgxness sdd continue --project ${plan.project} --change ${change}`,
|
|
216
|
+
'Human acceptance remains explicit; artifact presence is not acceptance.',
|
|
217
|
+
];
|
|
218
|
+
}
|
|
219
|
+
return [
|
|
220
|
+
field('Project', plan.project),
|
|
221
|
+
field('Change', input.change.length === 0 ? 'not selected' : input.change),
|
|
222
|
+
field('Input', `${input.change}_`),
|
|
223
|
+
field('Store', 'not opened'),
|
|
224
|
+
field('Acceptance', 'human-only'),
|
|
225
|
+
field('Safety', 'read-only gate'),
|
|
226
|
+
'',
|
|
227
|
+
'This panel intentionally does not open SQLite until a change id is selected.',
|
|
228
|
+
'Allowed input: letters, numbers, dot, underscore, dash; first character must be alphanumeric.',
|
|
229
|
+
'Daily SDD stays in OpenCode through conversation, VGXNESS MCP, hidden SDD subagents, and registry skills.',
|
|
230
|
+
'',
|
|
231
|
+
`Continue: vgxness sdd continue --project ${plan.project} --change ${change}`,
|
|
232
|
+
`Status: vgxness sdd status --project ${plan.project} --change ${change}`,
|
|
233
|
+
'Governance reminder: artifact presence is not acceptance; acceptance is explicit and human-only.',
|
|
234
|
+
'',
|
|
235
|
+
'Next implementation slice can ask for/select a change id before loading SDD state.',
|
|
236
|
+
];
|
|
237
|
+
}
|
|
238
|
+
function sddBlockerLines(blockers) {
|
|
239
|
+
if (blockers.length === 0)
|
|
240
|
+
return ['Blockers', '- none'];
|
|
241
|
+
return ['Blockers', ...blockers.slice(0, 4).map((blocker) => `- ${blocker.phase}: ${blocker.reason}`), ...(blockers.length > 4 ? [`- +${blockers.length - 4} more`] : [])];
|
|
242
|
+
}
|
|
243
|
+
function focusedSkillsLines(plan, skills) {
|
|
244
|
+
if (skills.state === 'unavailable') {
|
|
245
|
+
return [
|
|
246
|
+
field('Project', plan.project),
|
|
247
|
+
field('Scope', 'project'),
|
|
248
|
+
field('Store', 'unavailable'),
|
|
249
|
+
field('Database', skills.databasePath),
|
|
250
|
+
'',
|
|
251
|
+
skills.message,
|
|
252
|
+
'',
|
|
253
|
+
'Use `vgxness skills index --project <name> --scope project` from a Bun-backed runtime.',
|
|
254
|
+
'OpenCode native skill installation is not performed by this TUI.',
|
|
255
|
+
];
|
|
256
|
+
}
|
|
257
|
+
if (skills.state === 'ready') {
|
|
258
|
+
return [
|
|
259
|
+
field('Project', skills.index.project),
|
|
260
|
+
field('Scope', skills.index.scope),
|
|
261
|
+
field('Store', 'opened read-only'),
|
|
262
|
+
field('Skills', String(skills.index.totals.skills)),
|
|
263
|
+
field('Active', String(skills.index.totals.active)),
|
|
264
|
+
field('Preview-only', String(skills.index.totals.previewOnly)),
|
|
265
|
+
field('Provider writes', String(skills.index.safety.providerConfigWrites)),
|
|
266
|
+
'',
|
|
267
|
+
...skillSummarySection(skills.index.skills),
|
|
268
|
+
'',
|
|
269
|
+
'OpenCode native skill installation is not performed by this TUI.',
|
|
270
|
+
];
|
|
271
|
+
}
|
|
272
|
+
return [
|
|
273
|
+
field('Project', plan.project),
|
|
274
|
+
field('Scope', 'project'),
|
|
275
|
+
field('Provider', 'preview-only'),
|
|
276
|
+
'',
|
|
277
|
+
'Skill registry view is planned and remains read-only here.',
|
|
278
|
+
'Use `vgxness skills index --project <name> --scope project` for the current CLI view.',
|
|
279
|
+
'OpenCode native skill installation is not performed by this TUI.',
|
|
280
|
+
'',
|
|
281
|
+
'Next implementation slice can show registry diagnostics and attached skill summaries.',
|
|
282
|
+
];
|
|
283
|
+
}
|
|
284
|
+
function skillSummarySection(skills) {
|
|
285
|
+
if (skills.length === 0)
|
|
286
|
+
return ['Indexed skills', '- none'];
|
|
287
|
+
return ['Indexed skills', ...skills.slice(0, 8).map((skill) => `- ${skill.name} ${skill.status}${skill.activeVersion === null ? '' : ` @ ${skill.activeVersion}`}`), ...(skills.length > 8 ? [`- +${skills.length - 8} more`] : [])];
|
|
288
|
+
}
|
|
289
|
+
function runsLines() {
|
|
290
|
+
return ['Run dashboard is not loaded in this first Home TUI slice.', '', 'Use `vgxness status` or `vgxness resume --project <name>` for current CLI diagnostics.', 'Next slice can wire this panel to the run read model.'];
|
|
291
|
+
}
|
|
292
|
+
function sddLines() {
|
|
293
|
+
return [
|
|
294
|
+
'Change: not selected. Press Enter for the focused SDD gate.',
|
|
295
|
+
'',
|
|
296
|
+
'The Home TUI does not open local SDD state until a change id is selected.',
|
|
297
|
+
'Daily SDD should stay in OpenCode through conversation, VGXNESS MCP, and hidden SDD subagents.',
|
|
298
|
+
'',
|
|
299
|
+
'CLI fallback: `vgxness sdd continue --project <name> --change <id>`.',
|
|
300
|
+
];
|
|
301
|
+
}
|
|
302
|
+
function skillsLines() {
|
|
303
|
+
return ['Skill registry view is planned.', '', 'Read-only CLI today: `vgxness skills index --project <name> --scope project`.'];
|
|
304
|
+
}
|
|
305
|
+
function providerLines(plan) {
|
|
306
|
+
if (plan.opencode === undefined)
|
|
307
|
+
return [field('Provider', plan.provider), 'No provider-specific setup plan is available.'];
|
|
308
|
+
return [
|
|
309
|
+
field('Provider', 'opencode'),
|
|
310
|
+
field('Scope', plan.opencode.scope),
|
|
311
|
+
field('Action', plan.opencode.action),
|
|
312
|
+
field('Installs agents', plan.opencode.installsAgents ? 'yes' : 'no'),
|
|
313
|
+
field('Target', plan.opencode.targetPath ?? 'not applicable'),
|
|
314
|
+
'',
|
|
315
|
+
'Apply remains explicit: `vgxness setup apply --yes` after review.',
|
|
316
|
+
];
|
|
317
|
+
}
|
|
318
|
+
function focusedProviderLines(plan) {
|
|
319
|
+
if (plan.opencode === undefined) {
|
|
320
|
+
return [
|
|
321
|
+
field('Provider', providerTitle(plan.provider)),
|
|
322
|
+
field('Setup', plan.status),
|
|
323
|
+
field('Provider writes', String(plan.safety.writesProviderConfig)),
|
|
324
|
+
'',
|
|
325
|
+
'No OpenCode-specific setup plan is available for this provider choice.',
|
|
326
|
+
'Use `vgxness setup plan --provider <name>` for copyable CLI guidance.',
|
|
327
|
+
];
|
|
328
|
+
}
|
|
329
|
+
return [
|
|
330
|
+
field('Provider', 'OpenCode'),
|
|
331
|
+
field('Scope', plan.opencode.scope),
|
|
332
|
+
field('Planned action', plan.opencode.action),
|
|
333
|
+
field('Target', plan.opencode.targetPath ?? 'not applicable'),
|
|
334
|
+
field('Installs agents', plan.opencode.installsAgents ? 'yes' : 'no'),
|
|
335
|
+
field('Agent count', String(plan.opencode.agentNames.length)),
|
|
336
|
+
field('Bash policy', bashPolicySummary(plan.opencode.bashPermissionPolicy)),
|
|
337
|
+
field('Provider writes', String(plan.safety.writesProviderConfig)),
|
|
338
|
+
'',
|
|
339
|
+
plan.opencode.agentNames.length === 0 ? 'Agents: none planned.' : `Agents: ${plan.opencode.agentNames.join(', ')}`,
|
|
340
|
+
'Apply remains explicit: `vgxness setup apply --yes` after review.',
|
|
341
|
+
];
|
|
342
|
+
}
|
|
343
|
+
function bashPolicySummary(policy) {
|
|
344
|
+
return policy.manager === 'allow' ? `${policy.topLevel} globally, allow for VGXNESS manager` : policy.topLevel;
|
|
345
|
+
}
|
|
346
|
+
function summaryItems(plan) {
|
|
347
|
+
const ready = plan.status === 'ready';
|
|
348
|
+
const hasConflicts = plan.conflicts.length > 0;
|
|
349
|
+
return [
|
|
350
|
+
{ label: 'Setup', value: ready ? 'Ready' : statusTitle(plan.status), tone: ready ? 'success' : hasConflicts ? 'danger' : 'warning' },
|
|
351
|
+
{ label: 'Provider', value: providerTitle(plan.provider), tone: plan.provider === 'opencode' ? 'info' : 'warning' },
|
|
352
|
+
{ label: 'Safety', value: 'Read-only', tone: 'success' },
|
|
353
|
+
];
|
|
354
|
+
}
|
|
355
|
+
function statusTitle(status) {
|
|
356
|
+
if (status === 'manual-required')
|
|
357
|
+
return 'Manual';
|
|
358
|
+
return status.charAt(0).toUpperCase() + status.slice(1);
|
|
359
|
+
}
|
|
360
|
+
function providerTitle(provider) {
|
|
361
|
+
if (provider === 'opencode')
|
|
362
|
+
return 'OpenCode';
|
|
363
|
+
return provider.charAt(0).toUpperCase() + provider.slice(1);
|
|
364
|
+
}
|
|
365
|
+
function nextSetupStep(plan) {
|
|
366
|
+
if (plan.status === 'ready')
|
|
367
|
+
return 'Next: review setup details, then apply explicitly only if intended.';
|
|
368
|
+
if (plan.status === 'conflict')
|
|
369
|
+
return 'Next: resolve the provider conflict before applying setup.';
|
|
370
|
+
return 'Next: follow manual setup guidance before applying changes.';
|
|
371
|
+
}
|
|
372
|
+
function labelForHomeTab(tab) {
|
|
373
|
+
if (tab === 'setup')
|
|
374
|
+
return 'Setup';
|
|
375
|
+
if (tab === 'status')
|
|
376
|
+
return 'Status';
|
|
377
|
+
if (tab === 'runs')
|
|
378
|
+
return 'Runs';
|
|
379
|
+
if (tab === 'sdd')
|
|
380
|
+
return 'SDD';
|
|
381
|
+
if (tab === 'skills')
|
|
382
|
+
return 'Skills';
|
|
383
|
+
return 'Provider';
|
|
384
|
+
}
|
|
385
|
+
function field(label, value) {
|
|
386
|
+
return `${label.padEnd(16)} ${value}`;
|
|
387
|
+
}
|