@pellux/goodvibes-agent 0.1.69 → 0.1.71
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 +12 -0
- package/package.json +42 -1
- package/src/agent/skill-discovery.ts +119 -0
- package/src/input/commands/delegation-runtime.ts +0 -8
- package/src/input/commands/experience-runtime.ts +0 -177
- package/src/input/commands/guidance-runtime.ts +9 -77
- package/src/input/commands/local-runtime.ts +1 -57
- package/src/input/commands/local-setup-review.ts +1 -1
- package/src/input/commands/operator-runtime.ts +1 -145
- package/src/input/commands/platform-access-runtime.ts +2 -195
- package/src/input/commands/product-runtime.ts +0 -116
- package/src/input/commands/security-runtime.ts +88 -0
- package/src/input/commands/session-content.ts +0 -97
- package/src/input/commands/shell-core.ts +1 -22
- package/src/input/commands.ts +2 -43
- package/src/panels/builtin/operations.ts +3 -184
- package/src/panels/index.ts +0 -11
- package/src/version.ts +1 -1
- package/src/input/commands/branch-runtime.ts +0 -72
- package/src/input/commands/control-room-runtime.ts +0 -234
- package/src/input/commands/discovery-runtime.ts +0 -61
- package/src/input/commands/hooks-runtime.ts +0 -207
- package/src/input/commands/incident-runtime.ts +0 -106
- package/src/input/commands/integration-runtime.ts +0 -437
- package/src/input/commands/local-setup.ts +0 -288
- package/src/input/commands/managed-runtime.ts +0 -240
- package/src/input/commands/marketplace-runtime.ts +0 -305
- package/src/input/commands/memory-product-runtime.ts +0 -148
- package/src/input/commands/operator-panel-runtime.ts +0 -146
- package/src/input/commands/platform-services-runtime.ts +0 -271
- package/src/input/commands/profile-sync-runtime.ts +0 -110
- package/src/input/commands/provider.ts +0 -363
- package/src/input/commands/remote-runtime-pool.ts +0 -89
- package/src/input/commands/remote-runtime-setup.ts +0 -226
- package/src/input/commands/remote-runtime.ts +0 -432
- package/src/input/commands/replay-runtime.ts +0 -25
- package/src/input/commands/services-runtime.ts +0 -220
- package/src/input/commands/settings-sync-runtime.ts +0 -197
- package/src/input/commands/share-runtime.ts +0 -127
- package/src/input/commands/skills-runtime.ts +0 -226
- package/src/input/commands/teleport-runtime.ts +0 -68
- package/src/panels/cockpit-panel.ts +0 -183
- package/src/panels/communication-panel.ts +0 -153
- package/src/panels/control-plane-panel.ts +0 -211
- package/src/panels/forensics-panel.ts +0 -364
- package/src/panels/hooks-panel.ts +0 -239
- package/src/panels/incident-review-panel.ts +0 -197
- package/src/panels/marketplace-panel.ts +0 -212
- package/src/panels/ops-control-panel.ts +0 -150
- package/src/panels/ops-strategy-panel.ts +0 -235
- package/src/panels/orchestration-panel.ts +0 -272
- package/src/panels/plugins-panel.ts +0 -178
- package/src/panels/remote-panel.ts +0 -449
- package/src/panels/routes-panel.ts +0 -178
- package/src/panels/services-panel.ts +0 -231
- package/src/panels/settings-sync-panel.ts +0 -120
- package/src/panels/skills-panel.ts +0 -431
- package/src/panels/watchers-panel.ts +0 -193
- package/src/verification/live-verifier.ts +0 -588
- package/src/verification/verification-ledger.ts +0 -239
|
@@ -1,437 +0,0 @@
|
|
|
1
|
-
import { resolve } from 'path';
|
|
2
|
-
import type { PluginStatus } from '@pellux/goodvibes-sdk/platform/plugins';
|
|
3
|
-
import { getPluginDirectories, getUserPluginDirectory } from '../../plugins/loader';
|
|
4
|
-
import type { CommandRegistry } from '../command-registry.ts';
|
|
5
|
-
import {
|
|
6
|
-
installEcosystemCatalogEntry,
|
|
7
|
-
listInstalledEcosystemEntries,
|
|
8
|
-
loadEcosystemCatalog,
|
|
9
|
-
removeEcosystemCatalogEntry,
|
|
10
|
-
reviewEcosystemCatalogEntry,
|
|
11
|
-
searchEcosystemCatalog,
|
|
12
|
-
updateInstalledEcosystemEntry,
|
|
13
|
-
upsertEcosystemCatalogEntry,
|
|
14
|
-
uninstallEcosystemCatalogEntry,
|
|
15
|
-
} from '@/runtime/index.ts';
|
|
16
|
-
import { requireEcosystemCatalogPaths, requirePluginPathOptions } from './runtime-services.ts';
|
|
17
|
-
import { requireYesFlag, stripYesFlag } from './confirmation.ts';
|
|
18
|
-
|
|
19
|
-
export function registerIntegrationRuntimeCommands(registry: CommandRegistry): void {
|
|
20
|
-
registry.register({
|
|
21
|
-
name: 'plugin',
|
|
22
|
-
aliases: [],
|
|
23
|
-
description: 'Manage plugins, trust, review, and ecosystem paths',
|
|
24
|
-
usage: 'list | dirs | inspect <name> | review | installed | catalog-review <id> | publish-local <id> <path> <summary...> --yes | unpublish <id> --yes | install <id> [project|user] --yes | update <id> [project|user] --yes | uninstall <id> [project|user] --yes | enable <name> --yes | disable <name> --yes | reload --yes',
|
|
25
|
-
argsHint: 'list | dirs | inspect | review | installed | catalog-review | publish-local --yes | unpublish --yes | install --yes | update --yes | uninstall --yes | enable --yes | disable --yes | reload --yes',
|
|
26
|
-
async handler(args, ctx) {
|
|
27
|
-
const parsed = stripYesFlag(args);
|
|
28
|
-
const commandArgs = [...parsed.rest];
|
|
29
|
-
const pluginManager = ctx.extensions.pluginManager;
|
|
30
|
-
const ecosystemPaths = requireEcosystemCatalogPaths(ctx);
|
|
31
|
-
const pluginPaths = requirePluginPathOptions(ctx);
|
|
32
|
-
if (!pluginManager) {
|
|
33
|
-
ctx.print('Plugin manager is not available in this runtime.');
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
const sub = commandArgs[0];
|
|
37
|
-
|
|
38
|
-
if (!sub || sub === 'open' || sub === 'panel') {
|
|
39
|
-
if (ctx.showPanel) ctx.showPanel('plugins');
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (sub === 'list') {
|
|
44
|
-
const plugins = pluginManager.list() as PluginStatus[];
|
|
45
|
-
if (plugins.length === 0) {
|
|
46
|
-
const directories = getPluginDirectories(pluginPaths)
|
|
47
|
-
.map((dir) => ` ${dir}`)
|
|
48
|
-
.join('\n');
|
|
49
|
-
ctx.print(
|
|
50
|
-
`No plugins installed.\nPlugin search directories:\n${directories}\nPlace a plugin folder in one of those locations with manifest.json and index.ts.`
|
|
51
|
-
);
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
const lines: string[] = ['Installed plugins:'];
|
|
55
|
-
for (const p of plugins) {
|
|
56
|
-
const statusIcon = p.active ? '[active] ' : p.enabled ? '[loading] ' : '[disabled]';
|
|
57
|
-
lines.push(` ${statusIcon} ${p.name.padEnd(24)} v${p.version} — ${p.description}`);
|
|
58
|
-
if (p.author) lines.push(` by ${p.author}`);
|
|
59
|
-
}
|
|
60
|
-
lines.push('');
|
|
61
|
-
lines.push('Use /plugin enable <name> --yes or /plugin disable <name> --yes to toggle plugins.');
|
|
62
|
-
ctx.print(lines.join('\n'));
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
if (sub === 'dirs') {
|
|
66
|
-
const directories = getPluginDirectories(pluginPaths);
|
|
67
|
-
ctx.print([
|
|
68
|
-
'Plugin Search Directories',
|
|
69
|
-
...directories.map((dir) => ` ${dir}`),
|
|
70
|
-
'',
|
|
71
|
-
`User plugin directory: ${getUserPluginDirectory(pluginPaths)}`,
|
|
72
|
-
].join('\n'));
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
if (sub === 'inspect') {
|
|
76
|
-
const name = commandArgs[1];
|
|
77
|
-
if (!name) {
|
|
78
|
-
ctx.print('Usage: /plugin inspect <name>');
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
const status = pluginManager.list().find((plugin) => plugin.name === name);
|
|
82
|
-
if (!status) {
|
|
83
|
-
ctx.print(`Error: Plugin '${name}' not found.`);
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
const capabilities = pluginManager.capabilities(name);
|
|
87
|
-
const trust = pluginManager.getTrustRecord(name);
|
|
88
|
-
const quarantine = pluginManager.getQuarantineRecord(name);
|
|
89
|
-
ctx.print([
|
|
90
|
-
`Plugin ${name}`,
|
|
91
|
-
` version: ${status.version}`,
|
|
92
|
-
` state: ${status.active ? 'active' : status.enabled ? 'enabled' : 'disabled'}`,
|
|
93
|
-
` trustTier: ${status.trustTier}`,
|
|
94
|
-
` quarantined: ${status.quarantined ? 'yes' : 'no'}`,
|
|
95
|
-
` requestedCapabilities: ${capabilities?.requested.length ?? 0}`,
|
|
96
|
-
` highRiskCapabilities: ${capabilities?.highRisk.length ?? 0}`,
|
|
97
|
-
` blockedCapabilities: ${capabilities?.blocked.length ?? 0}`,
|
|
98
|
-
` signedFingerprint: ${trust?.signatureFingerprint ?? 'n/a'}`,
|
|
99
|
-
` quarantineReason: ${quarantine?.reason ?? 'n/a'}`,
|
|
100
|
-
].join('\n'));
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
if (sub === 'review') {
|
|
104
|
-
const plugins = pluginManager.list();
|
|
105
|
-
ctx.print([
|
|
106
|
-
'Plugin Security Review',
|
|
107
|
-
` total: ${plugins.length}`,
|
|
108
|
-
` active: ${plugins.filter((plugin) => plugin.active).length}`,
|
|
109
|
-
` trusted: ${plugins.filter((plugin) => plugin.trustTier === 'trusted').length}`,
|
|
110
|
-
` limited: ${plugins.filter((plugin) => plugin.trustTier === 'limited').length}`,
|
|
111
|
-
` untrusted: ${plugins.filter((plugin) => plugin.trustTier === 'untrusted').length}`,
|
|
112
|
-
` quarantined: ${plugins.filter((plugin) => plugin.quarantined).length}`,
|
|
113
|
-
].join('\n'));
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
if (sub === 'browse' || sub === 'catalog') {
|
|
117
|
-
const query = commandArgs.slice(1).join(' ');
|
|
118
|
-
const entries = query
|
|
119
|
-
? searchEcosystemCatalog('plugin', query, ecosystemPaths)
|
|
120
|
-
: loadEcosystemCatalog('plugin', ecosystemPaths);
|
|
121
|
-
if (entries.length === 0) {
|
|
122
|
-
ctx.print(query
|
|
123
|
-
? `No curated plugin catalog entries matched "${query}".`
|
|
124
|
-
: 'No curated plugin catalog entries found. Add .goodvibes/agent/ecosystem/plugins.json to publish a local-first plugin catalog.');
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
ctx.print([
|
|
128
|
-
`Curated Plugin Catalog (${entries.length})`,
|
|
129
|
-
...entries.map((entry) => ` ${entry.id} ${entry.name} [${entry.tags.join(', ') || 'untagged'}] ${entry.summary}`),
|
|
130
|
-
].join('\n'));
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
if (sub === 'installed') {
|
|
134
|
-
const receipts = listInstalledEcosystemEntries('plugin', ecosystemPaths);
|
|
135
|
-
if (receipts.length === 0) {
|
|
136
|
-
ctx.print('No curated plugins installed from local catalogs yet.');
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
ctx.print([
|
|
140
|
-
`Installed Curated Plugins (${receipts.length})`,
|
|
141
|
-
...receipts.map((receipt) => ` ${receipt.entry.id} ${receipt.scope} ${receipt.targetPath}`),
|
|
142
|
-
].join('\n'));
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
if (sub === 'catalog-review') {
|
|
146
|
-
const entryId = commandArgs[1];
|
|
147
|
-
if (!entryId) {
|
|
148
|
-
ctx.print('Usage: /plugin catalog-review <catalog-id>');
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
const entry = loadEcosystemCatalog('plugin', ecosystemPaths).find((candidate) => candidate.id === entryId);
|
|
152
|
-
if (!entry) {
|
|
153
|
-
ctx.print(`Unknown curated plugin entry: ${entryId}`);
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
const review = reviewEcosystemCatalogEntry(entry, ecosystemPaths);
|
|
157
|
-
ctx.print([
|
|
158
|
-
`Plugin Catalog Review: ${entry.name}`,
|
|
159
|
-
` id: ${entry.id}`,
|
|
160
|
-
` source: ${entry.source}`,
|
|
161
|
-
` sourceKind: ${review.sourceKind}`,
|
|
162
|
-
` sourceExists: ${review.sourceExists ? 'yes' : 'no'}`,
|
|
163
|
-
` recommendedScope: ${review.recommendedScope}`,
|
|
164
|
-
` risk: ${review.riskLevel}`,
|
|
165
|
-
` trust notes: ${entry.trustNotes ?? '(none)'}`,
|
|
166
|
-
` provenance: ${entry.provenance ?? '(none)'}`,
|
|
167
|
-
` update hint: ${entry.updateHint ?? '(none)'}`,
|
|
168
|
-
].join('\n'));
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
if (sub === 'install-hint') {
|
|
172
|
-
const entryId = commandArgs[1];
|
|
173
|
-
if (!entryId) {
|
|
174
|
-
ctx.print('Usage: /plugin install-hint <catalog-id>');
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
const entry = loadEcosystemCatalog('plugin', ecosystemPaths).find((candidate) => candidate.id === entryId);
|
|
178
|
-
if (!entry) {
|
|
179
|
-
ctx.print(`Unknown curated plugin entry: ${entryId}`);
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
ctx.print([
|
|
183
|
-
`Plugin Install Guidance: ${entry.name}`,
|
|
184
|
-
` id: ${entry.id}`,
|
|
185
|
-
` source: ${entry.source}`,
|
|
186
|
-
` tags: ${entry.tags.join(', ') || '(none)'}`,
|
|
187
|
-
` trust notes: ${entry.trustNotes ?? '(none)'}`,
|
|
188
|
-
` install hint: ${entry.installHint ?? 'Place the plugin under a configured plugin search directory and use /plugin reload --yes.'}`,
|
|
189
|
-
].join('\n'));
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
if (sub === 'publish-local') {
|
|
193
|
-
const entryId = commandArgs[1];
|
|
194
|
-
const sourcePath = commandArgs[2];
|
|
195
|
-
const summary = commandArgs.slice(3).join(' ').trim();
|
|
196
|
-
if (!entryId || !sourcePath || !summary) {
|
|
197
|
-
ctx.print('Usage: /plugin publish-local <catalog-id> <path> <summary...> --yes');
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
if (!parsed.yes) {
|
|
201
|
-
requireYesFlag(ctx, `publish curated plugin ${entryId}`, '/plugin publish-local <catalog-id> <path> <summary...> --yes');
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
const result = upsertEcosystemCatalogEntry({
|
|
205
|
-
id: entryId,
|
|
206
|
-
kind: 'plugin',
|
|
207
|
-
name: entryId.replace(/[-_]/g, ' ').replace(/\b\w/g, (char) => char.toUpperCase()),
|
|
208
|
-
summary,
|
|
209
|
-
source: sourcePath,
|
|
210
|
-
tags: ['local-first', 'published'],
|
|
211
|
-
provenance: 'operator-published',
|
|
212
|
-
updateHint: 'Use /plugin publish-local again to refresh catalog metadata after edits.',
|
|
213
|
-
}, ecosystemPaths);
|
|
214
|
-
ctx.print(result.ok
|
|
215
|
-
? `Published curated plugin ${entryId} into ${result.path}`
|
|
216
|
-
: `Error: ${result.error}`);
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
if (sub === 'unpublish') {
|
|
220
|
-
const entryId = commandArgs[1];
|
|
221
|
-
if (!entryId) {
|
|
222
|
-
ctx.print('Usage: /plugin unpublish <catalog-id> --yes');
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
if (!parsed.yes) {
|
|
226
|
-
requireYesFlag(ctx, `unpublish curated plugin ${entryId}`, '/plugin unpublish <catalog-id> --yes');
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
const result = removeEcosystemCatalogEntry('plugin', entryId, ecosystemPaths);
|
|
230
|
-
ctx.print(result.ok
|
|
231
|
-
? `Removed curated plugin ${entryId} from ${result.path}`
|
|
232
|
-
: `Error: ${result.error}`);
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
if (sub === 'install') {
|
|
236
|
-
const entryId = commandArgs[1];
|
|
237
|
-
const scopeArg = commandArgs[2];
|
|
238
|
-
if (!entryId) {
|
|
239
|
-
ctx.print('Usage: /plugin install <catalog-id> [project|user] --yes');
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
if (!parsed.yes) {
|
|
243
|
-
requireYesFlag(ctx, `install curated plugin ${entryId}`, '/plugin install <catalog-id> [project|user] --yes');
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
const scope = scopeArg === 'user' ? 'user' : 'project';
|
|
247
|
-
const result = installEcosystemCatalogEntry('plugin', entryId, { ...ecosystemPaths, scope });
|
|
248
|
-
ctx.print(result.ok
|
|
249
|
-
? `Installed curated plugin ${entryId} into ${result.receipt.targetPath}`
|
|
250
|
-
: `Error: ${result.error}`);
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
|
-
if (sub === 'update') {
|
|
254
|
-
const entryId = commandArgs[1];
|
|
255
|
-
const scopeArg = commandArgs[2];
|
|
256
|
-
if (!entryId) {
|
|
257
|
-
ctx.print('Usage: /plugin update <catalog-id> [project|user] --yes');
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
if (!parsed.yes) {
|
|
261
|
-
requireYesFlag(ctx, `update curated plugin ${entryId}`, '/plugin update <catalog-id> [project|user] --yes');
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
const scope = scopeArg === 'user' ? 'user' : 'project';
|
|
265
|
-
const result = updateInstalledEcosystemEntry('plugin', entryId, { ...ecosystemPaths, scope });
|
|
266
|
-
ctx.print(result.ok
|
|
267
|
-
? `Updated curated plugin ${entryId} in ${result.receipt.targetPath}`
|
|
268
|
-
: `Error: ${result.error}`);
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
271
|
-
if (sub === 'uninstall') {
|
|
272
|
-
const entryId = commandArgs[1];
|
|
273
|
-
const scopeArg = commandArgs[2];
|
|
274
|
-
if (!entryId) {
|
|
275
|
-
ctx.print('Usage: /plugin uninstall <catalog-id> [project|user] --yes');
|
|
276
|
-
return;
|
|
277
|
-
}
|
|
278
|
-
if (!parsed.yes) {
|
|
279
|
-
requireYesFlag(ctx, `uninstall curated plugin ${entryId}`, '/plugin uninstall <catalog-id> [project|user] --yes');
|
|
280
|
-
return;
|
|
281
|
-
}
|
|
282
|
-
const scope = scopeArg === 'user' ? 'user' : 'project';
|
|
283
|
-
const result = uninstallEcosystemCatalogEntry('plugin', entryId, { ...ecosystemPaths, scope });
|
|
284
|
-
ctx.print(result.ok
|
|
285
|
-
? `Uninstalled curated plugin ${entryId} from ${result.removedPath}`
|
|
286
|
-
: `Error: ${result.error}`);
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
if (sub === 'enable') {
|
|
290
|
-
const name = commandArgs[1];
|
|
291
|
-
if (!name) { ctx.print('Usage: /plugin enable <name> --yes'); return; }
|
|
292
|
-
if (!parsed.yes) {
|
|
293
|
-
requireYesFlag(ctx, `enable plugin ${name}`, '/plugin enable <name> --yes');
|
|
294
|
-
return;
|
|
295
|
-
}
|
|
296
|
-
const result = await pluginManager.enable(name);
|
|
297
|
-
ctx.print(result.ok ? `Plugin '${name}' enabled and activated.` : `Error: ${result.error}`);
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
if (sub === 'disable') {
|
|
301
|
-
const name = commandArgs[1];
|
|
302
|
-
if (!name) { ctx.print('Usage: /plugin disable <name> --yes'); return; }
|
|
303
|
-
if (!parsed.yes) {
|
|
304
|
-
requireYesFlag(ctx, `disable plugin ${name}`, '/plugin disable <name> --yes');
|
|
305
|
-
return;
|
|
306
|
-
}
|
|
307
|
-
const result = await pluginManager.disable(name);
|
|
308
|
-
ctx.print(result.ok ? `Plugin '${name}' disabled.` : `Error: ${result.error}`);
|
|
309
|
-
return;
|
|
310
|
-
}
|
|
311
|
-
if (sub === 'reload') {
|
|
312
|
-
if (!parsed.yes) {
|
|
313
|
-
requireYesFlag(ctx, 'reload plugins', '/plugin reload --yes');
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
ctx.print('Reloading plugins...');
|
|
317
|
-
const { reloaded, failed } = await pluginManager.reload();
|
|
318
|
-
ctx.print(`Done. ${reloaded} plugin(s) reloaded${failed > 0 ? `, ${failed} failed` : ''}.`);
|
|
319
|
-
return;
|
|
320
|
-
}
|
|
321
|
-
if (sub === 'trust') {
|
|
322
|
-
const name = commandArgs[1];
|
|
323
|
-
const rawTier = commandArgs[2];
|
|
324
|
-
if (!name || !rawTier) {
|
|
325
|
-
ctx.print('Usage: /plugin trust <name> <untrusted|limited|trusted> [note] --yes');
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
if (!parsed.yes) {
|
|
329
|
-
requireYesFlag(ctx, `set plugin ${name} trust tier`, '/plugin trust <name> <untrusted|limited|trusted> [note] --yes');
|
|
330
|
-
return;
|
|
331
|
-
}
|
|
332
|
-
if (rawTier !== 'untrusted' && rawTier !== 'limited' && rawTier !== 'trusted') {
|
|
333
|
-
ctx.print(`Error: Invalid trust tier '${rawTier}'. Must be: untrusted, limited, or trusted.`);
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
336
|
-
const tier = rawTier as 'untrusted' | 'limited' | 'trusted';
|
|
337
|
-
const note = commandArgs.slice(3).join(' ') || undefined;
|
|
338
|
-
if (tier === 'trusted') {
|
|
339
|
-
const sigResult = pluginManager.trustSigned(name);
|
|
340
|
-
if (sigResult.ok) {
|
|
341
|
-
ctx.print(`Plugin '${name}' elevated to 'trusted' via signed manifest${sigResult.fingerprint ? ` (fingerprint: ${sigResult.fingerprint})` : ''}.\nReload the plugin to apply updated capability grants.`);
|
|
342
|
-
return;
|
|
343
|
-
}
|
|
344
|
-
ctx.print(`Warning: Signature validation failed (${sigResult.error}).\nGranting 'trusted' tier by operator override. High-risk capabilities will be available on next reload.`);
|
|
345
|
-
}
|
|
346
|
-
const result = pluginManager.trust(name, tier, note);
|
|
347
|
-
ctx.print(result.ok
|
|
348
|
-
? `Plugin '${name}' trust tier set to '${tier}'.${tier === 'trusted' ? '\nReload the plugin to apply high-risk capability grants.' : ''}`
|
|
349
|
-
: `Error: ${result.error}`);
|
|
350
|
-
return;
|
|
351
|
-
}
|
|
352
|
-
if (sub === 'verify') {
|
|
353
|
-
const name = commandArgs[1];
|
|
354
|
-
if (!name) { ctx.print('Usage: /plugin verify <name>'); return; }
|
|
355
|
-
const result = pluginManager.verify(name);
|
|
356
|
-
if (!result.ok && result.reason?.includes('not found')) {
|
|
357
|
-
ctx.print(`Error: ${result.reason}`);
|
|
358
|
-
return;
|
|
359
|
-
}
|
|
360
|
-
ctx.print(result.valid
|
|
361
|
-
? `Plugin '${name}' manifest signature is VALID.${result.fingerprint ? `\nFingerprint: ${result.fingerprint}` : ''}`
|
|
362
|
-
: `Plugin '${name}' manifest signature is INVALID.\nReason: ${result.reason ?? 'Unknown'}`);
|
|
363
|
-
return;
|
|
364
|
-
}
|
|
365
|
-
if (sub === 'capabilities') {
|
|
366
|
-
const name = commandArgs[1];
|
|
367
|
-
if (!name) { ctx.print('Usage: /plugin capabilities <name>'); return; }
|
|
368
|
-
const info = pluginManager.capabilities(name);
|
|
369
|
-
if (!info) {
|
|
370
|
-
ctx.print(`Error: Plugin '${name}' not found.`);
|
|
371
|
-
return;
|
|
372
|
-
}
|
|
373
|
-
const lines: string[] = [`Plugin: ${name}`, `Trust tier: ${info.tier}`, '', `Requested capabilities (${info.requested.length}):`];
|
|
374
|
-
if (info.requested.length === 0) lines.push(' (none)');
|
|
375
|
-
else {
|
|
376
|
-
for (const cap of info.requested) {
|
|
377
|
-
const tag = info.blocked.includes(cap)
|
|
378
|
-
? '[BLOCKED - requires trusted tier]'
|
|
379
|
-
: info.highRisk.includes(cap) ? '[high-risk, granted]' : '[safe]';
|
|
380
|
-
lines.push(` ${cap.padEnd(32)} ${tag}`);
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
if (info.blocked.length > 0) {
|
|
384
|
-
lines.push('');
|
|
385
|
-
lines.push(`${info.blocked.length} high-risk capability/capabilities blocked by trust tier '${info.tier}'.`);
|
|
386
|
-
lines.push(`Use /plugin trust ${name} trusted --yes to escalate.`);
|
|
387
|
-
}
|
|
388
|
-
ctx.print(lines.join('\n'));
|
|
389
|
-
return;
|
|
390
|
-
}
|
|
391
|
-
if (sub === 'quarantine') {
|
|
392
|
-
const name = commandArgs[1];
|
|
393
|
-
const action = commandArgs[2] ?? 'add';
|
|
394
|
-
if (!name) {
|
|
395
|
-
ctx.print('Usage: /plugin quarantine <name> [add|lift] [reason] --yes');
|
|
396
|
-
return;
|
|
397
|
-
}
|
|
398
|
-
if (!parsed.yes) {
|
|
399
|
-
requireYesFlag(ctx, `${action === 'lift' ? 'lift quarantine for' : 'quarantine'} plugin ${name}`, '/plugin quarantine <name> [add|lift] [reason] --yes');
|
|
400
|
-
return;
|
|
401
|
-
}
|
|
402
|
-
if (action === 'lift') {
|
|
403
|
-
const result = pluginManager.liftQuarantine(name);
|
|
404
|
-
ctx.print(result.ok ? `Plugin '${name}' quarantine lifted. Reload to restore safe capabilities.` : `Error: ${result.error}`);
|
|
405
|
-
return;
|
|
406
|
-
}
|
|
407
|
-
const reason = (action === 'add' ? commandArgs.slice(3) : commandArgs.slice(2)).join(' ') || 'quarantined by operator';
|
|
408
|
-
const result = pluginManager.quarantine(name, reason);
|
|
409
|
-
ctx.print(result.ok
|
|
410
|
-
? `Plugin '${name}' quarantined.\nReason: ${reason}\nHigh-risk capabilities revoked. Reload to fully apply. Use /plugin quarantine <name> lift --yes to restore.`
|
|
411
|
-
: `Error: ${result.error}`);
|
|
412
|
-
return;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
ctx.print(
|
|
416
|
-
'Usage: /plugin <subcommand>\n'
|
|
417
|
-
+ ' list — show installed plugins and their status\n'
|
|
418
|
-
+ ' enable <name> --yes — enable a plugin\n'
|
|
419
|
-
+ ' disable <name> --yes — disable a plugin\n'
|
|
420
|
-
+ ' reload --yes — reload all enabled plugins\n'
|
|
421
|
-
+ ' trust <name> <tier> [note] --yes — set trust tier (untrusted|limited|trusted)\n'
|
|
422
|
-
+ ' verify <name> — inspect a plugin manifest signature\n'
|
|
423
|
-
+ ' capabilities <name> — show capability grants and blocks\n'
|
|
424
|
-
+ ' browse [query] — browse curated local-first plugin catalog entries\n'
|
|
425
|
-
+ ' installed — list curated catalog installs with provenance receipts\n'
|
|
426
|
-
+ ' catalog-review <id> — review source, provenance, and risk for a curated plugin\n'
|
|
427
|
-
+ ' publish-local <id> <path> <summary...> --yes — publish a local plugin directory into the curated catalog\n'
|
|
428
|
-
+ ' unpublish <id> --yes — remove a local curated plugin catalog entry\n'
|
|
429
|
-
+ ' install-hint <catalog-id> — show install guidance for a curated plugin entry\n'
|
|
430
|
-
+ ' install <catalog-id> [scope] --yes — install a local-path curated plugin into project|user scope\n'
|
|
431
|
-
+ ' uninstall <catalog-id> [scope] --yes — remove a curated plugin install receipt and target path\n'
|
|
432
|
-
+ ' quarantine <name> [reason] --yes — quarantine a plugin (revoke high-risk caps)\n'
|
|
433
|
-
+ ' quarantine <name> lift --yes — lift quarantine from a plugin'
|
|
434
|
-
);
|
|
435
|
-
},
|
|
436
|
-
});
|
|
437
|
-
}
|