@pellux/goodvibes-agent 0.1.70 → 0.1.72
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/README.md +3 -3
- package/docs/README.md +2 -2
- package/docs/getting-started.md +1 -1
- package/docs/runtime-connection.md +37 -0
- package/package.json +43 -2
- package/src/agent/skill-discovery.ts +119 -0
- package/src/cli/config-overrides.ts +1 -5
- package/src/cli/entrypoint.ts +0 -6
- package/src/cli/help.ts +0 -43
- package/src/cli/index.ts +0 -2
- package/src/cli/management-commands.ts +1 -109
- package/src/cli/management.ts +1 -32
- package/src/cli/package-verification.ts +12 -4
- package/src/cli/parser.ts +0 -16
- package/src/cli/status.ts +1 -1
- package/src/cli/types.ts +0 -8
- 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 +0 -69
- 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 +0 -13
- package/src/input/commands.ts +2 -95
- package/src/panels/builtin/operations.ts +3 -184
- package/src/panels/confirm-state.ts +1 -1
- package/src/panels/index.ts +0 -11
- package/src/version.ts +1 -1
- package/docs/deployment-and-services.md +0 -52
- package/src/cli/service-command.ts +0 -26
- package/src/cli/surface-command.ts +0 -247
- 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,33 +1,12 @@
|
|
|
1
1
|
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import { dirname
|
|
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 {
|
|
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
|
+
}
|
|
@@ -4,7 +4,6 @@ import { writeFile } from 'node:fs/promises';
|
|
|
4
4
|
import type { CommandRegistry } from '../command-registry.ts';
|
|
5
5
|
import type { SelectionItem } from '../selection-modal.ts';
|
|
6
6
|
import { exportToMarkdown } from '@pellux/goodvibes-sdk/platform/export';
|
|
7
|
-
import { TemplateManager, parseTemplateArgs } from '@pellux/goodvibes-sdk/platform/templates';
|
|
8
7
|
import { requireSessionManager, requireSessionMemoryStore, requireShellPaths } from './runtime-services.ts';
|
|
9
8
|
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
10
9
|
import { requireYesFlag, stripYesFlag } from './confirmation.ts';
|
|
@@ -260,102 +259,6 @@ export function registerSessionContentCommands(registry: CommandRegistry): void
|
|
|
260
259
|
},
|
|
261
260
|
});
|
|
262
261
|
|
|
263
|
-
registry.register({
|
|
264
|
-
name: 'template',
|
|
265
|
-
aliases: ['tmpl'],
|
|
266
|
-
description: 'Manage and use prompt templates',
|
|
267
|
-
usage: 'save <name> --yes | use <name> [args] | list | edit <name> | delete <name> --yes',
|
|
268
|
-
argsHint: '<save|use|list|edit|delete> [name]',
|
|
269
|
-
handler(args, ctx) {
|
|
270
|
-
const shellPaths = requireShellPaths(ctx);
|
|
271
|
-
const templateManager = new TemplateManager({
|
|
272
|
-
projectRoot: shellPaths.workingDirectory,
|
|
273
|
-
homeDirectory: shellPaths.homeDirectory,
|
|
274
|
-
});
|
|
275
|
-
const parsed = stripYesFlag(args);
|
|
276
|
-
const sub = parsed.rest[0];
|
|
277
|
-
const rest = parsed.rest.slice(1);
|
|
278
|
-
if (!sub || sub === 'list') {
|
|
279
|
-
const templates = templateManager.list();
|
|
280
|
-
if (ctx.openSelection) {
|
|
281
|
-
const actions = new Map([['e', 'edit' as const]]);
|
|
282
|
-
const items: SelectionItem[] = templates.length === 0
|
|
283
|
-
? [{ id: '_empty', label: 'No templates saved', detail: 'Use /template save <name> --yes' }]
|
|
284
|
-
: templates.map(t => ({ id: t.name, label: t.name, detail: t.preview, category: t.scope === 'project' ? 'project' : 'global', actions: '[e] edit' }));
|
|
285
|
-
ctx.openSelection('Templates', items, { allowSearch: true, customActions: actions }, (result) => {
|
|
286
|
-
if (!result) return;
|
|
287
|
-
const content = templateManager.load(result.item.id);
|
|
288
|
-
if (content !== null) {
|
|
289
|
-
if (result.action === 'edit') ctx.print(`Template: ${result.item.id}\n\n${content}`);
|
|
290
|
-
else ctx.submitInput?.(content);
|
|
291
|
-
} else {
|
|
292
|
-
ctx.print(`Template not found: ${result.item.id}`);
|
|
293
|
-
}
|
|
294
|
-
});
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
|
-
ctx.print(['Templates:', '', ...templates.map(t => ` ${t.scope === 'project' ? '[project]' : '[global] '} ${t.name.padEnd(28)} ${t.preview}`)].join('\n'));
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
if (sub === 'save') {
|
|
301
|
-
const name = rest[0];
|
|
302
|
-
if (!name) {
|
|
303
|
-
ctx.print('Usage: /template save <name> --yes');
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
306
|
-
if (!parsed.yes) {
|
|
307
|
-
requireYesFlag(ctx, `save prompt template ${name}`, '/template save <name> --yes');
|
|
308
|
-
return;
|
|
309
|
-
}
|
|
310
|
-
try {
|
|
311
|
-
templateManager.save(name, ctx.session.conversationManager.getLastUserMessage() || '# Template\n\nReplace this with your template content.\n');
|
|
312
|
-
ctx.print(`Template saved: ${name}`);
|
|
313
|
-
} catch (e) {
|
|
314
|
-
ctx.print(`Failed to save template: ${summarizeError(e)}`);
|
|
315
|
-
}
|
|
316
|
-
return;
|
|
317
|
-
}
|
|
318
|
-
if (sub === 'use') {
|
|
319
|
-
const name = rest[0];
|
|
320
|
-
if (!name) {
|
|
321
|
-
ctx.print('Usage: /template use <name> [args...]');
|
|
322
|
-
return;
|
|
323
|
-
}
|
|
324
|
-
const templateContent = templateManager.load(name);
|
|
325
|
-
if (templateContent === null) {
|
|
326
|
-
ctx.print(`Template not found: ${name}\nRun /template list to see available templates.`);
|
|
327
|
-
return;
|
|
328
|
-
}
|
|
329
|
-
ctx.submitInput?.(templateManager.expand(templateContent, parseTemplateArgs(rest.slice(1))));
|
|
330
|
-
return;
|
|
331
|
-
}
|
|
332
|
-
if (sub === 'edit') {
|
|
333
|
-
const name = rest[0];
|
|
334
|
-
if (!name) {
|
|
335
|
-
ctx.print('Usage: /template edit <name>');
|
|
336
|
-
return;
|
|
337
|
-
}
|
|
338
|
-
const content = templateManager.load(name);
|
|
339
|
-
ctx.print(content === null ? `Template not found: ${name}` : `Template: ${name}\n\n${content}`);
|
|
340
|
-
return;
|
|
341
|
-
}
|
|
342
|
-
if (sub === 'delete') {
|
|
343
|
-
const name = rest[0];
|
|
344
|
-
if (!name) {
|
|
345
|
-
ctx.print('Usage: /template delete <name> --yes');
|
|
346
|
-
return;
|
|
347
|
-
}
|
|
348
|
-
if (!parsed.yes) {
|
|
349
|
-
requireYesFlag(ctx, `delete prompt template ${name}`, '/template delete <name> --yes');
|
|
350
|
-
return;
|
|
351
|
-
}
|
|
352
|
-
ctx.print(templateManager.delete(name) ? `Template deleted: ${name}` : `Template not found: ${name}`);
|
|
353
|
-
return;
|
|
354
|
-
}
|
|
355
|
-
ctx.print(`Unknown subcommand: ${sub}\nUsage: /template save <name> --yes | use <name> | list | edit <name> | delete <name> --yes`);
|
|
356
|
-
},
|
|
357
|
-
});
|
|
358
|
-
|
|
359
262
|
registry.register({
|
|
360
263
|
name: 'memory',
|
|
361
264
|
description: 'Manage session memories (pinned across context compaction)',
|
|
@@ -219,19 +219,6 @@ export function registerShellCoreCommands(registry: CommandRegistry): void {
|
|
|
219
219
|
},
|
|
220
220
|
});
|
|
221
221
|
|
|
222
|
-
registry.register({
|
|
223
|
-
name: 'wq',
|
|
224
|
-
aliases: [':wq'],
|
|
225
|
-
description: 'Blocked in Agent; git commit/exit is owned by GoodVibes TUI',
|
|
226
|
-
handler(_args, ctx) {
|
|
227
|
-
ctx.print([
|
|
228
|
-
'Blocked: /wq is not available in GoodVibes Agent.',
|
|
229
|
-
'Git commit, worktree, and coding-session exit flows belong to GoodVibes TUI.',
|
|
230
|
-
'No files, commits, or repository state were changed.',
|
|
231
|
-
].join('\n'));
|
|
232
|
-
},
|
|
233
|
-
});
|
|
234
|
-
|
|
235
222
|
registry.register({
|
|
236
223
|
name: 'effort',
|
|
237
224
|
aliases: ['e'],
|