@rayrun/cli 0.3.0 → 0.5.0
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/README.md +198 -15
- package/index.js +4 -1
- package/package.json +9 -4
- package/src/deployments.js +233 -0
- package/src/main.js +26 -0
- package/src/management.js +1193 -4
- package/src/sanitizeTerminalError.js +16 -0
- package/src/skills.js +122 -0
package/src/management.js
CHANGED
|
@@ -1,45 +1,115 @@
|
|
|
1
|
+
import {
|
|
2
|
+
parseSecretBindings,
|
|
3
|
+
readDeploymentDirectory,
|
|
4
|
+
readDeploymentProjectState,
|
|
5
|
+
writeDeploymentProjectState,
|
|
6
|
+
} from './deployments.js';
|
|
7
|
+
import { readSkillDirectory, writeSkillDirectory } from './skills.js';
|
|
1
8
|
/* eslint-disable node/no-process-env -- management intentionally reads the invoking user's Rayrun environment */
|
|
2
9
|
import { Rayrun, RayrunApiError } from '@rayrun/sdk';
|
|
10
|
+
import { diffLines } from 'diff';
|
|
11
|
+
import { createHash } from 'node:crypto';
|
|
12
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
3
13
|
import { stripVTControlCharacters } from 'node:util';
|
|
4
14
|
|
|
5
15
|
export const managementUsage = `Management:
|
|
16
|
+
rayrun deploy [directory] [--connection <uid>] [--name <name>] [--secret <NAME=secret-uid> ...] [--wait] [--json]
|
|
17
|
+
rayrun deployments list [--connection <uid>] [--limit <1-100>] [--json]
|
|
18
|
+
rayrun deployments get <build-uid> [--json]
|
|
19
|
+
rayrun deployments logs <build-uid> [--json]
|
|
20
|
+
rayrun deployments releases <connection-uid> [--limit <1-100>] [--cursor <cursor>] [--json]
|
|
21
|
+
rayrun deployments rollback <connection-uid> <release-uid> [--wait] [--json]
|
|
6
22
|
rayrun connect mcp <url> --name <name> [--transport <streamable-http|sse>] [--json]
|
|
7
23
|
rayrun connect openapi <spec-url> [--name <name>] [--base-url <url>] [--json]
|
|
8
24
|
rayrun connections list [--limit <1-100>] [--cursor <cursor>] [--json]
|
|
25
|
+
rayrun connections history <connection-uid> [--limit <1-100>] [--cursor <cursor>] [--json]
|
|
26
|
+
rayrun connections diff <connection-uid> <version-uid> [--json]
|
|
27
|
+
rayrun connections restore <connection-uid> <version-uid> [--reason <note>] [--json]
|
|
28
|
+
rayrun access-profiles list [--limit <1-100>] [--cursor <cursor>] [--json]
|
|
29
|
+
rayrun access-profiles history <profile-uid> [--limit <1-100>] [--cursor <cursor>] [--json]
|
|
30
|
+
rayrun access-profiles diff <profile-uid> <version-uid> [--json]
|
|
31
|
+
rayrun access-profiles restore <profile-uid> <version-uid> [--reason <note>] [--confirm-risk] [--json]
|
|
32
|
+
rayrun webhooks list [--json]
|
|
33
|
+
rayrun webhooks update <webhook-uid> --config <json> [--reason <note>] [--json]
|
|
34
|
+
rayrun webhooks history <webhook-uid> [--limit <1-100>] [--cursor <cursor>] [--json]
|
|
35
|
+
rayrun webhooks diff <webhook-uid> <version-uid> [--json]
|
|
36
|
+
rayrun webhooks restore <webhook-uid> <version-uid> [--reason <note>] [--json]
|
|
37
|
+
rayrun skills validate <directory> [--json]
|
|
38
|
+
rayrun skills push <directory> [--publish] [--confirm-risk] [--mode <both|code|direct>] [--profiles <uid,uid>|--all-clients] [--reason <note>] [--json]
|
|
39
|
+
rayrun skills list [--json]
|
|
40
|
+
rayrun skills show <skill-uid> [--json]
|
|
41
|
+
rayrun skills history <skill-uid> [--json]
|
|
42
|
+
rayrun skills diff <skill-uid> <version-uid> [--json]
|
|
43
|
+
rayrun skills pull <skill-uid> --output <new-directory> [--version <version-uid>] [--json]
|
|
44
|
+
rayrun skills publish <skill-uid> [--confirm-risk] [--mode <both|code|direct>] [--profiles <uid,uid>|--all-clients] [--reason <note>] [--json]
|
|
45
|
+
rayrun skills audience <skill-uid> [--mode <both|code|direct>] [--profiles <uid,uid>|--all-clients] [--json]
|
|
46
|
+
rayrun skills enable|disable|archive <skill-uid> [--json]
|
|
47
|
+
rayrun skills restore <skill-uid> <version-uid> [--reason <note>] [--json]
|
|
48
|
+
rayrun skills delete <skill-uid> --confirm-name <name> [--json]
|
|
9
49
|
rayrun clients list [--limit <1-100>] [--cursor <cursor>] [--json]
|
|
10
50
|
rayrun tools search <query> [--connection <uid>] [--limit <1-100>] [--cursor <cursor>] [--json]
|
|
11
51
|
rayrun policy inspect <client-uid> [--query <query>] [--limit <1-100>] [--cursor <cursor>] [--json]
|
|
12
52
|
rayrun approvals list [--limit <1-100>] [--cursor <cursor>] [--json]
|
|
13
53
|
rayrun activity list [--limit <1-100>] [--cursor <cursor>] [--json]
|
|
54
|
+
rayrun hooks pull <connection-uid> <tool-uid> [--output <file>] [--types-output <file>] [--json]
|
|
55
|
+
rayrun hooks save <connection-uid> <tool-uid> [--file <file>] [--config <json>] [--reason <note>] [--json]
|
|
56
|
+
rayrun hooks test <connection-uid> <tool-uid> --arguments <json> [--file <file>] [--mock-result <json>] [--config <json>] [--json]
|
|
57
|
+
rayrun hooks deploy <connection-uid> <tool-uid> [--file <file>] [--config <json>] [--reason <note>] [--shadow] [--json]
|
|
58
|
+
rayrun hooks history <connection-uid> <tool-uid> [--limit <1-100>] [--cursor <cursor>] [--json]
|
|
59
|
+
rayrun hooks diff <connection-uid> <tool-uid> <version-uid> [--json]
|
|
60
|
+
rayrun hooks restore <connection-uid> <tool-uid> <version-uid> [--reason <note>] [--json]
|
|
61
|
+
rayrun hooks reset <connection-uid> <tool-uid> --confirm <hook-uid>@<version> --reason <note>
|
|
62
|
+
rayrun hooks rollback <connection-uid> <tool-uid> <revision-uid> [--shadow] [--json]
|
|
63
|
+
rayrun hooks deactivate <connection-uid> <tool-uid> [--shadow] [--json]
|
|
64
|
+
rayrun hooks logs <connection-uid> <tool-uid> [--limit <1-100>] [--cursor <cursor>] [--json]
|
|
14
65
|
|
|
15
66
|
Environment:
|
|
16
67
|
RAYRUN_API_KEY API key created in Dashboard -> Settings -> API keys
|
|
17
68
|
RAYRUN_API_URL Optional API origin (defaults to https://ray.run)`;
|
|
18
69
|
|
|
19
70
|
const managementCommands = new Set([
|
|
71
|
+
'access-profiles',
|
|
20
72
|
'activity',
|
|
21
73
|
'approvals',
|
|
22
74
|
'clients',
|
|
23
75
|
'connect',
|
|
24
76
|
'connections',
|
|
77
|
+
'deploy',
|
|
78
|
+
'deployments',
|
|
79
|
+
'hooks',
|
|
25
80
|
'policy',
|
|
81
|
+
'skills',
|
|
26
82
|
'tools',
|
|
83
|
+
'webhooks',
|
|
27
84
|
]);
|
|
28
85
|
|
|
29
86
|
export const isManagementCommand = (command) => managementCommands.has(command);
|
|
30
87
|
|
|
31
88
|
const optionNames = new Map([
|
|
32
89
|
['--base-url', 'baseUrl'],
|
|
90
|
+
['--arguments', 'argumentsJson'],
|
|
91
|
+
['--config', 'configJson'],
|
|
92
|
+
['--confirm', 'confirmationHookUid'],
|
|
33
93
|
['--connection', 'connectionUid'],
|
|
94
|
+
['--confirm-name', 'confirmationName'],
|
|
34
95
|
['--cursor', 'cursor'],
|
|
35
96
|
['--limit', 'limit'],
|
|
97
|
+
['--file', 'filePath'],
|
|
98
|
+
['--mock-result', 'mockResultJson'],
|
|
36
99
|
['--name', 'name'],
|
|
100
|
+
['--mode', 'mode'],
|
|
101
|
+
['--profiles', 'profiles'],
|
|
37
102
|
['--query', 'query'],
|
|
103
|
+
['--reason', 'reason'],
|
|
104
|
+
['--output', 'outputPath'],
|
|
38
105
|
['--transport', 'transport'],
|
|
106
|
+
['--secret', 'secret'],
|
|
107
|
+
['--version', 'versionUid'],
|
|
108
|
+
['--types-output', 'typesOutputPath'],
|
|
39
109
|
]);
|
|
40
110
|
|
|
41
111
|
const parseCommandArguments = (arguments_, allowedOptions) => {
|
|
42
|
-
const options = { json: false };
|
|
112
|
+
const options = { json: false, secrets: [] };
|
|
43
113
|
const positional = [];
|
|
44
114
|
|
|
45
115
|
for (let index = 0; index < arguments_.length; index += 1) {
|
|
@@ -50,14 +120,43 @@ const parseCommandArguments = (arguments_, allowedOptions) => {
|
|
|
50
120
|
options.json = true;
|
|
51
121
|
continue;
|
|
52
122
|
}
|
|
123
|
+
if (argument === '--shadow') {
|
|
124
|
+
if (!allowedOptions.has('shadow')) throw new Error(`Unknown option: ${argument}`);
|
|
125
|
+
options.shadow = true;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (argument === '--confirm-risk') {
|
|
129
|
+
if (!allowedOptions.has('confirmRisk')) throw new Error(`Unknown option: ${argument}`);
|
|
130
|
+
options.confirmRisk = true;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (argument === '--publish') {
|
|
134
|
+
if (!allowedOptions.has('publish')) throw new Error(`Unknown option: ${argument}`);
|
|
135
|
+
options.publish = true;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (argument === '--wait') {
|
|
139
|
+
if (!allowedOptions.has('wait')) throw new Error(`Unknown option: ${argument}`);
|
|
140
|
+
options.wait = true;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (argument === '--all-clients') {
|
|
144
|
+
if (!allowedOptions.has('allClients')) throw new Error(`Unknown option: ${argument}`);
|
|
145
|
+
options.allClients = true;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
53
148
|
|
|
54
149
|
const optionName = optionNames.get(argument);
|
|
55
150
|
if (optionName) {
|
|
56
151
|
if (!allowedOptions.has(optionName)) throw new Error(`Unknown option: ${argument}`);
|
|
57
152
|
const value = arguments_[index + 1];
|
|
58
153
|
if (!value || value.startsWith('--')) throw new Error(`${argument} requires a value.`);
|
|
59
|
-
if (
|
|
60
|
-
|
|
154
|
+
if (optionName === 'secret') options.secrets.push(value);
|
|
155
|
+
else {
|
|
156
|
+
if (options[optionName] !== undefined)
|
|
157
|
+
throw new Error(`${argument} can only be used once.`);
|
|
158
|
+
options[optionName] = value;
|
|
159
|
+
}
|
|
61
160
|
index += 1;
|
|
62
161
|
continue;
|
|
63
162
|
}
|
|
@@ -104,12 +203,31 @@ const formatCell = (value) => {
|
|
|
104
203
|
if (typeof value === 'boolean') return value ? 'yes' : 'no';
|
|
105
204
|
const sanitized = stripVTControlCharacters(String(value))
|
|
106
205
|
.replaceAll(/\p{Cc}+/gu, ' ')
|
|
206
|
+
.replaceAll(
|
|
207
|
+
/\p{Bidi_Control}/gu,
|
|
208
|
+
(character) => `\\u{${character.codePointAt(0).toString(16).toUpperCase()}}`,
|
|
209
|
+
)
|
|
107
210
|
.replaceAll(/\s+/gu, ' ')
|
|
108
211
|
.trim();
|
|
109
212
|
|
|
110
213
|
return sanitized || '-';
|
|
111
214
|
};
|
|
112
215
|
|
|
216
|
+
const formatTerminalText = (value) => {
|
|
217
|
+
return stripVTControlCharacters(String(value))
|
|
218
|
+
.replaceAll(/\r\n?/gu, '\n')
|
|
219
|
+
.split('\n')
|
|
220
|
+
.map((line) =>
|
|
221
|
+
line
|
|
222
|
+
.replaceAll(/\p{Cc}+/gu, ' ')
|
|
223
|
+
.replaceAll(
|
|
224
|
+
/\p{Bidi_Control}/gu,
|
|
225
|
+
(character) => `\\u{${character.codePointAt(0).toString(16).toUpperCase()}}`,
|
|
226
|
+
),
|
|
227
|
+
)
|
|
228
|
+
.join('\n');
|
|
229
|
+
};
|
|
230
|
+
|
|
113
231
|
export const renderTable = (items, columns) => {
|
|
114
232
|
if (items.length === 0) return 'No results.\n';
|
|
115
233
|
|
|
@@ -150,10 +268,503 @@ const requireShape = (condition) => {
|
|
|
150
268
|
if (!condition) throw new Error(managementUsage);
|
|
151
269
|
};
|
|
152
270
|
|
|
271
|
+
const parseJsonOption = (value, option, fallback) => {
|
|
272
|
+
if (value === undefined) return fallback;
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
return JSON.parse(value);
|
|
276
|
+
} catch {
|
|
277
|
+
throw new Error(`${option} must be valid JSON.`);
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
const hookMode = (options) => (options.shadow ? 'shadow' : 'active');
|
|
282
|
+
|
|
283
|
+
const deploymentFinished = (deployment) =>
|
|
284
|
+
['cancelled', 'failed'].includes(deployment.build.status) ||
|
|
285
|
+
(deployment.release &&
|
|
286
|
+
['active', 'cancelled', 'failed', 'superseded'].includes(deployment.release.status));
|
|
287
|
+
|
|
288
|
+
const waitForDeployment = async (client, { buildUid, connectionUid, releaseUid }) => {
|
|
289
|
+
const deadline = Date.now() + 60 * 60 * 1_000;
|
|
290
|
+
while (Date.now() < deadline) {
|
|
291
|
+
const deployment = await client.deployments.get(buildUid);
|
|
292
|
+
if (releaseUid) {
|
|
293
|
+
const exactRelease = await client.deployments.getRelease(connectionUid, releaseUid);
|
|
294
|
+
const exactDeployment = { ...deployment, release: exactRelease };
|
|
295
|
+
if (deploymentFinished(exactDeployment)) return exactDeployment;
|
|
296
|
+
} else if (deploymentFinished(deployment)) {
|
|
297
|
+
return deployment;
|
|
298
|
+
}
|
|
299
|
+
await new Promise((resolve) => setTimeout(resolve, 2_000));
|
|
300
|
+
}
|
|
301
|
+
throw new Error(
|
|
302
|
+
'Deployment is still running after 60 minutes. Inspect it with rayrun deployments get.',
|
|
303
|
+
);
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
const writeDeployment = ({ deployment, options, output }) => {
|
|
307
|
+
if (options.json) {
|
|
308
|
+
writeValue(output, deployment);
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
const status = deployment.release?.status ?? deployment.build.status;
|
|
312
|
+
const toolSummary = deployment.release?.toolNames.map(formatCell).join(', ');
|
|
313
|
+
output.write(
|
|
314
|
+
`${formatCell(deployment.connection.displayName)}: ${formatCell(status)} (build ${formatCell(deployment.build.uid)}, source r${String(deployment.source.revision)})${toolSummary ? ` — ${toolSummary}` : ''}\n`,
|
|
315
|
+
);
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
const renderLineDiff = (before, after, beforeLabel, afterLabel) => {
|
|
319
|
+
const parts = diffLines(before, after, { timeout: 75 });
|
|
320
|
+
if (!parts) {
|
|
321
|
+
return `--- ${beforeLabel}\n+++ ${afterLabel}\nDiff exceeded the 75 ms safety limit. Use --json to inspect both versions.\n`;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const lines = parts
|
|
325
|
+
.flatMap((part) => {
|
|
326
|
+
const prefix = part.added ? '+' : part.removed ? '-' : ' ';
|
|
327
|
+
const partLines = stripVTControlCharacters(part.value)
|
|
328
|
+
.split('\n')
|
|
329
|
+
.map((line) =>
|
|
330
|
+
line
|
|
331
|
+
.replaceAll(/\p{Cc}+/gu, ' ')
|
|
332
|
+
.replaceAll(
|
|
333
|
+
/\p{Bidi_Control}/gu,
|
|
334
|
+
(character) => `\\u{${character.codePointAt(0).toString(16).toUpperCase()}}`,
|
|
335
|
+
),
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
if (partLines.at(-1) === '') partLines.pop();
|
|
339
|
+
|
|
340
|
+
return partLines.map((line) => `${prefix}${line}`);
|
|
341
|
+
})
|
|
342
|
+
.join('\n');
|
|
343
|
+
|
|
344
|
+
return `--- ${beforeLabel}\n+++ ${afterLabel}\n${lines}\n`;
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
const skillFileSummary = (files) => {
|
|
348
|
+
return files.map((file) => {
|
|
349
|
+
const bytes = Buffer.from(file.contentBase64, 'base64');
|
|
350
|
+
return {
|
|
351
|
+
digest: `sha256:${createHash('sha256').update(bytes).digest('hex')}`,
|
|
352
|
+
path: file.path,
|
|
353
|
+
size: bytes.byteLength,
|
|
354
|
+
};
|
|
355
|
+
});
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
const skillAudience = (options, fallback) => {
|
|
359
|
+
if (options.allClients && options.profiles !== undefined) {
|
|
360
|
+
throw new Error('Use either --profiles or --all-clients, not both.');
|
|
361
|
+
}
|
|
362
|
+
const deliveryMode = options.mode ?? fallback?.deliveryMode ?? 'both';
|
|
363
|
+
if (!['both', 'code', 'direct'].includes(deliveryMode)) {
|
|
364
|
+
throw new Error('--mode must be both, code, or direct.');
|
|
365
|
+
}
|
|
366
|
+
let profileUids;
|
|
367
|
+
if (options.allClients) {
|
|
368
|
+
profileUids = [];
|
|
369
|
+
} else if (options.profiles !== undefined) {
|
|
370
|
+
profileUids = options.profiles
|
|
371
|
+
.split(',')
|
|
372
|
+
.map((uid) => uid.trim())
|
|
373
|
+
.filter(Boolean);
|
|
374
|
+
if (profileUids.length === 0) {
|
|
375
|
+
throw new Error('--profiles requires at least one profile UID.');
|
|
376
|
+
}
|
|
377
|
+
} else if (fallback) {
|
|
378
|
+
profileUids = fallback.profileUids;
|
|
379
|
+
} else {
|
|
380
|
+
throw new Error('Choose an audience with --profiles <uid,uid> or --all-clients.');
|
|
381
|
+
}
|
|
382
|
+
return {
|
|
383
|
+
deliveryMode,
|
|
384
|
+
profileUids,
|
|
385
|
+
};
|
|
386
|
+
};
|
|
387
|
+
|
|
153
388
|
const runCommand = async ({ arguments_, client, output }) => {
|
|
154
389
|
const command = arguments_[0];
|
|
155
390
|
const action = arguments_[1];
|
|
156
391
|
|
|
392
|
+
if (command === 'deploy') {
|
|
393
|
+
const { options, positional } = parseCommandArguments(
|
|
394
|
+
arguments_.slice(1),
|
|
395
|
+
new Set(['connectionUid', 'json', 'name', 'secret', 'wait']),
|
|
396
|
+
);
|
|
397
|
+
requireShape(positional.length <= 1);
|
|
398
|
+
const directory = positional[0] ?? '.';
|
|
399
|
+
const state = options.connectionUid ? null : await readDeploymentProjectState(directory);
|
|
400
|
+
const connectionUid = options.connectionUid ?? state?.connectionUid;
|
|
401
|
+
if (connectionUid && options.name) {
|
|
402
|
+
throw new Error('--name is only available when creating a source deployment.');
|
|
403
|
+
}
|
|
404
|
+
const files = await readDeploymentDirectory(directory);
|
|
405
|
+
await client.deployments.validate(files);
|
|
406
|
+
const created = await client.deployments.create({
|
|
407
|
+
connectionUid,
|
|
408
|
+
displayName: options.name,
|
|
409
|
+
files,
|
|
410
|
+
secretBindings: parseSecretBindings(options.secrets),
|
|
411
|
+
});
|
|
412
|
+
await writeDeploymentProjectState(directory, created.connectionUid);
|
|
413
|
+
if (options.wait) {
|
|
414
|
+
const deployment = await waitForDeployment(client, created);
|
|
415
|
+
writeDeployment({ deployment, options, output });
|
|
416
|
+
if (
|
|
417
|
+
['cancelled', 'failed'].includes(deployment.build.status) ||
|
|
418
|
+
(deployment.release &&
|
|
419
|
+
['cancelled', 'failed', 'superseded'].includes(deployment.release.status))
|
|
420
|
+
) {
|
|
421
|
+
throw new Error(
|
|
422
|
+
`Deployment ${created.buildUid} failed. Run rayrun deployments logs ${created.buildUid}.`,
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
} else if (options.json) writeValue(output, created);
|
|
426
|
+
else {
|
|
427
|
+
output.write(
|
|
428
|
+
created.unchanged
|
|
429
|
+
? `Already active as build ${created.buildUid}.\n`
|
|
430
|
+
: `Queued build ${created.buildUid}. Wait with: rayrun deployments get ${created.buildUid}\n`,
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (command === 'deployments' && action === 'list') {
|
|
437
|
+
const { options, positional } = parseCommandArguments(
|
|
438
|
+
arguments_.slice(2),
|
|
439
|
+
new Set(['connectionUid', 'json', 'limit']),
|
|
440
|
+
);
|
|
441
|
+
requireShape(positional.length === 0);
|
|
442
|
+
const response = await client.deployments.list({
|
|
443
|
+
connectionUid: options.connectionUid,
|
|
444
|
+
limit: options.limit,
|
|
445
|
+
});
|
|
446
|
+
if (options.json) writeValue(output, response);
|
|
447
|
+
else {
|
|
448
|
+
output.write(
|
|
449
|
+
renderTable(response.items, [
|
|
450
|
+
{ label: 'BUILD', value: (item) => item.build.uid },
|
|
451
|
+
{ label: 'SERVICE', value: (item) => item.connection.displayName },
|
|
452
|
+
{ label: 'SOURCE', value: (item) => `r${String(item.source.revision)}` },
|
|
453
|
+
{ label: 'STATUS', value: (item) => item.release?.status ?? item.build.status },
|
|
454
|
+
{ label: 'CREATED', value: (item) => item.build.createdAt },
|
|
455
|
+
]),
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
if (command === 'deployments' && action === 'get') {
|
|
462
|
+
const { options, positional } = parseCommandArguments(arguments_.slice(2), new Set(['json']));
|
|
463
|
+
requireShape(positional.length === 1);
|
|
464
|
+
writeDeployment({ deployment: await client.deployments.get(positional[0]), options, output });
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
if (command === 'deployments' && action === 'logs') {
|
|
469
|
+
const { options, positional } = parseCommandArguments(arguments_.slice(2), new Set(['json']));
|
|
470
|
+
requireShape(positional.length === 1);
|
|
471
|
+
const buildLog = await client.deployments.getLog(positional[0]);
|
|
472
|
+
if (options.json) writeValue(output, buildLog);
|
|
473
|
+
else
|
|
474
|
+
output.write(
|
|
475
|
+
`${buildLog.truncated ? '[older output truncated]\n' : ''}${formatTerminalText(buildLog.text)}\n`,
|
|
476
|
+
);
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
if (command === 'deployments' && action === 'releases') {
|
|
481
|
+
const { options, positional } = parseCommandArguments(
|
|
482
|
+
arguments_.slice(2),
|
|
483
|
+
new Set(['cursor', 'json', 'limit']),
|
|
484
|
+
);
|
|
485
|
+
requireShape(positional.length === 1);
|
|
486
|
+
const response = await client.deployments.listReleases(positional[0], pageQuery(options));
|
|
487
|
+
writePage({
|
|
488
|
+
columns: [
|
|
489
|
+
{ label: 'RELEASE', value: (item) => item.uid },
|
|
490
|
+
{ label: 'NUMBER', value: (item) => item.number },
|
|
491
|
+
{ label: 'SOURCE', value: (item) => `r${String(item.sourceRevision)}` },
|
|
492
|
+
{ label: 'STATUS', value: (item) => item.status },
|
|
493
|
+
{ label: 'ACTIVATED', value: (item) => item.activatedAt },
|
|
494
|
+
],
|
|
495
|
+
options,
|
|
496
|
+
output,
|
|
497
|
+
page: response,
|
|
498
|
+
});
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (command === 'deployments' && action === 'rollback') {
|
|
503
|
+
const { options, positional } = parseCommandArguments(
|
|
504
|
+
arguments_.slice(2),
|
|
505
|
+
new Set(['json', 'wait']),
|
|
506
|
+
);
|
|
507
|
+
requireShape(positional.length === 2);
|
|
508
|
+
const created = await client.deployments.rollback(positional[0], positional[1]);
|
|
509
|
+
if (options.wait) {
|
|
510
|
+
const deadline = Date.now() + 20 * 60 * 1_000;
|
|
511
|
+
let release;
|
|
512
|
+
while (Date.now() < deadline) {
|
|
513
|
+
release = await client.deployments.getRelease(positional[0], created.releaseUid);
|
|
514
|
+
if (['active', 'cancelled', 'failed', 'superseded'].includes(release.status)) break;
|
|
515
|
+
await new Promise((resolve) => setTimeout(resolve, 2_000));
|
|
516
|
+
}
|
|
517
|
+
if (!release || !['active', 'cancelled', 'failed', 'superseded'].includes(release.status)) {
|
|
518
|
+
throw new Error('Rollback is still running after 20 minutes.');
|
|
519
|
+
}
|
|
520
|
+
if (options.json) writeValue(output, release);
|
|
521
|
+
else output.write(`Release ${release.uid}: ${release.status}.\n`);
|
|
522
|
+
if (release.status !== 'active') throw new Error(`Rollback ${release.uid} failed.`);
|
|
523
|
+
} else if (options.json) writeValue(output, created);
|
|
524
|
+
else output.write(`Queued rollback release ${created.releaseUid}.\n`);
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
if (command === 'skills' && action === 'validate') {
|
|
529
|
+
const { options, positional } = parseCommandArguments(arguments_.slice(2), new Set(['json']));
|
|
530
|
+
requireShape(positional.length === 1);
|
|
531
|
+
const result = await client.skills.validate(await readSkillDirectory(positional[0]));
|
|
532
|
+
if (options.json) writeValue(output, result);
|
|
533
|
+
else {
|
|
534
|
+
output.write(
|
|
535
|
+
`Valid ${result.name}: ${String(result.fileCount)} files, ${String(result.byteCount)} bytes${result.riskFlags.length ? `; review ${result.riskFlags.join(', ')}` : ''}.\n`,
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
if (command === 'skills' && action === 'push') {
|
|
542
|
+
const { options, positional } = parseCommandArguments(
|
|
543
|
+
arguments_.slice(2),
|
|
544
|
+
new Set(['allClients', 'confirmRisk', 'json', 'mode', 'profiles', 'publish', 'reason']),
|
|
545
|
+
);
|
|
546
|
+
requireShape(positional.length === 1);
|
|
547
|
+
const files = await readSkillDirectory(positional[0]);
|
|
548
|
+
const validation = await client.skills.validate(files);
|
|
549
|
+
const listed = await client.skills.list();
|
|
550
|
+
const existing = listed.items.find((item) => item.name === validation.name);
|
|
551
|
+
const audience = options.publish
|
|
552
|
+
? skillAudience(
|
|
553
|
+
options,
|
|
554
|
+
existing
|
|
555
|
+
? {
|
|
556
|
+
deliveryMode: existing.deliveryMode,
|
|
557
|
+
profileUids: existing.profiles.map((profile) => profile.uid),
|
|
558
|
+
}
|
|
559
|
+
: undefined,
|
|
560
|
+
)
|
|
561
|
+
: undefined;
|
|
562
|
+
let result = existing
|
|
563
|
+
? await client.skills.saveDraft(existing.uid, {
|
|
564
|
+
changeReason: options.reason,
|
|
565
|
+
expectedVersion: existing.configurationVersion,
|
|
566
|
+
files,
|
|
567
|
+
source: 'cli',
|
|
568
|
+
})
|
|
569
|
+
: await client.skills.create({ changeReason: options.reason, files, source: 'cli' });
|
|
570
|
+
if (options.publish) {
|
|
571
|
+
result = await client.skills.publish(result.skillUid, {
|
|
572
|
+
...audience,
|
|
573
|
+
changeReason: options.reason,
|
|
574
|
+
expectedVersion: result.configurationVersion,
|
|
575
|
+
riskConfirmed: options.confirmRisk,
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
if (options.json) writeValue(output, result);
|
|
579
|
+
else {
|
|
580
|
+
output.write(
|
|
581
|
+
`${options.publish ? 'Published' : result.unchanged ? 'Unchanged' : 'Saved'} ${validation.name} (${result.skillUid}) at configuration v${String(result.configurationVersion)}.\n`,
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
if (command === 'skills' && action === 'list') {
|
|
588
|
+
const { options, positional } = parseCommandArguments(arguments_.slice(2), new Set(['json']));
|
|
589
|
+
requireShape(positional.length === 0);
|
|
590
|
+
const result = await client.skills.list();
|
|
591
|
+
if (options.json) writeValue(output, result);
|
|
592
|
+
else {
|
|
593
|
+
output.write(
|
|
594
|
+
renderTable(result.items, [
|
|
595
|
+
{ label: 'UID', value: (item) => item.uid },
|
|
596
|
+
{ label: 'NAME', value: (item) => item.name },
|
|
597
|
+
{ label: 'STATE', value: (item) => item.state },
|
|
598
|
+
{ label: 'REVISION', value: (item) => item.publishedRevisionNumber },
|
|
599
|
+
{ label: 'MODE', value: (item) => item.deliveryMode },
|
|
600
|
+
{ label: 'FETCHES', value: (item) => item.fetchCount },
|
|
601
|
+
]),
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
if (command === 'skills' && ['history', 'show'].includes(action)) {
|
|
608
|
+
const { options, positional } = parseCommandArguments(arguments_.slice(2), new Set(['json']));
|
|
609
|
+
requireShape(positional.length === 1);
|
|
610
|
+
const detail = await client.skills.get(positional[0]);
|
|
611
|
+
if (options.json || action === 'show') writeValue(output, detail);
|
|
612
|
+
else {
|
|
613
|
+
output.write(
|
|
614
|
+
renderTable(detail.versions, [
|
|
615
|
+
{ label: 'REVISION', value: (item) => item.revisionNumber },
|
|
616
|
+
{ label: 'UID', value: (item) => item.uid },
|
|
617
|
+
{ label: 'WHEN', value: (item) => item.createdAt },
|
|
618
|
+
{ label: 'ACTOR', value: (item) => item.actorName },
|
|
619
|
+
{ label: 'DRAFT', value: (item) => item.isCurrentDraft },
|
|
620
|
+
{ label: 'PUBLISHED', value: (item) => item.isPublished },
|
|
621
|
+
{ label: 'NOTE', value: (item) => item.changeReason },
|
|
622
|
+
]),
|
|
623
|
+
);
|
|
624
|
+
}
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
if (command === 'skills' && action === 'diff') {
|
|
629
|
+
const { options, positional } = parseCommandArguments(arguments_.slice(2), new Set(['json']));
|
|
630
|
+
requireShape(positional.length === 2);
|
|
631
|
+
const [draft, version] = await Promise.all([
|
|
632
|
+
client.skills.export(positional[0]),
|
|
633
|
+
client.skills.exportVersion(positional[0], positional[1]),
|
|
634
|
+
]);
|
|
635
|
+
const comparison = {
|
|
636
|
+
draft: skillFileSummary(draft.files),
|
|
637
|
+
version: skillFileSummary(version.files),
|
|
638
|
+
};
|
|
639
|
+
if (options.json) writeValue(output, comparison);
|
|
640
|
+
else {
|
|
641
|
+
output.write(
|
|
642
|
+
renderLineDiff(
|
|
643
|
+
`${JSON.stringify(comparison.version, null, 2)}\n`,
|
|
644
|
+
`${JSON.stringify(comparison.draft, null, 2)}\n`,
|
|
645
|
+
positional[1],
|
|
646
|
+
'current draft',
|
|
647
|
+
),
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
if (command === 'skills' && action === 'pull') {
|
|
654
|
+
const { options, positional } = parseCommandArguments(
|
|
655
|
+
arguments_.slice(2),
|
|
656
|
+
new Set(['json', 'outputPath', 'versionUid']),
|
|
657
|
+
);
|
|
658
|
+
requireShape(positional.length === 1 && typeof options.outputPath === 'string');
|
|
659
|
+
const result = options.versionUid
|
|
660
|
+
? await client.skills.exportVersion(positional[0], options.versionUid)
|
|
661
|
+
: await client.skills.export(positional[0]);
|
|
662
|
+
await writeSkillDirectory(options.outputPath, result.files);
|
|
663
|
+
if (options.json)
|
|
664
|
+
writeValue(output, { fileCount: result.files.length, output: options.outputPath });
|
|
665
|
+
else output.write(`Wrote ${String(result.files.length)} files to ${options.outputPath}.\n`);
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
if (command === 'skills' && action === 'publish') {
|
|
670
|
+
const { options, positional } = parseCommandArguments(
|
|
671
|
+
arguments_.slice(2),
|
|
672
|
+
new Set(['allClients', 'confirmRisk', 'json', 'mode', 'profiles', 'reason']),
|
|
673
|
+
);
|
|
674
|
+
requireShape(positional.length === 1);
|
|
675
|
+
const detail = await client.skills.get(positional[0]);
|
|
676
|
+
const result = await client.skills.publish(positional[0], {
|
|
677
|
+
...skillAudience(options, {
|
|
678
|
+
deliveryMode: detail.skill.deliveryMode,
|
|
679
|
+
profileUids: detail.skill.profiles.map((profile) => profile.uid),
|
|
680
|
+
}),
|
|
681
|
+
changeReason: options.reason,
|
|
682
|
+
expectedVersion: detail.skill.configurationVersion,
|
|
683
|
+
riskConfirmed: options.confirmRisk,
|
|
684
|
+
});
|
|
685
|
+
if (options.json) writeValue(output, result);
|
|
686
|
+
else
|
|
687
|
+
output.write(
|
|
688
|
+
`Published ${detail.skill.name} at configuration v${String(result.configurationVersion)}.\n`,
|
|
689
|
+
);
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
if (command === 'skills' && action === 'audience') {
|
|
694
|
+
const { options, positional } = parseCommandArguments(
|
|
695
|
+
arguments_.slice(2),
|
|
696
|
+
new Set(['allClients', 'json', 'mode', 'profiles']),
|
|
697
|
+
);
|
|
698
|
+
requireShape(positional.length === 1);
|
|
699
|
+
const detail = await client.skills.get(positional[0]);
|
|
700
|
+
const result = await client.skills.updateAudience(positional[0], {
|
|
701
|
+
...skillAudience(options, {
|
|
702
|
+
deliveryMode: detail.skill.deliveryMode,
|
|
703
|
+
profileUids: detail.skill.profiles.map((profile) => profile.uid),
|
|
704
|
+
}),
|
|
705
|
+
expectedVersion: detail.skill.configurationVersion,
|
|
706
|
+
});
|
|
707
|
+
if (options.json) writeValue(output, result);
|
|
708
|
+
else {
|
|
709
|
+
output.write(
|
|
710
|
+
`${result.unchanged ? 'Kept' : 'Updated'} audience for ${detail.skill.name} at configuration v${String(result.configurationVersion)}.\n`,
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
if (command === 'skills' && ['archive', 'disable', 'enable'].includes(action)) {
|
|
717
|
+
const { options, positional } = parseCommandArguments(arguments_.slice(2), new Set(['json']));
|
|
718
|
+
requireShape(positional.length === 1);
|
|
719
|
+
const detail = await client.skills.get(positional[0]);
|
|
720
|
+
const result =
|
|
721
|
+
action === 'archive'
|
|
722
|
+
? await client.skills.archive(positional[0], detail.skill.configurationVersion)
|
|
723
|
+
: await client.skills.setEnabled(
|
|
724
|
+
positional[0],
|
|
725
|
+
action === 'enable',
|
|
726
|
+
detail.skill.configurationVersion,
|
|
727
|
+
);
|
|
728
|
+
if (options.json) writeValue(output, result);
|
|
729
|
+
else output.write(`${action[0].toUpperCase()}${action.slice(1)}d ${detail.skill.name}.\n`);
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
if (command === 'skills' && action === 'restore') {
|
|
734
|
+
const { options, positional } = parseCommandArguments(
|
|
735
|
+
arguments_.slice(2),
|
|
736
|
+
new Set(['json', 'reason']),
|
|
737
|
+
);
|
|
738
|
+
requireShape(positional.length === 2);
|
|
739
|
+
const detail = await client.skills.get(positional[0]);
|
|
740
|
+
const result = await client.skills.restoreVersion(positional[0], positional[1], {
|
|
741
|
+
changeReason: options.reason,
|
|
742
|
+
expectedVersion: detail.skill.configurationVersion,
|
|
743
|
+
});
|
|
744
|
+
if (options.json) writeValue(output, result);
|
|
745
|
+
else output.write(`Restored ${positional[1]} as a new draft.\n`);
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
if (command === 'skills' && action === 'delete') {
|
|
750
|
+
const { options, positional } = parseCommandArguments(
|
|
751
|
+
arguments_.slice(2),
|
|
752
|
+
new Set(['confirmationName', 'json']),
|
|
753
|
+
);
|
|
754
|
+
requireShape(positional.length === 1 && typeof options.confirmationName === 'string');
|
|
755
|
+
const detail = await client.skills.get(positional[0]);
|
|
756
|
+
const result = await client.skills.delete(positional[0], {
|
|
757
|
+
confirmationName: options.confirmationName,
|
|
758
|
+
expectedVersion: detail.skill.configurationVersion,
|
|
759
|
+
});
|
|
760
|
+
if (options.json) writeValue(output, result);
|
|
761
|
+
else
|
|
762
|
+
output.write(
|
|
763
|
+
`Permanently deleted content for ${detail.skill.name}; its name remains reserved.\n`,
|
|
764
|
+
);
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
|
|
157
768
|
if (command === 'connect' && action === 'mcp') {
|
|
158
769
|
const { options, positional } = parseCommandArguments(
|
|
159
770
|
arguments_.slice(2),
|
|
@@ -213,6 +824,306 @@ const runCommand = async ({ arguments_, client, output }) => {
|
|
|
213
824
|
return;
|
|
214
825
|
}
|
|
215
826
|
|
|
827
|
+
if (command === 'connections' && action === 'history') {
|
|
828
|
+
const { options, positional } = parseCommandArguments(
|
|
829
|
+
arguments_.slice(2),
|
|
830
|
+
new Set(['cursor', 'json', 'limit']),
|
|
831
|
+
);
|
|
832
|
+
requireShape(positional.length === 1);
|
|
833
|
+
const page = await client.connections.listConfigurationVersions(
|
|
834
|
+
positional[0],
|
|
835
|
+
pageQuery(options),
|
|
836
|
+
);
|
|
837
|
+
writePage({
|
|
838
|
+
columns: [
|
|
839
|
+
{ label: 'VERSION', value: (item) => item.revisionNumber },
|
|
840
|
+
{ label: 'UID', value: (item) => item.uid },
|
|
841
|
+
{ label: 'WHEN', value: (item) => item.createdAt },
|
|
842
|
+
{ label: 'ACTOR', value: (item) => `${item.actor.name} (${item.actor.type})` },
|
|
843
|
+
{ label: 'CHANGE', value: (item) => item.changeType },
|
|
844
|
+
{ label: 'NOTE', value: (item) => item.changeReason },
|
|
845
|
+
{ label: 'CURRENT', value: (item) => item.isCurrent },
|
|
846
|
+
],
|
|
847
|
+
options,
|
|
848
|
+
output,
|
|
849
|
+
page,
|
|
850
|
+
});
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
if (command === 'connections' && action === 'diff') {
|
|
855
|
+
const { options, positional } = parseCommandArguments(arguments_.slice(2), new Set(['json']));
|
|
856
|
+
requireShape(positional.length === 2);
|
|
857
|
+
const result = await client.connections.getConfigurationVersion(positional[0], positional[1]);
|
|
858
|
+
if (options.json) {
|
|
859
|
+
writeValue(output, result);
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
const previousLabel = result.version.previous
|
|
863
|
+
? `configuration v${String(result.version.previous.revisionNumber)}`
|
|
864
|
+
: 'empty configuration';
|
|
865
|
+
output.write(
|
|
866
|
+
renderLineDiff(
|
|
867
|
+
`${JSON.stringify(result.version.previous?.snapshot ?? {}, null, 2)}\n`,
|
|
868
|
+
`${JSON.stringify(result.version.snapshot, null, 2)}\n`,
|
|
869
|
+
previousLabel,
|
|
870
|
+
`configuration v${String(result.version.revisionNumber)}`,
|
|
871
|
+
),
|
|
872
|
+
);
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
if (command === 'connections' && action === 'restore') {
|
|
877
|
+
const { options, positional } = parseCommandArguments(
|
|
878
|
+
arguments_.slice(2),
|
|
879
|
+
new Set(['json', 'reason']),
|
|
880
|
+
);
|
|
881
|
+
requireShape(positional.length === 2);
|
|
882
|
+
const history = await client.connections.listConfigurationVersions(positional[0], { limit: 1 });
|
|
883
|
+
const current = history.items[0];
|
|
884
|
+
if (!current?.isCurrent) {
|
|
885
|
+
throw new Error('The current connection configuration could not be read.');
|
|
886
|
+
}
|
|
887
|
+
const result = await client.connections.restoreConfigurationVersion(
|
|
888
|
+
positional[0],
|
|
889
|
+
positional[1],
|
|
890
|
+
{ expectedVersion: current.revisionNumber, reason: options.reason },
|
|
891
|
+
);
|
|
892
|
+
if (options.json) writeValue(output, result);
|
|
893
|
+
else
|
|
894
|
+
output.write(
|
|
895
|
+
`Restored ${positional[1]} as connection configuration v${String(result.configurationVersion)} (${result.versionUid}).\n`,
|
|
896
|
+
);
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
if (command === 'access-profiles' && action === 'list') {
|
|
901
|
+
const { options, positional } = parseCommandArguments(
|
|
902
|
+
arguments_.slice(2),
|
|
903
|
+
new Set(['cursor', 'json', 'limit']),
|
|
904
|
+
);
|
|
905
|
+
requireShape(positional.length === 0);
|
|
906
|
+
const page = await client.accessProfiles.list(pageQuery(options));
|
|
907
|
+
writePage({
|
|
908
|
+
columns: [
|
|
909
|
+
{ label: 'UID', value: (item) => item.uid },
|
|
910
|
+
{ label: 'NAME', value: (item) => item.name },
|
|
911
|
+
{ label: 'POLICY', value: (item) => item.toolAccessMode },
|
|
912
|
+
{ label: 'VERSION', value: (item) => item.toolPolicyVersion },
|
|
913
|
+
{ label: 'CLIENTS', value: (item) => item.assignedClientCount },
|
|
914
|
+
{ label: 'SKILLS', value: (item) => item.assignedSkillCount },
|
|
915
|
+
],
|
|
916
|
+
options,
|
|
917
|
+
output,
|
|
918
|
+
page,
|
|
919
|
+
});
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
if (command === 'access-profiles' && action === 'history') {
|
|
924
|
+
const { options, positional } = parseCommandArguments(
|
|
925
|
+
arguments_.slice(2),
|
|
926
|
+
new Set(['cursor', 'json', 'limit']),
|
|
927
|
+
);
|
|
928
|
+
requireShape(positional.length === 1);
|
|
929
|
+
const page = await client.accessProfiles.listConfigurationVersions(
|
|
930
|
+
positional[0],
|
|
931
|
+
pageQuery(options),
|
|
932
|
+
);
|
|
933
|
+
writePage({
|
|
934
|
+
columns: [
|
|
935
|
+
{ label: 'VERSION', value: (item) => item.revisionNumber },
|
|
936
|
+
{ label: 'UID', value: (item) => item.uid },
|
|
937
|
+
{ label: 'WHEN', value: (item) => item.createdAt },
|
|
938
|
+
{ label: 'ACTOR', value: (item) => `${item.actor.name} (${item.actor.type})` },
|
|
939
|
+
{ label: 'CHANGE', value: (item) => item.changeType },
|
|
940
|
+
{ label: 'NOTE', value: (item) => item.changeReason },
|
|
941
|
+
{ label: 'CURRENT', value: (item) => item.isCurrent },
|
|
942
|
+
],
|
|
943
|
+
options,
|
|
944
|
+
output,
|
|
945
|
+
page,
|
|
946
|
+
});
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
if (command === 'access-profiles' && action === 'diff') {
|
|
951
|
+
const { options, positional } = parseCommandArguments(arguments_.slice(2), new Set(['json']));
|
|
952
|
+
requireShape(positional.length === 2);
|
|
953
|
+
const result = await client.accessProfiles.getConfigurationVersion(
|
|
954
|
+
positional[0],
|
|
955
|
+
positional[1],
|
|
956
|
+
);
|
|
957
|
+
if (options.json) {
|
|
958
|
+
writeValue(output, result);
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
const previousLabel = result.version.previous
|
|
962
|
+
? `profile v${String(result.version.previous.revisionNumber)}`
|
|
963
|
+
: 'empty profile';
|
|
964
|
+
output.write(
|
|
965
|
+
renderLineDiff(
|
|
966
|
+
`${JSON.stringify(result.version.previous?.snapshot ?? {}, null, 2)}\n`,
|
|
967
|
+
`${JSON.stringify(result.version.snapshot, null, 2)}\n`,
|
|
968
|
+
previousLabel,
|
|
969
|
+
`profile v${String(result.version.revisionNumber)}`,
|
|
970
|
+
),
|
|
971
|
+
);
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
if (command === 'access-profiles' && action === 'restore') {
|
|
976
|
+
const { options, positional } = parseCommandArguments(
|
|
977
|
+
arguments_.slice(2),
|
|
978
|
+
new Set(['confirmRisk', 'json', 'reason']),
|
|
979
|
+
);
|
|
980
|
+
requireShape(positional.length === 2);
|
|
981
|
+
const history = await client.accessProfiles.listConfigurationVersions(positional[0], {
|
|
982
|
+
limit: 1,
|
|
983
|
+
});
|
|
984
|
+
const current = history.items[0];
|
|
985
|
+
if (!current?.isCurrent) {
|
|
986
|
+
throw new Error('The current access-profile configuration could not be read.');
|
|
987
|
+
}
|
|
988
|
+
const result = await client.accessProfiles.restoreConfigurationVersion(
|
|
989
|
+
positional[0],
|
|
990
|
+
positional[1],
|
|
991
|
+
{
|
|
992
|
+
expectedVersion: current.revisionNumber,
|
|
993
|
+
reason: options.reason,
|
|
994
|
+
riskConfirmed: options.confirmRisk === true,
|
|
995
|
+
},
|
|
996
|
+
);
|
|
997
|
+
if (options.json) writeValue(output, result);
|
|
998
|
+
else
|
|
999
|
+
output.write(
|
|
1000
|
+
`Restored ${positional[1]} as access-profile configuration v${String(result.toolPolicyVersion)} (${result.versionUid}).\n`,
|
|
1001
|
+
);
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
if (command === 'webhooks' && action === 'list') {
|
|
1006
|
+
const { options, positional } = parseCommandArguments(arguments_.slice(2), new Set(['json']));
|
|
1007
|
+
requireShape(positional.length === 0);
|
|
1008
|
+
const result = await client.webhooks.list();
|
|
1009
|
+
if (options.json) {
|
|
1010
|
+
writeValue(output, result);
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
output.write(
|
|
1014
|
+
renderTable(result.items, [
|
|
1015
|
+
{ label: 'UID', value: (item) => item.uid },
|
|
1016
|
+
{ label: 'DESCRIPTION', value: (item) => item.description },
|
|
1017
|
+
{ label: 'DESTINATION', value: (item) => item.url },
|
|
1018
|
+
{ label: 'ENABLED', value: (item) => item.enabled },
|
|
1019
|
+
{ label: 'VERSION', value: (item) => item.configurationVersion },
|
|
1020
|
+
{ label: 'HEALTH', value: (item) => item.health },
|
|
1021
|
+
]),
|
|
1022
|
+
);
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
if (command === 'webhooks' && action === 'update') {
|
|
1027
|
+
const { options, positional } = parseCommandArguments(
|
|
1028
|
+
arguments_.slice(2),
|
|
1029
|
+
new Set(['configJson', 'json', 'reason']),
|
|
1030
|
+
);
|
|
1031
|
+
requireShape(positional.length === 1 && options.configJson !== undefined);
|
|
1032
|
+
const configuration = parseJsonOption(options.configJson, '--config');
|
|
1033
|
+
if (!configuration || typeof configuration !== 'object' || Array.isArray(configuration)) {
|
|
1034
|
+
throw new Error('--config must be a JSON object.');
|
|
1035
|
+
}
|
|
1036
|
+
const listed = await client.webhooks.list();
|
|
1037
|
+
const current = listed.items.find((webhook) => webhook.uid === positional[0]);
|
|
1038
|
+
if (!current) {
|
|
1039
|
+
throw new Error(`Webhook ${positional[0]} was not found.`);
|
|
1040
|
+
}
|
|
1041
|
+
const result = await client.webhooks.update(positional[0], {
|
|
1042
|
+
...configuration,
|
|
1043
|
+
expectedVersion: current.configurationVersion,
|
|
1044
|
+
reason: options.reason,
|
|
1045
|
+
});
|
|
1046
|
+
if (options.json) writeValue(output, result);
|
|
1047
|
+
else if (result.unchanged)
|
|
1048
|
+
output.write(
|
|
1049
|
+
`No changes; webhook configuration v${String(result.configurationVersion)} is current.\n`,
|
|
1050
|
+
);
|
|
1051
|
+
else
|
|
1052
|
+
output.write(
|
|
1053
|
+
`Saved webhook configuration v${String(result.configurationVersion)} (${result.versionUid}).\n`,
|
|
1054
|
+
);
|
|
1055
|
+
return;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
if (command === 'webhooks' && action === 'history') {
|
|
1059
|
+
const { options, positional } = parseCommandArguments(
|
|
1060
|
+
arguments_.slice(2),
|
|
1061
|
+
new Set(['cursor', 'json', 'limit']),
|
|
1062
|
+
);
|
|
1063
|
+
requireShape(positional.length === 1);
|
|
1064
|
+
const page = await client.webhooks.listConfigurationVersions(positional[0], pageQuery(options));
|
|
1065
|
+
writePage({
|
|
1066
|
+
columns: [
|
|
1067
|
+
{ label: 'VERSION', value: (item) => item.revisionNumber },
|
|
1068
|
+
{ label: 'UID', value: (item) => item.uid },
|
|
1069
|
+
{ label: 'WHEN', value: (item) => item.createdAt },
|
|
1070
|
+
{ label: 'ACTOR', value: (item) => `${item.actor.name} (${item.actor.type})` },
|
|
1071
|
+
{ label: 'CHANGE', value: (item) => item.changeType },
|
|
1072
|
+
{ label: 'NOTE', value: (item) => item.changeReason },
|
|
1073
|
+
{ label: 'CURRENT', value: (item) => item.isCurrent },
|
|
1074
|
+
],
|
|
1075
|
+
options,
|
|
1076
|
+
output,
|
|
1077
|
+
page,
|
|
1078
|
+
});
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
if (command === 'webhooks' && action === 'diff') {
|
|
1083
|
+
const { options, positional } = parseCommandArguments(arguments_.slice(2), new Set(['json']));
|
|
1084
|
+
requireShape(positional.length === 2);
|
|
1085
|
+
const result = await client.webhooks.getConfigurationVersion(positional[0], positional[1]);
|
|
1086
|
+
if (options.json) {
|
|
1087
|
+
writeValue(output, result);
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
const previousLabel = result.version.previous
|
|
1091
|
+
? `webhook v${String(result.version.previous.revisionNumber)}`
|
|
1092
|
+
: 'empty webhook configuration';
|
|
1093
|
+
output.write(
|
|
1094
|
+
renderLineDiff(
|
|
1095
|
+
`${JSON.stringify(result.version.previous?.snapshot ?? {}, null, 2)}\n`,
|
|
1096
|
+
`${JSON.stringify(result.version.snapshot, null, 2)}\n`,
|
|
1097
|
+
previousLabel,
|
|
1098
|
+
`webhook v${String(result.version.revisionNumber)}`,
|
|
1099
|
+
),
|
|
1100
|
+
);
|
|
1101
|
+
return;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
if (command === 'webhooks' && action === 'restore') {
|
|
1105
|
+
const { options, positional } = parseCommandArguments(
|
|
1106
|
+
arguments_.slice(2),
|
|
1107
|
+
new Set(['json', 'reason']),
|
|
1108
|
+
);
|
|
1109
|
+
requireShape(positional.length === 2);
|
|
1110
|
+
const history = await client.webhooks.listConfigurationVersions(positional[0], { limit: 1 });
|
|
1111
|
+
const current = history.items[0];
|
|
1112
|
+
if (!current?.isCurrent) {
|
|
1113
|
+
throw new Error('The current webhook configuration could not be read.');
|
|
1114
|
+
}
|
|
1115
|
+
const result = await client.webhooks.restoreConfigurationVersion(positional[0], positional[1], {
|
|
1116
|
+
expectedVersion: current.revisionNumber,
|
|
1117
|
+
reason: options.reason,
|
|
1118
|
+
});
|
|
1119
|
+
if (options.json) writeValue(output, result);
|
|
1120
|
+
else
|
|
1121
|
+
output.write(
|
|
1122
|
+
`Restored ${positional[1]} as webhook configuration v${String(result.configurationVersion)} (${result.versionUid}).\n`,
|
|
1123
|
+
);
|
|
1124
|
+
return;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
216
1127
|
if (command === 'clients' && action === 'list') {
|
|
217
1128
|
const { options, positional } = parseCommandArguments(
|
|
218
1129
|
arguments_.slice(2),
|
|
@@ -345,6 +1256,274 @@ const runCommand = async ({ arguments_, client, output }) => {
|
|
|
345
1256
|
return;
|
|
346
1257
|
}
|
|
347
1258
|
|
|
1259
|
+
if (command === 'hooks' && action === 'pull') {
|
|
1260
|
+
const { options, positional } = parseCommandArguments(
|
|
1261
|
+
arguments_.slice(2),
|
|
1262
|
+
new Set(['json', 'outputPath', 'typesOutputPath']),
|
|
1263
|
+
);
|
|
1264
|
+
requireShape(positional.length === 2);
|
|
1265
|
+
const { hook } = await client.hooks.get(positional[0], positional[1]);
|
|
1266
|
+
|
|
1267
|
+
if (options.outputPath) await writeFile(options.outputPath, hook.draftSource, 'utf8');
|
|
1268
|
+
if (options.typesOutputPath) await writeFile(options.typesOutputPath, hook.types, 'utf8');
|
|
1269
|
+
if (options.json) writeValue(output, { hook });
|
|
1270
|
+
else if (!options.outputPath) output.write(hook.draftSource);
|
|
1271
|
+
else output.write(`Wrote hook source to ${options.outputPath}.\n`);
|
|
1272
|
+
return;
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
if (command === 'hooks' && action === 'test') {
|
|
1276
|
+
const { options, positional } = parseCommandArguments(
|
|
1277
|
+
arguments_.slice(2),
|
|
1278
|
+
new Set(['argumentsJson', 'configJson', 'filePath', 'json', 'mockResultJson']),
|
|
1279
|
+
);
|
|
1280
|
+
requireShape(positional.length === 2 && options.argumentsJson !== undefined);
|
|
1281
|
+
const { hook } = await client.hooks.get(positional[0], positional[1]);
|
|
1282
|
+
const source = options.filePath ? await readFile(options.filePath, 'utf8') : hook.draftSource;
|
|
1283
|
+
const body = {
|
|
1284
|
+
arguments: parseJsonOption(options.argumentsJson, '--arguments'),
|
|
1285
|
+
config: parseJsonOption(options.configJson, '--config', hook.draftConfig),
|
|
1286
|
+
source,
|
|
1287
|
+
...(options.mockResultJson === undefined
|
|
1288
|
+
? {}
|
|
1289
|
+
: { mockResult: parseJsonOption(options.mockResultJson, '--mock-result') }),
|
|
1290
|
+
};
|
|
1291
|
+
const result = await client.hooks.test(positional[0], positional[1], body);
|
|
1292
|
+
writeValue(output, result);
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
if (command === 'hooks' && action === 'save') {
|
|
1297
|
+
const { options, positional } = parseCommandArguments(
|
|
1298
|
+
arguments_.slice(2),
|
|
1299
|
+
new Set(['configJson', 'filePath', 'json', 'reason']),
|
|
1300
|
+
);
|
|
1301
|
+
requireShape(positional.length === 2);
|
|
1302
|
+
const { hook } = await client.hooks.get(positional[0], positional[1]);
|
|
1303
|
+
const source = options.filePath ? await readFile(options.filePath, 'utf8') : hook.draftSource;
|
|
1304
|
+
const result = await client.hooks.saveDraft(positional[0], positional[1], {
|
|
1305
|
+
config: parseJsonOption(options.configJson, '--config', hook.draftConfig),
|
|
1306
|
+
expectedVersion: hook.version,
|
|
1307
|
+
reason: options.reason,
|
|
1308
|
+
source,
|
|
1309
|
+
});
|
|
1310
|
+
|
|
1311
|
+
if (options.json) writeValue(output, result);
|
|
1312
|
+
else if (result.draftVersion.unchanged)
|
|
1313
|
+
output.write(
|
|
1314
|
+
`No changes; draft v${String(result.draftVersion.revisionNumber)} is current.\n`,
|
|
1315
|
+
);
|
|
1316
|
+
else
|
|
1317
|
+
output.write(
|
|
1318
|
+
`Saved draft v${String(result.draftVersion.revisionNumber)} (${result.draftVersion.uid}).\n`,
|
|
1319
|
+
);
|
|
1320
|
+
return;
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
if (command === 'hooks' && action === 'history') {
|
|
1324
|
+
const { options, positional } = parseCommandArguments(
|
|
1325
|
+
arguments_.slice(2),
|
|
1326
|
+
new Set(['cursor', 'json', 'limit']),
|
|
1327
|
+
);
|
|
1328
|
+
requireShape(positional.length === 2);
|
|
1329
|
+
const page = await client.hooks.listDraftVersions(
|
|
1330
|
+
positional[0],
|
|
1331
|
+
positional[1],
|
|
1332
|
+
pageQuery(options),
|
|
1333
|
+
);
|
|
1334
|
+
writePage({
|
|
1335
|
+
columns: [
|
|
1336
|
+
{ label: 'VERSION', value: (item) => item.revisionNumber },
|
|
1337
|
+
{ label: 'UID', value: (item) => item.uid },
|
|
1338
|
+
{ label: 'WHEN', value: (item) => item.createdAt },
|
|
1339
|
+
{ label: 'ACTOR', value: (item) => `${item.actor.name} (${item.actor.type})` },
|
|
1340
|
+
{ label: 'CHANGE', value: (item) => item.changeType },
|
|
1341
|
+
{ label: 'NOTE', value: (item) => item.changeReason },
|
|
1342
|
+
{ label: 'CURRENT', value: (item) => item.isCurrent },
|
|
1343
|
+
],
|
|
1344
|
+
options,
|
|
1345
|
+
output,
|
|
1346
|
+
page,
|
|
1347
|
+
});
|
|
1348
|
+
return;
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
if (command === 'hooks' && action === 'diff') {
|
|
1352
|
+
const { options, positional } = parseCommandArguments(arguments_.slice(2), new Set(['json']));
|
|
1353
|
+
requireShape(positional.length === 3);
|
|
1354
|
+
const result = await client.hooks.getDraftVersion(positional[0], positional[1], positional[2]);
|
|
1355
|
+
|
|
1356
|
+
if (options.json) {
|
|
1357
|
+
writeValue(output, result);
|
|
1358
|
+
return;
|
|
1359
|
+
}
|
|
1360
|
+
const previousLabel = result.version.previous
|
|
1361
|
+
? `draft v${String(result.version.previous.revisionNumber)}`
|
|
1362
|
+
: 'empty draft';
|
|
1363
|
+
const currentLabel = `draft v${String(result.version.revisionNumber)}`;
|
|
1364
|
+
output.write(
|
|
1365
|
+
renderLineDiff(
|
|
1366
|
+
result.version.previous?.source ?? '',
|
|
1367
|
+
result.version.source,
|
|
1368
|
+
`${previousLabel} source`,
|
|
1369
|
+
`${currentLabel} source`,
|
|
1370
|
+
),
|
|
1371
|
+
);
|
|
1372
|
+
output.write(
|
|
1373
|
+
renderLineDiff(
|
|
1374
|
+
`${JSON.stringify(result.version.previous?.config ?? {}, null, 2)}\n`,
|
|
1375
|
+
`${JSON.stringify(result.version.config, null, 2)}\n`,
|
|
1376
|
+
`${previousLabel} config`,
|
|
1377
|
+
`${currentLabel} config`,
|
|
1378
|
+
),
|
|
1379
|
+
);
|
|
1380
|
+
return;
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
if (command === 'hooks' && action === 'restore') {
|
|
1384
|
+
const { options, positional } = parseCommandArguments(
|
|
1385
|
+
arguments_.slice(2),
|
|
1386
|
+
new Set(['json', 'reason']),
|
|
1387
|
+
);
|
|
1388
|
+
requireShape(positional.length === 3);
|
|
1389
|
+
const { hook } = await client.hooks.get(positional[0], positional[1]);
|
|
1390
|
+
const result = await client.hooks.restoreDraft(positional[0], positional[1], positional[2], {
|
|
1391
|
+
expectedVersion: hook.version,
|
|
1392
|
+
reason: options.reason,
|
|
1393
|
+
});
|
|
1394
|
+
|
|
1395
|
+
if (options.json) writeValue(output, result);
|
|
1396
|
+
else
|
|
1397
|
+
output.write(
|
|
1398
|
+
`Restored ${positional[2]} as draft v${String(result.draftVersion.revisionNumber)} (${result.draftVersion.uid}).\n`,
|
|
1399
|
+
);
|
|
1400
|
+
return;
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
if (command === 'hooks' && action === 'reset') {
|
|
1404
|
+
const { options, positional } = parseCommandArguments(
|
|
1405
|
+
arguments_.slice(2),
|
|
1406
|
+
new Set(['confirmationHookUid', 'reason']),
|
|
1407
|
+
);
|
|
1408
|
+
requireShape(
|
|
1409
|
+
positional.length === 2 &&
|
|
1410
|
+
typeof options.confirmationHookUid === 'string' &&
|
|
1411
|
+
typeof options.reason === 'string',
|
|
1412
|
+
);
|
|
1413
|
+
const separatorIndex = options.confirmationHookUid.lastIndexOf('@');
|
|
1414
|
+
const hookUid = options.confirmationHookUid.slice(0, separatorIndex);
|
|
1415
|
+
const expectedVersion = Number(options.confirmationHookUid.slice(separatorIndex + 1));
|
|
1416
|
+
if (separatorIndex <= 0 || !Number.isInteger(expectedVersion) || expectedVersion <= 0) {
|
|
1417
|
+
throw new Error('--confirm must be the current hook UID and version: <hook-uid>@<version>.');
|
|
1418
|
+
}
|
|
1419
|
+
await client.hooks.reset(positional[0], positional[1], {
|
|
1420
|
+
confirmationHookUid: hookUid,
|
|
1421
|
+
expectedVersion,
|
|
1422
|
+
reason: options.reason,
|
|
1423
|
+
});
|
|
1424
|
+
output.write(`Permanently reset hook ${hookUid} at version ${String(expectedVersion)}.\n`);
|
|
1425
|
+
return;
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
if (command === 'hooks' && action === 'deploy') {
|
|
1429
|
+
const { options, positional } = parseCommandArguments(
|
|
1430
|
+
arguments_.slice(2),
|
|
1431
|
+
new Set(['configJson', 'filePath', 'json', 'reason', 'shadow']),
|
|
1432
|
+
);
|
|
1433
|
+
requireShape(positional.length === 2);
|
|
1434
|
+
const connectionUid = positional[0];
|
|
1435
|
+
const toolUid = positional[1];
|
|
1436
|
+
let { hook } = await client.hooks.get(connectionUid, toolUid);
|
|
1437
|
+
|
|
1438
|
+
if (hook.hookUid === null || options.filePath || options.configJson !== undefined) {
|
|
1439
|
+
const source = options.filePath ? await readFile(options.filePath, 'utf8') : hook.draftSource;
|
|
1440
|
+
const saved = await client.hooks.saveDraft(connectionUid, toolUid, {
|
|
1441
|
+
config: parseJsonOption(options.configJson, '--config', hook.draftConfig),
|
|
1442
|
+
expectedVersion: hook.version,
|
|
1443
|
+
reason: options.reason,
|
|
1444
|
+
source,
|
|
1445
|
+
});
|
|
1446
|
+
hook = saved.hook;
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
const result = await client.hooks.deploy(connectionUid, toolUid, {
|
|
1450
|
+
expectedVersion: hook.version,
|
|
1451
|
+
mode: hookMode(options),
|
|
1452
|
+
});
|
|
1453
|
+
if (options.json) writeValue(output, result);
|
|
1454
|
+
else {
|
|
1455
|
+
const revisionUid =
|
|
1456
|
+
hookMode(options) === 'shadow'
|
|
1457
|
+
? result.hook.shadowRevisionUid
|
|
1458
|
+
: result.hook.activeRevisionUid;
|
|
1459
|
+
output.write(`Deployed ${String(revisionUid)} in ${hookMode(options)} mode.\n`);
|
|
1460
|
+
}
|
|
1461
|
+
return;
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
if (command === 'hooks' && ['deactivate', 'rollback'].includes(action)) {
|
|
1465
|
+
const { options, positional } = parseCommandArguments(
|
|
1466
|
+
arguments_.slice(2),
|
|
1467
|
+
new Set(['json', 'shadow']),
|
|
1468
|
+
);
|
|
1469
|
+
requireShape(positional.length === (action === 'rollback' ? 3 : 2));
|
|
1470
|
+
const { hook } = await client.hooks.get(positional[0], positional[1]);
|
|
1471
|
+
const result = await client.hooks.setDeployment(positional[0], positional[1], {
|
|
1472
|
+
expectedVersion: hook.version,
|
|
1473
|
+
mode: hookMode(options),
|
|
1474
|
+
revisionUid: action === 'rollback' ? positional[2] : null,
|
|
1475
|
+
});
|
|
1476
|
+
if (options.json) writeValue(output, result);
|
|
1477
|
+
else {
|
|
1478
|
+
output.write(
|
|
1479
|
+
action === 'rollback'
|
|
1480
|
+
? `Deployed ${positional[2]} in ${hookMode(options)} mode.\n`
|
|
1481
|
+
: `Deactivated ${hookMode(options)} mode.\n`,
|
|
1482
|
+
);
|
|
1483
|
+
}
|
|
1484
|
+
return;
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
if (command === 'hooks' && action === 'logs') {
|
|
1488
|
+
const { options, positional } = parseCommandArguments(
|
|
1489
|
+
arguments_.slice(2),
|
|
1490
|
+
new Set(['cursor', 'json', 'limit']),
|
|
1491
|
+
);
|
|
1492
|
+
requireShape(positional.length === 2);
|
|
1493
|
+
const page = await client.hooks.listRuns(positional[0], positional[1], pageQuery(options));
|
|
1494
|
+
writePage({
|
|
1495
|
+
columns: [
|
|
1496
|
+
{ label: 'WHEN', value: (item) => item.createdAt },
|
|
1497
|
+
{ label: 'REQUEST', value: (item) => item.requestId },
|
|
1498
|
+
{ label: 'REVISION', value: (item) => item.revisionUid },
|
|
1499
|
+
{ label: 'MODE', value: (item) => (item.shadow ? 'shadow' : 'active') },
|
|
1500
|
+
{ label: 'STAGE', value: (item) => item.stage },
|
|
1501
|
+
{ label: 'OUTCOME', value: (item) => item.outcome },
|
|
1502
|
+
{
|
|
1503
|
+
label: 'CHANGED',
|
|
1504
|
+
value: (item) =>
|
|
1505
|
+
item.differsFromActive === null ? undefined : item.differsFromActive ? 'yes' : 'no',
|
|
1506
|
+
},
|
|
1507
|
+
{ label: 'DURATION', value: (item) => `${String(item.durationMs)}ms` },
|
|
1508
|
+
{
|
|
1509
|
+
label: 'LOGS',
|
|
1510
|
+
value: (item) =>
|
|
1511
|
+
item.logs
|
|
1512
|
+
?.map(
|
|
1513
|
+
(entry) =>
|
|
1514
|
+
`[${entry.level}] ${entry.message}${entry.data === undefined ? '' : ` ${JSON.stringify(entry.data)}`}`,
|
|
1515
|
+
)
|
|
1516
|
+
.join(' | ') ?? 'not captured',
|
|
1517
|
+
},
|
|
1518
|
+
{ label: 'ERROR', value: (item) => item.errorMessage },
|
|
1519
|
+
],
|
|
1520
|
+
options,
|
|
1521
|
+
output,
|
|
1522
|
+
page,
|
|
1523
|
+
});
|
|
1524
|
+
return;
|
|
1525
|
+
}
|
|
1526
|
+
|
|
348
1527
|
throw new Error(managementUsage);
|
|
349
1528
|
};
|
|
350
1529
|
|
|
@@ -372,6 +1551,16 @@ export const runManagementCommand = async (
|
|
|
372
1551
|
} catch (error) {
|
|
373
1552
|
if (!(error instanceof RayrunApiError)) throw error;
|
|
374
1553
|
const requestId = error.requestId ? `, request ${error.requestId}` : '';
|
|
375
|
-
|
|
1554
|
+
const diagnosticTarget = error.code === 'invalid_source' ? 'source' : 'hook.ts';
|
|
1555
|
+
const diagnostics = error.diagnostics
|
|
1556
|
+
.map(
|
|
1557
|
+
(entry) =>
|
|
1558
|
+
`${diagnosticTarget}${entry.line === undefined ? '' : `:${String(entry.line)}${entry.column === undefined ? '' : `:${String(entry.column)}`}`}: ${entry.message}`,
|
|
1559
|
+
)
|
|
1560
|
+
.join('\n');
|
|
1561
|
+
throw new Error(
|
|
1562
|
+
`${error.message} (${error.code}${requestId})${diagnostics ? `\n${diagnostics}` : ''}`,
|
|
1563
|
+
{ cause: error },
|
|
1564
|
+
);
|
|
376
1565
|
}
|
|
377
1566
|
};
|