vgxness 1.1.1 → 1.2.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/cli/commands/dashboard-dispatcher.js +38 -6
- package/dist/cli/tui/main-menu/main-menu-read-model.js +6 -6
- package/dist/cli/tui/setup/screens/cancellation-screen.js +1 -1
- package/dist/cli/tui/setup/screens/final-confirmation-screen.js +1 -1
- package/dist/cli/tui/setup/screens/opencode-details-screen.js +1 -1
- package/dist/cli/tui/setup/screens/plan-review-screen.js +1 -1
- package/dist/cli/tui/setup/screens/project-database-screen.js +1 -1
- package/dist/cli/tui/setup/screens/provider-screen.js +1 -1
- package/dist/cli/tui/setup/screens/result-screen.js +6 -1
- package/dist/cli/tui/setup/screens/screen-components.js +3 -3
- package/dist/cli/tui/setup/screens/welcome-screen.js +1 -1
- package/dist/cli/tui/setup/setup-tui-read-model.js +44 -4
- package/dist/cli/tui/setup/setup-tui-render-shape.js +23 -3
- package/package.json +1 -1
|
@@ -6,6 +6,7 @@ import { InMemoryRunGateway } from '../../code/runtime/gateways.js';
|
|
|
6
6
|
import { MemoryServiceCodeGateway } from '../../code/runtime/memory-service-gateway.js';
|
|
7
7
|
import { RunsCodeRunGateway } from '../../code/runtime/runs-code-run-gateway.js';
|
|
8
8
|
import { SddWorkflowGateway } from '../../code/runtime/sdd-workflow-gateway.js';
|
|
9
|
+
import React from 'react';
|
|
9
10
|
import { ProviderChangePlanService } from '../../mcp/provider-change-plan.js';
|
|
10
11
|
import { ProviderDoctorService } from '../../mcp/provider-doctor.js';
|
|
11
12
|
import { ProviderStatusService } from '../../mcp/provider-status.js';
|
|
@@ -20,14 +21,47 @@ import { buildDashboardApprovalsReadModel, buildDashboardInstallationReadModel,
|
|
|
20
21
|
import { renderDashboard as renderStatusDashboard } from '../dashboard-renderer.js';
|
|
21
22
|
import { sanitizeDashboardError, } from '../dashboard-tui-read-model.js';
|
|
22
23
|
import { dashboardKeyFromInput, loadInitialDashboardState, reduceDashboardKey, refreshDashboard, renderDashboard as renderInteractiveDashboard, resolveDashboardRenderStyle, } from '../interactive-dashboard.js';
|
|
24
|
+
import { MainMenuApp } from '../tui/main-menu/index.js';
|
|
25
|
+
import { renderInkApp } from '../tui/render-ink-app.js';
|
|
23
26
|
import { canRunInteractiveTui } from '../tui/terminal-capabilities.js';
|
|
24
|
-
import { collectRunDetails, collectRunInsights, createSetupLifecycleService, readSetupStatus } from './setup-dispatcher.js';
|
|
27
|
+
import { collectRunDetails, collectRunInsights, createSetupLifecycleService, readSetupStatus, runSetupTuiCommand } from './setup-dispatcher.js';
|
|
25
28
|
function defaultNoTtyGuidance() {
|
|
26
29
|
return ([
|
|
27
|
-
'VGXNESS
|
|
28
|
-
'Next: rerun `vgx` in an interactive terminal, use `vgx dashboard interactive` for the
|
|
30
|
+
'VGXNESS main menu requires a TTY; no provider config was written.',
|
|
31
|
+
'Next: rerun `vgx` in an interactive terminal, run `vgx init --plan` or `vgx setup plan` for read-only installation guidance, use `vgx dashboard interactive` for the dashboard, or run `vgx dashboard status --project <name>` for scriptable read-only output.',
|
|
29
32
|
].join('\n') + '\n');
|
|
30
33
|
}
|
|
34
|
+
function guidanceForMainMenuResult(result) {
|
|
35
|
+
if (result.type === 'show-doctor-guidance')
|
|
36
|
+
return ['Doctor / recovery guidance:', '- Read-only status: `vgx setup status`', '- Provider doctor: `vgx mcp doctor opencode`', '- No provider config was written.'].join('\n') + '\n';
|
|
37
|
+
if (result.type === 'show-sdd-guidance')
|
|
38
|
+
return ['SDD / workflow guidance:', '- Use VGXNESS MCP tools from OpenCode for normal SDD progression.', '- Manual status: `vgx sdd status --project <name> --change <change>`', '- No provider config was written.'].join('\n') + '\n';
|
|
39
|
+
if (result.type === 'show-advanced-cli')
|
|
40
|
+
return ['Advanced CLI guidance:', '- Installation preview: `vgx init --plan` or `vgx setup plan`', '- Dashboard: `vgx dashboard interactive`', '- Scriptable status: `vgx dashboard status --project <name>`', '- No provider config was written.'].join('\n') + '\n';
|
|
41
|
+
if (result.type === 'exit')
|
|
42
|
+
return 'Exited main menu; no provider config was written.\n';
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
async function renderDefaultMainMenu(environment, onResult) {
|
|
46
|
+
await renderInkApp(React.createElement(MainMenuApp, { onResult }), {
|
|
47
|
+
stdin: environment.stdin,
|
|
48
|
+
stdout: environment.stdout,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
export async function runDefaultInteractiveEntrypointWithMainMenu(environment, input = {}) {
|
|
52
|
+
if (!canRunInteractiveTui(environment.stdin, environment.stdout))
|
|
53
|
+
return okText(defaultNoTtyGuidance());
|
|
54
|
+
let selected;
|
|
55
|
+
await (input.renderMainMenu ?? ((onResult) => renderDefaultMainMenu(environment, onResult)))((result) => {
|
|
56
|
+
selected = result;
|
|
57
|
+
});
|
|
58
|
+
const result = selected ?? { type: 'exit' };
|
|
59
|
+
if (result.type === 'open-setup')
|
|
60
|
+
return (input.setupTui ?? runSetupTuiCommand)(environment);
|
|
61
|
+
if (result.type === 'open-dashboard')
|
|
62
|
+
return (input.dashboardInteractive ?? runDashboardInteractiveCommand)({ positionals: ['dashboard', 'interactive'], flags: {} }, environment);
|
|
63
|
+
return okText(guidanceForMainMenuResult(result) ?? 'No provider config was written.\n');
|
|
64
|
+
}
|
|
31
65
|
function buildDashboardProfiles(managerProfiles, project, setupStatus, sectionErrors) {
|
|
32
66
|
const resolved = managerProfiles.resolveEffectiveManager({ project, scope: 'project', managerName: 'vgxness-manager' });
|
|
33
67
|
if (!resolved.ok) {
|
|
@@ -431,9 +465,7 @@ export async function runDashboardInteractiveCommand(parsed, environment) {
|
|
|
431
465
|
}
|
|
432
466
|
}
|
|
433
467
|
export async function runDefaultInteractiveEntrypoint(environment) {
|
|
434
|
-
|
|
435
|
-
return okText(defaultNoTtyGuidance());
|
|
436
|
-
return runDashboardInteractiveCommand({ positionals: ['dashboard', 'interactive'], flags: {} }, environment);
|
|
468
|
+
return runDefaultInteractiveEntrypointWithMainMenu(environment);
|
|
437
469
|
}
|
|
438
470
|
export async function runCodeCliCommand(parsed, environment) {
|
|
439
471
|
const [, command] = parsed.positionals;
|
|
@@ -2,12 +2,12 @@ import { formatTuiFooter, tuiBadges } from '../visual/index.js';
|
|
|
2
2
|
import { mainMenuOptionIds } from './main-menu-state.js';
|
|
3
3
|
const optionCopy = {
|
|
4
4
|
setup: {
|
|
5
|
-
label: '
|
|
6
|
-
description: '
|
|
5
|
+
label: 'Installation',
|
|
6
|
+
description: 'Start guided installation for memory and OpenCode integration safely.',
|
|
7
7
|
badges: [tuiBadges.requiresConfirmation],
|
|
8
|
-
detailTitle: '
|
|
8
|
+
detailTitle: 'Installation',
|
|
9
9
|
detailLines: [
|
|
10
|
-
'Opens the
|
|
10
|
+
'Opens the guided Installation TUI.',
|
|
11
11
|
'Plan/review screens are read-only.',
|
|
12
12
|
'Provider writes require final confirmation; OpenCode is the only automatic provider with explicit consent.',
|
|
13
13
|
],
|
|
@@ -60,12 +60,12 @@ export function buildMainMenuViewModel(state) {
|
|
|
60
60
|
return {
|
|
61
61
|
title: 'VGXNESS Main Menu',
|
|
62
62
|
subtitle: 'Keyboard-first local control plane',
|
|
63
|
-
contextLines: ['Choose where to go next.
|
|
63
|
+
contextLines: ['Choose where to go next. Installation is first; the menu itself is read-only and does not write provider config.'],
|
|
64
64
|
options,
|
|
65
65
|
detail: { title: focused.detailTitle, lines: focused.detailLines, badges: focused.badges },
|
|
66
66
|
safetyLines: [
|
|
67
67
|
'Previews are read-only.',
|
|
68
|
-
'
|
|
68
|
+
'Installation/provider writes require final confirmation.',
|
|
69
69
|
'OpenCode is the only automatic provider path and still requires explicit consent.',
|
|
70
70
|
],
|
|
71
71
|
helpLines: state.helpVisible
|
|
@@ -2,5 +2,5 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { Text } from 'ink';
|
|
3
3
|
import { badgeLabels, Panel, SetupScreenFrame, setupFooterHints } from './screen-components.js';
|
|
4
4
|
export function CancellationScreen() {
|
|
5
|
-
return (_jsx(SetupScreenFrame, { title: "Setup cancelled", tone: "cancelled", footer: [setupFooterHints.close], children: _jsxs(Panel, { title: "Cancelled", badge: { label: badgeLabels.cancelled }, children: [_jsx(Text, { children: "Setup was not completed." }), _jsx(Text, { children: "No provider config was written." })] }) }));
|
|
5
|
+
return (_jsx(SetupScreenFrame, { title: "Setup cancelled", tone: "cancelled", footer: [setupFooterHints.close], children: _jsxs(Panel, { title: "Cancelled", badge: { label: badgeLabels.cancelled }, children: [_jsx(Text, { children: "Setup was not completed." }), _jsx(Text, { children: "No provider config was written." }), _jsx(Text, { children: "No agent seeding was performed." })] }) }));
|
|
6
6
|
}
|
|
@@ -2,5 +2,5 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { Text } from 'ink';
|
|
3
3
|
import { Panel, PlanSummary, SetupWorkspace, setupFooterHints } from './screen-components.js';
|
|
4
4
|
export function FinalConfirmationScreen(props) {
|
|
5
|
-
return (_jsx(SetupWorkspace, { title: "Final confirmation", viewModel: props.viewModel, footer: [setupFooterHints.confirmApply, setupFooterHints.back, setupFooterHints.cancel], children: _jsxs(Panel, { title: "Confirm OpenCode setup", children: [_jsx(PlanSummary, { viewModel: props.viewModel }), _jsx(Text, { children: "[warning]" }), _jsx(Text, { color: "yellow", children: "WARNING: OpenCode provider config may be modified at the target path above. Existing config is backed up when planned." }), _jsx(Text, { children: "Press Enter to confirm and apply OpenCode setup, or Esc/q to cancel without writing." })] }) }));
|
|
5
|
+
return (_jsx(SetupWorkspace, { title: "Final confirmation", viewModel: props.viewModel, footer: [setupFooterHints.confirmApply, setupFooterHints.back, setupFooterHints.cancel], children: _jsxs(Panel, { title: "Confirm OpenCode setup", children: [_jsx(PlanSummary, { viewModel: props.viewModel }), _jsx(Text, { children: "[warning]" }), _jsx(Text, { color: "yellow", children: "WARNING: OpenCode provider config may be modified at the target path above. Existing config is backed up when planned." }), _jsx(Text, { children: "After confirmation: run doctor, then restart OpenCode to reload MCP configuration." }), _jsx(Text, { children: "Press Enter to confirm and apply OpenCode setup, or Esc/q to cancel without writing." })] }) }));
|
|
6
6
|
}
|
|
@@ -6,5 +6,5 @@ export function OpenCodeDetailsScreen(props) {
|
|
|
6
6
|
if (vm.providerLabel !== 'OpenCode') {
|
|
7
7
|
return (_jsx(SetupWorkspace, { title: "OpenCode details", viewModel: vm, footer: [setupFooterHints.continue, setupFooterHints.back, setupFooterHints.cancel], children: _jsx(ManualNoProviderNotice, {}) }));
|
|
8
8
|
}
|
|
9
|
-
return (_jsx(SetupWorkspace, { title: "OpenCode details", viewModel: vm, footer: [setupFooterHints.select, setupFooterHints.continue, setupFooterHints.back, setupFooterHints.cancel], children: _jsxs(Panel, { title: "OpenCode plan", badge: { label: badgeLabels.writeAfterConfirm }, children: [_jsx(ChoiceList, { choices: vm.scopeChoices }), _jsx(ChoiceList, { choices: vm.installModeChoices }), _jsx(KeyValue, { label: "Scope", value: vm.scopeLabel }), _jsx(KeyValue, { label: "Install mode", value: vm.installModeLabel }), _jsx(KeyValue, { label: "Target config", value: vm.targetPathLabel }), _jsx(KeyValue, { label: "Action", value: vm.opencodeActionLabel }), _jsx(Text, { children: "Details are preview-only here; writes require final confirmation." })] }) }));
|
|
9
|
+
return (_jsx(SetupWorkspace, { title: "OpenCode details", viewModel: vm, footer: [setupFooterHints.select, setupFooterHints.continue, setupFooterHints.back, setupFooterHints.cancel], children: _jsxs(Panel, { title: "OpenCode plan", badge: { label: badgeLabels.writeAfterConfirm }, children: [_jsx(ChoiceList, { choices: vm.scopeChoices }), _jsx(ChoiceList, { choices: vm.installModeChoices }), _jsx(KeyValue, { label: "Scope", value: vm.scopeLabel }), _jsx(KeyValue, { label: "Install mode", value: vm.installModeLabel }), _jsx(KeyValue, { label: "Target config", value: vm.targetPathLabel }), _jsx(KeyValue, { label: "Action", value: vm.opencodeActionLabel }), _jsx(KeyValue, { label: "Agent readiness", value: vm.agentReadinessLabel }), _jsx(Text, { children: vm.agentReadinessDetail }), _jsx(Text, { children: "Details are preview-only here; writes require final confirmation." })] }) }));
|
|
10
10
|
}
|
|
@@ -2,5 +2,5 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { Text } from 'ink';
|
|
3
3
|
import { badgeLabels, Panel, PlanSummary, SetupWorkspace, setupFooterHints } from './screen-components.js';
|
|
4
4
|
export function PlanReviewScreen(props) {
|
|
5
|
-
return (_jsx(SetupWorkspace, { title: "Read-only plan review", viewModel: props.viewModel, footer: [setupFooterHints.finalConfirmation, setupFooterHints.back, setupFooterHints.cancel], children: _jsxs(Panel, { title: "Plan summary", badge: { label: badgeLabels.readOnly }, children: [_jsx(PlanSummary, { viewModel: props.viewModel }), _jsx(Text, { children: "Review only: this screen does not write provider config." }), _jsx(Text, { children: props.viewModel.canAutoApply ? 'Press Enter to continue to final confirmation.' : 'Manual setup required; no automatic apply is available.' })] }) }));
|
|
5
|
+
return (_jsx(SetupWorkspace, { title: "Read-only plan review", viewModel: props.viewModel, footer: [setupFooterHints.finalConfirmation, setupFooterHints.back, setupFooterHints.cancel], children: _jsxs(Panel, { title: "Plan summary", badge: { label: badgeLabels.readOnly }, children: [_jsx(PlanSummary, { viewModel: props.viewModel }), _jsxs(Text, { children: ["Doctor next step: ", props.viewModel.nextCommands.find((command) => /\bdoctor\b/.test(command)) ?? 'vgx doctor'] }), _jsx(Text, { children: "Restart next step: Restart OpenCode after confirmed setup so it reloads MCP configuration." }), _jsx(Text, { children: "Review only: this screen does not write provider config." }), _jsx(Text, { children: props.viewModel.canAutoApply ? 'Press Enter to continue to final confirmation.' : 'Manual setup required; no automatic apply is available.' })] }) }));
|
|
6
6
|
}
|
|
@@ -2,5 +2,5 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { ChoiceList, KeyValue, Panel, SetupWorkspace, setupFooterHints } from './screen-components.js';
|
|
3
3
|
export function ProjectDatabaseScreen(props) {
|
|
4
4
|
const vm = props.viewModel;
|
|
5
|
-
return (_jsxs(SetupWorkspace, { title: "Project and database", viewModel: vm, footer: [setupFooterHints.select, setupFooterHints.continue, setupFooterHints.back, setupFooterHints.cancel], children: [_jsxs(Panel, { title: "Project", children: [_jsx(KeyValue, { label: "Project", value: vm.projectLabel }), _jsx(KeyValue, { label: "Workspace", value: vm.workspaceRootLabel })] }), _jsxs(Panel, { title: "Database", children: [_jsx(ChoiceList, { choices: vm.databaseChoices }), _jsx(KeyValue, { label: "Mode", value: vm.databaseLabel }), _jsx(KeyValue, { label: "Path", value: vm.databasePathLabel }), _jsx(KeyValue, { label: "Source", value: vm.databaseSourceLabel })] })] }));
|
|
5
|
+
return (_jsxs(SetupWorkspace, { title: "Project and database", viewModel: vm, footer: [setupFooterHints.select, setupFooterHints.continue, setupFooterHints.back, setupFooterHints.cancel], children: [_jsxs(Panel, { title: "Project", children: [_jsx(KeyValue, { label: "Project", value: vm.projectLabel }), _jsx(KeyValue, { label: "Workspace", value: vm.workspaceRootLabel })] }), _jsxs(Panel, { title: "Database", children: [_jsx(ChoiceList, { choices: vm.databaseChoices }), _jsx(KeyValue, { label: "Mode", value: vm.databaseLabel }), _jsx(KeyValue, { label: "Path", value: vm.databasePathLabel }), _jsx(KeyValue, { label: "Source", value: vm.databaseSourceLabel }), _jsx(KeyValue, { label: "Memory path explanation", value: vm.memoryPathExplanation })] })] }));
|
|
6
6
|
}
|
|
@@ -3,5 +3,5 @@ import { Text } from 'ink';
|
|
|
3
3
|
import { ChoiceList, Panel, SetupWorkspace, setupFooterHints } from './screen-components.js';
|
|
4
4
|
export function ProviderScreen(props) {
|
|
5
5
|
const vm = props.viewModel;
|
|
6
|
-
return (_jsx(SetupWorkspace, { title: "Provider", viewModel: vm, footer: [setupFooterHints.select, setupFooterHints.continue, setupFooterHints.back, setupFooterHints.cancel], children: _jsxs(Panel, { title: "Provider selection", children: [_jsx(ChoiceList, { choices: vm.providerChoices }), _jsx(Text, { children: "OpenCode setup remains gated by final confirmation. Non-OpenCode/manual has no auto-apply." })] }) }));
|
|
6
|
+
return (_jsx(SetupWorkspace, { title: "Provider", viewModel: vm, footer: [setupFooterHints.select, setupFooterHints.continue, setupFooterHints.back, setupFooterHints.cancel], children: _jsxs(Panel, { title: "Provider selection", children: [_jsx(ChoiceList, { choices: vm.providerChoices }), _jsx(Text, { children: vm.providerInstallabilityLabel }), _jsx(Text, { children: "OpenCode setup remains gated by final confirmation. Non-OpenCode/manual has no auto-apply." })] }) }));
|
|
7
7
|
}
|
|
@@ -4,8 +4,13 @@ import { badgeLabels, KeyValue, Panel, SetupScreenFrame, setupFooterHints } from
|
|
|
4
4
|
export function ResultScreen(props) {
|
|
5
5
|
const result = props.result;
|
|
6
6
|
if (result?.status === 'installed')
|
|
7
|
-
return (_jsx(SetupScreenFrame, { title: "Setup complete", tone: "success", footer: [setupFooterHints.close], children: _jsxs(Panel, { title: "Installed", badge: { label: badgeLabels.success }, children: [_jsx(KeyValue, { label: "Target config", value: result.targetPath }), result.backupPath === undefined ? _jsx(Text, { children: "Backup: none created" }) : _jsx(KeyValue, { label: "Backup", value: result.backupPath }), result.warnings.map((warning) => (_jsxs(Text, { children: ["Warning: ", warning] }, warning)))] }) }));
|
|
7
|
+
return (_jsx(SetupScreenFrame, { title: "Setup complete", tone: "success", footer: [setupFooterHints.close], children: _jsxs(Panel, { title: "Installed", badge: { label: badgeLabels.success }, children: [_jsx(KeyValue, { label: "Target config", value: result.targetPath }), result.backupPath === undefined ? _jsx(Text, { children: "Backup: none created" }) : _jsx(KeyValue, { label: "Backup", value: result.backupPath }), result.warnings.map((warning) => (_jsxs(Text, { children: ["Warning: ", warning] }, warning))), resultNextCommands(props.viewModel).map((command) => (_jsxs(Text, { children: ["Next: ", command] }, command)))] }) }));
|
|
8
8
|
if (result?.status === 'manual-required')
|
|
9
9
|
return (_jsx(SetupScreenFrame, { title: "Manual setup", tone: "warning", footer: [setupFooterHints.close], children: _jsxs(Panel, { title: "Manual follow-up", badge: { label: badgeLabels.warning }, children: [_jsx(Text, { children: result.message }), props.viewModel.nextCommands.map((command) => (_jsxs(Text, { children: ["Next: ", command] }, command)))] }) }));
|
|
10
10
|
return (_jsx(SetupScreenFrame, { title: "Setup result", footer: [setupFooterHints.close], children: _jsx(Panel, { title: "No write", children: _jsx(Text, { children: "No provider config write occurred." }) }) }));
|
|
11
11
|
}
|
|
12
|
+
function resultNextCommands(viewModel) {
|
|
13
|
+
const filtered = viewModel.nextCommands.filter((command) => !/^vgx(?:ness)? setup apply\b/.test(command));
|
|
14
|
+
const withDoctor = filtered.some((command) => /\bdoctor\b/.test(command)) ? filtered : [...filtered, 'vgx doctor'];
|
|
15
|
+
return withDoctor.some((command) => /Restart OpenCode/i.test(command)) ? withDoctor : [...withDoctor, 'Restart OpenCode and verify the vgxness MCP server is visible.'];
|
|
16
|
+
}
|
|
@@ -64,10 +64,10 @@ export function KeyValue(props) {
|
|
|
64
64
|
}
|
|
65
65
|
export function PlanSummary(props) {
|
|
66
66
|
const vm = props.viewModel;
|
|
67
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(KeyValue, { label: "Readiness", value: `${vm.readinessBadge} ${vm.readinessLabel}` }), _jsx(KeyValue, { label: "Provider", value: vm.providerLabel }), _jsx(KeyValue, { label: "Database", value: vm.databaseLabel }), _jsx(KeyValue, { label: "Database path", value: vm.databasePathLabel }), _jsx(KeyValue, { label: "OpenCode scope", value: vm.scopeLabel }), _jsx(KeyValue, { label: "Install mode", value: vm.installModeLabel }), _jsx(KeyValue, { label: "Target config", value: vm.targetPathLabel }), _jsx(Text, { children: vm.backupLabel }), vm.plannedActions.map((action) => (_jsxs(Text, { children: ["Action: ", action.safetyBadge, " ", action.label, action.targetPathLabel === undefined ? '' : ` (${action.targetPathLabel})`] }, `action:${action.id}`))), vm.warnings.map((warning) => (_jsxs(Text, { children: ["Warning: ", warning] }, `warning:${warning}`))), vm.blockers.map((blocker) => (_jsxs(Text, { children: ["Blocked: ", blocker] }, `blocker:${blocker}`)))] }));
|
|
67
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(KeyValue, { label: "Readiness", value: `${vm.readinessBadge} ${vm.readinessLabel}` }), _jsx(KeyValue, { label: "Provider", value: vm.providerLabel }), _jsx(KeyValue, { label: "Database", value: vm.databaseLabel }), _jsx(KeyValue, { label: "Database path", value: vm.databasePathLabel }), _jsx(KeyValue, { label: "OpenCode scope", value: vm.scopeLabel }), _jsx(KeyValue, { label: "Install mode", value: vm.installModeLabel }), _jsx(KeyValue, { label: "Provider installability", value: vm.providerInstallabilityLabel }), _jsx(KeyValue, { label: "Agent readiness", value: vm.agentReadinessLabel }), _jsx(KeyValue, { label: "Agent detail", value: vm.agentReadinessDetail }), _jsx(KeyValue, { label: "Target config", value: vm.targetPathLabel }), _jsx(Text, { children: vm.backupLabel }), vm.plannedActions.map((action) => (_jsxs(Text, { children: ["Action: ", action.safetyBadge, " ", action.label, action.targetPathLabel === undefined ? '' : ` (${action.targetPathLabel})`] }, `action:${action.id}`))), vm.warnings.map((warning) => (_jsxs(Text, { children: ["Warning: ", warning] }, `warning:${warning}`))), vm.blockers.map((blocker) => (_jsxs(Text, { children: ["Blocked: ", blocker] }, `blocker:${blocker}`)))] }));
|
|
68
68
|
}
|
|
69
69
|
export function ManualNoProviderNotice() {
|
|
70
|
-
return (_jsxs(Panel, { title: "Manual / no-provider-write mode", badge: { label: badgeLabels.manual }, children: [_jsx(Text, { children: "OpenCode-only scope and install controls are disabled because provider is Manual / none." }), _jsx(Text, { children: "No provider config will be written; use the manual next commands from the plan." })] }));
|
|
70
|
+
return (_jsxs(Panel, { title: "Manual / no-provider-write mode", badge: { label: badgeLabels.manual }, children: [_jsx(Text, { children: "OpenCode-only scope and install controls are disabled because provider is Manual / none." }), _jsx(Text, { children: "Claude remains deferred/not installable in guided setup." }), _jsx(Text, { children: "No provider config will be written; use the manual next commands from the plan." })] }));
|
|
71
71
|
}
|
|
72
72
|
export function compactPath(path, width) {
|
|
73
73
|
if (width <= 0 || path.length <= width)
|
|
@@ -81,7 +81,7 @@ export function compactPath(path, width) {
|
|
|
81
81
|
export function wrapLabel(value, width) {
|
|
82
82
|
if (width <= 0 || value.length <= width)
|
|
83
83
|
return value;
|
|
84
|
-
const protectedPhrases = ['final confirmation', 'confirm and apply', 'read-only', 'requires confirmation', 'will write after confirm', 'error', 'cancelled'];
|
|
84
|
+
const protectedPhrases = ['final confirmation', 'confirm and apply', 'not installable', 'read-only', 'requires confirmation', 'will write after confirm', 'error', 'cancelled'];
|
|
85
85
|
const phrase = protectedPhrases.find((candidate) => value.includes(candidate));
|
|
86
86
|
if (phrase !== undefined)
|
|
87
87
|
return `${value.slice(0, Math.max(0, width - phrase.length - 5)).trim()} ... ${phrase}`.trim();
|
|
@@ -2,5 +2,5 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { Text } from 'ink';
|
|
3
3
|
import { Panel, SetupWorkspace, setupFooterHints } from './screen-components.js';
|
|
4
4
|
export function WelcomeScreen(props) {
|
|
5
|
-
return (_jsx(SetupWorkspace, { title: "Detect", viewModel: props.viewModel, footer: [setupFooterHints.continue, setupFooterHints.help, setupFooterHints.cancel], children: _jsxs(Panel, { title: "
|
|
5
|
+
return (_jsx(SetupWorkspace, { title: "Detect", viewModel: props.viewModel, footer: [setupFooterHints.continue, setupFooterHints.help, setupFooterHints.cancel], children: _jsxs(Panel, { title: "Guided installation sequence", children: [_jsx(Text, { children: "Setup welcome: global memory \u2192 provider \u2192 OpenCode details \u2192 agent readiness \u2192 preview \u2192 final confirmation \u2192 doctor/restart." }), _jsx(Text, { children: "VGXNESS Setup Assistant" }), _jsx(Text, { children: "Configure local storage and OpenCode integration with read-only previews first." }), _jsx(Text, { children: "No provider config is written until the final confirmation." })] }) }));
|
|
6
6
|
}
|
|
@@ -12,6 +12,8 @@ export function setupTuiViewModelFromPlan(plan, status, state) {
|
|
|
12
12
|
const workspaceRoot = plan?.workspaceRoot ?? status?.environment.workspaceRoot ?? 'pending workspace';
|
|
13
13
|
const databasePath = plan?.db.path ?? status?.store.path ?? 'pending database path';
|
|
14
14
|
const databaseSource = plan?.db.source ?? 'pending';
|
|
15
|
+
const memoryExplanation = memoryPathExplanation(plan, databasePath, databaseSource);
|
|
16
|
+
const agentReadiness = agentReadinessLabels(plan, status);
|
|
15
17
|
const blockers = plan?.conflicts
|
|
16
18
|
.filter((conflict) => conflict.severity === 'blocking')
|
|
17
19
|
.map((conflict) => `${conflict.message}${conflict.recovery === undefined ? '' : ` Recovery: ${conflict.recovery}`}`) ?? [];
|
|
@@ -40,6 +42,12 @@ export function setupTuiViewModelFromPlan(plan, status, state) {
|
|
|
40
42
|
: 'Manual / none (no OpenCode install)',
|
|
41
43
|
targetPathLabel: isOpenCode && opencode?.targetPath !== undefined ? compactPath(opencode.targetPath, 72) : 'No provider config target; manual/no-provider-write mode',
|
|
42
44
|
opencodeActionLabel: isOpenCode ? opencodeActionLabel(opencode?.action) : 'No automatic provider write; use manual setup guidance.',
|
|
45
|
+
memoryPathExplanation: memoryExplanation,
|
|
46
|
+
providerInstallabilityLabel: isOpenCode
|
|
47
|
+
? 'OpenCode is the only installable provider in this guided setup. Claude is deferred guidance only.'
|
|
48
|
+
: 'Manual / none is read-only guidance; no provider is installable from this selection. Claude is deferred guidance only.',
|
|
49
|
+
agentReadinessLabel: agentReadiness.label,
|
|
50
|
+
agentReadinessDetail: agentReadiness.detail,
|
|
43
51
|
plannedActions: plan?.actions.map((action) => ({
|
|
44
52
|
id: action.id,
|
|
45
53
|
label: action.description,
|
|
@@ -65,7 +73,8 @@ export function setupTuiViewModelFromPlan(plan, status, state) {
|
|
|
65
73
|
choice('database:custom', 'Custom database path', 'Deferred/read-only in the TUI unless already supplied by flags or environment.', selections?.databaseMode === 'custom' || (selections === undefined && plan?.db.mode === 'custom'), state?.focusedChoiceId, [badgeLabels.deferred, badgeLabels.readOnly]),
|
|
66
74
|
],
|
|
67
75
|
providerChoices: [
|
|
68
|
-
choice('provider:opencode', 'OpenCode', '
|
|
76
|
+
choice('provider:opencode', 'OpenCode', 'Only installable provider in this guided setup; writes still require final confirmation.', (selections?.provider ?? plan?.provider ?? 'opencode') === 'opencode', state?.focusedChoiceId, [badgeLabels.recommended]),
|
|
77
|
+
choice('provider:claude-deferred', 'Claude (deferred)', 'Guidance only for now; not selectable and not installable in this setup flow.', false, state?.focusedChoiceId, [badgeLabels.deferred, badgeLabels.readOnly]),
|
|
69
78
|
choice('provider:none', 'Manual / none', 'No automatic provider config write; follow manual setup guidance.', (selections?.provider ?? plan?.provider) === 'none', state?.focusedChoiceId, [badgeLabels.manual, badgeLabels.readOnly]),
|
|
70
79
|
],
|
|
71
80
|
scopeChoices: isOpenCode
|
|
@@ -76,7 +85,7 @@ export function setupTuiViewModelFromPlan(plan, status, state) {
|
|
|
76
85
|
: [],
|
|
77
86
|
installModeChoices: isOpenCode
|
|
78
87
|
? [
|
|
79
|
-
choice('install:mcp-plus-agents', 'MCP plus agents', 'Install
|
|
88
|
+
choice('install:mcp-plus-agents', 'MCP plus agents', 'Install OpenCode MCP/agent entries after confirmation; local VGXNESS agent seeding is not done by preview screens.', (selections?.installMode ?? (opencode?.installsAgents === false ? 'mcp-only' : 'mcp-plus-agents')) === 'mcp-plus-agents', state?.focusedChoiceId, [badgeLabels.recommended, badgeLabels.writeAfterConfirm]),
|
|
80
89
|
choice('install:mcp-only', 'MCP only', 'Install only the MCP server entry after confirmation.', (selections?.installMode ?? (opencode?.installsAgents === false ? 'mcp-only' : 'mcp-plus-agents')) === 'mcp-only', state?.focusedChoiceId, [badgeLabels.writeAfterConfirm]),
|
|
81
90
|
]
|
|
82
91
|
: [],
|
|
@@ -88,9 +97,12 @@ function previewDetailLines(input) {
|
|
|
88
97
|
const actions = plan?.actions.map((action) => `Planned action: ${action.description}${action.targetPath === undefined ? '' : ` Target: ${compactPath(action.targetPath, 72)}`}${action.backupRequired ? ' Backup required.' : ''}`) ?? ['Planned action: create read-only preview plan.'];
|
|
89
98
|
return [
|
|
90
99
|
`Provider: ${input.provider === 'opencode' ? 'OpenCode [recommended]' : 'Manual / none [manual] [read-only]'}`,
|
|
91
|
-
`
|
|
100
|
+
`Memory path: ${plan?.db.mode ?? 'pending'} at ${compactPath(input.databasePath, 72)} (source: ${String(input.databaseSource)})`,
|
|
101
|
+
`Memory guidance: ${memoryPathExplanation(plan, input.databasePath, input.databaseSource)}`,
|
|
92
102
|
`Scope: ${input.isOpenCode ? (opencode?.scope ?? 'user') : 'disabled for manual/none provider'}`,
|
|
93
103
|
`Install mode: ${input.isOpenCode ? (opencode?.installsAgents === false ? 'mcp-only' : 'mcp-plus-agents') : 'disabled for manual/none provider'}`,
|
|
104
|
+
`Provider installability: ${input.isOpenCode ? 'OpenCode installable after final confirmation; Claude deferred/not installable.' : 'No provider installable; OpenCode controls disabled and Claude deferred.'}`,
|
|
105
|
+
`Agent readiness: ${agentReadinessFromPlan(plan)}`,
|
|
94
106
|
`Target config: ${input.isOpenCode && opencode?.targetPath !== undefined ? compactPath(opencode.targetPath, 72) : 'none; no provider config will be written'}`,
|
|
95
107
|
`Safety: ${input.isOpenCode ? '[will write after confirm] only on final confirmation' : '[read-only] manual/no-provider-write mode'}`,
|
|
96
108
|
...actions,
|
|
@@ -109,10 +121,38 @@ function helpLines(screen) {
|
|
|
109
121
|
'Next/back: Tab continues; Shift+Tab goes back. Enter continues on review and confirms only on final confirmation.',
|
|
110
122
|
'Cancel/close: q or Esc cancels setup; when help is open, ?/h toggles it closed.',
|
|
111
123
|
reviewLine,
|
|
112
|
-
'Provider support: OpenCode is
|
|
124
|
+
'Provider support: OpenCode is the only installable provider; Claude is [deferred] [read-only] guidance and Manual / none writes no provider config.',
|
|
125
|
+
'Agent readiness: the preview checks vgxness-manager/SDD readiness guidance; preview screens never seed agents.',
|
|
113
126
|
'No-write guarantee: no provider config is written before explicit final confirmation.',
|
|
114
127
|
];
|
|
115
128
|
}
|
|
129
|
+
function memoryPathExplanation(plan, path, source) {
|
|
130
|
+
if (plan?.db.mode === 'project-local')
|
|
131
|
+
return `Project-local memory would use ${compactPath(path, 72)} for this workspace only; this preview creates no file.`;
|
|
132
|
+
if (plan?.db.mode === 'custom')
|
|
133
|
+
return `Custom memory path ${compactPath(path, 72)} is used only when supplied by flags or environment; this preview is read-only.`;
|
|
134
|
+
return `Global memory is the default local store at ${compactPath(path, 72)} (source: ${String(source)}), shared across VGXNESS projects unless a project/custom path is chosen.`;
|
|
135
|
+
}
|
|
136
|
+
function agentReadinessLabels(plan, status) {
|
|
137
|
+
const names = plan?.opencode?.agentNames.length === 0 ? ['vgxness-manager', 'SDD agents'] : (plan?.opencode?.agentNames ?? ['vgxness-manager', 'SDD agents']);
|
|
138
|
+
if (status?.agents.deferred === true)
|
|
139
|
+
return {
|
|
140
|
+
label: 'Agent readiness deferred',
|
|
141
|
+
detail: status.agents.nextAction ?? 'Select a project to check vgxness-manager and SDD agent readiness. No agent seeding occurs in preview.',
|
|
142
|
+
};
|
|
143
|
+
if (status?.agents.status === 'ready')
|
|
144
|
+
return { label: 'Agents ready', detail: `Required local agent context is ready for ${status.agents.agentName}; preview target agents: ${names.join(', ')}.` };
|
|
145
|
+
if (status?.agents.status === 'blocked')
|
|
146
|
+
return {
|
|
147
|
+
label: 'Agents need attention',
|
|
148
|
+
detail: `${status.agents.blocker ?? 'Required agents are not ready.'} Next: ${status.agents.nextAction ?? 'vgx agents seed --scope project'}`,
|
|
149
|
+
};
|
|
150
|
+
return { label: 'Agent readiness preview', detail: `Required agents: ${names.join(', ')}. Preview/status screens do not seed agents.` };
|
|
151
|
+
}
|
|
152
|
+
function agentReadinessFromPlan(plan) {
|
|
153
|
+
const names = plan?.opencode?.agentNames.length === 0 ? ['vgxness-manager', 'SDD agents'] : (plan?.opencode?.agentNames ?? ['vgxness-manager', 'SDD agents']);
|
|
154
|
+
return `preview required agents (${names.join(', ')}); no agent seeding on read-only screens`;
|
|
155
|
+
}
|
|
116
156
|
function choice(id, label, description, selected, focusedChoiceId, badges) {
|
|
117
157
|
return { id, label, description, selected, focused: focusedChoiceId === undefined ? selected : focusedChoiceId === id, badges };
|
|
118
158
|
}
|
|
@@ -9,8 +9,8 @@ function screenLines(input, width) {
|
|
|
9
9
|
switch (input.screen) {
|
|
10
10
|
case 'welcome':
|
|
11
11
|
return workspaceLines(vm, [
|
|
12
|
-
'
|
|
13
|
-
'Setup welcome',
|
|
12
|
+
'Guided installation sequence',
|
|
13
|
+
'Setup welcome: global memory → provider → OpenCode details → agent readiness → preview → final confirmation → doctor/restart.',
|
|
14
14
|
'VGXNESS Setup Assistant',
|
|
15
15
|
'Configure local storage and OpenCode integration with read-only previews first.',
|
|
16
16
|
'No provider config is written until the final confirmation.',
|
|
@@ -25,11 +25,13 @@ function screenLines(input, width) {
|
|
|
25
25
|
`Mode: ${vm.databaseLabel}`,
|
|
26
26
|
`Path: ${compactPath(vm.databasePathLabel, width - 7)}`,
|
|
27
27
|
`Source: ${vm.databaseSourceLabel}`,
|
|
28
|
+
`Memory path explanation: ${vm.memoryPathExplanation}`,
|
|
28
29
|
], width, choiceFooter());
|
|
29
30
|
case 'provider':
|
|
30
31
|
return workspaceLines(vm, [
|
|
31
32
|
'Provider selection',
|
|
32
33
|
...choiceLines(vm.providerChoices),
|
|
34
|
+
vm.providerInstallabilityLabel,
|
|
33
35
|
'OpenCode setup remains gated by final confirmation. Manual / none has no automatic install and no provider config will be written.',
|
|
34
36
|
], width, choiceFooter());
|
|
35
37
|
case 'opencode-details':
|
|
@@ -42,11 +44,14 @@ function screenLines(input, width) {
|
|
|
42
44
|
`Install mode: ${vm.installModeLabel}`,
|
|
43
45
|
`Target config: ${compactPath(vm.targetPathLabel, width - 15)}`,
|
|
44
46
|
`Action: ${vm.opencodeActionLabel}`,
|
|
47
|
+
`Agent readiness: ${vm.agentReadinessLabel}`,
|
|
48
|
+
vm.agentReadinessDetail,
|
|
45
49
|
'Details are preview-only here; writes require final confirmation.',
|
|
46
50
|
]
|
|
47
51
|
: [
|
|
48
52
|
'Manual / no-provider-write mode ' + badgeLabels.manual,
|
|
49
53
|
'OpenCode-only scope and install controls are disabled.',
|
|
54
|
+
'Claude remains deferred/not installable; no provider config will be written.',
|
|
50
55
|
'No provider config will be written; use manual setup guidance.',
|
|
51
56
|
], width, vm.providerLabel === 'OpenCode' ? choiceFooter() : [setupFooterHints.continue, setupFooterHints.back, setupFooterHints.cancel]);
|
|
52
57
|
case 'plan-review':
|
|
@@ -54,6 +59,8 @@ function screenLines(input, width) {
|
|
|
54
59
|
'Read-only plan review',
|
|
55
60
|
'Plan summary ' + badgeLabels.readOnly,
|
|
56
61
|
...planLines(vm, width),
|
|
62
|
+
`Doctor next step: ${doctorCommand(vm)}`,
|
|
63
|
+
'Restart next step: Restart OpenCode after confirmed setup so it reloads MCP configuration.',
|
|
57
64
|
'Review only: this screen does not write provider config.',
|
|
58
65
|
vm.canAutoApply ? 'Press Enter to continue to final confirmation.' : 'Manual setup required; no automatic apply is available.',
|
|
59
66
|
], width, [setupFooterHints.finalConfirmation, setupFooterHints.back, setupFooterHints.cancel]);
|
|
@@ -64,6 +71,7 @@ function screenLines(input, width) {
|
|
|
64
71
|
'Confirm OpenCode setup',
|
|
65
72
|
...planLines(vm, width),
|
|
66
73
|
'WARNING: OpenCode provider config may be modified at the target path above. Existing config is backed up when planned.',
|
|
74
|
+
'After confirmation: run doctor, then restart OpenCode to reload MCP configuration.',
|
|
67
75
|
'Press Enter to confirm and apply OpenCode setup, or Esc/q to cancel without writing.',
|
|
68
76
|
], width, [setupFooterHints.confirmApply, setupFooterHints.back, setupFooterHints.cancel]);
|
|
69
77
|
case 'applying':
|
|
@@ -80,7 +88,7 @@ function screenLines(input, width) {
|
|
|
80
88
|
footer([setupFooterHints.close], width),
|
|
81
89
|
];
|
|
82
90
|
case 'cancelled':
|
|
83
|
-
return ['Setup cancelled', badgeLabels.cancelled, 'Setup was not completed.', 'No provider config was written.', footer([setupFooterHints.close], width)];
|
|
91
|
+
return ['Setup cancelled', badgeLabels.cancelled, 'Setup was not completed.', 'No provider config was written.', 'No agent seeding was performed.', footer([setupFooterHints.close], width)];
|
|
84
92
|
}
|
|
85
93
|
}
|
|
86
94
|
function planLines(vm, width) {
|
|
@@ -91,6 +99,9 @@ function planLines(vm, width) {
|
|
|
91
99
|
`Database path: ${compactPath(vm.databasePathLabel, width - 15)}`,
|
|
92
100
|
`OpenCode scope: ${vm.scopeLabel}`,
|
|
93
101
|
`Install mode: ${vm.installModeLabel}`,
|
|
102
|
+
`Provider installability: ${vm.providerInstallabilityLabel}`,
|
|
103
|
+
`Agent readiness: ${vm.agentReadinessLabel}`,
|
|
104
|
+
`Agent detail: ${vm.agentReadinessDetail}`,
|
|
94
105
|
`Target config: ${compactPath(vm.targetPathLabel, width - 15)}`,
|
|
95
106
|
vm.backupLabel,
|
|
96
107
|
...vm.plannedActions.map((action) => `Action: ${action.safetyBadge} ${action.label}${action.targetPathLabel === undefined ? '' : ` (${compactPath(action.targetPathLabel, width - 12)})`}`),
|
|
@@ -129,6 +140,7 @@ function resultLines(result, vm, width) {
|
|
|
129
140
|
`Target config: ${compactPath(result.targetPath, width - 15)}`,
|
|
130
141
|
result.backupPath === undefined ? 'Backup: none created' : `Backup: ${compactPath(result.backupPath, width - 8)}`,
|
|
131
142
|
...result.warnings.map((warning) => `Warning: ${warning}`),
|
|
143
|
+
...resultNextCommands(vm).map((command) => `Next: ${command}`),
|
|
132
144
|
footer([setupFooterHints.close], width),
|
|
133
145
|
];
|
|
134
146
|
if (result?.status === 'manual-required')
|
|
@@ -141,6 +153,14 @@ function resultLines(result, vm, width) {
|
|
|
141
153
|
];
|
|
142
154
|
return ['Setup result', 'No provider config write occurred.', footer([setupFooterHints.close], width)];
|
|
143
155
|
}
|
|
156
|
+
function resultNextCommands(vm) {
|
|
157
|
+
const filtered = vm.nextCommands.filter((command) => !/^vgx(?:ness)? setup apply\b/.test(command));
|
|
158
|
+
const withDoctor = filtered.some((command) => /\bdoctor\b/.test(command)) ? filtered : [...filtered, 'vgx doctor'];
|
|
159
|
+
return withDoctor.some((command) => /Restart OpenCode/i.test(command)) ? withDoctor : [...withDoctor, 'Restart OpenCode and verify the vgxness MCP server is visible.'];
|
|
160
|
+
}
|
|
161
|
+
function doctorCommand(vm) {
|
|
162
|
+
return vm.nextCommands.find((command) => /\bdoctor\b/.test(command)) ?? 'vgx doctor';
|
|
163
|
+
}
|
|
144
164
|
function footer(hints, width) {
|
|
145
165
|
return formatFooter(hints, width);
|
|
146
166
|
}
|