@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,288 +0,0 @@
|
|
|
1
|
-
import { dirname, join } from 'path';
|
|
2
|
-
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
-
import type { CommandRegistry } from '../command-registry.ts';
|
|
4
|
-
import type { ConfigKey } from '../../config/index.ts';
|
|
5
|
-
import { CONFIG_SCHEMA } from '../../config/index.ts';
|
|
6
|
-
import { listHookPointContracts } from '@pellux/goodvibes-sdk/platform/hooks';
|
|
7
|
-
import type { SetupTransferBundle } from './local-setup-transfer.ts';
|
|
8
|
-
import {
|
|
9
|
-
buildSetupTransferBundle,
|
|
10
|
-
createSetupLink,
|
|
11
|
-
exportSetupTransferBundle,
|
|
12
|
-
inspectSetupTransferBundle,
|
|
13
|
-
parseSetupLink,
|
|
14
|
-
} from './local-setup-transfer.ts';
|
|
15
|
-
import { buildSetupReviewSnapshot, exportSetupSupportBundle } from './local-setup-review.ts';
|
|
16
|
-
import { openOnboardingWizard, requirePanelManager, requireShellPaths } from './runtime-services.ts';
|
|
17
|
-
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
18
|
-
import { GOODVIBES_AGENT_SURFACE_ROOT } from '../../config/surface.ts';
|
|
19
|
-
import { requireYesFlag, stripYesFlag } from './confirmation.ts';
|
|
20
|
-
|
|
21
|
-
type SetupSnapshot = Awaited<ReturnType<typeof buildSetupReviewSnapshot>>;
|
|
22
|
-
|
|
23
|
-
export function registerLocalSetupCommands(registry: CommandRegistry): void {
|
|
24
|
-
registry.register({
|
|
25
|
-
name: 'setup',
|
|
26
|
-
aliases: ['startup'],
|
|
27
|
-
description: 'Launch the onboarding wizard and review Agent startup readiness',
|
|
28
|
-
usage: '[review|doctor|services|hooks|remote|onboarding|support-bundle <dir> --yes|export <path> --yes|transfer <export|inspect|import> <path> [--yes]|link <surface> [target]|open-link <uri>]',
|
|
29
|
-
async handler(args, ctx) {
|
|
30
|
-
const parsed = stripYesFlag(args);
|
|
31
|
-
const commandArgs = [...parsed.rest];
|
|
32
|
-
const sub = commandArgs[0] ?? 'review';
|
|
33
|
-
let shellPaths: ReturnType<typeof requireShellPaths> | null = null;
|
|
34
|
-
let snapshotPromise: Promise<SetupSnapshot> | null = null;
|
|
35
|
-
const getShellPaths = () => (shellPaths ??= requireShellPaths(ctx));
|
|
36
|
-
const getSnapshot = async (): Promise<SetupSnapshot> => {
|
|
37
|
-
snapshotPromise ??= buildSetupReviewSnapshot(ctx);
|
|
38
|
-
return snapshotPromise;
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
if (sub === 'review') {
|
|
42
|
-
const snapshot = await getSnapshot();
|
|
43
|
-
ctx.print([
|
|
44
|
-
'Startup Readiness Review',
|
|
45
|
-
` session: ${snapshot.sessionId}`,
|
|
46
|
-
` providers/models: ${snapshot.providerCount}`,
|
|
47
|
-
` services configured: ${snapshot.serviceCount}`,
|
|
48
|
-
` oauth providers: ${snapshot.oauthProviderCount + snapshot.builtinSubscriptionProviderCount}`,
|
|
49
|
-
` active subscriptions: ${snapshot.activeSubscriptionCount}`,
|
|
50
|
-
` pending subscriptions: ${snapshot.pendingSubscriptionCount}`,
|
|
51
|
-
` skills discovered: ${snapshot.skillCount}`,
|
|
52
|
-
` plugins discovered: ${snapshot.pluginCount}`,
|
|
53
|
-
` quarantined plugins: ${snapshot.quarantinedPluginCount}`,
|
|
54
|
-
` plugin search dirs: ${snapshot.pluginDirectories.length}`,
|
|
55
|
-
` managed hooks: ${snapshot.managedHookCount}`,
|
|
56
|
-
` managed hook chains: ${snapshot.managedHookChainCount}`,
|
|
57
|
-
` mcp servers known: ${snapshot.mcpServerCount}`,
|
|
58
|
-
` mcp quarantined: ${snapshot.quarantinedMcpCount}`,
|
|
59
|
-
` mcp elevated: ${snapshot.elevatedMcpCount}`,
|
|
60
|
-
` remote workers: ${snapshot.remoteRunnerCount}`,
|
|
61
|
-
'',
|
|
62
|
-
` service ids: ${snapshot.services.join(', ') || '(none)'}`,
|
|
63
|
-
` plugin dirs: ${snapshot.pluginDirectories.join(', ') || '(none)'}`,
|
|
64
|
-
].join('\n'));
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (sub === 'doctor') {
|
|
69
|
-
const snapshot = await getSnapshot();
|
|
70
|
-
ctx.print([
|
|
71
|
-
'Startup Doctor',
|
|
72
|
-
...snapshot.issues.map((issue) => ` [${issue.severity.toUpperCase()}] ${issue.area}: ${issue.message}`),
|
|
73
|
-
...(snapshot.serviceIssues.length > 0
|
|
74
|
-
? ['', ' Service issues:', ...snapshot.serviceIssues.map((issue) => ` - ${issue}`)]
|
|
75
|
-
: []),
|
|
76
|
-
].join('\n'));
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (sub === 'services') {
|
|
81
|
-
const snapshot = await getSnapshot();
|
|
82
|
-
ctx.print([
|
|
83
|
-
'Startup Services',
|
|
84
|
-
` configured: ${snapshot.serviceCount}`,
|
|
85
|
-
` oauth providers: ${snapshot.oauthProviderCount + snapshot.builtinSubscriptionProviderCount}`,
|
|
86
|
-
` active subscriptions: ${snapshot.activeSubscriptionCount}`,
|
|
87
|
-
` pending subscriptions: ${snapshot.pendingSubscriptionCount}`,
|
|
88
|
-
` issues: ${snapshot.serviceIssues.length}`,
|
|
89
|
-
...snapshot.services.map((name) => ` ${name}`),
|
|
90
|
-
...(snapshot.serviceIssues.length > 0
|
|
91
|
-
? ['', ...snapshot.serviceIssues.map((issue) => ` issue: ${issue}`)]
|
|
92
|
-
: []),
|
|
93
|
-
].join('\n'));
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (sub === 'hooks') {
|
|
98
|
-
const snapshot = await getSnapshot();
|
|
99
|
-
const contracts = listHookPointContracts();
|
|
100
|
-
ctx.print([
|
|
101
|
-
'Startup Hooks',
|
|
102
|
-
` managed hooks: ${snapshot.managedHookCount}`,
|
|
103
|
-
` managed chains: ${snapshot.managedHookChainCount}`,
|
|
104
|
-
` hook contracts: ${contracts.length}`,
|
|
105
|
-
].join('\n'));
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (sub === 'remote') {
|
|
110
|
-
const snapshot = await getSnapshot();
|
|
111
|
-
const runners = ctx.ops.remoteRuntime?.listContracts() ?? [];
|
|
112
|
-
ctx.print([
|
|
113
|
-
'Startup Remote',
|
|
114
|
-
` worker contracts: ${snapshot.remoteRunnerCount}`,
|
|
115
|
-
...runners.map((runner) => ` ${runner.id} [${runner.trustClass}] ${runner.label}`),
|
|
116
|
-
].join('\n'));
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (sub === 'onboarding') {
|
|
121
|
-
openOnboardingWizard(ctx, { mode: 'edit', reset: true });
|
|
122
|
-
ctx.print('Opening onboarding wizard.');
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (sub === 'support-bundle') {
|
|
127
|
-
const dirArg = commandArgs[1];
|
|
128
|
-
if (!dirArg) {
|
|
129
|
-
ctx.print('Usage: /setup support-bundle <dir> --yes');
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
if (!parsed.yes) {
|
|
133
|
-
requireYesFlag(ctx, `export setup support bundle to ${dirArg}`, '/setup support-bundle <dir> --yes');
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
const snapshot = await getSnapshot();
|
|
137
|
-
const targetDir = exportSetupSupportBundle(dirArg, snapshot, ctx);
|
|
138
|
-
writeFileSync(join(targetDir, 'remote-summary.json'), JSON.stringify({
|
|
139
|
-
runners: ctx.ops.remoteRuntime?.listContracts() ?? [],
|
|
140
|
-
artifacts: (ctx.ops.remoteRuntime?.listArtifacts() ?? []).map((artifact) => ({
|
|
141
|
-
id: artifact.id,
|
|
142
|
-
runnerId: artifact.runnerId,
|
|
143
|
-
status: artifact.task.status,
|
|
144
|
-
createdAt: artifact.createdAt,
|
|
145
|
-
})),
|
|
146
|
-
}, null, 2) + '\n', 'utf-8');
|
|
147
|
-
ctx.print(`Exported support bundle to ${targetDir}`);
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (sub === 'export') {
|
|
152
|
-
const pathArg = commandArgs[1];
|
|
153
|
-
if (!pathArg) {
|
|
154
|
-
ctx.print('Usage: /setup export <path> --yes');
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
if (!parsed.yes) {
|
|
158
|
-
requireYesFlag(ctx, `export startup review to ${pathArg}`, '/setup export <path> --yes');
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
const snapshot = await getSnapshot();
|
|
162
|
-
const targetPath = getShellPaths().resolveWorkspacePath(pathArg);
|
|
163
|
-
mkdirSync(dirname(targetPath), { recursive: true });
|
|
164
|
-
writeFileSync(targetPath, JSON.stringify(snapshot, null, 2) + '\n', 'utf-8');
|
|
165
|
-
ctx.print(`Exported startup review to ${targetPath}`);
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
if (sub === 'transfer') {
|
|
170
|
-
const mode = commandArgs[1]?.toLowerCase();
|
|
171
|
-
const pathArg = commandArgs[2];
|
|
172
|
-
if (!mode || !pathArg) {
|
|
173
|
-
ctx.print('Usage: /setup transfer <export|inspect|import> <path> [--yes]');
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
const targetPath = getShellPaths().resolveWorkspacePath(pathArg);
|
|
177
|
-
if (mode === 'export') {
|
|
178
|
-
if (!parsed.yes) {
|
|
179
|
-
requireYesFlag(ctx, `export setup transfer bundle to ${pathArg}`, '/setup transfer export <path> --yes');
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
const snapshot = await getSnapshot();
|
|
183
|
-
const bundle = buildSetupTransferBundle(ctx, snapshot);
|
|
184
|
-
ctx.print(`Exported setup transfer bundle to ${exportSetupTransferBundle(ctx, pathArg, bundle)}`);
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
if (mode === 'inspect') {
|
|
188
|
-
try {
|
|
189
|
-
const bundle = JSON.parse(readFileSync(targetPath, 'utf-8')) as SetupTransferBundle;
|
|
190
|
-
ctx.print(`${inspectSetupTransferBundle(bundle)}\n path: ${targetPath}`);
|
|
191
|
-
} catch (error) {
|
|
192
|
-
ctx.print(`Failed to inspect setup transfer bundle: ${summarizeError(error)}`);
|
|
193
|
-
}
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
if (mode === 'import') {
|
|
197
|
-
if (!parsed.yes) {
|
|
198
|
-
requireYesFlag(ctx, `import setup transfer bundle from ${pathArg}`, '/setup transfer import <path> --yes');
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
try {
|
|
202
|
-
const bundle = JSON.parse(readFileSync(targetPath, 'utf-8')) as SetupTransferBundle;
|
|
203
|
-
for (const entry of CONFIG_SCHEMA) {
|
|
204
|
-
if (Object.prototype.hasOwnProperty.call(bundle.config, entry.key)) {
|
|
205
|
-
ctx.platform.configManager.setDynamic(entry.key as ConfigKey, (bundle.config as Record<string, unknown>)[entry.key]);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
if (bundle.services) {
|
|
209
|
-
const servicesPath = getShellPaths().resolveProjectPath(GOODVIBES_AGENT_SURFACE_ROOT, 'services.json');
|
|
210
|
-
mkdirSync(dirname(servicesPath), { recursive: true });
|
|
211
|
-
writeFileSync(servicesPath, JSON.stringify(bundle.services, null, 2) + '\n', 'utf-8');
|
|
212
|
-
}
|
|
213
|
-
if (bundle.ecosystem?.plugins) {
|
|
214
|
-
const pluginsPath = getShellPaths().resolveProjectPath(GOODVIBES_AGENT_SURFACE_ROOT, 'ecosystem', 'plugins.json');
|
|
215
|
-
mkdirSync(dirname(pluginsPath), { recursive: true });
|
|
216
|
-
writeFileSync(pluginsPath, JSON.stringify(bundle.ecosystem.plugins, null, 2) + '\n', 'utf-8');
|
|
217
|
-
}
|
|
218
|
-
if (bundle.ecosystem?.skills) {
|
|
219
|
-
const skillsPath = getShellPaths().resolveProjectPath(GOODVIBES_AGENT_SURFACE_ROOT, 'ecosystem', 'skills.json');
|
|
220
|
-
mkdirSync(dirname(skillsPath), { recursive: true });
|
|
221
|
-
writeFileSync(skillsPath, JSON.stringify(bundle.ecosystem.skills, null, 2) + '\n', 'utf-8');
|
|
222
|
-
}
|
|
223
|
-
ctx.print(`Imported setup transfer bundle from ${targetPath}`);
|
|
224
|
-
} catch (error) {
|
|
225
|
-
ctx.print(`Failed to import setup transfer bundle: ${summarizeError(error)}`);
|
|
226
|
-
}
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
ctx.print('Usage: /setup transfer <export|inspect|import> <path> [--yes]');
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
if (sub === 'link') {
|
|
234
|
-
const surface = commandArgs[1];
|
|
235
|
-
const target = commandArgs[2];
|
|
236
|
-
if (!surface) {
|
|
237
|
-
ctx.print('Usage: /setup link <cockpit|security|remote|knowledge|incident|hooks|orchestration|tasks> [target]');
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
ctx.print(createSetupLink(surface, target));
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
if (sub === 'open-link') {
|
|
245
|
-
const link = commandArgs[1];
|
|
246
|
-
if (!link) {
|
|
247
|
-
ctx.print('Usage: /setup open-link <goodvibes://...>');
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
const parsed = parseSetupLink(link);
|
|
251
|
-
if (!parsed) {
|
|
252
|
-
ctx.print(`Invalid setup link: ${link}`);
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
const panelOpeners: Record<string, (() => void) | undefined> = {
|
|
256
|
-
cockpit: ctx.openCockpitPanel,
|
|
257
|
-
security: ctx.openSecurityPanel,
|
|
258
|
-
remote: ctx.openRemotePanel,
|
|
259
|
-
knowledge: ctx.openKnowledgePanel,
|
|
260
|
-
incident: ctx.openIncidentPanel,
|
|
261
|
-
hooks: ctx.openHooksPanel,
|
|
262
|
-
orchestration: ctx.openOrchestrationPanel,
|
|
263
|
-
};
|
|
264
|
-
if (parsed.surface === 'tasks') {
|
|
265
|
-
if (ctx.showPanel) ctx.showPanel('tasks');
|
|
266
|
-
else {
|
|
267
|
-
const panelManager = requirePanelManager(ctx);
|
|
268
|
-
panelManager.open('tasks');
|
|
269
|
-
panelManager.show();
|
|
270
|
-
ctx.renderRequest();
|
|
271
|
-
}
|
|
272
|
-
ctx.print(`Opened setup link for tasks${parsed.target ? ` (${parsed.target})` : ''}.`);
|
|
273
|
-
return;
|
|
274
|
-
}
|
|
275
|
-
const openPanel = panelOpeners[parsed.surface];
|
|
276
|
-
if (!openPanel) {
|
|
277
|
-
ctx.print(`Unsupported setup link surface: ${parsed.surface}`);
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
openPanel();
|
|
281
|
-
ctx.print(`Opened setup link for ${parsed.surface}${parsed.target ? ` (${parsed.target})` : ''}.`);
|
|
282
|
-
return;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
ctx.print('Usage: /setup [review|doctor|services|hooks|remote|onboarding|support-bundle <dir> --yes|export <path> --yes|transfer <export|inspect|import> <path> [--yes]|link <surface> [target]|open-link <uri>]');
|
|
286
|
-
},
|
|
287
|
-
});
|
|
288
|
-
}
|
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import { dirname, resolve } from 'node:path';
|
|
3
|
-
import type { CommandRegistry } from '../command-registry.ts';
|
|
4
|
-
import { profileDataToConfigSnapshot } from '@pellux/goodvibes-sdk/platform/profiles';
|
|
5
|
-
import { CONFIG_SCHEMA, type ConfigKey } from '../../config/index.ts';
|
|
6
|
-
import { getProviderIdFromModel } from '../../config/provider-model.ts';
|
|
7
|
-
import { CONFIG_KEYS } from '@pellux/goodvibes-sdk/platform/config';
|
|
8
|
-
import type { ManagedSettingsBundle } from '@/runtime/index.ts';
|
|
9
|
-
import {
|
|
10
|
-
applyStagedManagedBundle,
|
|
11
|
-
clearManagedSettingLock,
|
|
12
|
-
formatStagedManagedBundleReview,
|
|
13
|
-
getSettingsControlPlaneSnapshot,
|
|
14
|
-
inspectManagedSettingsBundle,
|
|
15
|
-
recordSettingsSyncEvent,
|
|
16
|
-
recordSettingsSyncFailure,
|
|
17
|
-
rollbackManagedApply,
|
|
18
|
-
setManagedSettingLock,
|
|
19
|
-
stageManagedSettingsBundle,
|
|
20
|
-
} from '@/runtime/index.ts';
|
|
21
|
-
import { requireProfileManager, requireShellPaths } from './runtime-services.ts';
|
|
22
|
-
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
23
|
-
import { requireYesFlag, stripYesFlag } from './confirmation.ts';
|
|
24
|
-
|
|
25
|
-
function buildConfigSnapshot(
|
|
26
|
-
manager: { get: (key: ConfigKey) => unknown },
|
|
27
|
-
): Record<string, unknown> {
|
|
28
|
-
const snapshot: Record<string, unknown> = {};
|
|
29
|
-
for (const entry of CONFIG_SCHEMA) {
|
|
30
|
-
try {
|
|
31
|
-
snapshot[entry.key] = structuredClone(manager.get(entry.key));
|
|
32
|
-
} catch {
|
|
33
|
-
// ignore unreadable settings
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
return snapshot;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function registerManagedRuntimeCommands(registry: CommandRegistry): void {
|
|
40
|
-
registry.register({
|
|
41
|
-
name: 'managed',
|
|
42
|
-
description: 'Export, inspect, and apply managed settings bundles',
|
|
43
|
-
usage: '[review|staged|rollback-history|export <profile> <path> --yes|inspect <path>|stage <path> --yes|apply <path> [key ...] --yes|apply-staged [key ...] --yes|rollback <token> --yes|lock <key> <source> <reason...> --yes|unlock <key> --yes]',
|
|
44
|
-
handler(args, ctx) {
|
|
45
|
-
const parsed = stripYesFlag(args);
|
|
46
|
-
const commandArgs = [...parsed.rest];
|
|
47
|
-
const shellPaths = requireShellPaths(ctx);
|
|
48
|
-
const controlPlaneConfigDir = ctx.platform.configManager.getControlPlaneConfigDir();
|
|
49
|
-
const sub = commandArgs[0] ?? 'review';
|
|
50
|
-
const pm = requireProfileManager(ctx);
|
|
51
|
-
if (sub === 'review') {
|
|
52
|
-
const profiles = pm.list();
|
|
53
|
-
const snapshot = getSettingsControlPlaneSnapshot(ctx.platform.configManager);
|
|
54
|
-
ctx.print([
|
|
55
|
-
'Managed Settings Review',
|
|
56
|
-
` saved profiles: ${profiles.length}`,
|
|
57
|
-
` live config keys: ${Object.keys(buildConfigSnapshot(ctx.platform.configManager)).length}`,
|
|
58
|
-
` staged bundle: ${snapshot.stagedManagedBundle ? snapshot.stagedManagedBundle.profileName : 'none'}`,
|
|
59
|
-
` active locks: ${snapshot.managedLockCount}`,
|
|
60
|
-
` rollback records: ${snapshot.rollbackHistory.length}`,
|
|
61
|
-
].join('\n'));
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (sub === 'staged') {
|
|
66
|
-
ctx.print(formatStagedManagedBundleReview(ctx.platform.configManager));
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (sub === 'rollback-history') {
|
|
71
|
-
const snapshot = getSettingsControlPlaneSnapshot(ctx.platform.configManager);
|
|
72
|
-
ctx.print(snapshot.rollbackHistory.length > 0
|
|
73
|
-
? [
|
|
74
|
-
'Managed Rollback History',
|
|
75
|
-
...snapshot.rollbackHistory.map((entry) => (
|
|
76
|
-
` ${entry.token} ${entry.profileName} restored=${entry.restoredKeys.length} ${new Date(entry.appliedAt).toLocaleString()}`
|
|
77
|
-
)),
|
|
78
|
-
].join('\n')
|
|
79
|
-
: 'Managed Rollback History\n No managed rollback records yet.');
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (sub === 'lock') {
|
|
84
|
-
const key = commandArgs[1] as ConfigKey | undefined;
|
|
85
|
-
const source = commandArgs[2];
|
|
86
|
-
const reason = commandArgs.slice(3).join(' ').trim();
|
|
87
|
-
if (!key || !source || !reason) {
|
|
88
|
-
ctx.print('Usage: /managed lock <key> <source> <reason...> --yes');
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
if (!parsed.yes) {
|
|
92
|
-
requireYesFlag(ctx, `lock managed setting ${key}`, '/managed lock <key> <source> <reason...> --yes');
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
setManagedSettingLock(key, source, reason, controlPlaneConfigDir);
|
|
96
|
-
ctx.print(`Managed lock recorded for ${key}.`);
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (sub === 'unlock') {
|
|
101
|
-
const key = commandArgs[1] as ConfigKey | undefined;
|
|
102
|
-
if (!key) {
|
|
103
|
-
ctx.print('Usage: /managed unlock <key> --yes');
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
if (!parsed.yes) {
|
|
107
|
-
requireYesFlag(ctx, `unlock managed setting ${key}`, '/managed unlock <key> --yes');
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
ctx.print(clearManagedSettingLock(key, controlPlaneConfigDir) ? `Managed lock cleared for ${key}.` : `No managed lock found for ${key}.`);
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (sub === 'export') {
|
|
115
|
-
const profileName = commandArgs[1];
|
|
116
|
-
const pathArg = commandArgs[2];
|
|
117
|
-
if (!profileName || !pathArg) {
|
|
118
|
-
ctx.print('Usage: /managed export <profile> <path> --yes');
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
if (!parsed.yes) {
|
|
122
|
-
requireYesFlag(ctx, `export managed settings profile ${profileName} to ${pathArg}`, '/managed export <profile> <path> --yes');
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
const loaded = pm.load(profileName);
|
|
126
|
-
const bundle: ManagedSettingsBundle = {
|
|
127
|
-
version: 1,
|
|
128
|
-
exportedAt: Date.now(),
|
|
129
|
-
profileName,
|
|
130
|
-
settings: profileDataToConfigSnapshot(loaded.data),
|
|
131
|
-
};
|
|
132
|
-
const targetPath = shellPaths.resolveWorkspacePath(pathArg);
|
|
133
|
-
mkdirSync(dirname(targetPath), { recursive: true });
|
|
134
|
-
writeFileSync(targetPath, JSON.stringify(bundle, null, 2) + '\n', 'utf-8');
|
|
135
|
-
recordSettingsSyncEvent({
|
|
136
|
-
surface: 'managed',
|
|
137
|
-
direction: 'export',
|
|
138
|
-
path: targetPath,
|
|
139
|
-
timestamp: Date.now(),
|
|
140
|
-
detail: `${Object.keys(bundle.settings).length} settings exported from ${profileName}`,
|
|
141
|
-
}, controlPlaneConfigDir);
|
|
142
|
-
ctx.print(`Managed settings bundle exported to ${targetPath}`);
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
if (sub === 'apply-staged') {
|
|
147
|
-
const requestedKeys = commandArgs.slice(1).filter((value): value is ConfigKey => CONFIG_KEYS.has(value as ConfigKey));
|
|
148
|
-
const invalidKeys = commandArgs.slice(1).filter((value) => !CONFIG_KEYS.has(value as ConfigKey));
|
|
149
|
-
if (invalidKeys.length > 0) {
|
|
150
|
-
ctx.print(`Unknown config key(s): ${invalidKeys.join(', ')}`);
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
if (!parsed.yes) {
|
|
154
|
-
requireYesFlag(ctx, 'apply staged managed settings', '/managed apply-staged [key ...] --yes');
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
try {
|
|
158
|
-
const result = applyStagedManagedBundle(ctx.platform.configManager, requestedKeys);
|
|
159
|
-
ctx.session.runtime.model = String(ctx.platform.configManager.get('provider.model'));
|
|
160
|
-
ctx.session.runtime.provider = getProviderIdFromModel(ctx.platform.configManager.get('provider.model'));
|
|
161
|
-
ctx.session.runtime.reasoningEffort = ctx.platform.configManager.get('provider.reasoningEffort') as string;
|
|
162
|
-
ctx.print(`Staged managed settings applied (${result.appliedCount} changes, rollback ${result.rollbackToken}${result.remainingCount > 0 ? `, ${result.remainingCount} still staged` : ''}).`);
|
|
163
|
-
} catch (error) {
|
|
164
|
-
recordSettingsSyncFailure('managed', summarizeError(error), controlPlaneConfigDir);
|
|
165
|
-
ctx.print(summarizeError(error));
|
|
166
|
-
}
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if (sub === 'rollback') {
|
|
171
|
-
const token = commandArgs[1];
|
|
172
|
-
if (!token) {
|
|
173
|
-
ctx.print('Usage: /managed rollback <token> --yes');
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
if (!parsed.yes) {
|
|
177
|
-
requireYesFlag(ctx, `rollback managed settings token ${token}`, '/managed rollback <token> --yes');
|
|
178
|
-
return;
|
|
179
|
-
}
|
|
180
|
-
try {
|
|
181
|
-
const restored = rollbackManagedApply(ctx.platform.configManager, token);
|
|
182
|
-
ctx.session.runtime.model = String(ctx.platform.configManager.get('provider.model'));
|
|
183
|
-
ctx.session.runtime.provider = getProviderIdFromModel(ctx.platform.configManager.get('provider.model'));
|
|
184
|
-
ctx.session.runtime.reasoningEffort = ctx.platform.configManager.get('provider.reasoningEffort') as string;
|
|
185
|
-
ctx.print(`Managed rollback ${token} restored ${restored} setting(s).`);
|
|
186
|
-
} catch (error) {
|
|
187
|
-
recordSettingsSyncFailure('managed', summarizeError(error), controlPlaneConfigDir);
|
|
188
|
-
ctx.print(summarizeError(error));
|
|
189
|
-
}
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const pathArg = commandArgs[1];
|
|
194
|
-
if (!pathArg) {
|
|
195
|
-
ctx.print(`Usage: /managed ${sub} <path>${sub === 'stage' || sub === 'apply' ? ' --yes' : ''}`);
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
const sourcePath = shellPaths.resolveWorkspacePath(pathArg);
|
|
199
|
-
const bundle = JSON.parse(readFileSync(sourcePath, 'utf-8')) as ManagedSettingsBundle;
|
|
200
|
-
|
|
201
|
-
if (sub === 'inspect') {
|
|
202
|
-
ctx.print(inspectManagedSettingsBundle(ctx.platform.configManager, bundle, sourcePath));
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
if (sub === 'stage') {
|
|
207
|
-
if (!parsed.yes) {
|
|
208
|
-
requireYesFlag(ctx, `stage managed settings bundle from ${pathArg}`, '/managed stage <path> --yes');
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
const stage = stageManagedSettingsBundle(ctx.platform.configManager, bundle, sourcePath);
|
|
212
|
-
ctx.print(`Managed settings bundle staged from ${sourcePath} (${stage.changeCount} changes, risk=${stage.risk}).`);
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
if (sub === 'apply') {
|
|
217
|
-
const requestedKeys = commandArgs.slice(2).filter((value): value is ConfigKey => CONFIG_KEYS.has(value as ConfigKey));
|
|
218
|
-
const invalidKeys = commandArgs.slice(2).filter((value) => !CONFIG_KEYS.has(value as ConfigKey));
|
|
219
|
-
if (invalidKeys.length > 0) {
|
|
220
|
-
ctx.print(`Unknown config key(s): ${invalidKeys.join(', ')}`);
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
|
-
if (!parsed.yes) {
|
|
224
|
-
requireYesFlag(ctx, `apply managed settings bundle from ${pathArg}`, '/managed apply <path> [key ...] --yes');
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
stageManagedSettingsBundle(ctx.platform.configManager, bundle, sourcePath);
|
|
228
|
-
const result = applyStagedManagedBundle(ctx.platform.configManager, requestedKeys);
|
|
229
|
-
ctx.session.runtime.model = String(ctx.platform.configManager.get('provider.model'));
|
|
230
|
-
ctx.session.runtime.provider = getProviderIdFromModel(ctx.platform.configManager.get('provider.model'));
|
|
231
|
-
ctx.session.runtime.reasoningEffort = ctx.platform.configManager.get('provider.reasoningEffort') as string;
|
|
232
|
-
ctx.print(`Managed settings bundle applied from ${sourcePath} (${result.appliedCount} changes, rollback ${result.rollbackToken}${result.remainingCount > 0 ? `, ${result.remainingCount} still staged` : ''}).`);
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
recordSettingsSyncFailure('managed', `unsupported subcommand: ${sub}`, controlPlaneConfigDir);
|
|
237
|
-
ctx.print('Usage: /managed [review|staged|rollback-history|export <profile> <path> --yes|inspect <path>|stage <path> --yes|apply <path> [key ...] --yes|apply-staged [key ...] --yes|rollback <token> --yes|lock <key> <source> <reason...> --yes|unlock <key> --yes]');
|
|
238
|
-
},
|
|
239
|
-
});
|
|
240
|
-
}
|