vgxness 1.5.2 → 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.
- package/dist/agents/canonical-agent-projection.js +18 -0
- package/dist/agents/renderers/claude-renderer.js +3 -3
- package/dist/cli/cli-flags.js +1 -1
- package/dist/cli/cli-help.js +7 -7
- package/dist/cli/commands/interactive-entrypoint-dispatcher.js +2 -2
- package/dist/cli/commands/mcp-dispatcher.js +11 -1
- package/dist/cli/commands/setup-dispatcher.js +9 -0
- package/dist/cli/tui/main-menu/main-menu-read-model.js +41 -44
- package/dist/cli/tui/main-menu/main-menu-render-shape.js +15 -15
- package/dist/cli/tui/opentui/main-menu/screen.js +39 -41
- package/dist/cli/tui/opentui/main-menu/smoke.js +1 -1
- package/dist/cli/tui/opentui/main-menu/view.js +1 -1
- package/dist/cli/tui/setup/setup-tui-read-model.js +15 -12
- package/dist/mcp/claude-code-agent-config.js +23 -7
- package/dist/mcp/claude-code-cli.js +71 -0
- package/dist/mcp/claude-code-config.js +1 -1
- package/dist/mcp/claude-code-project-memory.js +127 -0
- package/dist/mcp/claude-code-scope.js +18 -0
- package/dist/mcp/client-install-claude-code-contract.js +40 -12
- package/dist/mcp/client-install-claude-code.js +61 -10
- package/dist/mcp/index.js +3 -0
- package/dist/mcp/provider-change-plan.js +56 -4
- package/dist/mcp/provider-doctor.js +55 -5
- package/dist/mcp/provider-health-types.js +4 -0
- package/dist/mcp/provider-status.js +84 -8
- package/dist/mcp/schema.js +4 -3
- package/dist/setup/providers/claude-setup-adapter.js +9 -7
- package/dist/setup/setup-plan.js +60 -1
- package/docs/architecture.md +2 -2
- package/docs/cli.md +37 -2
- package/docs/glossary.md +2 -2
- package/docs/prd.md +2 -2
- package/docs/providers.md +33 -6
- package/docs/roadmap.md +1 -1
- package/package.json +1 -1
|
@@ -73,6 +73,24 @@ export function projectCanonicalAgentManifestToClaudeCode(manifest = canonicalAg
|
|
|
73
73
|
}
|
|
74
74
|
return { defaultAgent: canonicalDefaultAgentName, agents };
|
|
75
75
|
}
|
|
76
|
+
export function projectCanonicalAgentManifestToClaudeProjectMemory(manifest = canonicalAgentManifest) {
|
|
77
|
+
assertValidCanonicalManifest(manifest);
|
|
78
|
+
return {
|
|
79
|
+
defaultAgent: canonicalDefaultAgentName,
|
|
80
|
+
promptContractVersion: canonicalPromptContractVersion,
|
|
81
|
+
repositoryInstructions: 'AGENTS.md',
|
|
82
|
+
guidance: [
|
|
83
|
+
`Use \`${canonicalDefaultAgentName}\` as the VGXNESS project manager/personality for this project.`,
|
|
84
|
+
'Follow project-root `AGENTS.md` when present.',
|
|
85
|
+
'Use VGXNESS MCP as durable state for SDD artifacts, runs, memory, agents, permissions, and provider diagnostics.',
|
|
86
|
+
'SDD artifact acceptance is human-only; never infer acceptance from generated output or file presence.',
|
|
87
|
+
'Before phase advancement, check VGXNESS SDD readiness/status.',
|
|
88
|
+
'Preserve confirmation and preflight requirements for risky side effects.',
|
|
89
|
+
'Do not tell users to run terminal SDD phase commands for normal daily SDD flow.',
|
|
90
|
+
'Be concise, evidence-grounded, user-controlled, practical, and willing to challenge unsafe assumptions.',
|
|
91
|
+
],
|
|
92
|
+
};
|
|
93
|
+
}
|
|
76
94
|
export function canonicalProviderProjection(provider, manifest = canonicalAgentManifest) {
|
|
77
95
|
assertValidCanonicalManifest(manifest);
|
|
78
96
|
const normalized = provider.trim().toLowerCase();
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { ok, validationFailure } from './provider-adapter.js';
|
|
2
2
|
const previewWarnings = [
|
|
3
|
-
'Claude rendering returns installable preview artifacts only; it does not install
|
|
3
|
+
'Claude rendering returns installable preview artifacts only; it does not install and does not write provider configuration.',
|
|
4
4
|
'VGX does not execute Claude or run Claude Code from this preview.',
|
|
5
|
-
'VGXNESS does not install Claude Code
|
|
5
|
+
'VGXNESS does not install Claude Code or write provider configuration from renderer previews; project-root CLAUDE.md is only managed by confirmed/preflighted Claude project install.',
|
|
6
6
|
'VGX does not guarantee exact Claude Code configuration compatibility; this output is a neutral preview only.',
|
|
7
7
|
];
|
|
8
8
|
const previewSafety = {
|
|
@@ -23,7 +23,7 @@ const claudeCapabilities = {
|
|
|
23
23
|
'Preview artifacts only; does not execute Claude or Claude Code.',
|
|
24
24
|
'Does not install Claude Code or provider tooling.',
|
|
25
25
|
'Renderer preview does not write .claude/, CLAUDE.md, or provider configuration.',
|
|
26
|
-
'Confirmed apply writes only project .mcp.json
|
|
26
|
+
'Confirmed apply writes only project .mcp.json, .claude/agents/*.md, and the project-root CLAUDE.md managed block.',
|
|
27
27
|
'Claude Code compatibility can drift; validate with provider doctor after confirmed install.',
|
|
28
28
|
],
|
|
29
29
|
render: {
|
package/dist/cli/cli-flags.js
CHANGED
|
@@ -315,7 +315,7 @@ function setupDatabaseFlags(flags) {
|
|
|
315
315
|
}
|
|
316
316
|
function setupProviderFlag(flags) {
|
|
317
317
|
const value = optionalStringFlag(flags, 'provider') ?? vgxnessSetupDefaults.defaultProvider;
|
|
318
|
-
return value === 'opencode' || value === 'none' ? { ok: true, value } : validationFailure('--provider must be opencode or none');
|
|
318
|
+
return value === 'opencode' || value === 'claude' || value === 'none' ? { ok: true, value } : validationFailure('--provider must be opencode, claude, or none');
|
|
319
319
|
}
|
|
320
320
|
function setupInstallModeFlag(flags) {
|
|
321
321
|
const value = optionalStringFlag(flags, 'mode');
|
package/dist/cli/cli-help.js
CHANGED
|
@@ -6,9 +6,9 @@ Global flags:
|
|
|
6
6
|
--version, -v Print the installed package version.
|
|
7
7
|
|
|
8
8
|
Areas:
|
|
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]
|
|
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]
|
|
11
|
-
setup apply --yes [--project <name>] [--provider opencode|none] [--scope user|project] [--db global|project-local|custom|<path>] [--db-path <path>] [--mode mcp-only|mcp-plus-agents]
|
|
9
|
+
init [--project <name>] [--provider opencode|claude|none] [--scope user|project] [--db global|project-local|custom|<path>] [--db-path <path>] [--mode mcp-only|mcp-plus-agents] [--json]
|
|
10
|
+
setup plan [--project <name>] [--provider opencode|claude|none] [--scope user|project] [--db global|project-local|custom|<path>] [--db-path <path>] [--mode mcp-only|mcp-plus-agents] [--json]
|
|
11
|
+
setup apply --yes [--project <name>] [--provider opencode|claude|none] [--scope user|project] [--db global|project-local|custom|<path>] [--db-path <path>] [--mode mcp-only|mcp-plus-agents]
|
|
12
12
|
setup backup list --provider opencode [--scope project|user|global] [--target <path>] [--json]
|
|
13
13
|
setup rollback --backup <path> [--preview|--yes] [--json]
|
|
14
14
|
setup status [--project <name>] [--scope project|personal] [--db <path>] [--json]
|
|
@@ -59,8 +59,8 @@ Areas:
|
|
|
59
59
|
mcp setup --preview --provider opencode|claude [--db <path>]
|
|
60
60
|
mcp install opencode --plan [--scope user|project] [--db <path>] [--mcp-only|--no-agents] [--overwrite-vgxness|--reinstall]
|
|
61
61
|
mcp install opencode --yes [--scope user|project] [--db <path>] [--mcp-only|--no-agents] [--overwrite-vgxness|--reinstall]
|
|
62
|
-
mcp install claude --plan [--db <path>] [--overwrite-vgxness|--reinstall]
|
|
63
|
-
mcp install claude --yes [--db <path>] [--overwrite-vgxness|--reinstall]
|
|
62
|
+
mcp install claude --plan [--scope local|project|user] [--db <path>] [--overwrite-vgxness|--reinstall]
|
|
63
|
+
mcp install claude --yes --run-id <id> [--scope local|project|user] [--db <path>] [--phase <phase>] [--agent-id <id>] [--overwrite-vgxness|--reinstall]
|
|
64
64
|
mcp doctor opencode [--scope user|project] [--project-root <path>]
|
|
65
65
|
mcp doctor [--db <path>] [--project <name>] [--change <id>] [--timeout-ms <ms>]
|
|
66
66
|
MCP setup preview is read-only; it does not install or write .opencode/, .claude/, or provider config.
|
|
@@ -69,7 +69,7 @@ Areas:
|
|
|
69
69
|
Use --overwrite-vgxness (alias --reinstall) to reinstall only VGXNESS-managed OpenCode entries while preserving unrelated config; --yes is still required to write.
|
|
70
70
|
It writes only after --yes. The default target is $HOME/.config/opencode/opencode.json; use --scope project to target .opencode/opencode.json explicitly.
|
|
71
71
|
Project OpenCode config can override user config. Plans are read-only; applies refuse unsafe existing config and create backups before merge.
|
|
72
|
-
Claude
|
|
72
|
+
Claude support is secondary. Claude scopes are local|project|user; compatibility aliases personal/global map to user with warnings. Plans show Claude CLI argv, project .mcp.json compatibility, agents, and guarded project-root CLAUDE.md managed memory separately. Project scope confirmed applies write .mcp.json, .claude/agents/*.md, and the CLAUDE.md managed block as needed. Confirmed Claude writes/CLI execution require VGXNESS run preflight metadata (--run-id, with optional --phase/--agent-id). VGXNESS does not manually read/write ~/.claude.json or private Claude config; status/doctor/change-plan are read-only and do not execute Claude Code.
|
|
73
73
|
|
|
74
74
|
skills register --project <name> --name <name> --description <text>
|
|
75
75
|
skills list [--project <name>] [--scope project|personal]
|
|
@@ -98,7 +98,7 @@ Areas:
|
|
|
98
98
|
No args in an interactive TTY opens the OpenTUI main menu.
|
|
99
99
|
No args without a TTY prints static safe setup guidance and exits 0 without opening project state.
|
|
100
100
|
Setup TUI may launch without --project; Installation remains available and project-scoped checks are deferred while project screens render project-required recovery states.
|
|
101
|
-
Provider setup support: OpenCode supported primary; Claude
|
|
101
|
+
Provider setup support: OpenCode supported primary/default; Claude supported secondary for CLI MCP registration, project compatibility, and project/user agent planning; Antigravity placeholder; Custom/future extension point.
|
|
102
102
|
Provider config writes/install/apply are external-only and require explicit confirmation.
|
|
103
103
|
|
|
104
104
|
sdd status --project <name> --change <id> [--json]
|
|
@@ -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
|
|
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
|
|
28
|
+
return 'Exited dashboard; no provider config was written.\n';
|
|
29
29
|
return undefined;
|
|
30
30
|
}
|
|
31
31
|
async function renderDefaultMainMenu(environment, onResult) {
|
|
@@ -5,6 +5,7 @@ import { planOpenCodeMcpInstall } from '../../mcp/client-install-opencode-contra
|
|
|
5
5
|
import { createMcpClientSetupPreview, isMcpClientSetupProvider } from '../../mcp/client-setup-preview.js';
|
|
6
6
|
import { createMcpDoctorReport } from '../../mcp/doctor.js';
|
|
7
7
|
import { createOpenCodeMcpVisibilityReport } from '../../mcp/opencode-visibility.js';
|
|
8
|
+
import { resolveClaudeCodeScope } from '../../mcp/claude-code-scope.js';
|
|
8
9
|
import { RunService } from '../../runs/run-service.js';
|
|
9
10
|
import { databasePathFor, databasePathSelectionFor, opencodeInstallScopeFlag, optionalNumberFlag, optionalStringFlag } from '../cli-flags.js';
|
|
10
11
|
import { usageFailure, validationFailure } from '../cli-help.js';
|
|
@@ -24,8 +25,11 @@ export function runMcpInstallCommand(parsed, environment) {
|
|
|
24
25
|
const mcpOnly = parsed.flags['mcp-only'] === true || parsed.flags['no-agents'] === true;
|
|
25
26
|
const overwriteVgxness = parsed.flags['overwrite-vgxness'] === true || parsed.flags.reinstall === true;
|
|
26
27
|
if (client === 'claude') {
|
|
28
|
+
const claudeScope = claudeInstallScopeFlag(parsed.flags);
|
|
29
|
+
if (!claudeScope.ok)
|
|
30
|
+
return resultFailure(claudeScope);
|
|
27
31
|
if (parsed.flags.plan === true) {
|
|
28
|
-
return jsonResult({ ok: true, value: planClaudeCodeMcpInstall({ cwd: environment.cwd, databasePath: databasePath.value.path, databasePathSource: databasePath.value.source, overwriteVgxness }) });
|
|
32
|
+
return jsonResult({ ok: true, value: planClaudeCodeMcpInstall({ cwd: environment.cwd, databasePath: databasePath.value.path, databasePathSource: databasePath.value.source, overwriteVgxness, scope: claudeScope.value }) });
|
|
29
33
|
}
|
|
30
34
|
if (parsed.flags.yes !== true) {
|
|
31
35
|
return resultFailure({ ok: false, error: { code: 'validation_failed', message: 'confirmation_required: `mcp install claude` requires explicit --yes before any project config write.' } });
|
|
@@ -44,6 +48,7 @@ export function runMcpInstallCommand(parsed, environment) {
|
|
|
44
48
|
databasePathSource: databasePath.value.source,
|
|
45
49
|
confirmed: true,
|
|
46
50
|
overwriteVgxness,
|
|
51
|
+
scope: claudeScope.value,
|
|
47
52
|
preflight,
|
|
48
53
|
});
|
|
49
54
|
return result.status === 'installed' ? jsonResult({ ok: true, value: result }) : resultFailure({ ok: false, error: { code: 'validation_failed', message: `${result.reason}: ${result.message}` } });
|
|
@@ -87,6 +92,11 @@ export function runMcpInstallCommand(parsed, environment) {
|
|
|
87
92
|
});
|
|
88
93
|
})();
|
|
89
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
|
+
}
|
|
90
100
|
function createClaudeCodeInstallPreflight(parsed, database, environment) {
|
|
91
101
|
return (request) => {
|
|
92
102
|
const runId = optionalStringFlag(parsed.flags, 'run-id');
|
|
@@ -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,
|
|
@@ -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
|
-
'
|
|
6
|
-
'
|
|
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: '
|
|
13
|
-
description: '
|
|
22
|
+
label: 'Setup OpenCode',
|
|
23
|
+
description: 'guided install; confirm before writes',
|
|
14
24
|
badges: [tuiBadges.requiresConfirmation],
|
|
15
|
-
detailTitle: '
|
|
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: '
|
|
30
|
+
description: 'health and repair commands',
|
|
25
31
|
badges: [tuiBadges.previewOnly],
|
|
26
|
-
detailTitle: 'Doctor
|
|
27
|
-
detailLines: ['Shows
|
|
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
|
|
31
|
-
description: '
|
|
36
|
+
label: 'SDD workflow',
|
|
37
|
+
description: 'artifacts, phases, next step',
|
|
32
38
|
badges: [tuiBadges.previewOnly],
|
|
33
|
-
detailTitle: 'SDD
|
|
34
|
-
detailLines: ['Use OpenCode conversation plus VGXNESS MCP for daily
|
|
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: '
|
|
44
|
+
description: 'manual commands, no automation',
|
|
39
45
|
badges: [tuiBadges.manual],
|
|
40
46
|
detailTitle: 'Advanced CLI',
|
|
41
|
-
detailLines: ['
|
|
47
|
+
detailLines: ['Prints explicit command references; no provider config is written.'],
|
|
42
48
|
},
|
|
43
49
|
exit: {
|
|
44
|
-
label: '
|
|
45
|
-
description: '
|
|
50
|
+
label: 'Quit',
|
|
51
|
+
description: 'leave cleanly',
|
|
46
52
|
badges: [tuiBadges.exit, tuiBadges.noProviderWrites],
|
|
47
|
-
detailTitle: '
|
|
48
|
-
detailLines: ['Close the
|
|
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:
|
|
62
|
-
subtitle:
|
|
63
|
-
|
|
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: '
|
|
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
|
|
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:
|
|
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.
|
|
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
|
-
|
|
13
|
+
`Stats: ${stats}`,
|
|
14
|
+
'Actions',
|
|
11
15
|
...vm.options.map(choiceLine),
|
|
12
|
-
`
|
|
16
|
+
`Focused detail: ${vm.detail.title}${vm.detail.badges.length === 0 ? '' : ` ${vm.detail.badges.join(' ')}`}`,
|
|
13
17
|
...vm.detail.lines,
|
|
14
|
-
`${vm.statusSnapshot.title}
|
|
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(
|
|
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
|
-
},
|
|
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
|
-
]),
|
|
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: '#
|
|
33
|
+
borderColor: '#7DD3FC',
|
|
65
34
|
paddingX: 1,
|
|
66
|
-
title:
|
|
67
|
-
}, Text({ content: vm.
|
|
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
|
|
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(
|
|
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
|
-
:
|
|
43
|
-
|
|
44
|
-
|
|
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
|
|
48
|
-
: 'Manual / none is read-only guidance
|
|
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', '
|
|
79
|
-
choice('provider:claude-
|
|
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
|
|
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
|
|
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
|
];
|
|
@@ -1,22 +1,38 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
2
3
|
import { join, resolve } from 'node:path';
|
|
3
4
|
import { projectCanonicalAgentManifestToClaudeCode } from '../agents/canonical-agent-projection.js';
|
|
4
5
|
import { assertInsideWorkspace } from './claude-code-config.js';
|
|
5
6
|
export const claudeCodeGeneratedMarker = 'VGXNESS-GENERATED';
|
|
6
|
-
export function expectedClaudeCodeAgentFiles(
|
|
7
|
+
export function expectedClaudeCodeAgentFiles(input) {
|
|
8
|
+
const target = typeof input === 'string' ? resolveClaudeAgentTarget({ workspaceRoot: input, scope: 'project' }) : resolveClaudeAgentTarget(input);
|
|
7
9
|
return projectCanonicalAgentManifestToClaudeCode().agents.map((agent) => {
|
|
8
|
-
const path = resolve(
|
|
9
|
-
|
|
10
|
+
const path = resolve(target.directoryPath, agent.fileName);
|
|
11
|
+
if (target.scope === 'project')
|
|
12
|
+
assertInsideWorkspace(target.workspaceRoot, path);
|
|
10
13
|
return { ...agent, path };
|
|
11
14
|
});
|
|
12
15
|
}
|
|
13
16
|
export function renderClaudeCodeAgentMarkdown(agent) {
|
|
14
17
|
return `---\nname: ${yamlScalar(agent.name)}\ndescription: ${yamlScalar(agent.description)}\n---\n\n<!-- ${claudeCodeGeneratedMarker} claude-code-provider-support provider=claude artifact=claude-code-subagent promptContractVersion=${agent.promptContractVersion} safe-update=true -->\n\n${agent.instructions.trim()}\n`;
|
|
15
18
|
}
|
|
16
|
-
export function inspectClaudeCodeAgents(
|
|
17
|
-
const
|
|
18
|
-
const expected = expectedClaudeCodeAgentFiles(workspaceRoot);
|
|
19
|
-
return { directoryPath, directoryExists: existsSync(directoryPath), agents: expected.map(inspectAgent) };
|
|
19
|
+
export function inspectClaudeCodeAgents(input) {
|
|
20
|
+
const target = typeof input === 'string' ? resolveClaudeAgentTarget({ workspaceRoot: input, scope: 'project' }) : resolveClaudeAgentTarget(input);
|
|
21
|
+
const expected = expectedClaudeCodeAgentFiles({ workspaceRoot: target.workspaceRoot, scope: target.scope, ...(target.env === undefined ? {} : { env: target.env }) });
|
|
22
|
+
return { scope: target.scope, directoryPath: target.directoryPath, directoryExists: existsSync(target.directoryPath), external: target.scope === 'user', agents: expected.map(inspectAgent) };
|
|
23
|
+
}
|
|
24
|
+
export function resolveClaudeAgentTarget(input) {
|
|
25
|
+
const scope = input.scope ?? 'project';
|
|
26
|
+
if (scope === 'project')
|
|
27
|
+
return { workspaceRoot: input.workspaceRoot, scope, directoryPath: join(input.workspaceRoot, '.claude', 'agents'), ...(input.env === undefined ? {} : { env: input.env }) };
|
|
28
|
+
const home = safeHomeDirectory(input.env);
|
|
29
|
+
return { workspaceRoot: input.workspaceRoot, scope, directoryPath: join(home, '.claude', 'agents'), ...(input.env === undefined ? {} : { env: input.env }) };
|
|
30
|
+
}
|
|
31
|
+
export function safeHomeDirectory(env = process.env) {
|
|
32
|
+
const candidate = env.HOME?.trim() || homedir();
|
|
33
|
+
if (!candidate || candidate === '/' || candidate.includes('\0') || candidate.includes('~'))
|
|
34
|
+
throw new Error('Unable to resolve a safe home directory for Claude user agents.');
|
|
35
|
+
return resolve(candidate);
|
|
20
36
|
}
|
|
21
37
|
export function isVgxnessOwnedClaudeAgentMarkdown(contents) {
|
|
22
38
|
return contents.includes(claudeCodeGeneratedMarker) && contents.includes('provider=claude') && contents.includes('artifact=claude-code-subagent');
|