vgxness 1.2.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -6
- package/dist/cli/cli-help.js +4 -7
- package/dist/cli/commands/index.js +1 -1
- package/dist/cli/commands/interactive-entrypoint-dispatcher.js +150 -0
- package/dist/cli/commands/setup-dispatcher.js +7 -4
- package/dist/cli/dispatcher.js +1 -8
- package/dist/cli/index.js +0 -0
- package/dist/cli/setup-wizard-renderer.js +1 -1
- package/dist/cli/tui/main-menu/index.js +0 -1
- package/dist/cli/tui/main-menu/main-menu-controller.js +0 -2
- package/dist/cli/tui/main-menu/main-menu-read-model.js +2 -8
- package/dist/cli/tui/main-menu/main-menu-render-shape.js +5 -1
- package/dist/cli/tui/main-menu/main-menu-state.js +1 -1
- package/dist/cli/tui/opentui/code/index.js +210 -0
- package/dist/cli/tui/opentui/code/screen.js +107 -0
- package/dist/cli/tui/opentui/code/smoke.js +32 -0
- package/dist/cli/tui/opentui/main-menu/index.js +3 -0
- package/dist/cli/tui/opentui/main-menu/renderer.js +68 -0
- package/dist/cli/tui/opentui/main-menu/screen.js +62 -0
- package/dist/cli/tui/opentui/main-menu/smoke.js +17 -0
- package/dist/cli/tui/opentui/main-menu/view.js +8 -0
- package/dist/cli/tui/opentui/setup/index.js +3 -0
- package/dist/cli/tui/opentui/setup/renderer.js +87 -0
- package/dist/cli/tui/opentui/setup/screen.js +170 -0
- package/dist/cli/tui/opentui/setup/smoke.js +42 -0
- package/dist/cli/tui/opentui/setup/view.js +12 -0
- package/dist/cli/tui/setup/setup-tui-input.js +43 -0
- package/dist/cli/tui/setup/setup-tui-read-model.js +1 -1
- package/dist/cli/tui/setup/setup-tui-render-shape.js +1 -1
- package/dist/cli/tui/setup/setup-tui-view-helpers.js +46 -0
- package/dist/cli/tui/visual/index.js +0 -2
- package/dist/code/tui/approval-actions.js +33 -0
- package/dist/code/tui/prompt-mode.js +11 -0
- package/dist/code/tui/runtime-events.js +320 -0
- package/dist/sdd/sdd-workflow-service.js +0 -24
- package/dist/setup/providers/antigravity-setup-adapter.js +1 -1
- package/dist/setup/providers/claude-setup-adapter.js +2 -2
- package/dist/setup/providers/custom-setup-adapter.js +1 -1
- package/dist/setup/providers/opencode-setup-adapter.js +3 -3
- package/dist/setup/setup-lifecycle-service.js +1 -1
- package/docs/architecture.md +4 -4
- package/docs/cli.md +11 -10
- package/docs/funcionamiento-del-sistema.md +6 -7
- package/docs/prd.md +4 -4
- package/docs/vgxcode.md +76 -0
- package/package.json +5 -6
- package/dist/cli/commands/dashboard-dispatcher.js +0 -560
- package/dist/cli/dashboard-operational-read-models.js +0 -428
- package/dist/cli/dashboard-renderer.js +0 -158
- package/dist/cli/dashboard-screen-renderers.js +0 -256
- package/dist/cli/dashboard-tui-read-model.js +0 -73
- package/dist/cli/dashboard-tui-state.js +0 -314
- package/dist/cli/interactive-dashboard.js +0 -34
- package/dist/cli/tui/dashboard/dashboard-adapter.js +0 -4
- package/dist/cli/tui/main-menu/main-menu-app.js +0 -28
- package/dist/cli/tui/render-ink-app.js +0 -10
- package/dist/cli/tui/setup/screens/applying-screen.js +0 -6
- package/dist/cli/tui/setup/screens/cancellation-screen.js +0 -6
- package/dist/cli/tui/setup/screens/error-recovery-screen.js +0 -6
- package/dist/cli/tui/setup/screens/final-confirmation-screen.js +0 -6
- package/dist/cli/tui/setup/screens/opencode-details-screen.js +0 -10
- package/dist/cli/tui/setup/screens/plan-review-screen.js +0 -6
- package/dist/cli/tui/setup/screens/project-database-screen.js +0 -6
- package/dist/cli/tui/setup/screens/provider-screen.js +0 -7
- package/dist/cli/tui/setup/screens/result-screen.js +0 -16
- package/dist/cli/tui/setup/screens/screen-components.js +0 -103
- package/dist/cli/tui/setup/screens/welcome-screen.js +0 -6
- package/dist/cli/tui/setup/setup-tui-app.js +0 -113
- package/dist/cli/tui/visual/choice-list.js +0 -10
- package/dist/cli/tui/visual/layout.js +0 -10
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ VGXNESS is an installable CLI and MCP control plane for guided AI-agent workflow
|
|
|
6
6
|
|
|
7
7
|
This package is proprietary software. The npm package ships inspectable JavaScript (`dist/`) so Node can run it, but it is **not open-source licensed** and may not be redistributed unless you have written permission. See [LICENSE](./LICENSE).
|
|
8
8
|
|
|
9
|
-
OpenCode is the primary supported provider. Other providers remain preview/manual only.
|
|
9
|
+
OpenCode is the primary supported provider. Other providers remain preview/manual only. Provider config writes require explicit CLI confirmation.
|
|
10
10
|
|
|
11
11
|
## Requirements
|
|
12
12
|
|
|
@@ -162,19 +162,20 @@ vgx setup apply --yes
|
|
|
162
162
|
|
|
163
163
|
- Preview, status, and plan commands are read-only.
|
|
164
164
|
- Provider config writes require explicit `--yes` confirmation.
|
|
165
|
-
-
|
|
165
|
+
- Setup/status TUI surfaces are preview-oriented; run copied commands explicitly when you choose to act.
|
|
166
166
|
- SDD artifacts are SQLite-backed through VGXNESS services. Do not create or write `openspec/`.
|
|
167
167
|
- `vgx sdd accept-artifact` records explicit human-only acceptance; saving a draft never implies acceptance.
|
|
168
168
|
- OpenCode is the primary supported provider; other providers are preview/manual extension points.
|
|
169
169
|
|
|
170
|
-
##
|
|
170
|
+
## Main menu entrypoint
|
|
171
171
|
|
|
172
|
-
Run `vgx` or `vgxness` with no arguments in an interactive terminal to open the
|
|
172
|
+
Run `vgx` or `vgxness` with no arguments in an interactive terminal to open the OpenTUI main menu. In non-TTY shells, no-args prints static safe setup guidance and exits 0 without inferring project state.
|
|
173
173
|
|
|
174
|
-
For scripts, use
|
|
174
|
+
For scripts, use explicit read-only commands with an explicit project:
|
|
175
175
|
|
|
176
176
|
```bash
|
|
177
|
-
vgx
|
|
177
|
+
vgx setup status --project <name>
|
|
178
|
+
vgx sdd status --project <name> --change <change>
|
|
178
179
|
```
|
|
179
180
|
|
|
180
181
|
For project-local or custom databases, use `--db project-local` or `--db custom --db-path <path>` with setup commands. Existing low-level commands remain available:
|
package/dist/cli/cli-help.js
CHANGED
|
@@ -91,14 +91,11 @@ Areas:
|
|
|
91
91
|
subagents list --parent-agent-id <id>
|
|
92
92
|
subagents get --id <id> | --project <name> --name <name> [--scope project|personal]
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
No args without a TTY prints static safe guidance and exits 0 without reading dashboard status or opening project state.
|
|
98
|
-
Dashboard menu: Installation, Status, Agents, Skills, Memory, SDD, Runs, Approvals, Permissions, Settings. Menu: ↑/↓ or j/k, Enter, 1-9/0. Section: b/Esc/Backspace back, r, ?, q.
|
|
99
|
-
Dashboardinteractive may launch without --project; Installation remains available and project-scoped checks are deferred while project screens render project-required recovery states.
|
|
94
|
+
No args in an interactive TTY opens the OpenTUI main menu.
|
|
95
|
+
No args without a TTY prints static safe setup guidance and exits 0 without opening project state.
|
|
96
|
+
Setup TUI may launch without --project; Installation remains available and project-scoped checks are deferred while project screens render project-required recovery states.
|
|
100
97
|
Provider setup support: OpenCode supported primary; Claude preview-only; Antigravity placeholder; Custom/future extension point.
|
|
101
|
-
|
|
98
|
+
Provider config writes/install/apply are external-only and require explicit confirmation.
|
|
102
99
|
|
|
103
100
|
sdd status --project <name> --change <id> [--json]
|
|
104
101
|
sdd next --project <name> --change <id> [--json]
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { runAgentCommand, runSkillCommand, runSubagentCommand } from './agent-skill-dispatcher.js';
|
|
2
|
-
export { runCodeCliCommand,
|
|
2
|
+
export { runCodeCliCommand, runDefaultInteractiveEntrypoint } from './interactive-entrypoint-dispatcher.js';
|
|
3
3
|
export { runDoctorAliasCommand, runMcpDoctorCommand, runMcpDoctorOpenCodeCommand, runMcpInstallCommand, runMcpSetupCommand } from './mcp-dispatcher.js';
|
|
4
4
|
export { runMemoryCommand, runMemoryImportCommand, runOpenCodeCommand, runOrchestratorCommand, runSddCommand } from './memory-sdd-dispatcher.js';
|
|
5
5
|
export { runApprovalsCommand, runPermissionsCommand, runRunsCommand } from './run-permission-dispatcher.js';
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { runCodeCommand } from '../../code/cli/code-command.js';
|
|
2
|
+
import { InMemoryRunGateway } from '../../code/runtime/gateways.js';
|
|
3
|
+
import { MemoryServiceCodeGateway } from '../../code/runtime/memory-service-gateway.js';
|
|
4
|
+
import { RunsCodeRunGateway } from '../../code/runtime/runs-code-run-gateway.js';
|
|
5
|
+
import { SddWorkflowGateway } from '../../code/runtime/sdd-workflow-gateway.js';
|
|
6
|
+
import { MemoryService } from '../../memory/memory-service.js';
|
|
7
|
+
import { RunService } from '../../runs/run-service.js';
|
|
8
|
+
import { SddWorkflowService } from '../../sdd/sdd-workflow-service.js';
|
|
9
|
+
import { codeApprovalPolicyFlag, codeMemoryPolicyFlag, codeTranscriptModeFlag, codeVerificationModeFlag, databasePathFor, optionalNumberFlag, optionalStringFlag, } from '../cli-flags.js';
|
|
10
|
+
import { okText, usageFailure, validationFailure } from '../cli-help.js';
|
|
11
|
+
import { openCliDatabase, resultFailure } from '../cli-helpers.js';
|
|
12
|
+
import { canRunInteractiveTui } from '../tui/terminal-capabilities.js';
|
|
13
|
+
import { runSetupTuiCommand } from './setup-dispatcher.js';
|
|
14
|
+
function defaultNoTtyGuidance() {
|
|
15
|
+
return ([
|
|
16
|
+
'VGXNESS main menu requires a TTY; no provider config was written.',
|
|
17
|
+
'Next: rerun `vgx` in an interactive terminal, run `vgx init --plan`, or run `vgx setup plan` for read-only installation guidance.',
|
|
18
|
+
].join('\n') + '\n');
|
|
19
|
+
}
|
|
20
|
+
function guidanceForMainMenuResult(result) {
|
|
21
|
+
if (result.type === 'show-doctor-guidance')
|
|
22
|
+
return ['Doctor / recovery guidance:', '- Read-only status: `vgx setup status`', '- Provider doctor: `vgx mcp doctor opencode`', '- No provider config was written.'].join('\n') + '\n';
|
|
23
|
+
if (result.type === 'show-sdd-guidance')
|
|
24
|
+
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';
|
|
25
|
+
if (result.type === 'show-advanced-cli')
|
|
26
|
+
return ['Advanced CLI guidance:', '- Installation preview: `vgx init --plan` or `vgx setup plan`', '- Setup status: `vgx setup status --project <name>`', '- SDD status: `vgx sdd status --project <name> --change <change>`', '- No provider config was written.'].join('\n') + '\n';
|
|
27
|
+
if (result.type === 'exit')
|
|
28
|
+
return 'Exited main menu; no provider config was written.\n';
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
async function renderDefaultMainMenu(environment, onResult) {
|
|
32
|
+
const { renderOpenTuiMainMenu } = await import('../tui/opentui/main-menu/renderer.js');
|
|
33
|
+
await renderOpenTuiMainMenu({
|
|
34
|
+
stdin: environment.stdin,
|
|
35
|
+
stdout: environment.stdout,
|
|
36
|
+
onResult,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
export async function runDefaultInteractiveEntrypointWithMainMenu(environment, input = {}) {
|
|
40
|
+
if (!canRunInteractiveTui(environment.stdin, environment.stdout))
|
|
41
|
+
return okText(defaultNoTtyGuidance());
|
|
42
|
+
let selected;
|
|
43
|
+
await (input.renderMainMenu ?? ((onResult) => renderDefaultMainMenu(environment, onResult)))((result) => {
|
|
44
|
+
selected = result;
|
|
45
|
+
});
|
|
46
|
+
const result = selected ?? { type: 'exit' };
|
|
47
|
+
if (result.type === 'open-setup')
|
|
48
|
+
return (input.setupTui ?? runSetupTuiCommand)(environment);
|
|
49
|
+
return okText(guidanceForMainMenuResult(result) ?? 'No provider config was written.\n');
|
|
50
|
+
}
|
|
51
|
+
function codeApprovalChannelFlag(flags) {
|
|
52
|
+
const value = optionalStringFlag(flags, 'approval-channel');
|
|
53
|
+
if (value === undefined)
|
|
54
|
+
return { ok: true, value: undefined };
|
|
55
|
+
return value === 'stdio' ? { ok: true, value } : validationFailure('--approval-channel must be stdio');
|
|
56
|
+
}
|
|
57
|
+
export async function runDefaultInteractiveEntrypoint(environment) {
|
|
58
|
+
return runDefaultInteractiveEntrypointWithMainMenu(environment);
|
|
59
|
+
}
|
|
60
|
+
export async function runCodeCliCommand(parsed, environment) {
|
|
61
|
+
const [, command] = parsed.positionals;
|
|
62
|
+
if (command !== 'inspect' && command !== 'plan' && command !== 'craft' && command !== 'craft-preview' && command !== 'sdd')
|
|
63
|
+
return usageFailure(`Unknown code command: ${command ?? ''}`.trim());
|
|
64
|
+
const eventsJsonl = parsed.flags['events-jsonl'] === true;
|
|
65
|
+
const approvalChannel = codeApprovalChannelFlag(parsed.flags);
|
|
66
|
+
if (!approvalChannel.ok)
|
|
67
|
+
return resultFailure(approvalChannel);
|
|
68
|
+
if (approvalChannel.value === 'stdio' && (command !== 'craft' || !eventsJsonl))
|
|
69
|
+
return usageFailure('--approval-channel stdio is supported only for code craft --events-jsonl');
|
|
70
|
+
if (eventsJsonl && command !== 'inspect' && command !== 'plan' && command !== 'craft-preview' && approvalChannel.value !== 'stdio')
|
|
71
|
+
return usageFailure('code craft --events-jsonl requires --approval-channel stdio; JSONL without approvals is currently supported only for read-only inspect, plan, and craft-preview');
|
|
72
|
+
const maxSourceBytes = optionalNumberFlag(parsed.flags, 'max-source-bytes');
|
|
73
|
+
if (!maxSourceBytes.ok)
|
|
74
|
+
return resultFailure(maxSourceBytes);
|
|
75
|
+
const output = parsed.flags.json === true || optionalStringFlag(parsed.flags, 'output') === 'json' ? 'json' : 'human';
|
|
76
|
+
const approvalPolicy = codeApprovalPolicyFlag(parsed.flags);
|
|
77
|
+
if (!approvalPolicy.ok)
|
|
78
|
+
return resultFailure(approvalPolicy);
|
|
79
|
+
const verificationMode = codeVerificationModeFlag(parsed.flags);
|
|
80
|
+
if (!verificationMode.ok)
|
|
81
|
+
return resultFailure(verificationMode);
|
|
82
|
+
const transcriptMode = codeTranscriptModeFlag(parsed.flags);
|
|
83
|
+
if (!transcriptMode.ok)
|
|
84
|
+
return resultFailure(transcriptMode);
|
|
85
|
+
const memoryPolicy = codeMemoryPolicyFlag(parsed.flags);
|
|
86
|
+
if (!memoryPolicy.ok)
|
|
87
|
+
return resultFailure(memoryPolicy);
|
|
88
|
+
const provider = optionalStringFlag(parsed.flags, 'provider');
|
|
89
|
+
const model = optionalStringFlag(parsed.flags, 'model');
|
|
90
|
+
if (eventsJsonl) {
|
|
91
|
+
if (approvalChannel.value === 'stdio' && (environment.stdin === undefined || environment.stdout === undefined))
|
|
92
|
+
return resultFailure(validationFailure('--approval-channel stdio requires CLI stdin and stdout streams'));
|
|
93
|
+
return await runCodeCommand({
|
|
94
|
+
command,
|
|
95
|
+
args: parsed.positionals.slice(2),
|
|
96
|
+
cwd: environment.cwd,
|
|
97
|
+
output,
|
|
98
|
+
runGateway: new InMemoryRunGateway(),
|
|
99
|
+
project: optionalStringFlag(parsed.flags, 'project') ?? 'vgxness',
|
|
100
|
+
...(provider === undefined ? {} : { provider }),
|
|
101
|
+
...(model === undefined ? {} : { model }),
|
|
102
|
+
stream: parsed.flags.stream === true,
|
|
103
|
+
env: environment.env,
|
|
104
|
+
eventsJsonl,
|
|
105
|
+
memoryPolicy: 'off',
|
|
106
|
+
...(approvalChannel.value === undefined ? {} : { approvalChannel: approvalChannel.value }),
|
|
107
|
+
...(approvalChannel.value === 'stdio' ? { approvalDecisionInput: environment.stdin, eventOutput: environment.stdout } : {}),
|
|
108
|
+
...(maxSourceBytes.value !== undefined ? { maxSourceBytes: maxSourceBytes.value } : {}),
|
|
109
|
+
...(approvalPolicy.value === undefined ? {} : { approvalPolicy: approvalPolicy.value }),
|
|
110
|
+
...(verificationMode.value === undefined ? {} : { verificationMode: verificationMode.value }),
|
|
111
|
+
...(transcriptMode.value === undefined ? {} : { transcriptMode: transcriptMode.value }),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
const selectedDatabasePath = databasePathFor(parsed.flags, environment);
|
|
115
|
+
if (!selectedDatabasePath.ok)
|
|
116
|
+
return resultFailure(selectedDatabasePath);
|
|
117
|
+
const opened = openCliDatabase(selectedDatabasePath.value);
|
|
118
|
+
if (!opened.ok)
|
|
119
|
+
return resultFailure(opened);
|
|
120
|
+
try {
|
|
121
|
+
const runs = new RunService(opened.value);
|
|
122
|
+
const memory = new MemoryService(opened.value);
|
|
123
|
+
return await runCodeCommand({
|
|
124
|
+
command,
|
|
125
|
+
args: parsed.positionals.slice(2),
|
|
126
|
+
cwd: environment.cwd,
|
|
127
|
+
output,
|
|
128
|
+
runGateway: new RunsCodeRunGateway(runs),
|
|
129
|
+
sddGateway: new SddWorkflowGateway(new SddWorkflowService(memory)),
|
|
130
|
+
memoryGateway: new MemoryServiceCodeGateway(memory),
|
|
131
|
+
project: optionalStringFlag(parsed.flags, 'project') ?? 'vgxness',
|
|
132
|
+
...(provider === undefined ? {} : { provider }),
|
|
133
|
+
...(model === undefined ? {} : { model }),
|
|
134
|
+
stream: parsed.flags.stream === true,
|
|
135
|
+
env: environment.env,
|
|
136
|
+
eventsJsonl,
|
|
137
|
+
persistArtifact: parsed.flags['save-artifact'] === true || parsed.flags.persist === true,
|
|
138
|
+
...(maxSourceBytes.value !== undefined ? { maxSourceBytes: maxSourceBytes.value } : {}),
|
|
139
|
+
...(approvalPolicy.value === undefined ? {} : { approvalPolicy: approvalPolicy.value }),
|
|
140
|
+
...(verificationMode.value === undefined ? {} : { verificationMode: verificationMode.value }),
|
|
141
|
+
...(transcriptMode.value === undefined ? {} : { transcriptMode: transcriptMode.value }),
|
|
142
|
+
...(memoryPolicy.value === undefined ? {} : { memoryPolicy: memoryPolicy.value }),
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
finally {
|
|
146
|
+
opened.value.close();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// Re-export helpers
|
|
150
|
+
export { defaultNoTtyGuidance, };
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { resolve } from 'node:path';
|
|
2
|
-
import React from 'react';
|
|
3
2
|
import { AgentRegistryService } from '../../agents/agent-registry-service.js';
|
|
4
3
|
import { resolveAgentProfileModel } from '../../agents/profile-model-routing.js';
|
|
5
4
|
import { installOpenCodeMcpClient } from '../../mcp/client-install-opencode.js';
|
|
@@ -14,8 +13,6 @@ import { okText, usageFailure, validationFailure } from '../cli-help.js';
|
|
|
14
13
|
import { jsonResult, openCliDatabase, resultFailure } from '../cli-helpers.js';
|
|
15
14
|
import { renderSetupPlan } from '../setup-plan-renderer.js';
|
|
16
15
|
import { renderSetupStatus } from '../setup-status-renderer.js';
|
|
17
|
-
import { renderInkApp } from '../tui/render-ink-app.js';
|
|
18
|
-
import { SetupTuiApp } from '../tui/setup/setup-tui-app.js';
|
|
19
16
|
import { createDefaultSetupTuiServices } from '../tui/setup/setup-tui-services.js';
|
|
20
17
|
import { canRunInteractiveTui } from '../tui/terminal-capabilities.js';
|
|
21
18
|
function renderSetupRollbackApply(result, parsed) {
|
|
@@ -361,7 +358,13 @@ export async function runSetupTuiCommand(environment, input) {
|
|
|
361
358
|
},
|
|
362
359
|
};
|
|
363
360
|
try {
|
|
364
|
-
|
|
361
|
+
const { renderOpenTuiSetup } = await import('../tui/opentui/setup/index.js');
|
|
362
|
+
await renderOpenTuiSetup({
|
|
363
|
+
services: createDefaultSetupTuiServices({ lifecycle, cwd: environment.cwd, env: environment.env }),
|
|
364
|
+
runtime,
|
|
365
|
+
stdin: environment.stdin,
|
|
366
|
+
stdout: environment.stdout,
|
|
367
|
+
});
|
|
365
368
|
return { exitCode: 0, stdout: '', stderr: '' };
|
|
366
369
|
}
|
|
367
370
|
finally {
|
package/dist/cli/dispatcher.js
CHANGED
|
@@ -4,7 +4,7 @@ import { isWorkflowId } from '../workflows/schema.js';
|
|
|
4
4
|
import { databasePathFor, parseArgs, requiredFlag } from './cli-flags.js';
|
|
5
5
|
import { okText, usageFailure, visibleHelpText } from './cli-help.js';
|
|
6
6
|
import { openCliDatabase, resultFailure } from './cli-helpers.js';
|
|
7
|
-
import { runAgentCommand, runApprovalsCommand, runCodeCliCommand,
|
|
7
|
+
import { runAgentCommand, runApprovalsCommand, runCodeCliCommand, runDefaultInteractiveEntrypoint, runDoctorAliasCommand, runInitCommand, runMcpDoctorCommand, runMcpInstallCommand, runMcpSetupCommand, runMemoryCommand, runMemoryImportCommand, runOpenCodeCommand, runOrchestratorCommand, runPermissionsCommand, runRunsCommand, runSddCommand, runSetupApplyCommand, runSetupLifecycleCommand, runSetupPlanCommand, runSetupRollbackCommand, runSkillCommand, runSubagentCommand, runVerificationPlanCommand, runVerificationReportCommand, runWorkflowExecuteCommand, runWorkflowPreviewCommand, runWorkflowRunCommand, } from './commands/index.js';
|
|
8
8
|
const _promptBuffers = new WeakMap();
|
|
9
9
|
const require = createRequire(import.meta.url);
|
|
10
10
|
const packageJson = require('../../package.json');
|
|
@@ -75,8 +75,6 @@ export function dispatchCli(argv, environment) {
|
|
|
75
75
|
return runOrchestratorCommand(command, parsed, opened.value);
|
|
76
76
|
if (area === 'opencode')
|
|
77
77
|
return runOpenCodeCommand(command, parsed, opened.value, environment);
|
|
78
|
-
if (area === 'dashboard')
|
|
79
|
-
return runDashboardCommand(command, parsed, opened.value, environment, databasePath);
|
|
80
78
|
return usageFailure(`Unknown command area: ${area}`);
|
|
81
79
|
}
|
|
82
80
|
finally {
|
|
@@ -91,8 +89,6 @@ export async function dispatchCliAsync(argv, environment) {
|
|
|
91
89
|
const [area, command] = parsed.positionals;
|
|
92
90
|
if (argv.length === 0)
|
|
93
91
|
return runDefaultInteractiveEntrypoint(environment);
|
|
94
|
-
if (area === 'dashboard' && command === 'interactive')
|
|
95
|
-
return runDashboardInteractiveCommand(parsed, environment);
|
|
96
92
|
if (area === 'mcp') {
|
|
97
93
|
if (!command)
|
|
98
94
|
return usageFailure('Missing command for mcp');
|
|
@@ -222,9 +218,6 @@ function validateCommand(area, command) {
|
|
|
222
218
|
if (area === 'verification') {
|
|
223
219
|
return command === 'plan' || command === 'report' ? { ok: true } : { ok: false, message: `Unknown verification command: ${command}` };
|
|
224
220
|
}
|
|
225
|
-
if (area === 'dashboard') {
|
|
226
|
-
return command === 'status' ? { ok: true } : { ok: false, message: `Unknown dashboard command: ${command}` };
|
|
227
|
-
}
|
|
228
221
|
if (area === 'code') {
|
|
229
222
|
return command === 'inspect' || command === 'plan' || command === 'craft' || command === 'sdd'
|
|
230
223
|
? { ok: true }
|
package/dist/cli/index.js
CHANGED
|
File without changes
|
|
@@ -58,7 +58,7 @@ function renderFocusedProviderDetails(cards, state) {
|
|
|
58
58
|
return [];
|
|
59
59
|
const targets = focusedCard.targets.length === 0
|
|
60
60
|
? 'Targets: none'
|
|
61
|
-
: `Targets: ${focusedCard.targets.map((target) => `${target.label} (${target.kind}${target.path === undefined ? '' : `: ${target.path}`};
|
|
61
|
+
: `Targets: ${focusedCard.targets.map((target) => `${target.label} (${target.kind}${target.path === undefined ? '' : `: ${target.path}`}; TUI-write: no)`).join('; ')}`;
|
|
62
62
|
const safetyLabels = setupSafetyLabels(focusedCard);
|
|
63
63
|
return [
|
|
64
64
|
'',
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
export function resultForMainMenuOption(optionId) {
|
|
2
2
|
if (optionId === 'setup')
|
|
3
3
|
return { type: 'open-setup' };
|
|
4
|
-
if (optionId === 'dashboard')
|
|
5
|
-
return { type: 'open-dashboard' };
|
|
6
4
|
if (optionId === 'doctor')
|
|
7
5
|
return { type: 'show-doctor-guidance' };
|
|
8
6
|
if (optionId === 'sdd')
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { tuiBadges } from '../visual/badges.js';
|
|
2
|
+
import { formatTuiFooter } from '../visual/footer.js';
|
|
2
3
|
import { mainMenuOptionIds } from './main-menu-state.js';
|
|
3
4
|
const optionCopy = {
|
|
4
5
|
setup: {
|
|
@@ -12,13 +13,6 @@ const optionCopy = {
|
|
|
12
13
|
'Provider writes require final confirmation; OpenCode is the only automatic provider with explicit consent.',
|
|
13
14
|
],
|
|
14
15
|
},
|
|
15
|
-
dashboard: {
|
|
16
|
-
label: 'Dashboard',
|
|
17
|
-
description: 'Inspect project health, runs, approvals, SDD, agents, and skills.',
|
|
18
|
-
badges: [tuiBadges.readOnly],
|
|
19
|
-
detailTitle: 'Interactive dashboard',
|
|
20
|
-
detailLines: ['Opens the existing read-only dashboard.', 'Dashboard navigation does not apply provider config or write OpenCode setup.'],
|
|
21
|
-
},
|
|
22
16
|
doctor: {
|
|
23
17
|
label: 'Doctor / recovery',
|
|
24
18
|
description: 'See health-check and recovery commands.',
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { formatBadges, tuiBadges } from '../visual/badges.js';
|
|
2
2
|
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 } };
|
|
@@ -27,3 +27,7 @@ function clampLine(line, width) {
|
|
|
27
27
|
return `${line.slice(0, Math.max(0, width - phrase.length - 5)).trim()} ... ${phrase}`.trim();
|
|
28
28
|
return `${line.slice(0, Math.max(0, width - 1)).trim()}…`;
|
|
29
29
|
}
|
|
30
|
+
function choiceLine(choice) {
|
|
31
|
+
const badges = formatBadges([...(choice.focused === true ? [tuiBadges.focused] : []), ...choice.badges]);
|
|
32
|
+
return `${choice.focused === true ? '›' : ' '} ${choice.label}${badges.length === 0 ? '' : ` ${badges}`} — ${choice.description}`;
|
|
33
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createTuiViewport } from '../visual/viewport.js';
|
|
2
|
-
export const mainMenuOptionIds = ['setup', '
|
|
2
|
+
export const mainMenuOptionIds = ['setup', 'doctor', 'sdd', 'advanced-cli', 'exit'];
|
|
3
3
|
export function createMainMenuState(input = {}) {
|
|
4
4
|
return {
|
|
5
5
|
focusedOptionId: input.focusedOptionId ?? 'setup',
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { createCliRenderer } from '@opentui/core';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { dirname, resolve } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { ApprovalDecisionWriter, getApprovalActionsState } from '../../../../code/tui/approval-actions.js';
|
|
6
|
+
import { buildSafetyReadModel, parseVgxcodeJsonl, parseVgxcodeJsonlLine } from '../../../../code/tui/runtime-events.js';
|
|
7
|
+
import { parsePromptSubmission, toggleReadOnlyMode } from '../../../../code/tui/prompt-mode.js';
|
|
8
|
+
import { createVgxcodeScreen } from './screen.js';
|
|
9
|
+
async function main() {
|
|
10
|
+
const project = process.argv[2] ?? 'vgxness';
|
|
11
|
+
const parsedStdin = await readPipedRuntimeEvents();
|
|
12
|
+
const state = {
|
|
13
|
+
project,
|
|
14
|
+
prompt: '',
|
|
15
|
+
submittedPrompt: '',
|
|
16
|
+
events: [...parsedStdin.events],
|
|
17
|
+
errors: [...parsedStdin.errors],
|
|
18
|
+
status: parsedStdin.errors.length > 0 ? 'error' : parsedStdin.events.length > 0 ? 'completed' : 'idle',
|
|
19
|
+
source: parsedStdin.events.length > 0 || parsedStdin.errors.length > 0 ? 'stdin' : 'interactive',
|
|
20
|
+
mode: 'inspect',
|
|
21
|
+
childStdinOpen: false,
|
|
22
|
+
decisionWriter: undefined,
|
|
23
|
+
};
|
|
24
|
+
const renderer = await createCliRenderer({
|
|
25
|
+
exitOnCtrlC: true,
|
|
26
|
+
clearOnShutdown: true,
|
|
27
|
+
screenMode: 'alternate-screen',
|
|
28
|
+
consoleMode: 'disabled',
|
|
29
|
+
});
|
|
30
|
+
const render = () => {
|
|
31
|
+
const current = renderer.root.getRenderable('vgxcode-screen');
|
|
32
|
+
if (current)
|
|
33
|
+
renderer.root.remove('vgxcode-screen');
|
|
34
|
+
const safety = buildSafetyReadModel(state.events);
|
|
35
|
+
renderer.root.add(createVgxcodeScreen({
|
|
36
|
+
...state,
|
|
37
|
+
approvalActionsEnabled: getApprovalActionsState({
|
|
38
|
+
source: state.source,
|
|
39
|
+
status: state.status,
|
|
40
|
+
mode: state.mode,
|
|
41
|
+
childStdinOpen: state.childStdinOpen,
|
|
42
|
+
pendingApprovalCount: safety.pendingApprovalCount,
|
|
43
|
+
...(safety.latestPendingApproval === undefined ? {} : { latestPendingApproval: safety.latestPendingApproval }),
|
|
44
|
+
}).enabled,
|
|
45
|
+
}));
|
|
46
|
+
renderer.requestRender();
|
|
47
|
+
};
|
|
48
|
+
render();
|
|
49
|
+
if (state.source === 'interactive') {
|
|
50
|
+
renderer.keyInput.on('keypress', (key) => {
|
|
51
|
+
if (key.ctrl && key.name === 'c')
|
|
52
|
+
return;
|
|
53
|
+
if (state.status === 'running') {
|
|
54
|
+
if (key.name === 'a' || key.sequence === 'a')
|
|
55
|
+
writePendingApprovalDecision(state, 'approved', render);
|
|
56
|
+
if (key.name === 'd' || key.sequence === 'd')
|
|
57
|
+
writePendingApprovalDecision(state, 'denied', render);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (key.name === 'tab') {
|
|
61
|
+
state.mode = toggleReadOnlyMode(state.mode);
|
|
62
|
+
render();
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (key.name === 'return' || key.name === 'enter') {
|
|
66
|
+
void runReadOnlyBridge(state, render);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (key.name === 'backspace' || key.name === 'delete') {
|
|
70
|
+
state.prompt = state.prompt.slice(0, -1);
|
|
71
|
+
render();
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const text = printableKeyText(key.sequence);
|
|
75
|
+
if (text !== '') {
|
|
76
|
+
state.prompt += text;
|
|
77
|
+
render();
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async function readPipedRuntimeEvents() {
|
|
83
|
+
if (process.stdin.isTTY)
|
|
84
|
+
return { events: [], errors: [] };
|
|
85
|
+
let input = '';
|
|
86
|
+
for await (const chunk of process.stdin)
|
|
87
|
+
input += String(chunk);
|
|
88
|
+
return parseVgxcodeJsonl(input);
|
|
89
|
+
}
|
|
90
|
+
async function runReadOnlyBridge(state, render) {
|
|
91
|
+
const submission = parsePromptSubmission(state.prompt, state.mode);
|
|
92
|
+
const prompt = submission.prompt;
|
|
93
|
+
if (prompt === '') {
|
|
94
|
+
state.status = 'error';
|
|
95
|
+
state.errors = ['Enter a prompt before running inspect, plan, or craft-preview. Prefix with /plan, /inspect, or /craft-preview; press Tab to switch inspect/plan.'];
|
|
96
|
+
render();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
state.mode = submission.mode;
|
|
100
|
+
state.submittedPrompt = prompt;
|
|
101
|
+
state.prompt = '';
|
|
102
|
+
state.status = 'running';
|
|
103
|
+
state.errors = [];
|
|
104
|
+
state.events = [];
|
|
105
|
+
state.childStdinOpen = false;
|
|
106
|
+
state.decisionWriter = undefined;
|
|
107
|
+
render();
|
|
108
|
+
const root = resolve(dirname(fileURLToPath(import.meta.url)), '../../../../..');
|
|
109
|
+
const approvalCapableCraft = state.mode === 'craft';
|
|
110
|
+
const child = spawn('npm', commandArgsForMode(state.mode, prompt), {
|
|
111
|
+
cwd: root,
|
|
112
|
+
stdio: approvalCapableCraft ? ['pipe', 'pipe', 'pipe'] : ['ignore', 'pipe', 'pipe'],
|
|
113
|
+
env: process.env,
|
|
114
|
+
});
|
|
115
|
+
state.childStdinOpen = approvalCapableCraft && child.stdin !== null;
|
|
116
|
+
state.decisionWriter = approvalCapableCraft && child.stdin !== null ? new ApprovalDecisionWriter(child.stdin) : undefined;
|
|
117
|
+
if (child.stdout === null || child.stderr === null) {
|
|
118
|
+
state.status = 'error';
|
|
119
|
+
state.childStdinOpen = false;
|
|
120
|
+
state.decisionWriter = undefined;
|
|
121
|
+
state.errors.push('Failed to start root CLI with JSONL output streams.');
|
|
122
|
+
render();
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
child.stdin?.on('close', () => {
|
|
126
|
+
state.childStdinOpen = false;
|
|
127
|
+
render();
|
|
128
|
+
});
|
|
129
|
+
child.stdin?.on('error', () => {
|
|
130
|
+
state.childStdinOpen = false;
|
|
131
|
+
render();
|
|
132
|
+
});
|
|
133
|
+
let lineNumber = 0;
|
|
134
|
+
let stdoutBuffer = '';
|
|
135
|
+
let stderr = '';
|
|
136
|
+
child.stdout.setEncoding('utf8');
|
|
137
|
+
child.stdout.on('data', (chunk) => {
|
|
138
|
+
stdoutBuffer += chunk;
|
|
139
|
+
const lines = stdoutBuffer.split(/\r?\n/);
|
|
140
|
+
stdoutBuffer = lines.pop() ?? '';
|
|
141
|
+
for (const line of lines) {
|
|
142
|
+
lineNumber += 1;
|
|
143
|
+
const parsed = parseVgxcodeJsonlLine(line, lineNumber);
|
|
144
|
+
if (parsed.event)
|
|
145
|
+
state.events.push(parsed.event);
|
|
146
|
+
if (parsed.error)
|
|
147
|
+
state.errors.push(parsed.error);
|
|
148
|
+
}
|
|
149
|
+
render();
|
|
150
|
+
});
|
|
151
|
+
child.stderr.setEncoding('utf8');
|
|
152
|
+
child.stderr.on('data', (chunk) => {
|
|
153
|
+
stderr += chunk;
|
|
154
|
+
});
|
|
155
|
+
child.on('error', (error) => {
|
|
156
|
+
state.status = 'error';
|
|
157
|
+
state.childStdinOpen = false;
|
|
158
|
+
state.errors.push(`Failed to start root CLI: ${error.message}`);
|
|
159
|
+
render();
|
|
160
|
+
});
|
|
161
|
+
child.on('close', (code, signal) => {
|
|
162
|
+
state.childStdinOpen = false;
|
|
163
|
+
state.decisionWriter = undefined;
|
|
164
|
+
if (stdoutBuffer.trim() !== '') {
|
|
165
|
+
lineNumber += 1;
|
|
166
|
+
const parsed = parseVgxcodeJsonlLine(stdoutBuffer, lineNumber);
|
|
167
|
+
if (parsed.event)
|
|
168
|
+
state.events.push(parsed.event);
|
|
169
|
+
if (parsed.error)
|
|
170
|
+
state.errors.push(parsed.error);
|
|
171
|
+
}
|
|
172
|
+
state.status = state.errors.length > 0 ? 'error' : 'completed';
|
|
173
|
+
if (code !== 0 && !(state.mode === 'craft-preview' && code === 3)) {
|
|
174
|
+
const detail = stderr.trim() === '' ? `signal ${signal ?? 'none'}` : stderr.trim();
|
|
175
|
+
state.status = 'error';
|
|
176
|
+
state.errors.push(`Root CLI ${state.mode} failed (exit ${code ?? 'signal'}): ${detail}`);
|
|
177
|
+
}
|
|
178
|
+
render();
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
function commandArgsForMode(mode, prompt) {
|
|
182
|
+
const args = ['run', '--silent', 'cli', '--', 'code', mode, prompt, '--events-jsonl'];
|
|
183
|
+
if (mode === 'craft')
|
|
184
|
+
args.push('--approval-channel', 'stdio');
|
|
185
|
+
return args;
|
|
186
|
+
}
|
|
187
|
+
function writePendingApprovalDecision(state, status, render) {
|
|
188
|
+
const model = buildSafetyReadModel(state.events);
|
|
189
|
+
const actions = getApprovalActionsState({
|
|
190
|
+
source: state.source,
|
|
191
|
+
status: state.status,
|
|
192
|
+
mode: state.mode,
|
|
193
|
+
childStdinOpen: state.childStdinOpen,
|
|
194
|
+
pendingApprovalCount: model.pendingApprovalCount,
|
|
195
|
+
...(model.latestPendingApproval === undefined ? {} : { latestPendingApproval: model.latestPendingApproval }),
|
|
196
|
+
});
|
|
197
|
+
if (!actions.enabled)
|
|
198
|
+
return;
|
|
199
|
+
const wrote = state.decisionWriter?.write(actions.approval, status, status === 'approved' ? 'Approved from vgxcode.' : 'Denied from vgxcode.') ?? false;
|
|
200
|
+
if (!wrote)
|
|
201
|
+
return;
|
|
202
|
+
render();
|
|
203
|
+
}
|
|
204
|
+
function printableKeyText(sequence) {
|
|
205
|
+
if (sequence.length !== 1)
|
|
206
|
+
return '';
|
|
207
|
+
const code = sequence.charCodeAt(0);
|
|
208
|
+
return code >= 32 && code !== 127 ? sequence : '';
|
|
209
|
+
}
|
|
210
|
+
await main();
|