@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.
Files changed (60) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/package.json +42 -1
  3. package/src/agent/skill-discovery.ts +119 -0
  4. package/src/input/commands/delegation-runtime.ts +0 -8
  5. package/src/input/commands/experience-runtime.ts +0 -177
  6. package/src/input/commands/guidance-runtime.ts +9 -77
  7. package/src/input/commands/local-runtime.ts +1 -57
  8. package/src/input/commands/local-setup-review.ts +1 -1
  9. package/src/input/commands/operator-runtime.ts +1 -145
  10. package/src/input/commands/platform-access-runtime.ts +2 -195
  11. package/src/input/commands/product-runtime.ts +0 -116
  12. package/src/input/commands/security-runtime.ts +88 -0
  13. package/src/input/commands/session-content.ts +0 -97
  14. package/src/input/commands/shell-core.ts +1 -22
  15. package/src/input/commands.ts +2 -43
  16. package/src/panels/builtin/operations.ts +3 -184
  17. package/src/panels/index.ts +0 -11
  18. package/src/version.ts +1 -1
  19. package/src/input/commands/branch-runtime.ts +0 -72
  20. package/src/input/commands/control-room-runtime.ts +0 -234
  21. package/src/input/commands/discovery-runtime.ts +0 -61
  22. package/src/input/commands/hooks-runtime.ts +0 -207
  23. package/src/input/commands/incident-runtime.ts +0 -106
  24. package/src/input/commands/integration-runtime.ts +0 -437
  25. package/src/input/commands/local-setup.ts +0 -288
  26. package/src/input/commands/managed-runtime.ts +0 -240
  27. package/src/input/commands/marketplace-runtime.ts +0 -305
  28. package/src/input/commands/memory-product-runtime.ts +0 -148
  29. package/src/input/commands/operator-panel-runtime.ts +0 -146
  30. package/src/input/commands/platform-services-runtime.ts +0 -271
  31. package/src/input/commands/profile-sync-runtime.ts +0 -110
  32. package/src/input/commands/provider.ts +0 -363
  33. package/src/input/commands/remote-runtime-pool.ts +0 -89
  34. package/src/input/commands/remote-runtime-setup.ts +0 -226
  35. package/src/input/commands/remote-runtime.ts +0 -432
  36. package/src/input/commands/replay-runtime.ts +0 -25
  37. package/src/input/commands/services-runtime.ts +0 -220
  38. package/src/input/commands/settings-sync-runtime.ts +0 -197
  39. package/src/input/commands/share-runtime.ts +0 -127
  40. package/src/input/commands/skills-runtime.ts +0 -226
  41. package/src/input/commands/teleport-runtime.ts +0 -68
  42. package/src/panels/cockpit-panel.ts +0 -183
  43. package/src/panels/communication-panel.ts +0 -153
  44. package/src/panels/control-plane-panel.ts +0 -211
  45. package/src/panels/forensics-panel.ts +0 -364
  46. package/src/panels/hooks-panel.ts +0 -239
  47. package/src/panels/incident-review-panel.ts +0 -197
  48. package/src/panels/marketplace-panel.ts +0 -212
  49. package/src/panels/ops-control-panel.ts +0 -150
  50. package/src/panels/ops-strategy-panel.ts +0 -235
  51. package/src/panels/orchestration-panel.ts +0 -272
  52. package/src/panels/plugins-panel.ts +0 -178
  53. package/src/panels/remote-panel.ts +0 -449
  54. package/src/panels/routes-panel.ts +0 -178
  55. package/src/panels/services-panel.ts +0 -231
  56. package/src/panels/settings-sync-panel.ts +0 -120
  57. package/src/panels/skills-panel.ts +0 -431
  58. package/src/panels/watchers-panel.ts +0 -193
  59. package/src/verification/live-verifier.ts +0 -588
  60. package/src/verification/verification-ledger.ts +0 -239
@@ -1,25 +1,11 @@
1
1
  import type { CommandRegistry } from '../command-registry.ts';
2
2
  import type { ProfileData } from '@pellux/goodvibes-sdk/platform/profiles';
3
- import type { ReplaySnapshotInput } from '@/runtime/index.ts';
4
3
  import { logger } from '@pellux/goodvibes-sdk/platform/utils';
5
- import { registerOperatorPanelCommand } from './operator-panel-runtime.ts';
6
- import { requireProfileManager, requireReplayEngine } from './runtime-services.ts';
4
+ import { requireProfileManager } from './runtime-services.ts';
7
5
  import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
8
6
  import { requireYesFlag, stripYesFlag } from './confirmation.ts';
9
7
 
10
- function printOpsMutationBlocked(print: (text: string) => void, target: string): void {
11
- print([
12
- `[Ops] ${target} mutation is blocked in GoodVibes Agent.`,
13
- ' policy: Agent does not control copied local task/agent lifecycle from the operator surface.',
14
- ' normal work: continue in the main conversation.',
15
- ' build/fix/review: use /delegate <task> for explicit GoodVibes TUI handoff.',
16
- ' result: no local task or agent state was changed.',
17
- ].join('\n'));
18
- }
19
-
20
8
  export function registerOperatorRuntimeCommands(registry: CommandRegistry): void {
21
- registerOperatorPanelCommand(registry);
22
-
23
9
  registry.register({
24
10
  name: 'settings',
25
11
  aliases: ['cfg-ui'],
@@ -249,134 +235,4 @@ export function registerOperatorRuntimeCommands(registry: CommandRegistry): void
249
235
  );
250
236
  },
251
237
  });
252
-
253
- registry.register({
254
- name: 'ops',
255
- description: 'Operator runtime status: view Agent posture without local task/agent lifecycle mutations',
256
- usage: '[view]',
257
- argsHint: '[view]',
258
- handler(args, ctx) {
259
- const sub = args[0];
260
-
261
- if (sub === 'view' || sub === undefined) {
262
- if (ctx.openOpsPanel) ctx.openOpsPanel();
263
- else ctx.print('Operator runtime status panel is not available. Enable the operator-control-plane feature flag.');
264
- return;
265
- }
266
-
267
- if (sub === 'task') {
268
- printOpsMutationBlocked(ctx.print, 'Task');
269
- return;
270
- }
271
-
272
- if (sub === 'agent') {
273
- printOpsMutationBlocked(ctx.print, 'Agent');
274
- return;
275
- }
276
-
277
- ctx.print(
278
- 'Usage: /ops <subcommand>\n'
279
- + ' /ops view — open the Ops Control panel (Ctrl+O)\n'
280
- + ' task/agent lifecycle commands are blocked in Agent; use /delegate for explicit build handoff'
281
- );
282
- },
283
- });
284
-
285
- registry.register({
286
- name: 'forensics',
287
- aliases: ['foren'],
288
- description: 'Failure Forensics: view, inspect, and export auto-classified failure reports',
289
- usage: '[latest | show <id> | export <id>]',
290
- argsHint: '[latest|show|export]',
291
- handler(args, ctx) {
292
- const sub = args[0];
293
- if (sub === undefined || sub === 'view') {
294
- if (ctx.openForensicsPanel) ctx.openForensicsPanel();
295
- else ctx.print('Forensics panel is not available.');
296
- return;
297
- }
298
- if (sub === 'latest') {
299
- if (!ctx.extensions.forensicsRegistry) {
300
- ctx.print('[Forensics] Registry not active.');
301
- return;
302
- }
303
- const report = ctx.extensions.forensicsRegistry.latest();
304
- if (!report) {
305
- ctx.print('[Forensics] No failure reports recorded this session.');
306
- return;
307
- }
308
- const lines: string[] = [
309
- `[Forensics] Latest failure report (id: ${report.id})`,
310
- ` Time: ${new Date(report.generatedAt).toISOString()}`,
311
- ` Classification: ${report.classification}`,
312
- ` Summary: ${report.summary}`,
313
- ];
314
- if (report.errorMessage) lines.push(` Error: ${report.errorMessage}`);
315
- if (report.stopReason) lines.push(` Stop reason: ${report.stopReason}`);
316
- if (report.taskId) lines.push(` Task ID: ${report.taskId}`);
317
- if (report.turnId) lines.push(` Turn ID: ${report.turnId}`);
318
- if (report.causalChain.length > 0) {
319
- lines.push(' Causal chain:');
320
- for (const entry of report.causalChain) {
321
- const marker = entry.isRootCause ? ' ● ' : ' · ';
322
- lines.push(` ${marker}${entry.description}`);
323
- }
324
- }
325
- if (report.jumpLinks.length > 0) {
326
- lines.push(' Jump links:');
327
- for (const link of report.jumpLinks) {
328
- lines.push(` [${link.kind}] ${link.label} → ${link.target}${link.args ? ` (${link.args})` : ''}`);
329
- }
330
- }
331
- lines.push(` Use "/forensics show ${report.id}" for full JSON.`);
332
- ctx.print(lines.join('\n'));
333
- return;
334
- }
335
- if (sub === 'show') {
336
- const id = args[1];
337
- if (!id) {
338
- ctx.print('Usage: /forensics show <id>');
339
- return;
340
- }
341
- if (!ctx.extensions.forensicsRegistry) {
342
- ctx.print('[Forensics] Registry not active.');
343
- return;
344
- }
345
- const json = ctx.extensions.forensicsRegistry.exportAsJson(id);
346
- if (!json) {
347
- ctx.print(`[Forensics] No report found with id "${id}". Use /forensics latest to see the most recent.`);
348
- return;
349
- }
350
- ctx.print(json);
351
- return;
352
- }
353
- if (sub === 'export') {
354
- const id = args[1];
355
- if (!id) {
356
- ctx.print('Usage: /forensics export <id>');
357
- return;
358
- }
359
- if (!ctx.extensions.forensicsRegistry) {
360
- ctx.print('[Forensics] Registry not active.');
361
- return;
362
- }
363
- const json = ctx.extensions.forensicsRegistry.exportBundleAsJson(id, {
364
- replaySnapshot: requireReplayEngine(ctx).getSnapshot() as ReplaySnapshotInput,
365
- });
366
- if (!json) {
367
- ctx.print(`[Forensics] No report found with id "${id}".`);
368
- return;
369
- }
370
- ctx.print(`[Forensics] Incident bundle ${id}:\n${json}`);
371
- return;
372
- }
373
- ctx.print(
374
- 'Usage: /forensics <subcommand>\n'
375
- + ' /forensics — open the Forensics panel\n'
376
- + ' /forensics latest — print the most recent failure report summary\n'
377
- + ' /forensics show <id> — show full JSON for a specific report\n'
378
- + ' /forensics export <id> — export incident bundle JSON to the conversation'
379
- );
380
- },
381
- });
382
238
  }
@@ -1,33 +1,12 @@
1
1
  import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
- import { dirname, resolve } from 'node:path';
2
+ import { dirname } from 'node:path';
3
3
  import type { CommandRegistry } from '../command-registry.ts';
4
- import { VERSION } from '../../version.ts';
5
4
  import { listBuiltinSubscriptionProviders } from '@pellux/goodvibes-sdk/platform/config';
6
5
  import { handleLocalAuthCommand } from './local-auth-runtime.ts';
7
6
  import { buildAuthInspectionSnapshot, inspectProviderAuth } from '@/runtime/index.ts';
8
- import { requireProfileManager, requireSecretsManager, requireServiceRegistry, requireShellPaths, requireSubscriptionManager } from './runtime-services.ts';
7
+ import { requireSecretsManager, requireServiceRegistry, requireShellPaths, requireSubscriptionManager } from './runtime-services.ts';
9
8
  import { requireYesFlag, stripYesFlag } from './confirmation.ts';
10
9
 
11
- interface InstallBundle {
12
- readonly version: 1;
13
- readonly exportedAt: number;
14
- readonly appVersion: string;
15
- readonly workingDirectory: string;
16
- readonly homeDirectory: string;
17
- readonly profileCount: number;
18
- readonly secretKeyCount: number;
19
- readonly setupLinks: readonly string[];
20
- }
21
-
22
- interface UpdateBundle {
23
- readonly version: 1;
24
- readonly exportedAt: number;
25
- readonly appVersion: string;
26
- readonly updateChannel: 'stable' | 'preview';
27
- readonly subscriptionProviders: readonly string[];
28
- readonly notes: readonly string[];
29
- }
30
-
31
10
  interface AuthReviewBundle {
32
11
  readonly version: 1;
33
12
  readonly exportedAt: number;
@@ -51,32 +30,6 @@ function authServiceSecretPrefix(target: AuthServiceLoginTarget): string {
51
30
  return target === 'runtime' ? 'RUNTIME' : 'LISTENER';
52
31
  }
53
32
 
54
- function buildSetupLink(surface: string, target?: string): string {
55
- const params = target ? `?target=${encodeURIComponent(target)}` : '';
56
- return `goodvibes://open/${surface}${params}`;
57
- }
58
-
59
- function inspectInstallBundle(bundle: InstallBundle): string {
60
- return [
61
- 'Install Bundle Review',
62
- ` appVersion: ${bundle.appVersion}`,
63
- ` workingDirectory: ${bundle.workingDirectory}`,
64
- ` profileCount: ${bundle.profileCount}`,
65
- ` secretKeys: ${bundle.secretKeyCount}`,
66
- ` setupLinks: ${bundle.setupLinks.length}`,
67
- ].join('\n');
68
- }
69
-
70
- function inspectUpdateBundle(bundle: UpdateBundle): string {
71
- return [
72
- 'Update Bundle Review',
73
- ` appVersion: ${bundle.appVersion}`,
74
- ` updateChannel: ${bundle.updateChannel}`,
75
- ` subscriptionProviders: ${bundle.subscriptionProviders.length}`,
76
- ` notes: ${bundle.notes.length}`,
77
- ].join('\n');
78
- }
79
-
80
33
  function inspectAuthBundle(bundle: AuthReviewBundle): string {
81
34
  return [
82
35
  'Auth Review Bundle',
@@ -157,152 +110,6 @@ export function registerPlatformAccessRuntimeCommands(registry: CommandRegistry)
157
110
  },
158
111
  });
159
112
 
160
- registry.register({
161
- name: 'install',
162
- description: 'Review install posture and export portable install bundles',
163
- usage: '[review|bundle export <path> --yes|bundle inspect <path>]',
164
- async handler(args, ctx) {
165
- const parsed = stripYesFlag(args);
166
- const commandArgs = [...parsed.rest];
167
- const shellPaths = requireShellPaths(ctx);
168
- const sub = commandArgs[0] ?? 'review';
169
- if (sub === 'review') {
170
- const profiles = requireProfileManager(ctx).list();
171
- const secretKeys = await requireSecretsManager(ctx).list();
172
- ctx.print([
173
- 'Install Review',
174
- ` version: ${VERSION}`,
175
- ` profiles: ${profiles.length}`,
176
- ` secret keys: ${secretKeys.length}`,
177
- ` setup links: 4`,
178
- ].join('\n'));
179
- return;
180
- }
181
- if (sub === 'bundle') {
182
- const mode = commandArgs[1];
183
- const pathArg = commandArgs[2];
184
- if ((mode === 'export' || mode === 'inspect') && !pathArg) {
185
- ctx.print(`Usage: /install bundle ${mode} <path>${mode === 'export' ? ' --yes' : ''}`);
186
- return;
187
- }
188
- const targetPath = shellPaths.resolveWorkspacePath(pathArg!);
189
- if (mode === 'export') {
190
- if (!parsed.yes) {
191
- requireYesFlag(ctx, `export install bundle to ${pathArg}`, '/install bundle export <path> --yes');
192
- return;
193
- }
194
- const profiles = requireProfileManager(ctx).list();
195
- const secretKeys = await requireSecretsManager(ctx).list();
196
- const bundle: InstallBundle = {
197
- version: 1,
198
- exportedAt: Date.now(),
199
- appVersion: VERSION,
200
- workingDirectory: shellPaths.workingDirectory,
201
- homeDirectory: shellPaths.homeDirectory,
202
- profileCount: profiles.length,
203
- secretKeyCount: secretKeys.length,
204
- setupLinks: [
205
- buildSetupLink('cockpit'),
206
- buildSetupLink('security'),
207
- buildSetupLink('remote'),
208
- buildSetupLink('knowledge'),
209
- ],
210
- };
211
- mkdirSync(dirname(targetPath), { recursive: true });
212
- writeFileSync(targetPath, JSON.stringify(bundle, null, 2) + '\n', 'utf-8');
213
- ctx.print(`Install bundle exported to ${targetPath}`);
214
- return;
215
- }
216
- if (mode === 'inspect') {
217
- const bundle = JSON.parse(readFileSync(targetPath, 'utf-8')) as InstallBundle;
218
- ctx.print(inspectInstallBundle(bundle));
219
- return;
220
- }
221
- }
222
- ctx.print('Usage: /install [review|bundle export <path> --yes|bundle inspect <path>]');
223
- },
224
- });
225
-
226
- registry.register({
227
- name: 'update',
228
- aliases: ['upgrade'],
229
- description: 'Review update posture, choose release channel guidance, and package portable update bundles',
230
- usage: '[review|channel <stable|preview> --yes|bundle export <path> --yes|bundle inspect <path>]',
231
- handler(args, ctx) {
232
- const parsed = stripYesFlag(args);
233
- const commandArgs = [...parsed.rest];
234
- const shellPaths = requireShellPaths(ctx);
235
- const sub = commandArgs[0] ?? 'review';
236
- const subscriptions = requireSubscriptionManager(ctx);
237
- const serviceRegistry = requireServiceRegistry(ctx);
238
- const secretsManager = requireSecretsManager(ctx);
239
- const builtinProviders = listBuiltinSubscriptionProviders().map((entry) => entry.provider);
240
- const activeSubscriptions = subscriptions.list().map((entry) => entry.provider);
241
- if (sub === 'review') {
242
- const channel = ctx.platform.configManager.get('release.channel');
243
- ctx.print([
244
- 'Update Review',
245
- ` version: ${VERSION}`,
246
- ` channel: ${channel}`,
247
- ` built-in subscription providers: ${builtinProviders.length}${builtinProviders.length > 0 ? ` (${builtinProviders.join(', ')})` : ''}`,
248
- ` active subscriptions: ${activeSubscriptions.length}${activeSubscriptions.length > 0 ? ` (${activeSubscriptions.join(', ')})` : ''}`,
249
- ' use /update channel <stable|preview> --yes to change release posture',
250
- ].join('\n'));
251
- return;
252
- }
253
- if (sub === 'channel') {
254
- const channel = commandArgs[1];
255
- if (channel !== 'stable' && channel !== 'preview') {
256
- ctx.print('Usage: /update channel <stable|preview> --yes');
257
- return;
258
- }
259
- if (!parsed.yes) {
260
- requireYesFlag(ctx, `set update channel to ${channel}`, '/update channel <stable|preview> --yes');
261
- return;
262
- }
263
- ctx.platform.configManager.setDynamic('release.channel', channel);
264
- ctx.print(`Update channel set to ${channel}.`);
265
- return;
266
- }
267
- if (sub === 'bundle') {
268
- const mode = commandArgs[1];
269
- const pathArg = commandArgs[2];
270
- if ((mode === 'export' || mode === 'inspect') && !pathArg) {
271
- ctx.print(`Usage: /update bundle ${mode} <path>${mode === 'export' ? ' --yes' : ''}`);
272
- return;
273
- }
274
- const targetPath = shellPaths.resolveWorkspacePath(pathArg!);
275
- if (mode === 'export') {
276
- if (!parsed.yes) {
277
- requireYesFlag(ctx, `export update bundle to ${pathArg}`, '/update bundle export <path> --yes');
278
- return;
279
- }
280
- const bundle: UpdateBundle = {
281
- version: 1,
282
- exportedAt: Date.now(),
283
- appVersion: VERSION,
284
- updateChannel: ctx.platform.configManager.get('release.channel') as 'stable' | 'preview',
285
- subscriptionProviders: [...new Set([...builtinProviders, ...activeSubscriptions])],
286
- notes: [
287
- 'Preview channel is recommended only when operator review is enabled.',
288
- 'OAuth-backed provider subscriptions survive channel changes and continue to apply to supported provider surfaces.',
289
- ],
290
- };
291
- mkdirSync(dirname(targetPath), { recursive: true });
292
- writeFileSync(targetPath, JSON.stringify(bundle, null, 2) + '\n', 'utf-8');
293
- ctx.print(`Update bundle exported to ${targetPath}`);
294
- return;
295
- }
296
- if (mode === 'inspect') {
297
- const bundle = JSON.parse(readFileSync(targetPath, 'utf-8')) as UpdateBundle;
298
- ctx.print(inspectUpdateBundle(bundle));
299
- return;
300
- }
301
- }
302
- ctx.print('Usage: /update [review|channel <stable|preview> --yes|bundle export <path> --yes|bundle inspect <path>]');
303
- },
304
- });
305
-
306
113
  registry.register({
307
114
  name: 'auth',
308
115
  description: 'Review auth posture and exchange session login tokens with local services',
@@ -129,120 +129,4 @@ export function registerProductRuntimeCommands(registry: CommandRegistry): void
129
129
  ctx.print('Usage: /trust [review|bundle export <path> --yes|bundle inspect <path>]');
130
130
  },
131
131
  });
132
- registry.register({
133
- name: 'bridge',
134
- description: 'Review self-hosted bridge and remote worker flows',
135
- usage: '[status|pools|worker <id>|review <artifactId>|export <artifactId> [path] --yes|import <path> --yes]',
136
- async handler(args, ctx) {
137
- const parsed = stripYesFlag(args);
138
- const commandArgs = [...parsed.rest];
139
- const shellPaths = requireShellPaths(ctx);
140
- if (!ctx.ops.remoteRuntime) {
141
- ctx.print('Remote worker registry is not available in this runtime.');
142
- return;
143
- }
144
- const remoteRegistry = ctx.ops.remoteRuntime;
145
- const sub = commandArgs[0] ?? 'status';
146
- if (sub === 'status') {
147
- const remote = requireReadModels(ctx).remote.getSnapshot();
148
- ctx.print([
149
- 'Bridge Status',
150
- ` remote pools: ${remote.pools.length}`,
151
- ` worker contracts: ${remote.contracts.length}`,
152
- ` review artifacts: ${remote.artifacts.length}`,
153
- ].join('\n'));
154
- return;
155
- }
156
- if (sub === 'pools') {
157
- const pools = remoteRegistry.listPools();
158
- ctx.print(pools.length > 0
159
- ? ['Bridge Pools', ...pools.map((pool) => ` ${pool.id} workers=${pool.runnerIds.length} trust=${pool.trustClass}`)].join('\n')
160
- : 'Bridge Pools\n No worker pools registered yet.');
161
- return;
162
- }
163
- if (sub === 'assign') {
164
- const poolId = commandArgs[1];
165
- const runnerId = commandArgs[2];
166
- if (!poolId || !runnerId) {
167
- ctx.print('Usage: /bridge assign <pool> <worker> --yes');
168
- return;
169
- }
170
- ctx.print([
171
- 'Bridge worker assignment is read-only in GoodVibes Agent.',
172
- ` requested: /bridge assign ${poolId} ${runnerId}`,
173
- ' policy: Agent reviews remote bridge state but does not mutate worker topology',
174
- ' next: inspect /bridge pools or delegate explicit build/fix/review work to GoodVibes TUI',
175
- ].join('\n'));
176
- return;
177
- }
178
- if (sub === 'runner' || sub === 'worker') {
179
- const runnerId = commandArgs[1];
180
- if (!runnerId) {
181
- ctx.print('Usage: /bridge worker <id>');
182
- return;
183
- }
184
- const contract = remoteRegistry.getContract(runnerId);
185
- if (!contract) {
186
- ctx.print(`Unknown worker contract: ${runnerId}`);
187
- return;
188
- }
189
- ctx.print([
190
- `Bridge Worker ${runnerId}`,
191
- ` template: ${contract.template}`,
192
- ` trustClass: ${contract.trustClass}`,
193
- ` transport: ${contract.sourceTransport}/${contract.transport.state}`,
194
- ` tools: ${contract.capabilityCeiling.allowedTools.join(', ') || '(none)'}`,
195
- ` pool: ${contract.poolId ?? '(unassigned)'}`,
196
- ].join('\n'));
197
- return;
198
- }
199
- if (sub === 'review') {
200
- const artifactId = commandArgs[1];
201
- if (!artifactId) {
202
- ctx.print('Usage: /bridge review <artifactId>');
203
- return;
204
- }
205
- const summary = remoteRegistry.buildReviewSummary(artifactId);
206
- ctx.print(summary ?? `Unknown remote artifact: ${artifactId}`);
207
- return;
208
- }
209
- if (sub === 'export') {
210
- const artifactId = commandArgs[1];
211
- if (!artifactId) {
212
- ctx.print('Usage: /bridge export <artifactId> [path] --yes');
213
- return;
214
- }
215
- if (!parsed.yes) {
216
- requireYesFlag(ctx, `export bridge artifact ${artifactId}`, '/bridge export <artifactId> [path] --yes');
217
- return;
218
- }
219
- const exported = await remoteRegistry.exportArtifact(
220
- artifactId,
221
- commandArgs[2] ? shellPaths.resolveWorkspacePath(commandArgs[2]) : undefined,
222
- );
223
- if (!exported) {
224
- ctx.print(`Unknown remote artifact: ${artifactId}`);
225
- return;
226
- }
227
- ctx.print(`Exported remote bridge artifact to ${exported.path}`);
228
- return;
229
- }
230
- if (sub === 'import') {
231
- const pathArg = commandArgs[1];
232
- if (!pathArg) {
233
- ctx.print('Usage: /bridge import <path> --yes');
234
- return;
235
- }
236
- if (!parsed.yes) {
237
- requireYesFlag(ctx, `import bridge artifact from ${pathArg}`, '/bridge import <path> --yes');
238
- return;
239
- }
240
- const artifact = await remoteRegistry.importArtifact(shellPaths.resolveWorkspacePath(pathArg));
241
- ctx.print(`Imported remote bridge artifact ${artifact.id} for worker ${artifact.runnerId}.`);
242
- return;
243
- }
244
- ctx.print('Usage: /bridge [status|pools|worker <id>|review <artifactId>|export <artifactId> [path] --yes|import <path> --yes]');
245
- },
246
- });
247
-
248
132
  }
@@ -0,0 +1,88 @@
1
+ import type { CommandRegistry } from '../command-registry.ts';
2
+ import { buildMcpAttackPathReview } from '@/runtime/index.ts';
3
+ import { listBuiltinSubscriptionProviders } from '@pellux/goodvibes-sdk/platform/config';
4
+ import { requireReadModels, requireSubscriptionManager, requireTokenAuditor } from './runtime-services.ts';
5
+
6
+ export function registerSecurityRuntimeCommands(registry: CommandRegistry): void {
7
+ registry.register({
8
+ name: 'security',
9
+ aliases: [],
10
+ description: 'Inspect security posture, attack paths, and review state',
11
+ usage: '[review | attack-paths | tokens]',
12
+ handler(args, ctx) {
13
+ if (args.length === 0) {
14
+ if (ctx.openSecurityPanel) {
15
+ ctx.openSecurityPanel();
16
+ return;
17
+ }
18
+ ctx.print('Security panel is not available in this runtime.');
19
+ return;
20
+ }
21
+
22
+ const subcommand = args[0]?.toLowerCase() ?? 'review';
23
+ const audit = requireTokenAuditor(ctx).auditAll(Date.now());
24
+ const securitySnapshot = requireReadModels(ctx).security.getSnapshot();
25
+ const policySnapshot = ctx.extensions.policyRuntimeState?.getSnapshot();
26
+ if (!policySnapshot) {
27
+ ctx.print('Policy runtime state is not available in this runtime.');
28
+ return;
29
+ }
30
+ const attackPaths = buildMcpAttackPathReview({
31
+ servers: securitySnapshot.mcpServers,
32
+ recentDecisions: securitySnapshot.recentMcpDecisions,
33
+ });
34
+
35
+ if (subcommand === 'tokens') {
36
+ if (audit.results.length === 0) {
37
+ ctx.print('No registered API tokens are currently under audit.');
38
+ return;
39
+ }
40
+ ctx.print([
41
+ `Token Audit (${audit.results.length})`,
42
+ ...audit.results.map((result) => (
43
+ ` ${result.label} policy=${result.scope.policyId} scope=${result.scope.outcome} rotation=${result.rotation.outcome} blocked=${result.blocked ? 'yes' : 'no'}`
44
+ )),
45
+ ].join('\n'));
46
+ return;
47
+ }
48
+
49
+ if (subcommand === 'attack-paths') {
50
+ if (attackPaths.findings.length === 0) {
51
+ ctx.print('No MCP attack-path findings are currently active.');
52
+ return;
53
+ }
54
+ ctx.print([
55
+ 'MCP Attack-Path Review',
56
+ ` summary: ${attackPaths.summary}`,
57
+ ...attackPaths.findings.slice(0, 12).map((finding) => (
58
+ ` ${finding.severity.toUpperCase()} ${finding.serverName} ${finding.route}\n ${finding.reason}`
59
+ )),
60
+ ].join('\n'));
61
+ return;
62
+ }
63
+
64
+ const plugins = ctx.extensions.pluginManager?.list() ?? [];
65
+ const subscriptions = requireSubscriptionManager(ctx);
66
+ const builtinProviders = listBuiltinSubscriptionProviders();
67
+ ctx.print([
68
+ 'Security Review',
69
+ ` tokens: ${audit.results.length}`,
70
+ ` blocked tokens: ${audit.blocked.length}`,
71
+ ` scope violations: ${audit.scopeViolations.length}`,
72
+ ` rotation overdue: ${audit.rotationOverdue.length}`,
73
+ ` rotation warnings: ${audit.rotationWarnings.length}`,
74
+ ` built-in subscription providers: ${builtinProviders.length}`,
75
+ ` active subscriptions: ${subscriptions.list().length}`,
76
+ ` pending subscriptions: ${subscriptions.listPending().length}`,
77
+ ` policy lint findings: ${policySnapshot.lintFindings.length}`,
78
+ ` policy preflight: ${policySnapshot.lastPreflightReview?.status ?? 'n/a'}`,
79
+ ` mcp servers: ${securitySnapshot.mcpServers.length}`,
80
+ ` mcp quarantined: ${securitySnapshot.mcpServers.filter((server) => server.schemaFreshness === 'quarantined').length}`,
81
+ ` mcp elevated: ${securitySnapshot.mcpServers.filter((server) => server.trustMode === 'allow-all').length}`,
82
+ ` mcp attack-path findings: ${attackPaths.findings.length}`,
83
+ ` quarantined plugins: ${plugins.filter((plugin) => plugin.quarantined).length}`,
84
+ ` untrusted plugins: ${plugins.filter((plugin) => plugin.trustTier === 'untrusted').length}`,
85
+ ].join('\n'));
86
+ },
87
+ });
88
+ }