vgxness 1.1.0 → 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/README.md +1 -1
- 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/dist/mcp/doctor.js +10 -4
- package/dist/memory/sqlite/database.js +43 -51
- package/docs/cli.md +2 -1
- package/docs/funcionamiento-del-sistema.md +3 -4
- package/package.json +5 -7
package/README.md
CHANGED
|
@@ -58,7 +58,7 @@ npm run probe:bun
|
|
|
58
58
|
npm run probe:bun -- --yes # mutates dependencies only after explicit consent
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-
Runtime SQLite readiness is proven with `bun:sqlite`. `bun run verify:bun-sqlite` copies the source migrations into a temporary directory, applies them against a temporary database, checks foreign keys, busy timeout, FTS/search, transaction rollback, integrity, and cleanup.
|
|
61
|
+
Runtime SQLite readiness is proven only with `bun:sqlite`. `bun run verify:bun-sqlite` copies the source migrations into a temporary directory, applies them against a temporary database, checks foreign keys, busy timeout, FTS/search, transaction rollback, integrity, and cleanup. Node.js remains development/build/test tooling, but real local SQLite storage is supported through the Bun runtime path.
|
|
62
62
|
|
|
63
63
|
#### Bun package evidence
|
|
64
64
|
|
|
@@ -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
|
}
|
package/dist/mcp/doctor.js
CHANGED
|
@@ -66,7 +66,7 @@ async function runRealMcpSmoke(options) {
|
|
|
66
66
|
const client = new Client({ name: 'vgxness-doctor', version: '0.1.0' }, { capabilities: {} });
|
|
67
67
|
const invocation = resolveDoctorCliInvocation(options.databasePath);
|
|
68
68
|
const transport = new StdioClientTransport({
|
|
69
|
-
command:
|
|
69
|
+
command: invocation.command,
|
|
70
70
|
args: invocation.args,
|
|
71
71
|
cwd: options.cwd,
|
|
72
72
|
stderr: 'pipe',
|
|
@@ -173,11 +173,17 @@ function parseMcpToolResult(result) {
|
|
|
173
173
|
return { ok: false, error: { code: 'validation_failed', message: 'MCP response did not contain text JSON' } };
|
|
174
174
|
return JSON.parse(text);
|
|
175
175
|
}
|
|
176
|
-
export function resolveDoctorCliInvocation(databasePath, moduleUrl = import.meta.url) {
|
|
176
|
+
export function resolveDoctorCliInvocation(databasePath, moduleUrl = import.meta.url, options = {}) {
|
|
177
|
+
const runtime = options.runtime ?? currentRuntime();
|
|
177
178
|
const builtCliPath = fileURLToPath(new URL('../cli/index.js', moduleUrl));
|
|
178
179
|
if (existsSync(builtCliPath))
|
|
179
|
-
return { args: [builtCliPath, 'mcp', 'start', '--db', databasePath] };
|
|
180
|
-
|
|
180
|
+
return { command: process.execPath, args: [builtCliPath, 'mcp', 'start', '--db', databasePath] };
|
|
181
|
+
if (runtime === 'bun')
|
|
182
|
+
return { command: process.execPath, args: [cliSourcePath(moduleUrl), 'mcp', 'start', '--db', databasePath] };
|
|
183
|
+
return { command: process.execPath, args: ['--import', import.meta.resolve('tsx'), cliSourcePath(moduleUrl), 'mcp', 'start', '--db', databasePath] };
|
|
184
|
+
}
|
|
185
|
+
function currentRuntime() {
|
|
186
|
+
return typeof globalThis.Bun === 'undefined' ? 'node' : 'bun';
|
|
181
187
|
}
|
|
182
188
|
function cliSourcePath(moduleUrl) {
|
|
183
189
|
return fileURLToPath(new URL('../cli/index.ts', moduleUrl));
|
|
@@ -1,17 +1,10 @@
|
|
|
1
1
|
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
2
3
|
import { dirname, join } from 'node:path';
|
|
3
4
|
import { fileURLToPath } from 'node:url';
|
|
4
5
|
const migrationsDirectory = join(dirname(fileURLToPath(import.meta.url)), 'migrations');
|
|
5
6
|
export const sqliteBusyTimeoutMs = 5000;
|
|
6
|
-
const
|
|
7
|
-
const nativeBindingFailurePatterns = [
|
|
8
|
-
/better_sqlite3\.node/i,
|
|
9
|
-
/better-sqlite3/i,
|
|
10
|
-
/NODE_MODULE_VERSION/i,
|
|
11
|
-
/ERR_DLOPEN_FAILED/i,
|
|
12
|
-
/Could not locate the bindings file/i,
|
|
13
|
-
/compiled against a different Node\.js version/i,
|
|
14
|
-
];
|
|
7
|
+
const bunRequiredGuidance = 'Real local SQLite storage requires Bun. Use the installed Bun-backed vgxness/vgx runtime, or run Bun storage verification with bun run verify:bun-sqlite.';
|
|
15
8
|
export class MemoryDatabase {
|
|
16
9
|
connection;
|
|
17
10
|
constructor(connection) {
|
|
@@ -99,39 +92,37 @@ export function openMemoryDatabase(options) {
|
|
|
99
92
|
}
|
|
100
93
|
export function buildMemoryStoreUnavailableMessage(path, cause) {
|
|
101
94
|
const message = `Unable to open local memory store at ${path}`;
|
|
102
|
-
if (!
|
|
95
|
+
if (!isUnsupportedSqliteRuntimeError(cause))
|
|
103
96
|
return message;
|
|
104
|
-
return `${message}. ${
|
|
97
|
+
return `${message}. ${bunRequiredGuidance}`;
|
|
105
98
|
}
|
|
106
|
-
export function isLikelyBetterSqlite3NativeBindingFailure(cause) {
|
|
107
|
-
const values = collectDiagnosticValues(cause);
|
|
108
|
-
return values.some((value) => nativeBindingFailurePatterns.some((pattern) => pattern.test(value)));
|
|
109
|
-
}
|
|
110
|
-
const sqliteDriver = await resolveSqliteDriver();
|
|
111
99
|
function openSqliteConnection(options) {
|
|
112
|
-
return
|
|
100
|
+
return resolveSqliteDriver()(options);
|
|
113
101
|
}
|
|
114
|
-
|
|
102
|
+
function resolveSqliteDriver() {
|
|
115
103
|
if (isBunRuntime())
|
|
116
104
|
return resolveBunSqliteDriver();
|
|
117
|
-
|
|
105
|
+
throw createUnsupportedSqliteRuntimeError();
|
|
118
106
|
}
|
|
119
107
|
function isBunRuntime() {
|
|
120
108
|
return typeof globalThis.Bun !== 'undefined';
|
|
121
109
|
}
|
|
122
|
-
|
|
123
|
-
const
|
|
124
|
-
const
|
|
125
|
-
return (options) => new BetterSqlite3Database(options.path, { readonly: options.readonly ?? false });
|
|
126
|
-
}
|
|
127
|
-
async function resolveBunSqliteDriver() {
|
|
128
|
-
const bunSqliteSpecifier = 'bun:sqlite';
|
|
129
|
-
const module = (await import(bunSqliteSpecifier));
|
|
110
|
+
function resolveBunSqliteDriver() {
|
|
111
|
+
const require = createRequire(import.meta.url);
|
|
112
|
+
const module = require('bun:sqlite');
|
|
130
113
|
const BunDatabase = module.Database;
|
|
131
114
|
if (typeof BunDatabase !== 'function')
|
|
132
115
|
throw new Error('bun:sqlite Database constructor is unavailable');
|
|
133
116
|
return (options) => createBunSqliteConnection(new BunDatabase(options.path, { readonly: options.readonly ?? false, strict: true }));
|
|
134
117
|
}
|
|
118
|
+
function createUnsupportedSqliteRuntimeError() {
|
|
119
|
+
const error = new Error(bunRequiredGuidance);
|
|
120
|
+
error.name = 'UnsupportedSqliteRuntimeError';
|
|
121
|
+
return error;
|
|
122
|
+
}
|
|
123
|
+
function isUnsupportedSqliteRuntimeError(cause) {
|
|
124
|
+
return cause instanceof Error && cause.name === 'UnsupportedSqliteRuntimeError';
|
|
125
|
+
}
|
|
135
126
|
function createBunSqliteConnection(database) {
|
|
136
127
|
const record = database;
|
|
137
128
|
return {
|
|
@@ -139,7 +130,7 @@ function createBunSqliteConnection(database) {
|
|
|
139
130
|
const prepare = record.prepare;
|
|
140
131
|
if (typeof prepare !== 'function')
|
|
141
132
|
throw new Error('bun:sqlite prepare() is unavailable');
|
|
142
|
-
return prepare.call(database, sql);
|
|
133
|
+
return createBunSqliteStatement(prepare.call(database, sql));
|
|
143
134
|
},
|
|
144
135
|
exec(sql) {
|
|
145
136
|
const exec = record.exec;
|
|
@@ -174,6 +165,30 @@ function createBunSqliteConnection(database) {
|
|
|
174
165
|
},
|
|
175
166
|
};
|
|
176
167
|
}
|
|
168
|
+
function createBunSqliteStatement(statement) {
|
|
169
|
+
const record = statement;
|
|
170
|
+
return {
|
|
171
|
+
get(...params) {
|
|
172
|
+
const get = record.get;
|
|
173
|
+
if (typeof get !== 'function')
|
|
174
|
+
throw new Error('bun:sqlite statement.get() is unavailable');
|
|
175
|
+
const row = get.call(statement, ...params);
|
|
176
|
+
return row === null ? undefined : row;
|
|
177
|
+
},
|
|
178
|
+
all(...params) {
|
|
179
|
+
const all = record.all;
|
|
180
|
+
if (typeof all !== 'function')
|
|
181
|
+
throw new Error('bun:sqlite statement.all() is unavailable');
|
|
182
|
+
return all.call(statement, ...params);
|
|
183
|
+
},
|
|
184
|
+
run(...params) {
|
|
185
|
+
const run = record.run;
|
|
186
|
+
if (typeof run !== 'function')
|
|
187
|
+
throw new Error('bun:sqlite statement.run() is unavailable');
|
|
188
|
+
return run.call(statement, ...params);
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
}
|
|
177
192
|
function runBunPragma(database, statement) {
|
|
178
193
|
const normalizedStatement = statement.trim();
|
|
179
194
|
if (isPragmaSetter(normalizedStatement)) {
|
|
@@ -211,26 +226,3 @@ function migrationFailure(message, cause) {
|
|
|
211
226
|
error.cause = cause;
|
|
212
227
|
return { ok: false, error };
|
|
213
228
|
}
|
|
214
|
-
function collectDiagnosticValues(value, seen = new Set()) {
|
|
215
|
-
if (value === undefined || value === null)
|
|
216
|
-
return [];
|
|
217
|
-
if (typeof value === 'string')
|
|
218
|
-
return [value];
|
|
219
|
-
if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint' || typeof value === 'symbol')
|
|
220
|
-
return [String(value)];
|
|
221
|
-
if (typeof value !== 'object')
|
|
222
|
-
return [];
|
|
223
|
-
if (seen.has(value))
|
|
224
|
-
return [];
|
|
225
|
-
seen.add(value);
|
|
226
|
-
const diagnosticValues = [];
|
|
227
|
-
const record = value;
|
|
228
|
-
for (const key of ['code', 'message', 'stack', 'name']) {
|
|
229
|
-
const property = record[key];
|
|
230
|
-
if (property !== undefined)
|
|
231
|
-
diagnosticValues.push(...collectDiagnosticValues(property, seen));
|
|
232
|
-
}
|
|
233
|
-
if (record.cause !== undefined)
|
|
234
|
-
diagnosticValues.push(...collectDiagnosticValues(record.cause, seen));
|
|
235
|
-
return diagnosticValues;
|
|
236
|
-
}
|
package/docs/cli.md
CHANGED
|
@@ -77,12 +77,13 @@ bun install --frozen-lockfile
|
|
|
77
77
|
bun run check:bun-lock
|
|
78
78
|
bun run verify:typecheck
|
|
79
79
|
bun run verify:test
|
|
80
|
+
bun run verify:test:bun-storage
|
|
80
81
|
bun run verify:bun-sqlite
|
|
81
82
|
bun run verify:package
|
|
82
83
|
bun run package:bun:evidence -- --require-pass
|
|
83
84
|
```
|
|
84
85
|
|
|
85
|
-
`bun run verify:bun-sqlite` is the SQLite runtime release gate. It uses `bun:sqlite` against a temporary database, copies/applies the source migrations, and checks foreign keys, busy timeout, FTS/search, transaction rollback, integrity, and cleanup. `bun run verify:
|
|
86
|
+
`bun run verify:bun-sqlite` is the SQLite runtime release gate. It uses `bun:sqlite` against a temporary database, copies/applies the source migrations, and checks foreign keys, busy timeout, FTS/search, transaction rollback, integrity, and cleanup. `bun run verify:test:bun-storage` covers storage-backed tests under Bun. Node.js remains development/build/test tooling, but real local SQLite storage is supported through the Bun runtime path.
|
|
86
87
|
|
|
87
88
|
Keep lockfile changes intentional: when `package.json` dependency specifiers change, refresh and review the root `bun.lock` diff. The read-only drift check does not run `bun install` and does not mutate `node_modules`:
|
|
88
89
|
|
|
@@ -43,13 +43,12 @@ Esa capa no reemplaza a OpenCode, Claude Code o futuros adapters. Los coordina.
|
|
|
43
43
|
|
|
44
44
|
## Como esta construido
|
|
45
45
|
|
|
46
|
-
El proyecto esta escrito en TypeScript
|
|
46
|
+
El proyecto esta escrito en TypeScript. Node.js 22 o superior sigue siendo tooling de desarrollo/build/tests; la persistencia local real en SQLite corre por el runtime Bun soportado.
|
|
47
47
|
|
|
48
48
|
Dependencias principales:
|
|
49
49
|
|
|
50
50
|
| Dependencia | Uso |
|
|
51
51
|
|---|---|
|
|
52
|
-
| `better-sqlite3` | Persistencia local en SQLite. |
|
|
53
52
|
| `@modelcontextprotocol/sdk` | Servidor MCP por stdio. |
|
|
54
53
|
| `zod` | Validacion de schemas de entrada. |
|
|
55
54
|
| `tsx` | Ejecucion TypeScript en desarrollo. |
|
|
@@ -135,9 +134,9 @@ Archivo clave:
|
|
|
135
134
|
src/memory/sqlite/database.ts
|
|
136
135
|
```
|
|
137
136
|
|
|
138
|
-
Al abrir la base, el sistema:
|
|
137
|
+
Al abrir la base con el runtime Bun, el sistema:
|
|
139
138
|
|
|
140
|
-
1. Crea una conexion
|
|
139
|
+
1. Crea una conexion SQLite usando `bun:sqlite`.
|
|
141
140
|
2. Activa `foreign_keys`.
|
|
142
141
|
3. Configura `busy_timeout`.
|
|
143
142
|
4. Aplica migraciones SQL si la base no esta en modo readonly.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vgxness",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "CLI and MCP control plane for guided AI-agent workflows, SDD, memory, and OpenCode setup.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"repository": {
|
|
@@ -22,12 +22,12 @@
|
|
|
22
22
|
"scripts": {
|
|
23
23
|
"cli": "tsx src/cli/index.ts",
|
|
24
24
|
"build": "tsc -p tsconfig.build.json && node scripts/copy-migrations.mjs",
|
|
25
|
-
"verify": "bun run verify:typecheck && bun run verify:test && bun run verify:bun-sqlite && bun run verify:package",
|
|
25
|
+
"verify": "bun run verify:typecheck && bun run verify:test && bun run verify:test:bun-storage && bun run verify:bun-sqlite && bun run verify:package",
|
|
26
26
|
"verify:typecheck": "tsc --noEmit",
|
|
27
|
-
"verify:test": "node
|
|
27
|
+
"verify:test": "node scripts/run-node-tests.mjs",
|
|
28
|
+
"verify:test:bun-storage": "node scripts/run-bun-storage-tests.mjs",
|
|
28
29
|
"verify:package": "bun run package:bun:evidence",
|
|
29
30
|
"verify:bun-sqlite": "bun scripts/probe-bun-sqlite.mjs",
|
|
30
|
-
"verify:node-sqlite": "node scripts/probe-native-sqlite.mjs",
|
|
31
31
|
"ci:bun": "bun run check:bun-lock && bun run verify",
|
|
32
32
|
"check:bun-lock": "node scripts/check-bun-lock.mjs",
|
|
33
33
|
"check:vgxcode:bun-lock": "node scripts/check-bun-lock.mjs --package-json packages/vgxcode/package.json --bun-lock packages/vgxcode/bun.lock",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"probe:bun-runtime": "node scripts/probe-bun-runtime-cli-mcp.mjs",
|
|
37
37
|
"verify:bun-runtime": "node scripts/probe-bun-runtime-cli-mcp.mjs --require-pass",
|
|
38
38
|
"package:bun:evidence": "node scripts/probe-bun-package-evidence.mjs",
|
|
39
|
-
"test": "node
|
|
39
|
+
"test": "node scripts/run-node-tests.mjs",
|
|
40
40
|
"typecheck": "tsc --noEmit"
|
|
41
41
|
},
|
|
42
42
|
"bin": {
|
|
@@ -58,10 +58,8 @@
|
|
|
58
58
|
"zod": "^4.4.3"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
|
-
"@types/better-sqlite3": "^7.6.13",
|
|
62
61
|
"@types/node": "^22.15.18",
|
|
63
62
|
"@types/react": "^19.2.15",
|
|
64
|
-
"better-sqlite3": "^12.10.0",
|
|
65
63
|
"tsx": "^4.19.4",
|
|
66
64
|
"typescript": "^5.8.3"
|
|
67
65
|
},
|