vgxness 1.4.0 → 1.5.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/cli/cli-help.js +3 -2
- package/dist/cli/commands/mcp-dispatcher.js +3 -0
- package/dist/cli/commands/setup-dispatcher.js +9 -0
- package/dist/cli/setup-plan-renderer.js +1 -0
- package/dist/cli/tui/setup/setup-tui-controller.js +7 -3
- package/dist/cli/tui/setup/setup-tui-read-model.js +9 -1
- package/dist/cli/tui/setup/setup-tui-render-shape.js +3 -0
- package/dist/cli/tui/setup/setup-tui-services.js +2 -0
- package/dist/cli/tui/setup/setup-tui-state.js +9 -2
- package/dist/mcp/client-install-opencode-contract.js +43 -29
- package/dist/mcp/client-install-opencode.js +15 -12
- package/dist/setup/providers/opencode-setup-adapter.js +23 -7
- package/dist/setup/setup-plan.js +7 -2
- package/package.json +1 -1
package/dist/cli/cli-help.js
CHANGED
|
@@ -57,13 +57,14 @@ Areas:
|
|
|
57
57
|
Approval commands resolve explicit run/preflight approval records; they do not change agent seed permissions or globally allow shell/provider access.
|
|
58
58
|
|
|
59
59
|
mcp setup --preview --provider opencode|claude [--db <path>]
|
|
60
|
-
mcp install opencode --plan [--scope user|project] [--db <path>] [--mcp-only|--no-agents]
|
|
61
|
-
mcp install opencode --yes [--scope user|project] [--db <path>] [--mcp-only|--no-agents]
|
|
60
|
+
mcp install opencode --plan [--scope user|project] [--db <path>] [--mcp-only|--no-agents] [--overwrite-vgxness|--reinstall]
|
|
61
|
+
mcp install opencode --yes [--scope user|project] [--db <path>] [--mcp-only|--no-agents] [--overwrite-vgxness|--reinstall]
|
|
62
62
|
mcp doctor opencode [--scope user|project] [--project-root <path>]
|
|
63
63
|
mcp doctor [--db <path>] [--project <name>] [--change <id>] [--timeout-ms <ms>]
|
|
64
64
|
MCP setup preview is read-only; it does not install or write .opencode/, .claude/, or provider config.
|
|
65
65
|
Without --db, MCP install and setup commands use the vgxness global default database; pass --db .vgx/memory.sqlite for project-local compatibility.
|
|
66
66
|
OpenCode install defaults to user/global scope and installs mcp.vgxness plus vgxness-manager and hidden vgxness-sdd-* agents; use --mcp-only for legacy MCP-only config.
|
|
67
|
+
Use --overwrite-vgxness (alias --reinstall) to reinstall only VGXNESS-managed OpenCode entries while preserving unrelated config; --yes is still required to write.
|
|
67
68
|
It writes only after --yes. The default target is $HOME/.config/opencode/opencode.json; use --scope project to target .opencode/opencode.json explicitly.
|
|
68
69
|
Project OpenCode config can override user config. Plans are read-only; applies refuse unsafe existing config and create backups before merge.
|
|
69
70
|
|
|
@@ -19,6 +19,7 @@ export function runMcpInstallCommand(parsed, environment) {
|
|
|
19
19
|
if (!scope.ok)
|
|
20
20
|
return resultFailure(scope);
|
|
21
21
|
const mcpOnly = parsed.flags['mcp-only'] === true || parsed.flags['no-agents'] === true;
|
|
22
|
+
const overwriteVgxness = parsed.flags['overwrite-vgxness'] === true || parsed.flags.reinstall === true;
|
|
22
23
|
if (parsed.flags.plan === true) {
|
|
23
24
|
return jsonResult({
|
|
24
25
|
ok: true,
|
|
@@ -29,6 +30,7 @@ export function runMcpInstallCommand(parsed, environment) {
|
|
|
29
30
|
scope: scope.value,
|
|
30
31
|
env: environment.env,
|
|
31
32
|
mcpOnly,
|
|
33
|
+
overwriteVgxness,
|
|
32
34
|
}),
|
|
33
35
|
});
|
|
34
36
|
}
|
|
@@ -40,6 +42,7 @@ export function runMcpInstallCommand(parsed, environment) {
|
|
|
40
42
|
env: environment.env,
|
|
41
43
|
confirmed: parsed.flags.yes === true,
|
|
42
44
|
mcpOnly,
|
|
45
|
+
overwriteVgxness,
|
|
43
46
|
});
|
|
44
47
|
return result.status === 'installed'
|
|
45
48
|
? jsonResult({ ok: true, value: result })
|
|
@@ -143,6 +143,7 @@ export async function applySetupPlanInput(input, environment) {
|
|
|
143
143
|
env: environment.env,
|
|
144
144
|
confirmed: true,
|
|
145
145
|
mcpOnly: input.installMode === 'mcp-only',
|
|
146
|
+
...(input.overwriteVgxness === undefined ? {} : { overwriteVgxness: input.overwriteVgxness }),
|
|
146
147
|
});
|
|
147
148
|
return result.status === 'installed'
|
|
148
149
|
? {
|
|
@@ -172,6 +173,7 @@ export function setupPlanInputFromFlags(parsed, environment) {
|
|
|
172
173
|
const installMode = setupInstallModeFlag(parsed.flags);
|
|
173
174
|
if (!installMode.ok)
|
|
174
175
|
return installMode;
|
|
176
|
+
const overwriteVgxness = parsed.flags['overwrite-vgxness'] === true || parsed.flags.reinstall === true;
|
|
175
177
|
return {
|
|
176
178
|
ok: true,
|
|
177
179
|
value: {
|
|
@@ -183,6 +185,7 @@ export function setupPlanInputFromFlags(parsed, environment) {
|
|
|
183
185
|
provider: provider.value,
|
|
184
186
|
scope: scope.value,
|
|
185
187
|
installMode: installMode.value,
|
|
188
|
+
overwriteVgxness,
|
|
186
189
|
},
|
|
187
190
|
};
|
|
188
191
|
}
|
|
@@ -196,6 +199,7 @@ function defaultWizardInput(environment) {
|
|
|
196
199
|
provider: vgxnessSetupDefaults.defaultProvider,
|
|
197
200
|
scope: vgxnessSetupDefaults.defaultOpenCodeScope,
|
|
198
201
|
installMode: vgxnessSetupDefaults.defaultInstallMode,
|
|
202
|
+
overwriteVgxness: false,
|
|
199
203
|
};
|
|
200
204
|
}
|
|
201
205
|
function defaulted(value, fallback) {
|
|
@@ -281,6 +285,8 @@ export function promptSetupWizard(environment, defaults) {
|
|
|
281
285
|
: undefined;
|
|
282
286
|
if (installMode === undefined)
|
|
283
287
|
return validationFailure('Install mode must be MCP + manager/SDD agents or MCP only.');
|
|
288
|
+
const overwriteText = defaulted(await promptLine(environment, 'Reinstall VGXNESS entries: overwrite existing mcp.vgxness and VGXNESS agents after final confirmation; preserve unrelated OpenCode config [no]: '), 'no');
|
|
289
|
+
const overwriteVgxness = overwriteText.toLowerCase() === 'yes' || overwriteText.toLowerCase() === 'y' || overwriteText.toLowerCase() === 'true';
|
|
284
290
|
const input = {
|
|
285
291
|
project,
|
|
286
292
|
workspaceRoot: environment.cwd,
|
|
@@ -290,6 +296,7 @@ export function promptSetupWizard(environment, defaults) {
|
|
|
290
296
|
provider,
|
|
291
297
|
scope,
|
|
292
298
|
installMode,
|
|
299
|
+
overwriteVgxness,
|
|
293
300
|
};
|
|
294
301
|
const flags = {
|
|
295
302
|
project,
|
|
@@ -298,6 +305,7 @@ export function promptSetupWizard(environment, defaults) {
|
|
|
298
305
|
scope,
|
|
299
306
|
mode: installMode,
|
|
300
307
|
yes: true,
|
|
308
|
+
...(overwriteVgxness ? { 'overwrite-vgxness': true } : {}),
|
|
301
309
|
...(databasePath === undefined ? {} : { 'db-path': databasePath }),
|
|
302
310
|
};
|
|
303
311
|
return { ok: true, value: { input, flags } };
|
|
@@ -355,6 +363,7 @@ export async function runSetupTuiCommand(environment, input) {
|
|
|
355
363
|
provider: vgxnessSetupDefaults.defaultProvider,
|
|
356
364
|
scope: vgxnessSetupDefaults.defaultOpenCodeScope,
|
|
357
365
|
installMode: vgxnessSetupDefaults.defaultInstallMode,
|
|
366
|
+
overwriteVgxness: false,
|
|
358
367
|
},
|
|
359
368
|
};
|
|
360
369
|
try {
|
|
@@ -23,6 +23,7 @@ function renderOpenCode(plan) {
|
|
|
23
23
|
`- Action: ${plan.opencode.action}`,
|
|
24
24
|
`- Target path: ${target}`,
|
|
25
25
|
`- Installs agents: ${String(plan.opencode.installsAgents)}`,
|
|
26
|
+
`- Reinstall VGXNESS entries: ${String(plan.opencode.overwriteVgxness)}`,
|
|
26
27
|
`- Agent names: ${plan.opencode.agentNames.length === 0 ? 'none' : plan.opencode.agentNames.join(', ')}`,
|
|
27
28
|
];
|
|
28
29
|
}
|
|
@@ -59,20 +59,24 @@ function canApplyCurrentState(previous, reduced) {
|
|
|
59
59
|
if (plan?.provider !== 'opencode' || plan.status !== 'ready' || plan.opencode === undefined)
|
|
60
60
|
return false;
|
|
61
61
|
const installMode = plan.opencode.installsAgents === false ? 'mcp-only' : 'mcp-plus-agents';
|
|
62
|
-
return plan.opencode.scope === reduced.selections.scope &&
|
|
62
|
+
return (plan.opencode.scope === reduced.selections.scope &&
|
|
63
|
+
installMode === reduced.selections.installMode &&
|
|
64
|
+
(plan.opencode.overwriteVgxness === true) === (reduced.selections.overwriteVgxness === true));
|
|
63
65
|
}
|
|
64
66
|
function shouldRefreshPlan(previous, next, action) {
|
|
65
67
|
if (action.type !== 'select-focused-choice' &&
|
|
66
68
|
action.type !== 'select-database-mode' &&
|
|
67
69
|
action.type !== 'select-provider' &&
|
|
68
70
|
action.type !== 'select-scope' &&
|
|
69
|
-
action.type !== 'select-install-mode'
|
|
71
|
+
action.type !== 'select-install-mode' &&
|
|
72
|
+
action.type !== 'toggle-overwrite-vgxness')
|
|
70
73
|
return false;
|
|
71
74
|
return (previous.selections.databaseMode !== next.selections.databaseMode ||
|
|
72
75
|
previous.selections.databasePath !== next.selections.databasePath ||
|
|
73
76
|
previous.selections.provider !== next.selections.provider ||
|
|
74
77
|
previous.selections.scope !== next.selections.scope ||
|
|
75
|
-
previous.selections.installMode !== next.selections.installMode
|
|
78
|
+
previous.selections.installMode !== next.selections.installMode ||
|
|
79
|
+
(previous.selections.overwriteVgxness === true) !== (next.selections.overwriteVgxness === true));
|
|
76
80
|
}
|
|
77
81
|
function refreshPlan(state, services, runtime) {
|
|
78
82
|
const plan = services.createPlan(setupPlanInputFromTui(runtime));
|
|
@@ -59,7 +59,9 @@ export function setupTuiViewModelFromPlan(plan, status, state) {
|
|
|
59
59
|
backupLabel: backups.length === 0 ? 'Backup: none planned' : `Backup planned: ${backups.join('; ')}`,
|
|
60
60
|
nextCommands: plan?.nextCommands ?? ['vgxness setup plan'],
|
|
61
61
|
canAutoApply: plan?.provider === 'opencode' && plan.status === 'ready' && state?.selections.provider !== 'none' && plan.opencode !== undefined,
|
|
62
|
-
safetyWarning:
|
|
62
|
+
safetyWarning: isOpenCode && (selections?.overwriteVgxness === true || opencode?.overwriteVgxness === true)
|
|
63
|
+
? 'Final confirmation is required. Reinstall is enabled: VGXNESS OpenCode entries will be overwritten after a managed backup when the target exists; unrelated config is preserved.'
|
|
64
|
+
: 'Final confirmation is required before any provider config write. OpenCode config may be modified and backed up.',
|
|
63
65
|
frameLabel: 'VGXNESS Setup Assistant workspace',
|
|
64
66
|
progressLabel: progressLabel(state?.screen),
|
|
65
67
|
previewLabel,
|
|
@@ -89,6 +91,11 @@ export function setupTuiViewModelFromPlan(plan, status, state) {
|
|
|
89
91
|
choice('install:mcp-only', 'MCP only', 'Install only the MCP server entry after confirmation.', (selections?.installMode ?? (opencode?.installsAgents === false ? 'mcp-only' : 'mcp-plus-agents')) === 'mcp-only', state?.focusedChoiceId, [badgeLabels.writeAfterConfirm]),
|
|
90
92
|
]
|
|
91
93
|
: [],
|
|
94
|
+
overwriteChoices: isOpenCode
|
|
95
|
+
? [
|
|
96
|
+
choice('overwrite:vgxness', 'Reinstall VGXNESS entries', 'Default off. When enabled, final confirmation overwrites existing VGXNESS OpenCode entries and preserves unrelated OpenCode config.', selections?.overwriteVgxness ?? opencode?.overwriteVgxness ?? false, state?.focusedChoiceId, [badgeLabels.warning, badgeLabels.writeAfterConfirm]),
|
|
97
|
+
]
|
|
98
|
+
: [],
|
|
92
99
|
};
|
|
93
100
|
}
|
|
94
101
|
function previewDetailLines(input) {
|
|
@@ -101,6 +108,7 @@ function previewDetailLines(input) {
|
|
|
101
108
|
`Memory guidance: ${memoryPathExplanation(plan, input.databasePath, input.databaseSource)}`,
|
|
102
109
|
`Scope: ${input.isOpenCode ? (opencode?.scope ?? 'user') : 'disabled for manual/none provider'}`,
|
|
103
110
|
`Install mode: ${input.isOpenCode ? (opencode?.installsAgents === false ? 'mcp-only' : 'mcp-plus-agents') : 'disabled for manual/none provider'}`,
|
|
111
|
+
`Reinstall VGXNESS entries: ${input.isOpenCode ? String(opencode?.overwriteVgxness ?? false) : 'disabled for manual/none provider'}`,
|
|
104
112
|
`Provider installability: ${input.isOpenCode ? 'OpenCode installable after final confirmation; Claude deferred/not installable.' : 'No provider installable; OpenCode controls disabled and Claude deferred.'}`,
|
|
105
113
|
`Agent readiness: ${agentReadinessFromPlan(plan)}`,
|
|
106
114
|
`Target config: ${input.isOpenCode && opencode?.targetPath !== undefined ? compactPath(opencode.targetPath, 72) : 'none; no provider config will be written'}`,
|
|
@@ -40,6 +40,7 @@ function screenLines(input, width) {
|
|
|
40
40
|
'OpenCode plan ' + badgeLabels.writeAfterConfirm,
|
|
41
41
|
...choiceLines(vm.scopeChoices),
|
|
42
42
|
...choiceLines(vm.installModeChoices),
|
|
43
|
+
...choiceLines(vm.overwriteChoices),
|
|
43
44
|
`Scope: ${vm.scopeLabel}`,
|
|
44
45
|
`Install mode: ${vm.installModeLabel}`,
|
|
45
46
|
`Target config: ${compactPath(vm.targetPathLabel, width - 15)}`,
|
|
@@ -70,6 +71,7 @@ function screenLines(input, width) {
|
|
|
70
71
|
'Confirm OpenCode setup',
|
|
71
72
|
...planLines(vm, width),
|
|
72
73
|
'WARNING: OpenCode provider config may be modified at the target path above. Existing config is backed up when planned.',
|
|
74
|
+
vm.safetyWarning,
|
|
73
75
|
'After confirmation: run doctor, then restart OpenCode to reload MCP configuration.',
|
|
74
76
|
'Press Enter to confirm and apply OpenCode setup, or Esc/q to cancel without writing.',
|
|
75
77
|
], width, [setupFooterHints.confirmApply, setupFooterHints.back, setupFooterHints.cancel]);
|
|
@@ -98,6 +100,7 @@ function planLines(vm, width) {
|
|
|
98
100
|
`Database path: ${compactPath(vm.databasePathLabel, width - 15)}`,
|
|
99
101
|
`OpenCode scope: ${vm.scopeLabel}`,
|
|
100
102
|
`Install mode: ${vm.installModeLabel}`,
|
|
103
|
+
vm.safetyWarning,
|
|
101
104
|
`Provider installability: ${vm.providerInstallabilityLabel}`,
|
|
102
105
|
`Agent readiness: ${vm.agentReadinessLabel}`,
|
|
103
106
|
`Agent detail: ${vm.agentReadinessDetail}`,
|
|
@@ -11,6 +11,7 @@ export function setupPlanInputFromTui(input) {
|
|
|
11
11
|
provider: input.selections.provider,
|
|
12
12
|
scope: input.selections.scope,
|
|
13
13
|
installMode: input.selections.installMode,
|
|
14
|
+
...(input.selections.overwriteVgxness === undefined ? {} : { overwriteVgxness: input.selections.overwriteVgxness }),
|
|
14
15
|
};
|
|
15
16
|
}
|
|
16
17
|
export function createDefaultSetupTuiServices(input) {
|
|
@@ -44,6 +45,7 @@ export async function applySetupTuiPlan(planInput, runtime) {
|
|
|
44
45
|
env: runtime.env,
|
|
45
46
|
confirmed: true,
|
|
46
47
|
mcpOnly: planInput.installMode === 'mcp-only',
|
|
48
|
+
...(planInput.overwriteVgxness === undefined ? {} : { overwriteVgxness: planInput.overwriteVgxness }),
|
|
47
49
|
});
|
|
48
50
|
if (installed.status !== 'installed')
|
|
49
51
|
return { ok: false, error: { code: 'validation_failed', message: `${installed.reason}: ${installed.message}` } };
|
|
@@ -4,7 +4,7 @@ const screenChoiceIds = {
|
|
|
4
4
|
welcome: [],
|
|
5
5
|
'project-database': ['database:global', 'database:project-local', 'database:custom'],
|
|
6
6
|
provider: ['provider:opencode', 'provider:none'],
|
|
7
|
-
'opencode-details': ['scope:project', 'scope:user', 'install:mcp-plus-agents', 'install:mcp-only'],
|
|
7
|
+
'opencode-details': ['scope:project', 'scope:user', 'install:mcp-plus-agents', 'install:mcp-only', 'overwrite:vgxness'],
|
|
8
8
|
'plan-review': [],
|
|
9
9
|
'final-confirmation': [],
|
|
10
10
|
applying: [],
|
|
@@ -20,6 +20,7 @@ export function createSetupTuiState(input = {}) {
|
|
|
20
20
|
provider: vgxnessSetupDefaults.defaultProvider,
|
|
21
21
|
scope: vgxnessSetupDefaults.defaultOpenCodeScope,
|
|
22
22
|
installMode: vgxnessSetupDefaults.defaultInstallMode,
|
|
23
|
+
overwriteVgxness: false,
|
|
23
24
|
},
|
|
24
25
|
...(input.focusedChoiceId === undefined ? {} : { focusedChoiceId: input.focusedChoiceId }),
|
|
25
26
|
helpVisible: input.helpVisible ?? false,
|
|
@@ -55,6 +56,8 @@ export function reduceSetupTuiState(state, action) {
|
|
|
55
56
|
return { ...state, selections: { ...state.selections, scope: action.scope } };
|
|
56
57
|
case 'select-install-mode':
|
|
57
58
|
return { ...state, selections: { ...state.selections, installMode: action.installMode } };
|
|
59
|
+
case 'toggle-overwrite-vgxness':
|
|
60
|
+
return { ...state, selections: { ...state.selections, overwriteVgxness: state.selections.overwriteVgxness !== true } };
|
|
58
61
|
case 'refresh-plan-start':
|
|
59
62
|
return { ...state, previewRefresh: 'refreshing' };
|
|
60
63
|
case 'refresh-plan-success':
|
|
@@ -131,6 +134,8 @@ function selectFocusedChoice(state) {
|
|
|
131
134
|
return reduceSetupTuiState(state, { type: 'select-install-mode', installMode: 'mcp-plus-agents' });
|
|
132
135
|
case 'install:mcp-only':
|
|
133
136
|
return reduceSetupTuiState(state, { type: 'select-install-mode', installMode: 'mcp-only' });
|
|
137
|
+
case 'overwrite:vgxness':
|
|
138
|
+
return reduceSetupTuiState(state, { type: 'toggle-overwrite-vgxness' });
|
|
134
139
|
default:
|
|
135
140
|
return state;
|
|
136
141
|
}
|
|
@@ -154,7 +159,9 @@ function canAutoApplyFromState(state) {
|
|
|
154
159
|
if (state.plan?.provider !== 'opencode' || state.plan.status !== 'ready' || state.plan.opencode === undefined)
|
|
155
160
|
return false;
|
|
156
161
|
const installMode = state.plan.opencode.installsAgents === false ? 'mcp-only' : 'mcp-plus-agents';
|
|
157
|
-
return state.plan.opencode.scope === state.selections.scope &&
|
|
162
|
+
return (state.plan.opencode.scope === state.selections.scope &&
|
|
163
|
+
installMode === state.selections.installMode &&
|
|
164
|
+
(state.plan.opencode.overwriteVgxness === true) === (state.selections.overwriteVgxness === true));
|
|
158
165
|
}
|
|
159
166
|
function advance(state) {
|
|
160
167
|
const index = setupTuiScreenOrder.indexOf(state.screen);
|
|
@@ -33,8 +33,9 @@ export function planOpenCodeMcpInstall(input) {
|
|
|
33
33
|
const agentPlan = createOpenCodeDefaultAgentInstallPlan({
|
|
34
34
|
mcpOnly: input.mcpOnly,
|
|
35
35
|
});
|
|
36
|
+
const overwriteVgxness = input.overwriteVgxness === true;
|
|
36
37
|
if (scope === 'user') {
|
|
37
|
-
return planUserOpenCodeMcpInstall(input, databasePathSource, server, agentPlan);
|
|
38
|
+
return planUserOpenCodeMcpInstall(input, databasePathSource, server, agentPlan, overwriteVgxness);
|
|
38
39
|
}
|
|
39
40
|
const existingTargets = supportedConfigTargets
|
|
40
41
|
.map((relativePath) => ({
|
|
@@ -48,11 +49,11 @@ export function planOpenCodeMcpInstall(input) {
|
|
|
48
49
|
kind: 'manual-check',
|
|
49
50
|
message: 'Remove ambiguity by keeping exactly one OpenCode project config target before installing.',
|
|
50
51
|
},
|
|
51
|
-
], agentPlan);
|
|
52
|
+
], agentPlan, overwriteVgxness);
|
|
52
53
|
}
|
|
53
54
|
if (existingTargets.length === 0) {
|
|
54
55
|
return {
|
|
55
|
-
...baseContract(input.databasePath, databasePathSource, scope, join(input.cwd, '.opencode', 'opencode.json'), false, 'create', agentPlan),
|
|
56
|
+
...baseContract(input.databasePath, databasePathSource, scope, join(input.cwd, '.opencode', 'opencode.json'), false, 'create', agentPlan, overwriteVgxness),
|
|
56
57
|
status: 'would_install',
|
|
57
58
|
action: 'create',
|
|
58
59
|
targetPath: join(input.cwd, '.opencode', 'opencode.json'),
|
|
@@ -64,31 +65,34 @@ export function planOpenCodeMcpInstall(input) {
|
|
|
64
65
|
}
|
|
65
66
|
const [target] = existingTargets;
|
|
66
67
|
if (target === undefined)
|
|
67
|
-
return refusal('ambiguous_target', 'Unable to resolve OpenCode project config target.', input.databasePath, databasePathSource, scope);
|
|
68
|
+
return refusal('ambiguous_target', 'Unable to resolve OpenCode project config target.', input.databasePath, databasePathSource, scope, undefined, [], undefined, overwriteVgxness);
|
|
68
69
|
if (target.relativePath.endsWith('.jsonc')) {
|
|
69
|
-
return refusal('unsupported_jsonc', `OpenCode JSONC config ${target.relativePath} is not supported yet; use JSON or remove comments first.`, input.databasePath, databasePathSource, scope, target.absolutePath, [], agentPlan);
|
|
70
|
+
return refusal('unsupported_jsonc', `OpenCode JSONC config ${target.relativePath} is not supported yet; use JSON or remove comments first.`, input.databasePath, databasePathSource, scope, target.absolutePath, [], agentPlan, overwriteVgxness);
|
|
70
71
|
}
|
|
71
72
|
const parsed = parseConfig(target.absolutePath);
|
|
72
73
|
if (!parsed.ok)
|
|
73
|
-
return refusal(parsed.reason, parsed.message, input.databasePath, databasePathSource, scope, target.absolutePath, [], agentPlan);
|
|
74
|
+
return refusal(parsed.reason, parsed.message, input.databasePath, databasePathSource, scope, target.absolutePath, [], agentPlan, overwriteVgxness);
|
|
74
75
|
const config = parsed.value;
|
|
75
76
|
if (config.mcp !== undefined && !isRecord(config.mcp)) {
|
|
76
|
-
return refusal('invalid_mcp_shape', 'Existing top-level mcp must be a JSON object before vgxness can be merged.', input.databasePath, databasePathSource, scope, target.absolutePath, [], agentPlan);
|
|
77
|
+
return refusal('invalid_mcp_shape', 'Existing top-level mcp must be a JSON object before vgxness can be merged.', input.databasePath, databasePathSource, scope, target.absolutePath, [], agentPlan, overwriteVgxness);
|
|
77
78
|
}
|
|
78
|
-
if (isRecord(config.mcp) && Object.hasOwn(config.mcp, 'vgxness')) {
|
|
79
|
-
return refusal('existing_vgxness_mcp', 'Existing OpenCode config already contains mcp.vgxness; overwrite is refused by default.', input.databasePath, databasePathSource, scope, target.absolutePath, [], agentPlan);
|
|
79
|
+
if (isRecord(config.mcp) && Object.hasOwn(config.mcp, 'vgxness') && !overwriteVgxness) {
|
|
80
|
+
return refusal('existing_vgxness_mcp', 'Existing OpenCode config already contains mcp.vgxness; overwrite is refused by default.', input.databasePath, databasePathSource, scope, target.absolutePath, [], agentPlan, overwriteVgxness);
|
|
80
81
|
}
|
|
81
82
|
const agentConflict = findConflictingVgxnessAgents(config, agentPlan);
|
|
82
|
-
if (agentConflict.length > 0) {
|
|
83
|
+
if (agentConflict.length > 0 && !overwriteVgxness) {
|
|
83
84
|
return refusal('existing_vgxness_agent', `Existing OpenCode config contains custom VGXNESS agent entries that would be overwritten: ${agentConflict.join(', ')}. Remove, rename, or manually reconcile them before installing.`, input.databasePath, databasePathSource, scope, target.absolutePath, [
|
|
84
85
|
{
|
|
85
86
|
kind: 'manual-check',
|
|
86
87
|
message: `Manually reconcile conflicting VGXNESS agent entries: ${agentConflict.join(', ')}.`,
|
|
87
88
|
},
|
|
88
|
-
], agentPlan);
|
|
89
|
+
], agentPlan, overwriteVgxness);
|
|
90
|
+
}
|
|
91
|
+
if (agentPlan.installsAgents && config.agent !== undefined && !isRecord(config.agent)) {
|
|
92
|
+
return refusal('unsupported_config_shape', 'Existing top-level agent must be a JSON object before VGXNESS agent entries can be merged or overwritten.', input.databasePath, databasePathSource, scope, target.absolutePath, [], agentPlan, overwriteVgxness);
|
|
89
93
|
}
|
|
90
94
|
return {
|
|
91
|
-
...baseContract(input.databasePath, databasePathSource, scope, target.absolutePath, true, 'merge-preserve-existing', agentPlan),
|
|
95
|
+
...baseContract(input.databasePath, databasePathSource, scope, target.absolutePath, true, 'merge-preserve-existing', agentPlan, overwriteVgxness),
|
|
92
96
|
status: 'would_install',
|
|
93
97
|
action: 'merge',
|
|
94
98
|
targetPath: target.absolutePath,
|
|
@@ -98,22 +102,22 @@ export function planOpenCodeMcpInstall(input) {
|
|
|
98
102
|
existingSchema: typeof config.$schema === 'string' ? config.$schema : opencodeConfigSchema,
|
|
99
103
|
};
|
|
100
104
|
}
|
|
101
|
-
function planUserOpenCodeMcpInstall(input, databasePathSource, server, agentPlan) {
|
|
105
|
+
function planUserOpenCodeMcpInstall(input, databasePathSource, server, agentPlan, overwriteVgxness) {
|
|
102
106
|
const target = resolveOpenCodeMcpInstallTarget({
|
|
103
107
|
cwd: input.cwd,
|
|
104
108
|
scope: 'user',
|
|
105
109
|
env: input.env,
|
|
106
110
|
});
|
|
107
111
|
if (!target.ok) {
|
|
108
|
-
return refusal('unsupported_config_shape', target.message, input.databasePath, databasePathSource, 'user');
|
|
112
|
+
return refusal('unsupported_config_shape', target.message, input.databasePath, databasePathSource, 'user', undefined, [], undefined, overwriteVgxness);
|
|
109
113
|
}
|
|
110
114
|
const jsoncPath = `${target.path}c`;
|
|
111
115
|
if (existsSync(jsoncPath)) {
|
|
112
|
-
return refusal('unsupported_jsonc', 'OpenCode user JSONC config opencode.jsonc is not supported yet; use JSON or remove comments first.', input.databasePath, databasePathSource, 'user', jsoncPath, [], agentPlan);
|
|
116
|
+
return refusal('unsupported_jsonc', 'OpenCode user JSONC config opencode.jsonc is not supported yet; use JSON or remove comments first.', input.databasePath, databasePathSource, 'user', jsoncPath, [], agentPlan, overwriteVgxness);
|
|
113
117
|
}
|
|
114
118
|
if (!existsSync(target.path)) {
|
|
115
119
|
return {
|
|
116
|
-
...baseContract(input.databasePath, databasePathSource, 'user', target.path, false, 'create', agentPlan),
|
|
120
|
+
...baseContract(input.databasePath, databasePathSource, 'user', target.path, false, 'create', agentPlan, overwriteVgxness),
|
|
117
121
|
status: 'would_install',
|
|
118
122
|
action: 'create',
|
|
119
123
|
targetPath: target.path,
|
|
@@ -125,25 +129,28 @@ function planUserOpenCodeMcpInstall(input, databasePathSource, server, agentPlan
|
|
|
125
129
|
}
|
|
126
130
|
const parsed = parseConfig(target.path);
|
|
127
131
|
if (!parsed.ok)
|
|
128
|
-
return refusal(parsed.reason, parsed.message, input.databasePath, databasePathSource, 'user', target.path, [], agentPlan);
|
|
132
|
+
return refusal(parsed.reason, parsed.message, input.databasePath, databasePathSource, 'user', target.path, [], agentPlan, overwriteVgxness);
|
|
129
133
|
const config = parsed.value;
|
|
130
134
|
if (config.mcp !== undefined && !isRecord(config.mcp)) {
|
|
131
|
-
return refusal('invalid_mcp_shape', 'Existing top-level mcp must be a JSON object before vgxness can be merged.', input.databasePath, databasePathSource, 'user', target.path, [], agentPlan);
|
|
135
|
+
return refusal('invalid_mcp_shape', 'Existing top-level mcp must be a JSON object before vgxness can be merged.', input.databasePath, databasePathSource, 'user', target.path, [], agentPlan, overwriteVgxness);
|
|
132
136
|
}
|
|
133
|
-
if (isRecord(config.mcp) && Object.hasOwn(config.mcp, 'vgxness')) {
|
|
134
|
-
return refusal('existing_vgxness_mcp', 'Existing OpenCode config already contains mcp.vgxness; overwrite is refused by default.', input.databasePath, databasePathSource, 'user', target.path, [], agentPlan);
|
|
137
|
+
if (isRecord(config.mcp) && Object.hasOwn(config.mcp, 'vgxness') && !overwriteVgxness) {
|
|
138
|
+
return refusal('existing_vgxness_mcp', 'Existing OpenCode config already contains mcp.vgxness; overwrite is refused by default.', input.databasePath, databasePathSource, 'user', target.path, [], agentPlan, overwriteVgxness);
|
|
135
139
|
}
|
|
136
140
|
const agentConflict = findConflictingVgxnessAgents(config, agentPlan);
|
|
137
|
-
if (agentConflict.length > 0) {
|
|
141
|
+
if (agentConflict.length > 0 && !overwriteVgxness) {
|
|
138
142
|
return refusal('existing_vgxness_agent', `Existing OpenCode config contains custom VGXNESS agent entries that would be overwritten: ${agentConflict.join(', ')}. Remove, rename, or manually reconcile them before installing.`, input.databasePath, databasePathSource, 'user', target.path, [
|
|
139
143
|
{
|
|
140
144
|
kind: 'manual-check',
|
|
141
145
|
message: `Manually reconcile conflicting VGXNESS agent entries: ${agentConflict.join(', ')}.`,
|
|
142
146
|
},
|
|
143
|
-
], agentPlan);
|
|
147
|
+
], agentPlan, overwriteVgxness);
|
|
148
|
+
}
|
|
149
|
+
if (agentPlan.installsAgents && config.agent !== undefined && !isRecord(config.agent)) {
|
|
150
|
+
return refusal('unsupported_config_shape', 'Existing top-level agent must be a JSON object before VGXNESS agent entries can be merged or overwritten.', input.databasePath, databasePathSource, 'user', target.path, [], agentPlan, overwriteVgxness);
|
|
144
151
|
}
|
|
145
152
|
return {
|
|
146
|
-
...baseContract(input.databasePath, databasePathSource, 'user', target.path, true, 'merge-preserve-existing', agentPlan),
|
|
153
|
+
...baseContract(input.databasePath, databasePathSource, 'user', target.path, true, 'merge-preserve-existing', agentPlan, overwriteVgxness),
|
|
147
154
|
status: 'would_install',
|
|
148
155
|
action: 'merge',
|
|
149
156
|
targetPath: target.path,
|
|
@@ -206,7 +213,7 @@ function deepEqual(left, right) {
|
|
|
206
213
|
const rightKeys = Object.keys(right).sort();
|
|
207
214
|
return leftKeys.length === rightKeys.length && leftKeys.every((key, index) => key === rightKeys[index] && deepEqual(left[key], right[key]));
|
|
208
215
|
}
|
|
209
|
-
function baseContract(databasePath, source, scope, targetPath, backupRequired, mergePolicy, agentPlan = createOpenCodeDefaultAgentInstallPlan({ mcpOnly: true })) {
|
|
216
|
+
function baseContract(databasePath, source, scope, targetPath, backupRequired, mergePolicy, agentPlan = createOpenCodeDefaultAgentInstallPlan({ mcpOnly: true }), overwriteVgxness = false) {
|
|
210
217
|
return {
|
|
211
218
|
version: 1,
|
|
212
219
|
kind: 'mcp-client-install-opencode',
|
|
@@ -221,16 +228,17 @@ function baseContract(databasePath, source, scope, targetPath, backupRequired, m
|
|
|
221
228
|
mergePolicy,
|
|
222
229
|
},
|
|
223
230
|
scope,
|
|
224
|
-
warnings: warningsForScope(scope),
|
|
231
|
+
warnings: warningsForScope(scope, overwriteVgxness, agentPlan),
|
|
225
232
|
verificationHints: verificationHints(databasePath, source),
|
|
226
233
|
manualTest: manualTestForScope(scope, databasePath, source),
|
|
227
234
|
installsAgents: agentPlan.installsAgents,
|
|
228
235
|
agentNames: agentPlan.agentNames,
|
|
236
|
+
overwriteVgxness,
|
|
229
237
|
...(agentPlan.defaultAgent !== undefined ? { defaultAgent: agentPlan.defaultAgent } : {}),
|
|
230
238
|
};
|
|
231
239
|
}
|
|
232
|
-
function refusal(reason, message, databasePath, source, scope, targetPath, extraVerificationHints = [], agentPlan = createOpenCodeDefaultAgentInstallPlan({ mcpOnly: true })) {
|
|
233
|
-
const contract = baseContract(databasePath, source, scope, targetPath, false, 'refuse-no-clobber', agentPlan);
|
|
240
|
+
function refusal(reason, message, databasePath, source, scope, targetPath, extraVerificationHints = [], agentPlan = createOpenCodeDefaultAgentInstallPlan({ mcpOnly: true }), overwriteVgxness = false) {
|
|
241
|
+
const contract = baseContract(databasePath, source, scope, targetPath, false, 'refuse-no-clobber', agentPlan, overwriteVgxness);
|
|
234
242
|
return {
|
|
235
243
|
...contract,
|
|
236
244
|
verificationHints: [...extraVerificationHints, ...contract.verificationHints],
|
|
@@ -240,12 +248,18 @@ function refusal(reason, message, databasePath, source, scope, targetPath, extra
|
|
|
240
248
|
...(targetPath !== undefined ? { targetPath } : {}),
|
|
241
249
|
};
|
|
242
250
|
}
|
|
243
|
-
function warningsForScope(scope) {
|
|
251
|
+
function warningsForScope(scope, overwriteVgxness, agentPlan) {
|
|
252
|
+
const overwriteWarnings = overwriteVgxness
|
|
253
|
+
? [
|
|
254
|
+
`Reinstall/overwrite is enabled: existing VGXNESS-managed OpenCode entries will be replaced (${agentPlan.installsAgents ? 'mcp.vgxness, default_agent, instructions AGENTS.md, and known VGXNESS agents' : 'mcp.vgxness only'}); unrelated OpenCode config is preserved.`,
|
|
255
|
+
]
|
|
256
|
+
: [];
|
|
244
257
|
if (scope === 'project')
|
|
245
|
-
return ['Restart OpenCode after installation so it reloads the project MCP config.'];
|
|
258
|
+
return ['Restart OpenCode after installation so it reloads the project MCP config.', ...overwriteWarnings];
|
|
246
259
|
return [
|
|
247
260
|
'Restart OpenCode after installation so it reloads the user MCP config.',
|
|
248
261
|
'OpenCode project config may override user config for a workspace; check project-level config if vgxness is not visible.',
|
|
262
|
+
...overwriteWarnings,
|
|
249
263
|
];
|
|
250
264
|
}
|
|
251
265
|
function manualTestForScope(scope, databasePath, source) {
|
|
@@ -25,9 +25,10 @@ export async function installOpenCodeMcpClient(input) {
|
|
|
25
25
|
...(input.scope !== undefined ? { scope: input.scope } : {}),
|
|
26
26
|
...(input.env !== undefined ? { env: input.env } : {}),
|
|
27
27
|
...(input.mcpOnly !== undefined ? { mcpOnly: input.mcpOnly } : {}),
|
|
28
|
+
...(input.overwriteVgxness !== undefined ? { overwriteVgxness: input.overwriteVgxness } : {}),
|
|
28
29
|
});
|
|
29
30
|
if (!input.confirmed) {
|
|
30
|
-
return refusal('confirmation_required', confirmationRequiredMessage(plan.scope), input.databasePath, databasePathSource, server, confirmationRequiredSafety(plan), 'targetPath' in plan ? plan.targetPath : undefined, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan);
|
|
31
|
+
return refusal('confirmation_required', confirmationRequiredMessage(plan.scope), input.databasePath, databasePathSource, server, confirmationRequiredSafety(plan), 'targetPath' in plan ? plan.targetPath : undefined, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan, plan.overwriteVgxness);
|
|
31
32
|
}
|
|
32
33
|
if (plan.status === 'refused')
|
|
33
34
|
return refusalFromPlan(plan, input.databasePath, databasePathSource, server);
|
|
@@ -35,28 +36,28 @@ export async function installOpenCodeMcpClient(input) {
|
|
|
35
36
|
if (existsSync(plan.targetPath)) {
|
|
36
37
|
const reparsed = parseConfig(plan.targetPath);
|
|
37
38
|
if (!reparsed.ok)
|
|
38
|
-
return refusal(reparsed.reason, reparsed.message, input.databasePath, databasePathSource, server, applySafety(plan), plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan);
|
|
39
|
+
return refusal(reparsed.reason, reparsed.message, input.databasePath, databasePathSource, server, applySafety(plan), plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan, plan.overwriteVgxness);
|
|
39
40
|
const conflictingAgents = findConflictingVgxnessAgents(reparsed.value, agentPlan);
|
|
40
|
-
if (conflictingAgents.length > 0)
|
|
41
|
+
if (conflictingAgents.length > 0 && input.overwriteVgxness !== true)
|
|
41
42
|
return agentConflictRefusal(conflictingAgents, input.databasePath, databasePathSource, server, applySafety(plan), plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan);
|
|
42
|
-
return refusal('unsupported_config_shape', 'OpenCode config appeared after planning; rerun setup apply so it can be merged safely without overwriting user config.', input.databasePath, databasePathSource, server, applySafety(plan), plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan);
|
|
43
|
+
return refusal('unsupported_config_shape', 'OpenCode config appeared after planning; rerun setup apply so it can be merged safely without overwriting user config.', input.databasePath, databasePathSource, server, applySafety(plan), plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan, plan.overwriteVgxness);
|
|
43
44
|
}
|
|
44
45
|
writeConfig(plan.targetPath, mergeVgxnessOpenCodeConfig({ $schema: opencodeConfigSchema }, plan.server, plan.installsAgents));
|
|
45
|
-
return validateInstalledResult(plan.targetPath, undefined, server, input.databasePath, databasePathSource, applySafety(plan), plan.verificationHints, plan.warnings, plan.manualTest, agentPlan);
|
|
46
|
+
return validateInstalledResult(plan.targetPath, undefined, server, input.databasePath, databasePathSource, applySafety(plan), plan.verificationHints, plan.warnings, plan.manualTest, agentPlan, plan.overwriteVgxness);
|
|
46
47
|
}
|
|
47
48
|
const parsed = parseConfig(plan.targetPath);
|
|
48
49
|
if (!parsed.ok)
|
|
49
|
-
return refusal(parsed.reason, parsed.message, input.databasePath, databasePathSource, server, applySafety(plan), plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan);
|
|
50
|
+
return refusal(parsed.reason, parsed.message, input.databasePath, databasePathSource, server, applySafety(plan), plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan, plan.overwriteVgxness);
|
|
50
51
|
const conflictingAgents = findConflictingVgxnessAgents(parsed.value, agentPlan);
|
|
51
|
-
if (conflictingAgents.length > 0)
|
|
52
|
+
if (conflictingAgents.length > 0 && input.overwriteVgxness !== true)
|
|
52
53
|
return agentConflictRefusal(conflictingAgents, input.databasePath, databasePathSource, server, applySafety(plan), plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan);
|
|
53
54
|
const backup = createBackup(plan.targetPath, plan.scope);
|
|
54
55
|
if (!backup.ok)
|
|
55
|
-
return refusal('post_write_validation_failed', backup.error.message, input.databasePath, databasePathSource, server, applySafety(plan), plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan);
|
|
56
|
+
return refusal('post_write_validation_failed', backup.error.message, input.databasePath, databasePathSource, server, applySafety(plan), plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan, plan.overwriteVgxness);
|
|
56
57
|
const config = parsed.value;
|
|
57
58
|
const mergedConfig = mergeVgxnessOpenCodeConfig({ ...config, $schema: plan.existingSchema ?? opencodeConfigSchema }, plan.server, plan.installsAgents);
|
|
58
59
|
writeConfig(plan.targetPath, mergedConfig);
|
|
59
|
-
return validateInstalledResult(plan.targetPath, backup.value, server, input.databasePath, databasePathSource, applySafety(plan), plan.verificationHints, plan.warnings, plan.manualTest, agentPlan);
|
|
60
|
+
return validateInstalledResult(plan.targetPath, backup.value, server, input.databasePath, databasePathSource, applySafety(plan), plan.verificationHints, plan.warnings, plan.manualTest, agentPlan, plan.overwriteVgxness);
|
|
60
61
|
}
|
|
61
62
|
function mergeVgxnessOpenCodeConfig(config, server, installsAgents) {
|
|
62
63
|
const merged = {
|
|
@@ -101,7 +102,7 @@ function createBackup(path, scope) {
|
|
|
101
102
|
description: 'Backup existing OpenCode config before merging VGXNESS MCP configuration.',
|
|
102
103
|
});
|
|
103
104
|
}
|
|
104
|
-
function validateInstalledResult(targetPath, backup, server, databasePath, source, safety, verificationHints, warningMessages, manualTestGuidance, agentPlan) {
|
|
105
|
+
function validateInstalledResult(targetPath, backup, server, databasePath, source, safety, verificationHints, warningMessages, manualTestGuidance, agentPlan, overwriteVgxness = false) {
|
|
105
106
|
const parsed = parseConfig(targetPath);
|
|
106
107
|
if (!parsed.ok)
|
|
107
108
|
return refusal('post_write_validation_failed', 'OpenCode config could not be re-read after write.', databasePath, source, server, safety, targetPath, verificationHints, warningMessages, manualTestGuidance, agentPlan);
|
|
@@ -129,6 +130,7 @@ function validateInstalledResult(targetPath, backup, server, databasePath, sourc
|
|
|
129
130
|
manualTest: manualTestGuidance,
|
|
130
131
|
installsAgents: agentPlan.installsAgents,
|
|
131
132
|
agentNames: agentPlan.agentNames,
|
|
133
|
+
overwriteVgxness,
|
|
132
134
|
...(agentPlan.defaultAgent !== undefined ? { defaultAgent: agentPlan.defaultAgent } : {}),
|
|
133
135
|
};
|
|
134
136
|
}
|
|
@@ -153,7 +155,7 @@ function isOpenCodeLocalMcpServerConfig(value) {
|
|
|
153
155
|
function isRecord(value) {
|
|
154
156
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
155
157
|
}
|
|
156
|
-
function refusal(reason, message, databasePath, source, server, safety, targetPath, verificationHints = defaultVerificationHints(databasePath, source), warningMessages = warnings(), manualTestGuidance = manualTest(databasePath, source), agentPlan = createOpenCodeDefaultAgentInstallPlan({ mcpOnly: true })) {
|
|
158
|
+
function refusal(reason, message, databasePath, source, server, safety, targetPath, verificationHints = defaultVerificationHints(databasePath, source), warningMessages = warnings(), manualTestGuidance = manualTest(databasePath, source), agentPlan = createOpenCodeDefaultAgentInstallPlan({ mcpOnly: true }), overwriteVgxness = false) {
|
|
157
159
|
return {
|
|
158
160
|
version: 1,
|
|
159
161
|
kind: 'mcp-client-install-opencode',
|
|
@@ -168,11 +170,12 @@ function refusal(reason, message, databasePath, source, server, safety, targetPa
|
|
|
168
170
|
manualTest: manualTestGuidance,
|
|
169
171
|
installsAgents: agentPlan.installsAgents,
|
|
170
172
|
agentNames: agentPlan.agentNames,
|
|
173
|
+
overwriteVgxness,
|
|
171
174
|
...(agentPlan.defaultAgent !== undefined ? { defaultAgent: agentPlan.defaultAgent } : {}),
|
|
172
175
|
};
|
|
173
176
|
}
|
|
174
177
|
function refusalFromPlan(plan, databasePath, source, server) {
|
|
175
|
-
return refusal(plan.reason, plan.message, databasePath, source, server, plan.safety, plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, plan);
|
|
178
|
+
return refusal(plan.reason, plan.message, databasePath, source, server, plan.safety, plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, plan, plan.overwriteVgxness);
|
|
176
179
|
}
|
|
177
180
|
function agentConflictRefusal(conflictingAgents, databasePath, source, server, safety, targetPath, verificationHints, warningMessages, manualTestGuidance, agentPlan) {
|
|
178
181
|
return refusal('existing_vgxness_agent', `Existing OpenCode config contains custom VGXNESS agent entries that would be overwritten: ${conflictingAgents.join(', ')}. Remove, rename, or manually reconcile them before installing.`, databasePath, source, server, safety, targetPath, verificationHints, warningMessages, manualTestGuidance, agentPlan);
|
|
@@ -49,9 +49,10 @@ export const openCodeSetupAdapter = {
|
|
|
49
49
|
databasePath,
|
|
50
50
|
...(context.databasePathSource !== undefined ? { databasePathSource: context.databasePathSource } : {}),
|
|
51
51
|
...(context.env !== undefined ? { env: context.env } : {}),
|
|
52
|
+
...(context.overwriteVgxness !== undefined ? { overwriteVgxness: context.overwriteVgxness } : {}),
|
|
52
53
|
});
|
|
53
54
|
const targetPath = 'targetPath' in contract ? contract.targetPath : undefined;
|
|
54
|
-
const actions = contract.status === 'would_install' ? [externalInstallAction(contract.scope, targetPath)] : [];
|
|
55
|
+
const actions = contract.status === 'would_install' ? [externalInstallAction(contract.scope, targetPath, contract.overwriteVgxness)] : [];
|
|
55
56
|
return {
|
|
56
57
|
providerId: 'opencode',
|
|
57
58
|
status: contract.status === 'would_install' ? 'available' : 'blocked',
|
|
@@ -59,7 +60,9 @@ export const openCodeSetupAdapter = {
|
|
|
59
60
|
mutating: false,
|
|
60
61
|
writesProviderConfig: false,
|
|
61
62
|
summary: contract.status === 'would_install'
|
|
62
|
-
?
|
|
63
|
+
? contract.overwriteVgxness
|
|
64
|
+
? `OpenCode ${contract.action} reinstall plan is available; confirmed apply overwrites VGXNESS entries only, preserves unrelated config, and creates managed backups for existing targets.`
|
|
65
|
+
: `OpenCode ${contract.action} plan is available for external application; confirmed merges create managed VGXNESS backups.`
|
|
63
66
|
: contract.message,
|
|
64
67
|
...(targetPath !== undefined ? { targetPath } : {}),
|
|
65
68
|
warnings: contract.warnings,
|
|
@@ -105,7 +108,11 @@ function openCodePlanPreview(visibility, plan) {
|
|
|
105
108
|
if (!isOpenCodeInstallContract(source))
|
|
106
109
|
return undefined;
|
|
107
110
|
if (source.status === 'would_install') {
|
|
108
|
-
const action = source.
|
|
111
|
+
const action = source.overwriteVgxness
|
|
112
|
+
? 'Reinstall VGXNESS OpenCode entries'
|
|
113
|
+
: source.action === 'create'
|
|
114
|
+
? 'Install OpenCode MCP config'
|
|
115
|
+
: 'Update OpenCode MCP config';
|
|
109
116
|
return {
|
|
110
117
|
kind: source.action === 'create' ? 'install' : 'update',
|
|
111
118
|
action,
|
|
@@ -115,7 +122,7 @@ function openCodePlanPreview(visibility, plan) {
|
|
|
115
122
|
targetPath: source.targetPath,
|
|
116
123
|
backupRequired: source.backupRequired,
|
|
117
124
|
confirmationRequired: Boolean(plan?.actions.some((candidate) => candidate.safety.requiresExplicitConfirmation)),
|
|
118
|
-
risks: source.action === 'create' ? createInstallRisks() : mergeInstallRisks(),
|
|
125
|
+
risks: source.overwriteVgxness ? overwriteInstallRisks(source.installsAgents) : source.action === 'create' ? createInstallRisks() : mergeInstallRisks(),
|
|
119
126
|
warnings: source.warnings,
|
|
120
127
|
};
|
|
121
128
|
}
|
|
@@ -140,6 +147,13 @@ function mergeInstallRisks() {
|
|
|
140
147
|
'OpenCode config discovery may still be affected by target precedence or malformed local config.',
|
|
141
148
|
];
|
|
142
149
|
}
|
|
150
|
+
function overwriteInstallRisks(installsAgents) {
|
|
151
|
+
return [
|
|
152
|
+
`Existing VGXNESS-managed OpenCode entries will be replaced (${installsAgents ? 'mcp.vgxness, default_agent, instructions AGENTS.md, and known VGXNESS agents' : 'mcp.vgxness only'}).`,
|
|
153
|
+
'Unrelated OpenCode config, unrelated agents, and unrelated MCP servers are preserved; existing targets still receive managed backups before writes.',
|
|
154
|
+
'OpenCode must be restarted after external installation before it discovers the updated vgxness MCP server.',
|
|
155
|
+
];
|
|
156
|
+
}
|
|
143
157
|
function refusedRepairRisks(message) {
|
|
144
158
|
return [
|
|
145
159
|
`Install planner refused automatic repair: ${message}`,
|
|
@@ -155,13 +169,15 @@ function isOpenCodeInstallContract(value) {
|
|
|
155
169
|
function databasePathOrPlaceholder(context) {
|
|
156
170
|
return context.databasePath ?? '<database-path>';
|
|
157
171
|
}
|
|
158
|
-
function externalInstallAction(scope, targetPath) {
|
|
172
|
+
function externalInstallAction(scope, targetPath, overwriteVgxness = false) {
|
|
159
173
|
return {
|
|
160
174
|
id: `opencode-mcp-install-${scope}`,
|
|
161
175
|
label: `Copy external OpenCode ${scope === 'user' ? 'user/global' : 'project'} install command`,
|
|
162
176
|
kind: 'copy-command',
|
|
163
|
-
command: ['vgxness', 'mcp', 'install', 'opencode', '--scope', scope, '--yes'],
|
|
164
|
-
description:
|
|
177
|
+
command: ['vgxness', 'mcp', 'install', 'opencode', '--scope', scope, '--yes', ...(overwriteVgxness ? ['--overwrite-vgxness'] : [])],
|
|
178
|
+
description: overwriteVgxness
|
|
179
|
+
? 'Copy and run this outside the TUI only after reviewing that VGXNESS entries will be overwritten and unrelated config preserved.'
|
|
180
|
+
: 'Copy and run this outside the TUI only after reviewing the read-only plan.',
|
|
165
181
|
safety: externalProviderWriteSafety(targetPath),
|
|
166
182
|
};
|
|
167
183
|
}
|
package/dist/setup/setup-plan.js
CHANGED
|
@@ -54,6 +54,7 @@ export function createSetupPlan(input) {
|
|
|
54
54
|
scope,
|
|
55
55
|
env: input.env,
|
|
56
56
|
mcpOnly: installMode === 'mcp-only',
|
|
57
|
+
...(input.overwriteVgxness === undefined ? {} : { overwriteVgxness: input.overwriteVgxness }),
|
|
57
58
|
});
|
|
58
59
|
return { ok: true, value: setupPlanFromOpenCode({ ...common, provider: 'opencode', installMode, scope, opencode }) };
|
|
59
60
|
}
|
|
@@ -70,6 +71,7 @@ function setupPlanFromOpenCode(input) {
|
|
|
70
71
|
...(input.opencode.targetPath === undefined ? {} : { targetPath: input.opencode.targetPath }),
|
|
71
72
|
installsAgents: input.opencode.installsAgents,
|
|
72
73
|
agentNames: input.opencode.agentNames,
|
|
74
|
+
...(input.opencode.overwriteVgxness ? { overwriteVgxness: true } : {}),
|
|
73
75
|
},
|
|
74
76
|
actions: [],
|
|
75
77
|
conflicts: [
|
|
@@ -96,11 +98,12 @@ function setupPlanFromOpenCode(input) {
|
|
|
96
98
|
mcpServer: input.opencode.server,
|
|
97
99
|
installsAgents: input.opencode.installsAgents,
|
|
98
100
|
agentNames: input.opencode.agentNames,
|
|
101
|
+
...(input.opencode.overwriteVgxness ? { overwriteVgxness: true } : {}),
|
|
99
102
|
},
|
|
100
103
|
actions: [
|
|
101
104
|
{
|
|
102
105
|
id: `opencode-${input.opencode.action}`,
|
|
103
|
-
description: `${input.opencode.action === 'create' ? 'Create' : 'Merge'} OpenCode config with mcp.vgxness using vgxness mcp start${input.installMode === 'mcp-plus-agents' ? ' and manager/SDD agents' : ''}.`,
|
|
106
|
+
description: `${input.opencode.overwriteVgxness ? 'Reinstall/overwrite VGXNESS entries in' : input.opencode.action === 'create' ? 'Create' : 'Merge'} OpenCode config with mcp.vgxness using vgxness mcp start${input.installMode === 'mcp-plus-agents' ? ' and manager/SDD agents' : ''}${input.opencode.overwriteVgxness ? '; unrelated OpenCode config is preserved' : ''}.`,
|
|
104
107
|
mutating: false,
|
|
105
108
|
targetPath: input.opencode.targetPath,
|
|
106
109
|
backupRequired: input.opencode.backupRequired,
|
|
@@ -111,7 +114,9 @@ function setupPlanFromOpenCode(input) {
|
|
|
111
114
|
? [
|
|
112
115
|
{
|
|
113
116
|
targetPath: input.opencode.targetPath,
|
|
114
|
-
reason:
|
|
117
|
+
reason: input.opencode.overwriteVgxness
|
|
118
|
+
? 'Existing OpenCode config would receive a managed VGXNESS backup with rollback metadata before VGXNESS entries are overwritten/reinstalled.'
|
|
119
|
+
: 'Existing OpenCode config would receive a managed VGXNESS backup with rollback metadata before merge.',
|
|
115
120
|
},
|
|
116
121
|
]
|
|
117
122
|
: [],
|