vgxness 1.2.0 → 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 +8 -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 +10 -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
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
export function helpText() {
|
|
2
2
|
return `Usage: vgxness <area> <command> [flags]
|
|
3
3
|
|
|
4
|
+
Global flags:
|
|
5
|
+
--help, -h Show this help.
|
|
6
|
+
--version, -v Print the installed package version.
|
|
7
|
+
|
|
4
8
|
Areas:
|
|
5
9
|
init [--project <name>] [--provider opencode|none] [--scope user|project] [--db global|project-local|custom|<path>] [--db-path <path>] [--mode mcp-only|mcp-plus-agents] [--json]
|
|
6
10
|
setup plan [--project <name>] [--provider opencode|none] [--scope user|project] [--db global|project-local|custom|<path>] [--db-path <path>] [--mode mcp-only|mcp-plus-agents] [--json]
|
|
@@ -87,14 +91,11 @@ Areas:
|
|
|
87
91
|
subagents list --parent-agent-id <id>
|
|
88
92
|
subagents get --id <id> | --project <name> --name <name> [--scope project|personal]
|
|
89
93
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
No args without a TTY prints static safe guidance and exits 0 without reading dashboard status or opening project state.
|
|
94
|
-
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.
|
|
95
|
-
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.
|
|
96
97
|
Provider setup support: OpenCode supported primary; Claude preview-only; Antigravity placeholder; Custom/future extension point.
|
|
97
|
-
|
|
98
|
+
Provider config writes/install/apply are external-only and require explicit confirmation.
|
|
98
99
|
|
|
99
100
|
sdd status --project <name> --change <id> [--json]
|
|
100
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
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
// Thin CLI router -- all command handlers live in src/cli/commands/*.ts
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
2
3
|
import { isWorkflowId } from '../workflows/schema.js';
|
|
3
4
|
import { databasePathFor, parseArgs, requiredFlag } from './cli-flags.js';
|
|
4
5
|
import { okText, usageFailure, visibleHelpText } from './cli-help.js';
|
|
5
6
|
import { openCliDatabase, resultFailure } from './cli-helpers.js';
|
|
6
|
-
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';
|
|
7
8
|
const _promptBuffers = new WeakMap();
|
|
9
|
+
const require = createRequire(import.meta.url);
|
|
10
|
+
const packageJson = require('../../package.json');
|
|
11
|
+
const cliVersion = typeof packageJson.version === 'string' ? packageJson.version : 'unknown';
|
|
8
12
|
export function dispatchCli(argv, environment) {
|
|
13
|
+
if (isGlobalVersionRequest(argv))
|
|
14
|
+
return okText(`${cliVersion}\n`);
|
|
9
15
|
const parsed = parseArgs(argv);
|
|
10
16
|
const [area, command] = parsed.positionals;
|
|
11
17
|
if (!area || area === 'help' || area === '--help' || area === '-h')
|
|
@@ -69,21 +75,20 @@ export function dispatchCli(argv, environment) {
|
|
|
69
75
|
return runOrchestratorCommand(command, parsed, opened.value);
|
|
70
76
|
if (area === 'opencode')
|
|
71
77
|
return runOpenCodeCommand(command, parsed, opened.value, environment);
|
|
72
|
-
if (area === 'dashboard')
|
|
73
|
-
return runDashboardCommand(command, parsed, opened.value, environment, databasePath);
|
|
74
78
|
return usageFailure(`Unknown command area: ${area}`);
|
|
75
79
|
}
|
|
76
80
|
finally {
|
|
77
81
|
opened.value.close();
|
|
78
82
|
}
|
|
79
83
|
}
|
|
84
|
+
function isGlobalVersionRequest(argv) {
|
|
85
|
+
return argv.length === 1 && (argv[0] === '--version' || argv[0] === '-v');
|
|
86
|
+
}
|
|
80
87
|
export async function dispatchCliAsync(argv, environment) {
|
|
81
88
|
const parsed = parseArgs(argv);
|
|
82
89
|
const [area, command] = parsed.positionals;
|
|
83
90
|
if (argv.length === 0)
|
|
84
91
|
return runDefaultInteractiveEntrypoint(environment);
|
|
85
|
-
if (area === 'dashboard' && command === 'interactive')
|
|
86
|
-
return runDashboardInteractiveCommand(parsed, environment);
|
|
87
92
|
if (area === 'mcp') {
|
|
88
93
|
if (!command)
|
|
89
94
|
return usageFailure('Missing command for mcp');
|
|
@@ -213,9 +218,6 @@ function validateCommand(area, command) {
|
|
|
213
218
|
if (area === 'verification') {
|
|
214
219
|
return command === 'plan' || command === 'report' ? { ok: true } : { ok: false, message: `Unknown verification command: ${command}` };
|
|
215
220
|
}
|
|
216
|
-
if (area === 'dashboard') {
|
|
217
|
-
return command === 'status' ? { ok: true } : { ok: false, message: `Unknown dashboard command: ${command}` };
|
|
218
|
-
}
|
|
219
221
|
if (area === 'code') {
|
|
220
222
|
return command === 'inspect' || command === 'plan' || command === 'craft' || command === 'sdd'
|
|
221
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();
|