@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,197 +0,0 @@
|
|
|
1
|
-
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import { dirname, resolve } from 'node:path';
|
|
3
|
-
import {
|
|
4
|
-
applySettingsSyncBundle,
|
|
5
|
-
clearManagedSettingLock,
|
|
6
|
-
exportSettingsSyncBundle,
|
|
7
|
-
formatResolvedSettingReview,
|
|
8
|
-
resolveSettingsSyncConflict,
|
|
9
|
-
formatStagedManagedBundleReview,
|
|
10
|
-
formatSettingsControlPlaneReview,
|
|
11
|
-
getSettingsControlPlaneSnapshot,
|
|
12
|
-
inspectSettingsSyncBundle,
|
|
13
|
-
recordSettingsSyncEvent,
|
|
14
|
-
recordSettingsSyncFailure,
|
|
15
|
-
setManagedSettingLock,
|
|
16
|
-
type SettingsSyncBundle,
|
|
17
|
-
} from '@/runtime/index.ts';
|
|
18
|
-
import { getProviderIdFromModel } from '../../config/provider-model.ts';
|
|
19
|
-
import { type ConfigKey } from '../../config/index.ts';
|
|
20
|
-
import { CONFIG_KEYS } from '@pellux/goodvibes-sdk/platform/config';
|
|
21
|
-
import type { CommandRegistry } from '../command-registry.ts';
|
|
22
|
-
import { openCommandPanel, requireShellPaths } from './runtime-services.ts';
|
|
23
|
-
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
24
|
-
import { requireYesFlag, stripYesFlag } from './confirmation.ts';
|
|
25
|
-
|
|
26
|
-
export function registerSettingsSyncRuntimeCommands(registry: CommandRegistry): void {
|
|
27
|
-
registry.register({
|
|
28
|
-
name: 'settingssync',
|
|
29
|
-
aliases: ['settings-sync'],
|
|
30
|
-
description: 'Review sync posture, export/import settings-sync bundles, and open the settings sync workspace',
|
|
31
|
-
usage: '[review|panel|show <key>|staged|conflicts|resolve <key> <local|synced> --yes|failures|rollback-history|export <path> --yes|inspect <path>|pull <path> --yes|push <path> --yes|lock <key> <source> <reason...> --yes|unlock <key> --yes]',
|
|
32
|
-
handler(args, ctx) {
|
|
33
|
-
const parsed = stripYesFlag(args);
|
|
34
|
-
const commandArgs = [...parsed.rest];
|
|
35
|
-
const shellPaths = requireShellPaths(ctx);
|
|
36
|
-
const controlPlaneConfigDir = ctx.platform.configManager.getControlPlaneConfigDir();
|
|
37
|
-
const sub = (commandArgs[0] ?? 'review').toLowerCase();
|
|
38
|
-
if (sub === 'panel' || sub === 'open') {
|
|
39
|
-
openCommandPanel(ctx, 'settings-sync');
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
if (sub === 'show') {
|
|
43
|
-
const key = commandArgs[1] as ConfigKey | undefined;
|
|
44
|
-
if (!key || !CONFIG_KEYS.has(key)) {
|
|
45
|
-
ctx.print('Usage: /settingssync show <config-key>');
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
ctx.print(formatResolvedSettingReview(ctx.platform.configManager, key));
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
if (sub === 'staged') {
|
|
52
|
-
ctx.print(formatStagedManagedBundleReview(ctx.platform.configManager));
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
if (sub === 'conflicts') {
|
|
56
|
-
const snapshot = getSettingsControlPlaneSnapshot(ctx.platform.configManager);
|
|
57
|
-
ctx.print(snapshot.conflicts.length > 0
|
|
58
|
-
? [
|
|
59
|
-
'Settings Sync Conflicts',
|
|
60
|
-
...snapshot.conflicts.map((conflict) => ` ${conflict.key} source=${conflict.source} path=${conflict.path}`),
|
|
61
|
-
].join('\n')
|
|
62
|
-
: 'Settings Sync Conflicts\n No settings conflicts recorded.');
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
if (sub === 'resolve') {
|
|
66
|
-
const key = commandArgs[1] as ConfigKey | undefined;
|
|
67
|
-
const resolution = (commandArgs[2] ?? '').toLowerCase();
|
|
68
|
-
if (!key || !CONFIG_KEYS.has(key) || (resolution !== 'local' && resolution !== 'synced')) {
|
|
69
|
-
ctx.print('Usage: /settingssync resolve <config-key> <local|synced> --yes');
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
if (!parsed.yes) {
|
|
73
|
-
requireYesFlag(ctx, `resolve synced conflict for ${key}`, '/settingssync resolve <config-key> <local|synced> --yes');
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
const changed = resolveSettingsSyncConflict(ctx.platform.configManager, key, resolution);
|
|
77
|
-
if (!changed) {
|
|
78
|
-
ctx.print(`No synced conflict found for ${key}.`);
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
ctx.session.runtime.model = String(ctx.platform.configManager.get('provider.model'));
|
|
82
|
-
ctx.session.runtime.provider = getProviderIdFromModel(ctx.platform.configManager.get('provider.model'));
|
|
83
|
-
ctx.session.runtime.reasoningEffort = ctx.platform.configManager.get('provider.reasoningEffort') as string;
|
|
84
|
-
ctx.print(`Resolved synced conflict for ${key} using the ${resolution} value.`);
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
if (sub === 'failures') {
|
|
88
|
-
const snapshot = getSettingsControlPlaneSnapshot(ctx.platform.configManager);
|
|
89
|
-
ctx.print(snapshot.recentFailures.length > 0
|
|
90
|
-
? [
|
|
91
|
-
'Settings Sync Failures',
|
|
92
|
-
...snapshot.recentFailures.map((failure) => ` ${failure.surface} ${failure.message}`),
|
|
93
|
-
].join('\n')
|
|
94
|
-
: 'Settings Sync Failures\n No recent sync or managed-setting failures recorded.');
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
if (sub === 'rollback-history') {
|
|
98
|
-
const snapshot = getSettingsControlPlaneSnapshot(ctx.platform.configManager);
|
|
99
|
-
ctx.print(snapshot.rollbackHistory.length > 0
|
|
100
|
-
? [
|
|
101
|
-
'Managed Rollback History',
|
|
102
|
-
...snapshot.rollbackHistory.map((entry) => (
|
|
103
|
-
` ${entry.token} ${entry.profileName} restored=${entry.restoredKeys.length} ${new Date(entry.appliedAt).toLocaleString()}`
|
|
104
|
-
)),
|
|
105
|
-
].join('\n')
|
|
106
|
-
: 'Managed Rollback History\n No managed apply rollback records yet.');
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
if (sub === 'export' || sub === 'push') {
|
|
110
|
-
const pathArg = commandArgs[1];
|
|
111
|
-
if (!pathArg) {
|
|
112
|
-
ctx.print(`Usage: /settingssync ${sub} <path> --yes`);
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
if (!parsed.yes) {
|
|
116
|
-
requireYesFlag(ctx, `${sub === 'push' ? 'push' : 'export'} settings sync bundle to ${pathArg}`, `/settingssync ${sub} <path> --yes`);
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
const targetPath = shellPaths.resolveWorkspacePath(pathArg);
|
|
120
|
-
const bundle = exportSettingsSyncBundle(ctx.platform.configManager);
|
|
121
|
-
mkdirSync(dirname(targetPath), { recursive: true });
|
|
122
|
-
writeFileSync(targetPath, JSON.stringify(bundle, null, 2) + '\n', 'utf-8');
|
|
123
|
-
recordSettingsSyncEvent({
|
|
124
|
-
surface: 'settings-sync',
|
|
125
|
-
direction: sub === 'push' ? 'push' : 'export',
|
|
126
|
-
path: targetPath,
|
|
127
|
-
timestamp: Date.now(),
|
|
128
|
-
detail: `${Object.keys(bundle.settings).length} settings exported`,
|
|
129
|
-
}, controlPlaneConfigDir);
|
|
130
|
-
ctx.print(`Settings sync bundle exported to ${targetPath}`);
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
if (sub === 'inspect') {
|
|
134
|
-
const pathArg = commandArgs[1];
|
|
135
|
-
if (!pathArg) {
|
|
136
|
-
ctx.print('Usage: /settingssync inspect <path>');
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
const sourcePath = shellPaths.resolveWorkspacePath(pathArg);
|
|
140
|
-
const bundle = JSON.parse(readFileSync(sourcePath, 'utf-8')) as SettingsSyncBundle;
|
|
141
|
-
ctx.print(inspectSettingsSyncBundle(bundle));
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
if (sub === 'pull') {
|
|
145
|
-
const pathArg = commandArgs[1];
|
|
146
|
-
if (!pathArg) {
|
|
147
|
-
ctx.print('Usage: /settingssync pull <path> --yes');
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
if (!parsed.yes) {
|
|
151
|
-
requireYesFlag(ctx, `pull settings sync bundle from ${pathArg}`, '/settingssync pull <path> --yes');
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
const sourcePath = shellPaths.resolveWorkspacePath(pathArg);
|
|
155
|
-
try {
|
|
156
|
-
const bundle = JSON.parse(readFileSync(sourcePath, 'utf-8')) as SettingsSyncBundle;
|
|
157
|
-
const result = applySettingsSyncBundle(ctx.platform.configManager, bundle, sourcePath);
|
|
158
|
-
ctx.print(`Settings sync bundle pulled from ${sourcePath} (${result.appliedCount} applied, ${result.conflictCount} conflicts).`);
|
|
159
|
-
} catch (error) {
|
|
160
|
-
recordSettingsSyncFailure('settings-sync', summarizeError(error), controlPlaneConfigDir);
|
|
161
|
-
ctx.print(summarizeError(error));
|
|
162
|
-
}
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
if (sub === 'lock') {
|
|
166
|
-
const key = commandArgs[1] as ConfigKey | undefined;
|
|
167
|
-
const source = commandArgs[2];
|
|
168
|
-
const reason = commandArgs.slice(3).join(' ').trim();
|
|
169
|
-
if (!key || !source || !reason || !CONFIG_KEYS.has(key)) {
|
|
170
|
-
ctx.print('Usage: /settingssync lock <config-key> <source> <reason...> --yes');
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
if (!parsed.yes) {
|
|
174
|
-
requireYesFlag(ctx, `lock managed setting ${key}`, '/settingssync lock <config-key> <source> <reason...> --yes');
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
setManagedSettingLock(key, source, reason, controlPlaneConfigDir);
|
|
178
|
-
ctx.print(`Managed lock recorded for ${key}.`);
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
if (sub === 'unlock') {
|
|
182
|
-
const key = commandArgs[1] as ConfigKey | undefined;
|
|
183
|
-
if (!key || !CONFIG_KEYS.has(key)) {
|
|
184
|
-
ctx.print('Usage: /settingssync unlock <config-key> --yes');
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
if (!parsed.yes) {
|
|
188
|
-
requireYesFlag(ctx, `unlock managed setting ${key}`, '/settingssync unlock <config-key> --yes');
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
ctx.print(clearManagedSettingLock(key, controlPlaneConfigDir) ? `Managed lock cleared for ${key}.` : `No managed lock found for ${key}.`);
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
ctx.print(formatSettingsControlPlaneReview(ctx.platform.configManager).join('\n'));
|
|
195
|
-
},
|
|
196
|
-
});
|
|
197
|
-
}
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
import { writeFile } from 'node:fs/promises';
|
|
2
|
-
import { resolve } from 'path';
|
|
3
|
-
import type { CommandRegistry } from '../command-registry.ts';
|
|
4
|
-
import {
|
|
5
|
-
type ExportMessage,
|
|
6
|
-
defaultExportPath,
|
|
7
|
-
exportToHTML,
|
|
8
|
-
exportToJSON,
|
|
9
|
-
exportToMarkdownExtended,
|
|
10
|
-
} from '@pellux/goodvibes-sdk/platform/export';
|
|
11
|
-
import { logger } from '@pellux/goodvibes-sdk/platform/utils';
|
|
12
|
-
import { requireShellPaths } from './runtime-services.ts';
|
|
13
|
-
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
14
|
-
import { requireYesFlag, stripYesFlag } from './confirmation.ts';
|
|
15
|
-
|
|
16
|
-
export function registerShareRuntimeCommands(registry: CommandRegistry): void {
|
|
17
|
-
registry.register({
|
|
18
|
-
name: 'share',
|
|
19
|
-
aliases: [],
|
|
20
|
-
description: 'Export the current session to a shareable format (html, json, md)',
|
|
21
|
-
usage: '<html|json|md> [path] [--redact] --yes',
|
|
22
|
-
argsHint: '<html|json|md> [path]',
|
|
23
|
-
async handler(args, ctx) {
|
|
24
|
-
const parsed = stripYesFlag(args);
|
|
25
|
-
const commandArgs = [...parsed.rest];
|
|
26
|
-
const shellPaths = requireShellPaths(ctx);
|
|
27
|
-
const FORMATS = ['html', 'json', 'md'] as const;
|
|
28
|
-
type Format = typeof FORMATS[number];
|
|
29
|
-
|
|
30
|
-
const format = commandArgs[0]?.toLowerCase() as Format | undefined;
|
|
31
|
-
if (!format || !FORMATS.includes(format)) {
|
|
32
|
-
ctx.print(
|
|
33
|
-
'Usage: /share <html|json|md> [path] [--redact] --yes\n'
|
|
34
|
-
+ ' html — self-contained HTML with syntax highlighting\n'
|
|
35
|
-
+ ' json — structured JSON (machine-readable)\n'
|
|
36
|
-
+ ' md — Markdown\n\n'
|
|
37
|
-
+ 'Options:\n'
|
|
38
|
-
+ ' --redact Redact API keys and personal paths from output\n\n'
|
|
39
|
-
+ 'Default path: ~/goodvibes-exports/session-<timestamp>.<ext>',
|
|
40
|
-
);
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const remainingArgs = commandArgs.slice(1);
|
|
45
|
-
const redact = remainingArgs.includes('--redact');
|
|
46
|
-
const pathArgs = remainingArgs.filter(a => a !== '--redact');
|
|
47
|
-
const outputPath = pathArgs.length > 0
|
|
48
|
-
? shellPaths.resolveWorkspacePath(pathArgs[0])
|
|
49
|
-
: defaultExportPath(format, shellPaths.homeDirectory);
|
|
50
|
-
|
|
51
|
-
if (!parsed.yes) {
|
|
52
|
-
requireYesFlag(ctx, `export ${format.toUpperCase()} session to ${outputPath}`, '/share <html|json|md> [path] [--redact] --yes');
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const convData = ctx.session.conversationManager.toJSON() as {
|
|
57
|
-
messages: Array<{
|
|
58
|
-
role: string;
|
|
59
|
-
content: unknown;
|
|
60
|
-
toolCalls?: Array<{ id: string; name: string; arguments: Record<string, unknown> }>;
|
|
61
|
-
callId?: string;
|
|
62
|
-
toolName?: string;
|
|
63
|
-
reasoningContent?: string;
|
|
64
|
-
reasoningSummary?: string;
|
|
65
|
-
usage?: {
|
|
66
|
-
inputTokens: number;
|
|
67
|
-
outputTokens: number;
|
|
68
|
-
cacheReadTokens?: number;
|
|
69
|
-
cacheWriteTokens?: number;
|
|
70
|
-
};
|
|
71
|
-
cancelled?: boolean;
|
|
72
|
-
}>;
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
if (!convData.messages || convData.messages.length === 0) {
|
|
76
|
-
ctx.print('Nothing to export — conversation is empty.');
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const messages: ExportMessage[] = convData.messages.map(m => ({
|
|
81
|
-
role: m.role as ExportMessage['role'],
|
|
82
|
-
content: m.content as string,
|
|
83
|
-
toolCalls: m.toolCalls,
|
|
84
|
-
callId: m.callId,
|
|
85
|
-
toolName: m.toolName,
|
|
86
|
-
reasoningContent: m.reasoningContent,
|
|
87
|
-
reasoningSummary: m.reasoningSummary,
|
|
88
|
-
usage: m.usage,
|
|
89
|
-
cancelled: m.cancelled,
|
|
90
|
-
}));
|
|
91
|
-
const metadata = {
|
|
92
|
-
model: ctx.session.runtime.model,
|
|
93
|
-
provider: ctx.session.runtime.provider,
|
|
94
|
-
sessionId: ctx.session.runtime.sessionId,
|
|
95
|
-
title: ctx.session.conversationManager.title || undefined,
|
|
96
|
-
};
|
|
97
|
-
const options = { redact };
|
|
98
|
-
|
|
99
|
-
let outputContent: string;
|
|
100
|
-
try {
|
|
101
|
-
if (format === 'html') outputContent = exportToHTML(messages, metadata, options);
|
|
102
|
-
else if (format === 'json') outputContent = exportToJSON(messages, metadata, options);
|
|
103
|
-
else outputContent = exportToMarkdownExtended(messages, metadata, options);
|
|
104
|
-
} catch (err) {
|
|
105
|
-
ctx.print(`Export failed: ${summarizeError(err)}`);
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const { mkdirSync } = await import('node:fs');
|
|
110
|
-
const { dirname } = await import('node:path');
|
|
111
|
-
try {
|
|
112
|
-
mkdirSync(dirname(outputPath), { recursive: true });
|
|
113
|
-
} catch (mkdirErr) {
|
|
114
|
-
logger.warn(`[share] mkdir failed for ${dirname(outputPath)}:`, mkdirErr instanceof Error ? { message: mkdirErr.message } : undefined);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
try {
|
|
118
|
-
await writeFile(outputPath, outputContent, 'utf-8');
|
|
119
|
-
} catch (err) {
|
|
120
|
-
ctx.print(`Failed to write export: ${summarizeError(err)}`);
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
ctx.print(`Exported ${format.toUpperCase()} session to ${outputPath}${redact ? ' (sensitive data redacted)' : ''}`);
|
|
125
|
-
},
|
|
126
|
-
});
|
|
127
|
-
}
|
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
import type { CommandRegistry } from '../command-registry.ts';
|
|
2
|
-
import { discoverSkills } from '../../panels/skills-panel.ts';
|
|
3
|
-
import {
|
|
4
|
-
installEcosystemCatalogEntry,
|
|
5
|
-
listInstalledEcosystemEntries,
|
|
6
|
-
loadEcosystemCatalog,
|
|
7
|
-
removeEcosystemCatalogEntry,
|
|
8
|
-
reviewEcosystemCatalogEntry,
|
|
9
|
-
searchEcosystemCatalog,
|
|
10
|
-
uninstallEcosystemCatalogEntry,
|
|
11
|
-
updateInstalledEcosystemEntry,
|
|
12
|
-
upsertEcosystemCatalogEntry,
|
|
13
|
-
} from '@/runtime/index.ts';
|
|
14
|
-
import { requireEcosystemCatalogPaths, requirePanelManager, requireShellPaths } from './runtime-services.ts';
|
|
15
|
-
import { runAgentSkillsRuntimeCommand } from './agent-skills-runtime.ts';
|
|
16
|
-
|
|
17
|
-
export function registerSkillsRuntimeCommands(registry: CommandRegistry): void {
|
|
18
|
-
registry.register({
|
|
19
|
-
name: 'skills',
|
|
20
|
-
aliases: ['skill'],
|
|
21
|
-
description: 'Inspect installed skill packs',
|
|
22
|
-
usage: '[open|local ...|list|show <name>|origins|browse [query]|installed|catalog-review <id>|publish-local <id> <path> <summary...>|unpublish <id>|install-hint <catalog-id>|install <id> [project|user]|update <id> [project|user]|uninstall <id> [project|user]]',
|
|
23
|
-
async handler(args, ctx) {
|
|
24
|
-
const sub = args[0] ?? 'open';
|
|
25
|
-
if (sub === 'local' || sub === 'agent') {
|
|
26
|
-
await runAgentSkillsRuntimeCommand(args.slice(1), ctx);
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
if (sub === 'open' || sub === 'panel') {
|
|
30
|
-
if (ctx.showPanel) ctx.showPanel('skills');
|
|
31
|
-
else {
|
|
32
|
-
const panelManager = requirePanelManager(ctx);
|
|
33
|
-
panelManager.open('skills');
|
|
34
|
-
panelManager.show();
|
|
35
|
-
ctx.renderRequest();
|
|
36
|
-
}
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
const skills = await discoverSkills(requireShellPaths(ctx));
|
|
40
|
-
const ecosystemPaths = requireEcosystemCatalogPaths(ctx);
|
|
41
|
-
if (sub === 'list') {
|
|
42
|
-
if (skills.length === 0) {
|
|
43
|
-
ctx.print('No skills discovered.');
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
ctx.print([
|
|
47
|
-
`Skills (${skills.length})`,
|
|
48
|
-
...skills.map((skill) => ` ${skill.name} [${skill.origin}] ${skill.description || 'No description provided.'}`),
|
|
49
|
-
].join('\n'));
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
if (sub === 'origins') {
|
|
53
|
-
const counts = new Map<string, number>();
|
|
54
|
-
for (const skill of skills) counts.set(skill.origin, (counts.get(skill.origin) ?? 0) + 1);
|
|
55
|
-
ctx.print([
|
|
56
|
-
'Skill Origins',
|
|
57
|
-
...[...counts.entries()].map(([origin, count]) => ` ${origin}: ${count}`),
|
|
58
|
-
].join('\n'));
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
if (sub === 'show') {
|
|
62
|
-
const name = args[1];
|
|
63
|
-
if (!name) {
|
|
64
|
-
ctx.print('Usage: /skills show <name>');
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
const skill = skills.find((entry) => entry.name === name);
|
|
68
|
-
if (!skill) {
|
|
69
|
-
ctx.print(`Unknown skill: ${name}`);
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
ctx.print([
|
|
73
|
-
`Skill ${skill.name}`,
|
|
74
|
-
` origin: ${skill.origin}`,
|
|
75
|
-
` path: ${skill.path}`,
|
|
76
|
-
` description: ${skill.description || 'No description provided.'}`,
|
|
77
|
-
` dependencies: ${skill.dependencies.join(', ') || '(none)'}`,
|
|
78
|
-
` includes: ${skill.includes.join(', ') || '(none)'}`,
|
|
79
|
-
].join('\n'));
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
if (sub === 'browse' || sub === 'catalog') {
|
|
83
|
-
const query = args.slice(1).join(' ');
|
|
84
|
-
const entries = query ? searchEcosystemCatalog('skill', query, ecosystemPaths) : loadEcosystemCatalog('skill', ecosystemPaths);
|
|
85
|
-
if (entries.length === 0) {
|
|
86
|
-
ctx.print(query
|
|
87
|
-
? `No curated skill catalog entries matched "${query}".`
|
|
88
|
-
: 'No curated skill catalog entries found. Add .goodvibes/agent/ecosystem/skills.json to publish a local-first skill catalog.');
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
ctx.print([
|
|
92
|
-
`Curated Skill Catalog (${entries.length})`,
|
|
93
|
-
...entries.map((entry) => ` ${entry.id} ${entry.name} [${entry.tags.join(', ') || 'untagged'}] ${entry.summary}`),
|
|
94
|
-
].join('\n'));
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
if (sub === 'installed') {
|
|
98
|
-
const receipts = listInstalledEcosystemEntries('skill', ecosystemPaths);
|
|
99
|
-
if (receipts.length === 0) {
|
|
100
|
-
ctx.print('No curated skills installed from local catalogs yet.');
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
ctx.print([
|
|
104
|
-
`Installed Curated Skills (${receipts.length})`,
|
|
105
|
-
...receipts.map((receipt) => ` ${receipt.entry.id} ${receipt.scope} ${receipt.targetPath}`),
|
|
106
|
-
].join('\n'));
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
if (sub === 'catalog-review') {
|
|
110
|
-
const entryId = args[1];
|
|
111
|
-
if (!entryId) {
|
|
112
|
-
ctx.print('Usage: /skills catalog-review <catalog-id>');
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
const entry = loadEcosystemCatalog('skill', ecosystemPaths).find((candidate) => candidate.id === entryId);
|
|
116
|
-
if (!entry) {
|
|
117
|
-
ctx.print(`Unknown curated skill entry: ${entryId}`);
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
const review = reviewEcosystemCatalogEntry(entry, ecosystemPaths);
|
|
121
|
-
ctx.print([
|
|
122
|
-
`Skill Catalog Review: ${entry.name}`,
|
|
123
|
-
` id: ${entry.id}`,
|
|
124
|
-
` source: ${entry.source}`,
|
|
125
|
-
` sourceKind: ${review.sourceKind}`,
|
|
126
|
-
` sourceExists: ${review.sourceExists ? 'yes' : 'no'}`,
|
|
127
|
-
` recommendedScope: ${review.recommendedScope}`,
|
|
128
|
-
` risk: ${review.riskLevel}`,
|
|
129
|
-
` trust notes: ${entry.trustNotes ?? '(none)'}`,
|
|
130
|
-
` provenance: ${entry.provenance ?? '(none)'}`,
|
|
131
|
-
` update hint: ${entry.updateHint ?? '(none)'}`,
|
|
132
|
-
].join('\n'));
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
if (sub === 'publish-local') {
|
|
136
|
-
const entryId = args[1];
|
|
137
|
-
const sourcePath = args[2];
|
|
138
|
-
const summary = args.slice(3).join(' ').trim();
|
|
139
|
-
if (!entryId || !sourcePath || !summary) {
|
|
140
|
-
ctx.print('Usage: /skills publish-local <catalog-id> <path> <summary...>');
|
|
141
|
-
return;
|
|
142
|
-
}
|
|
143
|
-
const result = upsertEcosystemCatalogEntry({
|
|
144
|
-
id: entryId,
|
|
145
|
-
kind: 'skill',
|
|
146
|
-
name: entryId.replace(/[-_]/g, ' ').replace(/\b\w/g, (char) => char.toUpperCase()),
|
|
147
|
-
summary,
|
|
148
|
-
source: sourcePath,
|
|
149
|
-
tags: ['local-first', 'published'],
|
|
150
|
-
provenance: 'operator-published',
|
|
151
|
-
updateHint: 'Use /skills publish-local again to refresh catalog metadata after edits.',
|
|
152
|
-
}, ecosystemPaths);
|
|
153
|
-
ctx.print(result.ok ? `Published curated skill ${entryId} into ${result.path}` : `Error: ${result.error}`);
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
if (sub === 'unpublish') {
|
|
157
|
-
const entryId = args[1];
|
|
158
|
-
if (!entryId) {
|
|
159
|
-
ctx.print('Usage: /skills unpublish <catalog-id>');
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
const result = removeEcosystemCatalogEntry('skill', entryId, ecosystemPaths);
|
|
163
|
-
ctx.print(result.ok ? `Removed curated skill ${entryId} from ${result.path}` : `Error: ${result.error}`);
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
if (sub === 'install-hint') {
|
|
167
|
-
const entryId = args[1];
|
|
168
|
-
if (!entryId) {
|
|
169
|
-
ctx.print('Usage: /skills install-hint <catalog-id>');
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
const entry = loadEcosystemCatalog('skill', ecosystemPaths).find((candidate) => candidate.id === entryId);
|
|
173
|
-
if (!entry) {
|
|
174
|
-
ctx.print(`Unknown curated skill entry: ${entryId}`);
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
ctx.print([
|
|
178
|
-
`Skill Install Guidance: ${entry.name}`,
|
|
179
|
-
` id: ${entry.id}`,
|
|
180
|
-
` source: ${entry.source}`,
|
|
181
|
-
` tags: ${entry.tags.join(', ') || '(none)'}`,
|
|
182
|
-
` trust notes: ${entry.trustNotes ?? '(none)'}`,
|
|
183
|
-
` install hint: ${entry.installHint ?? 'Place the skill pack under a configured skill directory and refresh the skills panel.'}`,
|
|
184
|
-
].join('\n'));
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
if (sub === 'install') {
|
|
188
|
-
const entryId = args[1];
|
|
189
|
-
const scopeArg = args[2];
|
|
190
|
-
if (!entryId) {
|
|
191
|
-
ctx.print('Usage: /skills install <catalog-id> [project|user]');
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
const scope = scopeArg === 'project' ? 'project' : 'user';
|
|
195
|
-
const result = installEcosystemCatalogEntry('skill', entryId, { ...ecosystemPaths, scope });
|
|
196
|
-
ctx.print(result.ok ? `Installed curated skill ${entryId} into ${result.receipt.targetPath}` : `Error: ${result.error}`);
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
if (sub === 'update') {
|
|
200
|
-
const entryId = args[1];
|
|
201
|
-
const scopeArg = args[2];
|
|
202
|
-
if (!entryId) {
|
|
203
|
-
ctx.print('Usage: /skills update <catalog-id> [project|user]');
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
const scope = scopeArg === 'project' ? 'project' : 'user';
|
|
207
|
-
const result = updateInstalledEcosystemEntry('skill', entryId, { ...ecosystemPaths, scope });
|
|
208
|
-
ctx.print(result.ok ? `Updated curated skill ${entryId} in ${result.receipt.targetPath}` : `Error: ${result.error}`);
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
if (sub === 'uninstall') {
|
|
212
|
-
const entryId = args[1];
|
|
213
|
-
const scopeArg = args[2];
|
|
214
|
-
if (!entryId) {
|
|
215
|
-
ctx.print('Usage: /skills uninstall <catalog-id> [project|user]');
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
const scope = scopeArg === 'project' ? 'project' : 'user';
|
|
219
|
-
const result = uninstallEcosystemCatalogEntry('skill', entryId, { ...ecosystemPaths, scope });
|
|
220
|
-
ctx.print(result.ok ? `Uninstalled curated skill ${entryId} from ${result.removedPath}` : `Error: ${result.error}`);
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
|
-
ctx.print('Usage: /skills [open|local ...|list|show <name>|origins|browse [query]|installed|catalog-review <id>|publish-local <id> <path> <summary...>|unpublish <id>|install-hint <catalog-id>|install <id> [project|user]|update <id> [project|user]|uninstall <id> [project|user]]');
|
|
224
|
-
},
|
|
225
|
-
});
|
|
226
|
-
}
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs';
|
|
2
|
-
import type { CommandRegistry } from '../command-registry.ts';
|
|
3
|
-
import type { RemoteSessionBundle } from '@/runtime/index.ts';
|
|
4
|
-
import { requirePeerClient, requireShellPaths } from './runtime-services.ts';
|
|
5
|
-
import { requireYesFlag, stripYesFlag } from './confirmation.ts';
|
|
6
|
-
|
|
7
|
-
function inspectRemoteSessionBundle(bundle: RemoteSessionBundle): string {
|
|
8
|
-
return [
|
|
9
|
-
'Teleport Bundle Review',
|
|
10
|
-
` session: ${bundle.sessionId}`,
|
|
11
|
-
` exportedAt: ${new Date(bundle.exportedAt).toISOString()}`,
|
|
12
|
-
` active connections: ${bundle.activeConnectionIds.length}`,
|
|
13
|
-
` pools: ${bundle.pools.length}`,
|
|
14
|
-
` contracts: ${bundle.contracts.length}`,
|
|
15
|
-
` artifacts: ${bundle.artifacts.length}`,
|
|
16
|
-
].join('\n');
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function registerTeleportRuntimeCommands(registry: CommandRegistry): void {
|
|
20
|
-
registry.register({
|
|
21
|
-
name: 'teleport',
|
|
22
|
-
description: 'Package, inspect, and import portable remote-session handoff bundles',
|
|
23
|
-
usage: '[export <path> --yes|inspect <path>|import <path> --yes]',
|
|
24
|
-
async handler(args, ctx) {
|
|
25
|
-
const parsed = stripYesFlag(args);
|
|
26
|
-
const commandArgs = [...parsed.rest];
|
|
27
|
-
const shellPaths = requireShellPaths(ctx);
|
|
28
|
-
const mode = (commandArgs[0] ?? 'export').toLowerCase();
|
|
29
|
-
const pathArg = commandArgs[1];
|
|
30
|
-
if (!pathArg) {
|
|
31
|
-
ctx.print('Usage: /teleport [export <path> --yes|inspect <path>|import <path> --yes]');
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
let peerClient;
|
|
35
|
-
try {
|
|
36
|
-
peerClient = requirePeerClient(ctx);
|
|
37
|
-
} catch {
|
|
38
|
-
ctx.print('Remote worker registry is not available in this runtime.');
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
const targetPath = shellPaths.resolveWorkspacePath(pathArg);
|
|
42
|
-
if (mode === 'export') {
|
|
43
|
-
if (!parsed.yes) {
|
|
44
|
-
requireYesFlag(ctx, `export teleport bundle to ${pathArg}`, '/teleport export <path> --yes');
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
const exported = await peerClient.runners.exportSessionBundle(targetPath);
|
|
48
|
-
ctx.print(`Teleport bundle exported for session ${exported.bundle.sessionId} to ${exported.path}`);
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
if (mode === 'inspect') {
|
|
52
|
-
const bundle = JSON.parse(readFileSync(targetPath, 'utf-8')) as RemoteSessionBundle;
|
|
53
|
-
ctx.print(inspectRemoteSessionBundle(bundle));
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
if (mode === 'import') {
|
|
57
|
-
if (!parsed.yes) {
|
|
58
|
-
requireYesFlag(ctx, `import teleport bundle from ${pathArg}`, '/teleport import <path> --yes');
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
const bundle = await peerClient.runners.importSessionBundle(targetPath);
|
|
62
|
-
ctx.print(`Imported teleport bundle ${bundle.sessionId} with ${bundle.contracts.length} contracts.`);
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
ctx.print('Usage: /teleport [export <path> --yes|inspect <path>|import <path> --yes]');
|
|
66
|
-
},
|
|
67
|
-
});
|
|
68
|
-
}
|