@getmagical/cli 0.1.3
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 +46 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2220 -0
- package/dist/index.js.map +1 -0
- package/package.json +43 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2220 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli-formatting.ts
|
|
4
|
+
import { inspect, stripVTControlCharacters, styleText } from "util";
|
|
5
|
+
import { Help } from "commander";
|
|
6
|
+
var EMPTY_VALUE = "-";
|
|
7
|
+
var TABLE_GAP = " ";
|
|
8
|
+
function applyStyle(value, style, enabled = true) {
|
|
9
|
+
if (!enabled) {
|
|
10
|
+
return value;
|
|
11
|
+
}
|
|
12
|
+
return styleText(style, value);
|
|
13
|
+
}
|
|
14
|
+
function indent(text, level = 2) {
|
|
15
|
+
const padding = " ".repeat(level);
|
|
16
|
+
return text.split("\n").map((line) => `${padding}${line}`).join("\n");
|
|
17
|
+
}
|
|
18
|
+
function stripAnsi(value) {
|
|
19
|
+
return stripVTControlCharacters(value);
|
|
20
|
+
}
|
|
21
|
+
function displayWidth(value) {
|
|
22
|
+
return stripAnsi(value).length;
|
|
23
|
+
}
|
|
24
|
+
function padCell(value, width) {
|
|
25
|
+
return value.padEnd(width + value.length - displayWidth(value));
|
|
26
|
+
}
|
|
27
|
+
function formatHeading(title) {
|
|
28
|
+
return applyStyle(title, ["bold", "cyan"]);
|
|
29
|
+
}
|
|
30
|
+
function formatLabel(label) {
|
|
31
|
+
return applyStyle(label, "dim");
|
|
32
|
+
}
|
|
33
|
+
function formatCommandSnippet(command) {
|
|
34
|
+
return applyStyle(command, ["bold", "cyan"]);
|
|
35
|
+
}
|
|
36
|
+
function formatFlag(flag) {
|
|
37
|
+
return applyStyle(flag, ["bold", "yellow"]);
|
|
38
|
+
}
|
|
39
|
+
function formatErrorLabel(label) {
|
|
40
|
+
return applyStyle(label, ["bold", "red"], process.stderr.isTTY);
|
|
41
|
+
}
|
|
42
|
+
function splitWords(value) {
|
|
43
|
+
const tokens = [];
|
|
44
|
+
for (const word of value.replaceAll("_", " ").replaceAll("-", " ").split(" ")) {
|
|
45
|
+
if (word.length === 0) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
let currentToken = word[0] ?? "";
|
|
49
|
+
for (const character of word.slice(1)) {
|
|
50
|
+
const previousCharacter = currentToken.at(-1) ?? "";
|
|
51
|
+
const shouldBreak = character >= "A" && character <= "Z" && (previousCharacter >= "a" && previousCharacter <= "z" || previousCharacter >= "0" && previousCharacter <= "9");
|
|
52
|
+
if (shouldBreak) {
|
|
53
|
+
tokens.push(currentToken);
|
|
54
|
+
currentToken = character;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
currentToken += character;
|
|
58
|
+
}
|
|
59
|
+
tokens.push(currentToken);
|
|
60
|
+
}
|
|
61
|
+
return tokens;
|
|
62
|
+
}
|
|
63
|
+
function startCase(value) {
|
|
64
|
+
return splitWords(value).map((segment) => {
|
|
65
|
+
const lowerSegment = segment.toLowerCase();
|
|
66
|
+
if (lowerSegment === "id") {
|
|
67
|
+
return "ID";
|
|
68
|
+
}
|
|
69
|
+
if (lowerSegment === "url") {
|
|
70
|
+
return "URL";
|
|
71
|
+
}
|
|
72
|
+
if (lowerSegment === "api") {
|
|
73
|
+
return "API";
|
|
74
|
+
}
|
|
75
|
+
return segment.charAt(0).toUpperCase() + segment.slice(1);
|
|
76
|
+
}).join(" ");
|
|
77
|
+
}
|
|
78
|
+
function formatScalar(value) {
|
|
79
|
+
if (value === null || value === void 0) {
|
|
80
|
+
return EMPTY_VALUE;
|
|
81
|
+
}
|
|
82
|
+
if (typeof value === "string") {
|
|
83
|
+
return value.length === 0 ? EMPTY_VALUE : value;
|
|
84
|
+
}
|
|
85
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
86
|
+
return String(value);
|
|
87
|
+
}
|
|
88
|
+
return inspect(value, {
|
|
89
|
+
breakLength: Infinity,
|
|
90
|
+
colors: false,
|
|
91
|
+
compact: true,
|
|
92
|
+
depth: 2
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
function isRecord(value) {
|
|
96
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
97
|
+
}
|
|
98
|
+
function isPrimitiveLike(value) {
|
|
99
|
+
return value === null || value === void 0 || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
100
|
+
}
|
|
101
|
+
function formatTable(options) {
|
|
102
|
+
const widths = options.columns.map(
|
|
103
|
+
(column, columnIndex) => Math.max(
|
|
104
|
+
displayWidth(column),
|
|
105
|
+
...options.rows.map((row) => displayWidth(row[columnIndex] ?? ""))
|
|
106
|
+
)
|
|
107
|
+
);
|
|
108
|
+
const renderRow = (cells) => cells.map((cell, columnIndex) => padCell(cell, widths[columnIndex] ?? 0)).join(TABLE_GAP).trimEnd();
|
|
109
|
+
const divider = widths.map((width) => "-".repeat(width)).join(TABLE_GAP);
|
|
110
|
+
return [
|
|
111
|
+
renderRow(options.columns.map((column) => applyStyle(column, "bold"))),
|
|
112
|
+
divider,
|
|
113
|
+
...options.rows.map(renderRow)
|
|
114
|
+
].join("\n");
|
|
115
|
+
}
|
|
116
|
+
function canRenderRecordArrayAsTable(items) {
|
|
117
|
+
if (items.length === 0 || !items.every(isRecord)) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
const keys = [...new Set(items.flatMap((item) => Object.keys(item)))];
|
|
121
|
+
if (keys.length === 0 || keys.length > 8) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
return items.every((item) => keys.every((key) => isPrimitiveLike(item[key])));
|
|
125
|
+
}
|
|
126
|
+
function formatRecord(record) {
|
|
127
|
+
const entries = Object.entries(record);
|
|
128
|
+
if (entries.length === 0) {
|
|
129
|
+
return EMPTY_VALUE;
|
|
130
|
+
}
|
|
131
|
+
const labelWidth = Math.max(
|
|
132
|
+
...entries.map(([key]) => displayWidth(startCase(key)))
|
|
133
|
+
);
|
|
134
|
+
return entries.map(([key, value]) => {
|
|
135
|
+
const label = `${startCase(key)}:`;
|
|
136
|
+
if (isPrimitiveLike(value)) {
|
|
137
|
+
return `${padCell(formatLabel(label), labelWidth + 1)} ${formatScalar(value)}`;
|
|
138
|
+
}
|
|
139
|
+
return `${formatLabel(label)}
|
|
140
|
+
${indent(formatHumanReadableOutput(value))}`;
|
|
141
|
+
}).join("\n");
|
|
142
|
+
}
|
|
143
|
+
function formatArray(items) {
|
|
144
|
+
if (items.length === 0) {
|
|
145
|
+
return "No results.";
|
|
146
|
+
}
|
|
147
|
+
if (items.every(isPrimitiveLike)) {
|
|
148
|
+
return items.map((item) => `- ${formatScalar(item)}`).join("\n");
|
|
149
|
+
}
|
|
150
|
+
if (canRenderRecordArrayAsTable(items)) {
|
|
151
|
+
const keys = [...new Set(items.flatMap((item) => Object.keys(item)))];
|
|
152
|
+
return formatTable({
|
|
153
|
+
columns: keys.map((key) => startCase(key)),
|
|
154
|
+
rows: items.map((item) => keys.map((key) => formatScalar(item[key])))
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
return items.map((item, index) => {
|
|
158
|
+
const title = formatHeading(`Item ${index + 1}`);
|
|
159
|
+
return `${title}
|
|
160
|
+
${indent(formatHumanReadableOutput(item))}`;
|
|
161
|
+
}).join("\n\n");
|
|
162
|
+
}
|
|
163
|
+
function renderKeyValueBlock(entries) {
|
|
164
|
+
const labelWidth = Math.max(...entries.map(([label]) => displayWidth(label)));
|
|
165
|
+
return entries.map(
|
|
166
|
+
([label, value]) => `${padCell(formatLabel(`${label}:`), labelWidth + 1)} ${value}`
|
|
167
|
+
).join("\n");
|
|
168
|
+
}
|
|
169
|
+
function renderSection(title, bodyLines) {
|
|
170
|
+
return [formatHeading(title), "", ...bodyLines].join("\n");
|
|
171
|
+
}
|
|
172
|
+
var MagicalHelp = class extends Help {
|
|
173
|
+
outputHasColors = process.stdout.isTTY;
|
|
174
|
+
constructor() {
|
|
175
|
+
super();
|
|
176
|
+
this.helpWidth = 96;
|
|
177
|
+
this.showGlobalOptions = true;
|
|
178
|
+
this.sortSubcommands = true;
|
|
179
|
+
}
|
|
180
|
+
prepareContext(contextOptions) {
|
|
181
|
+
super.prepareContext(contextOptions);
|
|
182
|
+
this.outputHasColors = contextOptions.outputHasColors ?? process.stdout.isTTY;
|
|
183
|
+
}
|
|
184
|
+
styleTitle(str) {
|
|
185
|
+
return applyStyle(str, ["bold", "cyan"], this.outputHasColors);
|
|
186
|
+
}
|
|
187
|
+
styleCommandText(str) {
|
|
188
|
+
return applyStyle(str, ["bold", "cyan"], this.outputHasColors);
|
|
189
|
+
}
|
|
190
|
+
styleSubcommandText(str) {
|
|
191
|
+
return applyStyle(str, ["bold", "cyan"], this.outputHasColors);
|
|
192
|
+
}
|
|
193
|
+
styleOptionText(str) {
|
|
194
|
+
return applyStyle(str, ["bold", "yellow"], this.outputHasColors);
|
|
195
|
+
}
|
|
196
|
+
styleArgumentText(str) {
|
|
197
|
+
return applyStyle(str, ["bold", "green"], this.outputHasColors);
|
|
198
|
+
}
|
|
199
|
+
styleDescriptionText(str) {
|
|
200
|
+
return applyStyle(str, "dim", this.outputHasColors);
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
function formatAuthLoginPrompt(options) {
|
|
204
|
+
return renderSection("Sign In With Your Browser", [
|
|
205
|
+
renderKeyValueBlock([
|
|
206
|
+
["Open", options.verificationUrl],
|
|
207
|
+
["Code", options.userCode]
|
|
208
|
+
])
|
|
209
|
+
]);
|
|
210
|
+
}
|
|
211
|
+
function formatAuthLoginSuccess(options) {
|
|
212
|
+
return renderSection("Signed In", [
|
|
213
|
+
renderKeyValueBlock([
|
|
214
|
+
["Account", options.account.email ?? options.account.userId],
|
|
215
|
+
["Organizations", String(options.organizationCount)],
|
|
216
|
+
[
|
|
217
|
+
"Default org",
|
|
218
|
+
options.defaultOrganization ? `${options.defaultOrganization.name} (${options.defaultOrganization.id})` : "None selected"
|
|
219
|
+
]
|
|
220
|
+
])
|
|
221
|
+
]);
|
|
222
|
+
}
|
|
223
|
+
function formatBrowserOpenFallback() {
|
|
224
|
+
return "Browser auto-open failed. Paste the URL above into any browser.";
|
|
225
|
+
}
|
|
226
|
+
function formatLogoutSuccess() {
|
|
227
|
+
return renderSection("Signed Out", ["Saved CLI auth state was cleared."]);
|
|
228
|
+
}
|
|
229
|
+
function formatOrganizationList(options) {
|
|
230
|
+
if (options.organizations.length === 0) {
|
|
231
|
+
return renderSection("Organizations", ["No organizations are available."]);
|
|
232
|
+
}
|
|
233
|
+
const rows = options.organizations.map((organization) => {
|
|
234
|
+
const status = [
|
|
235
|
+
options.defaultOrgId === organization.id ? "default" : null,
|
|
236
|
+
options.lastUsedOrgId === organization.id ? "last used" : null
|
|
237
|
+
].filter((value) => value !== null).join(", ");
|
|
238
|
+
return [
|
|
239
|
+
status || EMPTY_VALUE,
|
|
240
|
+
organization.name,
|
|
241
|
+
organization.id,
|
|
242
|
+
organization.primaryDomain ?? EMPTY_VALUE
|
|
243
|
+
];
|
|
244
|
+
});
|
|
245
|
+
return renderSection("Organizations", [
|
|
246
|
+
formatTable({
|
|
247
|
+
columns: ["Status", "Name", "ID", "Primary domain"],
|
|
248
|
+
rows
|
|
249
|
+
})
|
|
250
|
+
]);
|
|
251
|
+
}
|
|
252
|
+
function formatDefaultOrganizationSelection(organization) {
|
|
253
|
+
return renderSection("Default Organization Updated", [
|
|
254
|
+
`${organization.name} (${organization.id})`
|
|
255
|
+
]);
|
|
256
|
+
}
|
|
257
|
+
function formatHumanReadableOutput(value) {
|
|
258
|
+
if (isPrimitiveLike(value)) {
|
|
259
|
+
return formatScalar(value);
|
|
260
|
+
}
|
|
261
|
+
if (Array.isArray(value)) {
|
|
262
|
+
return formatArray(value);
|
|
263
|
+
}
|
|
264
|
+
if (isRecord(value)) {
|
|
265
|
+
return formatRecord(value);
|
|
266
|
+
}
|
|
267
|
+
return inspect(value, {
|
|
268
|
+
colors: false,
|
|
269
|
+
depth: null
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
function formatCliError(message) {
|
|
273
|
+
return `${formatErrorLabel("Error:")} ${message}`;
|
|
274
|
+
}
|
|
275
|
+
function formatExamples(title, examples) {
|
|
276
|
+
return [
|
|
277
|
+
formatHeading(title),
|
|
278
|
+
"",
|
|
279
|
+
...examples.map((example) => ` ${formatCommandSnippet(example)}`)
|
|
280
|
+
].join("\n");
|
|
281
|
+
}
|
|
282
|
+
function formatOutputModesNote() {
|
|
283
|
+
return renderSection("Output", [
|
|
284
|
+
`Human-readable by default.`,
|
|
285
|
+
`Add ${formatFlag("--json")} before or after a command for stable machine-readable output.`
|
|
286
|
+
]);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// src/errors.ts
|
|
290
|
+
var CliError = class extends Error {
|
|
291
|
+
constructor(message, options) {
|
|
292
|
+
super(message, options);
|
|
293
|
+
this.name = "CliError";
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
function getErrorMessage(error) {
|
|
297
|
+
if (error instanceof Error) {
|
|
298
|
+
return error.message;
|
|
299
|
+
}
|
|
300
|
+
return "Unknown CLI error";
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// src/program.ts
|
|
304
|
+
import { spawn } from "child_process";
|
|
305
|
+
|
|
306
|
+
// ../mcp-registry/src/operation-specs.ts
|
|
307
|
+
import { z } from "zod";
|
|
308
|
+
|
|
309
|
+
// ../mcp-registry/src/operation-spec.ts
|
|
310
|
+
function stripLeadingGraphqlComments(document) {
|
|
311
|
+
return document.replace(/^(?:\s*#.*\n)+/u, "").trimStart();
|
|
312
|
+
}
|
|
313
|
+
function assertDocumentMatchesOperation({
|
|
314
|
+
document,
|
|
315
|
+
kind,
|
|
316
|
+
operationName
|
|
317
|
+
}) {
|
|
318
|
+
const normalizedDocument = stripLeadingGraphqlComments(document);
|
|
319
|
+
const match = /^(?<actualKind>query|mutation)\s+(?<actualOperationName>[_A-Za-z][_0-9A-Za-z]*)/u.exec(
|
|
320
|
+
normalizedDocument
|
|
321
|
+
);
|
|
322
|
+
const actualKind = match?.groups?.["actualKind"];
|
|
323
|
+
const actualOperationName = match?.groups?.["actualOperationName"];
|
|
324
|
+
if (!actualKind || !actualOperationName) {
|
|
325
|
+
throw new Error(
|
|
326
|
+
`Operation "${operationName}" is missing a top-level GraphQL ${kind} definition.`
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
if (actualKind !== kind || actualOperationName !== operationName) {
|
|
330
|
+
throw new Error(
|
|
331
|
+
`Operation "${operationName}" does not match its GraphQL document (${actualKind} ${actualOperationName}).`
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
function createOperationSpec(spec) {
|
|
336
|
+
assertDocumentMatchesOperation(spec);
|
|
337
|
+
return spec;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// ../mcp-registry/src/operation-specs.ts
|
|
341
|
+
function stringOption({
|
|
342
|
+
description,
|
|
343
|
+
flagName,
|
|
344
|
+
inputKey,
|
|
345
|
+
required = false
|
|
346
|
+
}) {
|
|
347
|
+
return {
|
|
348
|
+
description,
|
|
349
|
+
flagName,
|
|
350
|
+
inputKey,
|
|
351
|
+
required,
|
|
352
|
+
valueType: "string"
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
function numberOption({
|
|
356
|
+
description,
|
|
357
|
+
flagName,
|
|
358
|
+
inputKey,
|
|
359
|
+
required = false
|
|
360
|
+
}) {
|
|
361
|
+
return {
|
|
362
|
+
description,
|
|
363
|
+
flagName,
|
|
364
|
+
inputKey,
|
|
365
|
+
required,
|
|
366
|
+
valueType: "number"
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
function booleanOption({
|
|
370
|
+
description,
|
|
371
|
+
flagName,
|
|
372
|
+
inputKey
|
|
373
|
+
}) {
|
|
374
|
+
return {
|
|
375
|
+
description,
|
|
376
|
+
flagName,
|
|
377
|
+
inputKey,
|
|
378
|
+
valueType: "boolean"
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
function stringArrayOption({
|
|
382
|
+
description,
|
|
383
|
+
flagName,
|
|
384
|
+
inputKey
|
|
385
|
+
}) {
|
|
386
|
+
return {
|
|
387
|
+
description,
|
|
388
|
+
flagName,
|
|
389
|
+
inputKey,
|
|
390
|
+
valueHint: "comma-separated",
|
|
391
|
+
valueType: "string-array"
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
function jsonOption({
|
|
395
|
+
description,
|
|
396
|
+
flagName,
|
|
397
|
+
inputKey,
|
|
398
|
+
required = false
|
|
399
|
+
}) {
|
|
400
|
+
return {
|
|
401
|
+
description,
|
|
402
|
+
flagName,
|
|
403
|
+
inputKey,
|
|
404
|
+
required,
|
|
405
|
+
valueHint: "JSON",
|
|
406
|
+
valueType: "json"
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
var jsonObjectSchema = z.record(z.string(), z.unknown());
|
|
410
|
+
var operationSpecs = [
|
|
411
|
+
createOperationSpec({
|
|
412
|
+
operationName: "GetAgent",
|
|
413
|
+
kind: "query",
|
|
414
|
+
inputSchema: z.object({
|
|
415
|
+
id: z.string().min(1)
|
|
416
|
+
}),
|
|
417
|
+
cli: {
|
|
418
|
+
name: "get-agent",
|
|
419
|
+
path: ["agents", "get"],
|
|
420
|
+
description: "Fetch a single agent with config and schema fields.",
|
|
421
|
+
resultKey: "agent",
|
|
422
|
+
options: [
|
|
423
|
+
stringOption({
|
|
424
|
+
inputKey: "id",
|
|
425
|
+
flagName: "id",
|
|
426
|
+
description: "Agent ID.",
|
|
427
|
+
required: true
|
|
428
|
+
})
|
|
429
|
+
]
|
|
430
|
+
},
|
|
431
|
+
mcp: { fileName: "agent.graphql" },
|
|
432
|
+
document: `# Query to fetch a single agent with all fields
|
|
433
|
+
# Config selection is limited to scalar/JSON fields to keep responses flat
|
|
434
|
+
query GetAgent($id: ID!) {
|
|
435
|
+
agent(id: $id) {
|
|
436
|
+
id
|
|
437
|
+
name
|
|
438
|
+
description
|
|
439
|
+
instructions
|
|
440
|
+
organizationId
|
|
441
|
+
createdAt
|
|
442
|
+
updatedAt
|
|
443
|
+
sourceAgentId
|
|
444
|
+
inputSchema
|
|
445
|
+
outputSchema
|
|
446
|
+
config {
|
|
447
|
+
type
|
|
448
|
+
language
|
|
449
|
+
model
|
|
450
|
+
tools
|
|
451
|
+
maxSessionMessages
|
|
452
|
+
commonParametersSchema
|
|
453
|
+
taskQueue
|
|
454
|
+
waitUponQueueing
|
|
455
|
+
queueableAgentIds
|
|
456
|
+
additionalSystemPrompt
|
|
457
|
+
ignoreUnstableSessionState
|
|
458
|
+
}
|
|
459
|
+
childAgentIds
|
|
460
|
+
}
|
|
461
|
+
}`
|
|
462
|
+
}),
|
|
463
|
+
createOperationSpec({
|
|
464
|
+
operationName: "GetAgentRun",
|
|
465
|
+
kind: "query",
|
|
466
|
+
inputSchema: z.object({
|
|
467
|
+
id: z.string().min(1)
|
|
468
|
+
}),
|
|
469
|
+
cli: {
|
|
470
|
+
name: "get-agent-run",
|
|
471
|
+
path: ["agent-runs", "get"],
|
|
472
|
+
description: "Fetch a single agent run.",
|
|
473
|
+
resultKey: "agentRun",
|
|
474
|
+
options: [
|
|
475
|
+
stringOption({
|
|
476
|
+
inputKey: "id",
|
|
477
|
+
flagName: "id",
|
|
478
|
+
description: "Agent run ID.",
|
|
479
|
+
required: true
|
|
480
|
+
})
|
|
481
|
+
]
|
|
482
|
+
},
|
|
483
|
+
mcp: { fileName: "agentRun.graphql" },
|
|
484
|
+
document: `# Query a single agent run
|
|
485
|
+
query GetAgentRun($id: ID!) {
|
|
486
|
+
agentRun(id: $id) {
|
|
487
|
+
id
|
|
488
|
+
agentVersionId
|
|
489
|
+
agentName
|
|
490
|
+
status
|
|
491
|
+
statusDetail
|
|
492
|
+
isChildAgent
|
|
493
|
+
input
|
|
494
|
+
startedAt
|
|
495
|
+
finishedAt
|
|
496
|
+
parentAgentRunId
|
|
497
|
+
}
|
|
498
|
+
}`
|
|
499
|
+
}),
|
|
500
|
+
createOperationSpec({
|
|
501
|
+
operationName: "GetAutomationAgents",
|
|
502
|
+
kind: "query",
|
|
503
|
+
inputSchema: z.object({
|
|
504
|
+
id: z.string().min(1)
|
|
505
|
+
}),
|
|
506
|
+
cli: {
|
|
507
|
+
name: "get-automation-agents",
|
|
508
|
+
path: ["automations", "agents"],
|
|
509
|
+
description: "Fetch the agents attached to an automation.",
|
|
510
|
+
resultKey: "automation",
|
|
511
|
+
options: [
|
|
512
|
+
stringOption({
|
|
513
|
+
inputKey: "id",
|
|
514
|
+
flagName: "id",
|
|
515
|
+
description: "Automation ID.",
|
|
516
|
+
required: true
|
|
517
|
+
})
|
|
518
|
+
]
|
|
519
|
+
},
|
|
520
|
+
mcp: { fileName: "automationAgents.graphql" },
|
|
521
|
+
document: `# Query to fetch automation agents with flat fields only
|
|
522
|
+
# Excludes nested agent config and schemas for MCP tool simplicity
|
|
523
|
+
query GetAutomationAgents($id: ID!) {
|
|
524
|
+
automation(id: $id) {
|
|
525
|
+
id
|
|
526
|
+
name
|
|
527
|
+
agents {
|
|
528
|
+
automationId
|
|
529
|
+
agentId
|
|
530
|
+
isTrigger
|
|
531
|
+
agent {
|
|
532
|
+
id
|
|
533
|
+
name
|
|
534
|
+
description
|
|
535
|
+
instructions
|
|
536
|
+
organizationId
|
|
537
|
+
createdAt
|
|
538
|
+
updatedAt
|
|
539
|
+
sourceAgentId
|
|
540
|
+
childAgentIds
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}`
|
|
545
|
+
}),
|
|
546
|
+
createOperationSpec({
|
|
547
|
+
operationName: "GetAutomationRun",
|
|
548
|
+
kind: "query",
|
|
549
|
+
inputSchema: z.object({
|
|
550
|
+
id: z.string().min(1)
|
|
551
|
+
}),
|
|
552
|
+
cli: {
|
|
553
|
+
name: "get-automation-run",
|
|
554
|
+
path: ["automation-runs", "get"],
|
|
555
|
+
description: "Fetch a single automation run.",
|
|
556
|
+
resultKey: "automationRun",
|
|
557
|
+
options: [
|
|
558
|
+
stringOption({
|
|
559
|
+
inputKey: "id",
|
|
560
|
+
flagName: "id",
|
|
561
|
+
description: "Automation run ID.",
|
|
562
|
+
required: true
|
|
563
|
+
})
|
|
564
|
+
]
|
|
565
|
+
},
|
|
566
|
+
mcp: { fileName: "automationRun.graphql" },
|
|
567
|
+
document: `# Query a full automation run details payload
|
|
568
|
+
query GetAutomationRun($id: ID!) {
|
|
569
|
+
automationRun(id: $id) {
|
|
570
|
+
id
|
|
571
|
+
automationId
|
|
572
|
+
status
|
|
573
|
+
statusDetail
|
|
574
|
+
summary
|
|
575
|
+
invocationType
|
|
576
|
+
input
|
|
577
|
+
additionalPrompt
|
|
578
|
+
debugUrl
|
|
579
|
+
createdAt
|
|
580
|
+
startedAt
|
|
581
|
+
pausedAt
|
|
582
|
+
finishedAt
|
|
583
|
+
}
|
|
584
|
+
}`
|
|
585
|
+
}),
|
|
586
|
+
createOperationSpec({
|
|
587
|
+
operationName: "GetAutomationRunAgentRuns",
|
|
588
|
+
kind: "query",
|
|
589
|
+
inputSchema: z.object({
|
|
590
|
+
automationRunId: z.string().min(1)
|
|
591
|
+
}),
|
|
592
|
+
cli: {
|
|
593
|
+
name: "get-automation-run-agent-runs",
|
|
594
|
+
path: ["automation-runs", "agent-runs"],
|
|
595
|
+
description: "Fetch ordered agent runs for an automation run.",
|
|
596
|
+
resultKey: "automationRunAgentRuns",
|
|
597
|
+
options: [
|
|
598
|
+
stringOption({
|
|
599
|
+
inputKey: "automationRunId",
|
|
600
|
+
flagName: "automation-run-id",
|
|
601
|
+
description: "Automation run ID.",
|
|
602
|
+
required: true
|
|
603
|
+
})
|
|
604
|
+
]
|
|
605
|
+
},
|
|
606
|
+
mcp: { fileName: "automationRunAgentRuns.graphql" },
|
|
607
|
+
document: `# Query ordered agent runs for an automation run
|
|
608
|
+
query GetAutomationRunAgentRuns($automationRunId: ID!) {
|
|
609
|
+
automationRunAgentRuns(automationRunId: $automationRunId) {
|
|
610
|
+
id
|
|
611
|
+
agentVersionId
|
|
612
|
+
agentName
|
|
613
|
+
status
|
|
614
|
+
statusDetail
|
|
615
|
+
isChildAgent
|
|
616
|
+
input
|
|
617
|
+
startedAt
|
|
618
|
+
finishedAt
|
|
619
|
+
parentAgentRunId
|
|
620
|
+
}
|
|
621
|
+
}`
|
|
622
|
+
}),
|
|
623
|
+
createOperationSpec({
|
|
624
|
+
operationName: "GetAutomationRunIterations",
|
|
625
|
+
kind: "query",
|
|
626
|
+
inputSchema: z.object({
|
|
627
|
+
automationRunId: z.string().min(1),
|
|
628
|
+
agentRunId: z.string().min(1).optional(),
|
|
629
|
+
limit: z.number().int().positive().optional(),
|
|
630
|
+
reverse: z.boolean().optional()
|
|
631
|
+
}),
|
|
632
|
+
cli: {
|
|
633
|
+
name: "get-automation-run-iterations",
|
|
634
|
+
path: ["automation-runs", "iterations"],
|
|
635
|
+
description: "Fetch ordered iterations for an automation run.",
|
|
636
|
+
resultKey: "automationRunIterations",
|
|
637
|
+
options: [
|
|
638
|
+
stringOption({
|
|
639
|
+
inputKey: "automationRunId",
|
|
640
|
+
flagName: "automation-run-id",
|
|
641
|
+
description: "Automation run ID.",
|
|
642
|
+
required: true
|
|
643
|
+
}),
|
|
644
|
+
stringOption({
|
|
645
|
+
inputKey: "agentRunId",
|
|
646
|
+
flagName: "agent-run-id",
|
|
647
|
+
description: "Optional agent run ID to filter by."
|
|
648
|
+
}),
|
|
649
|
+
numberOption({
|
|
650
|
+
inputKey: "limit",
|
|
651
|
+
flagName: "limit",
|
|
652
|
+
description: "Maximum number of iterations to return."
|
|
653
|
+
}),
|
|
654
|
+
booleanOption({
|
|
655
|
+
inputKey: "reverse",
|
|
656
|
+
flagName: "reverse",
|
|
657
|
+
description: "Return newest iterations first."
|
|
658
|
+
})
|
|
659
|
+
]
|
|
660
|
+
},
|
|
661
|
+
mcp: { fileName: "automationRunIterations.graphql" },
|
|
662
|
+
document: `# Query iterations for an automation run with optional filtering by agent run
|
|
663
|
+
query GetAutomationRunIterations(
|
|
664
|
+
$automationRunId: ID!
|
|
665
|
+
$agentRunId: ID
|
|
666
|
+
$limit: Int
|
|
667
|
+
$reverse: Boolean
|
|
668
|
+
) {
|
|
669
|
+
automationRunIterations(
|
|
670
|
+
automationRunId: $automationRunId
|
|
671
|
+
agentRunId: $agentRunId
|
|
672
|
+
limit: $limit
|
|
673
|
+
reverse: $reverse
|
|
674
|
+
) {
|
|
675
|
+
id
|
|
676
|
+
sequence
|
|
677
|
+
agentRunId
|
|
678
|
+
metadata
|
|
679
|
+
startedAt
|
|
680
|
+
finishedAt
|
|
681
|
+
totalDurationMs
|
|
682
|
+
llmResponseDurationMs
|
|
683
|
+
withMarksScreenshotUrl
|
|
684
|
+
withMarksMimeType
|
|
685
|
+
memories {
|
|
686
|
+
id
|
|
687
|
+
type
|
|
688
|
+
value
|
|
689
|
+
sequence
|
|
690
|
+
screenshotUrl
|
|
691
|
+
screenshotMimeType
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}`
|
|
695
|
+
}),
|
|
696
|
+
createOperationSpec({
|
|
697
|
+
operationName: "GetAutomationRuns",
|
|
698
|
+
kind: "query",
|
|
699
|
+
inputSchema: z.object({
|
|
700
|
+
automationId: z.string().min(1),
|
|
701
|
+
status: z.array(z.string().min(1)).optional(),
|
|
702
|
+
invocationType: z.array(z.string().min(1)).optional(),
|
|
703
|
+
limit: z.number().int().positive().optional(),
|
|
704
|
+
page: z.number().int().positive().optional()
|
|
705
|
+
}),
|
|
706
|
+
cli: {
|
|
707
|
+
name: "get-automation-runs",
|
|
708
|
+
path: ["automation-runs", "list"],
|
|
709
|
+
description: "Fetch automation run history.",
|
|
710
|
+
resultKey: "automationRuns",
|
|
711
|
+
options: [
|
|
712
|
+
stringOption({
|
|
713
|
+
inputKey: "automationId",
|
|
714
|
+
flagName: "automation-id",
|
|
715
|
+
description: "Automation ID.",
|
|
716
|
+
required: true
|
|
717
|
+
}),
|
|
718
|
+
stringArrayOption({
|
|
719
|
+
inputKey: "status",
|
|
720
|
+
flagName: "status",
|
|
721
|
+
description: "Optional run statuses."
|
|
722
|
+
}),
|
|
723
|
+
stringArrayOption({
|
|
724
|
+
inputKey: "invocationType",
|
|
725
|
+
flagName: "invocation-type",
|
|
726
|
+
description: "Optional invocation types."
|
|
727
|
+
}),
|
|
728
|
+
numberOption({
|
|
729
|
+
inputKey: "limit",
|
|
730
|
+
flagName: "limit",
|
|
731
|
+
description: "Maximum number of runs to return."
|
|
732
|
+
}),
|
|
733
|
+
numberOption({
|
|
734
|
+
inputKey: "page",
|
|
735
|
+
flagName: "page",
|
|
736
|
+
description: "Page number."
|
|
737
|
+
})
|
|
738
|
+
]
|
|
739
|
+
},
|
|
740
|
+
mcp: { fileName: "automationRuns.graphql" },
|
|
741
|
+
document: `# Query automation run history for a specific automation with optional filters
|
|
742
|
+
query GetAutomationRuns(
|
|
743
|
+
$automationId: ID!
|
|
744
|
+
$status: [RunStatus!]
|
|
745
|
+
$invocationType: [InvocationType!]
|
|
746
|
+
$limit: Int
|
|
747
|
+
$page: Int
|
|
748
|
+
) {
|
|
749
|
+
automationRuns(
|
|
750
|
+
automationId: $automationId
|
|
751
|
+
status: $status
|
|
752
|
+
invocationType: $invocationType
|
|
753
|
+
limit: $limit
|
|
754
|
+
page: $page
|
|
755
|
+
) {
|
|
756
|
+
id
|
|
757
|
+
automationId
|
|
758
|
+
status
|
|
759
|
+
statusDetail
|
|
760
|
+
invocationType
|
|
761
|
+
summary
|
|
762
|
+
createdAt
|
|
763
|
+
startedAt
|
|
764
|
+
pausedAt
|
|
765
|
+
finishedAt
|
|
766
|
+
}
|
|
767
|
+
}`
|
|
768
|
+
}),
|
|
769
|
+
createOperationSpec({
|
|
770
|
+
operationName: "GetAutomations",
|
|
771
|
+
kind: "query",
|
|
772
|
+
inputSchema: z.object({
|
|
773
|
+
limit: z.number().positive().optional(),
|
|
774
|
+
order: jsonObjectSchema.optional(),
|
|
775
|
+
where: jsonObjectSchema.optional()
|
|
776
|
+
}),
|
|
777
|
+
cli: {
|
|
778
|
+
name: "get-automations",
|
|
779
|
+
path: ["automations", "list"],
|
|
780
|
+
description: "Fetch automations with optional list filters.",
|
|
781
|
+
resultKey: "automations",
|
|
782
|
+
options: [
|
|
783
|
+
numberOption({
|
|
784
|
+
inputKey: "limit",
|
|
785
|
+
flagName: "limit",
|
|
786
|
+
description: "Maximum number of automations to return."
|
|
787
|
+
}),
|
|
788
|
+
jsonOption({
|
|
789
|
+
inputKey: "order",
|
|
790
|
+
flagName: "order-json",
|
|
791
|
+
description: "GraphQL order object."
|
|
792
|
+
}),
|
|
793
|
+
jsonOption({
|
|
794
|
+
inputKey: "where",
|
|
795
|
+
flagName: "where-json",
|
|
796
|
+
description: "GraphQL filter object."
|
|
797
|
+
})
|
|
798
|
+
]
|
|
799
|
+
},
|
|
800
|
+
mcp: { fileName: "automations.graphql" },
|
|
801
|
+
document: `# Query to fetch automations with flat fields only (no nested objects)
|
|
802
|
+
# Excludes config and agents for MCP tool simplicity
|
|
803
|
+
query GetAutomations(
|
|
804
|
+
$limit: Float
|
|
805
|
+
$order: AutomationsOrder
|
|
806
|
+
$where: AutomationsWhereInput
|
|
807
|
+
) {
|
|
808
|
+
automations(limit: $limit, order: $order, where: $where) {
|
|
809
|
+
id
|
|
810
|
+
name
|
|
811
|
+
organizationId
|
|
812
|
+
createdAt
|
|
813
|
+
updatedAt
|
|
814
|
+
environment
|
|
815
|
+
lastSavedAt
|
|
816
|
+
publishedVersionId
|
|
817
|
+
draftVersionId
|
|
818
|
+
}
|
|
819
|
+
}`
|
|
820
|
+
}),
|
|
821
|
+
createOperationSpec({
|
|
822
|
+
operationName: "GetAutomationWithConfigs",
|
|
823
|
+
kind: "query",
|
|
824
|
+
inputSchema: z.object({
|
|
825
|
+
id: z.string().min(1)
|
|
826
|
+
}),
|
|
827
|
+
cli: {
|
|
828
|
+
name: "get-automation-with-configs",
|
|
829
|
+
path: ["automations", "get"],
|
|
830
|
+
description: "Fetch a single automation with config fields.",
|
|
831
|
+
resultKey: "automation",
|
|
832
|
+
options: [
|
|
833
|
+
stringOption({
|
|
834
|
+
inputKey: "id",
|
|
835
|
+
flagName: "id",
|
|
836
|
+
description: "Automation ID.",
|
|
837
|
+
required: true
|
|
838
|
+
})
|
|
839
|
+
]
|
|
840
|
+
},
|
|
841
|
+
mcp: { fileName: "automationWithConfigs.graphql" },
|
|
842
|
+
document: `# Query to fetch a single automation with config but excluding agents
|
|
843
|
+
# Config selection is limited to scalar/JSON fields to keep responses flat
|
|
844
|
+
query GetAutomationWithConfigs($id: ID!) {
|
|
845
|
+
automation(id: $id) {
|
|
846
|
+
id
|
|
847
|
+
name
|
|
848
|
+
organizationId
|
|
849
|
+
createdAt
|
|
850
|
+
updatedAt
|
|
851
|
+
environment
|
|
852
|
+
lastSavedAt
|
|
853
|
+
publishedVersionId
|
|
854
|
+
draftVersionId
|
|
855
|
+
config {
|
|
856
|
+
cronExpression
|
|
857
|
+
calendars
|
|
858
|
+
interval
|
|
859
|
+
enableVisualChangeChecks
|
|
860
|
+
isCDPforSOM
|
|
861
|
+
humanInterventionTimeoutSeconds
|
|
862
|
+
availableSlots
|
|
863
|
+
additionalSystemPrompt
|
|
864
|
+
ignoreUnstableSessionState
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
}`
|
|
868
|
+
}),
|
|
869
|
+
createOperationSpec({
|
|
870
|
+
operationName: "GetAvailableCommands",
|
|
871
|
+
kind: "query",
|
|
872
|
+
inputSchema: z.object({
|
|
873
|
+
env: z.string().min(1)
|
|
874
|
+
}),
|
|
875
|
+
cli: {
|
|
876
|
+
name: "get-available-commands",
|
|
877
|
+
path: ["catalog", "commands"],
|
|
878
|
+
description: "Fetch available command definitions by environment.",
|
|
879
|
+
resultKey: "availableCommands",
|
|
880
|
+
options: [
|
|
881
|
+
stringOption({
|
|
882
|
+
inputKey: "env",
|
|
883
|
+
flagName: "env",
|
|
884
|
+
description: "Environment name.",
|
|
885
|
+
required: true
|
|
886
|
+
})
|
|
887
|
+
]
|
|
888
|
+
},
|
|
889
|
+
mcp: { fileName: "availableCommands.graphql" },
|
|
890
|
+
document: `# Query to fetch available command definitions by environment
|
|
891
|
+
query GetAvailableCommands($env: AvailableCommandsEnv!) {
|
|
892
|
+
availableCommands(env: $env) {
|
|
893
|
+
id
|
|
894
|
+
description
|
|
895
|
+
inputSchema
|
|
896
|
+
}
|
|
897
|
+
}`
|
|
898
|
+
}),
|
|
899
|
+
createOperationSpec({
|
|
900
|
+
operationName: "GetAvailableModels",
|
|
901
|
+
kind: "query",
|
|
902
|
+
inputSchema: z.object({
|
|
903
|
+
agentId: z.string().min(1)
|
|
904
|
+
}),
|
|
905
|
+
cli: {
|
|
906
|
+
name: "get-available-models",
|
|
907
|
+
path: ["catalog", "models"],
|
|
908
|
+
description: "Fetch available model definitions for an agent.",
|
|
909
|
+
resultKey: "availableModels",
|
|
910
|
+
options: [
|
|
911
|
+
stringOption({
|
|
912
|
+
inputKey: "agentId",
|
|
913
|
+
flagName: "agent-id",
|
|
914
|
+
description: "Agent ID.",
|
|
915
|
+
required: true
|
|
916
|
+
})
|
|
917
|
+
]
|
|
918
|
+
},
|
|
919
|
+
mcp: { fileName: "availableModels.graphql" },
|
|
920
|
+
document: `# Query to fetch available model definitions and pools for a specific agent
|
|
921
|
+
query GetAvailableModels($agentId: String!) {
|
|
922
|
+
availableModels(agentId: $agentId) {
|
|
923
|
+
availableModelPools {
|
|
924
|
+
name
|
|
925
|
+
poolId
|
|
926
|
+
models {
|
|
927
|
+
id
|
|
928
|
+
name
|
|
929
|
+
description
|
|
930
|
+
status
|
|
931
|
+
provider
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
allAvailableModels {
|
|
935
|
+
id
|
|
936
|
+
name
|
|
937
|
+
description
|
|
938
|
+
status
|
|
939
|
+
provider
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
}`
|
|
943
|
+
}),
|
|
944
|
+
createOperationSpec({
|
|
945
|
+
operationName: "GetAvailableTools",
|
|
946
|
+
kind: "query",
|
|
947
|
+
inputSchema: z.object({
|
|
948
|
+
env: z.string().min(1),
|
|
949
|
+
automationId: z.string().min(1).optional()
|
|
950
|
+
}),
|
|
951
|
+
cli: {
|
|
952
|
+
name: "get-available-tools",
|
|
953
|
+
path: ["catalog", "tools"],
|
|
954
|
+
description: "Fetch available tool definitions by environment.",
|
|
955
|
+
resultKey: "availableTools",
|
|
956
|
+
options: [
|
|
957
|
+
stringOption({
|
|
958
|
+
inputKey: "env",
|
|
959
|
+
flagName: "env",
|
|
960
|
+
description: "Environment name.",
|
|
961
|
+
required: true
|
|
962
|
+
}),
|
|
963
|
+
stringOption({
|
|
964
|
+
inputKey: "automationId",
|
|
965
|
+
flagName: "automation-id",
|
|
966
|
+
description: "Optional automation ID."
|
|
967
|
+
})
|
|
968
|
+
]
|
|
969
|
+
},
|
|
970
|
+
mcp: { fileName: "availableTools.graphql" },
|
|
971
|
+
document: `# Query to fetch available tool definitions by environment
|
|
972
|
+
query GetAvailableTools($env: AvailableCommandsEnv!, $automationId: String) {
|
|
973
|
+
availableTools(env: $env, automationId: $automationId) {
|
|
974
|
+
id
|
|
975
|
+
description
|
|
976
|
+
inputSchema
|
|
977
|
+
}
|
|
978
|
+
}`
|
|
979
|
+
}),
|
|
980
|
+
createOperationSpec({
|
|
981
|
+
operationName: "CreateAutomation",
|
|
982
|
+
kind: "mutation",
|
|
983
|
+
inputSchema: z.object({
|
|
984
|
+
input: jsonObjectSchema
|
|
985
|
+
}),
|
|
986
|
+
cli: {
|
|
987
|
+
name: "create-automation",
|
|
988
|
+
path: ["automations", "create"],
|
|
989
|
+
description: "Create a draft automation.",
|
|
990
|
+
resultKey: "createAutomation",
|
|
991
|
+
options: [
|
|
992
|
+
jsonOption({
|
|
993
|
+
inputKey: "input",
|
|
994
|
+
flagName: "input-json",
|
|
995
|
+
description: "CreateAutomationInput payload.",
|
|
996
|
+
required: true
|
|
997
|
+
})
|
|
998
|
+
]
|
|
999
|
+
},
|
|
1000
|
+
mcp: { fileName: "createAutomation.graphql" },
|
|
1001
|
+
document: `# Mutation to create a draft automation with optional initial values
|
|
1002
|
+
# Returns flat fields only for MCP tool simplicity
|
|
1003
|
+
mutation CreateAutomation($input: CreateAutomationInput!) {
|
|
1004
|
+
createAutomation(input: $input) {
|
|
1005
|
+
id
|
|
1006
|
+
name
|
|
1007
|
+
organizationId
|
|
1008
|
+
createdAt
|
|
1009
|
+
updatedAt
|
|
1010
|
+
environment
|
|
1011
|
+
lastSavedAt
|
|
1012
|
+
publishedVersionId
|
|
1013
|
+
draftVersionId
|
|
1014
|
+
}
|
|
1015
|
+
}`
|
|
1016
|
+
}),
|
|
1017
|
+
createOperationSpec({
|
|
1018
|
+
operationName: "GetGraphqlSchema",
|
|
1019
|
+
kind: "query",
|
|
1020
|
+
inputSchema: z.object({}),
|
|
1021
|
+
cli: {
|
|
1022
|
+
name: "get-graphql-schema",
|
|
1023
|
+
path: ["graphql", "schema"],
|
|
1024
|
+
description: "Fetch GraphQL schema metadata.",
|
|
1025
|
+
resultKey: "graphqlSchema",
|
|
1026
|
+
options: []
|
|
1027
|
+
},
|
|
1028
|
+
mcp: { fileName: "graphqlSchema.graphql" },
|
|
1029
|
+
document: `# Query to fetch full GraphQL schema metadata.
|
|
1030
|
+
# Uses the first-class graphqlSchema query field to avoid __schema validation
|
|
1031
|
+
# issues in MCP tool-load paths while still returning introspection JSON.
|
|
1032
|
+
# Response can be large: call sparingly (typically once per task/session) and
|
|
1033
|
+
# reuse cached results.
|
|
1034
|
+
query GetGraphqlSchema {
|
|
1035
|
+
graphqlSchema
|
|
1036
|
+
}`
|
|
1037
|
+
}),
|
|
1038
|
+
createOperationSpec({
|
|
1039
|
+
operationName: "PauseAutomationRun",
|
|
1040
|
+
kind: "mutation",
|
|
1041
|
+
inputSchema: z.object({
|
|
1042
|
+
automationRunId: z.string().min(1)
|
|
1043
|
+
}),
|
|
1044
|
+
cli: {
|
|
1045
|
+
name: "pause-automation-run",
|
|
1046
|
+
path: ["automation-runs", "pause"],
|
|
1047
|
+
description: "Pause an automation run.",
|
|
1048
|
+
resultKey: "pauseAutomationRun",
|
|
1049
|
+
options: [
|
|
1050
|
+
stringOption({
|
|
1051
|
+
inputKey: "automationRunId",
|
|
1052
|
+
flagName: "automation-run-id",
|
|
1053
|
+
description: "Automation run ID.",
|
|
1054
|
+
required: true
|
|
1055
|
+
})
|
|
1056
|
+
]
|
|
1057
|
+
},
|
|
1058
|
+
mcp: { fileName: "pauseAutomationRun.graphql" },
|
|
1059
|
+
document: `# Mutation to pause an automation run
|
|
1060
|
+
mutation PauseAutomationRun($automationRunId: ID!) {
|
|
1061
|
+
pauseAutomationRun(automationRunId: $automationRunId)
|
|
1062
|
+
}`
|
|
1063
|
+
}),
|
|
1064
|
+
createOperationSpec({
|
|
1065
|
+
operationName: "RerunAutomation",
|
|
1066
|
+
kind: "mutation",
|
|
1067
|
+
inputSchema: z.object({
|
|
1068
|
+
automationRunId: z.string().min(1),
|
|
1069
|
+
additionalPrompt: z.string().min(1).optional(),
|
|
1070
|
+
idempotencyKey: z.string().min(1)
|
|
1071
|
+
}),
|
|
1072
|
+
cli: {
|
|
1073
|
+
name: "rerun-automation",
|
|
1074
|
+
path: ["automation-runs", "rerun"],
|
|
1075
|
+
description: "Rerun an existing automation run.",
|
|
1076
|
+
resultKey: "rerunAutomation",
|
|
1077
|
+
options: [
|
|
1078
|
+
stringOption({
|
|
1079
|
+
inputKey: "automationRunId",
|
|
1080
|
+
flagName: "automation-run-id",
|
|
1081
|
+
description: "Automation run ID.",
|
|
1082
|
+
required: true
|
|
1083
|
+
}),
|
|
1084
|
+
stringOption({
|
|
1085
|
+
inputKey: "additionalPrompt",
|
|
1086
|
+
flagName: "additional-prompt",
|
|
1087
|
+
description: "Optional prompt appended to the rerun."
|
|
1088
|
+
}),
|
|
1089
|
+
stringOption({
|
|
1090
|
+
inputKey: "idempotencyKey",
|
|
1091
|
+
flagName: "idempotency-key",
|
|
1092
|
+
description: "Idempotency key for the rerun request.",
|
|
1093
|
+
required: true
|
|
1094
|
+
})
|
|
1095
|
+
]
|
|
1096
|
+
},
|
|
1097
|
+
mcp: { fileName: "rerunAutomation.graphql" },
|
|
1098
|
+
document: `# Mutation to rerun an existing automation run
|
|
1099
|
+
mutation RerunAutomation(
|
|
1100
|
+
$automationRunId: ID!
|
|
1101
|
+
$additionalPrompt: String
|
|
1102
|
+
$idempotencyKey: String!
|
|
1103
|
+
) {
|
|
1104
|
+
rerunAutomation(
|
|
1105
|
+
automationRunId: $automationRunId
|
|
1106
|
+
additionalPrompt: $additionalPrompt
|
|
1107
|
+
idempotencyKey: $idempotencyKey
|
|
1108
|
+
) {
|
|
1109
|
+
automationRunId
|
|
1110
|
+
workflowId
|
|
1111
|
+
workflowRunId
|
|
1112
|
+
}
|
|
1113
|
+
}`
|
|
1114
|
+
}),
|
|
1115
|
+
createOperationSpec({
|
|
1116
|
+
operationName: "ResumeAutomationRun",
|
|
1117
|
+
kind: "mutation",
|
|
1118
|
+
inputSchema: z.object({
|
|
1119
|
+
automationRunId: z.string().min(1),
|
|
1120
|
+
message: z.string().min(1).optional()
|
|
1121
|
+
}),
|
|
1122
|
+
cli: {
|
|
1123
|
+
name: "resume-automation-run",
|
|
1124
|
+
path: ["automation-runs", "resume"],
|
|
1125
|
+
description: "Resume a paused automation run.",
|
|
1126
|
+
resultKey: "resumeAutomationRun",
|
|
1127
|
+
options: [
|
|
1128
|
+
stringOption({
|
|
1129
|
+
inputKey: "automationRunId",
|
|
1130
|
+
flagName: "automation-run-id",
|
|
1131
|
+
description: "Automation run ID.",
|
|
1132
|
+
required: true
|
|
1133
|
+
}),
|
|
1134
|
+
stringOption({
|
|
1135
|
+
inputKey: "message",
|
|
1136
|
+
flagName: "message",
|
|
1137
|
+
description: "Optional resume message."
|
|
1138
|
+
})
|
|
1139
|
+
]
|
|
1140
|
+
},
|
|
1141
|
+
mcp: { fileName: "resumeAutomationRun.graphql" },
|
|
1142
|
+
document: `# Mutation to resume an automation run
|
|
1143
|
+
mutation ResumeAutomationRun($automationRunId: ID!, $message: String) {
|
|
1144
|
+
resumeAutomationRun(automationRunId: $automationRunId, message: $message)
|
|
1145
|
+
}`
|
|
1146
|
+
}),
|
|
1147
|
+
createOperationSpec({
|
|
1148
|
+
operationName: "RunDraftAutomation",
|
|
1149
|
+
kind: "mutation",
|
|
1150
|
+
inputSchema: z.object({
|
|
1151
|
+
draftAutomationId: z.string().min(1),
|
|
1152
|
+
agentId: z.string().min(1),
|
|
1153
|
+
idempotencyKey: z.string().min(1),
|
|
1154
|
+
input: z.unknown().optional(),
|
|
1155
|
+
additionalPrompt: z.string().min(1).optional()
|
|
1156
|
+
}),
|
|
1157
|
+
cli: {
|
|
1158
|
+
name: "run-draft-automation",
|
|
1159
|
+
path: ["automations", "run-draft"],
|
|
1160
|
+
description: "Run a draft automation with an explicit trigger agent.",
|
|
1161
|
+
resultKey: "runDraftAutomation",
|
|
1162
|
+
options: [
|
|
1163
|
+
stringOption({
|
|
1164
|
+
inputKey: "draftAutomationId",
|
|
1165
|
+
flagName: "draft-automation-id",
|
|
1166
|
+
description: "Draft automation ID.",
|
|
1167
|
+
required: true
|
|
1168
|
+
}),
|
|
1169
|
+
stringOption({
|
|
1170
|
+
inputKey: "agentId",
|
|
1171
|
+
flagName: "agent-id",
|
|
1172
|
+
description: "Trigger agent ID.",
|
|
1173
|
+
required: true
|
|
1174
|
+
}),
|
|
1175
|
+
stringOption({
|
|
1176
|
+
inputKey: "idempotencyKey",
|
|
1177
|
+
flagName: "idempotency-key",
|
|
1178
|
+
description: "Idempotency key for the run request.",
|
|
1179
|
+
required: true
|
|
1180
|
+
}),
|
|
1181
|
+
jsonOption({
|
|
1182
|
+
inputKey: "input",
|
|
1183
|
+
flagName: "input-json",
|
|
1184
|
+
description: "Optional JSON input payload."
|
|
1185
|
+
}),
|
|
1186
|
+
stringOption({
|
|
1187
|
+
inputKey: "additionalPrompt",
|
|
1188
|
+
flagName: "additional-prompt",
|
|
1189
|
+
description: "Optional prompt appended to the run."
|
|
1190
|
+
})
|
|
1191
|
+
]
|
|
1192
|
+
},
|
|
1193
|
+
mcp: { fileName: "runDraftAutomation.graphql" },
|
|
1194
|
+
document: `# Mutation to run a draft automation with an explicit agent.
|
|
1195
|
+
# \`draftAutomationId\` is the draft automation entity ID (\`Automation.id\`), not \`draftVersionId\`.
|
|
1196
|
+
mutation RunDraftAutomation(
|
|
1197
|
+
$draftAutomationId: ID!
|
|
1198
|
+
$agentId: ID!
|
|
1199
|
+
$idempotencyKey: String!
|
|
1200
|
+
$input: JSON
|
|
1201
|
+
$additionalPrompt: String
|
|
1202
|
+
) {
|
|
1203
|
+
runDraftAutomation(
|
|
1204
|
+
draftAutomationId: $draftAutomationId
|
|
1205
|
+
agentId: $agentId
|
|
1206
|
+
idempotencyKey: $idempotencyKey
|
|
1207
|
+
input: $input
|
|
1208
|
+
additionalPrompt: $additionalPrompt
|
|
1209
|
+
) {
|
|
1210
|
+
automationRunId
|
|
1211
|
+
workflowId
|
|
1212
|
+
workflowRunId
|
|
1213
|
+
}
|
|
1214
|
+
}`
|
|
1215
|
+
}),
|
|
1216
|
+
createOperationSpec({
|
|
1217
|
+
operationName: "StopAutomationRun",
|
|
1218
|
+
kind: "mutation",
|
|
1219
|
+
inputSchema: z.object({
|
|
1220
|
+
automationRunId: z.string().min(1)
|
|
1221
|
+
}),
|
|
1222
|
+
cli: {
|
|
1223
|
+
name: "stop-automation-run",
|
|
1224
|
+
path: ["automation-runs", "stop"],
|
|
1225
|
+
description: "Stop an automation run.",
|
|
1226
|
+
resultKey: "stopAutomationRun",
|
|
1227
|
+
options: [
|
|
1228
|
+
stringOption({
|
|
1229
|
+
inputKey: "automationRunId",
|
|
1230
|
+
flagName: "automation-run-id",
|
|
1231
|
+
description: "Automation run ID.",
|
|
1232
|
+
required: true
|
|
1233
|
+
})
|
|
1234
|
+
]
|
|
1235
|
+
},
|
|
1236
|
+
mcp: { fileName: "stopAutomationRun.graphql" },
|
|
1237
|
+
document: `# Mutation to stop an automation run
|
|
1238
|
+
mutation StopAutomationRun($automationRunId: ID!) {
|
|
1239
|
+
stopAutomationRun(automationRunId: $automationRunId)
|
|
1240
|
+
}`
|
|
1241
|
+
}),
|
|
1242
|
+
createOperationSpec({
|
|
1243
|
+
operationName: "UpdateAutomationById",
|
|
1244
|
+
kind: "mutation",
|
|
1245
|
+
inputSchema: z.object({
|
|
1246
|
+
id: z.string().min(1),
|
|
1247
|
+
input: jsonObjectSchema
|
|
1248
|
+
}),
|
|
1249
|
+
cli: {
|
|
1250
|
+
name: "update-automation-by-id",
|
|
1251
|
+
path: ["automations", "update"],
|
|
1252
|
+
description: "Update an automation.",
|
|
1253
|
+
resultKey: "updateAutomation",
|
|
1254
|
+
options: [
|
|
1255
|
+
stringOption({
|
|
1256
|
+
inputKey: "id",
|
|
1257
|
+
flagName: "id",
|
|
1258
|
+
description: "Automation ID.",
|
|
1259
|
+
required: true
|
|
1260
|
+
}),
|
|
1261
|
+
jsonOption({
|
|
1262
|
+
inputKey: "input",
|
|
1263
|
+
flagName: "input-json",
|
|
1264
|
+
description: "UpdateAutomationInput payload.",
|
|
1265
|
+
required: true
|
|
1266
|
+
})
|
|
1267
|
+
]
|
|
1268
|
+
},
|
|
1269
|
+
mcp: { fileName: "updateAutomation.graphql" },
|
|
1270
|
+
document: `# Mutation to update an automation
|
|
1271
|
+
# Returns only id and lastSavedAt for MCP tool simplicity
|
|
1272
|
+
mutation UpdateAutomationById($id: ID!, $input: UpdateAutomationInput!) {
|
|
1273
|
+
updateAutomation(id: $id, input: $input) {
|
|
1274
|
+
id
|
|
1275
|
+
lastSavedAt
|
|
1276
|
+
}
|
|
1277
|
+
}`
|
|
1278
|
+
})
|
|
1279
|
+
];
|
|
1280
|
+
|
|
1281
|
+
// src/program.ts
|
|
1282
|
+
import { Command, InvalidArgumentError, Option } from "commander";
|
|
1283
|
+
|
|
1284
|
+
// src/api.ts
|
|
1285
|
+
import { z as z2 } from "zod";
|
|
1286
|
+
var workosOrganizationsResponseSchema = z2.object({
|
|
1287
|
+
organizations: z2.array(
|
|
1288
|
+
z2.object({
|
|
1289
|
+
id: z2.string().min(1),
|
|
1290
|
+
name: z2.string().min(1),
|
|
1291
|
+
primaryDomain: z2.string().nullable().optional()
|
|
1292
|
+
})
|
|
1293
|
+
),
|
|
1294
|
+
success: z2.boolean()
|
|
1295
|
+
});
|
|
1296
|
+
var workosUserSchema = z2.object({
|
|
1297
|
+
email: z2.string().nullable(),
|
|
1298
|
+
id: z2.string().min(1)
|
|
1299
|
+
});
|
|
1300
|
+
var graphQlResponseSchema = z2.object({
|
|
1301
|
+
data: z2.record(z2.string(), z2.unknown()).optional(),
|
|
1302
|
+
errors: z2.array(
|
|
1303
|
+
z2.object({
|
|
1304
|
+
message: z2.string().min(1)
|
|
1305
|
+
})
|
|
1306
|
+
).optional()
|
|
1307
|
+
});
|
|
1308
|
+
function createMagicalApiClient({
|
|
1309
|
+
apiBaseUrl,
|
|
1310
|
+
fetchFn = fetch
|
|
1311
|
+
}) {
|
|
1312
|
+
async function fetchJson({
|
|
1313
|
+
accessToken,
|
|
1314
|
+
url,
|
|
1315
|
+
init
|
|
1316
|
+
}) {
|
|
1317
|
+
const headers = new Headers(init?.headers);
|
|
1318
|
+
headers.set("authorization", `Bearer ${accessToken}`);
|
|
1319
|
+
const response = await fetchFn(url, {
|
|
1320
|
+
...init,
|
|
1321
|
+
headers
|
|
1322
|
+
}).catch((error) => {
|
|
1323
|
+
throw new CliError(
|
|
1324
|
+
`Failed to reach Magical API at ${url}. Check MAGICAL_API_URL and make sure service-main is running.`,
|
|
1325
|
+
{ cause: error }
|
|
1326
|
+
);
|
|
1327
|
+
});
|
|
1328
|
+
const responseBody = await response.json().catch((error) => {
|
|
1329
|
+
throw new CliError(
|
|
1330
|
+
`Magical API returned a non-JSON response for ${url}.`,
|
|
1331
|
+
{
|
|
1332
|
+
cause: error
|
|
1333
|
+
}
|
|
1334
|
+
);
|
|
1335
|
+
});
|
|
1336
|
+
if (!response.ok) {
|
|
1337
|
+
throw new CliError(
|
|
1338
|
+
`Magical API request to ${url} failed with status ${response.status}.`
|
|
1339
|
+
);
|
|
1340
|
+
}
|
|
1341
|
+
return responseBody;
|
|
1342
|
+
}
|
|
1343
|
+
return {
|
|
1344
|
+
async executeGraphQl({
|
|
1345
|
+
accessToken,
|
|
1346
|
+
query,
|
|
1347
|
+
resultKey,
|
|
1348
|
+
variables
|
|
1349
|
+
}) {
|
|
1350
|
+
const headers = new Headers();
|
|
1351
|
+
headers.set("content-type", "application/json");
|
|
1352
|
+
const parsedResponse = graphQlResponseSchema.safeParse(
|
|
1353
|
+
await fetchJson({
|
|
1354
|
+
accessToken,
|
|
1355
|
+
url: `${apiBaseUrl}/graphql`,
|
|
1356
|
+
init: {
|
|
1357
|
+
method: "POST",
|
|
1358
|
+
headers,
|
|
1359
|
+
body: JSON.stringify({
|
|
1360
|
+
query,
|
|
1361
|
+
variables
|
|
1362
|
+
})
|
|
1363
|
+
}
|
|
1364
|
+
})
|
|
1365
|
+
);
|
|
1366
|
+
if (!parsedResponse.success) {
|
|
1367
|
+
throw new CliError("GraphQL response payload was invalid.");
|
|
1368
|
+
}
|
|
1369
|
+
if (parsedResponse.data.errors && parsedResponse.data.errors.length > 0) {
|
|
1370
|
+
throw new CliError(
|
|
1371
|
+
parsedResponse.data.errors.map((error) => error.message).join("\n")
|
|
1372
|
+
);
|
|
1373
|
+
}
|
|
1374
|
+
return parsedResponse.data.data?.[resultKey] ?? null;
|
|
1375
|
+
},
|
|
1376
|
+
async fetchOrganizations({ accessToken }) {
|
|
1377
|
+
const parsedResponse = workosOrganizationsResponseSchema.safeParse(
|
|
1378
|
+
await fetchJson({
|
|
1379
|
+
accessToken,
|
|
1380
|
+
url: `${apiBaseUrl}/workos/organizations`
|
|
1381
|
+
})
|
|
1382
|
+
);
|
|
1383
|
+
if (!parsedResponse.success) {
|
|
1384
|
+
throw new CliError("Organization list payload was invalid.");
|
|
1385
|
+
}
|
|
1386
|
+
return parsedResponse.data.organizations.map((organization) => ({
|
|
1387
|
+
id: organization.id,
|
|
1388
|
+
name: organization.name,
|
|
1389
|
+
primaryDomain: organization.primaryDomain ?? null
|
|
1390
|
+
}));
|
|
1391
|
+
},
|
|
1392
|
+
async fetchUser({
|
|
1393
|
+
accessToken,
|
|
1394
|
+
userId
|
|
1395
|
+
}) {
|
|
1396
|
+
const parsedResponse = workosUserSchema.safeParse(
|
|
1397
|
+
await fetchJson({
|
|
1398
|
+
accessToken,
|
|
1399
|
+
url: `${apiBaseUrl}/workos/user/${userId}`
|
|
1400
|
+
})
|
|
1401
|
+
);
|
|
1402
|
+
if (!parsedResponse.success) {
|
|
1403
|
+
throw new CliError("User payload was invalid.");
|
|
1404
|
+
}
|
|
1405
|
+
return parsedResponse.data;
|
|
1406
|
+
}
|
|
1407
|
+
};
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
// src/config.ts
|
|
1411
|
+
import os from "os";
|
|
1412
|
+
import path from "path";
|
|
1413
|
+
var DEFAULT_MAGICAL_API_URL = "https://api-agt.getmagical.io";
|
|
1414
|
+
var DEFAULT_WORKOS_API_URL = "https://api.workos.com";
|
|
1415
|
+
var DEFAULT_WORKOS_CLIENT_ID = "client_01JJZ6XJ1RF248P20WF63M4VAM";
|
|
1416
|
+
var DEFAULT_KEYCHAIN_SERVICE_NAME = "@getmagical/mcp-cli";
|
|
1417
|
+
var CLI_CONFIG_DIRECTORY_NAME = "magical-mcp-cli";
|
|
1418
|
+
var CLI_STATE_FILE_NAME = "state.json";
|
|
1419
|
+
function resolvePlatformConfigDirectory() {
|
|
1420
|
+
const customConfigDirectory = process.env["MAGICAL_CLI_CONFIG_DIR"];
|
|
1421
|
+
if (customConfigDirectory) {
|
|
1422
|
+
return customConfigDirectory;
|
|
1423
|
+
}
|
|
1424
|
+
if (process.platform === "darwin") {
|
|
1425
|
+
return path.join(os.homedir(), "Library", "Application Support");
|
|
1426
|
+
}
|
|
1427
|
+
if (process.platform === "win32") {
|
|
1428
|
+
const appDataDirectory = process.env["APPDATA"];
|
|
1429
|
+
return appDataDirectory ?? path.join(os.homedir(), "AppData", "Roaming");
|
|
1430
|
+
}
|
|
1431
|
+
return process.env["XDG_CONFIG_HOME"] ?? path.join(os.homedir(), ".config");
|
|
1432
|
+
}
|
|
1433
|
+
function getRuntimeConfig() {
|
|
1434
|
+
const configDirectory = path.join(
|
|
1435
|
+
resolvePlatformConfigDirectory(),
|
|
1436
|
+
CLI_CONFIG_DIRECTORY_NAME
|
|
1437
|
+
);
|
|
1438
|
+
return {
|
|
1439
|
+
magicalApiUrl: process.env["MAGICAL_API_URL"] ?? DEFAULT_MAGICAL_API_URL,
|
|
1440
|
+
stateFilePath: path.join(configDirectory, CLI_STATE_FILE_NAME),
|
|
1441
|
+
workosApiUrl: DEFAULT_WORKOS_API_URL,
|
|
1442
|
+
workosClientId: process.env["WORKOS_CLIENT_ID"] ?? DEFAULT_WORKOS_CLIENT_ID,
|
|
1443
|
+
keychainServiceName: DEFAULT_KEYCHAIN_SERVICE_NAME,
|
|
1444
|
+
...process.env["WORKOS_AUTHKIT_DOMAIN"] ? { workosAuthkitDomain: process.env["WORKOS_AUTHKIT_DOMAIN"] } : {}
|
|
1445
|
+
};
|
|
1446
|
+
}
|
|
1447
|
+
function requireWorkosClientId(config) {
|
|
1448
|
+
return config.workosClientId;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
// src/keychain.ts
|
|
1452
|
+
import keytar from "keytar";
|
|
1453
|
+
function buildKeychainAccountName(account) {
|
|
1454
|
+
return `${account.clientId}:${account.userId}`;
|
|
1455
|
+
}
|
|
1456
|
+
function createKeytarRefreshTokenStore(serviceName) {
|
|
1457
|
+
return {
|
|
1458
|
+
async clear(account) {
|
|
1459
|
+
await keytar.deletePassword(
|
|
1460
|
+
serviceName,
|
|
1461
|
+
buildKeychainAccountName(account)
|
|
1462
|
+
);
|
|
1463
|
+
},
|
|
1464
|
+
async load(account) {
|
|
1465
|
+
return keytar.getPassword(serviceName, buildKeychainAccountName(account));
|
|
1466
|
+
},
|
|
1467
|
+
async save(account, refreshToken) {
|
|
1468
|
+
await keytar.setPassword(
|
|
1469
|
+
serviceName,
|
|
1470
|
+
buildKeychainAccountName(account),
|
|
1471
|
+
refreshToken
|
|
1472
|
+
);
|
|
1473
|
+
}
|
|
1474
|
+
};
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
// src/state.ts
|
|
1478
|
+
import { mkdir, readFile, rm, writeFile } from "fs/promises";
|
|
1479
|
+
import path2 from "path";
|
|
1480
|
+
import { z as z3 } from "zod";
|
|
1481
|
+
var cliAccountSchema = z3.object({
|
|
1482
|
+
clientId: z3.string().min(1),
|
|
1483
|
+
email: z3.string().nullable(),
|
|
1484
|
+
userId: z3.string().min(1)
|
|
1485
|
+
});
|
|
1486
|
+
var cliOrganizationSchema = z3.object({
|
|
1487
|
+
id: z3.string().min(1),
|
|
1488
|
+
name: z3.string().min(1),
|
|
1489
|
+
primaryDomain: z3.string().nullable().default(null)
|
|
1490
|
+
});
|
|
1491
|
+
var cliStateSchema = z3.object({
|
|
1492
|
+
account: cliAccountSchema,
|
|
1493
|
+
defaultOrgId: z3.string().nullable(),
|
|
1494
|
+
lastUsedOrgId: z3.string().nullable(),
|
|
1495
|
+
organizations: z3.array(cliOrganizationSchema)
|
|
1496
|
+
});
|
|
1497
|
+
function createFileStateStore(stateFilePath) {
|
|
1498
|
+
return {
|
|
1499
|
+
async clear() {
|
|
1500
|
+
await rm(stateFilePath, { force: true });
|
|
1501
|
+
},
|
|
1502
|
+
async load() {
|
|
1503
|
+
try {
|
|
1504
|
+
const fileContents = await readFile(stateFilePath, "utf8");
|
|
1505
|
+
const parsed = cliStateSchema.safeParse(JSON.parse(fileContents));
|
|
1506
|
+
if (!parsed.success) {
|
|
1507
|
+
throw new CliError("Stored CLI state is invalid.");
|
|
1508
|
+
}
|
|
1509
|
+
return parsed.data;
|
|
1510
|
+
} catch (error) {
|
|
1511
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
1512
|
+
return null;
|
|
1513
|
+
}
|
|
1514
|
+
if (error instanceof CliError) {
|
|
1515
|
+
throw error;
|
|
1516
|
+
}
|
|
1517
|
+
throw new CliError("Failed to load CLI state.", { cause: error });
|
|
1518
|
+
}
|
|
1519
|
+
},
|
|
1520
|
+
async save(state) {
|
|
1521
|
+
await mkdir(path2.dirname(stateFilePath), { recursive: true });
|
|
1522
|
+
await writeFile(stateFilePath, `${JSON.stringify(state, null, 2)}
|
|
1523
|
+
`);
|
|
1524
|
+
}
|
|
1525
|
+
};
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
// src/workos-auth.ts
|
|
1529
|
+
import { z as z4 } from "zod";
|
|
1530
|
+
var DEFAULT_DEVICE_POLL_INTERVAL_SECONDS = 5;
|
|
1531
|
+
function buildFormHeaders() {
|
|
1532
|
+
const headers = new Headers();
|
|
1533
|
+
headers.set("content-type", "application/x-www-form-urlencoded");
|
|
1534
|
+
return headers;
|
|
1535
|
+
}
|
|
1536
|
+
var deviceAuthorizationSchema = z4.object({
|
|
1537
|
+
device_code: z4.string().min(1),
|
|
1538
|
+
expires_in: z4.number().int().positive().optional(),
|
|
1539
|
+
interval: z4.number().int().positive().optional(),
|
|
1540
|
+
user_code: z4.string().min(1),
|
|
1541
|
+
verification_uri: z4.url(),
|
|
1542
|
+
verification_uri_complete: z4.url().optional()
|
|
1543
|
+
});
|
|
1544
|
+
var tokenResponseSchema = z4.object({
|
|
1545
|
+
access_token: z4.string().min(1),
|
|
1546
|
+
expires_in: z4.number().int().positive().optional(),
|
|
1547
|
+
refresh_token: z4.string().min(1),
|
|
1548
|
+
token_type: z4.string().optional()
|
|
1549
|
+
});
|
|
1550
|
+
var tokenErrorSchema = z4.object({
|
|
1551
|
+
error: z4.string().min(1),
|
|
1552
|
+
error_description: z4.string().optional()
|
|
1553
|
+
});
|
|
1554
|
+
var accessTokenClaimsSchema = z4.object({
|
|
1555
|
+
org_id: z4.string().min(1).optional(),
|
|
1556
|
+
sid: z4.string().min(1).optional(),
|
|
1557
|
+
sub: z4.string().min(1)
|
|
1558
|
+
});
|
|
1559
|
+
function createWorkosAuthClient({
|
|
1560
|
+
fetchFn = fetch,
|
|
1561
|
+
sleep = async (milliseconds) => {
|
|
1562
|
+
await new Promise((resolve) => {
|
|
1563
|
+
setTimeout(resolve, milliseconds);
|
|
1564
|
+
});
|
|
1565
|
+
},
|
|
1566
|
+
workosApiUrl
|
|
1567
|
+
}) {
|
|
1568
|
+
async function postForm({
|
|
1569
|
+
endpointPath,
|
|
1570
|
+
formData,
|
|
1571
|
+
responseSchema
|
|
1572
|
+
}) {
|
|
1573
|
+
const response = await fetchFn(`${workosApiUrl}${endpointPath}`, {
|
|
1574
|
+
method: "POST",
|
|
1575
|
+
headers: buildFormHeaders(),
|
|
1576
|
+
body: formData
|
|
1577
|
+
});
|
|
1578
|
+
const responseBody = await response.json();
|
|
1579
|
+
if (!response.ok) {
|
|
1580
|
+
const parsedError = tokenErrorSchema.safeParse(responseBody);
|
|
1581
|
+
if (parsedError.success) {
|
|
1582
|
+
throw new CliError(
|
|
1583
|
+
parsedError.data.error_description ?? parsedError.data.error
|
|
1584
|
+
);
|
|
1585
|
+
}
|
|
1586
|
+
throw new CliError(
|
|
1587
|
+
`WorkOS request failed with status ${response.status}.`
|
|
1588
|
+
);
|
|
1589
|
+
}
|
|
1590
|
+
const parsedResponse = responseSchema.safeParse(responseBody);
|
|
1591
|
+
if (!parsedResponse.success) {
|
|
1592
|
+
throw new CliError("WorkOS returned an unexpected response payload.");
|
|
1593
|
+
}
|
|
1594
|
+
return parsedResponse.data;
|
|
1595
|
+
}
|
|
1596
|
+
return {
|
|
1597
|
+
decodeAccessTokenClaims(accessToken) {
|
|
1598
|
+
const [, payloadSegment] = accessToken.split(".");
|
|
1599
|
+
if (!payloadSegment) {
|
|
1600
|
+
throw new CliError("WorkOS access token is malformed.");
|
|
1601
|
+
}
|
|
1602
|
+
const payload = JSON.parse(
|
|
1603
|
+
Buffer.from(payloadSegment, "base64url").toString("utf8")
|
|
1604
|
+
);
|
|
1605
|
+
const parsedClaims = accessTokenClaimsSchema.safeParse(payload);
|
|
1606
|
+
if (!parsedClaims.success) {
|
|
1607
|
+
throw new CliError("WorkOS access token is missing required claims.");
|
|
1608
|
+
}
|
|
1609
|
+
return parsedClaims.data;
|
|
1610
|
+
},
|
|
1611
|
+
async pollForDeviceTokens({
|
|
1612
|
+
clientId,
|
|
1613
|
+
deviceCode,
|
|
1614
|
+
expiresInSeconds,
|
|
1615
|
+
intervalSeconds
|
|
1616
|
+
}) {
|
|
1617
|
+
const timeoutAt = Date.now() + (expiresInSeconds ?? 15 * 60) * 1e3;
|
|
1618
|
+
const initialIntervalMilliseconds = (intervalSeconds ?? DEFAULT_DEVICE_POLL_INTERVAL_SECONDS) * 1e3;
|
|
1619
|
+
async function poll(nextIntervalMilliseconds) {
|
|
1620
|
+
if (Date.now() >= timeoutAt) {
|
|
1621
|
+
throw new CliError(
|
|
1622
|
+
"WorkOS device authorization expired before completion."
|
|
1623
|
+
);
|
|
1624
|
+
}
|
|
1625
|
+
const formData = new URLSearchParams();
|
|
1626
|
+
formData.set("client_id", clientId);
|
|
1627
|
+
formData.set("device_code", deviceCode);
|
|
1628
|
+
formData.set(
|
|
1629
|
+
"grant_type",
|
|
1630
|
+
"urn:ietf:params:oauth:grant-type:device_code"
|
|
1631
|
+
);
|
|
1632
|
+
const response = await fetchFn(
|
|
1633
|
+
`${workosApiUrl}/user_management/authenticate`,
|
|
1634
|
+
{
|
|
1635
|
+
method: "POST",
|
|
1636
|
+
headers: buildFormHeaders(),
|
|
1637
|
+
body: formData
|
|
1638
|
+
}
|
|
1639
|
+
);
|
|
1640
|
+
const responseBody = await response.json();
|
|
1641
|
+
if (response.ok) {
|
|
1642
|
+
const parsedTokens = tokenResponseSchema.safeParse(responseBody);
|
|
1643
|
+
if (!parsedTokens.success) {
|
|
1644
|
+
throw new CliError("WorkOS returned an unexpected token response.");
|
|
1645
|
+
}
|
|
1646
|
+
return parsedTokens.data;
|
|
1647
|
+
}
|
|
1648
|
+
const parsedError = tokenErrorSchema.safeParse(responseBody);
|
|
1649
|
+
if (!parsedError.success) {
|
|
1650
|
+
throw new CliError(
|
|
1651
|
+
`WorkOS device authorization failed with status ${response.status}.`
|
|
1652
|
+
);
|
|
1653
|
+
}
|
|
1654
|
+
if (parsedError.data.error === "authorization_pending") {
|
|
1655
|
+
await sleep(nextIntervalMilliseconds);
|
|
1656
|
+
return poll(nextIntervalMilliseconds);
|
|
1657
|
+
}
|
|
1658
|
+
if (parsedError.data.error === "slow_down") {
|
|
1659
|
+
const slowedIntervalMilliseconds = nextIntervalMilliseconds + 5e3;
|
|
1660
|
+
await sleep(slowedIntervalMilliseconds);
|
|
1661
|
+
return poll(slowedIntervalMilliseconds);
|
|
1662
|
+
}
|
|
1663
|
+
throw new CliError(
|
|
1664
|
+
parsedError.data.error_description ?? parsedError.data.error
|
|
1665
|
+
);
|
|
1666
|
+
}
|
|
1667
|
+
return poll(initialIntervalMilliseconds);
|
|
1668
|
+
},
|
|
1669
|
+
async refreshTokens({
|
|
1670
|
+
clientId,
|
|
1671
|
+
organizationId,
|
|
1672
|
+
refreshToken
|
|
1673
|
+
}) {
|
|
1674
|
+
const formData = new URLSearchParams();
|
|
1675
|
+
formData.set("client_id", clientId);
|
|
1676
|
+
formData.set("grant_type", "refresh_token");
|
|
1677
|
+
formData.set("refresh_token", refreshToken);
|
|
1678
|
+
if (organizationId) {
|
|
1679
|
+
formData.set("organization_id", organizationId);
|
|
1680
|
+
}
|
|
1681
|
+
return postForm({
|
|
1682
|
+
endpointPath: "/user_management/authenticate",
|
|
1683
|
+
formData,
|
|
1684
|
+
responseSchema: tokenResponseSchema
|
|
1685
|
+
});
|
|
1686
|
+
},
|
|
1687
|
+
async startDeviceAuthorization({ clientId }) {
|
|
1688
|
+
const formData = new URLSearchParams();
|
|
1689
|
+
formData.set("client_id", clientId);
|
|
1690
|
+
return postForm({
|
|
1691
|
+
endpointPath: "/user_management/authorize/device",
|
|
1692
|
+
formData,
|
|
1693
|
+
responseSchema: deviceAuthorizationSchema
|
|
1694
|
+
});
|
|
1695
|
+
}
|
|
1696
|
+
};
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
// src/program.ts
|
|
1700
|
+
var rootOperationalGroupDescriptions = /* @__PURE__ */ new Map([
|
|
1701
|
+
["agents", "Query agents."],
|
|
1702
|
+
["agent-runs", "Query agent runs."],
|
|
1703
|
+
["automations", "Manage automations."],
|
|
1704
|
+
["automation-runs", "Inspect and control automation runs."],
|
|
1705
|
+
["catalog", "Inspect available commands, models, and tools."],
|
|
1706
|
+
["graphql", "Inspect GraphQL metadata."]
|
|
1707
|
+
]);
|
|
1708
|
+
function configureCommandPresentation(command) {
|
|
1709
|
+
const originalCreateCommand = command.createCommand.bind(command);
|
|
1710
|
+
command.createCommand = (name) => configureCommandPresentation(originalCreateCommand(name));
|
|
1711
|
+
command.createHelp = () => new MagicalHelp();
|
|
1712
|
+
return command;
|
|
1713
|
+
}
|
|
1714
|
+
function createOperationHelpExamples(operationSpec) {
|
|
1715
|
+
const baseCommand = `mgcl ${operationSpec.cli.path.join(" ")}`;
|
|
1716
|
+
const requiredOptions = operationSpec.cli.options.filter((optionSpec) => optionSpec.required).map((optionSpec) => {
|
|
1717
|
+
if (optionSpec.valueType === "boolean") {
|
|
1718
|
+
return `--${optionSpec.flagName}`;
|
|
1719
|
+
}
|
|
1720
|
+
if (optionSpec.valueType === "number") {
|
|
1721
|
+
return `--${optionSpec.flagName} 10`;
|
|
1722
|
+
}
|
|
1723
|
+
if (optionSpec.valueType === "json") {
|
|
1724
|
+
return `--${optionSpec.flagName} '{...}'`;
|
|
1725
|
+
}
|
|
1726
|
+
if (optionSpec.valueType === "string-array") {
|
|
1727
|
+
return `--${optionSpec.flagName} value-a,value-b`;
|
|
1728
|
+
}
|
|
1729
|
+
return `--${optionSpec.flagName} <${optionSpec.flagName}>`;
|
|
1730
|
+
});
|
|
1731
|
+
const baseInvocation = [baseCommand, ...requiredOptions].join(" ");
|
|
1732
|
+
return [
|
|
1733
|
+
baseInvocation,
|
|
1734
|
+
`${baseInvocation} --org <org>`,
|
|
1735
|
+
`${baseInvocation} --json`
|
|
1736
|
+
];
|
|
1737
|
+
}
|
|
1738
|
+
function buildCommandOptionProperty(flagName) {
|
|
1739
|
+
const [firstSegment, ...remainingSegments] = flagName.split("-");
|
|
1740
|
+
return [
|
|
1741
|
+
firstSegment ?? "",
|
|
1742
|
+
...remainingSegments.map(
|
|
1743
|
+
(segment) => `${segment[0]?.toUpperCase() ?? ""}${segment.slice(1)}`
|
|
1744
|
+
)
|
|
1745
|
+
].join("");
|
|
1746
|
+
}
|
|
1747
|
+
function parseCommandOptionValue(rawValue, optionSpec) {
|
|
1748
|
+
switch (optionSpec.valueType) {
|
|
1749
|
+
case "number": {
|
|
1750
|
+
const parsedNumber = Number(rawValue);
|
|
1751
|
+
if (Number.isNaN(parsedNumber)) {
|
|
1752
|
+
throw new InvalidArgumentError(
|
|
1753
|
+
`Expected a number for --${optionSpec.flagName}.`
|
|
1754
|
+
);
|
|
1755
|
+
}
|
|
1756
|
+
return parsedNumber;
|
|
1757
|
+
}
|
|
1758
|
+
case "string-array": {
|
|
1759
|
+
return rawValue.split(",").map((value) => value.trim()).filter((value) => value.length > 0);
|
|
1760
|
+
}
|
|
1761
|
+
case "json": {
|
|
1762
|
+
try {
|
|
1763
|
+
return JSON.parse(rawValue);
|
|
1764
|
+
} catch {
|
|
1765
|
+
throw new InvalidArgumentError(
|
|
1766
|
+
`Expected valid JSON for --${optionSpec.flagName}.`
|
|
1767
|
+
);
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
case "boolean": {
|
|
1771
|
+
return rawValue;
|
|
1772
|
+
}
|
|
1773
|
+
case "string": {
|
|
1774
|
+
return rawValue;
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
function isRecord2(value) {
|
|
1779
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1780
|
+
}
|
|
1781
|
+
function resolveOrganizationBySelector({
|
|
1782
|
+
organizations,
|
|
1783
|
+
selector
|
|
1784
|
+
}) {
|
|
1785
|
+
const matchedOrganization = organizations.find(
|
|
1786
|
+
(organization) => organization.id === selector || organization.name === selector
|
|
1787
|
+
);
|
|
1788
|
+
if (!matchedOrganization) {
|
|
1789
|
+
throw new CliError(`Unknown organization "${selector}".`);
|
|
1790
|
+
}
|
|
1791
|
+
return matchedOrganization;
|
|
1792
|
+
}
|
|
1793
|
+
function resolveSelectedOrganization({
|
|
1794
|
+
organizationSelector,
|
|
1795
|
+
state
|
|
1796
|
+
}) {
|
|
1797
|
+
if (organizationSelector) {
|
|
1798
|
+
return resolveOrganizationBySelector({
|
|
1799
|
+
organizations: state.organizations,
|
|
1800
|
+
selector: organizationSelector
|
|
1801
|
+
});
|
|
1802
|
+
}
|
|
1803
|
+
if (!state.defaultOrgId) {
|
|
1804
|
+
throw new CliError(
|
|
1805
|
+
'No default organization is configured. Use "mgcl org use <org>" or pass --org.'
|
|
1806
|
+
);
|
|
1807
|
+
}
|
|
1808
|
+
return resolveOrganizationBySelector({
|
|
1809
|
+
organizations: state.organizations,
|
|
1810
|
+
selector: state.defaultOrgId
|
|
1811
|
+
});
|
|
1812
|
+
}
|
|
1813
|
+
function requireLoggedInState(state) {
|
|
1814
|
+
if (!state) {
|
|
1815
|
+
throw new CliError('You are not logged in. Run "mgcl auth login" first.');
|
|
1816
|
+
}
|
|
1817
|
+
return state;
|
|
1818
|
+
}
|
|
1819
|
+
function writeJsonOutput(io, value) {
|
|
1820
|
+
io.info(JSON.stringify(value, null, 2));
|
|
1821
|
+
}
|
|
1822
|
+
function collectOperationVariables(operationSpec, options) {
|
|
1823
|
+
const variables = Object.fromEntries(
|
|
1824
|
+
operationSpec.cli.options.flatMap((optionSpec) => {
|
|
1825
|
+
const optionValue = options[buildCommandOptionProperty(optionSpec.flagName)];
|
|
1826
|
+
if (optionValue === void 0) {
|
|
1827
|
+
return [];
|
|
1828
|
+
}
|
|
1829
|
+
return [[optionSpec.inputKey, optionValue]];
|
|
1830
|
+
})
|
|
1831
|
+
);
|
|
1832
|
+
const parsedVariables = operationSpec.inputSchema.safeParse(variables);
|
|
1833
|
+
if (!parsedVariables.success) {
|
|
1834
|
+
throw new CliError(
|
|
1835
|
+
parsedVariables.error.issues[0]?.message ?? "Invalid command input."
|
|
1836
|
+
);
|
|
1837
|
+
}
|
|
1838
|
+
if (!isRecord2(parsedVariables.data)) {
|
|
1839
|
+
throw new CliError("Invalid command input.");
|
|
1840
|
+
}
|
|
1841
|
+
return parsedVariables.data;
|
|
1842
|
+
}
|
|
1843
|
+
async function withOrgScopedAccessToken({
|
|
1844
|
+
dependencies,
|
|
1845
|
+
organizationId,
|
|
1846
|
+
state
|
|
1847
|
+
}) {
|
|
1848
|
+
const refreshToken = await dependencies.refreshTokenStore.load(state.account);
|
|
1849
|
+
if (!refreshToken) {
|
|
1850
|
+
throw new CliError(
|
|
1851
|
+
'No refresh token was found. Run "mgcl auth login" again.'
|
|
1852
|
+
);
|
|
1853
|
+
}
|
|
1854
|
+
const tokenResponse = await dependencies.authClient.refreshTokens({
|
|
1855
|
+
clientId: state.account.clientId,
|
|
1856
|
+
organizationId,
|
|
1857
|
+
refreshToken
|
|
1858
|
+
}).catch((error) => {
|
|
1859
|
+
if (error instanceof CliError && error.message === "Refresh token already exchanged.") {
|
|
1860
|
+
throw new CliError(
|
|
1861
|
+
'Refresh token already exchanged. Run "mgcl auth login" again.',
|
|
1862
|
+
{ cause: error }
|
|
1863
|
+
);
|
|
1864
|
+
}
|
|
1865
|
+
throw error;
|
|
1866
|
+
});
|
|
1867
|
+
await dependencies.refreshTokenStore.save(
|
|
1868
|
+
state.account,
|
|
1869
|
+
tokenResponse.refresh_token
|
|
1870
|
+
);
|
|
1871
|
+
return {
|
|
1872
|
+
accessToken: tokenResponse.access_token
|
|
1873
|
+
};
|
|
1874
|
+
}
|
|
1875
|
+
async function handleAuthLogin(dependencies, options) {
|
|
1876
|
+
const clientId = requireWorkosClientId(dependencies.runtimeConfig);
|
|
1877
|
+
const deviceAuthorization = await dependencies.authClient.startDeviceAuthorization({ clientId });
|
|
1878
|
+
const verificationUrl = deviceAuthorization.verification_uri_complete ?? deviceAuthorization.verification_uri;
|
|
1879
|
+
const promptWriter = options.json ? dependencies.io.error : dependencies.io.info;
|
|
1880
|
+
promptWriter(
|
|
1881
|
+
formatAuthLoginPrompt({
|
|
1882
|
+
userCode: deviceAuthorization.user_code,
|
|
1883
|
+
verificationUrl
|
|
1884
|
+
})
|
|
1885
|
+
);
|
|
1886
|
+
await dependencies.openExternalUrl(verificationUrl).catch(() => {
|
|
1887
|
+
promptWriter(formatBrowserOpenFallback());
|
|
1888
|
+
});
|
|
1889
|
+
const tokenResponse = await dependencies.authClient.pollForDeviceTokens({
|
|
1890
|
+
clientId,
|
|
1891
|
+
deviceCode: deviceAuthorization.device_code,
|
|
1892
|
+
...deviceAuthorization.expires_in ? { expiresInSeconds: deviceAuthorization.expires_in } : {},
|
|
1893
|
+
...deviceAuthorization.interval ? { intervalSeconds: deviceAuthorization.interval } : {}
|
|
1894
|
+
});
|
|
1895
|
+
const accessTokenClaims = dependencies.authClient.decodeAccessTokenClaims(
|
|
1896
|
+
tokenResponse.access_token
|
|
1897
|
+
);
|
|
1898
|
+
const [user, organizations] = await Promise.all([
|
|
1899
|
+
dependencies.apiClient.fetchUser({
|
|
1900
|
+
accessToken: tokenResponse.access_token,
|
|
1901
|
+
userId: accessTokenClaims.sub
|
|
1902
|
+
}),
|
|
1903
|
+
dependencies.apiClient.fetchOrganizations({
|
|
1904
|
+
accessToken: tokenResponse.access_token
|
|
1905
|
+
})
|
|
1906
|
+
]);
|
|
1907
|
+
const defaultOrgId = accessTokenClaims.org_id ?? organizations[0]?.id ?? null;
|
|
1908
|
+
const nextState = {
|
|
1909
|
+
account: {
|
|
1910
|
+
clientId,
|
|
1911
|
+
email: user.email,
|
|
1912
|
+
userId: user.id
|
|
1913
|
+
},
|
|
1914
|
+
defaultOrgId,
|
|
1915
|
+
lastUsedOrgId: defaultOrgId,
|
|
1916
|
+
organizations
|
|
1917
|
+
};
|
|
1918
|
+
await Promise.all([
|
|
1919
|
+
dependencies.refreshTokenStore.save(
|
|
1920
|
+
nextState.account,
|
|
1921
|
+
tokenResponse.refresh_token
|
|
1922
|
+
),
|
|
1923
|
+
dependencies.stateStore.save(nextState)
|
|
1924
|
+
]);
|
|
1925
|
+
const defaultOrganization = organizations.find((organization) => organization.id === defaultOrgId) ?? null;
|
|
1926
|
+
if (options.json) {
|
|
1927
|
+
writeJsonOutput(dependencies.io, nextState);
|
|
1928
|
+
return;
|
|
1929
|
+
}
|
|
1930
|
+
dependencies.io.info(
|
|
1931
|
+
formatAuthLoginSuccess({
|
|
1932
|
+
account: nextState.account,
|
|
1933
|
+
defaultOrganization,
|
|
1934
|
+
organizationCount: organizations.length
|
|
1935
|
+
})
|
|
1936
|
+
);
|
|
1937
|
+
}
|
|
1938
|
+
async function handleAuthLogout(dependencies, options) {
|
|
1939
|
+
const state = await dependencies.stateStore.load();
|
|
1940
|
+
if (state) {
|
|
1941
|
+
await dependencies.refreshTokenStore.clear(state.account);
|
|
1942
|
+
}
|
|
1943
|
+
await dependencies.stateStore.clear();
|
|
1944
|
+
if (options.json) {
|
|
1945
|
+
writeJsonOutput(dependencies.io, { loggedOut: true });
|
|
1946
|
+
return;
|
|
1947
|
+
}
|
|
1948
|
+
dependencies.io.info(formatLogoutSuccess());
|
|
1949
|
+
}
|
|
1950
|
+
async function handleOrgList(dependencies, options) {
|
|
1951
|
+
const state = requireLoggedInState(await dependencies.stateStore.load());
|
|
1952
|
+
if (options.json) {
|
|
1953
|
+
writeJsonOutput(dependencies.io, state.organizations);
|
|
1954
|
+
return;
|
|
1955
|
+
}
|
|
1956
|
+
dependencies.io.info(
|
|
1957
|
+
formatOrganizationList({
|
|
1958
|
+
defaultOrgId: state.defaultOrgId,
|
|
1959
|
+
lastUsedOrgId: state.lastUsedOrgId,
|
|
1960
|
+
organizations: state.organizations
|
|
1961
|
+
})
|
|
1962
|
+
);
|
|
1963
|
+
}
|
|
1964
|
+
async function handleOrgUse(dependencies, organizationSelector, options) {
|
|
1965
|
+
const state = requireLoggedInState(await dependencies.stateStore.load());
|
|
1966
|
+
const organization = resolveOrganizationBySelector({
|
|
1967
|
+
organizations: state.organizations,
|
|
1968
|
+
selector: organizationSelector
|
|
1969
|
+
});
|
|
1970
|
+
const nextState = {
|
|
1971
|
+
...state,
|
|
1972
|
+
defaultOrgId: organization.id,
|
|
1973
|
+
lastUsedOrgId: organization.id
|
|
1974
|
+
};
|
|
1975
|
+
await dependencies.stateStore.save(nextState);
|
|
1976
|
+
if (options.json) {
|
|
1977
|
+
writeJsonOutput(dependencies.io, {
|
|
1978
|
+
defaultOrgId: organization.id,
|
|
1979
|
+
organization
|
|
1980
|
+
});
|
|
1981
|
+
return;
|
|
1982
|
+
}
|
|
1983
|
+
dependencies.io.info(formatDefaultOrganizationSelection(organization));
|
|
1984
|
+
}
|
|
1985
|
+
async function handleOperationCommand(dependencies, operationSpec, commandOptions) {
|
|
1986
|
+
const state = requireLoggedInState(await dependencies.stateStore.load());
|
|
1987
|
+
const organization = resolveSelectedOrganization({
|
|
1988
|
+
state,
|
|
1989
|
+
...commandOptions.org ? { organizationSelector: commandOptions.org } : {}
|
|
1990
|
+
});
|
|
1991
|
+
const variables = collectOperationVariables(operationSpec, commandOptions);
|
|
1992
|
+
const { accessToken } = await withOrgScopedAccessToken({
|
|
1993
|
+
dependencies,
|
|
1994
|
+
organizationId: organization.id,
|
|
1995
|
+
state
|
|
1996
|
+
});
|
|
1997
|
+
const result = await dependencies.apiClient.executeGraphQl({
|
|
1998
|
+
accessToken,
|
|
1999
|
+
query: operationSpec.document,
|
|
2000
|
+
resultKey: operationSpec.cli.resultKey,
|
|
2001
|
+
variables
|
|
2002
|
+
});
|
|
2003
|
+
await dependencies.stateStore.save({
|
|
2004
|
+
...state,
|
|
2005
|
+
lastUsedOrgId: organization.id
|
|
2006
|
+
});
|
|
2007
|
+
if (commandOptions.json) {
|
|
2008
|
+
writeJsonOutput(dependencies.io, result);
|
|
2009
|
+
return;
|
|
2010
|
+
}
|
|
2011
|
+
dependencies.io.info(formatHumanReadableOutput(result));
|
|
2012
|
+
}
|
|
2013
|
+
function addRegistryOperationCommand({
|
|
2014
|
+
program: program2,
|
|
2015
|
+
commandGroups,
|
|
2016
|
+
dependencies,
|
|
2017
|
+
operationSpec
|
|
2018
|
+
}) {
|
|
2019
|
+
const commandPath = [...operationSpec.cli.path];
|
|
2020
|
+
const leafCommandName = commandPath.pop();
|
|
2021
|
+
if (!leafCommandName) {
|
|
2022
|
+
throw new CliError(
|
|
2023
|
+
`CLI path is missing a leaf command for ${operationSpec.operationName}.`
|
|
2024
|
+
);
|
|
2025
|
+
}
|
|
2026
|
+
let parentCommand = program2;
|
|
2027
|
+
const currentGroupPath = [];
|
|
2028
|
+
for (const groupSegment of commandPath) {
|
|
2029
|
+
currentGroupPath.push(groupSegment);
|
|
2030
|
+
const groupPathKey = currentGroupPath.join(" ");
|
|
2031
|
+
const existingGroup = commandGroups.get(groupPathKey);
|
|
2032
|
+
if (existingGroup) {
|
|
2033
|
+
parentCommand = existingGroup;
|
|
2034
|
+
continue;
|
|
2035
|
+
}
|
|
2036
|
+
const nextGroup = parentCommand.command(groupSegment);
|
|
2037
|
+
if (currentGroupPath.length === 1) {
|
|
2038
|
+
nextGroup.helpGroup("Operation groups:");
|
|
2039
|
+
}
|
|
2040
|
+
const rootGroupDescription = currentGroupPath.length === 1 ? rootOperationalGroupDescriptions.get(groupSegment) : void 0;
|
|
2041
|
+
if (rootGroupDescription) {
|
|
2042
|
+
nextGroup.description(rootGroupDescription);
|
|
2043
|
+
}
|
|
2044
|
+
commandGroups.set(groupPathKey, nextGroup);
|
|
2045
|
+
parentCommand = nextGroup;
|
|
2046
|
+
}
|
|
2047
|
+
const command = parentCommand.command(leafCommandName);
|
|
2048
|
+
configureOperationalCommand(command, dependencies, operationSpec);
|
|
2049
|
+
const legacyAliasCommand = program2.command(operationSpec.cli.name, {
|
|
2050
|
+
hidden: true
|
|
2051
|
+
});
|
|
2052
|
+
configureOperationalCommand(legacyAliasCommand, dependencies, operationSpec);
|
|
2053
|
+
}
|
|
2054
|
+
function configureOperationalCommand(command, dependencies, operationSpec) {
|
|
2055
|
+
command.description(operationSpec.cli.description).addOption(
|
|
2056
|
+
new Option(
|
|
2057
|
+
"--org <org>",
|
|
2058
|
+
"Organization ID or exact organization name to use."
|
|
2059
|
+
).helpGroup("Shared options:")
|
|
2060
|
+
).addHelpText(
|
|
2061
|
+
"after",
|
|
2062
|
+
`
|
|
2063
|
+
${formatExamples(
|
|
2064
|
+
"Examples",
|
|
2065
|
+
createOperationHelpExamples(operationSpec)
|
|
2066
|
+
)}`
|
|
2067
|
+
);
|
|
2068
|
+
for (const optionSpec of operationSpec.cli.options) {
|
|
2069
|
+
const optionFlags = optionSpec.valueType === "boolean" ? `--${optionSpec.flagName}` : `--${optionSpec.flagName} <value>`;
|
|
2070
|
+
const optionDescription = optionSpec.valueHint ? `${optionSpec.description} (${optionSpec.valueHint})` : optionSpec.description;
|
|
2071
|
+
const nextOption = new Option(optionFlags, optionDescription).helpGroup(
|
|
2072
|
+
operationSpec.kind === "query" ? "Query options:" : "Mutation options:"
|
|
2073
|
+
);
|
|
2074
|
+
if (optionSpec.valueType === "boolean") {
|
|
2075
|
+
command.addOption(nextOption);
|
|
2076
|
+
continue;
|
|
2077
|
+
}
|
|
2078
|
+
const parser = (rawValue) => parseCommandOptionValue(rawValue, optionSpec);
|
|
2079
|
+
nextOption.argParser(parser);
|
|
2080
|
+
if (optionSpec.required) {
|
|
2081
|
+
nextOption.makeOptionMandatory(true);
|
|
2082
|
+
command.addOption(nextOption);
|
|
2083
|
+
continue;
|
|
2084
|
+
}
|
|
2085
|
+
command.addOption(nextOption);
|
|
2086
|
+
}
|
|
2087
|
+
command.action(async function() {
|
|
2088
|
+
await handleOperationCommand(
|
|
2089
|
+
dependencies,
|
|
2090
|
+
operationSpec,
|
|
2091
|
+
this.optsWithGlobals()
|
|
2092
|
+
);
|
|
2093
|
+
});
|
|
2094
|
+
}
|
|
2095
|
+
function createConsoleIo() {
|
|
2096
|
+
return {
|
|
2097
|
+
error(message) {
|
|
2098
|
+
process.stderr.write(`${message}
|
|
2099
|
+
`);
|
|
2100
|
+
},
|
|
2101
|
+
info(message) {
|
|
2102
|
+
process.stdout.write(`${message}
|
|
2103
|
+
`);
|
|
2104
|
+
}
|
|
2105
|
+
};
|
|
2106
|
+
}
|
|
2107
|
+
async function openExternalUrl(url) {
|
|
2108
|
+
const command = resolveOpenCommand(url);
|
|
2109
|
+
const childProcess = spawn(command.executable, command.args, {
|
|
2110
|
+
detached: true,
|
|
2111
|
+
stdio: "ignore"
|
|
2112
|
+
});
|
|
2113
|
+
await new Promise((resolve, reject) => {
|
|
2114
|
+
childProcess.on("error", reject);
|
|
2115
|
+
childProcess.on("spawn", () => {
|
|
2116
|
+
childProcess.unref();
|
|
2117
|
+
resolve();
|
|
2118
|
+
});
|
|
2119
|
+
});
|
|
2120
|
+
}
|
|
2121
|
+
function resolveOpenCommand(url) {
|
|
2122
|
+
if (process.platform === "darwin") {
|
|
2123
|
+
return { executable: "open", args: [url] };
|
|
2124
|
+
}
|
|
2125
|
+
if (process.platform === "win32") {
|
|
2126
|
+
return { executable: "cmd", args: ["/c", "start", "", url] };
|
|
2127
|
+
}
|
|
2128
|
+
return { executable: "xdg-open", args: [url] };
|
|
2129
|
+
}
|
|
2130
|
+
function createDefaultDependencies() {
|
|
2131
|
+
const runtimeConfig = getRuntimeConfig();
|
|
2132
|
+
return {
|
|
2133
|
+
apiClient: createMagicalApiClient({
|
|
2134
|
+
apiBaseUrl: runtimeConfig.magicalApiUrl
|
|
2135
|
+
}),
|
|
2136
|
+
authClient: createWorkosAuthClient({
|
|
2137
|
+
workosApiUrl: runtimeConfig.workosApiUrl
|
|
2138
|
+
}),
|
|
2139
|
+
io: createConsoleIo(),
|
|
2140
|
+
openExternalUrl,
|
|
2141
|
+
refreshTokenStore: createKeytarRefreshTokenStore(
|
|
2142
|
+
runtimeConfig.keychainServiceName
|
|
2143
|
+
),
|
|
2144
|
+
runtimeConfig,
|
|
2145
|
+
stateStore: createFileStateStore(runtimeConfig.stateFilePath)
|
|
2146
|
+
};
|
|
2147
|
+
}
|
|
2148
|
+
function buildProgram(dependencies) {
|
|
2149
|
+
const program2 = configureCommandPresentation(
|
|
2150
|
+
new Command().name("mgcl").description("Run Magical shared MCP operations from the command line.")
|
|
2151
|
+
).addOption(new Option("--json", "Render JSON output.")).showHelpAfterError().showSuggestionAfterError().addHelpText(
|
|
2152
|
+
"after",
|
|
2153
|
+
`
|
|
2154
|
+
${formatExamples("Quick start", [
|
|
2155
|
+
"mgcl auth login",
|
|
2156
|
+
"mgcl org list",
|
|
2157
|
+
"mgcl automations list --limit 10",
|
|
2158
|
+
"mgcl automation-runs get --id <run-id>"
|
|
2159
|
+
])}
|
|
2160
|
+
|
|
2161
|
+
${formatOutputModesNote()}`
|
|
2162
|
+
);
|
|
2163
|
+
const authCommand = configureCommandPresentation(
|
|
2164
|
+
new Command("auth").description("Manage CLI authentication.")
|
|
2165
|
+
).helpGroup("Workspace:").addHelpText(
|
|
2166
|
+
"after",
|
|
2167
|
+
`
|
|
2168
|
+
${formatExamples("Examples", [
|
|
2169
|
+
"mgcl auth login",
|
|
2170
|
+
"mgcl auth logout"
|
|
2171
|
+
])}`
|
|
2172
|
+
);
|
|
2173
|
+
authCommand.command("login").description("Authenticate with Magical and load organization memberships.").action(async function() {
|
|
2174
|
+
await handleAuthLogin(dependencies, this.optsWithGlobals());
|
|
2175
|
+
});
|
|
2176
|
+
authCommand.command("logout").description("Remove local CLI auth state.").action(async function() {
|
|
2177
|
+
await handleAuthLogout(dependencies, this.optsWithGlobals());
|
|
2178
|
+
});
|
|
2179
|
+
program2.addCommand(authCommand);
|
|
2180
|
+
const orgCommand = configureCommandPresentation(
|
|
2181
|
+
new Command("org").description("Manage organization selection.")
|
|
2182
|
+
).helpGroup("Workspace:").addHelpText(
|
|
2183
|
+
"after",
|
|
2184
|
+
`
|
|
2185
|
+
${formatExamples("Examples", [
|
|
2186
|
+
"mgcl org list",
|
|
2187
|
+
"mgcl org use <org>"
|
|
2188
|
+
])}`
|
|
2189
|
+
);
|
|
2190
|
+
orgCommand.command("list").description("List organizations available to the saved identity.").action(async function() {
|
|
2191
|
+
await handleOrgList(dependencies, this.optsWithGlobals());
|
|
2192
|
+
});
|
|
2193
|
+
orgCommand.command("use <org>").description("Set the default organization by ID or exact name.").action(async function(organizationSelector) {
|
|
2194
|
+
await handleOrgUse(
|
|
2195
|
+
dependencies,
|
|
2196
|
+
organizationSelector,
|
|
2197
|
+
this.optsWithGlobals()
|
|
2198
|
+
);
|
|
2199
|
+
});
|
|
2200
|
+
program2.addCommand(orgCommand);
|
|
2201
|
+
const operationalCommandGroups = /* @__PURE__ */ new Map();
|
|
2202
|
+
for (const operationSpec of operationSpecs) {
|
|
2203
|
+
addRegistryOperationCommand({
|
|
2204
|
+
program: program2,
|
|
2205
|
+
commandGroups: operationalCommandGroups,
|
|
2206
|
+
dependencies,
|
|
2207
|
+
operationSpec
|
|
2208
|
+
});
|
|
2209
|
+
}
|
|
2210
|
+
return program2;
|
|
2211
|
+
}
|
|
2212
|
+
|
|
2213
|
+
// src/index.ts
|
|
2214
|
+
var program = buildProgram(createDefaultDependencies());
|
|
2215
|
+
program.parseAsync(process.argv).catch((error) => {
|
|
2216
|
+
process.stderr.write(`${formatCliError(getErrorMessage(error))}
|
|
2217
|
+
`);
|
|
2218
|
+
process.exitCode = 1;
|
|
2219
|
+
});
|
|
2220
|
+
//# sourceMappingURL=index.js.map
|