vgxness 1.5.1 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +23 -2
  2. package/dist/agents/agent-seed-service.js +10 -0
  3. package/dist/agents/canonical-agent-manifest.js +177 -0
  4. package/dist/agents/canonical-agent-projection.js +164 -0
  5. package/dist/agents/renderers/claude-renderer.js +30 -52
  6. package/dist/cli/bun-bin.js +6 -0
  7. package/dist/cli/cli-flags.js +1 -1
  8. package/dist/cli/cli-help.js +7 -4
  9. package/dist/cli/commands/agent-skill-dispatcher.js +6 -5
  10. package/dist/cli/commands/interactive-entrypoint-dispatcher.js +2 -2
  11. package/dist/cli/commands/mcp-dispatcher.js +75 -3
  12. package/dist/cli/commands/setup-dispatcher.js +9 -0
  13. package/dist/cli/index.js +1 -1
  14. package/dist/cli/tui/main-menu/main-menu-read-model.js +41 -44
  15. package/dist/cli/tui/main-menu/main-menu-render-shape.js +15 -15
  16. package/dist/cli/tui/opentui/main-menu/screen.js +39 -41
  17. package/dist/cli/tui/opentui/main-menu/smoke.js +1 -1
  18. package/dist/cli/tui/opentui/main-menu/view.js +1 -1
  19. package/dist/cli/tui/setup/setup-tui-read-model.js +15 -12
  20. package/dist/governance/governance-report-builder.js +45 -26
  21. package/dist/mcp/claude-code-agent-config.js +95 -0
  22. package/dist/mcp/claude-code-cli.js +71 -0
  23. package/dist/mcp/claude-code-config.js +84 -0
  24. package/dist/mcp/claude-code-project-memory.js +127 -0
  25. package/dist/mcp/claude-code-scope.js +18 -0
  26. package/dist/mcp/client-install-claude-code-contract.js +114 -0
  27. package/dist/mcp/client-install-claude-code.js +136 -0
  28. package/dist/mcp/index.js +8 -0
  29. package/dist/mcp/opencode-default-agent-config.js +7 -113
  30. package/dist/mcp/provider-canonical-agent-manifest.js +39 -0
  31. package/dist/mcp/provider-change-plan.js +109 -1
  32. package/dist/mcp/provider-doctor.js +105 -1
  33. package/dist/mcp/provider-health-types.js +4 -0
  34. package/dist/mcp/provider-status.js +159 -3
  35. package/dist/mcp/schema.js +6 -5
  36. package/dist/mcp/validation.js +1 -1
  37. package/dist/memory/memory-service.js +4 -0
  38. package/dist/sdd/sdd-workflow-service.js +129 -59
  39. package/dist/setup/providers/claude-setup-adapter.js +13 -8
  40. package/dist/setup/setup-plan.js +60 -1
  41. package/docs/architecture.md +55 -113
  42. package/docs/cli.md +90 -2
  43. package/docs/code-runtime.md +218 -0
  44. package/docs/contributing.md +120 -0
  45. package/docs/glossary.md +211 -0
  46. package/docs/mcp.md +144 -0
  47. package/docs/prd.md +23 -26
  48. package/docs/providers.md +150 -0
  49. package/docs/roadmap.md +88 -0
  50. package/docs/safety.md +147 -0
  51. package/docs/storage.md +93 -0
  52. package/package.json +1 -1
  53. package/docs/funcionamiento-del-sistema.md +0 -865
  54. package/docs/harness-gap-analysis.md +0 -243
  55. package/docs/vgxcode.md +0 -87
  56. package/docs/vgxness-code.md +0 -48
@@ -266,13 +266,14 @@ export function runAgentCommand(command, parsed, database, environment) {
266
266
  if (command === 'seed') {
267
267
  if (parsed.positionals[2] !== 'load')
268
268
  return usageFailure('agents seed requires load');
269
- const file = requiredFlag(parsed.flags, 'file');
270
- if (!file.ok)
271
- return resultFailure(file);
272
- const manifest = readJsonFile(file.value, environment);
269
+ const file = optionalStringFlag(parsed.flags, 'file');
270
+ const seed = new AgentSeedService(database);
271
+ if (file === undefined)
272
+ return jsonResult(seed.loadBuiltInDefaults());
273
+ const manifest = readJsonFile(file, environment);
273
274
  if (!manifest.ok)
274
275
  return resultFailure(manifest);
275
- return jsonResult(new AgentSeedService(database).loadManifest(manifest.value));
276
+ return jsonResult(seed.loadManifest(manifest.value));
276
277
  }
277
278
  if (command === 'register') {
278
279
  const payload = registerPayload(parsed, environment, false);
@@ -13,7 +13,7 @@ import { canRunInteractiveTui } from '../tui/terminal-capabilities.js';
13
13
  import { runSetupTuiCommand } from './setup-dispatcher.js';
14
14
  function defaultNoTtyGuidance() {
15
15
  return ([
16
- 'VGXNESS main menu requires a TTY; no provider config was written.',
16
+ 'VGXNESS Dashboard requires a TTY; no provider config was written.',
17
17
  'Next: rerun `vgxness` in an interactive terminal, run `vgxness init --plan`, or run `vgxness setup plan` for read-only installation guidance.',
18
18
  ].join('\n') + '\n');
19
19
  }
@@ -25,7 +25,7 @@ function guidanceForMainMenuResult(result) {
25
25
  if (result.type === 'show-advanced-cli')
26
26
  return ['Advanced CLI guidance:', '- Installation preview: `vgxness init --plan` or `vgxness setup plan`', '- Setup status: `vgxness setup status --project <name>`', '- SDD status: `vgxness sdd status --project <name> --change <change>`', '- No provider config was written.'].join('\n') + '\n';
27
27
  if (result.type === 'exit')
28
- return 'Exited main menu; no provider config was written.\n';
28
+ return 'Exited dashboard; no provider config was written.\n';
29
29
  return undefined;
30
30
  }
31
31
  async function renderDefaultMainMenu(environment, onResult) {
@@ -1,17 +1,21 @@
1
1
  import { installOpenCodeMcpClient } from '../../mcp/client-install-opencode.js';
2
+ import { installClaudeCodeMcpClient } from '../../mcp/client-install-claude-code.js';
3
+ import { planClaudeCodeMcpInstall } from '../../mcp/client-install-claude-code-contract.js';
2
4
  import { planOpenCodeMcpInstall } from '../../mcp/client-install-opencode-contract.js';
3
5
  import { createMcpClientSetupPreview, isMcpClientSetupProvider } from '../../mcp/client-setup-preview.js';
4
6
  import { createMcpDoctorReport } from '../../mcp/doctor.js';
5
7
  import { createOpenCodeMcpVisibilityReport } from '../../mcp/opencode-visibility.js';
8
+ import { resolveClaudeCodeScope } from '../../mcp/claude-code-scope.js';
9
+ import { RunService } from '../../runs/run-service.js';
6
10
  import { databasePathFor, databasePathSelectionFor, opencodeInstallScopeFlag, optionalNumberFlag, optionalStringFlag } from '../cli-flags.js';
7
11
  import { usageFailure, validationFailure } from '../cli-help.js';
8
- import { jsonResult, resultFailure } from '../cli-helpers.js';
12
+ import { jsonResult, openCliDatabase, resultFailure } from '../cli-helpers.js';
9
13
  import { renderDoctorReport } from '../doctor-renderer.js';
10
14
  export function runMcpInstallCommand(parsed, environment) {
11
15
  return (async () => {
12
16
  const client = parsed.positionals[2];
13
- if (client !== 'opencode')
14
- return usageFailure('mcp install requires client opencode');
17
+ if (client !== 'opencode' && client !== 'claude')
18
+ return usageFailure('mcp install requires client opencode or claude');
15
19
  const databasePath = databasePathSelectionFor(parsed.flags, environment);
16
20
  if (!databasePath.ok)
17
21
  return resultFailure(databasePath);
@@ -20,6 +24,39 @@ export function runMcpInstallCommand(parsed, environment) {
20
24
  return resultFailure(scope);
21
25
  const mcpOnly = parsed.flags['mcp-only'] === true || parsed.flags['no-agents'] === true;
22
26
  const overwriteVgxness = parsed.flags['overwrite-vgxness'] === true || parsed.flags.reinstall === true;
27
+ if (client === 'claude') {
28
+ const claudeScope = claudeInstallScopeFlag(parsed.flags);
29
+ if (!claudeScope.ok)
30
+ return resultFailure(claudeScope);
31
+ if (parsed.flags.plan === true) {
32
+ return jsonResult({ ok: true, value: planClaudeCodeMcpInstall({ cwd: environment.cwd, databasePath: databasePath.value.path, databasePathSource: databasePath.value.source, overwriteVgxness, scope: claudeScope.value }) });
33
+ }
34
+ if (parsed.flags.yes !== true) {
35
+ return resultFailure({ ok: false, error: { code: 'validation_failed', message: 'confirmation_required: `mcp install claude` requires explicit --yes before any project config write.' } });
36
+ }
37
+ if (optionalStringFlag(parsed.flags, 'run-id') === undefined) {
38
+ return resultFailure({ ok: false, error: { code: 'validation_failed', message: 'preflight_failed: Claude Code provider config writes require VGXNESS preflight; pass --run-id for an existing VGXNESS run before using --yes.' } });
39
+ }
40
+ const opened = openCliDatabase(databasePath.value.path);
41
+ if (!opened.ok)
42
+ return resultFailure(opened);
43
+ try {
44
+ const preflight = createClaudeCodeInstallPreflight(parsed, opened.value, environment);
45
+ const result = await installClaudeCodeMcpClient({
46
+ cwd: environment.cwd,
47
+ databasePath: databasePath.value.path,
48
+ databasePathSource: databasePath.value.source,
49
+ confirmed: true,
50
+ overwriteVgxness,
51
+ scope: claudeScope.value,
52
+ preflight,
53
+ });
54
+ return result.status === 'installed' ? jsonResult({ ok: true, value: result }) : resultFailure({ ok: false, error: { code: 'validation_failed', message: `${result.reason}: ${result.message}` } });
55
+ }
56
+ finally {
57
+ opened.value.close();
58
+ }
59
+ }
23
60
  if (parsed.flags.plan === true) {
24
61
  return jsonResult({
25
62
  ok: true,
@@ -55,6 +92,41 @@ export function runMcpInstallCommand(parsed, environment) {
55
92
  });
56
93
  })();
57
94
  }
95
+ function claudeInstallScopeFlag(flags) {
96
+ const value = optionalStringFlag(flags, 'scope');
97
+ const resolved = resolveClaudeCodeScope(value);
98
+ return resolved.ok ? { ok: true, value: resolved.value.canonical } : validationFailure(resolved.error.message);
99
+ }
100
+ function createClaudeCodeInstallPreflight(parsed, database, environment) {
101
+ return (request) => {
102
+ const runId = optionalStringFlag(parsed.flags, 'run-id');
103
+ if (runId === undefined) {
104
+ return validationFailure('Claude Code provider config writes require VGXNESS preflight; pass --run-id for an existing VGXNESS run before using --yes.');
105
+ }
106
+ const service = new RunService(database);
107
+ const details = service.getRun(runId);
108
+ if (!details.ok)
109
+ return details;
110
+ const phase = optionalStringFlag(parsed.flags, 'phase') ?? details.value.phase;
111
+ const agentId = optionalStringFlag(parsed.flags, 'agent-id') ?? details.value.selectedAgentId;
112
+ const preflight = service.preflightOperation({
113
+ runId,
114
+ category: request.category,
115
+ operation: request.operation,
116
+ workspaceRoot: environment.cwd,
117
+ targetPath: request.targetPath,
118
+ providerToolName: request.providerToolName,
119
+ phase,
120
+ agentId,
121
+ });
122
+ if (!preflight.ok)
123
+ return preflight;
124
+ if (preflight.value.outcome !== 'allowed') {
125
+ return validationFailure(`VGXNESS preflight ${preflight.value.outcome} for Claude Code provider config write: ${request.targetPath}`);
126
+ }
127
+ return { ok: true, value: preflight.value };
128
+ };
129
+ }
58
130
  export function runMcpSetupCommand(parsed, environment) {
59
131
  if (parsed.flags.preview !== true)
60
132
  return usageFailure('mcp setup requires --preview; provider config installation is not supported');
@@ -135,6 +135,15 @@ export async function applySetupPlanInput(input, environment) {
135
135
  return validationFailure(`Setup plan is ${plan.value.status}; resolve conflicts before applying.`);
136
136
  if (input.provider === 'none')
137
137
  return { ok: true, value: { status: 'manual-required', message: 'Provider is none; there is no config to apply.', plan: plan.value } };
138
+ if (input.provider === 'claude')
139
+ return {
140
+ ok: true,
141
+ value: {
142
+ status: 'manual-required',
143
+ message: 'Claude setup apply is intentionally non-mutating here; use vgxness mcp install claude --scope project --yes --run-id <id> for guarded Claude writes.',
144
+ plan: plan.value,
145
+ },
146
+ };
138
147
  const result = await installOpenCodeMcpClient({
139
148
  cwd: environment.cwd,
140
149
  databasePath: plan.value.db.path,
package/dist/cli/index.js CHANGED
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { startVgxMcpStdioServer } from '../mcp/stdio-server.js';
3
2
  import { dispatchCliAsync } from './dispatcher.js';
4
3
  import { resolveMcpStartDatabasePath } from './mcp-start-path.js';
5
4
  void main();
@@ -12,6 +11,7 @@ async function main() {
12
11
  process.exitCode = 1;
13
12
  return;
14
13
  }
14
+ const { startVgxMcpStdioServer } = await import('../mcp/stdio-server.js');
15
15
  await startVgxMcpStdioServer({ databasePath: databasePath.value });
16
16
  return;
17
17
  }
@@ -1,78 +1,75 @@
1
1
  import { tuiBadges } from '../visual/badges.js';
2
- import { formatTuiFooter } from '../visual/footer.js';
3
2
  import { mainMenuOptionIds } from './main-menu-state.js';
3
+ const dashboardHero = {
4
+ title: 'VGXNESS ONLINE',
5
+ subtitle: 'vgxness — local-first SDD control',
6
+ compactTitle: 'VGXNESS Dashboard',
7
+ asciiLines: ['██╗ ██╗ ██████╗ ██╗ ██╗███╗ ██╗███████╗███████╗███████╗', '██║ ██║██╔════╝ ╚██╗██╔╝████╗ ██║██╔════╝██╔════╝██╔════╝', '██║ ██║██║ ███╗ ╚███╔╝ ██╔██╗ ██║█████╗ ███████╗███████╗', '╚██╗ ██╔╝██║ ██║ ██╔██╗ ██║╚██╗██║██╔══╝ ╚════██║╚════██║', ' ╚████╔╝ ╚██████╔╝██╔╝ ██╗██║ ╚████║███████╗███████║███████║'],
8
+ badges: [tuiBadges.readOnly, tuiBadges.noProviderWrites],
9
+ };
10
+ const statCards = [
11
+ { label: 'actions', value: '5', badge: '', description: 'safe routes' },
12
+ { label: 'provider', value: '1', badge: '', description: 'OpenCode' },
13
+ { label: 'writes', value: '0', badge: '', description: 'dashboard render' },
14
+ { label: 'SDD', value: 'guided', badge: '', description: 'MCP flow' },
15
+ ];
4
16
  const statusSnapshotLines = [
5
- 'Primary provider: OpenCode.',
6
- 'Manual checks: vgxness setup status; vgxness mcp doctor opencode.',
7
- 'SDD cue: vgxness sdd next --project <project> --change <change>.',
8
- 'No checks run here; copy and run commands explicitly when needed.',
17
+ 'OpenCode primary; dashboard does not call providers.',
18
+ 'Advanced checks stay explicit: setup status, mcp doctor opencode.',
9
19
  ];
10
20
  const optionCopy = {
11
21
  setup: {
12
- label: 'Installation',
13
- description: 'Start guided installation for memory and OpenCode integration safely.',
22
+ label: 'Setup OpenCode',
23
+ description: 'guided install; confirm before writes',
14
24
  badges: [tuiBadges.requiresConfirmation],
15
- detailTitle: 'Installation',
16
- detailLines: [
17
- 'Opens the guided Installation TUI.',
18
- 'Plan/review screens are read-only.',
19
- 'Provider writes require final confirmation; OpenCode is the only automatic provider with explicit consent.',
20
- ],
25
+ detailTitle: 'Setup OpenCode',
26
+ detailLines: ['Guided install opens in review mode; provider writes require final confirmation.'],
21
27
  },
22
28
  doctor: {
23
29
  label: 'Doctor / recovery',
24
- description: 'See health-check and recovery commands.',
30
+ description: 'health and repair commands',
25
31
  badges: [tuiBadges.previewOnly],
26
- detailTitle: 'Doctor guidance',
27
- detailLines: ['Shows commands for status and recovery.', 'This menu only prints guidance for doctor/recovery; it does not call providers.'],
32
+ detailTitle: 'Doctor / recovery',
33
+ detailLines: ['Shows status and recovery guidance only; it does not call providers.'],
28
34
  },
29
35
  sdd: {
30
- label: 'SDD / workflow guidance',
31
- description: 'Review safe workflow entry points and artifact guidance.',
36
+ label: 'SDD workflow',
37
+ description: 'artifacts, phases, next step',
32
38
  badges: [tuiBadges.previewOnly],
33
- detailTitle: 'SDD guidance',
34
- detailLines: ['Use OpenCode conversation plus VGXNESS MCP for daily SDD progression.', 'Terminal commands are shown only as advanced/manual references.'],
39
+ detailTitle: 'SDD workflow',
40
+ detailLines: ['Use OpenCode conversation plus VGXNESS MCP for daily progression.'],
35
41
  },
36
42
  'advanced-cli': {
37
43
  label: 'Advanced CLI',
38
- description: 'Show common explicit commands for experienced users.',
44
+ description: 'manual commands, no automation',
39
45
  badges: [tuiBadges.manual],
40
46
  detailTitle: 'Advanced CLI',
41
- detailLines: ['Commands remain explicit and user-triggered.', 'No new public product commands are introduced by this menu.'],
47
+ detailLines: ['Prints explicit command references; no provider config is written.'],
42
48
  },
43
49
  exit: {
44
- label: 'Exit',
45
- description: 'Close without changing files.',
50
+ label: 'Quit',
51
+ description: 'leave cleanly',
46
52
  badges: [tuiBadges.exit, tuiBadges.noProviderWrites],
47
- detailTitle: 'Exit',
48
- detailLines: ['Close the main menu.', 'No provider config was written.'],
53
+ detailTitle: 'Quit',
54
+ detailLines: ['Close the dashboard; no provider config was written.'],
49
55
  },
50
56
  };
51
57
  export function buildMainMenuViewModel(state) {
52
58
  const options = mainMenuOptionIds.map((id) => ({ id, ...optionCopy[id], focused: id === state.focusedOptionId }));
53
59
  const focused = optionCopy[state.focusedOptionId];
54
- const footerHints = [
55
- { key: '↑/↓ j/k', label: 'move' },
56
- { key: 'Enter', label: 'open focused item' },
57
- { key: '?/h', label: 'help' },
58
- { key: 'q/Esc', label: 'quit without writes', priority: 'critical' },
59
- ];
60
60
  return {
61
- title: 'VGXNESS Main Menu',
62
- subtitle: 'Keyboard-first local control plane',
63
- contextLines: ['Choose where to go next. Installation is first; the menu itself is read-only and does not write provider config.'],
61
+ title: dashboardHero.title,
62
+ subtitle: dashboardHero.subtitle,
63
+ hero: dashboardHero,
64
+ statCards,
65
+ contextLines: ['Pick a route. Dashboard render stays passive/read-only.'],
64
66
  options,
65
67
  detail: { title: focused.detailTitle, lines: focused.detailLines, badges: focused.badges },
66
- statusSnapshot: { title: 'Status snapshot', lines: statusSnapshotLines, badges: [tuiBadges.readOnly, tuiBadges.noProviderWrites] },
67
- safetyLines: [
68
- 'Previews are read-only.',
69
- 'Status snapshot is static guidance; no SQLite, doctor/status checks, provider calls, or provider config writes run from the main menu.',
70
- 'Installation/provider writes require final confirmation.',
71
- 'OpenCode is the only automatic provider path and still requires explicit consent.',
72
- ],
68
+ statusSnapshot: { title: 'Signal', lines: statusSnapshotLines, badges: [tuiBadges.readOnly] },
69
+ safetyLines: ['read-only • no provider writes • explicit confirmation'],
73
70
  helpLines: state.helpVisible
74
- ? ['Keys: ↑/↓ or j/k move, Enter open, ?/h toggle help, q/Esc quit.', 'Doctor, SDD, and Advanced CLI entries print guidance only.']
71
+ ? ['Keys: ↑/↓ or j/k move, Enter open, ?/h help, q/Esc quit.', 'Guidance entries print commands only; setup writes still require confirmation.']
75
72
  : [],
76
- footer: formatTuiFooter(footerHints, state.viewport.width, state.viewport.mode),
73
+ footer: state.viewport.mode === 'narrow' ? 'no provider writes • explicit confirmation' : 'j/k • enter • q • no provider writes • explicit confirmation',
77
74
  };
78
75
  }
@@ -3,20 +3,22 @@ import { buildMainMenuViewModel } from './main-menu-read-model.js';
3
3
  export function renderMainMenuShape(input) {
4
4
  const state = input.width === undefined ? input.state : { ...input.state, viewport: { ...input.state.viewport, width: input.width } };
5
5
  const vm = buildMainMenuViewModel(state);
6
+ const showAscii = state.viewport.mode === 'wide';
7
+ const stats = vm.statCards.map((card) => `${card.value} ${card.label}`).join(' ');
6
8
  const lines = [
7
- vm.title,
8
- vm.subtitle,
9
+ `Hero: ${state.viewport.mode === 'narrow' ? vm.hero.compactTitle : vm.hero.title} ${vm.hero.badges.join(' ')}`,
10
+ ...(showAscii ? vm.hero.asciiLines : []),
11
+ vm.hero.subtitle,
9
12
  ...vm.contextLines,
10
- 'Menu',
13
+ `Stats: ${stats}`,
14
+ 'Actions',
11
15
  ...vm.options.map(choiceLine),
12
- `Detail: ${vm.detail.title}${vm.detail.badges.length === 0 ? '' : ` ${vm.detail.badges.join(' ')}`}`,
16
+ `Focused detail: ${vm.detail.title}${vm.detail.badges.length === 0 ? '' : ` ${vm.detail.badges.join(' ')}`}`,
13
17
  ...vm.detail.lines,
14
- `${vm.statusSnapshot.title}${vm.statusSnapshot.badges.length === 0 ? '' : ` ${vm.statusSnapshot.badges.join(' ')}`}`,
18
+ `${vm.statusSnapshot.title}: ${vm.statusSnapshot.badges.join(' ')}`,
15
19
  ...vm.statusSnapshot.lines,
16
- 'Safety',
17
- ...vm.safetyLines,
18
- ...vm.helpLines.map((line) => `Help: ${line}`),
19
20
  `Footer: ${vm.footer}`,
21
+ ...vm.helpLines.map((line) => `Help: ${line}`),
20
22
  ];
21
23
  return lines.map((line) => clampLine(line, state.viewport.width)).join('\n');
22
24
  }
@@ -25,14 +27,12 @@ function clampLine(line, width) {
25
27
  return line;
26
28
  const protectedPhrases = [
27
29
  'final confirmation',
28
- 'read-only',
29
- 'Requires confirmation',
30
- 'explicit consent',
31
30
  'No provider writes',
31
+ 'no provider writes',
32
+ 'explicit confirmation',
33
+ 'Requires confirmation',
34
+ 'read-only',
32
35
  'quit without writes',
33
- 'vgxness setup status',
34
- 'vgxness mcp doctor opencode',
35
- 'no SQLite',
36
36
  'provider config writes',
37
37
  ];
38
38
  const phrase = protectedPhrases.find((candidate) => line.includes(candidate));
@@ -41,6 +41,6 @@ function clampLine(line, width) {
41
41
  return `${line.slice(0, Math.max(0, width - 1)).trim()}…`;
42
42
  }
43
43
  function choiceLine(choice) {
44
- const badges = formatBadges([...(choice.focused === true ? [tuiBadges.focused] : []), ...choice.badges]);
44
+ const badges = formatBadges(choice.focused === true ? [tuiBadges.focused] : []);
45
45
  return `${choice.focused === true ? '›' : ' '} ${choice.label}${badges.length === 0 ? '' : ` ${badges}`} — ${choice.description}`;
46
46
  }
@@ -12,43 +12,7 @@ export function createOpenTuiMainMenuScreen(state) {
12
12
  padding: 1,
13
13
  gap: 1,
14
14
  backgroundColor: '#080B12',
15
- }, Box({
16
- border: true,
17
- borderStyle: 'rounded',
18
- borderColor: '#7DD3FC',
19
- paddingX: 1,
20
- title: ' VGXNESS ',
21
- }, Text({ content: `${vm.title}\n${vm.subtitle} ${tuiBadges.readOnly}\n${vm.contextLines.join('\n')}`, fg: '#E0F2FE', wrapMode: 'word' })), Box({
22
- border: true,
23
- borderStyle: 'rounded',
24
- borderColor: '#14B8A6',
25
- paddingX: 1,
26
- title: ` ${vm.statusSnapshot.title} ${vm.statusSnapshot.badges.join(' ')} `,
27
- }, Text({ content: vm.statusSnapshot.lines.join('\n'), fg: '#CCFBF1', wrapMode: 'word' })), Box({
28
- flexDirection: state.viewport.mode === 'narrow' ? 'column' : 'row',
29
- flexGrow: 1,
30
- gap: 1,
31
- }, Box({
32
- border: true,
33
- borderStyle: 'rounded',
34
- borderColor: '#A78BFA',
35
- flexGrow: 1,
36
- padding: 1,
37
- title: ' Menu ',
38
- }, Text({ content: formatMainMenuOptions(vm.options), fg: '#F8FAFC', wrapMode: 'word' })), Box({
39
- border: true,
40
- borderStyle: 'rounded',
41
- borderColor: '#22C55E',
42
- flexGrow: 1,
43
- padding: 1,
44
- title: ` ${vm.detail.title}${vm.detail.badges.length === 0 ? '' : ` ${vm.detail.badges.join(' ')}`} `,
45
- }, Text({ content: vm.detail.lines.join('\n'), fg: '#DCFCE7', wrapMode: 'word' }))), Box({
46
- border: true,
47
- borderStyle: 'rounded',
48
- borderColor: '#F59E0B',
49
- paddingX: 1,
50
- title: ` Safety ${tuiBadges.noProviderWrites} `,
51
- }, Text({ content: vm.safetyLines.join('\n'), fg: '#FEF3C7', wrapMode: 'word' })), ...(vm.helpLines.length === 0
15
+ }, renderHeroPanel(vm, state), renderStatsStrip(vm), renderActionDetailPanel(vm, state), ...(vm.helpLines.length === 0
52
16
  ? []
53
17
  : [
54
18
  Box({
@@ -58,11 +22,45 @@ export function createOpenTuiMainMenuScreen(state) {
58
22
  paddingX: 1,
59
23
  title: ` Help ${tuiBadges.readOnly} `,
60
24
  }, Text({ content: vm.helpLines.join('\n'), fg: '#E0F2FE', wrapMode: 'word' })),
61
- ]), Box({
25
+ ]), renderFooterStrip(vm));
26
+ }
27
+ function renderHeroPanel(vm, state) {
28
+ const title = state.viewport.mode === 'narrow' ? vm.hero.compactTitle : vm.hero.title;
29
+ const ascii = state.viewport.mode === 'wide' ? `${vm.hero.asciiLines.join('\n')}\n` : '';
30
+ return Box({
62
31
  border: true,
63
32
  borderStyle: 'rounded',
64
- borderColor: '#475569',
33
+ borderColor: '#7DD3FC',
65
34
  paddingX: 1,
66
- title: ' Shortcuts ',
67
- }, Text({ content: vm.footer, fg: '#CBD5E1', wrapMode: 'word' })));
35
+ title: ` ${title} READ-ONLY `,
36
+ }, Text({ content: `${ascii}${vm.hero.subtitle}\n${vm.contextLines.join('\n')}`, fg: '#E0F2FE', wrapMode: 'word' }));
37
+ }
38
+ function renderStatsStrip(vm) {
39
+ const content = vm.statCards.map((card) => `${card.value} ${card.label}`).join(' ');
40
+ return Text({ content, fg: '#99F6E4', wrapMode: 'word' });
41
+ }
42
+ function renderActionDetailPanel(vm, state) {
43
+ return Box({
44
+ border: true,
45
+ borderStyle: 'rounded',
46
+ borderColor: '#A78BFA',
47
+ flexDirection: state.viewport.mode === 'narrow' ? 'column' : 'row',
48
+ flexGrow: 1,
49
+ gap: 1,
50
+ padding: 1,
51
+ title: ' Actions ',
52
+ }, renderActionsList(vm), renderFocusedDetail(vm));
53
+ }
54
+ function renderActionsList(vm) {
55
+ return Box({
56
+ flexGrow: 1,
57
+ }, Text({ content: formatMainMenuOptions(vm.options), fg: '#F8FAFC', wrapMode: 'word' }));
58
+ }
59
+ function renderFocusedDetail(vm) {
60
+ return Box({
61
+ flexGrow: 1,
62
+ }, Text({ content: `Detail: ${vm.detail.title}${vm.detail.badges.length === 0 ? '' : ` ${vm.detail.badges.join(' ')}`}\n${vm.detail.lines.join('\n')}`, fg: '#DCFCE7', wrapMode: 'word' }));
63
+ }
64
+ function renderFooterStrip(vm) {
65
+ return Text({ content: vm.footer, fg: '#CBD5E1', wrapMode: 'word' });
68
66
  }
@@ -6,7 +6,7 @@ try {
6
6
  setup.renderer.root.add(createOpenTuiMainMenuScreen(createMainMenuState({ focusedOptionId: 'setup', helpVisible: true, width: 120 })));
7
7
  await setup.renderOnce();
8
8
  const frame = setup.captureCharFrame();
9
- const requiredText = ['VGXNESS Main Menu', 'Installation', 'Requires confirmation', 'No provider writes', 'final confirmation', 'q/Esc'];
9
+ const requiredText = ['VGXNESS ONLINE', 'Setup OpenCode', 'Requires confirmation', 'no provider writes', 'final confirmation', 'q quit'];
10
10
  const missing = requiredText.filter((text) => !frame.includes(text));
11
11
  if (missing.length > 0)
12
12
  throw new Error(`Main menu smoke frame is missing expected text: ${missing.join(', ')}`);
@@ -3,6 +3,6 @@ export function formatMainMenuOptions(options) {
3
3
  return options.map(formatMainMenuOption).join('\n');
4
4
  }
5
5
  function formatMainMenuOption(option) {
6
- const badges = formatBadges([...(option.focused ? [tuiBadges.focused] : []), ...option.badges]);
6
+ const badges = formatBadges(option.focused ? [tuiBadges.focused] : []);
7
7
  return `${option.focused ? '›' : ' '} ${option.label}${badges.length === 0 ? '' : ` ${badges}`} — ${option.description}`;
8
8
  }
@@ -8,6 +8,7 @@ export function setupTuiViewModelFromPlan(plan, status, state) {
8
8
  const provider = selections?.provider ?? plan?.provider ?? 'opencode';
9
9
  const opencode = plan?.opencode;
10
10
  const isOpenCode = provider === 'opencode';
11
+ const isClaude = provider === 'claude';
11
12
  const project = plan?.project ?? status?.project ?? status?.projectState.project ?? 'pending project';
12
13
  const workspaceRoot = plan?.workspaceRoot ?? status?.environment.workspaceRoot ?? 'pending workspace';
13
14
  const databasePath = plan?.db.path ?? status?.store.path ?? 'pending database path';
@@ -30,22 +31,24 @@ export function setupTuiViewModelFromPlan(plan, status, state) {
30
31
  workspaceRootLabel: compactPath(workspaceRoot, 72),
31
32
  readinessLabel: label(readiness),
32
33
  readinessBadge: readinessBadge(readiness),
33
- providerLabel: isOpenCode ? 'OpenCode' : 'Manual / none',
34
+ providerLabel: isOpenCode ? 'OpenCode' : isClaude ? 'Claude (supported secondary)' : 'Manual / none',
34
35
  databaseLabel: plan === undefined ? 'pending' : plan.db.mode,
35
36
  databasePathLabel: compactPath(databasePath, 72),
36
37
  databaseSourceLabel: String(databaseSource),
37
- scopeLabel: isOpenCode ? (opencode?.scope ?? selections?.scope ?? 'user') : 'Manual / none (OpenCode controls disabled)',
38
+ scopeLabel: isOpenCode ? (opencode?.scope ?? selections?.scope ?? 'user') : isClaude ? 'Project-local Claude config only; OpenCode controls disabled' : 'Manual / none (OpenCode controls disabled)',
38
39
  installModeLabel: isOpenCode
39
40
  ? opencode?.installsAgents === false
40
41
  ? 'mcp-only'
41
42
  : (selections?.installMode ?? 'mcp-plus-agents')
42
- : 'Manual / none (no OpenCode install)',
43
- targetPathLabel: isOpenCode && opencode?.targetPath !== undefined ? compactPath(opencode.targetPath, 72) : 'No provider config target; manual/no-provider-write mode',
44
- opencodeActionLabel: isOpenCode ? opencodeActionLabel(opencode?.action) : 'No automatic provider write; use manual setup guidance.',
43
+ : isClaude
44
+ ? 'Claude guarded apply outside guided OpenCode install'
45
+ : 'Manual / none (no OpenCode install)',
46
+ targetPathLabel: isOpenCode && opencode?.targetPath !== undefined ? compactPath(opencode.targetPath, 72) : isClaude ? 'Claude targets: .mcp.json and .claude/agents/*.md via guarded apply' : 'No provider config target; manual/no-provider-write mode',
47
+ opencodeActionLabel: isOpenCode ? opencodeActionLabel(opencode?.action) : isClaude ? 'No guided setup write; use guarded Claude install with run preflight.' : 'No automatic provider write; use manual setup guidance.',
45
48
  memoryPathExplanation: memoryExplanation,
46
49
  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.',
50
+ ? 'OpenCode is the default guided install provider. Claude is supported secondary via guarded mcp install claude --scope project --yes --run-id <id>.'
51
+ : 'Manual / none is read-only guidance. Claude remains supported secondary through explicit guarded apply outside this guided OpenCode flow.',
49
52
  agentReadinessLabel: agentReadiness.label,
50
53
  agentReadinessDetail: agentReadiness.detail,
51
54
  plannedActions: plan?.actions.map((action) => ({
@@ -75,8 +78,8 @@ export function setupTuiViewModelFromPlan(plan, status, state) {
75
78
  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]),
76
79
  ],
77
80
  providerChoices: [
78
- 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]),
79
- 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]),
81
+ choice('provider:opencode', 'OpenCode', 'Default guided install provider; writes still require final confirmation.', (selections?.provider ?? plan?.provider ?? 'opencode') === 'opencode', state?.focusedChoiceId, [badgeLabels.recommended]),
82
+ choice('provider:claude-supported-secondary', 'Claude (supported secondary)', 'Claude CLI MCP registration and project compatibility apply only via guarded mcp install claude --scope project --yes --run-id <id>; not default.', (selections?.provider ?? plan?.provider) === 'claude', state?.focusedChoiceId, ['[supported secondary]', badgeLabels.readOnly]),
80
83
  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]),
81
84
  ],
82
85
  scopeChoices: isOpenCode
@@ -103,13 +106,13 @@ function previewDetailLines(input) {
103
106
  const opencode = plan?.opencode;
104
107
  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.'];
105
108
  return [
106
- `Provider: ${input.provider === 'opencode' ? 'OpenCode [recommended]' : 'Manual / none [manual] [read-only]'}`,
109
+ `Provider: ${input.provider === 'opencode' ? 'OpenCode [recommended default]' : input.provider === 'claude' ? 'Claude [supported secondary] [guarded explicit apply]' : 'Manual / none [manual] [read-only]'}`,
107
110
  `Memory path: ${plan?.db.mode ?? 'pending'} at ${compactPath(input.databasePath, 72)} (source: ${String(input.databaseSource)})`,
108
111
  `Memory guidance: ${memoryPathExplanation(plan, input.databasePath, input.databaseSource)}`,
109
112
  `Scope: ${input.isOpenCode ? (opencode?.scope ?? 'user') : 'disabled for manual/none provider'}`,
110
113
  `Install mode: ${input.isOpenCode ? (opencode?.installsAgents === false ? 'mcp-only' : 'mcp-plus-agents') : 'disabled for manual/none provider'}`,
111
114
  `Reinstall VGXNESS entries: ${input.isOpenCode ? String(opencode?.overwriteVgxness ?? false) : 'disabled for manual/none provider'}`,
112
- `Provider installability: ${input.isOpenCode ? 'OpenCode installable after final confirmation; Claude deferred/not installable.' : 'No provider installable; OpenCode controls disabled and Claude deferred.'}`,
115
+ `Provider installability: ${input.isOpenCode ? 'OpenCode installable after final confirmation; Claude supported secondary requires guarded mcp install claude --scope project --yes --run-id <id>.' : 'No guided provider install from this selection; Claude supported secondary uses guarded explicit apply.'}`,
113
116
  `Agent readiness: ${agentReadinessFromPlan(plan)}`,
114
117
  `Target config: ${input.isOpenCode && opencode?.targetPath !== undefined ? compactPath(opencode.targetPath, 72) : 'none; no provider config will be written'}`,
115
118
  `Safety: ${input.isOpenCode ? '[will write after confirm] only on final confirmation' : '[read-only] manual/no-provider-write mode'}`,
@@ -129,7 +132,7 @@ function helpLines(screen) {
129
132
  'Next/back: Tab continues; Shift+Tab goes back. Enter continues on review and confirms only on final confirmation.',
130
133
  'Cancel/close: q or Esc cancels setup; when help is open, ?/h toggles it closed.',
131
134
  reviewLine,
132
- 'Provider support: OpenCode is the only installable provider; Claude is [deferred] [read-only] guidance and Manual / none writes no provider config.',
135
+ 'Provider support: OpenCode is the default guided install provider; Claude is supported secondary for guarded explicit apply via mcp install claude --scope project --yes --run-id <id>; Manual / none writes no provider config.',
133
136
  'Agent readiness: the preview checks vgxness-manager/SDD readiness guidance; preview screens never seed agents.',
134
137
  'No-write guarantee: no provider config is written before explicit final confirmation.',
135
138
  ];