@pellux/goodvibes-agent 0.1.10 → 0.1.12
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/CHANGELOG.md +32 -0
- package/package.json +1 -1
- package/src/cli/agent-knowledge-command.ts +30 -3
- package/src/cli/help.ts +2 -2
- package/src/input/commands/cloudflare-runtime.ts +20 -5
- package/src/input/commands/confirmation.ts +24 -0
- package/src/input/commands/discovery-runtime.ts +16 -7
- package/src/input/commands/eval.ts +27 -14
- package/src/input/commands/experience-runtime.ts +65 -26
- package/src/input/commands/health-runtime.ts +1 -1
- package/src/input/commands/hooks-runtime.ts +50 -19
- package/src/input/commands/incident-runtime.ts +17 -6
- package/src/input/commands/integration-runtime.ts +93 -50
- package/src/input/commands/knowledge.ts +38 -12
- package/src/input/commands/local-auth-runtime.ts +36 -13
- package/src/input/commands/local-provider-runtime.ts +22 -11
- package/src/input/commands/local-runtime.ts +21 -11
- package/src/input/commands/local-setup.ts +35 -16
- package/src/input/commands/managed-runtime.ts +51 -20
- package/src/input/commands/marketplace-runtime.ts +31 -16
- package/src/input/commands/mcp-runtime.ts +65 -34
- package/src/input/commands/memory-product-runtime.ts +72 -35
- package/src/input/commands/memory.ts +9 -9
- package/src/input/commands/notify-runtime.ts +27 -8
- package/src/input/commands/operator-runtime.ts +85 -17
- package/src/input/commands/planning-runtime.ts +14 -2
- package/src/input/commands/platform-access-runtime.ts +88 -45
- package/src/input/commands/platform-services-runtime.ts +51 -25
- package/src/input/commands/product-runtime.ts +54 -27
- package/src/input/commands/profile-sync-runtime.ts +17 -6
- package/src/input/commands/recall-bundle.ts +38 -17
- package/src/input/commands/recall-query.ts +15 -4
- package/src/input/commands/recall-review.ts +9 -3
- package/src/input/commands/remote-runtime-setup.ts +45 -18
- package/src/input/commands/remote-runtime.ts +25 -9
- package/src/input/commands/replay-runtime.ts +9 -2
- package/src/input/commands/services-runtime.ts +21 -10
- package/src/input/commands/session-content.ts +53 -51
- package/src/input/commands/session-workflow.ts +10 -4
- package/src/input/commands/session.ts +1 -1
- package/src/input/commands/settings-sync-runtime.ts +40 -17
- package/src/input/commands/share-runtime.ts +12 -4
- package/src/input/commands/shell-core.ts +3 -3
- package/src/input/commands/subscription-runtime.ts +35 -20
- package/src/input/commands/teleport-runtime.ts +16 -5
- package/src/input/commands/work-plan-runtime.ts +23 -12
- package/src/input/handler-content-actions.ts +11 -62
- package/src/input/handler-interactions.ts +1 -1
- package/src/input/handler-onboarding-cloudflare.ts +48 -117
- package/src/input/keybindings.ts +1 -1
- package/src/input/mcp-workspace.ts +25 -49
- package/src/input/onboarding/onboarding-wizard-cloudflare-step.ts +8 -8
- package/src/input/onboarding/onboarding-wizard-cloudflare.ts +1 -6
- package/src/input/profile-picker-modal.ts +13 -31
- package/src/input/session-picker-modal.ts +4 -30
- package/src/input/settings-modal-subscriptions.ts +3 -3
- package/src/panels/incident-review-panel.ts +1 -1
- package/src/panels/local-auth-panel.ts +4 -4
- package/src/panels/provider-account-snapshot.ts +1 -1
- package/src/panels/provider-health-domains.ts +2 -2
- package/src/panels/settings-sync-panel.ts +2 -2
- package/src/panels/subscription-panel.ts +7 -7
- package/src/renderer/block-actions.ts +1 -1
- package/src/renderer/help-overlay.ts +2 -2
- package/src/renderer/mcp-workspace.ts +12 -12
- package/src/renderer/profile-picker-modal.ts +3 -11
- package/src/renderer/session-picker-modal.ts +2 -10
- package/src/verification/live-verifier.ts +100 -68
- package/src/version.ts +1 -1
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
} from '@/runtime/index.ts';
|
|
21
21
|
import { requireProfileManager, requireShellPaths } from './runtime-services.ts';
|
|
22
22
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
23
|
+
import { requireYesFlag, stripYesFlag } from './confirmation.ts';
|
|
23
24
|
|
|
24
25
|
function buildConfigSnapshot(
|
|
25
26
|
manager: { get: (key: ConfigKey) => unknown },
|
|
@@ -39,11 +40,13 @@ export function registerManagedRuntimeCommands(registry: CommandRegistry): void
|
|
|
39
40
|
registry.register({
|
|
40
41
|
name: 'managed',
|
|
41
42
|
description: 'Export, inspect, and apply managed settings bundles',
|
|
42
|
-
usage: '[review|staged|rollback-history|export <profile> <path
|
|
43
|
+
usage: '[review|staged|rollback-history|export <profile> <path> --yes|inspect <path>|stage <path> --yes|apply <path> [key ...] --yes|apply-staged [key ...] --yes|rollback <token> --yes|lock <key> <source> <reason...> --yes|unlock <key> --yes]',
|
|
43
44
|
handler(args, ctx) {
|
|
45
|
+
const parsed = stripYesFlag(args);
|
|
46
|
+
const commandArgs = [...parsed.rest];
|
|
44
47
|
const shellPaths = requireShellPaths(ctx);
|
|
45
48
|
const controlPlaneConfigDir = ctx.platform.configManager.getControlPlaneConfigDir();
|
|
46
|
-
const sub =
|
|
49
|
+
const sub = commandArgs[0] ?? 'review';
|
|
47
50
|
const pm = requireProfileManager(ctx);
|
|
48
51
|
if (sub === 'review') {
|
|
49
52
|
const profiles = pm.list();
|
|
@@ -78,11 +81,15 @@ export function registerManagedRuntimeCommands(registry: CommandRegistry): void
|
|
|
78
81
|
}
|
|
79
82
|
|
|
80
83
|
if (sub === 'lock') {
|
|
81
|
-
const key =
|
|
82
|
-
const source =
|
|
83
|
-
const reason =
|
|
84
|
+
const key = commandArgs[1] as ConfigKey | undefined;
|
|
85
|
+
const source = commandArgs[2];
|
|
86
|
+
const reason = commandArgs.slice(3).join(' ').trim();
|
|
84
87
|
if (!key || !source || !reason) {
|
|
85
|
-
ctx.print('Usage: /managed lock <key> <source> <reason...>');
|
|
88
|
+
ctx.print('Usage: /managed lock <key> <source> <reason...> --yes');
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (!parsed.yes) {
|
|
92
|
+
requireYesFlag(ctx, `lock managed setting ${key}`, '/managed lock <key> <source> <reason...> --yes');
|
|
86
93
|
return;
|
|
87
94
|
}
|
|
88
95
|
setManagedSettingLock(key, source, reason, controlPlaneConfigDir);
|
|
@@ -91,9 +98,13 @@ export function registerManagedRuntimeCommands(registry: CommandRegistry): void
|
|
|
91
98
|
}
|
|
92
99
|
|
|
93
100
|
if (sub === 'unlock') {
|
|
94
|
-
const key =
|
|
101
|
+
const key = commandArgs[1] as ConfigKey | undefined;
|
|
95
102
|
if (!key) {
|
|
96
|
-
ctx.print('Usage: /managed unlock <key>');
|
|
103
|
+
ctx.print('Usage: /managed unlock <key> --yes');
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (!parsed.yes) {
|
|
107
|
+
requireYesFlag(ctx, `unlock managed setting ${key}`, '/managed unlock <key> --yes');
|
|
97
108
|
return;
|
|
98
109
|
}
|
|
99
110
|
ctx.print(clearManagedSettingLock(key, controlPlaneConfigDir) ? `Managed lock cleared for ${key}.` : `No managed lock found for ${key}.`);
|
|
@@ -101,10 +112,14 @@ export function registerManagedRuntimeCommands(registry: CommandRegistry): void
|
|
|
101
112
|
}
|
|
102
113
|
|
|
103
114
|
if (sub === 'export') {
|
|
104
|
-
const profileName =
|
|
105
|
-
const pathArg =
|
|
115
|
+
const profileName = commandArgs[1];
|
|
116
|
+
const pathArg = commandArgs[2];
|
|
106
117
|
if (!profileName || !pathArg) {
|
|
107
|
-
ctx.print('Usage: /managed export <profile> <path>');
|
|
118
|
+
ctx.print('Usage: /managed export <profile> <path> --yes');
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (!parsed.yes) {
|
|
122
|
+
requireYesFlag(ctx, `export managed settings profile ${profileName} to ${pathArg}`, '/managed export <profile> <path> --yes');
|
|
108
123
|
return;
|
|
109
124
|
}
|
|
110
125
|
const loaded = pm.load(profileName);
|
|
@@ -129,12 +144,16 @@ export function registerManagedRuntimeCommands(registry: CommandRegistry): void
|
|
|
129
144
|
}
|
|
130
145
|
|
|
131
146
|
if (sub === 'apply-staged') {
|
|
132
|
-
const requestedKeys =
|
|
133
|
-
const invalidKeys =
|
|
147
|
+
const requestedKeys = commandArgs.slice(1).filter((value): value is ConfigKey => CONFIG_KEYS.has(value as ConfigKey));
|
|
148
|
+
const invalidKeys = commandArgs.slice(1).filter((value) => !CONFIG_KEYS.has(value as ConfigKey));
|
|
134
149
|
if (invalidKeys.length > 0) {
|
|
135
150
|
ctx.print(`Unknown config key(s): ${invalidKeys.join(', ')}`);
|
|
136
151
|
return;
|
|
137
152
|
}
|
|
153
|
+
if (!parsed.yes) {
|
|
154
|
+
requireYesFlag(ctx, 'apply staged managed settings', '/managed apply-staged [key ...] --yes');
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
138
157
|
try {
|
|
139
158
|
const result = applyStagedManagedBundle(ctx.platform.configManager, requestedKeys);
|
|
140
159
|
ctx.session.runtime.model = String(ctx.platform.configManager.get('provider.model'));
|
|
@@ -149,9 +168,13 @@ export function registerManagedRuntimeCommands(registry: CommandRegistry): void
|
|
|
149
168
|
}
|
|
150
169
|
|
|
151
170
|
if (sub === 'rollback') {
|
|
152
|
-
const token =
|
|
171
|
+
const token = commandArgs[1];
|
|
153
172
|
if (!token) {
|
|
154
|
-
ctx.print('Usage: /managed rollback <token>');
|
|
173
|
+
ctx.print('Usage: /managed rollback <token> --yes');
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (!parsed.yes) {
|
|
177
|
+
requireYesFlag(ctx, `rollback managed settings token ${token}`, '/managed rollback <token> --yes');
|
|
155
178
|
return;
|
|
156
179
|
}
|
|
157
180
|
try {
|
|
@@ -167,9 +190,9 @@ export function registerManagedRuntimeCommands(registry: CommandRegistry): void
|
|
|
167
190
|
return;
|
|
168
191
|
}
|
|
169
192
|
|
|
170
|
-
const pathArg =
|
|
193
|
+
const pathArg = commandArgs[1];
|
|
171
194
|
if (!pathArg) {
|
|
172
|
-
ctx.print(`Usage: /managed ${sub} <path
|
|
195
|
+
ctx.print(`Usage: /managed ${sub} <path>${sub === 'stage' || sub === 'apply' ? ' --yes' : ''}`);
|
|
173
196
|
return;
|
|
174
197
|
}
|
|
175
198
|
const sourcePath = shellPaths.resolveWorkspacePath(pathArg);
|
|
@@ -181,18 +204,26 @@ export function registerManagedRuntimeCommands(registry: CommandRegistry): void
|
|
|
181
204
|
}
|
|
182
205
|
|
|
183
206
|
if (sub === 'stage') {
|
|
207
|
+
if (!parsed.yes) {
|
|
208
|
+
requireYesFlag(ctx, `stage managed settings bundle from ${pathArg}`, '/managed stage <path> --yes');
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
184
211
|
const stage = stageManagedSettingsBundle(ctx.platform.configManager, bundle, sourcePath);
|
|
185
212
|
ctx.print(`Managed settings bundle staged from ${sourcePath} (${stage.changeCount} changes, risk=${stage.risk}).`);
|
|
186
213
|
return;
|
|
187
214
|
}
|
|
188
215
|
|
|
189
216
|
if (sub === 'apply') {
|
|
190
|
-
const requestedKeys =
|
|
191
|
-
const invalidKeys =
|
|
217
|
+
const requestedKeys = commandArgs.slice(2).filter((value): value is ConfigKey => CONFIG_KEYS.has(value as ConfigKey));
|
|
218
|
+
const invalidKeys = commandArgs.slice(2).filter((value) => !CONFIG_KEYS.has(value as ConfigKey));
|
|
192
219
|
if (invalidKeys.length > 0) {
|
|
193
220
|
ctx.print(`Unknown config key(s): ${invalidKeys.join(', ')}`);
|
|
194
221
|
return;
|
|
195
222
|
}
|
|
223
|
+
if (!parsed.yes) {
|
|
224
|
+
requireYesFlag(ctx, `apply managed settings bundle from ${pathArg}`, '/managed apply <path> [key ...] --yes');
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
196
227
|
stageManagedSettingsBundle(ctx.platform.configManager, bundle, sourcePath);
|
|
197
228
|
const result = applyStagedManagedBundle(ctx.platform.configManager, requestedKeys);
|
|
198
229
|
ctx.session.runtime.model = String(ctx.platform.configManager.get('provider.model'));
|
|
@@ -203,7 +234,7 @@ export function registerManagedRuntimeCommands(registry: CommandRegistry): void
|
|
|
203
234
|
}
|
|
204
235
|
|
|
205
236
|
recordSettingsSyncFailure('managed', `unsupported subcommand: ${sub}`, controlPlaneConfigDir);
|
|
206
|
-
ctx.print('Usage: /managed [review|staged|rollback-history|export <profile> <path
|
|
237
|
+
ctx.print('Usage: /managed [review|staged|rollback-history|export <profile> <path> --yes|inspect <path>|stage <path> --yes|apply <path> [key ...] --yes|apply-staged [key ...] --yes|rollback <token> --yes|lock <key> <source> <reason...> --yes|unlock <key> --yes]');
|
|
207
238
|
},
|
|
208
239
|
});
|
|
209
240
|
}
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
type EcosystemEntryKind,
|
|
21
21
|
} from '@/runtime/index.ts';
|
|
22
22
|
import { openCommandPanel, requireEcosystemCatalogPaths, requireReadModels, requireShellPaths } from './runtime-services.ts';
|
|
23
|
+
import { requireYesFlag, stripYesFlag } from './confirmation.ts';
|
|
23
24
|
|
|
24
25
|
function resolveMarketplaceEntry(
|
|
25
26
|
kind: EcosystemEntryKind,
|
|
@@ -39,11 +40,13 @@ export function registerMarketplaceRuntimeCommands(registry: CommandRegistry): v
|
|
|
39
40
|
name: 'marketplace',
|
|
40
41
|
aliases: ['catalog'],
|
|
41
42
|
description: 'Browse the unified plugin and skill marketplace',
|
|
42
|
-
usage: '[open|overview|recommend|browse [query]|review <plugin|skill|hook-pack|policy-pack> <id>|provenance <plugin|skill|hook-pack|policy-pack> <id>|install-hint <plugin|skill|hook-pack|policy-pack> <id>|install <plugin|skill|hook-pack|policy-pack> <id> [project|user]|update <plugin|skill|hook-pack|policy-pack> <id> [project|user]|rollback <plugin|skill|hook-pack|policy-pack> <id> [project|user] [backupId]|history <plugin|skill|hook-pack|policy-pack> <id> [project|user]|uninstall <plugin|skill|hook-pack|policy-pack> <id> [project|user]|receipt <plugin|skill|hook-pack|policy-pack> <id> [project|user]|bundle export <path> [project|user]|bundle inspect <path>|bundle import <path> [project|user]|installed]',
|
|
43
|
+
usage: '[open|overview|recommend|browse [query]|review <plugin|skill|hook-pack|policy-pack> <id>|provenance <plugin|skill|hook-pack|policy-pack> <id>|install-hint <plugin|skill|hook-pack|policy-pack> <id>|install <plugin|skill|hook-pack|policy-pack> <id> [project|user] --yes|update <plugin|skill|hook-pack|policy-pack> <id> [project|user] --yes|rollback <plugin|skill|hook-pack|policy-pack> <id> [project|user] [backupId] --yes|history <plugin|skill|hook-pack|policy-pack> <id> [project|user]|uninstall <plugin|skill|hook-pack|policy-pack> <id> [project|user] --yes|receipt <plugin|skill|hook-pack|policy-pack> <id> [project|user]|bundle export <path> [project|user] --yes|bundle inspect <path>|bundle import <path> [project|user] --yes|installed]',
|
|
43
44
|
handler(args, ctx) {
|
|
45
|
+
const parsed = stripYesFlag(args);
|
|
46
|
+
const commandArgs = [...parsed.rest];
|
|
44
47
|
const shellPaths = requireShellPaths(ctx);
|
|
45
48
|
const ecosystemPaths = requireEcosystemCatalogPaths(ctx);
|
|
46
|
-
const sub =
|
|
49
|
+
const sub = commandArgs[0] ?? 'open';
|
|
47
50
|
if (sub === 'open' || sub === 'panel') {
|
|
48
51
|
openCommandPanel(ctx, 'marketplace');
|
|
49
52
|
return;
|
|
@@ -71,7 +74,7 @@ export function registerMarketplaceRuntimeCommands(registry: CommandRegistry): v
|
|
|
71
74
|
return;
|
|
72
75
|
}
|
|
73
76
|
if (sub === 'browse') {
|
|
74
|
-
const query =
|
|
77
|
+
const query = commandArgs.slice(1).join(' ');
|
|
75
78
|
const pluginEntries = query ? searchEcosystemCatalog('plugin', query, ecosystemPaths) : loadEcosystemCatalog('plugin', ecosystemPaths);
|
|
76
79
|
const skillEntries = query ? searchEcosystemCatalog('skill', query, ecosystemPaths) : loadEcosystemCatalog('skill', ecosystemPaths);
|
|
77
80
|
const hookPackEntries = query ? searchEcosystemCatalog('hook-pack', query, ecosystemPaths) : loadEcosystemCatalog('hook-pack', ecosystemPaths);
|
|
@@ -113,14 +116,18 @@ export function registerMarketplaceRuntimeCommands(registry: CommandRegistry): v
|
|
|
113
116
|
return;
|
|
114
117
|
}
|
|
115
118
|
if (sub === 'bundle') {
|
|
116
|
-
const mode =
|
|
117
|
-
const target =
|
|
118
|
-
const scope =
|
|
119
|
+
const mode = commandArgs[1];
|
|
120
|
+
const target = commandArgs[2];
|
|
121
|
+
const scope = commandArgs[3] === 'user' ? 'user' : 'project';
|
|
119
122
|
if ((mode === 'export' || mode === 'inspect' || mode === 'import') && !target) {
|
|
120
|
-
ctx.print(`Usage: /marketplace bundle ${mode} <path>${mode === 'export' || mode === 'import' ? ' [project|user]' : ''}`);
|
|
123
|
+
ctx.print(`Usage: /marketplace bundle ${mode} <path>${mode === 'export' || mode === 'import' ? ' [project|user] --yes' : ''}`);
|
|
121
124
|
return;
|
|
122
125
|
}
|
|
123
126
|
if (mode === 'export') {
|
|
127
|
+
if (!parsed.yes) {
|
|
128
|
+
requireYesFlag(ctx, `export marketplace bundle to ${target}`, '/marketplace bundle export <path> [project|user] --yes');
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
124
131
|
const bundle = exportEcosystemCatalogBundle(scope, ecosystemPaths);
|
|
125
132
|
const targetPath = shellPaths.resolveWorkspacePath(target!);
|
|
126
133
|
mkdirSync(dirname(targetPath), { recursive: true });
|
|
@@ -141,6 +148,10 @@ export function registerMarketplaceRuntimeCommands(registry: CommandRegistry): v
|
|
|
141
148
|
return;
|
|
142
149
|
}
|
|
143
150
|
if (mode === 'import') {
|
|
151
|
+
if (!parsed.yes) {
|
|
152
|
+
requireYesFlag(ctx, `import marketplace bundle from ${target}`, '/marketplace bundle import <path> [project|user] --yes');
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
144
155
|
const bundle = JSON.parse(readFileSync(shellPaths.resolveWorkspacePath(target!), 'utf-8')) as EcosystemCatalogBundle;
|
|
145
156
|
const result = importEcosystemCatalogBundle(bundle, { ...ecosystemPaths, scope });
|
|
146
157
|
ctx.print([
|
|
@@ -150,14 +161,14 @@ export function registerMarketplaceRuntimeCommands(registry: CommandRegistry): v
|
|
|
150
161
|
].join('\n'));
|
|
151
162
|
return;
|
|
152
163
|
}
|
|
153
|
-
ctx.print('Usage: /marketplace bundle <export|inspect|import> <path> [project|user]');
|
|
164
|
+
ctx.print('Usage: /marketplace bundle <export|inspect|import> <path> [project|user] --yes');
|
|
154
165
|
return;
|
|
155
166
|
}
|
|
156
167
|
|
|
157
|
-
const kind =
|
|
158
|
-
const entryId =
|
|
168
|
+
const kind = commandArgs[1] as EcosystemEntryKind | undefined;
|
|
169
|
+
const entryId = commandArgs[2];
|
|
159
170
|
if ((sub === 'review' || sub === 'provenance' || sub === 'install-hint' || sub === 'install' || sub === 'update' || sub === 'rollback' || sub === 'history' || sub === 'uninstall' || sub === 'receipt') && (!kind || !entryId || !['plugin', 'skill', 'hook-pack', 'policy-pack'].includes(kind))) {
|
|
160
|
-
ctx.print(`Usage: /marketplace ${sub} <plugin|skill|hook-pack|policy-pack> <id>${sub === 'install' || sub === 'update' || sub === 'rollback' || sub === 'history' || sub === 'uninstall' || sub === 'receipt' ? ' [project|user]' : ''}`);
|
|
171
|
+
ctx.print(`Usage: /marketplace ${sub} <plugin|skill|hook-pack|policy-pack> <id>${sub === 'install' || sub === 'update' || sub === 'rollback' || sub === 'history' || sub === 'uninstall' || sub === 'receipt' ? ' [project|user]' : ''}${sub === 'install' || sub === 'update' || sub === 'rollback' || sub === 'uninstall' ? ' --yes' : ''}`);
|
|
161
172
|
return;
|
|
162
173
|
}
|
|
163
174
|
if (sub === 'review') {
|
|
@@ -216,7 +227,7 @@ export function registerMarketplaceRuntimeCommands(registry: CommandRegistry): v
|
|
|
216
227
|
return;
|
|
217
228
|
}
|
|
218
229
|
if (sub === 'receipt') {
|
|
219
|
-
const scope =
|
|
230
|
+
const scope = commandArgs[3] === 'user' ? 'user' : 'project';
|
|
220
231
|
const result = inspectInstalledEcosystemEntry(kind!, entryId!, { ...ecosystemPaths, scope });
|
|
221
232
|
if (!result.ok) {
|
|
222
233
|
ctx.print(`Error: ${result.error}`);
|
|
@@ -236,7 +247,7 @@ export function registerMarketplaceRuntimeCommands(registry: CommandRegistry): v
|
|
|
236
247
|
return;
|
|
237
248
|
}
|
|
238
249
|
if (sub === 'history') {
|
|
239
|
-
const scope =
|
|
250
|
+
const scope = commandArgs[3] === 'user' ? 'user' : 'project';
|
|
240
251
|
const backups = listEcosystemInstallBackups(kind!, entryId!, { ...ecosystemPaths, scope });
|
|
241
252
|
ctx.print(backups.length > 0
|
|
242
253
|
? [
|
|
@@ -247,7 +258,11 @@ export function registerMarketplaceRuntimeCommands(registry: CommandRegistry): v
|
|
|
247
258
|
return;
|
|
248
259
|
}
|
|
249
260
|
if (sub === 'install' || sub === 'update' || sub === 'rollback' || sub === 'uninstall') {
|
|
250
|
-
const scope =
|
|
261
|
+
const scope = commandArgs[3] === 'user' ? 'user' : 'project';
|
|
262
|
+
if (!parsed.yes) {
|
|
263
|
+
requireYesFlag(ctx, `${sub} curated ${kind} ${entryId}`, `/marketplace ${sub} <plugin|skill|hook-pack|policy-pack> <id> [project|user]${sub === 'rollback' ? ' [backupId]' : ''} --yes`);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
251
266
|
if (sub === 'install') {
|
|
252
267
|
const result = installEcosystemCatalogEntry(kind!, entryId!, { ...ecosystemPaths, scope });
|
|
253
268
|
if (!result.ok) {
|
|
@@ -267,7 +282,7 @@ export function registerMarketplaceRuntimeCommands(registry: CommandRegistry): v
|
|
|
267
282
|
return;
|
|
268
283
|
}
|
|
269
284
|
if (sub === 'rollback') {
|
|
270
|
-
const backupId =
|
|
285
|
+
const backupId = commandArgs[4];
|
|
271
286
|
const result = rollbackInstalledEcosystemEntry(kind!, entryId!, { ...ecosystemPaths, scope, backupId });
|
|
272
287
|
if (!result.ok) {
|
|
273
288
|
ctx.print(`Error: ${result.error}`);
|
|
@@ -284,7 +299,7 @@ export function registerMarketplaceRuntimeCommands(registry: CommandRegistry): v
|
|
|
284
299
|
ctx.print(`Uninstalled curated ${kind} ${entryId} from ${result.removedPath}`);
|
|
285
300
|
return;
|
|
286
301
|
}
|
|
287
|
-
ctx.print('Usage: /marketplace [open|overview|recommend|browse [query]|review <plugin|skill|hook-pack|policy-pack> <id>|provenance <plugin|skill|hook-pack|policy-pack> <id>|install-hint <plugin|skill|hook-pack|policy-pack> <id>|install <plugin|skill|hook-pack|policy-pack> <id> [project|user]|update <plugin|skill|hook-pack|policy-pack> <id> [project|user]|rollback <plugin|skill|hook-pack|policy-pack> <id> [project|user] [backupId]|history <plugin|skill|hook-pack|policy-pack> <id> [project|user]|uninstall <plugin|skill|hook-pack|policy-pack> <id> [project|user]|receipt <plugin|skill|hook-pack|policy-pack> <id> [project|user]|bundle export <path> [project|user]|bundle inspect <path>|bundle import <path> [project|user]|installed]');
|
|
302
|
+
ctx.print('Usage: /marketplace [open|overview|recommend|browse [query]|review <plugin|skill|hook-pack|policy-pack> <id>|provenance <plugin|skill|hook-pack|policy-pack> <id>|install-hint <plugin|skill|hook-pack|policy-pack> <id>|install <plugin|skill|hook-pack|policy-pack> <id> [project|user] --yes|update <plugin|skill|hook-pack|policy-pack> <id> [project|user] --yes|rollback <plugin|skill|hook-pack|policy-pack> <id> [project|user] [backupId] --yes|history <plugin|skill|hook-pack|policy-pack> <id> [project|user]|uninstall <plugin|skill|hook-pack|policy-pack> <id> [project|user] --yes|receipt <plugin|skill|hook-pack|policy-pack> <id> [project|user]|bundle export <path> [project|user] --yes|bundle inspect <path>|bundle import <path> [project|user] --yes|installed]');
|
|
288
303
|
},
|
|
289
304
|
});
|
|
290
305
|
}
|
|
@@ -2,6 +2,7 @@ import type { CommandContext, CommandRegistry } from '../command-registry.ts';
|
|
|
2
2
|
import type { McpConfigScope, McpReloadResult, McpServerConfig } from '@pellux/goodvibes-sdk/platform/mcp';
|
|
3
3
|
import { requireMcpApi, requireShellPaths } from './runtime-services.ts';
|
|
4
4
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
5
|
+
import { requireYesFlag, stripYesFlag } from './confirmation.ts';
|
|
5
6
|
|
|
6
7
|
const MCP_ROLES = ['general', 'docs', 'filesystem', 'git', 'database', 'browser', 'automation', 'ops', 'remote'] as const;
|
|
7
8
|
const MCP_TRUST_MODES = ['constrained', 'ask-on-risk', 'allow-all', 'blocked'] as const;
|
|
@@ -137,11 +138,13 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
137
138
|
aliases: [],
|
|
138
139
|
description: 'Manage MCP servers and their tools',
|
|
139
140
|
usage: '[add|remove|reload|config|review|tools [<server>]|auth-review|repair [server]]',
|
|
140
|
-
argsHint: '[
|
|
141
|
+
argsHint: '[review|tools|config|add --yes|remove --yes]',
|
|
141
142
|
async handler(args, ctx) {
|
|
142
143
|
const mcpApi = requireMcpApi(ctx);
|
|
143
144
|
const listServerSecurity = () => mcpApi.listServerSecurity();
|
|
144
|
-
const
|
|
145
|
+
const confirmation = stripYesFlag(args);
|
|
146
|
+
const commandArgs = [...confirmation.rest];
|
|
147
|
+
const subcommand = commandArgs[0];
|
|
145
148
|
if (!subcommand && ctx.openMcpWorkspace) {
|
|
146
149
|
ctx.openMcpWorkspace();
|
|
147
150
|
return;
|
|
@@ -161,7 +164,7 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
161
164
|
return;
|
|
162
165
|
}
|
|
163
166
|
if (subcommand === 'tools') {
|
|
164
|
-
const filterServer =
|
|
167
|
+
const filterServer = commandArgs[1];
|
|
165
168
|
ctx.print('Fetching MCP tool list...');
|
|
166
169
|
let allTools;
|
|
167
170
|
try {
|
|
@@ -207,7 +210,7 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
207
210
|
}
|
|
208
211
|
|
|
209
212
|
if (subcommand === 'repair') {
|
|
210
|
-
const serverName =
|
|
213
|
+
const serverName = commandArgs[1];
|
|
211
214
|
const servers = listServerSecurity();
|
|
212
215
|
const selected = serverName ? servers.find((server) => server.name === serverName) : servers.find((server) => !server.connected || server.schemaFreshness === 'quarantined');
|
|
213
216
|
if (!selected) {
|
|
@@ -218,7 +221,7 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
218
221
|
}
|
|
219
222
|
const nextSteps = [
|
|
220
223
|
selected.schemaFreshness === 'quarantined'
|
|
221
|
-
? `/mcp quarantine ${selected.name} approve operator`
|
|
224
|
+
? `/mcp quarantine ${selected.name} approve operator --yes`
|
|
222
225
|
: null,
|
|
223
226
|
!selected.connected ? '/services auth-review' : null,
|
|
224
227
|
'/mcp review',
|
|
@@ -239,14 +242,18 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
239
242
|
}
|
|
240
243
|
|
|
241
244
|
if (subcommand === 'trust') {
|
|
242
|
-
const serverName =
|
|
243
|
-
const mode =
|
|
245
|
+
const serverName = commandArgs[1];
|
|
246
|
+
const mode = commandArgs[2] as 'constrained' | 'ask-on-risk' | 'allow-all' | 'blocked' | undefined;
|
|
244
247
|
if (serverName && mode) {
|
|
245
248
|
if (mode === 'allow-all') {
|
|
246
249
|
ctx.print(`Use /settings → MCP to explicitly enable allow-all for ${serverName}. Direct CLI escalation is blocked.`);
|
|
247
250
|
ctx.openSettingsModal?.();
|
|
248
251
|
return;
|
|
249
252
|
}
|
|
253
|
+
if (!confirmation.yes) {
|
|
254
|
+
requireYesFlag(ctx, `change MCP trust mode for ${serverName}`, '/mcp trust <server> <constrained|ask-on-risk|blocked> --yes');
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
250
257
|
mcpApi.setServerTrustMode(serverName, mode);
|
|
251
258
|
ctx.print(`Updated MCP trust mode for ${serverName} to ${mode}.`);
|
|
252
259
|
return;
|
|
@@ -258,9 +265,13 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
258
265
|
}
|
|
259
266
|
|
|
260
267
|
if (subcommand === 'role') {
|
|
261
|
-
const serverName =
|
|
262
|
-
const role =
|
|
268
|
+
const serverName = commandArgs[1];
|
|
269
|
+
const role = commandArgs[2] as 'general' | 'docs' | 'filesystem' | 'git' | 'database' | 'browser' | 'automation' | 'ops' | 'remote' | undefined;
|
|
263
270
|
if (serverName && role) {
|
|
271
|
+
if (!confirmation.yes) {
|
|
272
|
+
requireYesFlag(ctx, `change MCP role for ${serverName}`, '/mcp role <server> <general|docs|filesystem|git|database|browser|automation|ops|remote> --yes');
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
264
275
|
mcpApi.setServerRole(serverName, role);
|
|
265
276
|
ctx.print(`Updated MCP role for ${serverName} to ${role}.`);
|
|
266
277
|
return;
|
|
@@ -272,21 +283,25 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
272
283
|
}
|
|
273
284
|
|
|
274
285
|
if (subcommand === 'add') {
|
|
275
|
-
|
|
286
|
+
if (!confirmation.yes) {
|
|
287
|
+
requireYesFlag(ctx, 'add or update an MCP server config', '/mcp add <name> <command> [args...] [--scope project|global] [--role <role>] [--trust <mode>] --yes');
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
let parsedAdd: ParsedMcpAddArgs;
|
|
276
291
|
try {
|
|
277
|
-
|
|
292
|
+
parsedAdd = parseAddServerArgs(commandArgs);
|
|
278
293
|
} catch (error) {
|
|
279
294
|
ctx.print(summarizeError(error));
|
|
280
295
|
return;
|
|
281
296
|
}
|
|
282
297
|
const shellPaths = requireShellPaths(ctx);
|
|
283
298
|
try {
|
|
284
|
-
const result = await mcpApi.upsertServerConfig(shellPaths,
|
|
285
|
-
const connected = listServerSecurity().find((entry) => entry.name ===
|
|
299
|
+
const result = await mcpApi.upsertServerConfig(shellPaths, parsedAdd.scope, parsedAdd.server);
|
|
300
|
+
const connected = listServerSecurity().find((entry) => entry.name === parsedAdd.server.name)?.connected ?? false;
|
|
286
301
|
ctx.print([
|
|
287
|
-
`MCP server "${
|
|
302
|
+
`MCP server "${parsedAdd.server.name}" saved to ${parsedAdd.scope} config: ${result.path}.`,
|
|
288
303
|
`Runtime reload: ${connected ? 'connected' : 'server saved; connection needs attention'} (+${result.reload.added} ~${result.reload.changed} -${result.reload.removed}, unchanged ${result.reload.unchanged}).`,
|
|
289
|
-
`Command: ${
|
|
304
|
+
`Command: ${parsedAdd.server.command}${parsedAdd.server.args?.length ? ` ${parsedAdd.server.args.join(' ')}` : ''}`,
|
|
290
305
|
'Next: /mcp tools',
|
|
291
306
|
].join('\n'));
|
|
292
307
|
} catch (error) {
|
|
@@ -296,16 +311,20 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
296
311
|
}
|
|
297
312
|
|
|
298
313
|
if (subcommand === 'remove') {
|
|
299
|
-
const serverName =
|
|
314
|
+
const serverName = commandArgs[1]?.trim();
|
|
300
315
|
if (!serverName) {
|
|
301
|
-
ctx.print('Usage: /mcp remove <server> [--scope project|global]');
|
|
316
|
+
ctx.print('Usage: /mcp remove <server> [--scope project|global] --yes');
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
if (!confirmation.yes) {
|
|
320
|
+
requireYesFlag(ctx, `remove MCP server ${serverName}`, '/mcp remove <server> [--scope project|global] --yes');
|
|
302
321
|
return;
|
|
303
322
|
}
|
|
304
323
|
let scope: McpConfigScope = 'project';
|
|
305
324
|
try {
|
|
306
|
-
for (let index = 2; index <
|
|
307
|
-
if (
|
|
308
|
-
const value = readFlagValue(
|
|
325
|
+
for (let index = 2; index < commandArgs.length; index += 1) {
|
|
326
|
+
if (commandArgs[index] === '--scope') {
|
|
327
|
+
const value = readFlagValue(commandArgs, index, '--scope');
|
|
309
328
|
if (!isMcpScope(value)) {
|
|
310
329
|
ctx.print(`Invalid MCP scope "${value}". Expected project or global.`);
|
|
311
330
|
return;
|
|
@@ -331,6 +350,10 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
331
350
|
}
|
|
332
351
|
|
|
333
352
|
if (subcommand === 'reload') {
|
|
353
|
+
if (!confirmation.yes) {
|
|
354
|
+
requireYesFlag(ctx, 'reload the MCP runtime from config', '/mcp reload --yes');
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
334
357
|
try {
|
|
335
358
|
const result = await reloadMcpRuntime(ctx);
|
|
336
359
|
const servers = listServerSecurity();
|
|
@@ -356,10 +379,10 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
356
379
|
return ` - ${server.name}: ${server.command}${server.args?.length ? ` ${server.args.join(' ')}` : ''} source=${entry.source.scope}/${entry.source.kind}${envKeys.length ? ` envKeys=${envKeys.join(',')}` : ''}`;
|
|
357
380
|
}),
|
|
358
381
|
'',
|
|
359
|
-
'Add or update from inside
|
|
360
|
-
' /mcp add <name> <command> [args...] [--scope project|global] [--role <role>] [--trust <mode>]',
|
|
382
|
+
'Add or update from inside Agent with explicit confirmation:',
|
|
383
|
+
' /mcp add <name> <command> [args...] [--scope project|global] [--role <role>] [--trust <mode>] --yes',
|
|
361
384
|
'Example:',
|
|
362
|
-
' /mcp add filesystem npx -y @modelcontextprotocol/server-filesystem . --scope project --role filesystem --trust constrained',
|
|
385
|
+
' /mcp add filesystem npx -y @modelcontextprotocol/server-filesystem . --scope project --role filesystem --trust constrained --yes',
|
|
363
386
|
].join('\n'));
|
|
364
387
|
} catch (error) {
|
|
365
388
|
ctx.print(`MCP config read failed: ${summarizeError(error)}`);
|
|
@@ -368,19 +391,27 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
368
391
|
}
|
|
369
392
|
|
|
370
393
|
if (subcommand === 'quarantine') {
|
|
371
|
-
const serverName =
|
|
372
|
-
const action =
|
|
394
|
+
const serverName = commandArgs[1];
|
|
395
|
+
const action = commandArgs[2];
|
|
373
396
|
if (!serverName) {
|
|
374
|
-
ctx.print('Usage: /mcp quarantine <server> [detail]\n /mcp quarantine <server> approve [operatorId]');
|
|
397
|
+
ctx.print('Usage: /mcp quarantine <server> [detail] --yes\n /mcp quarantine <server> approve [operatorId] --yes');
|
|
375
398
|
return;
|
|
376
399
|
}
|
|
377
400
|
if (action === 'approve') {
|
|
378
|
-
|
|
401
|
+
if (!confirmation.yes) {
|
|
402
|
+
requireYesFlag(ctx, `approve MCP schema quarantine override for ${serverName}`, '/mcp quarantine <server> approve [operatorId] --yes');
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
const operatorId = commandArgs[3] || 'operator';
|
|
379
406
|
mcpApi.approveSchemaQuarantine(serverName, operatorId);
|
|
380
407
|
ctx.print(`Approved MCP schema quarantine override for ${serverName} as ${operatorId}. Refresh is still recommended.`);
|
|
381
408
|
return;
|
|
382
409
|
}
|
|
383
|
-
|
|
410
|
+
if (!confirmation.yes) {
|
|
411
|
+
requireYesFlag(ctx, `quarantine MCP server ${serverName}`, '/mcp quarantine <server> [detail] --yes');
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
const detail = commandArgs.slice(2).join(' ') || 'quarantined by operator';
|
|
384
415
|
mcpApi.quarantineSchema(serverName, 'operator_flagged', detail);
|
|
385
416
|
ctx.print(`Quarantined MCP schema for ${serverName}.\nReason: ${detail}`);
|
|
386
417
|
return;
|
|
@@ -396,8 +427,8 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
396
427
|
+ ' ~/.config/claude/claude_desktop_config.json (Claude Desktop)\n'
|
|
397
428
|
+ ' .mcp/mcp.json (project-local)\n'
|
|
398
429
|
+ ' .goodvibes/mcp.json (goodvibes project)\n'
|
|
399
|
-
+ '\nAdd one from inside
|
|
400
|
-
+ ' /mcp add filesystem npx -y @modelcontextprotocol/server-filesystem . --scope project --role filesystem\n'
|
|
430
|
+
+ '\nAdd one from inside Agent with explicit confirmation:\n'
|
|
431
|
+
+ ' /mcp add filesystem npx -y @modelcontextprotocol/server-filesystem . --scope project --role filesystem --yes\n'
|
|
401
432
|
+ '\nFormat: { "servers": [{ "name": "my-server", "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"] }] }'
|
|
402
433
|
);
|
|
403
434
|
return;
|
|
@@ -416,10 +447,10 @@ export function registerMcpRuntimeCommands(registry: CommandRegistry): void {
|
|
|
416
447
|
if (connected.length > 0) {
|
|
417
448
|
lines.push('');
|
|
418
449
|
lines.push('Run "/mcp tools" to list all tools, or "/mcp tools <server>" for a specific server.');
|
|
419
|
-
lines.push('Run "/mcp" to open the fullscreen MCP workspace, or "/mcp add <name> <command> [args...] [--scope project|global]" to add/update
|
|
420
|
-
lines.push('Run "/mcp reload" after editing MCP config outside
|
|
421
|
-
lines.push('Run "/mcp trust <server> <mode>" to change trust mode, or "/mcp role <server> <role>" to change its coherence role.');
|
|
422
|
-
lines.push('Run "/mcp quarantine <server> [detail]" to block a server, or "/mcp quarantine <server> approve [operatorId]" to approve a temporary override.');
|
|
450
|
+
lines.push('Run "/mcp" to open the fullscreen MCP workspace, or "/mcp add <name> <command> [args...] [--scope project|global] --yes" to add/update.');
|
|
451
|
+
lines.push('Run "/mcp reload --yes" after editing MCP config outside Agent.');
|
|
452
|
+
lines.push('Run "/mcp trust <server> <mode> --yes" to change trust mode, or "/mcp role <server> <role> --yes" to change its coherence role.');
|
|
453
|
+
lines.push('Run "/mcp quarantine <server> [detail] --yes" to block a server, or "/mcp quarantine <server> approve [operatorId] --yes" to approve a temporary override.');
|
|
423
454
|
lines.push('Use /settings → MCP to explicitly enable allow-all for a server.');
|
|
424
455
|
}
|
|
425
456
|
if (disconnected.length > 0) {
|