@solgridai/cli 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +108 -2
- package/dist/index.js +4219 -0
- package/package.json +46 -4
- package/skills/solgrid-cli/SKILL.md +212 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,4219 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/context.ts
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
import { createInterface } from "node:readline/promises";
|
|
7
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
8
|
+
function createProcessContext() {
|
|
9
|
+
return {
|
|
10
|
+
env: process.env,
|
|
11
|
+
stdout: process.stdout,
|
|
12
|
+
stderr: process.stderr,
|
|
13
|
+
fetch: (url, init) => globalThis.fetch(url, init),
|
|
14
|
+
homeDir: homedir(),
|
|
15
|
+
now: () => new Date,
|
|
16
|
+
sleep: async (ms) => {
|
|
17
|
+
await delay(ms);
|
|
18
|
+
},
|
|
19
|
+
randomUUID: () => randomUUID(),
|
|
20
|
+
stdinIsTTY: process.stdin.isTTY === true,
|
|
21
|
+
confirm: async (question) => {
|
|
22
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
23
|
+
try {
|
|
24
|
+
const answer = await rl.question(`${question} [y/N] `);
|
|
25
|
+
return /^y(es)?$/i.test(answer.trim());
|
|
26
|
+
} finally {
|
|
27
|
+
rl.close();
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
exitCode: 0
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// node_modules/commander/lib/error.js
|
|
35
|
+
class CommanderError extends Error {
|
|
36
|
+
constructor(exitCode, code, message) {
|
|
37
|
+
super(message);
|
|
38
|
+
Error.captureStackTrace(this, this.constructor);
|
|
39
|
+
this.name = this.constructor.name;
|
|
40
|
+
this.code = code;
|
|
41
|
+
this.exitCode = exitCode;
|
|
42
|
+
this.nestedError = undefined;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
class InvalidArgumentError extends CommanderError {
|
|
47
|
+
constructor(message) {
|
|
48
|
+
super(1, "commander.invalidArgument", message);
|
|
49
|
+
Error.captureStackTrace(this, this.constructor);
|
|
50
|
+
this.name = this.constructor.name;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// node_modules/commander/lib/argument.js
|
|
55
|
+
class Argument {
|
|
56
|
+
constructor(name, description) {
|
|
57
|
+
this.description = description || "";
|
|
58
|
+
this.variadic = false;
|
|
59
|
+
this.parseArg = undefined;
|
|
60
|
+
this.defaultValue = undefined;
|
|
61
|
+
this.defaultValueDescription = undefined;
|
|
62
|
+
this.argChoices = undefined;
|
|
63
|
+
switch (name[0]) {
|
|
64
|
+
case "<":
|
|
65
|
+
this.required = true;
|
|
66
|
+
this._name = name.slice(1, -1);
|
|
67
|
+
break;
|
|
68
|
+
case "[":
|
|
69
|
+
this.required = false;
|
|
70
|
+
this._name = name.slice(1, -1);
|
|
71
|
+
break;
|
|
72
|
+
default:
|
|
73
|
+
this.required = true;
|
|
74
|
+
this._name = name;
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
if (this._name.endsWith("...")) {
|
|
78
|
+
this.variadic = true;
|
|
79
|
+
this._name = this._name.slice(0, -3);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
name() {
|
|
83
|
+
return this._name;
|
|
84
|
+
}
|
|
85
|
+
_collectValue(value, previous) {
|
|
86
|
+
if (previous === this.defaultValue || !Array.isArray(previous)) {
|
|
87
|
+
return [value];
|
|
88
|
+
}
|
|
89
|
+
previous.push(value);
|
|
90
|
+
return previous;
|
|
91
|
+
}
|
|
92
|
+
default(value, description) {
|
|
93
|
+
this.defaultValue = value;
|
|
94
|
+
this.defaultValueDescription = description;
|
|
95
|
+
return this;
|
|
96
|
+
}
|
|
97
|
+
argParser(fn) {
|
|
98
|
+
this.parseArg = fn;
|
|
99
|
+
return this;
|
|
100
|
+
}
|
|
101
|
+
choices(values) {
|
|
102
|
+
this.argChoices = values.slice();
|
|
103
|
+
this.parseArg = (arg, previous) => {
|
|
104
|
+
if (!this.argChoices.includes(arg)) {
|
|
105
|
+
throw new InvalidArgumentError(`Allowed choices are ${this.argChoices.join(", ")}.`);
|
|
106
|
+
}
|
|
107
|
+
if (this.variadic) {
|
|
108
|
+
return this._collectValue(arg, previous);
|
|
109
|
+
}
|
|
110
|
+
return arg;
|
|
111
|
+
};
|
|
112
|
+
return this;
|
|
113
|
+
}
|
|
114
|
+
argRequired() {
|
|
115
|
+
this.required = true;
|
|
116
|
+
return this;
|
|
117
|
+
}
|
|
118
|
+
argOptional() {
|
|
119
|
+
this.required = false;
|
|
120
|
+
return this;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function humanReadableArgName(arg) {
|
|
124
|
+
const nameOutput = arg.name() + (arg.variadic === true ? "..." : "");
|
|
125
|
+
return arg.required ? "<" + nameOutput + ">" : "[" + nameOutput + "]";
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// node_modules/commander/lib/command.js
|
|
129
|
+
import { EventEmitter } from "node:events";
|
|
130
|
+
import childProcess from "node:child_process";
|
|
131
|
+
import path from "node:path";
|
|
132
|
+
import fs from "node:fs";
|
|
133
|
+
import process2 from "node:process";
|
|
134
|
+
import { stripVTControlCharacters as stripVTControlCharacters2 } from "node:util";
|
|
135
|
+
|
|
136
|
+
// node_modules/commander/lib/help.js
|
|
137
|
+
import { stripVTControlCharacters } from "node:util";
|
|
138
|
+
|
|
139
|
+
class Help {
|
|
140
|
+
constructor() {
|
|
141
|
+
this.helpWidth = undefined;
|
|
142
|
+
this.minWidthToWrap = 40;
|
|
143
|
+
this.sortSubcommands = false;
|
|
144
|
+
this.sortOptions = false;
|
|
145
|
+
this.showGlobalOptions = false;
|
|
146
|
+
}
|
|
147
|
+
prepareContext(contextOptions) {
|
|
148
|
+
this.helpWidth = this.helpWidth ?? contextOptions.helpWidth ?? 80;
|
|
149
|
+
}
|
|
150
|
+
visibleCommands(cmd) {
|
|
151
|
+
const visibleCommands = cmd.commands.filter((cmd) => !cmd._hidden);
|
|
152
|
+
const helpCommand = cmd._getHelpCommand();
|
|
153
|
+
if (helpCommand && !helpCommand._hidden) {
|
|
154
|
+
visibleCommands.push(helpCommand);
|
|
155
|
+
}
|
|
156
|
+
if (this.sortSubcommands) {
|
|
157
|
+
visibleCommands.sort((a, b) => {
|
|
158
|
+
return a.name().localeCompare(b.name());
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
return visibleCommands;
|
|
162
|
+
}
|
|
163
|
+
compareOptions(a, b) {
|
|
164
|
+
const getSortKey = (option) => {
|
|
165
|
+
return option.short ? option.short.replace(/^-/, "") : option.long.replace(/^--/, "");
|
|
166
|
+
};
|
|
167
|
+
return getSortKey(a).localeCompare(getSortKey(b));
|
|
168
|
+
}
|
|
169
|
+
visibleOptions(cmd) {
|
|
170
|
+
const visibleOptions = cmd.options.filter((option) => !option.hidden);
|
|
171
|
+
const helpOption = cmd._getHelpOption();
|
|
172
|
+
if (helpOption && !helpOption.hidden) {
|
|
173
|
+
const removeShort = helpOption.short && cmd._findOption(helpOption.short);
|
|
174
|
+
const removeLong = helpOption.long && cmd._findOption(helpOption.long);
|
|
175
|
+
if (!removeShort && !removeLong) {
|
|
176
|
+
visibleOptions.push(helpOption);
|
|
177
|
+
} else if (helpOption.long && !removeLong) {
|
|
178
|
+
visibleOptions.push(cmd.createOption(helpOption.long, helpOption.description));
|
|
179
|
+
} else if (helpOption.short && !removeShort) {
|
|
180
|
+
visibleOptions.push(cmd.createOption(helpOption.short, helpOption.description));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (this.sortOptions) {
|
|
184
|
+
visibleOptions.sort(this.compareOptions);
|
|
185
|
+
}
|
|
186
|
+
return visibleOptions;
|
|
187
|
+
}
|
|
188
|
+
visibleGlobalOptions(cmd) {
|
|
189
|
+
if (!this.showGlobalOptions)
|
|
190
|
+
return [];
|
|
191
|
+
const globalOptions = [];
|
|
192
|
+
for (let ancestorCmd = cmd.parent;ancestorCmd; ancestorCmd = ancestorCmd.parent) {
|
|
193
|
+
const visibleOptions = ancestorCmd.options.filter((option) => !option.hidden);
|
|
194
|
+
globalOptions.push(...visibleOptions);
|
|
195
|
+
}
|
|
196
|
+
if (this.sortOptions) {
|
|
197
|
+
globalOptions.sort(this.compareOptions);
|
|
198
|
+
}
|
|
199
|
+
return globalOptions;
|
|
200
|
+
}
|
|
201
|
+
visibleArguments(cmd) {
|
|
202
|
+
if (cmd._argsDescription) {
|
|
203
|
+
cmd.registeredArguments.forEach((argument) => {
|
|
204
|
+
argument.description = argument.description || cmd._argsDescription[argument.name()] || "";
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
if (cmd.registeredArguments.find((argument) => argument.description)) {
|
|
208
|
+
return cmd.registeredArguments;
|
|
209
|
+
}
|
|
210
|
+
return [];
|
|
211
|
+
}
|
|
212
|
+
subcommandTerm(cmd) {
|
|
213
|
+
const args = cmd.registeredArguments.map((arg) => humanReadableArgName(arg)).join(" ");
|
|
214
|
+
return cmd._name + (cmd._aliases[0] ? "|" + cmd._aliases[0] : "") + (cmd.options.length ? " [options]" : "") + (args ? " " + args : "");
|
|
215
|
+
}
|
|
216
|
+
optionTerm(option) {
|
|
217
|
+
return option.flags;
|
|
218
|
+
}
|
|
219
|
+
argumentTerm(argument) {
|
|
220
|
+
return argument.name();
|
|
221
|
+
}
|
|
222
|
+
longestSubcommandTermLength(cmd, helper) {
|
|
223
|
+
return helper.visibleCommands(cmd).reduce((max, command) => {
|
|
224
|
+
return Math.max(max, this.displayWidth(helper.styleSubcommandTerm(helper.subcommandTerm(command))));
|
|
225
|
+
}, 0);
|
|
226
|
+
}
|
|
227
|
+
longestOptionTermLength(cmd, helper) {
|
|
228
|
+
return helper.visibleOptions(cmd).reduce((max, option) => {
|
|
229
|
+
return Math.max(max, this.displayWidth(helper.styleOptionTerm(helper.optionTerm(option))));
|
|
230
|
+
}, 0);
|
|
231
|
+
}
|
|
232
|
+
longestGlobalOptionTermLength(cmd, helper) {
|
|
233
|
+
return helper.visibleGlobalOptions(cmd).reduce((max, option) => {
|
|
234
|
+
return Math.max(max, this.displayWidth(helper.styleOptionTerm(helper.optionTerm(option))));
|
|
235
|
+
}, 0);
|
|
236
|
+
}
|
|
237
|
+
longestArgumentTermLength(cmd, helper) {
|
|
238
|
+
return helper.visibleArguments(cmd).reduce((max, argument) => {
|
|
239
|
+
return Math.max(max, this.displayWidth(helper.styleArgumentTerm(helper.argumentTerm(argument))));
|
|
240
|
+
}, 0);
|
|
241
|
+
}
|
|
242
|
+
commandUsage(cmd) {
|
|
243
|
+
let cmdName = cmd._name;
|
|
244
|
+
if (cmd._aliases[0]) {
|
|
245
|
+
cmdName = cmdName + "|" + cmd._aliases[0];
|
|
246
|
+
}
|
|
247
|
+
let ancestorCmdNames = "";
|
|
248
|
+
for (let ancestorCmd = cmd.parent;ancestorCmd; ancestorCmd = ancestorCmd.parent) {
|
|
249
|
+
ancestorCmdNames = ancestorCmd.name() + " " + ancestorCmdNames;
|
|
250
|
+
}
|
|
251
|
+
return ancestorCmdNames + cmdName + " " + cmd.usage();
|
|
252
|
+
}
|
|
253
|
+
commandDescription(cmd) {
|
|
254
|
+
return cmd.description();
|
|
255
|
+
}
|
|
256
|
+
subcommandDescription(cmd) {
|
|
257
|
+
return cmd.summary() || cmd.description();
|
|
258
|
+
}
|
|
259
|
+
optionDescription(option) {
|
|
260
|
+
const extraInfo = [];
|
|
261
|
+
if (option.argChoices) {
|
|
262
|
+
extraInfo.push(`choices: ${option.argChoices.map((choice) => JSON.stringify(choice)).join(", ")}`);
|
|
263
|
+
}
|
|
264
|
+
if (option.defaultValue !== undefined) {
|
|
265
|
+
const showDefault = option.required || option.optional || option.isBoolean() && typeof option.defaultValue === "boolean";
|
|
266
|
+
if (showDefault) {
|
|
267
|
+
extraInfo.push(`default: ${option.defaultValueDescription || JSON.stringify(option.defaultValue)}`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
if (option.presetArg !== undefined && option.optional) {
|
|
271
|
+
extraInfo.push(`preset: ${JSON.stringify(option.presetArg)}`);
|
|
272
|
+
}
|
|
273
|
+
if (option.envVar !== undefined) {
|
|
274
|
+
extraInfo.push(`env: ${option.envVar}`);
|
|
275
|
+
}
|
|
276
|
+
if (extraInfo.length > 0) {
|
|
277
|
+
const extraDescription = `(${extraInfo.join(", ")})`;
|
|
278
|
+
if (option.description) {
|
|
279
|
+
return `${option.description} ${extraDescription}`;
|
|
280
|
+
}
|
|
281
|
+
return extraDescription;
|
|
282
|
+
}
|
|
283
|
+
return option.description;
|
|
284
|
+
}
|
|
285
|
+
argumentDescription(argument) {
|
|
286
|
+
const extraInfo = [];
|
|
287
|
+
if (argument.argChoices) {
|
|
288
|
+
extraInfo.push(`choices: ${argument.argChoices.map((choice) => JSON.stringify(choice)).join(", ")}`);
|
|
289
|
+
}
|
|
290
|
+
if (argument.defaultValue !== undefined) {
|
|
291
|
+
extraInfo.push(`default: ${argument.defaultValueDescription || JSON.stringify(argument.defaultValue)}`);
|
|
292
|
+
}
|
|
293
|
+
if (extraInfo.length > 0) {
|
|
294
|
+
const extraDescription = `(${extraInfo.join(", ")})`;
|
|
295
|
+
if (argument.description) {
|
|
296
|
+
return `${argument.description} ${extraDescription}`;
|
|
297
|
+
}
|
|
298
|
+
return extraDescription;
|
|
299
|
+
}
|
|
300
|
+
return argument.description;
|
|
301
|
+
}
|
|
302
|
+
formatItemList(heading, items, helper) {
|
|
303
|
+
if (items.length === 0)
|
|
304
|
+
return [];
|
|
305
|
+
return [helper.styleTitle(heading), ...items, ""];
|
|
306
|
+
}
|
|
307
|
+
groupItems(unsortedItems, visibleItems, getGroup) {
|
|
308
|
+
const result = new Map;
|
|
309
|
+
unsortedItems.forEach((item) => {
|
|
310
|
+
const group = getGroup(item);
|
|
311
|
+
if (!result.has(group))
|
|
312
|
+
result.set(group, []);
|
|
313
|
+
});
|
|
314
|
+
visibleItems.forEach((item) => {
|
|
315
|
+
const group = getGroup(item);
|
|
316
|
+
if (!result.has(group)) {
|
|
317
|
+
result.set(group, []);
|
|
318
|
+
}
|
|
319
|
+
result.get(group).push(item);
|
|
320
|
+
});
|
|
321
|
+
return result;
|
|
322
|
+
}
|
|
323
|
+
formatHelp(cmd, helper) {
|
|
324
|
+
const termWidth = helper.padWidth(cmd, helper);
|
|
325
|
+
const helpWidth = helper.helpWidth ?? 80;
|
|
326
|
+
function callFormatItem(term, description) {
|
|
327
|
+
return helper.formatItem(term, termWidth, description, helper);
|
|
328
|
+
}
|
|
329
|
+
let output = [
|
|
330
|
+
`${helper.styleTitle("Usage:")} ${helper.styleUsage(helper.commandUsage(cmd))}`,
|
|
331
|
+
""
|
|
332
|
+
];
|
|
333
|
+
const commandDescription = helper.commandDescription(cmd);
|
|
334
|
+
if (commandDescription.length > 0) {
|
|
335
|
+
output = output.concat([
|
|
336
|
+
helper.boxWrap(helper.styleCommandDescription(commandDescription), helpWidth),
|
|
337
|
+
""
|
|
338
|
+
]);
|
|
339
|
+
}
|
|
340
|
+
const argumentList = helper.visibleArguments(cmd).map((argument) => {
|
|
341
|
+
return callFormatItem(helper.styleArgumentTerm(helper.argumentTerm(argument)), helper.styleArgumentDescription(helper.argumentDescription(argument)));
|
|
342
|
+
});
|
|
343
|
+
output = output.concat(this.formatItemList("Arguments:", argumentList, helper));
|
|
344
|
+
const optionGroups = this.groupItems(cmd.options, helper.visibleOptions(cmd), (option) => option.helpGroupHeading ?? "Options:");
|
|
345
|
+
optionGroups.forEach((options, group) => {
|
|
346
|
+
const optionList = options.map((option) => {
|
|
347
|
+
return callFormatItem(helper.styleOptionTerm(helper.optionTerm(option)), helper.styleOptionDescription(helper.optionDescription(option)));
|
|
348
|
+
});
|
|
349
|
+
output = output.concat(this.formatItemList(group, optionList, helper));
|
|
350
|
+
});
|
|
351
|
+
if (helper.showGlobalOptions) {
|
|
352
|
+
const globalOptionList = helper.visibleGlobalOptions(cmd).map((option) => {
|
|
353
|
+
return callFormatItem(helper.styleOptionTerm(helper.optionTerm(option)), helper.styleOptionDescription(helper.optionDescription(option)));
|
|
354
|
+
});
|
|
355
|
+
output = output.concat(this.formatItemList("Global Options:", globalOptionList, helper));
|
|
356
|
+
}
|
|
357
|
+
const commandGroups = this.groupItems(cmd.commands, helper.visibleCommands(cmd), (sub) => sub.helpGroup() || "Commands:");
|
|
358
|
+
commandGroups.forEach((commands, group) => {
|
|
359
|
+
const commandList = commands.map((sub) => {
|
|
360
|
+
return callFormatItem(helper.styleSubcommandTerm(helper.subcommandTerm(sub)), helper.styleSubcommandDescription(helper.subcommandDescription(sub)));
|
|
361
|
+
});
|
|
362
|
+
output = output.concat(this.formatItemList(group, commandList, helper));
|
|
363
|
+
});
|
|
364
|
+
return output.join(`
|
|
365
|
+
`);
|
|
366
|
+
}
|
|
367
|
+
displayWidth(str) {
|
|
368
|
+
return stripVTControlCharacters(str).length;
|
|
369
|
+
}
|
|
370
|
+
styleTitle(str) {
|
|
371
|
+
return str;
|
|
372
|
+
}
|
|
373
|
+
styleUsage(str) {
|
|
374
|
+
return str.split(" ").map((word) => {
|
|
375
|
+
if (word === "[options]")
|
|
376
|
+
return this.styleOptionText(word);
|
|
377
|
+
if (word === "[command]")
|
|
378
|
+
return this.styleSubcommandText(word);
|
|
379
|
+
if (word[0] === "[" || word[0] === "<")
|
|
380
|
+
return this.styleArgumentText(word);
|
|
381
|
+
return this.styleCommandText(word);
|
|
382
|
+
}).join(" ");
|
|
383
|
+
}
|
|
384
|
+
styleCommandDescription(str) {
|
|
385
|
+
return this.styleDescriptionText(str);
|
|
386
|
+
}
|
|
387
|
+
styleOptionDescription(str) {
|
|
388
|
+
return this.styleDescriptionText(str);
|
|
389
|
+
}
|
|
390
|
+
styleSubcommandDescription(str) {
|
|
391
|
+
return this.styleDescriptionText(str);
|
|
392
|
+
}
|
|
393
|
+
styleArgumentDescription(str) {
|
|
394
|
+
return this.styleDescriptionText(str);
|
|
395
|
+
}
|
|
396
|
+
styleDescriptionText(str) {
|
|
397
|
+
return str;
|
|
398
|
+
}
|
|
399
|
+
styleOptionTerm(str) {
|
|
400
|
+
return this.styleOptionText(str);
|
|
401
|
+
}
|
|
402
|
+
styleSubcommandTerm(str) {
|
|
403
|
+
return str.split(" ").map((word) => {
|
|
404
|
+
if (word === "[options]")
|
|
405
|
+
return this.styleOptionText(word);
|
|
406
|
+
if (word[0] === "[" || word[0] === "<")
|
|
407
|
+
return this.styleArgumentText(word);
|
|
408
|
+
return this.styleSubcommandText(word);
|
|
409
|
+
}).join(" ");
|
|
410
|
+
}
|
|
411
|
+
styleArgumentTerm(str) {
|
|
412
|
+
return this.styleArgumentText(str);
|
|
413
|
+
}
|
|
414
|
+
styleOptionText(str) {
|
|
415
|
+
return str;
|
|
416
|
+
}
|
|
417
|
+
styleArgumentText(str) {
|
|
418
|
+
return str;
|
|
419
|
+
}
|
|
420
|
+
styleSubcommandText(str) {
|
|
421
|
+
return str;
|
|
422
|
+
}
|
|
423
|
+
styleCommandText(str) {
|
|
424
|
+
return str;
|
|
425
|
+
}
|
|
426
|
+
padWidth(cmd, helper) {
|
|
427
|
+
return Math.max(helper.longestOptionTermLength(cmd, helper), helper.longestGlobalOptionTermLength(cmd, helper), helper.longestSubcommandTermLength(cmd, helper), helper.longestArgumentTermLength(cmd, helper));
|
|
428
|
+
}
|
|
429
|
+
preformatted(str) {
|
|
430
|
+
return /\n[^\S\r\n]/.test(str);
|
|
431
|
+
}
|
|
432
|
+
formatItem(term, termWidth, description, helper) {
|
|
433
|
+
const itemIndent = 2;
|
|
434
|
+
const itemIndentStr = " ".repeat(itemIndent);
|
|
435
|
+
if (!description)
|
|
436
|
+
return itemIndentStr + term;
|
|
437
|
+
const paddedTerm = term.padEnd(termWidth + term.length - helper.displayWidth(term));
|
|
438
|
+
const spacerWidth = 2;
|
|
439
|
+
const helpWidth = this.helpWidth ?? 80;
|
|
440
|
+
const remainingWidth = helpWidth - termWidth - spacerWidth - itemIndent;
|
|
441
|
+
let formattedDescription;
|
|
442
|
+
if (remainingWidth < this.minWidthToWrap || helper.preformatted(description)) {
|
|
443
|
+
formattedDescription = description;
|
|
444
|
+
} else {
|
|
445
|
+
const wrappedDescription = helper.boxWrap(description, remainingWidth);
|
|
446
|
+
formattedDescription = wrappedDescription.replace(/\n/g, `
|
|
447
|
+
` + " ".repeat(termWidth + spacerWidth));
|
|
448
|
+
}
|
|
449
|
+
return itemIndentStr + paddedTerm + " ".repeat(spacerWidth) + formattedDescription.replace(/\n/g, `
|
|
450
|
+
${itemIndentStr}`);
|
|
451
|
+
}
|
|
452
|
+
boxWrap(str, width) {
|
|
453
|
+
if (width < this.minWidthToWrap)
|
|
454
|
+
return str;
|
|
455
|
+
const rawLines = str.split(/\r\n|\n/);
|
|
456
|
+
const chunkPattern = /[\s]*[^\s]+/g;
|
|
457
|
+
const wrappedLines = [];
|
|
458
|
+
rawLines.forEach((line) => {
|
|
459
|
+
const chunks = line.match(chunkPattern);
|
|
460
|
+
if (chunks === null) {
|
|
461
|
+
wrappedLines.push("");
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
let sumChunks = [chunks.shift()];
|
|
465
|
+
let sumWidth = this.displayWidth(sumChunks[0]);
|
|
466
|
+
chunks.forEach((chunk) => {
|
|
467
|
+
const visibleWidth = this.displayWidth(chunk);
|
|
468
|
+
if (sumWidth + visibleWidth <= width) {
|
|
469
|
+
sumChunks.push(chunk);
|
|
470
|
+
sumWidth += visibleWidth;
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
wrappedLines.push(sumChunks.join(""));
|
|
474
|
+
const nextChunk = chunk.trimStart();
|
|
475
|
+
sumChunks = [nextChunk];
|
|
476
|
+
sumWidth = this.displayWidth(nextChunk);
|
|
477
|
+
});
|
|
478
|
+
wrappedLines.push(sumChunks.join(""));
|
|
479
|
+
});
|
|
480
|
+
return wrappedLines.join(`
|
|
481
|
+
`);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// node_modules/commander/lib/option.js
|
|
486
|
+
class Option {
|
|
487
|
+
constructor(flags, description) {
|
|
488
|
+
this.flags = flags;
|
|
489
|
+
this.description = description || "";
|
|
490
|
+
this.required = flags.includes("<");
|
|
491
|
+
this.optional = flags.includes("[");
|
|
492
|
+
this.variadic = /\w\.\.\.[>\]]$/.test(flags);
|
|
493
|
+
this.mandatory = false;
|
|
494
|
+
const optionFlags = splitOptionFlags(flags);
|
|
495
|
+
this.short = optionFlags.shortFlag;
|
|
496
|
+
this.long = optionFlags.longFlag;
|
|
497
|
+
this.negate = false;
|
|
498
|
+
if (this.long) {
|
|
499
|
+
this.negate = this.long.startsWith("--no-");
|
|
500
|
+
}
|
|
501
|
+
this.defaultValue = undefined;
|
|
502
|
+
this.defaultValueDescription = undefined;
|
|
503
|
+
this.presetArg = undefined;
|
|
504
|
+
this.envVar = undefined;
|
|
505
|
+
this.parseArg = undefined;
|
|
506
|
+
this.hidden = false;
|
|
507
|
+
this.argChoices = undefined;
|
|
508
|
+
this.conflictsWith = [];
|
|
509
|
+
this.implied = undefined;
|
|
510
|
+
this.helpGroupHeading = undefined;
|
|
511
|
+
}
|
|
512
|
+
default(value, description) {
|
|
513
|
+
this.defaultValue = value;
|
|
514
|
+
this.defaultValueDescription = description;
|
|
515
|
+
return this;
|
|
516
|
+
}
|
|
517
|
+
preset(arg) {
|
|
518
|
+
this.presetArg = arg;
|
|
519
|
+
return this;
|
|
520
|
+
}
|
|
521
|
+
conflicts(names) {
|
|
522
|
+
this.conflictsWith = this.conflictsWith.concat(names);
|
|
523
|
+
return this;
|
|
524
|
+
}
|
|
525
|
+
implies(impliedOptionValues) {
|
|
526
|
+
let newImplied = impliedOptionValues;
|
|
527
|
+
if (typeof impliedOptionValues === "string") {
|
|
528
|
+
newImplied = { [impliedOptionValues]: true };
|
|
529
|
+
}
|
|
530
|
+
this.implied = Object.assign(this.implied || {}, newImplied);
|
|
531
|
+
return this;
|
|
532
|
+
}
|
|
533
|
+
env(name) {
|
|
534
|
+
this.envVar = name;
|
|
535
|
+
return this;
|
|
536
|
+
}
|
|
537
|
+
argParser(fn) {
|
|
538
|
+
this.parseArg = fn;
|
|
539
|
+
return this;
|
|
540
|
+
}
|
|
541
|
+
makeOptionMandatory(mandatory = true) {
|
|
542
|
+
this.mandatory = !!mandatory;
|
|
543
|
+
return this;
|
|
544
|
+
}
|
|
545
|
+
hideHelp(hide = true) {
|
|
546
|
+
this.hidden = !!hide;
|
|
547
|
+
return this;
|
|
548
|
+
}
|
|
549
|
+
_collectValue(value, previous) {
|
|
550
|
+
if (previous === this.defaultValue || !Array.isArray(previous)) {
|
|
551
|
+
return [value];
|
|
552
|
+
}
|
|
553
|
+
previous.push(value);
|
|
554
|
+
return previous;
|
|
555
|
+
}
|
|
556
|
+
choices(values) {
|
|
557
|
+
this.argChoices = values.slice();
|
|
558
|
+
this.parseArg = (arg, previous) => {
|
|
559
|
+
if (!this.argChoices.includes(arg)) {
|
|
560
|
+
throw new InvalidArgumentError(`Allowed choices are ${this.argChoices.join(", ")}.`);
|
|
561
|
+
}
|
|
562
|
+
if (this.variadic) {
|
|
563
|
+
return this._collectValue(arg, previous);
|
|
564
|
+
}
|
|
565
|
+
return arg;
|
|
566
|
+
};
|
|
567
|
+
return this;
|
|
568
|
+
}
|
|
569
|
+
name() {
|
|
570
|
+
if (this.long) {
|
|
571
|
+
return this.long.replace(/^--/, "");
|
|
572
|
+
}
|
|
573
|
+
return this.short.replace(/^-/, "");
|
|
574
|
+
}
|
|
575
|
+
attributeName() {
|
|
576
|
+
if (this.negate) {
|
|
577
|
+
return camelcase(this.name().replace(/^no-/, ""));
|
|
578
|
+
}
|
|
579
|
+
return camelcase(this.name());
|
|
580
|
+
}
|
|
581
|
+
helpGroup(heading) {
|
|
582
|
+
this.helpGroupHeading = heading;
|
|
583
|
+
return this;
|
|
584
|
+
}
|
|
585
|
+
is(arg) {
|
|
586
|
+
return this.short === arg || this.long === arg;
|
|
587
|
+
}
|
|
588
|
+
isBoolean() {
|
|
589
|
+
return !this.required && !this.optional && !this.negate;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
class DualOptions {
|
|
594
|
+
constructor(options) {
|
|
595
|
+
this.positiveOptions = new Map;
|
|
596
|
+
this.negativeOptions = new Map;
|
|
597
|
+
this.dualOptions = new Set;
|
|
598
|
+
options.forEach((option) => {
|
|
599
|
+
if (option.negate) {
|
|
600
|
+
this.negativeOptions.set(option.attributeName(), option);
|
|
601
|
+
} else {
|
|
602
|
+
this.positiveOptions.set(option.attributeName(), option);
|
|
603
|
+
}
|
|
604
|
+
});
|
|
605
|
+
this.negativeOptions.forEach((value, key) => {
|
|
606
|
+
if (this.positiveOptions.has(key)) {
|
|
607
|
+
this.dualOptions.add(key);
|
|
608
|
+
}
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
valueFromOption(value, option) {
|
|
612
|
+
const optionKey = option.attributeName();
|
|
613
|
+
if (!this.dualOptions.has(optionKey))
|
|
614
|
+
return true;
|
|
615
|
+
const preset = this.negativeOptions.get(optionKey).presetArg;
|
|
616
|
+
const negativeValue = preset !== undefined ? preset : false;
|
|
617
|
+
return option.negate === (negativeValue === value);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
function camelcase(str) {
|
|
621
|
+
return str.split("-").reduce((str, word) => {
|
|
622
|
+
return str + word[0].toUpperCase() + word.slice(1);
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
function splitOptionFlags(flags) {
|
|
626
|
+
let shortFlag;
|
|
627
|
+
let longFlag;
|
|
628
|
+
const shortFlagExp = /^-[^-]$/;
|
|
629
|
+
const longFlagExp = /^--[^-]/;
|
|
630
|
+
const flagParts = flags.split(/[ |,]+/).concat("guard");
|
|
631
|
+
if (shortFlagExp.test(flagParts[0]))
|
|
632
|
+
shortFlag = flagParts.shift();
|
|
633
|
+
if (longFlagExp.test(flagParts[0]))
|
|
634
|
+
longFlag = flagParts.shift();
|
|
635
|
+
if (!shortFlag && shortFlagExp.test(flagParts[0]))
|
|
636
|
+
shortFlag = flagParts.shift();
|
|
637
|
+
if (!shortFlag && longFlagExp.test(flagParts[0])) {
|
|
638
|
+
shortFlag = longFlag;
|
|
639
|
+
longFlag = flagParts.shift();
|
|
640
|
+
}
|
|
641
|
+
if (flagParts[0].startsWith("-")) {
|
|
642
|
+
const unsupportedFlag = flagParts[0];
|
|
643
|
+
const baseError = `option creation failed due to '${unsupportedFlag}' in option flags '${flags}'`;
|
|
644
|
+
if (/^-[^-][^-]/.test(unsupportedFlag))
|
|
645
|
+
throw new Error(`${baseError}
|
|
646
|
+
- a short flag is a single dash and a single character
|
|
647
|
+
- either use a single dash and a single character (for a short flag)
|
|
648
|
+
- or use a double dash for a long option (and can have two, like '--ws, --workspace')`);
|
|
649
|
+
if (shortFlagExp.test(unsupportedFlag))
|
|
650
|
+
throw new Error(`${baseError}
|
|
651
|
+
- too many short flags`);
|
|
652
|
+
if (longFlagExp.test(unsupportedFlag))
|
|
653
|
+
throw new Error(`${baseError}
|
|
654
|
+
- too many long flags`);
|
|
655
|
+
throw new Error(`${baseError}
|
|
656
|
+
- unrecognised flag format`);
|
|
657
|
+
}
|
|
658
|
+
if (shortFlag === undefined && longFlag === undefined)
|
|
659
|
+
throw new Error(`option creation failed due to no flags found in '${flags}'.`);
|
|
660
|
+
return { shortFlag, longFlag };
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// node_modules/commander/lib/suggestSimilar.js
|
|
664
|
+
var maxDistance = 3;
|
|
665
|
+
function editDistance(a, b) {
|
|
666
|
+
if (Math.abs(a.length - b.length) > maxDistance)
|
|
667
|
+
return Math.max(a.length, b.length);
|
|
668
|
+
const d = [];
|
|
669
|
+
for (let i = 0;i <= a.length; i++) {
|
|
670
|
+
d[i] = [i];
|
|
671
|
+
}
|
|
672
|
+
for (let j = 0;j <= b.length; j++) {
|
|
673
|
+
d[0][j] = j;
|
|
674
|
+
}
|
|
675
|
+
for (let j = 1;j <= b.length; j++) {
|
|
676
|
+
for (let i = 1;i <= a.length; i++) {
|
|
677
|
+
let cost;
|
|
678
|
+
if (a[i - 1] === b[j - 1]) {
|
|
679
|
+
cost = 0;
|
|
680
|
+
} else {
|
|
681
|
+
cost = 1;
|
|
682
|
+
}
|
|
683
|
+
d[i][j] = Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost);
|
|
684
|
+
if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
|
|
685
|
+
d[i][j] = Math.min(d[i][j], d[i - 2][j - 2] + 1);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
return d[a.length][b.length];
|
|
690
|
+
}
|
|
691
|
+
function suggestSimilar(word, candidates) {
|
|
692
|
+
if (!candidates || candidates.length === 0)
|
|
693
|
+
return "";
|
|
694
|
+
candidates = Array.from(new Set(candidates));
|
|
695
|
+
const searchingOptions = word.startsWith("--");
|
|
696
|
+
if (searchingOptions) {
|
|
697
|
+
word = word.slice(2);
|
|
698
|
+
candidates = candidates.map((candidate) => candidate.slice(2));
|
|
699
|
+
}
|
|
700
|
+
let similar = [];
|
|
701
|
+
let bestDistance = maxDistance;
|
|
702
|
+
const minSimilarity = 0.4;
|
|
703
|
+
candidates.forEach((candidate) => {
|
|
704
|
+
if (candidate.length <= 1)
|
|
705
|
+
return;
|
|
706
|
+
const distance = editDistance(word, candidate);
|
|
707
|
+
const length = Math.max(word.length, candidate.length);
|
|
708
|
+
const similarity = (length - distance) / length;
|
|
709
|
+
if (similarity > minSimilarity) {
|
|
710
|
+
if (distance < bestDistance) {
|
|
711
|
+
bestDistance = distance;
|
|
712
|
+
similar = [candidate];
|
|
713
|
+
} else if (distance === bestDistance) {
|
|
714
|
+
similar.push(candidate);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
});
|
|
718
|
+
similar.sort((a, b) => a.localeCompare(b));
|
|
719
|
+
if (searchingOptions) {
|
|
720
|
+
similar = similar.map((candidate) => `--${candidate}`);
|
|
721
|
+
}
|
|
722
|
+
if (similar.length > 1) {
|
|
723
|
+
return `
|
|
724
|
+
(Did you mean one of ${similar.join(", ")}?)`;
|
|
725
|
+
}
|
|
726
|
+
if (similar.length === 1) {
|
|
727
|
+
return `
|
|
728
|
+
(Did you mean ${similar[0]}?)`;
|
|
729
|
+
}
|
|
730
|
+
return "";
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// node_modules/commander/lib/command.js
|
|
734
|
+
class Command extends EventEmitter {
|
|
735
|
+
constructor(name) {
|
|
736
|
+
super();
|
|
737
|
+
this.commands = [];
|
|
738
|
+
this.options = [];
|
|
739
|
+
this.parent = null;
|
|
740
|
+
this._allowUnknownOption = false;
|
|
741
|
+
this._allowExcessArguments = false;
|
|
742
|
+
this.registeredArguments = [];
|
|
743
|
+
this._args = this.registeredArguments;
|
|
744
|
+
this.args = [];
|
|
745
|
+
this.rawArgs = [];
|
|
746
|
+
this.processedArgs = [];
|
|
747
|
+
this._scriptPath = null;
|
|
748
|
+
this._name = name || "";
|
|
749
|
+
this._optionValues = {};
|
|
750
|
+
this._optionValueSources = {};
|
|
751
|
+
this._storeOptionsAsProperties = false;
|
|
752
|
+
this._actionHandler = null;
|
|
753
|
+
this._executableHandler = false;
|
|
754
|
+
this._executableFile = null;
|
|
755
|
+
this._executableDir = null;
|
|
756
|
+
this._defaultCommandName = null;
|
|
757
|
+
this._exitCallback = null;
|
|
758
|
+
this._aliases = [];
|
|
759
|
+
this._combineFlagAndOptionalValue = true;
|
|
760
|
+
this._description = "";
|
|
761
|
+
this._summary = "";
|
|
762
|
+
this._argsDescription = undefined;
|
|
763
|
+
this._enablePositionalOptions = false;
|
|
764
|
+
this._passThroughOptions = false;
|
|
765
|
+
this._lifeCycleHooks = {};
|
|
766
|
+
this._showHelpAfterError = false;
|
|
767
|
+
this._showSuggestionAfterError = true;
|
|
768
|
+
this._savedState = null;
|
|
769
|
+
this._outputConfiguration = {
|
|
770
|
+
writeOut: (str) => process2.stdout.write(str),
|
|
771
|
+
writeErr: (str) => process2.stderr.write(str),
|
|
772
|
+
outputError: (str, write) => write(str),
|
|
773
|
+
getOutHelpWidth: () => process2.stdout.isTTY ? process2.stdout.columns : undefined,
|
|
774
|
+
getErrHelpWidth: () => process2.stderr.isTTY ? process2.stderr.columns : undefined,
|
|
775
|
+
getOutHasColors: () => useColor() ?? (process2.stdout.isTTY && process2.stdout.hasColors?.()),
|
|
776
|
+
getErrHasColors: () => useColor() ?? (process2.stderr.isTTY && process2.stderr.hasColors?.()),
|
|
777
|
+
stripColor: (str) => stripVTControlCharacters2(str)
|
|
778
|
+
};
|
|
779
|
+
this._hidden = false;
|
|
780
|
+
this._helpOption = undefined;
|
|
781
|
+
this._addImplicitHelpCommand = undefined;
|
|
782
|
+
this._helpCommand = undefined;
|
|
783
|
+
this._helpConfiguration = {};
|
|
784
|
+
this._helpGroupHeading = undefined;
|
|
785
|
+
this._defaultCommandGroup = undefined;
|
|
786
|
+
this._defaultOptionGroup = undefined;
|
|
787
|
+
}
|
|
788
|
+
copyInheritedSettings(sourceCommand) {
|
|
789
|
+
this._outputConfiguration = sourceCommand._outputConfiguration;
|
|
790
|
+
this._helpOption = sourceCommand._helpOption;
|
|
791
|
+
this._helpCommand = sourceCommand._helpCommand;
|
|
792
|
+
this._helpConfiguration = sourceCommand._helpConfiguration;
|
|
793
|
+
this._exitCallback = sourceCommand._exitCallback;
|
|
794
|
+
this._storeOptionsAsProperties = sourceCommand._storeOptionsAsProperties;
|
|
795
|
+
this._combineFlagAndOptionalValue = sourceCommand._combineFlagAndOptionalValue;
|
|
796
|
+
this._allowExcessArguments = sourceCommand._allowExcessArguments;
|
|
797
|
+
this._enablePositionalOptions = sourceCommand._enablePositionalOptions;
|
|
798
|
+
this._showHelpAfterError = sourceCommand._showHelpAfterError;
|
|
799
|
+
this._showSuggestionAfterError = sourceCommand._showSuggestionAfterError;
|
|
800
|
+
return this;
|
|
801
|
+
}
|
|
802
|
+
_getCommandAndAncestors() {
|
|
803
|
+
const result = [];
|
|
804
|
+
for (let command = this;command; command = command.parent) {
|
|
805
|
+
result.push(command);
|
|
806
|
+
}
|
|
807
|
+
return result;
|
|
808
|
+
}
|
|
809
|
+
command(nameAndArgs, actionOptsOrExecDesc, execOpts) {
|
|
810
|
+
let desc = actionOptsOrExecDesc;
|
|
811
|
+
let opts = execOpts;
|
|
812
|
+
if (typeof desc === "object" && desc !== null) {
|
|
813
|
+
opts = desc;
|
|
814
|
+
desc = null;
|
|
815
|
+
}
|
|
816
|
+
opts = opts || {};
|
|
817
|
+
const [, name, args] = nameAndArgs.match(/([^ ]+) *(.*)/);
|
|
818
|
+
const cmd = this.createCommand(name);
|
|
819
|
+
if (desc) {
|
|
820
|
+
cmd.description(desc);
|
|
821
|
+
cmd._executableHandler = true;
|
|
822
|
+
}
|
|
823
|
+
if (opts.isDefault)
|
|
824
|
+
this._defaultCommandName = cmd._name;
|
|
825
|
+
cmd._hidden = !!(opts.noHelp || opts.hidden);
|
|
826
|
+
cmd._executableFile = opts.executableFile || null;
|
|
827
|
+
if (args)
|
|
828
|
+
cmd.arguments(args);
|
|
829
|
+
this._registerCommand(cmd);
|
|
830
|
+
cmd.parent = this;
|
|
831
|
+
cmd.copyInheritedSettings(this);
|
|
832
|
+
if (desc)
|
|
833
|
+
return this;
|
|
834
|
+
return cmd;
|
|
835
|
+
}
|
|
836
|
+
createCommand(name) {
|
|
837
|
+
return new Command(name);
|
|
838
|
+
}
|
|
839
|
+
createHelp() {
|
|
840
|
+
return Object.assign(new Help, this.configureHelp());
|
|
841
|
+
}
|
|
842
|
+
configureHelp(configuration) {
|
|
843
|
+
if (configuration === undefined)
|
|
844
|
+
return this._helpConfiguration;
|
|
845
|
+
this._helpConfiguration = configuration;
|
|
846
|
+
return this;
|
|
847
|
+
}
|
|
848
|
+
configureOutput(configuration) {
|
|
849
|
+
if (configuration === undefined)
|
|
850
|
+
return this._outputConfiguration;
|
|
851
|
+
this._outputConfiguration = {
|
|
852
|
+
...this._outputConfiguration,
|
|
853
|
+
...configuration
|
|
854
|
+
};
|
|
855
|
+
return this;
|
|
856
|
+
}
|
|
857
|
+
showHelpAfterError(displayHelp = true) {
|
|
858
|
+
if (typeof displayHelp !== "string")
|
|
859
|
+
displayHelp = !!displayHelp;
|
|
860
|
+
this._showHelpAfterError = displayHelp;
|
|
861
|
+
return this;
|
|
862
|
+
}
|
|
863
|
+
showSuggestionAfterError(displaySuggestion = true) {
|
|
864
|
+
this._showSuggestionAfterError = !!displaySuggestion;
|
|
865
|
+
return this;
|
|
866
|
+
}
|
|
867
|
+
addCommand(cmd, opts) {
|
|
868
|
+
if (!cmd._name) {
|
|
869
|
+
throw new Error(`Command passed to .addCommand() must have a name
|
|
870
|
+
- specify the name in Command constructor or using .name()`);
|
|
871
|
+
}
|
|
872
|
+
opts = opts || {};
|
|
873
|
+
if (opts.isDefault)
|
|
874
|
+
this._defaultCommandName = cmd._name;
|
|
875
|
+
if (opts.noHelp || opts.hidden)
|
|
876
|
+
cmd._hidden = true;
|
|
877
|
+
this._registerCommand(cmd);
|
|
878
|
+
cmd.parent = this;
|
|
879
|
+
cmd._checkForBrokenPassThrough();
|
|
880
|
+
return this;
|
|
881
|
+
}
|
|
882
|
+
createArgument(name, description) {
|
|
883
|
+
return new Argument(name, description);
|
|
884
|
+
}
|
|
885
|
+
argument(name, description, parseArg, defaultValue) {
|
|
886
|
+
const argument = this.createArgument(name, description);
|
|
887
|
+
if (typeof parseArg === "function") {
|
|
888
|
+
argument.default(defaultValue).argParser(parseArg);
|
|
889
|
+
} else {
|
|
890
|
+
argument.default(parseArg);
|
|
891
|
+
}
|
|
892
|
+
this.addArgument(argument);
|
|
893
|
+
return this;
|
|
894
|
+
}
|
|
895
|
+
arguments(names) {
|
|
896
|
+
names.trim().split(/ +/).forEach((detail) => {
|
|
897
|
+
this.argument(detail);
|
|
898
|
+
});
|
|
899
|
+
return this;
|
|
900
|
+
}
|
|
901
|
+
addArgument(argument) {
|
|
902
|
+
const previousArgument = this.registeredArguments.slice(-1)[0];
|
|
903
|
+
if (previousArgument?.variadic) {
|
|
904
|
+
throw new Error(`only the last argument can be variadic '${previousArgument.name()}'`);
|
|
905
|
+
}
|
|
906
|
+
if (argument.required && argument.defaultValue !== undefined && argument.parseArg === undefined) {
|
|
907
|
+
throw new Error(`a default value for a required argument is never used: '${argument.name()}'`);
|
|
908
|
+
}
|
|
909
|
+
this.registeredArguments.push(argument);
|
|
910
|
+
return this;
|
|
911
|
+
}
|
|
912
|
+
helpCommand(enableOrNameAndArgs, description) {
|
|
913
|
+
if (typeof enableOrNameAndArgs === "boolean") {
|
|
914
|
+
this._addImplicitHelpCommand = enableOrNameAndArgs;
|
|
915
|
+
if (enableOrNameAndArgs && this._defaultCommandGroup) {
|
|
916
|
+
this._initCommandGroup(this._getHelpCommand());
|
|
917
|
+
}
|
|
918
|
+
return this;
|
|
919
|
+
}
|
|
920
|
+
const nameAndArgs = enableOrNameAndArgs ?? "help [command]";
|
|
921
|
+
const [, helpName, helpArgs] = nameAndArgs.match(/([^ ]+) *(.*)/);
|
|
922
|
+
const helpDescription = description ?? "display help for command";
|
|
923
|
+
const helpCommand = this.createCommand(helpName);
|
|
924
|
+
helpCommand.helpOption(false);
|
|
925
|
+
if (helpArgs)
|
|
926
|
+
helpCommand.arguments(helpArgs);
|
|
927
|
+
if (helpDescription)
|
|
928
|
+
helpCommand.description(helpDescription);
|
|
929
|
+
this._addImplicitHelpCommand = true;
|
|
930
|
+
this._helpCommand = helpCommand;
|
|
931
|
+
if (enableOrNameAndArgs || description)
|
|
932
|
+
this._initCommandGroup(helpCommand);
|
|
933
|
+
return this;
|
|
934
|
+
}
|
|
935
|
+
addHelpCommand(helpCommand, deprecatedDescription) {
|
|
936
|
+
if (typeof helpCommand !== "object") {
|
|
937
|
+
this.helpCommand(helpCommand, deprecatedDescription);
|
|
938
|
+
return this;
|
|
939
|
+
}
|
|
940
|
+
this._addImplicitHelpCommand = true;
|
|
941
|
+
this._helpCommand = helpCommand;
|
|
942
|
+
this._initCommandGroup(helpCommand);
|
|
943
|
+
return this;
|
|
944
|
+
}
|
|
945
|
+
_getHelpCommand() {
|
|
946
|
+
const hasImplicitHelpCommand = this._addImplicitHelpCommand ?? (this.commands.length && !this._actionHandler && !this._findCommand("help"));
|
|
947
|
+
if (hasImplicitHelpCommand) {
|
|
948
|
+
if (this._helpCommand === undefined) {
|
|
949
|
+
this.helpCommand(undefined, undefined);
|
|
950
|
+
}
|
|
951
|
+
return this._helpCommand;
|
|
952
|
+
}
|
|
953
|
+
return null;
|
|
954
|
+
}
|
|
955
|
+
hook(event, listener) {
|
|
956
|
+
const allowedValues = ["preSubcommand", "preAction", "postAction"];
|
|
957
|
+
if (!allowedValues.includes(event)) {
|
|
958
|
+
throw new Error(`Unexpected value for event passed to hook : '${event}'.
|
|
959
|
+
Expecting one of '${allowedValues.join("', '")}'`);
|
|
960
|
+
}
|
|
961
|
+
if (this._lifeCycleHooks[event]) {
|
|
962
|
+
this._lifeCycleHooks[event].push(listener);
|
|
963
|
+
} else {
|
|
964
|
+
this._lifeCycleHooks[event] = [listener];
|
|
965
|
+
}
|
|
966
|
+
return this;
|
|
967
|
+
}
|
|
968
|
+
exitOverride(fn) {
|
|
969
|
+
if (fn) {
|
|
970
|
+
this._exitCallback = fn;
|
|
971
|
+
} else {
|
|
972
|
+
this._exitCallback = (err) => {
|
|
973
|
+
if (err.code !== "commander.executeSubCommandAsync") {
|
|
974
|
+
throw err;
|
|
975
|
+
}
|
|
976
|
+
};
|
|
977
|
+
}
|
|
978
|
+
return this;
|
|
979
|
+
}
|
|
980
|
+
_exit(exitCode, code, message) {
|
|
981
|
+
if (this._exitCallback) {
|
|
982
|
+
this._exitCallback(new CommanderError(exitCode, code, message));
|
|
983
|
+
}
|
|
984
|
+
process2.exit(exitCode);
|
|
985
|
+
}
|
|
986
|
+
action(fn) {
|
|
987
|
+
const listener = (args) => {
|
|
988
|
+
const expectedArgsCount = this.registeredArguments.length;
|
|
989
|
+
const actionArgs = args.slice(0, expectedArgsCount);
|
|
990
|
+
if (this._storeOptionsAsProperties) {
|
|
991
|
+
actionArgs[expectedArgsCount] = this;
|
|
992
|
+
} else {
|
|
993
|
+
actionArgs[expectedArgsCount] = this.opts();
|
|
994
|
+
}
|
|
995
|
+
actionArgs.push(this);
|
|
996
|
+
return fn.apply(this, actionArgs);
|
|
997
|
+
};
|
|
998
|
+
this._actionHandler = listener;
|
|
999
|
+
return this;
|
|
1000
|
+
}
|
|
1001
|
+
createOption(flags, description) {
|
|
1002
|
+
return new Option(flags, description);
|
|
1003
|
+
}
|
|
1004
|
+
_callParseArg(target, value, previous, invalidArgumentMessage) {
|
|
1005
|
+
try {
|
|
1006
|
+
return target.parseArg(value, previous);
|
|
1007
|
+
} catch (err) {
|
|
1008
|
+
if (err.code === "commander.invalidArgument") {
|
|
1009
|
+
const message = `${invalidArgumentMessage} ${err.message}`;
|
|
1010
|
+
this.error(message, { exitCode: err.exitCode, code: err.code });
|
|
1011
|
+
}
|
|
1012
|
+
throw err;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
_registerOption(option) {
|
|
1016
|
+
const matchingOption = option.short && this._findOption(option.short) || option.long && this._findOption(option.long);
|
|
1017
|
+
if (matchingOption) {
|
|
1018
|
+
const matchingFlag = option.long && this._findOption(option.long) ? option.long : option.short;
|
|
1019
|
+
throw new Error(`Cannot add option '${option.flags}'${this._name && ` to command '${this._name}'`} due to conflicting flag '${matchingFlag}'
|
|
1020
|
+
- already used by option '${matchingOption.flags}'`);
|
|
1021
|
+
}
|
|
1022
|
+
this._initOptionGroup(option);
|
|
1023
|
+
this.options.push(option);
|
|
1024
|
+
}
|
|
1025
|
+
_registerCommand(command) {
|
|
1026
|
+
const knownBy = (cmd) => {
|
|
1027
|
+
return [cmd.name()].concat(cmd.aliases());
|
|
1028
|
+
};
|
|
1029
|
+
const alreadyUsed = knownBy(command).find((name) => this._findCommand(name));
|
|
1030
|
+
if (alreadyUsed) {
|
|
1031
|
+
const existingCmd = knownBy(this._findCommand(alreadyUsed)).join("|");
|
|
1032
|
+
const newCmd = knownBy(command).join("|");
|
|
1033
|
+
throw new Error(`cannot add command '${newCmd}' as already have command '${existingCmd}'`);
|
|
1034
|
+
}
|
|
1035
|
+
this._initCommandGroup(command);
|
|
1036
|
+
this.commands.push(command);
|
|
1037
|
+
}
|
|
1038
|
+
addOption(option) {
|
|
1039
|
+
this._registerOption(option);
|
|
1040
|
+
const oname = option.name();
|
|
1041
|
+
const name = option.attributeName();
|
|
1042
|
+
if (option.defaultValue !== undefined) {
|
|
1043
|
+
this.setOptionValueWithSource(name, option.defaultValue, "default");
|
|
1044
|
+
}
|
|
1045
|
+
const handleOptionValue = (val, invalidValueMessage, valueSource) => {
|
|
1046
|
+
if (val == null && option.presetArg !== undefined) {
|
|
1047
|
+
val = option.presetArg;
|
|
1048
|
+
}
|
|
1049
|
+
const oldValue = this.getOptionValue(name);
|
|
1050
|
+
if (val !== null && option.parseArg) {
|
|
1051
|
+
val = this._callParseArg(option, val, oldValue, invalidValueMessage);
|
|
1052
|
+
} else if (val !== null && option.variadic) {
|
|
1053
|
+
val = option._collectValue(val, oldValue);
|
|
1054
|
+
}
|
|
1055
|
+
if (val == null) {
|
|
1056
|
+
if (option.negate) {
|
|
1057
|
+
val = false;
|
|
1058
|
+
} else if (option.isBoolean() || option.optional) {
|
|
1059
|
+
val = true;
|
|
1060
|
+
} else {
|
|
1061
|
+
val = "";
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
this.setOptionValueWithSource(name, val, valueSource);
|
|
1065
|
+
};
|
|
1066
|
+
this.on("option:" + oname, (val) => {
|
|
1067
|
+
const invalidValueMessage = `error: option '${option.flags}' argument '${val}' is invalid.`;
|
|
1068
|
+
handleOptionValue(val, invalidValueMessage, "cli");
|
|
1069
|
+
});
|
|
1070
|
+
if (option.envVar) {
|
|
1071
|
+
this.on("optionEnv:" + oname, (val) => {
|
|
1072
|
+
const invalidValueMessage = `error: option '${option.flags}' value '${val}' from env '${option.envVar}' is invalid.`;
|
|
1073
|
+
handleOptionValue(val, invalidValueMessage, "env");
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
1076
|
+
return this;
|
|
1077
|
+
}
|
|
1078
|
+
_optionEx(config, flags, description, fn, defaultValue) {
|
|
1079
|
+
if (typeof flags === "object" && flags instanceof Option) {
|
|
1080
|
+
throw new Error("To add an Option object use addOption() instead of option() or requiredOption()");
|
|
1081
|
+
}
|
|
1082
|
+
const option = this.createOption(flags, description);
|
|
1083
|
+
option.makeOptionMandatory(!!config.mandatory);
|
|
1084
|
+
if (typeof fn === "function") {
|
|
1085
|
+
option.default(defaultValue).argParser(fn);
|
|
1086
|
+
} else if (fn instanceof RegExp) {
|
|
1087
|
+
const regex = fn;
|
|
1088
|
+
fn = (val, def) => {
|
|
1089
|
+
const m = regex.exec(val);
|
|
1090
|
+
return m ? m[0] : def;
|
|
1091
|
+
};
|
|
1092
|
+
option.default(defaultValue).argParser(fn);
|
|
1093
|
+
} else {
|
|
1094
|
+
option.default(fn);
|
|
1095
|
+
}
|
|
1096
|
+
return this.addOption(option);
|
|
1097
|
+
}
|
|
1098
|
+
option(flags, description, parseArg, defaultValue) {
|
|
1099
|
+
return this._optionEx({}, flags, description, parseArg, defaultValue);
|
|
1100
|
+
}
|
|
1101
|
+
requiredOption(flags, description, parseArg, defaultValue) {
|
|
1102
|
+
return this._optionEx({ mandatory: true }, flags, description, parseArg, defaultValue);
|
|
1103
|
+
}
|
|
1104
|
+
combineFlagAndOptionalValue(combine = true) {
|
|
1105
|
+
this._combineFlagAndOptionalValue = !!combine;
|
|
1106
|
+
return this;
|
|
1107
|
+
}
|
|
1108
|
+
allowUnknownOption(allowUnknown = true) {
|
|
1109
|
+
this._allowUnknownOption = !!allowUnknown;
|
|
1110
|
+
return this;
|
|
1111
|
+
}
|
|
1112
|
+
allowExcessArguments(allowExcess = true) {
|
|
1113
|
+
this._allowExcessArguments = !!allowExcess;
|
|
1114
|
+
return this;
|
|
1115
|
+
}
|
|
1116
|
+
enablePositionalOptions(positional = true) {
|
|
1117
|
+
this._enablePositionalOptions = !!positional;
|
|
1118
|
+
return this;
|
|
1119
|
+
}
|
|
1120
|
+
passThroughOptions(passThrough = true) {
|
|
1121
|
+
this._passThroughOptions = !!passThrough;
|
|
1122
|
+
this._checkForBrokenPassThrough();
|
|
1123
|
+
return this;
|
|
1124
|
+
}
|
|
1125
|
+
_checkForBrokenPassThrough() {
|
|
1126
|
+
if (this.parent && this._passThroughOptions && !this.parent._enablePositionalOptions) {
|
|
1127
|
+
throw new Error(`passThroughOptions cannot be used for '${this._name}' without turning on enablePositionalOptions for parent command(s)`);
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
storeOptionsAsProperties(storeAsProperties = true) {
|
|
1131
|
+
if (this.options.length) {
|
|
1132
|
+
throw new Error("call .storeOptionsAsProperties() before adding options");
|
|
1133
|
+
}
|
|
1134
|
+
if (Object.keys(this._optionValues).length) {
|
|
1135
|
+
throw new Error("call .storeOptionsAsProperties() before setting option values");
|
|
1136
|
+
}
|
|
1137
|
+
this._storeOptionsAsProperties = !!storeAsProperties;
|
|
1138
|
+
return this;
|
|
1139
|
+
}
|
|
1140
|
+
getOptionValue(key) {
|
|
1141
|
+
if (this._storeOptionsAsProperties) {
|
|
1142
|
+
return this[key];
|
|
1143
|
+
}
|
|
1144
|
+
return this._optionValues[key];
|
|
1145
|
+
}
|
|
1146
|
+
setOptionValue(key, value) {
|
|
1147
|
+
return this.setOptionValueWithSource(key, value, undefined);
|
|
1148
|
+
}
|
|
1149
|
+
setOptionValueWithSource(key, value, source) {
|
|
1150
|
+
if (this._storeOptionsAsProperties) {
|
|
1151
|
+
this[key] = value;
|
|
1152
|
+
} else {
|
|
1153
|
+
this._optionValues[key] = value;
|
|
1154
|
+
}
|
|
1155
|
+
this._optionValueSources[key] = source;
|
|
1156
|
+
return this;
|
|
1157
|
+
}
|
|
1158
|
+
getOptionValueSource(key) {
|
|
1159
|
+
return this._optionValueSources[key];
|
|
1160
|
+
}
|
|
1161
|
+
getOptionValueSourceWithGlobals(key) {
|
|
1162
|
+
let source;
|
|
1163
|
+
this._getCommandAndAncestors().forEach((cmd) => {
|
|
1164
|
+
if (cmd.getOptionValueSource(key) !== undefined) {
|
|
1165
|
+
source = cmd.getOptionValueSource(key);
|
|
1166
|
+
}
|
|
1167
|
+
});
|
|
1168
|
+
return source;
|
|
1169
|
+
}
|
|
1170
|
+
_prepareUserArgs(argv, parseOptions) {
|
|
1171
|
+
if (argv !== undefined && !Array.isArray(argv)) {
|
|
1172
|
+
throw new Error("first parameter to parse must be array or undefined");
|
|
1173
|
+
}
|
|
1174
|
+
parseOptions = parseOptions || {};
|
|
1175
|
+
if (argv === undefined && parseOptions.from === undefined) {
|
|
1176
|
+
if (process2.versions?.electron) {
|
|
1177
|
+
parseOptions.from = "electron";
|
|
1178
|
+
}
|
|
1179
|
+
const execArgv = process2.execArgv ?? [];
|
|
1180
|
+
if (execArgv.includes("-e") || execArgv.includes("--eval") || execArgv.includes("-p") || execArgv.includes("--print")) {
|
|
1181
|
+
parseOptions.from = "eval";
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
if (argv === undefined) {
|
|
1185
|
+
argv = process2.argv;
|
|
1186
|
+
}
|
|
1187
|
+
this.rawArgs = argv.slice();
|
|
1188
|
+
let userArgs;
|
|
1189
|
+
switch (parseOptions.from) {
|
|
1190
|
+
case undefined:
|
|
1191
|
+
case "node":
|
|
1192
|
+
this._scriptPath = argv[1];
|
|
1193
|
+
userArgs = argv.slice(2);
|
|
1194
|
+
break;
|
|
1195
|
+
case "electron":
|
|
1196
|
+
if (process2.defaultApp) {
|
|
1197
|
+
this._scriptPath = argv[1];
|
|
1198
|
+
userArgs = argv.slice(2);
|
|
1199
|
+
} else {
|
|
1200
|
+
userArgs = argv.slice(1);
|
|
1201
|
+
}
|
|
1202
|
+
break;
|
|
1203
|
+
case "user":
|
|
1204
|
+
userArgs = argv.slice(0);
|
|
1205
|
+
break;
|
|
1206
|
+
case "eval":
|
|
1207
|
+
userArgs = argv.slice(1);
|
|
1208
|
+
break;
|
|
1209
|
+
default:
|
|
1210
|
+
throw new Error(`unexpected parse option { from: '${parseOptions.from}' }`);
|
|
1211
|
+
}
|
|
1212
|
+
if (!this._name && this._scriptPath)
|
|
1213
|
+
this.nameFromFilename(this._scriptPath);
|
|
1214
|
+
this._name = this._name || "program";
|
|
1215
|
+
return userArgs;
|
|
1216
|
+
}
|
|
1217
|
+
parse(argv, parseOptions) {
|
|
1218
|
+
this._prepareForParse();
|
|
1219
|
+
const userArgs = this._prepareUserArgs(argv, parseOptions);
|
|
1220
|
+
this._parseCommand([], userArgs);
|
|
1221
|
+
return this;
|
|
1222
|
+
}
|
|
1223
|
+
async parseAsync(argv, parseOptions) {
|
|
1224
|
+
this._prepareForParse();
|
|
1225
|
+
const userArgs = this._prepareUserArgs(argv, parseOptions);
|
|
1226
|
+
await this._parseCommand([], userArgs);
|
|
1227
|
+
return this;
|
|
1228
|
+
}
|
|
1229
|
+
_prepareForParse() {
|
|
1230
|
+
if (this._savedState === null) {
|
|
1231
|
+
this.options.filter((option) => option.negate && option.defaultValue === undefined && this.getOptionValue(option.attributeName()) === undefined).forEach((option) => {
|
|
1232
|
+
const positiveLongFlag = option.long.replace(/^--no-/, "--");
|
|
1233
|
+
if (!this._findOption(positiveLongFlag)) {
|
|
1234
|
+
this.setOptionValueWithSource(option.attributeName(), true, "default");
|
|
1235
|
+
}
|
|
1236
|
+
});
|
|
1237
|
+
this.saveStateBeforeParse();
|
|
1238
|
+
} else {
|
|
1239
|
+
this.restoreStateBeforeParse();
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
saveStateBeforeParse() {
|
|
1243
|
+
this._savedState = {
|
|
1244
|
+
_name: this._name,
|
|
1245
|
+
_optionValues: { ...this._optionValues },
|
|
1246
|
+
_optionValueSources: { ...this._optionValueSources }
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
1249
|
+
restoreStateBeforeParse() {
|
|
1250
|
+
if (this._storeOptionsAsProperties)
|
|
1251
|
+
throw new Error(`Can not call parse again when storeOptionsAsProperties is true.
|
|
1252
|
+
- either make a new Command for each call to parse, or stop storing options as properties`);
|
|
1253
|
+
this._name = this._savedState._name;
|
|
1254
|
+
this._scriptPath = null;
|
|
1255
|
+
this.rawArgs = [];
|
|
1256
|
+
this._optionValues = { ...this._savedState._optionValues };
|
|
1257
|
+
this._optionValueSources = { ...this._savedState._optionValueSources };
|
|
1258
|
+
this.args = [];
|
|
1259
|
+
this.processedArgs = [];
|
|
1260
|
+
}
|
|
1261
|
+
_checkForMissingExecutable(executableFile, executableDir, subcommandName) {
|
|
1262
|
+
if (fs.existsSync(executableFile))
|
|
1263
|
+
return;
|
|
1264
|
+
const executableDirMessage = executableDir ? `searched for local subcommand relative to directory '${executableDir}'` : "no directory for search for local subcommand, use .executableDir() to supply a custom directory";
|
|
1265
|
+
const executableMissing = `'${executableFile}' does not exist
|
|
1266
|
+
- if '${subcommandName}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
|
|
1267
|
+
- if the default executable name is not suitable, use the executableFile option to supply a custom name or path
|
|
1268
|
+
- ${executableDirMessage}`;
|
|
1269
|
+
throw new Error(executableMissing);
|
|
1270
|
+
}
|
|
1271
|
+
_executeSubCommand(subcommand, args) {
|
|
1272
|
+
args = args.slice();
|
|
1273
|
+
const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
|
|
1274
|
+
function findFile(baseDir, baseName) {
|
|
1275
|
+
const localBin = path.resolve(baseDir, baseName);
|
|
1276
|
+
if (fs.existsSync(localBin))
|
|
1277
|
+
return localBin;
|
|
1278
|
+
if (sourceExt.includes(path.extname(baseName)))
|
|
1279
|
+
return;
|
|
1280
|
+
const foundExt = sourceExt.find((ext) => fs.existsSync(`${localBin}${ext}`));
|
|
1281
|
+
if (foundExt)
|
|
1282
|
+
return `${localBin}${foundExt}`;
|
|
1283
|
+
return;
|
|
1284
|
+
}
|
|
1285
|
+
this._checkForMissingMandatoryOptions();
|
|
1286
|
+
this._checkForConflictingOptions();
|
|
1287
|
+
let executableFile = subcommand._executableFile || `${this._name}-${subcommand._name}`;
|
|
1288
|
+
let executableDir = this._executableDir || "";
|
|
1289
|
+
if (this._scriptPath) {
|
|
1290
|
+
let resolvedScriptPath;
|
|
1291
|
+
try {
|
|
1292
|
+
resolvedScriptPath = fs.realpathSync(this._scriptPath);
|
|
1293
|
+
} catch {
|
|
1294
|
+
resolvedScriptPath = this._scriptPath;
|
|
1295
|
+
}
|
|
1296
|
+
executableDir = path.resolve(path.dirname(resolvedScriptPath), executableDir);
|
|
1297
|
+
}
|
|
1298
|
+
if (executableDir) {
|
|
1299
|
+
let localFile = findFile(executableDir, executableFile);
|
|
1300
|
+
if (!localFile && !subcommand._executableFile && this._scriptPath) {
|
|
1301
|
+
const legacyName = path.basename(this._scriptPath, path.extname(this._scriptPath));
|
|
1302
|
+
if (legacyName !== this._name) {
|
|
1303
|
+
localFile = findFile(executableDir, `${legacyName}-${subcommand._name}`);
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
executableFile = localFile || executableFile;
|
|
1307
|
+
}
|
|
1308
|
+
const launchWithNode = sourceExt.includes(path.extname(executableFile));
|
|
1309
|
+
let proc;
|
|
1310
|
+
if (process2.platform !== "win32") {
|
|
1311
|
+
if (launchWithNode) {
|
|
1312
|
+
args.unshift(executableFile);
|
|
1313
|
+
args = incrementNodeInspectorPort(process2.execArgv).concat(args);
|
|
1314
|
+
proc = childProcess.spawn(process2.argv[0], args, { stdio: "inherit" });
|
|
1315
|
+
} else {
|
|
1316
|
+
proc = childProcess.spawn(executableFile, args, { stdio: "inherit" });
|
|
1317
|
+
}
|
|
1318
|
+
} else {
|
|
1319
|
+
this._checkForMissingExecutable(executableFile, executableDir, subcommand._name);
|
|
1320
|
+
args.unshift(executableFile);
|
|
1321
|
+
args = incrementNodeInspectorPort(process2.execArgv).concat(args);
|
|
1322
|
+
proc = childProcess.spawn(process2.execPath, args, { stdio: "inherit" });
|
|
1323
|
+
}
|
|
1324
|
+
if (!proc.killed) {
|
|
1325
|
+
const signals = ["SIGUSR1", "SIGUSR2", "SIGTERM", "SIGINT", "SIGHUP"];
|
|
1326
|
+
signals.forEach((signal) => {
|
|
1327
|
+
process2.on(signal, () => {
|
|
1328
|
+
if (proc.killed === false && proc.exitCode === null) {
|
|
1329
|
+
proc.kill(signal);
|
|
1330
|
+
}
|
|
1331
|
+
});
|
|
1332
|
+
});
|
|
1333
|
+
}
|
|
1334
|
+
const exitCallback = this._exitCallback;
|
|
1335
|
+
proc.on("close", (code) => {
|
|
1336
|
+
code = code ?? 1;
|
|
1337
|
+
if (!exitCallback) {
|
|
1338
|
+
process2.exit(code);
|
|
1339
|
+
} else {
|
|
1340
|
+
exitCallback(new CommanderError(code, "commander.executeSubCommandAsync", "(close)"));
|
|
1341
|
+
}
|
|
1342
|
+
});
|
|
1343
|
+
proc.on("error", (err) => {
|
|
1344
|
+
if (err.code === "ENOENT") {
|
|
1345
|
+
this._checkForMissingExecutable(executableFile, executableDir, subcommand._name);
|
|
1346
|
+
} else if (err.code === "EACCES") {
|
|
1347
|
+
throw new Error(`'${executableFile}' not executable`);
|
|
1348
|
+
}
|
|
1349
|
+
if (!exitCallback) {
|
|
1350
|
+
process2.exit(1);
|
|
1351
|
+
} else {
|
|
1352
|
+
const wrappedError = new CommanderError(1, "commander.executeSubCommandAsync", "(error)");
|
|
1353
|
+
wrappedError.nestedError = err;
|
|
1354
|
+
exitCallback(wrappedError);
|
|
1355
|
+
}
|
|
1356
|
+
});
|
|
1357
|
+
this.runningCommand = proc;
|
|
1358
|
+
}
|
|
1359
|
+
_dispatchSubcommand(commandName, operands, unknown) {
|
|
1360
|
+
const subCommand = this._findCommand(commandName);
|
|
1361
|
+
if (!subCommand)
|
|
1362
|
+
this.help({ error: true });
|
|
1363
|
+
subCommand._prepareForParse();
|
|
1364
|
+
let promiseChain;
|
|
1365
|
+
promiseChain = this._chainOrCallSubCommandHook(promiseChain, subCommand, "preSubcommand");
|
|
1366
|
+
promiseChain = this._chainOrCall(promiseChain, () => {
|
|
1367
|
+
if (subCommand._executableHandler) {
|
|
1368
|
+
this._executeSubCommand(subCommand, operands.concat(unknown));
|
|
1369
|
+
} else {
|
|
1370
|
+
return subCommand._parseCommand(operands, unknown);
|
|
1371
|
+
}
|
|
1372
|
+
});
|
|
1373
|
+
return promiseChain;
|
|
1374
|
+
}
|
|
1375
|
+
_dispatchHelpCommand(subcommandName) {
|
|
1376
|
+
if (!subcommandName) {
|
|
1377
|
+
this.help();
|
|
1378
|
+
}
|
|
1379
|
+
const subCommand = this._findCommand(subcommandName);
|
|
1380
|
+
if (subCommand && !subCommand._executableHandler) {
|
|
1381
|
+
subCommand.help();
|
|
1382
|
+
}
|
|
1383
|
+
return this._dispatchSubcommand(subcommandName, [], [this._getHelpOption()?.long ?? this._getHelpOption()?.short ?? "--help"]);
|
|
1384
|
+
}
|
|
1385
|
+
_checkNumberOfArguments() {
|
|
1386
|
+
this.registeredArguments.forEach((arg, i) => {
|
|
1387
|
+
if (arg.required && this.args[i] == null) {
|
|
1388
|
+
this.missingArgument(arg.name());
|
|
1389
|
+
}
|
|
1390
|
+
});
|
|
1391
|
+
if (this.registeredArguments.length > 0 && this.registeredArguments[this.registeredArguments.length - 1].variadic) {
|
|
1392
|
+
return;
|
|
1393
|
+
}
|
|
1394
|
+
if (this.args.length > this.registeredArguments.length) {
|
|
1395
|
+
this._excessArguments(this.args);
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
_processArguments() {
|
|
1399
|
+
const myParseArg = (argument, value, previous) => {
|
|
1400
|
+
let parsedValue = value;
|
|
1401
|
+
if (value !== null && argument.parseArg) {
|
|
1402
|
+
const invalidValueMessage = `error: command-argument value '${value}' is invalid for argument '${argument.name()}'.`;
|
|
1403
|
+
parsedValue = this._callParseArg(argument, value, previous, invalidValueMessage);
|
|
1404
|
+
}
|
|
1405
|
+
return parsedValue;
|
|
1406
|
+
};
|
|
1407
|
+
this._checkNumberOfArguments();
|
|
1408
|
+
const processedArgs = [];
|
|
1409
|
+
this.registeredArguments.forEach((declaredArg, index) => {
|
|
1410
|
+
let value = declaredArg.defaultValue;
|
|
1411
|
+
if (declaredArg.variadic) {
|
|
1412
|
+
if (index < this.args.length) {
|
|
1413
|
+
value = this.args.slice(index);
|
|
1414
|
+
if (declaredArg.parseArg) {
|
|
1415
|
+
value = value.reduce((processed, v) => {
|
|
1416
|
+
return myParseArg(declaredArg, v, processed);
|
|
1417
|
+
}, declaredArg.defaultValue);
|
|
1418
|
+
}
|
|
1419
|
+
} else if (value === undefined) {
|
|
1420
|
+
value = [];
|
|
1421
|
+
}
|
|
1422
|
+
} else if (index < this.args.length) {
|
|
1423
|
+
value = this.args[index];
|
|
1424
|
+
if (declaredArg.parseArg) {
|
|
1425
|
+
value = myParseArg(declaredArg, value, declaredArg.defaultValue);
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
processedArgs[index] = value;
|
|
1429
|
+
});
|
|
1430
|
+
this.processedArgs = processedArgs;
|
|
1431
|
+
}
|
|
1432
|
+
_chainOrCall(promise, fn) {
|
|
1433
|
+
if (promise?.then && typeof promise.then === "function") {
|
|
1434
|
+
return promise.then(() => fn());
|
|
1435
|
+
}
|
|
1436
|
+
return fn();
|
|
1437
|
+
}
|
|
1438
|
+
_chainOrCallHooks(promise, event) {
|
|
1439
|
+
let result = promise;
|
|
1440
|
+
const hooks = [];
|
|
1441
|
+
this._getCommandAndAncestors().reverse().filter((cmd) => cmd._lifeCycleHooks[event] !== undefined).forEach((hookedCommand) => {
|
|
1442
|
+
hookedCommand._lifeCycleHooks[event].forEach((callback) => {
|
|
1443
|
+
hooks.push({ hookedCommand, callback });
|
|
1444
|
+
});
|
|
1445
|
+
});
|
|
1446
|
+
if (event === "postAction") {
|
|
1447
|
+
hooks.reverse();
|
|
1448
|
+
}
|
|
1449
|
+
hooks.forEach((hookDetail) => {
|
|
1450
|
+
result = this._chainOrCall(result, () => {
|
|
1451
|
+
return hookDetail.callback(hookDetail.hookedCommand, this);
|
|
1452
|
+
});
|
|
1453
|
+
});
|
|
1454
|
+
return result;
|
|
1455
|
+
}
|
|
1456
|
+
_chainOrCallSubCommandHook(promise, subCommand, event) {
|
|
1457
|
+
let result = promise;
|
|
1458
|
+
if (this._lifeCycleHooks[event] !== undefined) {
|
|
1459
|
+
this._lifeCycleHooks[event].forEach((hook) => {
|
|
1460
|
+
result = this._chainOrCall(result, () => {
|
|
1461
|
+
return hook(this, subCommand);
|
|
1462
|
+
});
|
|
1463
|
+
});
|
|
1464
|
+
}
|
|
1465
|
+
return result;
|
|
1466
|
+
}
|
|
1467
|
+
_parseCommand(operands, unknown) {
|
|
1468
|
+
const parsed = this.parseOptions(unknown);
|
|
1469
|
+
this._parseOptionsEnv();
|
|
1470
|
+
this._parseOptionsImplied();
|
|
1471
|
+
operands = operands.concat(parsed.operands);
|
|
1472
|
+
unknown = parsed.unknown;
|
|
1473
|
+
this.args = operands.concat(unknown);
|
|
1474
|
+
if (operands && this._findCommand(operands[0])) {
|
|
1475
|
+
return this._dispatchSubcommand(operands[0], operands.slice(1), unknown);
|
|
1476
|
+
}
|
|
1477
|
+
if (this._getHelpCommand() && operands[0] === this._getHelpCommand().name()) {
|
|
1478
|
+
return this._dispatchHelpCommand(operands[1]);
|
|
1479
|
+
}
|
|
1480
|
+
if (this._defaultCommandName) {
|
|
1481
|
+
this._outputHelpIfRequested(unknown);
|
|
1482
|
+
return this._dispatchSubcommand(this._defaultCommandName, operands, unknown);
|
|
1483
|
+
}
|
|
1484
|
+
if (this.commands.length && this.args.length === 0 && !this._actionHandler && !this._defaultCommandName) {
|
|
1485
|
+
this.help({ error: true });
|
|
1486
|
+
}
|
|
1487
|
+
this._outputHelpIfRequested(parsed.unknown);
|
|
1488
|
+
this._checkForMissingMandatoryOptions();
|
|
1489
|
+
this._checkForConflictingOptions();
|
|
1490
|
+
const checkForUnknownOptions = () => {
|
|
1491
|
+
if (parsed.unknown.length > 0) {
|
|
1492
|
+
this.unknownOption(parsed.unknown[0]);
|
|
1493
|
+
}
|
|
1494
|
+
};
|
|
1495
|
+
const commandEvent = `command:${this.name()}`;
|
|
1496
|
+
if (this._actionHandler) {
|
|
1497
|
+
checkForUnknownOptions();
|
|
1498
|
+
this._processArguments();
|
|
1499
|
+
let promiseChain;
|
|
1500
|
+
promiseChain = this._chainOrCallHooks(promiseChain, "preAction");
|
|
1501
|
+
promiseChain = this._chainOrCall(promiseChain, () => this._actionHandler(this.processedArgs));
|
|
1502
|
+
if (this.parent) {
|
|
1503
|
+
promiseChain = this._chainOrCall(promiseChain, () => {
|
|
1504
|
+
this.parent.emit(commandEvent, operands, unknown);
|
|
1505
|
+
});
|
|
1506
|
+
}
|
|
1507
|
+
promiseChain = this._chainOrCallHooks(promiseChain, "postAction");
|
|
1508
|
+
return promiseChain;
|
|
1509
|
+
}
|
|
1510
|
+
if (this.parent?.listenerCount(commandEvent)) {
|
|
1511
|
+
checkForUnknownOptions();
|
|
1512
|
+
this._processArguments();
|
|
1513
|
+
this.parent.emit(commandEvent, operands, unknown);
|
|
1514
|
+
} else if (operands.length) {
|
|
1515
|
+
if (this._findCommand("*")) {
|
|
1516
|
+
return this._dispatchSubcommand("*", operands, unknown);
|
|
1517
|
+
}
|
|
1518
|
+
if (this.listenerCount("command:*")) {
|
|
1519
|
+
this.emit("command:*", operands, unknown);
|
|
1520
|
+
} else if (this.commands.length) {
|
|
1521
|
+
this.unknownCommand();
|
|
1522
|
+
} else {
|
|
1523
|
+
checkForUnknownOptions();
|
|
1524
|
+
this._processArguments();
|
|
1525
|
+
}
|
|
1526
|
+
} else if (this.commands.length) {
|
|
1527
|
+
checkForUnknownOptions();
|
|
1528
|
+
this.help({ error: true });
|
|
1529
|
+
} else {
|
|
1530
|
+
checkForUnknownOptions();
|
|
1531
|
+
this._processArguments();
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
_findCommand(name) {
|
|
1535
|
+
if (!name)
|
|
1536
|
+
return;
|
|
1537
|
+
return this.commands.find((cmd) => cmd._name === name || cmd._aliases.includes(name));
|
|
1538
|
+
}
|
|
1539
|
+
_findOption(arg) {
|
|
1540
|
+
return this.options.find((option) => option.is(arg));
|
|
1541
|
+
}
|
|
1542
|
+
_checkForMissingMandatoryOptions() {
|
|
1543
|
+
this._getCommandAndAncestors().forEach((cmd) => {
|
|
1544
|
+
cmd.options.forEach((anOption) => {
|
|
1545
|
+
if (anOption.mandatory && cmd.getOptionValue(anOption.attributeName()) === undefined) {
|
|
1546
|
+
cmd.missingMandatoryOptionValue(anOption);
|
|
1547
|
+
}
|
|
1548
|
+
});
|
|
1549
|
+
});
|
|
1550
|
+
}
|
|
1551
|
+
_checkForConflictingLocalOptions() {
|
|
1552
|
+
const definedNonDefaultOptions = this.options.filter((option) => {
|
|
1553
|
+
const optionKey = option.attributeName();
|
|
1554
|
+
if (this.getOptionValue(optionKey) === undefined) {
|
|
1555
|
+
return false;
|
|
1556
|
+
}
|
|
1557
|
+
return this.getOptionValueSource(optionKey) !== "default";
|
|
1558
|
+
});
|
|
1559
|
+
const optionsWithConflicting = definedNonDefaultOptions.filter((option) => option.conflictsWith.length > 0);
|
|
1560
|
+
optionsWithConflicting.forEach((option) => {
|
|
1561
|
+
const conflictingAndDefined = definedNonDefaultOptions.find((defined) => option.conflictsWith.includes(defined.attributeName()));
|
|
1562
|
+
if (conflictingAndDefined) {
|
|
1563
|
+
this._conflictingOption(option, conflictingAndDefined);
|
|
1564
|
+
}
|
|
1565
|
+
});
|
|
1566
|
+
}
|
|
1567
|
+
_checkForConflictingOptions() {
|
|
1568
|
+
this._getCommandAndAncestors().forEach((cmd) => {
|
|
1569
|
+
cmd._checkForConflictingLocalOptions();
|
|
1570
|
+
});
|
|
1571
|
+
}
|
|
1572
|
+
parseOptions(args) {
|
|
1573
|
+
const operands = [];
|
|
1574
|
+
const unknown = [];
|
|
1575
|
+
let dest = operands;
|
|
1576
|
+
function maybeOption(arg) {
|
|
1577
|
+
return arg.length > 1 && arg[0] === "-";
|
|
1578
|
+
}
|
|
1579
|
+
const negativeNumberArg = (arg) => {
|
|
1580
|
+
if (!/^-(\d+|\d*\.\d+)(e[+-]?\d+)?$/.test(arg))
|
|
1581
|
+
return false;
|
|
1582
|
+
return !this._getCommandAndAncestors().some((cmd) => cmd.options.map((opt) => opt.short).some((short) => /^-\d$/.test(short)));
|
|
1583
|
+
};
|
|
1584
|
+
let activeVariadicOption = null;
|
|
1585
|
+
let activeGroup = null;
|
|
1586
|
+
let i = 0;
|
|
1587
|
+
while (i < args.length || activeGroup) {
|
|
1588
|
+
const arg = activeGroup ?? args[i++];
|
|
1589
|
+
activeGroup = null;
|
|
1590
|
+
if (arg === "--") {
|
|
1591
|
+
if (dest === unknown)
|
|
1592
|
+
dest.push(arg);
|
|
1593
|
+
dest.push(...args.slice(i));
|
|
1594
|
+
break;
|
|
1595
|
+
}
|
|
1596
|
+
if (activeVariadicOption && (!maybeOption(arg) || negativeNumberArg(arg))) {
|
|
1597
|
+
this.emit(`option:${activeVariadicOption.name()}`, arg);
|
|
1598
|
+
continue;
|
|
1599
|
+
}
|
|
1600
|
+
activeVariadicOption = null;
|
|
1601
|
+
if (maybeOption(arg)) {
|
|
1602
|
+
const option = this._findOption(arg);
|
|
1603
|
+
if (option) {
|
|
1604
|
+
if (option.required) {
|
|
1605
|
+
const value = args[i++];
|
|
1606
|
+
if (value === undefined)
|
|
1607
|
+
this.optionMissingArgument(option);
|
|
1608
|
+
this.emit(`option:${option.name()}`, value);
|
|
1609
|
+
} else if (option.optional) {
|
|
1610
|
+
let value = null;
|
|
1611
|
+
if (i < args.length && (!maybeOption(args[i]) || negativeNumberArg(args[i]))) {
|
|
1612
|
+
value = args[i++];
|
|
1613
|
+
}
|
|
1614
|
+
this.emit(`option:${option.name()}`, value);
|
|
1615
|
+
} else {
|
|
1616
|
+
this.emit(`option:${option.name()}`);
|
|
1617
|
+
}
|
|
1618
|
+
activeVariadicOption = option.variadic ? option : null;
|
|
1619
|
+
continue;
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
if (arg.length > 2 && arg[0] === "-" && arg[1] !== "-") {
|
|
1623
|
+
const option = this._findOption(`-${arg[1]}`);
|
|
1624
|
+
if (option) {
|
|
1625
|
+
if (option.required || option.optional && this._combineFlagAndOptionalValue) {
|
|
1626
|
+
this.emit(`option:${option.name()}`, arg.slice(2));
|
|
1627
|
+
} else {
|
|
1628
|
+
this.emit(`option:${option.name()}`);
|
|
1629
|
+
activeGroup = `-${arg.slice(2)}`;
|
|
1630
|
+
}
|
|
1631
|
+
continue;
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
if (/^--[^=]+=/.test(arg)) {
|
|
1635
|
+
const index = arg.indexOf("=");
|
|
1636
|
+
const option = this._findOption(arg.slice(0, index));
|
|
1637
|
+
if (option && (option.required || option.optional)) {
|
|
1638
|
+
this.emit(`option:${option.name()}`, arg.slice(index + 1));
|
|
1639
|
+
continue;
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
if (dest === operands && maybeOption(arg) && !(this.commands.length === 0 && negativeNumberArg(arg))) {
|
|
1643
|
+
dest = unknown;
|
|
1644
|
+
}
|
|
1645
|
+
if ((this._enablePositionalOptions || this._passThroughOptions) && operands.length === 0 && unknown.length === 0) {
|
|
1646
|
+
if (this._findCommand(arg)) {
|
|
1647
|
+
operands.push(arg);
|
|
1648
|
+
unknown.push(...args.slice(i));
|
|
1649
|
+
break;
|
|
1650
|
+
} else if (this._getHelpCommand() && arg === this._getHelpCommand().name()) {
|
|
1651
|
+
operands.push(arg, ...args.slice(i));
|
|
1652
|
+
break;
|
|
1653
|
+
} else if (this._defaultCommandName) {
|
|
1654
|
+
unknown.push(arg, ...args.slice(i));
|
|
1655
|
+
break;
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
if (this._passThroughOptions) {
|
|
1659
|
+
dest.push(arg, ...args.slice(i));
|
|
1660
|
+
break;
|
|
1661
|
+
}
|
|
1662
|
+
dest.push(arg);
|
|
1663
|
+
}
|
|
1664
|
+
return { operands, unknown };
|
|
1665
|
+
}
|
|
1666
|
+
opts() {
|
|
1667
|
+
if (this._storeOptionsAsProperties) {
|
|
1668
|
+
const result = {};
|
|
1669
|
+
const len = this.options.length;
|
|
1670
|
+
for (let i = 0;i < len; i++) {
|
|
1671
|
+
const key = this.options[i].attributeName();
|
|
1672
|
+
result[key] = key === this._versionOptionName ? this._version : this[key];
|
|
1673
|
+
}
|
|
1674
|
+
return result;
|
|
1675
|
+
}
|
|
1676
|
+
return this._optionValues;
|
|
1677
|
+
}
|
|
1678
|
+
optsWithGlobals() {
|
|
1679
|
+
return this._getCommandAndAncestors().reduce((combinedOptions, cmd) => Object.assign(combinedOptions, cmd.opts()), {});
|
|
1680
|
+
}
|
|
1681
|
+
error(message, errorOptions) {
|
|
1682
|
+
this._outputConfiguration.outputError(`${message}
|
|
1683
|
+
`, this._outputConfiguration.writeErr);
|
|
1684
|
+
if (typeof this._showHelpAfterError === "string") {
|
|
1685
|
+
this._outputConfiguration.writeErr(`${this._showHelpAfterError}
|
|
1686
|
+
`);
|
|
1687
|
+
} else if (this._showHelpAfterError) {
|
|
1688
|
+
this._outputConfiguration.writeErr(`
|
|
1689
|
+
`);
|
|
1690
|
+
this.outputHelp({ error: true });
|
|
1691
|
+
}
|
|
1692
|
+
const config = errorOptions || {};
|
|
1693
|
+
const exitCode = config.exitCode || 1;
|
|
1694
|
+
const code = config.code || "commander.error";
|
|
1695
|
+
this._exit(exitCode, code, message);
|
|
1696
|
+
}
|
|
1697
|
+
_parseOptionsEnv() {
|
|
1698
|
+
this.options.forEach((option) => {
|
|
1699
|
+
if (option.envVar && option.envVar in process2.env) {
|
|
1700
|
+
const optionKey = option.attributeName();
|
|
1701
|
+
if (this.getOptionValue(optionKey) === undefined || ["default", "config", "env"].includes(this.getOptionValueSource(optionKey))) {
|
|
1702
|
+
if (option.required || option.optional) {
|
|
1703
|
+
this.emit(`optionEnv:${option.name()}`, process2.env[option.envVar]);
|
|
1704
|
+
} else {
|
|
1705
|
+
this.emit(`optionEnv:${option.name()}`);
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
});
|
|
1710
|
+
}
|
|
1711
|
+
_parseOptionsImplied() {
|
|
1712
|
+
const dualHelper = new DualOptions(this.options);
|
|
1713
|
+
const hasCustomOptionValue = (optionKey) => {
|
|
1714
|
+
return this.getOptionValue(optionKey) !== undefined && !["default", "implied"].includes(this.getOptionValueSource(optionKey));
|
|
1715
|
+
};
|
|
1716
|
+
this.options.filter((option) => option.implied !== undefined && hasCustomOptionValue(option.attributeName()) && dualHelper.valueFromOption(this.getOptionValue(option.attributeName()), option)).forEach((option) => {
|
|
1717
|
+
Object.keys(option.implied).filter((impliedKey) => !hasCustomOptionValue(impliedKey)).forEach((impliedKey) => {
|
|
1718
|
+
this.setOptionValueWithSource(impliedKey, option.implied[impliedKey], "implied");
|
|
1719
|
+
});
|
|
1720
|
+
});
|
|
1721
|
+
}
|
|
1722
|
+
missingArgument(name) {
|
|
1723
|
+
const message = `error: missing required argument '${name}'`;
|
|
1724
|
+
this.error(message, { code: "commander.missingArgument" });
|
|
1725
|
+
}
|
|
1726
|
+
optionMissingArgument(option) {
|
|
1727
|
+
const message = `error: option '${option.flags}' argument missing`;
|
|
1728
|
+
this.error(message, { code: "commander.optionMissingArgument" });
|
|
1729
|
+
}
|
|
1730
|
+
missingMandatoryOptionValue(option) {
|
|
1731
|
+
const message = `error: required option '${option.flags}' not specified`;
|
|
1732
|
+
this.error(message, { code: "commander.missingMandatoryOptionValue" });
|
|
1733
|
+
}
|
|
1734
|
+
_conflictingOption(option, conflictingOption) {
|
|
1735
|
+
const findBestOptionFromValue = (option) => {
|
|
1736
|
+
const optionKey = option.attributeName();
|
|
1737
|
+
const optionValue = this.getOptionValue(optionKey);
|
|
1738
|
+
const negativeOption = this.options.find((target) => target.negate && optionKey === target.attributeName());
|
|
1739
|
+
const positiveOption = this.options.find((target) => !target.negate && optionKey === target.attributeName());
|
|
1740
|
+
if (negativeOption && (negativeOption.presetArg === undefined && optionValue === false || negativeOption.presetArg !== undefined && optionValue === negativeOption.presetArg)) {
|
|
1741
|
+
return negativeOption;
|
|
1742
|
+
}
|
|
1743
|
+
return positiveOption || option;
|
|
1744
|
+
};
|
|
1745
|
+
const getErrorMessage = (option) => {
|
|
1746
|
+
const bestOption = findBestOptionFromValue(option);
|
|
1747
|
+
const optionKey = bestOption.attributeName();
|
|
1748
|
+
const source = this.getOptionValueSource(optionKey);
|
|
1749
|
+
if (source === "env") {
|
|
1750
|
+
return `environment variable '${bestOption.envVar}'`;
|
|
1751
|
+
}
|
|
1752
|
+
return `option '${bestOption.flags}'`;
|
|
1753
|
+
};
|
|
1754
|
+
const message = `error: ${getErrorMessage(option)} cannot be used with ${getErrorMessage(conflictingOption)}`;
|
|
1755
|
+
this.error(message, { code: "commander.conflictingOption" });
|
|
1756
|
+
}
|
|
1757
|
+
unknownOption(flag) {
|
|
1758
|
+
if (this._allowUnknownOption)
|
|
1759
|
+
return;
|
|
1760
|
+
let suggestion = "";
|
|
1761
|
+
if (flag.startsWith("--") && this._showSuggestionAfterError) {
|
|
1762
|
+
let candidateFlags = [];
|
|
1763
|
+
let command = this;
|
|
1764
|
+
do {
|
|
1765
|
+
const moreFlags = command.createHelp().visibleOptions(command).filter((option) => option.long).map((option) => option.long);
|
|
1766
|
+
candidateFlags = candidateFlags.concat(moreFlags);
|
|
1767
|
+
command = command.parent;
|
|
1768
|
+
} while (command && !command._enablePositionalOptions);
|
|
1769
|
+
suggestion = suggestSimilar(flag, candidateFlags);
|
|
1770
|
+
}
|
|
1771
|
+
const message = `error: unknown option '${flag}'${suggestion}`;
|
|
1772
|
+
this.error(message, { code: "commander.unknownOption" });
|
|
1773
|
+
}
|
|
1774
|
+
_excessArguments(receivedArgs) {
|
|
1775
|
+
if (this._allowExcessArguments)
|
|
1776
|
+
return;
|
|
1777
|
+
const expected = this.registeredArguments.length;
|
|
1778
|
+
const s = expected === 1 ? "" : "s";
|
|
1779
|
+
const received = receivedArgs.length;
|
|
1780
|
+
const forSubcommand = this.parent ? ` for '${this.name()}'` : "";
|
|
1781
|
+
const details = receivedArgs.join(", ");
|
|
1782
|
+
const message = `error: too many arguments${forSubcommand}. Expected ${expected} argument${s} but got ${received}: ${details}.`;
|
|
1783
|
+
this.error(message, { code: "commander.excessArguments" });
|
|
1784
|
+
}
|
|
1785
|
+
unknownCommand() {
|
|
1786
|
+
const unknownName = this.args[0];
|
|
1787
|
+
let suggestion = "";
|
|
1788
|
+
if (this._showSuggestionAfterError) {
|
|
1789
|
+
const candidateNames = [];
|
|
1790
|
+
this.createHelp().visibleCommands(this).forEach((command) => {
|
|
1791
|
+
candidateNames.push(command.name());
|
|
1792
|
+
if (command.alias())
|
|
1793
|
+
candidateNames.push(command.alias());
|
|
1794
|
+
});
|
|
1795
|
+
suggestion = suggestSimilar(unknownName, candidateNames);
|
|
1796
|
+
}
|
|
1797
|
+
const message = `error: unknown command '${unknownName}'${suggestion}`;
|
|
1798
|
+
this.error(message, { code: "commander.unknownCommand" });
|
|
1799
|
+
}
|
|
1800
|
+
version(str, flags, description) {
|
|
1801
|
+
if (str === undefined)
|
|
1802
|
+
return this._version;
|
|
1803
|
+
this._version = str;
|
|
1804
|
+
flags = flags || "-V, --version";
|
|
1805
|
+
description = description || "output the version number";
|
|
1806
|
+
const versionOption = this.createOption(flags, description);
|
|
1807
|
+
this._versionOptionName = versionOption.attributeName();
|
|
1808
|
+
this._registerOption(versionOption);
|
|
1809
|
+
this.on("option:" + versionOption.name(), () => {
|
|
1810
|
+
this._outputConfiguration.writeOut(`${str}
|
|
1811
|
+
`);
|
|
1812
|
+
this._exit(0, "commander.version", str);
|
|
1813
|
+
});
|
|
1814
|
+
return this;
|
|
1815
|
+
}
|
|
1816
|
+
description(str, argsDescription) {
|
|
1817
|
+
if (str === undefined && argsDescription === undefined)
|
|
1818
|
+
return this._description;
|
|
1819
|
+
this._description = str;
|
|
1820
|
+
if (argsDescription) {
|
|
1821
|
+
this._argsDescription = argsDescription;
|
|
1822
|
+
}
|
|
1823
|
+
return this;
|
|
1824
|
+
}
|
|
1825
|
+
summary(str) {
|
|
1826
|
+
if (str === undefined)
|
|
1827
|
+
return this._summary;
|
|
1828
|
+
this._summary = str;
|
|
1829
|
+
return this;
|
|
1830
|
+
}
|
|
1831
|
+
alias(alias) {
|
|
1832
|
+
if (alias === undefined)
|
|
1833
|
+
return this._aliases[0];
|
|
1834
|
+
let command = this;
|
|
1835
|
+
if (this.commands.length !== 0 && this.commands[this.commands.length - 1]._executableHandler) {
|
|
1836
|
+
command = this.commands[this.commands.length - 1];
|
|
1837
|
+
}
|
|
1838
|
+
if (alias === command._name)
|
|
1839
|
+
throw new Error("Command alias can't be the same as its name");
|
|
1840
|
+
const matchingCommand = this.parent?._findCommand(alias);
|
|
1841
|
+
if (matchingCommand) {
|
|
1842
|
+
const existingCmd = [matchingCommand.name()].concat(matchingCommand.aliases()).join("|");
|
|
1843
|
+
throw new Error(`cannot add alias '${alias}' to command '${this.name()}' as already have command '${existingCmd}'`);
|
|
1844
|
+
}
|
|
1845
|
+
command._aliases.push(alias);
|
|
1846
|
+
return this;
|
|
1847
|
+
}
|
|
1848
|
+
aliases(aliases) {
|
|
1849
|
+
if (aliases === undefined)
|
|
1850
|
+
return this._aliases;
|
|
1851
|
+
aliases.forEach((alias) => this.alias(alias));
|
|
1852
|
+
return this;
|
|
1853
|
+
}
|
|
1854
|
+
usage(str) {
|
|
1855
|
+
if (str === undefined) {
|
|
1856
|
+
if (this._usage)
|
|
1857
|
+
return this._usage;
|
|
1858
|
+
const args = this.registeredArguments.map((arg) => {
|
|
1859
|
+
return humanReadableArgName(arg);
|
|
1860
|
+
});
|
|
1861
|
+
return [].concat(this.options.length || this._helpOption !== null ? "[options]" : [], this.commands.length ? "[command]" : [], this.registeredArguments.length ? args : []).join(" ");
|
|
1862
|
+
}
|
|
1863
|
+
this._usage = str;
|
|
1864
|
+
return this;
|
|
1865
|
+
}
|
|
1866
|
+
name(str) {
|
|
1867
|
+
if (str === undefined)
|
|
1868
|
+
return this._name;
|
|
1869
|
+
this._name = str;
|
|
1870
|
+
return this;
|
|
1871
|
+
}
|
|
1872
|
+
helpGroup(heading) {
|
|
1873
|
+
if (heading === undefined)
|
|
1874
|
+
return this._helpGroupHeading ?? "";
|
|
1875
|
+
this._helpGroupHeading = heading;
|
|
1876
|
+
return this;
|
|
1877
|
+
}
|
|
1878
|
+
commandsGroup(heading) {
|
|
1879
|
+
if (heading === undefined)
|
|
1880
|
+
return this._defaultCommandGroup ?? "";
|
|
1881
|
+
this._defaultCommandGroup = heading;
|
|
1882
|
+
return this;
|
|
1883
|
+
}
|
|
1884
|
+
optionsGroup(heading) {
|
|
1885
|
+
if (heading === undefined)
|
|
1886
|
+
return this._defaultOptionGroup ?? "";
|
|
1887
|
+
this._defaultOptionGroup = heading;
|
|
1888
|
+
return this;
|
|
1889
|
+
}
|
|
1890
|
+
_initOptionGroup(option) {
|
|
1891
|
+
if (this._defaultOptionGroup && !option.helpGroupHeading)
|
|
1892
|
+
option.helpGroup(this._defaultOptionGroup);
|
|
1893
|
+
}
|
|
1894
|
+
_initCommandGroup(cmd) {
|
|
1895
|
+
if (this._defaultCommandGroup && !cmd.helpGroup())
|
|
1896
|
+
cmd.helpGroup(this._defaultCommandGroup);
|
|
1897
|
+
}
|
|
1898
|
+
nameFromFilename(filename) {
|
|
1899
|
+
this._name = path.basename(filename, path.extname(filename));
|
|
1900
|
+
return this;
|
|
1901
|
+
}
|
|
1902
|
+
executableDir(path2) {
|
|
1903
|
+
if (path2 === undefined)
|
|
1904
|
+
return this._executableDir;
|
|
1905
|
+
this._executableDir = path2;
|
|
1906
|
+
return this;
|
|
1907
|
+
}
|
|
1908
|
+
helpInformation(contextOptions) {
|
|
1909
|
+
const helper = this.createHelp();
|
|
1910
|
+
const context = this._getOutputContext(contextOptions);
|
|
1911
|
+
helper.prepareContext({
|
|
1912
|
+
error: context.error,
|
|
1913
|
+
helpWidth: context.helpWidth,
|
|
1914
|
+
outputHasColors: context.hasColors
|
|
1915
|
+
});
|
|
1916
|
+
const text = helper.formatHelp(this, helper);
|
|
1917
|
+
if (context.hasColors)
|
|
1918
|
+
return text;
|
|
1919
|
+
return this._outputConfiguration.stripColor(text);
|
|
1920
|
+
}
|
|
1921
|
+
_getOutputContext(contextOptions) {
|
|
1922
|
+
contextOptions = contextOptions || {};
|
|
1923
|
+
const error = !!contextOptions.error;
|
|
1924
|
+
let baseWrite;
|
|
1925
|
+
let hasColors;
|
|
1926
|
+
let helpWidth;
|
|
1927
|
+
if (error) {
|
|
1928
|
+
baseWrite = (str) => this._outputConfiguration.writeErr(str);
|
|
1929
|
+
hasColors = this._outputConfiguration.getErrHasColors();
|
|
1930
|
+
helpWidth = this._outputConfiguration.getErrHelpWidth();
|
|
1931
|
+
} else {
|
|
1932
|
+
baseWrite = (str) => this._outputConfiguration.writeOut(str);
|
|
1933
|
+
hasColors = this._outputConfiguration.getOutHasColors();
|
|
1934
|
+
helpWidth = this._outputConfiguration.getOutHelpWidth();
|
|
1935
|
+
}
|
|
1936
|
+
const write = (str) => {
|
|
1937
|
+
if (!hasColors)
|
|
1938
|
+
str = this._outputConfiguration.stripColor(str);
|
|
1939
|
+
return baseWrite(str);
|
|
1940
|
+
};
|
|
1941
|
+
return { error, write, hasColors, helpWidth };
|
|
1942
|
+
}
|
|
1943
|
+
outputHelp(contextOptions) {
|
|
1944
|
+
let deprecatedCallback;
|
|
1945
|
+
if (typeof contextOptions === "function") {
|
|
1946
|
+
deprecatedCallback = contextOptions;
|
|
1947
|
+
contextOptions = undefined;
|
|
1948
|
+
}
|
|
1949
|
+
const outputContext = this._getOutputContext(contextOptions);
|
|
1950
|
+
const eventContext = {
|
|
1951
|
+
error: outputContext.error,
|
|
1952
|
+
write: outputContext.write,
|
|
1953
|
+
command: this
|
|
1954
|
+
};
|
|
1955
|
+
this._getCommandAndAncestors().reverse().forEach((command) => command.emit("beforeAllHelp", eventContext));
|
|
1956
|
+
this.emit("beforeHelp", eventContext);
|
|
1957
|
+
let helpInformation = this.helpInformation({ error: outputContext.error });
|
|
1958
|
+
if (deprecatedCallback) {
|
|
1959
|
+
helpInformation = deprecatedCallback(helpInformation);
|
|
1960
|
+
if (typeof helpInformation !== "string" && !Buffer.isBuffer(helpInformation)) {
|
|
1961
|
+
throw new Error("outputHelp callback must return a string or a Buffer");
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
outputContext.write(helpInformation);
|
|
1965
|
+
if (this._getHelpOption()?.long) {
|
|
1966
|
+
this.emit(this._getHelpOption().long);
|
|
1967
|
+
}
|
|
1968
|
+
this.emit("afterHelp", eventContext);
|
|
1969
|
+
this._getCommandAndAncestors().forEach((command) => command.emit("afterAllHelp", eventContext));
|
|
1970
|
+
}
|
|
1971
|
+
helpOption(flags, description) {
|
|
1972
|
+
if (typeof flags === "boolean") {
|
|
1973
|
+
if (flags) {
|
|
1974
|
+
if (this._helpOption === null)
|
|
1975
|
+
this._helpOption = undefined;
|
|
1976
|
+
if (this._defaultOptionGroup) {
|
|
1977
|
+
this._initOptionGroup(this._getHelpOption());
|
|
1978
|
+
}
|
|
1979
|
+
} else {
|
|
1980
|
+
this._helpOption = null;
|
|
1981
|
+
}
|
|
1982
|
+
return this;
|
|
1983
|
+
}
|
|
1984
|
+
this._helpOption = this.createOption(flags ?? "-h, --help", description ?? "display help for command");
|
|
1985
|
+
if (flags || description)
|
|
1986
|
+
this._initOptionGroup(this._helpOption);
|
|
1987
|
+
return this;
|
|
1988
|
+
}
|
|
1989
|
+
_getHelpOption() {
|
|
1990
|
+
if (this._helpOption === undefined) {
|
|
1991
|
+
this.helpOption(undefined, undefined);
|
|
1992
|
+
}
|
|
1993
|
+
return this._helpOption;
|
|
1994
|
+
}
|
|
1995
|
+
addHelpOption(option) {
|
|
1996
|
+
this._helpOption = option;
|
|
1997
|
+
this._initOptionGroup(option);
|
|
1998
|
+
return this;
|
|
1999
|
+
}
|
|
2000
|
+
help(contextOptions) {
|
|
2001
|
+
this.outputHelp(contextOptions);
|
|
2002
|
+
let exitCode = Number(process2.exitCode ?? 0);
|
|
2003
|
+
if (exitCode === 0 && contextOptions && typeof contextOptions !== "function" && contextOptions.error) {
|
|
2004
|
+
exitCode = 1;
|
|
2005
|
+
}
|
|
2006
|
+
this._exit(exitCode, "commander.help", "(outputHelp)");
|
|
2007
|
+
}
|
|
2008
|
+
addHelpText(position, text) {
|
|
2009
|
+
const allowedValues = ["beforeAll", "before", "after", "afterAll"];
|
|
2010
|
+
if (!allowedValues.includes(position)) {
|
|
2011
|
+
throw new Error(`Unexpected value for position to addHelpText.
|
|
2012
|
+
Expecting one of '${allowedValues.join("', '")}'`);
|
|
2013
|
+
}
|
|
2014
|
+
const helpEvent = `${position}Help`;
|
|
2015
|
+
this.on(helpEvent, (context) => {
|
|
2016
|
+
let helpStr;
|
|
2017
|
+
if (typeof text === "function") {
|
|
2018
|
+
helpStr = text({ error: context.error, command: context.command });
|
|
2019
|
+
} else {
|
|
2020
|
+
helpStr = text;
|
|
2021
|
+
}
|
|
2022
|
+
if (helpStr) {
|
|
2023
|
+
context.write(`${helpStr}
|
|
2024
|
+
`);
|
|
2025
|
+
}
|
|
2026
|
+
});
|
|
2027
|
+
return this;
|
|
2028
|
+
}
|
|
2029
|
+
_outputHelpIfRequested(args) {
|
|
2030
|
+
const helpOption = this._getHelpOption();
|
|
2031
|
+
const helpRequested = helpOption && args.find((arg) => helpOption.is(arg));
|
|
2032
|
+
if (helpRequested) {
|
|
2033
|
+
this.outputHelp();
|
|
2034
|
+
this._exit(0, "commander.helpDisplayed", "(outputHelp)");
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
function incrementNodeInspectorPort(args) {
|
|
2039
|
+
return args.map((arg) => {
|
|
2040
|
+
if (!arg.startsWith("--inspect")) {
|
|
2041
|
+
return arg;
|
|
2042
|
+
}
|
|
2043
|
+
let debugOption;
|
|
2044
|
+
let debugHost = "127.0.0.1";
|
|
2045
|
+
let debugPort = "9229";
|
|
2046
|
+
let match;
|
|
2047
|
+
if ((match = arg.match(/^(--inspect(-brk)?)$/)) !== null) {
|
|
2048
|
+
debugOption = match[1];
|
|
2049
|
+
} else if ((match = arg.match(/^(--inspect(-brk|-port)?)=([^:]+)$/)) !== null) {
|
|
2050
|
+
debugOption = match[1];
|
|
2051
|
+
if (/^\d+$/.test(match[3])) {
|
|
2052
|
+
debugPort = match[3];
|
|
2053
|
+
} else {
|
|
2054
|
+
debugHost = match[3];
|
|
2055
|
+
}
|
|
2056
|
+
} else if ((match = arg.match(/^(--inspect(-brk|-port)?)=([^:]+):(\d+)$/)) !== null) {
|
|
2057
|
+
debugOption = match[1];
|
|
2058
|
+
debugHost = match[3];
|
|
2059
|
+
debugPort = match[4];
|
|
2060
|
+
}
|
|
2061
|
+
if (debugOption && debugPort !== "0") {
|
|
2062
|
+
return `${debugOption}=${debugHost}:${parseInt(debugPort) + 1}`;
|
|
2063
|
+
}
|
|
2064
|
+
return arg;
|
|
2065
|
+
});
|
|
2066
|
+
}
|
|
2067
|
+
function useColor() {
|
|
2068
|
+
if (process2.env.NO_COLOR || process2.env.FORCE_COLOR === "0" || process2.env.FORCE_COLOR === "false")
|
|
2069
|
+
return false;
|
|
2070
|
+
if (process2.env.FORCE_COLOR || process2.env.CLICOLOR_FORCE !== undefined)
|
|
2071
|
+
return true;
|
|
2072
|
+
return;
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
// node_modules/commander/index.js
|
|
2076
|
+
var program = new Command;
|
|
2077
|
+
// src/build-info.ts
|
|
2078
|
+
var buildInfo = { packageName: "@solgridai/cli", version: "0.1.0" };
|
|
2079
|
+
|
|
2080
|
+
// src/core/errors.ts
|
|
2081
|
+
class CliError extends Error {
|
|
2082
|
+
code;
|
|
2083
|
+
hint;
|
|
2084
|
+
details;
|
|
2085
|
+
data;
|
|
2086
|
+
constructor(code, message, options = {}) {
|
|
2087
|
+
super(message, { cause: options.cause });
|
|
2088
|
+
this.name = "CliError";
|
|
2089
|
+
this.code = code;
|
|
2090
|
+
this.hint = options.hint;
|
|
2091
|
+
this.details = options.details ?? [];
|
|
2092
|
+
this.data = options.data;
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2096
|
+
class ApiError extends CliError {
|
|
2097
|
+
status;
|
|
2098
|
+
key;
|
|
2099
|
+
constructor(code, message, options = {}) {
|
|
2100
|
+
super(code, message, options);
|
|
2101
|
+
this.name = "ApiError";
|
|
2102
|
+
this.status = options.status;
|
|
2103
|
+
this.key = options.key;
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
function errorCodeForStatus(status) {
|
|
2107
|
+
if (status === 400)
|
|
2108
|
+
return "INVALID_INPUT";
|
|
2109
|
+
if (status === 401)
|
|
2110
|
+
return "AUTH_FAILED";
|
|
2111
|
+
if (status === 403)
|
|
2112
|
+
return "FORBIDDEN";
|
|
2113
|
+
if (status === 404)
|
|
2114
|
+
return "NOT_FOUND";
|
|
2115
|
+
if (status === 409)
|
|
2116
|
+
return "CONFLICT";
|
|
2117
|
+
if (status === 413)
|
|
2118
|
+
return "TOO_LARGE";
|
|
2119
|
+
if (status === 422)
|
|
2120
|
+
return "UNPROCESSABLE";
|
|
2121
|
+
if (status === 429)
|
|
2122
|
+
return "RATE_LIMITED";
|
|
2123
|
+
if (status >= 500)
|
|
2124
|
+
return "SERVER_ERROR";
|
|
2125
|
+
return "INVALID_INPUT";
|
|
2126
|
+
}
|
|
2127
|
+
|
|
2128
|
+
// src/core/api/shape.ts
|
|
2129
|
+
function asRecord(value) {
|
|
2130
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
2131
|
+
}
|
|
2132
|
+
function check(test, expected) {
|
|
2133
|
+
return (value) => test(value) ? undefined : `to be ${expected}`;
|
|
2134
|
+
}
|
|
2135
|
+
var isObject = check((value) => asRecord(value) !== undefined, "an object");
|
|
2136
|
+
var isArray = check(Array.isArray, "a list");
|
|
2137
|
+
var isString = check((value) => typeof value === "string" && value.length > 0, "a non-empty string");
|
|
2138
|
+
var isText = check((value) => typeof value === "string", "a string");
|
|
2139
|
+
var isNumber = check((value) => typeof value === "number" && Number.isFinite(value), "a number");
|
|
2140
|
+
function optional(inner) {
|
|
2141
|
+
return (value) => value === undefined || value === null ? undefined : inner(value);
|
|
2142
|
+
}
|
|
2143
|
+
function objectWith(fields) {
|
|
2144
|
+
return (value) => {
|
|
2145
|
+
const record = asRecord(value);
|
|
2146
|
+
if (!record)
|
|
2147
|
+
return "to be an object";
|
|
2148
|
+
return firstProblem(record, fields);
|
|
2149
|
+
};
|
|
2150
|
+
}
|
|
2151
|
+
function listOf(item) {
|
|
2152
|
+
return (value) => {
|
|
2153
|
+
if (!Array.isArray(value))
|
|
2154
|
+
return "to be a list";
|
|
2155
|
+
for (const [index, entry] of value.entries()) {
|
|
2156
|
+
const problem = item(entry);
|
|
2157
|
+
if (problem)
|
|
2158
|
+
return `item ${index} ${problem}`;
|
|
2159
|
+
}
|
|
2160
|
+
return;
|
|
2161
|
+
};
|
|
2162
|
+
}
|
|
2163
|
+
var isMoney = objectWith({
|
|
2164
|
+
value: check((value) => typeof value === "number" ? Number.isFinite(value) : typeof value === "string", "a number or a decimal string"),
|
|
2165
|
+
currency: isString
|
|
2166
|
+
});
|
|
2167
|
+
var isPeriod = objectWith({ unit: isString, count: isNumber });
|
|
2168
|
+
var isPer = (value) => typeof value === "number" ? isNumber(value) : isPeriod(value) && "to be a number or a period";
|
|
2169
|
+
var LEAF_FIELDS = {
|
|
2170
|
+
type: isString,
|
|
2171
|
+
amount: optional(isMoney),
|
|
2172
|
+
flatFee: optional(isMoney),
|
|
2173
|
+
per: optional(isPer),
|
|
2174
|
+
unit: optional(isText)
|
|
2175
|
+
};
|
|
2176
|
+
var isLeafPrice = objectWith(LEAF_FIELDS);
|
|
2177
|
+
var isPrice = objectWith({
|
|
2178
|
+
...LEAF_FIELDS,
|
|
2179
|
+
period: optional(isPeriod),
|
|
2180
|
+
default: optional(isLeafPrice),
|
|
2181
|
+
selectors: optional(listOf(objectWith({ key: isString, label: optional(isText) }))),
|
|
2182
|
+
variants: optional(listOf(objectWith({ when: isObject, price: isLeafPrice }))),
|
|
2183
|
+
tiers: optional(listOf(objectWith({ label: isText, when: isObject, price: isLeafPrice }))),
|
|
2184
|
+
notes: optional(listOf(isText))
|
|
2185
|
+
});
|
|
2186
|
+
var isMetrics = objectWith({
|
|
2187
|
+
status: isString,
|
|
2188
|
+
runTimeMs: optional(objectWith({ p50: optional(isNumber), p95: optional(isNumber) }))
|
|
2189
|
+
});
|
|
2190
|
+
function expectShape(path, data, fields) {
|
|
2191
|
+
const record = asRecord(data);
|
|
2192
|
+
if (!record)
|
|
2193
|
+
throw unexpected(path, "expected an object in `data`");
|
|
2194
|
+
const problem = firstProblem(record, fields);
|
|
2195
|
+
if (problem) {
|
|
2196
|
+
const present = Object.keys(record).join(", ") || "none";
|
|
2197
|
+
throw unexpected(path, `expected ${problem} (fields received: ${present})`);
|
|
2198
|
+
}
|
|
2199
|
+
return data;
|
|
2200
|
+
}
|
|
2201
|
+
function firstProblem(record, fields) {
|
|
2202
|
+
for (const [field, test] of Object.entries(fields)) {
|
|
2203
|
+
const problem = test(record[field]);
|
|
2204
|
+
if (problem)
|
|
2205
|
+
return problem.startsWith("`") ? `\`${field}.${problem.slice(1)}` : `\`${field}\` ${problem}`;
|
|
2206
|
+
}
|
|
2207
|
+
return;
|
|
2208
|
+
}
|
|
2209
|
+
function unexpected(path, detail) {
|
|
2210
|
+
return new ApiError("INVALID_RESPONSE", `Unexpected response from ${path}: ${detail}.`, {
|
|
2211
|
+
hint: "The Solgrid API may not match this CLI version. Update the CLI, or report this with the command you ran."
|
|
2212
|
+
});
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2215
|
+
// src/core/api/endpoints.ts
|
|
2216
|
+
async function discover(client, params) {
|
|
2217
|
+
const data = await client.post("/v1/tools/discover", {
|
|
2218
|
+
query: params.query,
|
|
2219
|
+
...params.limit !== undefined ? { limit: params.limit } : {},
|
|
2220
|
+
...params.includeUnavailable ? { includeUnavailable: true } : {}
|
|
2221
|
+
});
|
|
2222
|
+
return expectShape("/v1/tools/discover", data, { results: listOf(objectWith(TOOL_FIELDS)) });
|
|
2223
|
+
}
|
|
2224
|
+
async function inspect(client, params) {
|
|
2225
|
+
const data = await client.post("/v1/tools/inspect", params);
|
|
2226
|
+
return expectShape("/v1/tools/inspect", data, TOOL_FIELDS);
|
|
2227
|
+
}
|
|
2228
|
+
async function createRun(client, params, idempotencyKey) {
|
|
2229
|
+
const data = await client.post("/v1/runs/create", {
|
|
2230
|
+
provider: params.provider,
|
|
2231
|
+
endpoint: params.endpoint,
|
|
2232
|
+
...params.input ? { input: params.input } : {}
|
|
2233
|
+
}, { "Idempotency-Key": idempotencyKey });
|
|
2234
|
+
return expectShape("/v1/runs/create", data, RUN_FIELDS);
|
|
2235
|
+
}
|
|
2236
|
+
async function stopRun(client, runId) {
|
|
2237
|
+
const data = await client.post("/v1/runs/stop", { id: runId });
|
|
2238
|
+
return expectShape("/v1/runs/stop", data, RUN_FIELDS);
|
|
2239
|
+
}
|
|
2240
|
+
async function getRun(client, runId, request = {}) {
|
|
2241
|
+
const data = await client.post("/v1/runs/get", { id: runId }, {}, request);
|
|
2242
|
+
return expectShape("/v1/runs/get", data, RUN_FIELDS);
|
|
2243
|
+
}
|
|
2244
|
+
async function getRunByIdempotencyKey(client, idempotencyKey, request = {}) {
|
|
2245
|
+
const path = "/v1/runs/get-by-idempotency-key";
|
|
2246
|
+
const data = await client.post(path, { idempotencyKey }, {}, request);
|
|
2247
|
+
return expectShape(path, data, RUN_FIELDS);
|
|
2248
|
+
}
|
|
2249
|
+
async function listRuns(client, params = {}) {
|
|
2250
|
+
const data = await client.post("/v1/runs/list", {
|
|
2251
|
+
...params.limit !== undefined ? { limit: params.limit } : {},
|
|
2252
|
+
...params.cursor ? { cursor: params.cursor } : {}
|
|
2253
|
+
});
|
|
2254
|
+
return expectShape("/v1/runs/list", data, { items: listOf(objectWith(RUN_FIELDS)) });
|
|
2255
|
+
}
|
|
2256
|
+
async function getBalance(client) {
|
|
2257
|
+
const data = await client.post("/v1/wallet/balance");
|
|
2258
|
+
return expectShape("/v1/wallet/balance", data, { balance: isMoney });
|
|
2259
|
+
}
|
|
2260
|
+
async function whoami(client) {
|
|
2261
|
+
const data = await client.post("/v1/auth/whoami");
|
|
2262
|
+
return expectShape("/v1/auth/whoami", data, { user: isObject });
|
|
2263
|
+
}
|
|
2264
|
+
var TOOL_FIELDS = {
|
|
2265
|
+
provider: isString,
|
|
2266
|
+
endpoint: isString,
|
|
2267
|
+
description: isText,
|
|
2268
|
+
price: isPrice,
|
|
2269
|
+
tags: optional(listOf(isText)),
|
|
2270
|
+
notes: optional(listOf(isText)),
|
|
2271
|
+
metrics: optional(isMetrics)
|
|
2272
|
+
};
|
|
2273
|
+
var RUN_FIELDS = { runId: isString, status: isString, cost: optional(isMoney) };
|
|
2274
|
+
|
|
2275
|
+
// src/core/api-key.ts
|
|
2276
|
+
var API_KEY_PATTERN = /^tl_[0-9a-f]{64}$/;
|
|
2277
|
+
var LABEL_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
2278
|
+
function isValidApiKey(key) {
|
|
2279
|
+
return API_KEY_PATTERN.test(key);
|
|
2280
|
+
}
|
|
2281
|
+
function isValidLabel(label) {
|
|
2282
|
+
return LABEL_PATTERN.test(label);
|
|
2283
|
+
}
|
|
2284
|
+
function maskApiKey(key) {
|
|
2285
|
+
if (key.length < 16)
|
|
2286
|
+
return "****";
|
|
2287
|
+
return `${key.slice(0, 11)}…${key.slice(-4)}`;
|
|
2288
|
+
}
|
|
2289
|
+
|
|
2290
|
+
// src/core/config.ts
|
|
2291
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
2292
|
+
import { join } from "node:path";
|
|
2293
|
+
|
|
2294
|
+
// src/core/constants.ts
|
|
2295
|
+
var DEFAULT_API_BASE_URL = "https://api.solgrid.ai";
|
|
2296
|
+
var APP_URL = "https://solgrid.ai/account";
|
|
2297
|
+
var CONSOLE_URLS = {
|
|
2298
|
+
apiKeys: `${APP_URL}/api-keys`,
|
|
2299
|
+
wallet: `${APP_URL}/wallet`,
|
|
2300
|
+
budgets: `${APP_URL}/budgets`
|
|
2301
|
+
};
|
|
2302
|
+
var ENV = {
|
|
2303
|
+
apiKey: "SOLGRID_API_KEY",
|
|
2304
|
+
apiBaseUrl: "SOLGRID_API_BASE_URL"
|
|
2305
|
+
};
|
|
2306
|
+
|
|
2307
|
+
// src/core/config.ts
|
|
2308
|
+
function resolveConfigDir(env, homeDir) {
|
|
2309
|
+
const base = env.XDG_CONFIG_HOME?.trim() || join(homeDir, ".config");
|
|
2310
|
+
return join(base, "solgrid", "cli");
|
|
2311
|
+
}
|
|
2312
|
+
|
|
2313
|
+
class ConfigStore {
|
|
2314
|
+
dir;
|
|
2315
|
+
configPath;
|
|
2316
|
+
credentialsPath;
|
|
2317
|
+
constructor(dir) {
|
|
2318
|
+
this.dir = dir;
|
|
2319
|
+
this.configPath = join(dir, "config.json");
|
|
2320
|
+
this.credentialsPath = join(dir, "credentials.json");
|
|
2321
|
+
}
|
|
2322
|
+
listKeys() {
|
|
2323
|
+
const active = this.getActiveLabel();
|
|
2324
|
+
return Object.entries(this.readCredentials().keys).map(([label, stored]) => ({ label, ...stored, active: label === active })).sort((a, b) => a.label.localeCompare(b.label));
|
|
2325
|
+
}
|
|
2326
|
+
getKey(label) {
|
|
2327
|
+
return ownKey(this.readCredentials().keys, label);
|
|
2328
|
+
}
|
|
2329
|
+
getActiveLabel() {
|
|
2330
|
+
return this.readConfig().activeKey || undefined;
|
|
2331
|
+
}
|
|
2332
|
+
addKey(label, key, addedAt, options = {}) {
|
|
2333
|
+
const credentials = this.readCredentials();
|
|
2334
|
+
const replaced = Boolean(ownKey(credentials.keys, label));
|
|
2335
|
+
if (replaced && !options.replace) {
|
|
2336
|
+
throw new CliError("USAGE", `A key labelled "${label}" already exists.`, {
|
|
2337
|
+
hint: "Pass --force to replace it, pick another label, or remove the old one first with `solgrid keys remove`."
|
|
2338
|
+
});
|
|
2339
|
+
}
|
|
2340
|
+
credentials.keys[label] = { key, addedAt: addedAt.toISOString() };
|
|
2341
|
+
this.writeCredentials(credentials);
|
|
2342
|
+
const active = this.getActiveLabel();
|
|
2343
|
+
const activated = options.activate || active === undefined || active === label;
|
|
2344
|
+
if (activated && active !== label)
|
|
2345
|
+
this.writeConfig({ ...this.readConfig(), activeKey: label });
|
|
2346
|
+
return { activated, replaced };
|
|
2347
|
+
}
|
|
2348
|
+
removeKey(label) {
|
|
2349
|
+
const credentials = this.readCredentials();
|
|
2350
|
+
if (!ownKey(credentials.keys, label))
|
|
2351
|
+
throw unknownLabel(label);
|
|
2352
|
+
delete credentials.keys[label];
|
|
2353
|
+
this.writeCredentials(credentials);
|
|
2354
|
+
const wasActive = this.getActiveLabel() === label;
|
|
2355
|
+
if (wasActive) {
|
|
2356
|
+
const { activeKey: _removed, ...rest } = this.readConfig();
|
|
2357
|
+
this.writeConfig(rest);
|
|
2358
|
+
}
|
|
2359
|
+
return { wasActive };
|
|
2360
|
+
}
|
|
2361
|
+
activate(label) {
|
|
2362
|
+
if (!this.getKey(label))
|
|
2363
|
+
throw unknownLabel(label);
|
|
2364
|
+
this.writeConfig({ ...this.readConfig(), activeKey: label });
|
|
2365
|
+
}
|
|
2366
|
+
readConfig() {
|
|
2367
|
+
return this.readJson(this.configPath) ?? {};
|
|
2368
|
+
}
|
|
2369
|
+
readCredentials() {
|
|
2370
|
+
const file = this.readJson(this.credentialsPath);
|
|
2371
|
+
return { keys: file?.keys ?? {} };
|
|
2372
|
+
}
|
|
2373
|
+
writeConfig(config) {
|
|
2374
|
+
this.writeJson(this.configPath, config, 420);
|
|
2375
|
+
}
|
|
2376
|
+
writeCredentials(credentials) {
|
|
2377
|
+
this.writeJson(this.credentialsPath, credentials, 384);
|
|
2378
|
+
}
|
|
2379
|
+
readJson(path) {
|
|
2380
|
+
if (!existsSync(path))
|
|
2381
|
+
return;
|
|
2382
|
+
try {
|
|
2383
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
2384
|
+
} catch (cause) {
|
|
2385
|
+
throw new CliError("CONFIG", `Cannot read ${path}: it is not valid JSON.`, {
|
|
2386
|
+
hint: "Fix or delete the file, then try again.",
|
|
2387
|
+
cause
|
|
2388
|
+
});
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
writeJson(path, value, mode) {
|
|
2392
|
+
mkdirSync(this.dir, { recursive: true, mode: 448 });
|
|
2393
|
+
const temp = `${path}.${process.pid}.tmp`;
|
|
2394
|
+
writeFileSync(temp, `${JSON.stringify(value, null, 2)}
|
|
2395
|
+
`, { mode });
|
|
2396
|
+
renameSync(temp, path);
|
|
2397
|
+
chmodSync(path, mode);
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2400
|
+
function resolveApiKey(store, env) {
|
|
2401
|
+
const fromEnv = env[ENV.apiKey]?.trim();
|
|
2402
|
+
if (fromEnv)
|
|
2403
|
+
return { key: fromEnv, source: "env" };
|
|
2404
|
+
const label = store.getActiveLabel();
|
|
2405
|
+
const stored = label ? store.getKey(label) : undefined;
|
|
2406
|
+
return label && stored ? { key: stored.key, source: "store", label } : undefined;
|
|
2407
|
+
}
|
|
2408
|
+
function ownKey(keys, label) {
|
|
2409
|
+
return Object.hasOwn(keys, label) ? keys[label] : undefined;
|
|
2410
|
+
}
|
|
2411
|
+
function unknownLabel(label) {
|
|
2412
|
+
return new CliError("USAGE", `No key labelled "${label}".`, {
|
|
2413
|
+
hint: "Run `solgrid keys list` to see stored keys."
|
|
2414
|
+
});
|
|
2415
|
+
}
|
|
2416
|
+
|
|
2417
|
+
// src/output/format.ts
|
|
2418
|
+
function formatHealth(metrics, style) {
|
|
2419
|
+
if (!metrics)
|
|
2420
|
+
return "";
|
|
2421
|
+
switch (metrics.status) {
|
|
2422
|
+
case "healthy":
|
|
2423
|
+
case "stable":
|
|
2424
|
+
return style("green", metrics.status);
|
|
2425
|
+
case "degraded":
|
|
2426
|
+
return style("yellow", metrics.status);
|
|
2427
|
+
case "outage":
|
|
2428
|
+
return style("red", metrics.status);
|
|
2429
|
+
default:
|
|
2430
|
+
return style("dim", metrics.status);
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
function formatDuration(ms) {
|
|
2434
|
+
if (ms < 1000)
|
|
2435
|
+
return `${Math.round(ms)}ms`;
|
|
2436
|
+
const seconds = ms / 1000;
|
|
2437
|
+
if (seconds < 60)
|
|
2438
|
+
return `${seconds < 10 ? seconds.toFixed(1) : Math.round(seconds)}s`;
|
|
2439
|
+
const minutes = Math.floor(seconds / 60);
|
|
2440
|
+
const rest = Math.round(seconds % 60);
|
|
2441
|
+
return rest ? `${minutes}m ${rest}s` : `${minutes}m`;
|
|
2442
|
+
}
|
|
2443
|
+
function formatStatus(status, style) {
|
|
2444
|
+
switch (status) {
|
|
2445
|
+
case "COMPLETED":
|
|
2446
|
+
return style("green", status);
|
|
2447
|
+
case "FAILED":
|
|
2448
|
+
case "BLOCKED":
|
|
2449
|
+
case "TIMED_OUT":
|
|
2450
|
+
return style("red", status);
|
|
2451
|
+
case "STOPPED":
|
|
2452
|
+
case "STOPPING":
|
|
2453
|
+
return style("yellow", status);
|
|
2454
|
+
default:
|
|
2455
|
+
return style("cyan", status);
|
|
2456
|
+
}
|
|
2457
|
+
}
|
|
2458
|
+
function formatTime(iso) {
|
|
2459
|
+
if (!iso)
|
|
2460
|
+
return "-";
|
|
2461
|
+
const date = new Date(iso);
|
|
2462
|
+
if (Number.isNaN(date.getTime()))
|
|
2463
|
+
return iso;
|
|
2464
|
+
return `${date.toISOString().slice(0, 19).replace("T", " ")} UTC`;
|
|
2465
|
+
}
|
|
2466
|
+
function truncate(text, max) {
|
|
2467
|
+
const single = text.replace(/\s+/g, " ").trim();
|
|
2468
|
+
return single.length > max ? `${single.slice(0, max - 1)}…` : single;
|
|
2469
|
+
}
|
|
2470
|
+
function renderHints(hints, style) {
|
|
2471
|
+
if (!hints)
|
|
2472
|
+
return [];
|
|
2473
|
+
return Object.entries(hints).filter(([, value]) => value !== undefined && value !== null && value !== "").map(([key, value]) => style("dim", `hint ${key}: ${typeof value === "string" ? value : JSON.stringify(value)}`));
|
|
2474
|
+
}
|
|
2475
|
+
function renderFields(fields, style) {
|
|
2476
|
+
const shown = fields.filter((field) => Boolean(field[1]));
|
|
2477
|
+
const width = Math.max(0, ...shown.map(([label]) => label.length));
|
|
2478
|
+
return shown.map(([label, value]) => `${style("dim", `${label}:`.padEnd(width + 1))} ${value}`);
|
|
2479
|
+
}
|
|
2480
|
+
|
|
2481
|
+
// src/output/money.ts
|
|
2482
|
+
var DECIMAL = /^-?\d+(\.\d+)?$/;
|
|
2483
|
+
function toDecimalString(value) {
|
|
2484
|
+
if (typeof value === "number")
|
|
2485
|
+
return Number.isFinite(value) ? value.toFixed(6) : undefined;
|
|
2486
|
+
if (typeof value !== "string")
|
|
2487
|
+
return;
|
|
2488
|
+
const trimmed = value.trim();
|
|
2489
|
+
return DECIMAL.test(trimmed) ? trimmed : undefined;
|
|
2490
|
+
}
|
|
2491
|
+
function formatAmount(amount) {
|
|
2492
|
+
if (!amount || typeof amount.value !== "number" && typeof amount.value !== "string")
|
|
2493
|
+
return "-";
|
|
2494
|
+
const decimal = toDecimalString(amount.value);
|
|
2495
|
+
if (decimal === undefined)
|
|
2496
|
+
return `${String(amount.value)} ${amount.currency}`.trim();
|
|
2497
|
+
const negative = decimal.startsWith("-");
|
|
2498
|
+
const [whole = "0", fraction = ""] = decimal.replace(/^-/, "").split(".");
|
|
2499
|
+
const trimmed = fraction.replace(/0+$/, "").padEnd(2, "0");
|
|
2500
|
+
const number = `${whole}.${trimmed}`;
|
|
2501
|
+
const sign = negative ? "-" : "";
|
|
2502
|
+
return amount.currency === "USD" ? `${sign}$${number}` : `${sign}${number} ${amount.currency}`;
|
|
2503
|
+
}
|
|
2504
|
+
|
|
2505
|
+
// src/core/api/client.ts
|
|
2506
|
+
var DEFAULT_TIMEOUT_MS = 30000;
|
|
2507
|
+
var TRANSFER_TIMEOUT_MS = 10 * 60000;
|
|
2508
|
+
|
|
2509
|
+
class ApiClient {
|
|
2510
|
+
options;
|
|
2511
|
+
baseUrl;
|
|
2512
|
+
constructor(options) {
|
|
2513
|
+
this.options = options;
|
|
2514
|
+
this.baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
2515
|
+
}
|
|
2516
|
+
post(path, body = {}, headers = {}, request = {}) {
|
|
2517
|
+
return this.send("POST", path, {
|
|
2518
|
+
body: JSON.stringify(body),
|
|
2519
|
+
headers: { "Content-Type": "application/json", ...headers },
|
|
2520
|
+
...request
|
|
2521
|
+
});
|
|
2522
|
+
}
|
|
2523
|
+
get(path, query = {}) {
|
|
2524
|
+
const search = new URLSearchParams(query).toString();
|
|
2525
|
+
return this.send("GET", search ? `${path}?${search}` : path);
|
|
2526
|
+
}
|
|
2527
|
+
upload(path, form) {
|
|
2528
|
+
return this.send("POST", path, { body: form, timeoutMs: TRANSFER_TIMEOUT_MS });
|
|
2529
|
+
}
|
|
2530
|
+
async openSignedUrl(url) {
|
|
2531
|
+
let response;
|
|
2532
|
+
try {
|
|
2533
|
+
response = await this.options.fetch(url, {
|
|
2534
|
+
method: "GET",
|
|
2535
|
+
headers: { "X-Solgrid-Client": this.options.clientId },
|
|
2536
|
+
signal: AbortSignal.timeout(TRANSFER_TIMEOUT_MS)
|
|
2537
|
+
});
|
|
2538
|
+
} catch (cause) {
|
|
2539
|
+
throw transportError(cause, new URL(url).origin, new URL(url).pathname, TRANSFER_TIMEOUT_MS);
|
|
2540
|
+
}
|
|
2541
|
+
if (!response.ok) {
|
|
2542
|
+
const text = await response.text().catch(() => "");
|
|
2543
|
+
throw envelopeError(response, parseEnvelope(text), text, url.split("?")[0] ?? url);
|
|
2544
|
+
}
|
|
2545
|
+
return response;
|
|
2546
|
+
}
|
|
2547
|
+
async send(method, path, options = {}) {
|
|
2548
|
+
const timeoutMs = options.timeoutMs ?? this.options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
2549
|
+
let response;
|
|
2550
|
+
let text;
|
|
2551
|
+
try {
|
|
2552
|
+
response = await this.options.fetch(`${this.baseUrl}${path}`, {
|
|
2553
|
+
method,
|
|
2554
|
+
headers: {
|
|
2555
|
+
Accept: "application/json",
|
|
2556
|
+
"X-Solgrid-Client": this.options.clientId,
|
|
2557
|
+
...this.options.apiKey ? { Authorization: `Bearer ${this.options.apiKey}` } : {},
|
|
2558
|
+
...options.headers
|
|
2559
|
+
},
|
|
2560
|
+
...options.body !== undefined ? { body: options.body } : {},
|
|
2561
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
2562
|
+
});
|
|
2563
|
+
text = await response.text();
|
|
2564
|
+
} catch (cause) {
|
|
2565
|
+
throw transportError(cause, this.baseUrl, path, timeoutMs);
|
|
2566
|
+
}
|
|
2567
|
+
const url = `${this.baseUrl}${path.split("?")[0] ?? path}`;
|
|
2568
|
+
const envelope = parseEnvelope(text);
|
|
2569
|
+
if (!response.ok || envelope?.success === false) {
|
|
2570
|
+
throw envelopeError(response, envelope, text, url);
|
|
2571
|
+
}
|
|
2572
|
+
if (!envelope || !("data" in envelope)) {
|
|
2573
|
+
throw new ApiError("INVALID_RESPONSE", `Unexpected response from ${path}: ${describeBody(response, text)}`, { status: response.status });
|
|
2574
|
+
}
|
|
2575
|
+
return envelope.data;
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
function parseEnvelope(text) {
|
|
2579
|
+
try {
|
|
2580
|
+
const value = JSON.parse(text);
|
|
2581
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
2582
|
+
} catch {
|
|
2583
|
+
return;
|
|
2584
|
+
}
|
|
2585
|
+
}
|
|
2586
|
+
function envelopeError(response, envelope, text, url) {
|
|
2587
|
+
if (!envelope && response.status === 404) {
|
|
2588
|
+
return new ApiError("NOT_FOUND", `${url} does not exist`, {
|
|
2589
|
+
status: 404,
|
|
2590
|
+
hint: "This Solgrid API may not support this command yet, or SOLGRID_API_BASE_URL points to the wrong server."
|
|
2591
|
+
});
|
|
2592
|
+
}
|
|
2593
|
+
const bodyCode = typeof envelope?.code === "number" && envelope.code >= 400 ? envelope.code : undefined;
|
|
2594
|
+
const status = bodyCode ?? (response.ok ? 400 : response.status);
|
|
2595
|
+
const key = typeof envelope?.message === "string" && envelope.message ? envelope.message : undefined;
|
|
2596
|
+
const details = [
|
|
2597
|
+
...validationDetails(envelope?.errorData),
|
|
2598
|
+
...frameworkErrorDetails(envelope?.errors)
|
|
2599
|
+
];
|
|
2600
|
+
const serverJsonHint = hasSerializerErrors(envelope?.errors) ? "The server failed to read JSON while handling this request. This is most likely a problem on the Solgrid side, not in your input; try again later or report it." : undefined;
|
|
2601
|
+
const message = key ?? (envelope ? `HTTP ${response.status}` : `HTTP ${response.status}: ${describeBody(response, text)}`);
|
|
2602
|
+
return new ApiError(errorCodeForStatus(status), message, {
|
|
2603
|
+
status,
|
|
2604
|
+
...key ? { key } : {},
|
|
2605
|
+
...serverJsonHint ? { hint: serverJsonHint } : {},
|
|
2606
|
+
details
|
|
2607
|
+
});
|
|
2608
|
+
}
|
|
2609
|
+
var FRAMEWORK_GENERAL_FIELDS = new Set(["generalErrors", "serializerErrors"]);
|
|
2610
|
+
function frameworkErrorDetails(errors) {
|
|
2611
|
+
if (!errors || typeof errors !== "object" || Array.isArray(errors))
|
|
2612
|
+
return [];
|
|
2613
|
+
return Object.entries(errors).flatMap(([field, messages]) => (Array.isArray(messages) ? messages : [messages]).filter((text) => typeof text === "string" && text.trim().length > 0).map((text) => FRAMEWORK_GENERAL_FIELDS.has(field) || !field ? text.trim() : `${field}: ${text.trim()}`));
|
|
2614
|
+
}
|
|
2615
|
+
function hasSerializerErrors(errors) {
|
|
2616
|
+
const value = errors?.serializerErrors;
|
|
2617
|
+
return Array.isArray(value) ? value.length > 0 : typeof value === "string" && value.length > 0;
|
|
2618
|
+
}
|
|
2619
|
+
function validationDetails(items) {
|
|
2620
|
+
if (!Array.isArray(items))
|
|
2621
|
+
return [];
|
|
2622
|
+
return items.flatMap((item) => {
|
|
2623
|
+
const text = item?.errorMessage?.trim();
|
|
2624
|
+
if (!text)
|
|
2625
|
+
return [];
|
|
2626
|
+
return [item.propertyName ? `${item.propertyName}: ${text}` : text];
|
|
2627
|
+
});
|
|
2628
|
+
}
|
|
2629
|
+
function transportError(cause, baseUrl, path, timeoutMs) {
|
|
2630
|
+
const name = cause instanceof Error ? cause.name : "";
|
|
2631
|
+
if (name === "TimeoutError" || name === "AbortError") {
|
|
2632
|
+
return new ApiError("TIMEOUT", `No response from ${baseUrl}${path} within ${timeoutMs / 1000}s.`, {
|
|
2633
|
+
cause
|
|
2634
|
+
});
|
|
2635
|
+
}
|
|
2636
|
+
const reason = cause instanceof Error ? cause.message : String(cause);
|
|
2637
|
+
return new ApiError("NETWORK_ERROR", `Could not reach ${baseUrl}: ${reason}`, {
|
|
2638
|
+
hint: "Check your network connection, or SOLGRID_API_BASE_URL if you set it.",
|
|
2639
|
+
cause
|
|
2640
|
+
});
|
|
2641
|
+
}
|
|
2642
|
+
function describeBody(response, text) {
|
|
2643
|
+
const type = response.headers.get("content-type")?.split(";")[0]?.trim() || "unknown content type";
|
|
2644
|
+
const snippet = text.replace(/\s+/g, " ").trim();
|
|
2645
|
+
if (!snippet)
|
|
2646
|
+
return `empty ${type} body`;
|
|
2647
|
+
return `${type} body "${snippet.length > 80 ? `${snippet.slice(0, 80)}…` : snippet}"`;
|
|
2648
|
+
}
|
|
2649
|
+
|
|
2650
|
+
// src/output/error-hints.ts
|
|
2651
|
+
var KEY_PRESENTATIONS = {
|
|
2652
|
+
wallet_balance_insufficient: {
|
|
2653
|
+
message: "Your wallet balance is too low for this run.",
|
|
2654
|
+
hint: `Top up at ${CONSOLE_URLS.wallet}`
|
|
2655
|
+
},
|
|
2656
|
+
daily_budget_exceeded: {
|
|
2657
|
+
message: "This run would exceed your daily budget.",
|
|
2658
|
+
hint: `Raise or disable the daily budget at ${CONSOLE_URLS.budgets}`
|
|
2659
|
+
},
|
|
2660
|
+
per_call_limit_exceeded: {
|
|
2661
|
+
message: "This run costs more than your per-call limit.",
|
|
2662
|
+
hint: `Raise or disable the per-call limit at ${CONSOLE_URLS.budgets}`
|
|
2663
|
+
},
|
|
2664
|
+
tool_not_found: {
|
|
2665
|
+
message: "No tool matches this provider and endpoint.",
|
|
2666
|
+
hint: 'Find tools with `solgrid discover -q "<what you need>"` and copy the provider and endpoint exactly.'
|
|
2667
|
+
},
|
|
2668
|
+
tool_price_unavailable: {
|
|
2669
|
+
message: "This tool cannot be run yet: no price is configured for it.",
|
|
2670
|
+
hint: "Pick another tool with `solgrid discover`, or try this one again later."
|
|
2671
|
+
},
|
|
2672
|
+
tool_quote_unavailable: {
|
|
2673
|
+
message: "No price is available for this tool yet.",
|
|
2674
|
+
hint: "Pick another tool with `solgrid discover`, or try this one again later."
|
|
2675
|
+
},
|
|
2676
|
+
tool_invocation_unavailable: {
|
|
2677
|
+
message: "This tool is not available to run.",
|
|
2678
|
+
hint: "Check the provider and endpoint with `solgrid inspect`, or pick another tool with `solgrid discover`."
|
|
2679
|
+
},
|
|
2680
|
+
file_size_invalid: {
|
|
2681
|
+
message: "The file is larger than Solgrid accepts for its type.",
|
|
2682
|
+
hint: "Limits: images and audio 20 MiB, video 200 MiB, Skill ZIP 10 MB."
|
|
2683
|
+
},
|
|
2684
|
+
file_type_mismatch: {
|
|
2685
|
+
message: "The file's content does not match the --type given.",
|
|
2686
|
+
hint: "Omit --type to let Solgrid detect the type from the content."
|
|
2687
|
+
},
|
|
2688
|
+
file_not_found: {
|
|
2689
|
+
message: "No such file, or it belongs to another account, or it was deleted.",
|
|
2690
|
+
hint: "Check the file ID; upload the file again if it is gone."
|
|
2691
|
+
}
|
|
2692
|
+
};
|
|
2693
|
+
function presentError(error) {
|
|
2694
|
+
if (error instanceof ApiError)
|
|
2695
|
+
return presentApiError(error);
|
|
2696
|
+
if (error.code === "UNEXPECTED") {
|
|
2697
|
+
return {
|
|
2698
|
+
message: `Unexpected error: ${error.message}`,
|
|
2699
|
+
hint: "This is probably a bug in the Solgrid CLI. Please report it with the command you ran."
|
|
2700
|
+
};
|
|
2701
|
+
}
|
|
2702
|
+
return { message: error.message, hint: error.hint };
|
|
2703
|
+
}
|
|
2704
|
+
function presentApiError(error) {
|
|
2705
|
+
const base = explain(error);
|
|
2706
|
+
const hint = [base.hint, error.hint].filter(Boolean).join(`
|
|
2707
|
+
`) || undefined;
|
|
2708
|
+
return { message: base.message, hint };
|
|
2709
|
+
}
|
|
2710
|
+
function explain(error) {
|
|
2711
|
+
const known = error.key ? KEY_PRESENTATIONS[error.key] : undefined;
|
|
2712
|
+
if (known)
|
|
2713
|
+
return known;
|
|
2714
|
+
switch (error.code) {
|
|
2715
|
+
case "AUTH_FAILED":
|
|
2716
|
+
return {
|
|
2717
|
+
message: "The API key is missing, invalid or revoked.",
|
|
2718
|
+
hint: `Create a key at ${CONSOLE_URLS.apiKeys}, then run: solgrid keys add -k <key> -l <label>`
|
|
2719
|
+
};
|
|
2720
|
+
case "INVALID_INPUT":
|
|
2721
|
+
return { message: `Invalid request: ${error.message}`, hint: undefined };
|
|
2722
|
+
case "NOT_FOUND":
|
|
2723
|
+
return { message: `Not found: ${error.message}`, hint: undefined };
|
|
2724
|
+
case "TOO_LARGE":
|
|
2725
|
+
return {
|
|
2726
|
+
message: "The upload was rejected as too large.",
|
|
2727
|
+
hint: "Limits: images and audio 20 MiB, video 200 MiB, Skill ZIP 10 MB. If the file is within them, a proxy in front of the API may have a lower limit."
|
|
2728
|
+
};
|
|
2729
|
+
case "RATE_LIMITED":
|
|
2730
|
+
return { message: "Too many requests.", hint: "Wait a moment and try again." };
|
|
2731
|
+
case "SERVER_ERROR":
|
|
2732
|
+
return {
|
|
2733
|
+
message: `Solgrid API error (${error.status ?? "5xx"}): ${error.message}`,
|
|
2734
|
+
hint: "This is usually temporary. Try again shortly."
|
|
2735
|
+
};
|
|
2736
|
+
default:
|
|
2737
|
+
return { message: error.message, hint: undefined };
|
|
2738
|
+
}
|
|
2739
|
+
}
|
|
2740
|
+
|
|
2741
|
+
// src/output/progress.ts
|
|
2742
|
+
var FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
2743
|
+
var INTERVAL_MS = 80;
|
|
2744
|
+
var noop = { start() {}, update() {}, stop() {} };
|
|
2745
|
+
function createProgress(stream, enabled) {
|
|
2746
|
+
if (!enabled || stream.isTTY !== true)
|
|
2747
|
+
return noop;
|
|
2748
|
+
let timer;
|
|
2749
|
+
let frame = 0;
|
|
2750
|
+
let label = "";
|
|
2751
|
+
const draw = () => {
|
|
2752
|
+
stream.write(`\r\x1B[2K${FRAMES[frame % FRAMES.length]} ${label}`);
|
|
2753
|
+
frame += 1;
|
|
2754
|
+
};
|
|
2755
|
+
return {
|
|
2756
|
+
start(text) {
|
|
2757
|
+
label = text;
|
|
2758
|
+
if (timer)
|
|
2759
|
+
return;
|
|
2760
|
+
draw();
|
|
2761
|
+
timer = setInterval(draw, INTERVAL_MS);
|
|
2762
|
+
timer.unref?.();
|
|
2763
|
+
},
|
|
2764
|
+
update(text) {
|
|
2765
|
+
label = text;
|
|
2766
|
+
},
|
|
2767
|
+
stop() {
|
|
2768
|
+
if (!timer)
|
|
2769
|
+
return;
|
|
2770
|
+
clearInterval(timer);
|
|
2771
|
+
timer = undefined;
|
|
2772
|
+
stream.write("\r\x1B[2K");
|
|
2773
|
+
}
|
|
2774
|
+
};
|
|
2775
|
+
}
|
|
2776
|
+
|
|
2777
|
+
// src/output/style.ts
|
|
2778
|
+
import { styleText } from "node:util";
|
|
2779
|
+
function colorEnabled(stream, env) {
|
|
2780
|
+
if (env.NO_COLOR)
|
|
2781
|
+
return false;
|
|
2782
|
+
if (env.FORCE_COLOR !== undefined)
|
|
2783
|
+
return env.FORCE_COLOR !== "0";
|
|
2784
|
+
return stream.isTTY === true && env.TERM !== "dumb";
|
|
2785
|
+
}
|
|
2786
|
+
function createStyler(stream, env) {
|
|
2787
|
+
if (!colorEnabled(stream, env))
|
|
2788
|
+
return (_format, text) => text;
|
|
2789
|
+
return (format, text) => styleText(format, text, { validateStream: false });
|
|
2790
|
+
}
|
|
2791
|
+
var ANSI_PATTERN = /\u001b\[[0-9;]*m/g;
|
|
2792
|
+
function stripAnsi(text) {
|
|
2793
|
+
return text.replace(ANSI_PATTERN, "");
|
|
2794
|
+
}
|
|
2795
|
+
|
|
2796
|
+
// src/output/output.ts
|
|
2797
|
+
function createOutput(ctx, json) {
|
|
2798
|
+
const style = createStyler(ctx.stdout, ctx.env);
|
|
2799
|
+
const errStyle = createStyler(ctx.stderr, ctx.env);
|
|
2800
|
+
const progress = createProgress(ctx.stderr, !json);
|
|
2801
|
+
return {
|
|
2802
|
+
json,
|
|
2803
|
+
style,
|
|
2804
|
+
progress,
|
|
2805
|
+
result(data, render) {
|
|
2806
|
+
progress.stop();
|
|
2807
|
+
if (json) {
|
|
2808
|
+
ctx.stdout.write(`${JSON.stringify(data, null, 2)}
|
|
2809
|
+
`);
|
|
2810
|
+
return;
|
|
2811
|
+
}
|
|
2812
|
+
const text = render();
|
|
2813
|
+
if (text)
|
|
2814
|
+
ctx.stdout.write(text.endsWith(`
|
|
2815
|
+
`) ? text : `${text}
|
|
2816
|
+
`);
|
|
2817
|
+
},
|
|
2818
|
+
error(error) {
|
|
2819
|
+
progress.stop();
|
|
2820
|
+
const cliError = toCliError(error);
|
|
2821
|
+
const { message, hint } = presentError(cliError);
|
|
2822
|
+
if (json) {
|
|
2823
|
+
const payload = {
|
|
2824
|
+
error: {
|
|
2825
|
+
code: cliError.code,
|
|
2826
|
+
message,
|
|
2827
|
+
...cliError instanceof ApiError && cliError.status !== undefined ? { status: cliError.status } : {},
|
|
2828
|
+
...cliError instanceof ApiError && cliError.key ? { key: cliError.key } : {},
|
|
2829
|
+
...cliError.details.length > 0 ? { details: cliError.details } : {},
|
|
2830
|
+
...hint ? { hint } : {}
|
|
2831
|
+
},
|
|
2832
|
+
...cliError.data
|
|
2833
|
+
};
|
|
2834
|
+
ctx.stdout.write(`${JSON.stringify(payload, null, 2)}
|
|
2835
|
+
`);
|
|
2836
|
+
return;
|
|
2837
|
+
}
|
|
2838
|
+
const lines = [`${errStyle(["bold", "red"], "Error:")} ${message}`];
|
|
2839
|
+
for (const detail of cliError.details)
|
|
2840
|
+
lines.push(` - ${detail}`);
|
|
2841
|
+
if (hint)
|
|
2842
|
+
lines.push(...hint.split(`
|
|
2843
|
+
`).map((line) => errStyle("dim", line)));
|
|
2844
|
+
ctx.stderr.write(`${lines.join(`
|
|
2845
|
+
`)}
|
|
2846
|
+
`);
|
|
2847
|
+
}
|
|
2848
|
+
};
|
|
2849
|
+
}
|
|
2850
|
+
function toCliError(error) {
|
|
2851
|
+
if (error instanceof CliError)
|
|
2852
|
+
return error;
|
|
2853
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2854
|
+
return new CliError("UNEXPECTED", message, { cause: error });
|
|
2855
|
+
}
|
|
2856
|
+
|
|
2857
|
+
// src/commands/shared.ts
|
|
2858
|
+
var JSON_FLAG = [
|
|
2859
|
+
"-j, --json",
|
|
2860
|
+
"Print the result as JSON (for scripts and agents)."
|
|
2861
|
+
];
|
|
2862
|
+
async function runAction(ctx, json, body) {
|
|
2863
|
+
const out = createOutput(ctx, json === true);
|
|
2864
|
+
try {
|
|
2865
|
+
await body(out);
|
|
2866
|
+
} catch (error) {
|
|
2867
|
+
out.error(error);
|
|
2868
|
+
ctx.exitCode = 1;
|
|
2869
|
+
}
|
|
2870
|
+
}
|
|
2871
|
+
function configStore(ctx) {
|
|
2872
|
+
return new ConfigStore(resolveConfigDir(ctx.env, ctx.homeDir));
|
|
2873
|
+
}
|
|
2874
|
+
function apiClient(ctx) {
|
|
2875
|
+
const resolved = resolveApiKey(configStore(ctx), ctx.env);
|
|
2876
|
+
if (!resolved) {
|
|
2877
|
+
throw new CliError("AUTH_FAILED", "No API key configured.", {
|
|
2878
|
+
hint: `Create a key at ${CONSOLE_URLS.apiKeys}, then run: solgrid keys add -k <key> -l <label>`
|
|
2879
|
+
});
|
|
2880
|
+
}
|
|
2881
|
+
return new ApiClient({
|
|
2882
|
+
baseUrl: ctx.env[ENV.apiBaseUrl]?.trim() || DEFAULT_API_BASE_URL,
|
|
2883
|
+
apiKey: resolved.key,
|
|
2884
|
+
clientId: `cli@${buildInfo.version}`,
|
|
2885
|
+
fetch: ctx.fetch
|
|
2886
|
+
});
|
|
2887
|
+
}
|
|
2888
|
+
|
|
2889
|
+
// src/commands/account.ts
|
|
2890
|
+
function registerAccountCommands(program, ctx) {
|
|
2891
|
+
program.command("whoami").description("Show which Solgrid account the current API key belongs to.").option(...JSON_FLAG).action((opts) => runAction(ctx, opts.json, async (out) => {
|
|
2892
|
+
const client = apiClient(ctx);
|
|
2893
|
+
out.progress.start("Checking API key…");
|
|
2894
|
+
const data = await whoami(client);
|
|
2895
|
+
out.result(data, () => {
|
|
2896
|
+
const key = resolveApiKey(configStore(ctx), ctx.env);
|
|
2897
|
+
const keySource = !key ? undefined : key.source === "env" ? `${maskApiKey(key.key)} (from ${ENV.apiKey})` : `${maskApiKey(key.key)} (stored as "${key.label}")`;
|
|
2898
|
+
return [
|
|
2899
|
+
...renderFields([
|
|
2900
|
+
["User", data.user.username],
|
|
2901
|
+
["Email", data.user.email],
|
|
2902
|
+
["User ID", data.user.userId],
|
|
2903
|
+
["Since", formatTime(data.user.createdAt)],
|
|
2904
|
+
["API key", keySource]
|
|
2905
|
+
], out.style),
|
|
2906
|
+
...renderHints(data.hints, out.style)
|
|
2907
|
+
].join(`
|
|
2908
|
+
`);
|
|
2909
|
+
});
|
|
2910
|
+
}));
|
|
2911
|
+
program.command("balance").description("Show the wallet balance available for runs.").option(...JSON_FLAG).action((opts) => runAction(ctx, opts.json, async (out) => {
|
|
2912
|
+
const client = apiClient(ctx);
|
|
2913
|
+
out.progress.start("Fetching balance…");
|
|
2914
|
+
const data = await getBalance(client);
|
|
2915
|
+
out.result(data, () => [
|
|
2916
|
+
`Available balance: ${out.style("bold", formatAmount(data.balance))}`,
|
|
2917
|
+
...renderHints(data.hints, out.style)
|
|
2918
|
+
].join(`
|
|
2919
|
+
`));
|
|
2920
|
+
}));
|
|
2921
|
+
}
|
|
2922
|
+
|
|
2923
|
+
// src/commands/files.ts
|
|
2924
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
2925
|
+
|
|
2926
|
+
// src/core/download.ts
|
|
2927
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
2928
|
+
import { constants } from "node:fs";
|
|
2929
|
+
import { copyFile, link, open, rename, unlink } from "node:fs/promises";
|
|
2930
|
+
import { dirname, join as join2 } from "node:path";
|
|
2931
|
+
async function saveDownload(body, destination, options) {
|
|
2932
|
+
const temp = join2(dirname(destination), `.solgrid-${randomUUID2()}.part`);
|
|
2933
|
+
let handle;
|
|
2934
|
+
try {
|
|
2935
|
+
handle = await open(temp, "wx", 420);
|
|
2936
|
+
} catch (cause) {
|
|
2937
|
+
await body.cancel().catch(() => {});
|
|
2938
|
+
throw ioError(`Cannot write to ${dirname(destination)}`, cause);
|
|
2939
|
+
}
|
|
2940
|
+
let committed = false;
|
|
2941
|
+
try {
|
|
2942
|
+
const bytes = await copyBody(body, handle, destination);
|
|
2943
|
+
await handle.close().catch(() => {});
|
|
2944
|
+
await commit(temp, destination, options.overwrite);
|
|
2945
|
+
committed = true;
|
|
2946
|
+
return bytes;
|
|
2947
|
+
} finally {
|
|
2948
|
+
await handle.close().catch(() => {});
|
|
2949
|
+
if (!committed)
|
|
2950
|
+
await unlink(temp).catch(() => {});
|
|
2951
|
+
}
|
|
2952
|
+
}
|
|
2953
|
+
async function copyBody(body, handle, destination) {
|
|
2954
|
+
const reader = body.getReader();
|
|
2955
|
+
let bytes = 0;
|
|
2956
|
+
for (;; ) {
|
|
2957
|
+
let chunk;
|
|
2958
|
+
try {
|
|
2959
|
+
chunk = await reader.read();
|
|
2960
|
+
} catch (cause) {
|
|
2961
|
+
const name = cause instanceof Error ? cause.name : "";
|
|
2962
|
+
throw new ApiError(name === "TimeoutError" || name === "AbortError" ? "TIMEOUT" : "NETWORK_ERROR", `The download was interrupted: ${cause instanceof Error ? cause.message : String(cause)}`, { hint: "Run the same command again to retry.", cause });
|
|
2963
|
+
}
|
|
2964
|
+
if (chunk.done)
|
|
2965
|
+
return bytes;
|
|
2966
|
+
try {
|
|
2967
|
+
await writeFully(handle, chunk.value);
|
|
2968
|
+
} catch (cause) {
|
|
2969
|
+
await reader.cancel().catch(() => {});
|
|
2970
|
+
throw ioError(`Cannot write ${destination}`, cause);
|
|
2971
|
+
}
|
|
2972
|
+
bytes += chunk.value.length;
|
|
2973
|
+
}
|
|
2974
|
+
}
|
|
2975
|
+
async function writeFully(handle, data) {
|
|
2976
|
+
let offset = 0;
|
|
2977
|
+
while (offset < data.length) {
|
|
2978
|
+
const { bytesWritten } = await handle.write(data, offset, data.length - offset);
|
|
2979
|
+
if (bytesWritten <= 0)
|
|
2980
|
+
throw new Error("the write made no progress");
|
|
2981
|
+
offset += bytesWritten;
|
|
2982
|
+
}
|
|
2983
|
+
}
|
|
2984
|
+
var NO_LINKS = new Set(["EPERM", "ENOTSUP", "EOPNOTSUPP", "EXDEV", "EMLINK", "ENOSYS"]);
|
|
2985
|
+
async function commit(temp, destination, overwrite) {
|
|
2986
|
+
try {
|
|
2987
|
+
if (overwrite) {
|
|
2988
|
+
await rename(temp, destination);
|
|
2989
|
+
return;
|
|
2990
|
+
}
|
|
2991
|
+
try {
|
|
2992
|
+
await link(temp, destination);
|
|
2993
|
+
} catch (error) {
|
|
2994
|
+
if (!NO_LINKS.has(errnoCode(error) ?? ""))
|
|
2995
|
+
throw error;
|
|
2996
|
+
await copyFile(temp, destination, constants.COPYFILE_EXCL);
|
|
2997
|
+
}
|
|
2998
|
+
await unlink(temp).catch(() => {});
|
|
2999
|
+
} catch (cause) {
|
|
3000
|
+
if (errnoCode(cause) === "EEXIST") {
|
|
3001
|
+
throw new CliError("USAGE", `${destination} already exists.`, {
|
|
3002
|
+
hint: "It appeared while downloading. Pass --force to overwrite it, or choose another path with -o.",
|
|
3003
|
+
cause
|
|
3004
|
+
});
|
|
3005
|
+
}
|
|
3006
|
+
throw ioError(`Cannot save ${destination}`, cause);
|
|
3007
|
+
}
|
|
3008
|
+
}
|
|
3009
|
+
function errnoCode(error) {
|
|
3010
|
+
return error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : undefined;
|
|
3011
|
+
}
|
|
3012
|
+
function ioError(what, cause) {
|
|
3013
|
+
const code = errnoCode(cause);
|
|
3014
|
+
const reason = code === "ENOENT" ? "the directory does not exist" : code === "EACCES" || code === "EPERM" ? "permission denied" : code === "ENOSPC" ? "no space left on the device" : cause instanceof Error ? cause.message : String(cause);
|
|
3015
|
+
return new CliError("IO_ERROR", `${what}: ${reason}.`, {
|
|
3016
|
+
hint: "Check the path given with -o, and that its directory exists and is writable.",
|
|
3017
|
+
cause
|
|
3018
|
+
});
|
|
3019
|
+
}
|
|
3020
|
+
function filenameFromDisposition(header) {
|
|
3021
|
+
if (!header)
|
|
3022
|
+
return;
|
|
3023
|
+
const extended = /(?:^|;)\s*filename\*\s*=\s*([^;]+)/i.exec(header)?.[1]?.trim();
|
|
3024
|
+
if (extended) {
|
|
3025
|
+
const match = /^([\w-]+)'[^']*'(.+)$/.exec(extended.replace(/^"(.*)"$/, "$1"));
|
|
3026
|
+
if (match?.[2] && /^utf-8$/i.test(match[1] ?? "")) {
|
|
3027
|
+
try {
|
|
3028
|
+
const name = safeName(decodeURIComponent(match[2]));
|
|
3029
|
+
if (name)
|
|
3030
|
+
return name;
|
|
3031
|
+
} catch {}
|
|
3032
|
+
}
|
|
3033
|
+
}
|
|
3034
|
+
const plain = /(?:^|;)\s*filename\s*=\s*(?:"((?:[^"\\]|\\.)*)"|([^;]*))/i.exec(header);
|
|
3035
|
+
const value = plain?.[1] !== undefined ? plain[1].replace(/\\(.)/g, "$1") : plain?.[2];
|
|
3036
|
+
return value ? safeName(value) : undefined;
|
|
3037
|
+
}
|
|
3038
|
+
function safeName(name) {
|
|
3039
|
+
const cleaned = (name.split(/[/\\]/).pop() ?? "").replace(/[\u0000-\u001f\u007f]/g, "").trim();
|
|
3040
|
+
return cleaned && cleaned !== "." && cleaned !== ".." ? cleaned : undefined;
|
|
3041
|
+
}
|
|
3042
|
+
|
|
3043
|
+
// src/core/files.ts
|
|
3044
|
+
import { openAsBlob, statSync } from "node:fs";
|
|
3045
|
+
import { basename, extname } from "node:path";
|
|
3046
|
+
var KIND_BY_NUMBER = { 1: "image", 2: "video", 3: "audio", 4: "skill" };
|
|
3047
|
+
var KIND_BY_NAME = {
|
|
3048
|
+
image: "image",
|
|
3049
|
+
video: "video",
|
|
3050
|
+
audio: "audio",
|
|
3051
|
+
archive: "skill",
|
|
3052
|
+
skill: "skill"
|
|
3053
|
+
};
|
|
3054
|
+
var STATUS_BY_NUMBER = { 1: "storing", 2: "available", 3: "failed" };
|
|
3055
|
+
var FILE_TYPE_CODE = { image: 1, video: 2, audio: 3, skill: 4 };
|
|
3056
|
+
function kindOf(value) {
|
|
3057
|
+
if (typeof value === "number")
|
|
3058
|
+
return KIND_BY_NUMBER[value];
|
|
3059
|
+
if (typeof value === "string")
|
|
3060
|
+
return KIND_BY_NAME[value.toLowerCase()];
|
|
3061
|
+
return;
|
|
3062
|
+
}
|
|
3063
|
+
function statusOf(value) {
|
|
3064
|
+
if (typeof value === "number")
|
|
3065
|
+
return STATUS_BY_NUMBER[value];
|
|
3066
|
+
if (typeof value === "string")
|
|
3067
|
+
return value.toLowerCase();
|
|
3068
|
+
return;
|
|
3069
|
+
}
|
|
3070
|
+
var MIB = 1024 * 1024;
|
|
3071
|
+
var SIZE_LIMIT = {
|
|
3072
|
+
image: 20 * MIB,
|
|
3073
|
+
audio: 20 * MIB,
|
|
3074
|
+
video: 200 * MIB,
|
|
3075
|
+
skill: 1e7
|
|
3076
|
+
};
|
|
3077
|
+
var KIND_BY_EXTENSION = {
|
|
3078
|
+
".png": "image",
|
|
3079
|
+
".jpg": "image",
|
|
3080
|
+
".jpeg": "image",
|
|
3081
|
+
".webp": "image",
|
|
3082
|
+
".gif": "image",
|
|
3083
|
+
".bmp": "image",
|
|
3084
|
+
".mp4": "video",
|
|
3085
|
+
".mov": "video",
|
|
3086
|
+
".webm": "video",
|
|
3087
|
+
".mkv": "video",
|
|
3088
|
+
".mp3": "audio",
|
|
3089
|
+
".wav": "audio",
|
|
3090
|
+
".m4a": "audio",
|
|
3091
|
+
".aac": "audio",
|
|
3092
|
+
".ogg": "audio",
|
|
3093
|
+
".flac": "audio",
|
|
3094
|
+
".zip": "skill"
|
|
3095
|
+
};
|
|
3096
|
+
var CONTENT_TYPE_BY_EXTENSION = {
|
|
3097
|
+
".png": "image/png",
|
|
3098
|
+
".jpg": "image/jpeg",
|
|
3099
|
+
".jpeg": "image/jpeg",
|
|
3100
|
+
".webp": "image/webp",
|
|
3101
|
+
".gif": "image/gif",
|
|
3102
|
+
".bmp": "image/bmp",
|
|
3103
|
+
".mp4": "video/mp4",
|
|
3104
|
+
".mov": "video/quicktime",
|
|
3105
|
+
".webm": "video/webm",
|
|
3106
|
+
".mkv": "video/x-matroska",
|
|
3107
|
+
".mp3": "audio/mpeg",
|
|
3108
|
+
".wav": "audio/wav",
|
|
3109
|
+
".m4a": "audio/mp4",
|
|
3110
|
+
".aac": "audio/aac",
|
|
3111
|
+
".ogg": "audio/ogg",
|
|
3112
|
+
".flac": "audio/flac",
|
|
3113
|
+
".zip": "application/zip"
|
|
3114
|
+
};
|
|
3115
|
+
var EXTENSION_BY_CONTENT_TYPE = {};
|
|
3116
|
+
for (const [ext, type] of Object.entries(CONTENT_TYPE_BY_EXTENSION)) {
|
|
3117
|
+
EXTENSION_BY_CONTENT_TYPE[type] ??= ext;
|
|
3118
|
+
}
|
|
3119
|
+
function guessKind(path) {
|
|
3120
|
+
return KIND_BY_EXTENSION[extname(path).toLowerCase()];
|
|
3121
|
+
}
|
|
3122
|
+
function extensionFor(contentType) {
|
|
3123
|
+
return contentType ? EXTENSION_BY_CONTENT_TYPE[contentType.split(";")[0]?.trim() ?? ""] ?? "" : "";
|
|
3124
|
+
}
|
|
3125
|
+
function formatBytes(bytes) {
|
|
3126
|
+
if (bytes < 1024)
|
|
3127
|
+
return `${bytes} B`;
|
|
3128
|
+
if (bytes < MIB)
|
|
3129
|
+
return `${(bytes / 1024).toFixed(1)} KiB`;
|
|
3130
|
+
return `${(bytes / MIB).toFixed(1)} MiB`;
|
|
3131
|
+
}
|
|
3132
|
+
function prepareUpload(path, declared) {
|
|
3133
|
+
let size;
|
|
3134
|
+
try {
|
|
3135
|
+
const stat = statSync(path);
|
|
3136
|
+
if (!stat.isFile())
|
|
3137
|
+
throw new Error("not a file");
|
|
3138
|
+
size = stat.size;
|
|
3139
|
+
} catch (cause) {
|
|
3140
|
+
throw new CliError("USAGE", `Cannot read ${path}.`, { cause });
|
|
3141
|
+
}
|
|
3142
|
+
if (size === 0)
|
|
3143
|
+
throw new CliError("USAGE", `${path} is empty.`);
|
|
3144
|
+
const kind = declared ?? guessKind(path);
|
|
3145
|
+
const limit = kind ? SIZE_LIMIT[kind] : SIZE_LIMIT.video;
|
|
3146
|
+
if (size > limit) {
|
|
3147
|
+
throw new CliError("USAGE", `${basename(path)} is ${formatBytes(size)}, over the ${formatBytes(limit)} limit${kind ? ` for ${kind} files` : ""}.`, { hint: "Limits: images and audio 20 MiB, video 200 MiB, Skill ZIP 10 MB." });
|
|
3148
|
+
}
|
|
3149
|
+
return { size, kind };
|
|
3150
|
+
}
|
|
3151
|
+
async function uploadFile(client, path, declared) {
|
|
3152
|
+
const contentType = CONTENT_TYPE_BY_EXTENSION[extname(path).toLowerCase()] ?? "application/octet-stream";
|
|
3153
|
+
const form = new FormData;
|
|
3154
|
+
if (declared)
|
|
3155
|
+
form.append("fileType", String(FILE_TYPE_CODE[declared]));
|
|
3156
|
+
form.append("file", await openAsBlob(path, { type: contentType }), basename(path));
|
|
3157
|
+
const data = await client.upload("/api/v1/file/upload", form);
|
|
3158
|
+
return expectShape("/api/v1/file/upload", data, { fileAssetId: isString });
|
|
3159
|
+
}
|
|
3160
|
+
async function getFileInfo(client, fileId) {
|
|
3161
|
+
const data = await client.get("/api/v1/file/info", { FileId: fileId });
|
|
3162
|
+
return expectShape("/api/v1/file/info", data, { fileAssetId: isString });
|
|
3163
|
+
}
|
|
3164
|
+
async function issueDownloadUrl(client, fileId) {
|
|
3165
|
+
const data = await client.post("/api/v1/file/download-url", { fileId });
|
|
3166
|
+
return expectShape("/api/v1/file/download-url", data, { downloadUrl: isString });
|
|
3167
|
+
}
|
|
3168
|
+
|
|
3169
|
+
// src/commands/files.ts
|
|
3170
|
+
var KINDS = ["image", "video", "audio", "skill"];
|
|
3171
|
+
function parseKind(value) {
|
|
3172
|
+
const kind = value.toLowerCase();
|
|
3173
|
+
if (!KINDS.includes(kind))
|
|
3174
|
+
throw new InvalidArgumentError(`must be one of ${KINDS.join(", ")}.`);
|
|
3175
|
+
return kind;
|
|
3176
|
+
}
|
|
3177
|
+
function registerFilesCommand(program, ctx) {
|
|
3178
|
+
const files = program.command("files").description("Upload local files to use as tool input, and fetch them back.").addHelpText("after", "\nUploads are private. Tools read them through a signed URL that expires after 24 hours;\nthe file itself is kept, so run `solgrid files url -i <fileId>` for a fresh URL.");
|
|
3179
|
+
files.command("upload").description("Upload a file and print a URL that can go straight into a tool's input.").argument("<path>", "the file to upload").option("--type <kind>", `declare the type (${KINDS.join(", ")}); detected from the content by default`, parseKind).option(...JSON_FLAG).action((path, opts) => runAction(ctx, opts.json, async (out) => {
|
|
3180
|
+
const { size } = prepareUpload(path, opts.type);
|
|
3181
|
+
const client = apiClient(ctx);
|
|
3182
|
+
out.progress.start(`Uploading ${path} (${formatBytes(size)})…`);
|
|
3183
|
+
const data = await uploadFile(client, path, opts.type);
|
|
3184
|
+
out.result(data, () => [
|
|
3185
|
+
`Uploaded ${path}.`,
|
|
3186
|
+
...renderFile(data, out.style),
|
|
3187
|
+
"",
|
|
3188
|
+
out.style("dim", "Pass the URL as the tool's file input, e.g. in `solgrid run -i`.")
|
|
3189
|
+
].join(`
|
|
3190
|
+
`));
|
|
3191
|
+
}));
|
|
3192
|
+
files.command("get").description("Show a file's type, size and status.").requiredOption("-i, --id <fileId>", "the file ID printed by upload").option(...JSON_FLAG).action((opts) => runAction(ctx, opts.json, async (out) => {
|
|
3193
|
+
const client = apiClient(ctx);
|
|
3194
|
+
out.progress.start("Fetching file…");
|
|
3195
|
+
const data = await getFileInfo(client, opts.id);
|
|
3196
|
+
out.result(data, () => renderFile(data, out.style).join(`
|
|
3197
|
+
`));
|
|
3198
|
+
}));
|
|
3199
|
+
files.command("url").description("Issue a fresh 24-hour URL for an uploaded file, e.g. after the old one expired.").requiredOption("-i, --id <fileId>", "the file ID printed by upload").option(...JSON_FLAG).action((opts) => runAction(ctx, opts.json, async (out) => {
|
|
3200
|
+
const client = apiClient(ctx);
|
|
3201
|
+
out.progress.start("Issuing URL…");
|
|
3202
|
+
const data = await issueDownloadUrl(client, opts.id);
|
|
3203
|
+
out.result(data, () => renderFields([
|
|
3204
|
+
["URL", data.downloadUrl],
|
|
3205
|
+
[
|
|
3206
|
+
"Expires",
|
|
3207
|
+
data.downloadUrlExpiresAt ? formatTime(data.downloadUrlExpiresAt) : undefined
|
|
3208
|
+
]
|
|
3209
|
+
], out.style).join(`
|
|
3210
|
+
`));
|
|
3211
|
+
}));
|
|
3212
|
+
files.command("download").description("Download an uploaded file.").requiredOption("-i, --id <fileId>", "the file ID printed by upload").option("-o, --output <path>", "where to save it (default: the file's name in the current directory)").option("--force", "overwrite the output file if it exists").option(...JSON_FLAG).action((opts) => runAction(ctx, opts.json, async (out) => {
|
|
3213
|
+
const client = apiClient(ctx);
|
|
3214
|
+
out.progress.start("Issuing URL…");
|
|
3215
|
+
const { downloadUrl } = await issueDownloadUrl(client, opts.id);
|
|
3216
|
+
if (!downloadUrl) {
|
|
3217
|
+
throw new CliError("INVALID_RESPONSE", "Solgrid did not return a download URL.");
|
|
3218
|
+
}
|
|
3219
|
+
out.progress.update("Downloading…");
|
|
3220
|
+
const response = await client.openSignedUrl(downloadUrl);
|
|
3221
|
+
const destination = opts.output ?? defaultName(response, opts.id);
|
|
3222
|
+
if (existsSync2(destination) && !opts.force) {
|
|
3223
|
+
await response.body?.cancel();
|
|
3224
|
+
throw new CliError("USAGE", `${destination} already exists.`, {
|
|
3225
|
+
hint: "Pass --force to overwrite it, or choose another path with -o."
|
|
3226
|
+
});
|
|
3227
|
+
}
|
|
3228
|
+
if (!response.body) {
|
|
3229
|
+
throw new CliError("INVALID_RESPONSE", "The download has no content.");
|
|
3230
|
+
}
|
|
3231
|
+
const bytes = await saveDownload(response.body, destination, {
|
|
3232
|
+
overwrite: opts.force === true
|
|
3233
|
+
});
|
|
3234
|
+
out.result({ fileAssetId: opts.id, path: destination, sizeBytes: bytes }, () => `Saved ${destination} (${formatBytes(bytes)}).`);
|
|
3235
|
+
}));
|
|
3236
|
+
}
|
|
3237
|
+
function renderFile(file, style) {
|
|
3238
|
+
const kind = kindOf(file.mediaKind ?? file.fileType);
|
|
3239
|
+
const status = statusOf(file.status);
|
|
3240
|
+
return renderFields([
|
|
3241
|
+
["File ID", file.fileAssetId],
|
|
3242
|
+
["Type", [kind, file.contentType].filter(Boolean).join(", ") || undefined],
|
|
3243
|
+
["Size", typeof file.sizeBytes === "number" ? formatBytes(file.sizeBytes) : undefined],
|
|
3244
|
+
["Status", status],
|
|
3245
|
+
["URL", file.downloadUrl],
|
|
3246
|
+
[
|
|
3247
|
+
"URL expires",
|
|
3248
|
+
file.downloadUrlExpiresAt ? formatTime(file.downloadUrlExpiresAt) : undefined
|
|
3249
|
+
]
|
|
3250
|
+
], style);
|
|
3251
|
+
}
|
|
3252
|
+
function defaultName(response, fileId) {
|
|
3253
|
+
return filenameFromDisposition(response.headers.get("content-disposition")) ?? `${fileId}${extensionFor(response.headers.get("content-type") ?? undefined)}`;
|
|
3254
|
+
}
|
|
3255
|
+
|
|
3256
|
+
// src/output/table.ts
|
|
3257
|
+
var WIDE_CHAR = /[ᄀ-ᅟ⺀-〾ぁ-㏿㐀-䶿一-鿿ꀀ-가-힣豈-︰-﹏-⦆¢-₩]/u;
|
|
3258
|
+
function displayWidth(text) {
|
|
3259
|
+
let width = 0;
|
|
3260
|
+
for (const char of stripAnsi(text))
|
|
3261
|
+
width += WIDE_CHAR.test(char) ? 2 : 1;
|
|
3262
|
+
return width;
|
|
3263
|
+
}
|
|
3264
|
+
function renderTable(headers, rows, options = {}) {
|
|
3265
|
+
const all = [headers, ...rows];
|
|
3266
|
+
const widths = headers.map((_, column) => Math.max(...all.map((row) => displayWidth(row[column] ?? ""))));
|
|
3267
|
+
const rightAligned = new Set(options.alignRight ?? []);
|
|
3268
|
+
const renderRow = (row, isHeader) => widths.map((width, column) => {
|
|
3269
|
+
const cell = row[column] ?? "";
|
|
3270
|
+
const padding = " ".repeat(width - displayWidth(cell));
|
|
3271
|
+
const shown = isHeader && options.formatHeader ? options.formatHeader(cell) : cell;
|
|
3272
|
+
const isLast = column === widths.length - 1;
|
|
3273
|
+
if (rightAligned.has(column))
|
|
3274
|
+
return padding + shown;
|
|
3275
|
+
return isLast ? shown : shown + padding;
|
|
3276
|
+
}).join(" ").trimEnd();
|
|
3277
|
+
return [renderRow(headers, true), ...rows.map((row) => renderRow(row, false))].join(`
|
|
3278
|
+
`);
|
|
3279
|
+
}
|
|
3280
|
+
|
|
3281
|
+
// src/commands/keys.ts
|
|
3282
|
+
function registerKeysCommand(program, ctx) {
|
|
3283
|
+
const keys = program.command("keys").description("Manage the API keys stored on this machine.").addHelpText("after", `
|
|
3284
|
+
Create keys at ${CONSOLE_URLS.apiKeys}. ${ENV.apiKey}, when set, takes precedence over the active key.`);
|
|
3285
|
+
keys.command("add").description("Store an API key under a label. The first key you add becomes active.").requiredOption("-k, --key <key>", "the API key (tl_…)").requiredOption("-l, --label <label>", "a name for this key, e.g. main or ci").option("--activate", "make this key active even if another key is").option("--force", "replace a stored key with the same label").option(...JSON_FLAG).action((opts) => runAction(ctx, opts.json, (out) => {
|
|
3286
|
+
const key = opts.key.trim();
|
|
3287
|
+
if (!isValidApiKey(key)) {
|
|
3288
|
+
throw new CliError("USAGE", "That does not look like a Solgrid API key.", {
|
|
3289
|
+
hint: `Keys start with tl_ followed by 64 hex characters. Copy one from ${CONSOLE_URLS.apiKeys}`
|
|
3290
|
+
});
|
|
3291
|
+
}
|
|
3292
|
+
if (!isValidLabel(opts.label)) {
|
|
3293
|
+
throw new CliError("USAGE", `Invalid label "${opts.label}".`, {
|
|
3294
|
+
hint: "Use up to 64 letters, digits, dots, dashes or underscores, starting with a letter or digit."
|
|
3295
|
+
});
|
|
3296
|
+
}
|
|
3297
|
+
const { activated, replaced } = configStore(ctx).addKey(opts.label, key, ctx.now(), {
|
|
3298
|
+
activate: opts.activate,
|
|
3299
|
+
replace: opts.force
|
|
3300
|
+
});
|
|
3301
|
+
out.result({ label: opts.label, key: maskApiKey(key), active: activated, replaced }, () => {
|
|
3302
|
+
const lines = [
|
|
3303
|
+
`${replaced ? "Replaced" : "Added"} key "${opts.label}" (${maskApiKey(key)}).`
|
|
3304
|
+
];
|
|
3305
|
+
lines.push(activated ? `It is now the active key.` : out.style("dim", `Make it active with: solgrid keys activate -l ${opts.label}`));
|
|
3306
|
+
return lines.join(`
|
|
3307
|
+
`);
|
|
3308
|
+
});
|
|
3309
|
+
}));
|
|
3310
|
+
keys.command("list").description("List stored keys. The active one is marked with *.").option(...JSON_FLAG).action((opts) => runAction(ctx, opts.json, (out) => {
|
|
3311
|
+
const entries = configStore(ctx).listKeys();
|
|
3312
|
+
const envOverride = Boolean(ctx.env[ENV.apiKey]?.trim());
|
|
3313
|
+
const data = {
|
|
3314
|
+
keys: entries.map((entry) => ({
|
|
3315
|
+
label: entry.label,
|
|
3316
|
+
key: maskApiKey(entry.key),
|
|
3317
|
+
addedAt: entry.addedAt,
|
|
3318
|
+
active: entry.active
|
|
3319
|
+
})),
|
|
3320
|
+
envOverride
|
|
3321
|
+
};
|
|
3322
|
+
out.result(data, () => {
|
|
3323
|
+
const lines = [];
|
|
3324
|
+
if (entries.length === 0) {
|
|
3325
|
+
lines.push("No API keys stored.", out.style("dim", "Add one with: solgrid keys add -k <key> -l <label>"));
|
|
3326
|
+
} else {
|
|
3327
|
+
lines.push(renderTable(["", "LABEL", "KEY", "ADDED"], data.keys.map((k) => [k.active ? "*" : "", k.label, k.key, k.addedAt.slice(0, 10)]), { formatHeader: (text) => out.style("dim", text) }));
|
|
3328
|
+
}
|
|
3329
|
+
if (envOverride) {
|
|
3330
|
+
lines.push(out.style("yellow", `${ENV.apiKey} is set and takes precedence over stored keys.`));
|
|
3331
|
+
}
|
|
3332
|
+
return lines.join(`
|
|
3333
|
+
`);
|
|
3334
|
+
});
|
|
3335
|
+
}));
|
|
3336
|
+
keys.command("activate").description("Make a stored key the active one.").requiredOption("-l, --label <label>", "label of the key to activate").option(...JSON_FLAG).action((opts) => runAction(ctx, opts.json, (out) => {
|
|
3337
|
+
configStore(ctx).activate(opts.label);
|
|
3338
|
+
out.result({ label: opts.label, active: true }, () => `"${opts.label}" is now the active key.`);
|
|
3339
|
+
}));
|
|
3340
|
+
keys.command("remove").description("Delete a stored key from this machine. This does not revoke it on Solgrid.").requiredOption("-l, --label <label>", "label of the key to remove").option("-y, --yes", "do not ask for confirmation").option(...JSON_FLAG).action((opts) => runAction(ctx, opts.json, async (out) => {
|
|
3341
|
+
const store = configStore(ctx);
|
|
3342
|
+
if (!store.getKey(opts.label)) {
|
|
3343
|
+
throw new CliError("USAGE", `No key labelled "${opts.label}".`, {
|
|
3344
|
+
hint: "Run `solgrid keys list` to see stored keys."
|
|
3345
|
+
});
|
|
3346
|
+
}
|
|
3347
|
+
if (!opts.yes) {
|
|
3348
|
+
if (opts.json || !ctx.stdinIsTTY) {
|
|
3349
|
+
throw new CliError("USAGE", "Refusing to remove a key without confirmation.", {
|
|
3350
|
+
hint: "Pass --yes to confirm in non-interactive use."
|
|
3351
|
+
});
|
|
3352
|
+
}
|
|
3353
|
+
if (!await ctx.confirm(`Remove key "${opts.label}" from this machine?`)) {
|
|
3354
|
+
out.result({ label: opts.label, removed: false }, () => "Cancelled.");
|
|
3355
|
+
return;
|
|
3356
|
+
}
|
|
3357
|
+
}
|
|
3358
|
+
const { wasActive } = store.removeKey(opts.label);
|
|
3359
|
+
out.result({ label: opts.label, removed: true, wasActive }, () => {
|
|
3360
|
+
const lines = [`Removed key "${opts.label}".`];
|
|
3361
|
+
if (wasActive) {
|
|
3362
|
+
lines.push(out.style("dim", "No key is active now. Pick one with: solgrid keys activate -l <label>"));
|
|
3363
|
+
}
|
|
3364
|
+
return lines.join(`
|
|
3365
|
+
`);
|
|
3366
|
+
});
|
|
3367
|
+
}));
|
|
3368
|
+
}
|
|
3369
|
+
|
|
3370
|
+
// src/commands/run.ts
|
|
3371
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
3372
|
+
|
|
3373
|
+
// src/core/api/types.ts
|
|
3374
|
+
var TERMINAL_RUN_STATUSES = new Set([
|
|
3375
|
+
"COMPLETED",
|
|
3376
|
+
"FAILED",
|
|
3377
|
+
"BLOCKED",
|
|
3378
|
+
"STOPPED",
|
|
3379
|
+
"TIMED_OUT"
|
|
3380
|
+
]);
|
|
3381
|
+
|
|
3382
|
+
// src/core/run-flow.ts
|
|
3383
|
+
function validateIdempotencyKey(key) {
|
|
3384
|
+
if (!key || key.length > 128 || key !== key.trim()) {
|
|
3385
|
+
throw new CliError("USAGE", "Invalid idempotency key.", {
|
|
3386
|
+
hint: "Use 1–128 characters without leading or trailing spaces."
|
|
3387
|
+
});
|
|
3388
|
+
}
|
|
3389
|
+
}
|
|
3390
|
+
var REJECTED = new Set([
|
|
3391
|
+
"INVALID_INPUT",
|
|
3392
|
+
"AUTH_FAILED",
|
|
3393
|
+
"FORBIDDEN",
|
|
3394
|
+
"NOT_FOUND",
|
|
3395
|
+
"UNPROCESSABLE"
|
|
3396
|
+
]);
|
|
3397
|
+
var NOT_RETRYABLE_CODES = new Set([
|
|
3398
|
+
"INVALID_INPUT",
|
|
3399
|
+
"AUTH_FAILED",
|
|
3400
|
+
"FORBIDDEN",
|
|
3401
|
+
"NOT_FOUND"
|
|
3402
|
+
]);
|
|
3403
|
+
var NOT_RETRYABLE_KEYS = new Set([
|
|
3404
|
+
"tool_price_unavailable",
|
|
3405
|
+
"tool_invocation_unavailable",
|
|
3406
|
+
"tool_quote_unavailable"
|
|
3407
|
+
]);
|
|
3408
|
+
function retryCanHelp(error) {
|
|
3409
|
+
if (!(error instanceof ApiError))
|
|
3410
|
+
return true;
|
|
3411
|
+
return !NOT_RETRYABLE_CODES.has(error.code) && !NOT_RETRYABLE_KEYS.has(error.key ?? "");
|
|
3412
|
+
}
|
|
3413
|
+
var TRANSIENT = new Set([
|
|
3414
|
+
"NETWORK_ERROR",
|
|
3415
|
+
"TIMEOUT",
|
|
3416
|
+
"SERVER_ERROR",
|
|
3417
|
+
"RATE_LIMITED",
|
|
3418
|
+
"INVALID_RESPONSE"
|
|
3419
|
+
]);
|
|
3420
|
+
var RESUBMIT_BACKOFF_MS = [1000, 2000, 4000];
|
|
3421
|
+
var LOOKUP_BACKOFF_MS = [1000, 2000];
|
|
3422
|
+
async function submitRun(client, request, options) {
|
|
3423
|
+
const { idempotencyKey } = request;
|
|
3424
|
+
for (let resubmissions = 0;; resubmissions++) {
|
|
3425
|
+
let failure;
|
|
3426
|
+
try {
|
|
3427
|
+
const run = await createRun(client, request, idempotencyKey);
|
|
3428
|
+
return { run, recovered: false };
|
|
3429
|
+
} catch (error) {
|
|
3430
|
+
if (!(error instanceof ApiError) || REJECTED.has(error.code)) {
|
|
3431
|
+
throw withContext(error, { idempotencyKey }, { retryable: retryCanHelp(error) });
|
|
3432
|
+
}
|
|
3433
|
+
failure = error;
|
|
3434
|
+
}
|
|
3435
|
+
const existing = await lookupByKey(client, idempotencyKey, options.sleep);
|
|
3436
|
+
if (existing)
|
|
3437
|
+
return { run: verifyRecovered(existing, request), recovered: true };
|
|
3438
|
+
if (resubmissions >= RESUBMIT_BACKOFF_MS.length) {
|
|
3439
|
+
throw withContext(failure, { idempotencyKey });
|
|
3440
|
+
}
|
|
3441
|
+
options.onRetry?.(resubmissions + 1, failure);
|
|
3442
|
+
await options.sleep(RESUBMIT_BACKOFF_MS[resubmissions] ?? 4000);
|
|
3443
|
+
}
|
|
3444
|
+
}
|
|
3445
|
+
async function lookupByKey(client, idempotencyKey, sleep) {
|
|
3446
|
+
for (let attempt = 0;; attempt++) {
|
|
3447
|
+
try {
|
|
3448
|
+
return await getRunByIdempotencyKey(client, idempotencyKey);
|
|
3449
|
+
} catch (error) {
|
|
3450
|
+
if (error instanceof ApiError && error.key === "tool_run_not_found")
|
|
3451
|
+
return;
|
|
3452
|
+
const transient = error instanceof ApiError && TRANSIENT.has(error.code);
|
|
3453
|
+
if (!transient || attempt >= LOOKUP_BACKOFF_MS.length) {
|
|
3454
|
+
throw withContext(error, { idempotencyKey }, { lead: "Could not confirm whether the run was accepted." });
|
|
3455
|
+
}
|
|
3456
|
+
await sleep(LOOKUP_BACKOFF_MS[attempt] ?? 2000);
|
|
3457
|
+
}
|
|
3458
|
+
}
|
|
3459
|
+
}
|
|
3460
|
+
function verifyRecovered(run, request) {
|
|
3461
|
+
const data = { idempotencyKey: request.idempotencyKey, runId: run.runId };
|
|
3462
|
+
const check = `Inspect it with: solgrid runs get -r ${run.runId}`;
|
|
3463
|
+
if (run.provider !== request.provider || run.endpoint !== request.endpoint) {
|
|
3464
|
+
throw new CliError("CONFLICT", "This idempotency key was already used to run a different tool.", {
|
|
3465
|
+
hint: `That run is ${run.runId} (${run.provider} ${run.endpoint}). Use a new key for a new run.`,
|
|
3466
|
+
data
|
|
3467
|
+
});
|
|
3468
|
+
}
|
|
3469
|
+
if (run.input === undefined) {
|
|
3470
|
+
throw new CliError("CONFLICT", `Found run ${run.runId} for this idempotency key, but cannot confirm it used the same input.`, { hint: check, data });
|
|
3471
|
+
}
|
|
3472
|
+
if (!sameInput(run.input, request.input)) {
|
|
3473
|
+
throw new CliError("CONFLICT", "This idempotency key was already used with different input.", {
|
|
3474
|
+
hint: `That run is ${run.runId}. Use a new key for a new run. ${check}`,
|
|
3475
|
+
data
|
|
3476
|
+
});
|
|
3477
|
+
}
|
|
3478
|
+
return run;
|
|
3479
|
+
}
|
|
3480
|
+
function withContext(error, data, options = {}) {
|
|
3481
|
+
if (!(error instanceof CliError))
|
|
3482
|
+
return error;
|
|
3483
|
+
const steps = [];
|
|
3484
|
+
if (options.lead)
|
|
3485
|
+
steps.push(options.lead);
|
|
3486
|
+
if (data.runId) {
|
|
3487
|
+
const check = `solgrid runs get -r ${data.runId}`;
|
|
3488
|
+
if (!error.hint?.includes(check))
|
|
3489
|
+
steps.push(`Check the run with: ${check} --wait`);
|
|
3490
|
+
} else if (data.idempotencyKey && options.retryable !== false) {
|
|
3491
|
+
steps.push(`Retry safely with --idempotency-key ${data.idempotencyKey}, or check: solgrid runs get --idempotency-key ${data.idempotencyKey}`);
|
|
3492
|
+
}
|
|
3493
|
+
const rebuilt = {
|
|
3494
|
+
hint: [error.hint, ...steps].filter(Boolean).join(`
|
|
3495
|
+
`),
|
|
3496
|
+
details: error.details,
|
|
3497
|
+
data: { ...error.data, ...data },
|
|
3498
|
+
cause: error
|
|
3499
|
+
};
|
|
3500
|
+
return error instanceof ApiError ? new ApiError(error.code, error.message, {
|
|
3501
|
+
...rebuilt,
|
|
3502
|
+
...error.status !== undefined ? { status: error.status } : {},
|
|
3503
|
+
...error.key ? { key: error.key } : {}
|
|
3504
|
+
}) : new CliError(error.code, error.message, rebuilt);
|
|
3505
|
+
}
|
|
3506
|
+
function sameInput(stored, submitted) {
|
|
3507
|
+
return canonical(normalize(stored)) === canonical(normalize(submitted));
|
|
3508
|
+
}
|
|
3509
|
+
function normalize(input) {
|
|
3510
|
+
const result = {};
|
|
3511
|
+
for (const key of ["body", "queryParams", "pathParams"]) {
|
|
3512
|
+
const value = input?.[key];
|
|
3513
|
+
if (value && Object.keys(value).length > 0)
|
|
3514
|
+
result[key] = value;
|
|
3515
|
+
}
|
|
3516
|
+
return result;
|
|
3517
|
+
}
|
|
3518
|
+
function canonical(value) {
|
|
3519
|
+
if (Array.isArray(value))
|
|
3520
|
+
return `[${value.map(canonical).join(",")}]`;
|
|
3521
|
+
if (value !== null && typeof value === "object") {
|
|
3522
|
+
const entries = Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
3523
|
+
return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${canonical(v)}`).join(",")}}`;
|
|
3524
|
+
}
|
|
3525
|
+
return JSON.stringify(value);
|
|
3526
|
+
}
|
|
3527
|
+
function isTerminal(status) {
|
|
3528
|
+
return TERMINAL_RUN_STATUSES.has(status);
|
|
3529
|
+
}
|
|
3530
|
+
var DEFAULT_WAIT_SECONDS = 300;
|
|
3531
|
+
var MAX_POLL_REQUEST_MS = 30000;
|
|
3532
|
+
var FIRST_POLL_MS = 1000;
|
|
3533
|
+
var MAX_POLL_MS = 1e4;
|
|
3534
|
+
async function waitFor(fetchRun, options) {
|
|
3535
|
+
const deadline = options.now().getTime() + options.timeoutMs;
|
|
3536
|
+
const remaining = () => deadline - options.now().getTime();
|
|
3537
|
+
const timedOut = () => waitTimeout(options.description, last, options.timeoutMs, options.checkCommand);
|
|
3538
|
+
let delay = FIRST_POLL_MS;
|
|
3539
|
+
let last;
|
|
3540
|
+
for (;; ) {
|
|
3541
|
+
const budget = remaining();
|
|
3542
|
+
if (budget <= 0)
|
|
3543
|
+
throw timedOut();
|
|
3544
|
+
try {
|
|
3545
|
+
last = await fetchRun(Math.min(budget, MAX_POLL_REQUEST_MS));
|
|
3546
|
+
} catch (error) {
|
|
3547
|
+
if (error instanceof ApiError && error.code === "TIMEOUT" && remaining() <= 0) {
|
|
3548
|
+
throw timedOut();
|
|
3549
|
+
}
|
|
3550
|
+
throw error;
|
|
3551
|
+
}
|
|
3552
|
+
options.onPoll?.(last);
|
|
3553
|
+
if (isTerminal(last.status))
|
|
3554
|
+
return last;
|
|
3555
|
+
const left = remaining();
|
|
3556
|
+
if (left <= 0)
|
|
3557
|
+
throw timedOut();
|
|
3558
|
+
await options.sleep(Math.min(delay, left));
|
|
3559
|
+
delay = Math.min(Math.round(delay * 1.5), MAX_POLL_MS);
|
|
3560
|
+
}
|
|
3561
|
+
}
|
|
3562
|
+
function waitForRun(client, runId, options) {
|
|
3563
|
+
return waitFor((timeoutMs) => getRun(client, runId, { timeoutMs }), {
|
|
3564
|
+
...options,
|
|
3565
|
+
description: `Run ${runId}`,
|
|
3566
|
+
checkCommand: `solgrid runs get -r ${runId} --wait`
|
|
3567
|
+
});
|
|
3568
|
+
}
|
|
3569
|
+
function waitTimeout(description, last, timeoutMs, checkCommand) {
|
|
3570
|
+
const runId = last?.runId;
|
|
3571
|
+
const status = last?.status;
|
|
3572
|
+
return new CliError("WAIT_TIMEOUT", `${runId ? `Run ${runId}` : description} did not finish within ${Math.round(timeoutMs / 1000)}s${status ? ` (still ${status})` : ""}.`, {
|
|
3573
|
+
hint: runId ? `It keeps running on Solgrid. Check again with: solgrid runs get -r ${runId} --wait` : checkCommand ? `The wait ended before the run could be fetched; it may still be running. Check again with a longer wait: ${checkCommand}` : "The run could not be fetched in time. Try again with a longer --wait.",
|
|
3574
|
+
data: { ...runId ? { runId } : {}, ...status ? { status } : {} }
|
|
3575
|
+
});
|
|
3576
|
+
}
|
|
3577
|
+
function resultOf(run) {
|
|
3578
|
+
return run.output ?? run.providerResponse?.data ?? run.providerResponse?.error ?? undefined;
|
|
3579
|
+
}
|
|
3580
|
+
|
|
3581
|
+
// src/commands/parse.ts
|
|
3582
|
+
function parsePositiveInt(value) {
|
|
3583
|
+
const parsed = Number(value);
|
|
3584
|
+
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
3585
|
+
throw new InvalidArgumentError("must be a positive whole number.");
|
|
3586
|
+
}
|
|
3587
|
+
return parsed;
|
|
3588
|
+
}
|
|
3589
|
+
|
|
3590
|
+
// src/commands/runs.ts
|
|
3591
|
+
var MAX_INLINE_OUTPUT_CHARS = 2000;
|
|
3592
|
+
function registerRunsCommand(program, ctx) {
|
|
3593
|
+
const runs = program.command("runs").description("List and inspect past runs.");
|
|
3594
|
+
runs.command("list").description("List recent runs, newest first.").option("-l, --limit <n>", "runs per page (default 20, up to 100)", parsePositiveInt).option("--cursor <cursor>", "continue from a previous page").option(...JSON_FLAG).action((opts) => runAction(ctx, opts.json, async (out) => {
|
|
3595
|
+
const client = apiClient(ctx);
|
|
3596
|
+
out.progress.start("Fetching runs…");
|
|
3597
|
+
const data = await listRuns(client, {
|
|
3598
|
+
...opts.limit !== undefined ? { limit: opts.limit } : {},
|
|
3599
|
+
...opts.cursor ? { cursor: opts.cursor } : {}
|
|
3600
|
+
});
|
|
3601
|
+
out.result(data, () => {
|
|
3602
|
+
if (data.items.length === 0) {
|
|
3603
|
+
return ["No runs yet.", ...renderHints(data.hints, out.style)].join(`
|
|
3604
|
+
`);
|
|
3605
|
+
}
|
|
3606
|
+
const lines = [
|
|
3607
|
+
renderTable(["RUN ID", "TOOL", "STATUS", "COST", "CREATED"], data.items.map((run) => [
|
|
3608
|
+
run.runId,
|
|
3609
|
+
`${run.provider} ${run.endpoint}`,
|
|
3610
|
+
formatStatus(run.status, out.style),
|
|
3611
|
+
run.cost ? formatAmount(run.cost) : "-",
|
|
3612
|
+
formatTime(run.createdAt)
|
|
3613
|
+
]), { formatHeader: (text) => out.style("dim", text), alignRight: [3] })
|
|
3614
|
+
];
|
|
3615
|
+
if (data.nextCursor) {
|
|
3616
|
+
lines.push("", out.style("dim", `More runs: solgrid runs list --cursor ${data.nextCursor}`));
|
|
3617
|
+
}
|
|
3618
|
+
lines.push(...renderHints(data.hints, out.style));
|
|
3619
|
+
return lines.join(`
|
|
3620
|
+
`);
|
|
3621
|
+
});
|
|
3622
|
+
}));
|
|
3623
|
+
runs.command("get").description("Show a run's status, cost and result.").option("-r, --run-id <id>", "the run to show").option("--idempotency-key <key>", "find the run submitted with this idempotency key instead").option("-w, --wait [seconds]", `wait for the run to finish (default ${DEFAULT_WAIT_SECONDS}s)`, parsePositiveInt).option(...JSON_FLAG).action((opts) => runAction(ctx, opts.json, async (out) => {
|
|
3624
|
+
if (Boolean(opts.runId) === Boolean(opts.idempotencyKey)) {
|
|
3625
|
+
throw new CliError("USAGE", "Pass exactly one of --run-id or --idempotency-key.");
|
|
3626
|
+
}
|
|
3627
|
+
const client = apiClient(ctx);
|
|
3628
|
+
const key = opts.idempotencyKey ?? "";
|
|
3629
|
+
out.progress.start("Fetching run…");
|
|
3630
|
+
let data;
|
|
3631
|
+
if (opts.wait === undefined) {
|
|
3632
|
+
data = opts.runId ? await getRun(client, opts.runId) : await getRunByIdempotencyKey(client, key);
|
|
3633
|
+
} else {
|
|
3634
|
+
let runId = opts.runId;
|
|
3635
|
+
const seconds = opts.wait === true ? DEFAULT_WAIT_SECONDS : opts.wait;
|
|
3636
|
+
data = await waitFor((timeoutMs) => runId ? getRun(client, runId, { timeoutMs }) : getRunByIdempotencyKey(client, key, { timeoutMs }), {
|
|
3637
|
+
description: opts.runId ? `Run ${opts.runId}` : `The run for idempotency key ${key}`,
|
|
3638
|
+
checkCommand: opts.runId ? `solgrid runs get -r ${opts.runId} --wait` : `solgrid runs get --idempotency-key ${key} --wait`,
|
|
3639
|
+
timeoutMs: seconds * 1000,
|
|
3640
|
+
now: () => ctx.now(),
|
|
3641
|
+
sleep: (ms) => ctx.sleep(ms),
|
|
3642
|
+
onPoll: (run) => {
|
|
3643
|
+
runId = run.runId;
|
|
3644
|
+
out.progress.update(`Waiting for ${run.runId} (${run.status})…`);
|
|
3645
|
+
}
|
|
3646
|
+
});
|
|
3647
|
+
}
|
|
3648
|
+
out.result(data, () => renderRun(data, out.style, { fullResultHint: "use --json for the full result" }));
|
|
3649
|
+
if (opts.wait !== undefined && data.status !== "COMPLETED")
|
|
3650
|
+
ctx.exitCode = 1;
|
|
3651
|
+
}));
|
|
3652
|
+
runs.command("stop").description("Ask Solgrid to stop a run that is still in progress.").requiredOption("-r, --run-id <id>", "the run to stop").option(...JSON_FLAG).action((opts) => runAction(ctx, opts.json, async (out) => {
|
|
3653
|
+
const client = apiClient(ctx);
|
|
3654
|
+
out.progress.start(`Stopping ${opts.runId}…`);
|
|
3655
|
+
try {
|
|
3656
|
+
const data = await stopRun(client, opts.runId);
|
|
3657
|
+
out.result(data, () => [
|
|
3658
|
+
`Stop requested for run ${data.runId} (${formatStatus(data.status, out.style)}).`,
|
|
3659
|
+
...data.message ? [data.message] : [],
|
|
3660
|
+
out.style("dim", `Check it with: solgrid runs get -r ${data.runId}`)
|
|
3661
|
+
].join(`
|
|
3662
|
+
`));
|
|
3663
|
+
} catch (error) {
|
|
3664
|
+
if (error instanceof ApiError && error.code === "CONFLICT") {
|
|
3665
|
+
throw new CliError("CONFLICT", `Run ${opts.runId} cannot be stopped: it has already finished, or its tool does not support stopping.`, {
|
|
3666
|
+
hint: `Check it with: solgrid runs get -r ${opts.runId}`,
|
|
3667
|
+
data: { runId: opts.runId },
|
|
3668
|
+
cause: error
|
|
3669
|
+
});
|
|
3670
|
+
}
|
|
3671
|
+
throw error;
|
|
3672
|
+
}
|
|
3673
|
+
}));
|
|
3674
|
+
}
|
|
3675
|
+
function describeRunError(error) {
|
|
3676
|
+
if (!error)
|
|
3677
|
+
return;
|
|
3678
|
+
const message = error.message || error.code;
|
|
3679
|
+
if (!message)
|
|
3680
|
+
return;
|
|
3681
|
+
return error.source ? `${message} (${error.source})` : message;
|
|
3682
|
+
}
|
|
3683
|
+
function renderRun(run, style, options) {
|
|
3684
|
+
const error = typeof run.error === "string" ? run.error : describeRunError(run.error);
|
|
3685
|
+
const lines = renderFields([
|
|
3686
|
+
["Run ID", run.runId],
|
|
3687
|
+
["Tool", `${run.providerName || run.provider} ${run.endpoint}`],
|
|
3688
|
+
["Status", formatStatus(run.status, style)],
|
|
3689
|
+
["Cost", run.cost ? formatAmount(run.cost) : undefined],
|
|
3690
|
+
["Created", formatTime(run.createdAt)],
|
|
3691
|
+
["Started", run.startedAt ? formatTime(run.startedAt) : undefined],
|
|
3692
|
+
["Completed", run.completedAt ? formatTime(run.completedAt) : undefined],
|
|
3693
|
+
["Provider HTTP", run.providerResponse ? String(run.providerResponse.httpStatus) : undefined],
|
|
3694
|
+
["Error", error ? style("red", error) : undefined]
|
|
3695
|
+
], style);
|
|
3696
|
+
const result = resultOf(run);
|
|
3697
|
+
if (result !== undefined) {
|
|
3698
|
+
const text = JSON.stringify(result, null, 2);
|
|
3699
|
+
lines.push("", style("bold", "Result"));
|
|
3700
|
+
lines.push(text.length > MAX_INLINE_OUTPUT_CHARS ? `${text.slice(0, MAX_INLINE_OUTPUT_CHARS)}
|
|
3701
|
+
${style("dim", `… ${text.length - MAX_INLINE_OUTPUT_CHARS} more characters; ${options.fullResultHint}.`)}` : text);
|
|
3702
|
+
}
|
|
3703
|
+
if (run.stoppable) {
|
|
3704
|
+
lines.push("", style("dim", `Still running. Stop it with: solgrid runs stop -r ${run.runId}`));
|
|
3705
|
+
}
|
|
3706
|
+
lines.push(...renderHints(run.hints, style));
|
|
3707
|
+
return lines.join(`
|
|
3708
|
+
`);
|
|
3709
|
+
}
|
|
3710
|
+
|
|
3711
|
+
// src/commands/run.ts
|
|
3712
|
+
function registerRunCommand(program, ctx) {
|
|
3713
|
+
program.command("run").description("Run a tool. Returns once the run is accepted, or waits for the result with --wait.").requiredOption("-p, --provider <provider>", "provider, as listed by discover").requiredOption("-e, --endpoint <endpoint>", "endpoint, as listed by discover").option("-i, --input <json>", "request body as a JSON object").option("-f, --input-file <path>", "read the request body from a JSON file").option("--query <json>", "query parameters as a JSON object").option("--path <json>", "path parameters as a JSON object").option("-w, --wait [seconds]", `wait for the run to finish (default ${DEFAULT_WAIT_SECONDS}s)`, parsePositiveInt).option("-o, --output <file>", "write the result to a file as JSON (implies --wait)").option("--idempotency-key <key>", "reuse the key from a failed attempt to retry without running twice").option(...JSON_FLAG).addHelpText("after", [
|
|
3714
|
+
"",
|
|
3715
|
+
"Exit code is 0 when the run is accepted (or, with --wait, completes) and 1 when it fails,",
|
|
3716
|
+
"times out or is stopped. Errors include the idempotency key (and the run ID once the run",
|
|
3717
|
+
"exists); pass the key back with --idempotency-key to retry without being charged twice."
|
|
3718
|
+
].join(`
|
|
3719
|
+
`)).action((opts) => runAction(ctx, opts.json, async (out) => {
|
|
3720
|
+
const input = readInput(opts);
|
|
3721
|
+
const idempotencyKey = opts.idempotencyKey ?? ctx.randomUUID();
|
|
3722
|
+
validateIdempotencyKey(idempotencyKey);
|
|
3723
|
+
const waitSeconds = opts.wait === true ? DEFAULT_WAIT_SECONDS : opts.wait;
|
|
3724
|
+
const client = apiClient(ctx);
|
|
3725
|
+
out.progress.start(`Submitting ${opts.provider} ${opts.endpoint}…`);
|
|
3726
|
+
const { run: submitted, recovered } = await submitRun(client, {
|
|
3727
|
+
provider: opts.provider,
|
|
3728
|
+
endpoint: opts.endpoint,
|
|
3729
|
+
...input ? { input } : {},
|
|
3730
|
+
idempotencyKey
|
|
3731
|
+
}, {
|
|
3732
|
+
sleep: (ms) => ctx.sleep(ms),
|
|
3733
|
+
onRetry: (attempt) => out.progress.update(`Submission interrupted, retrying (${attempt})…`)
|
|
3734
|
+
});
|
|
3735
|
+
try {
|
|
3736
|
+
await finishRun(ctx, client, out, {
|
|
3737
|
+
submitted,
|
|
3738
|
+
recovered,
|
|
3739
|
+
idempotencyKey,
|
|
3740
|
+
...waitSeconds !== undefined ? { waitSeconds } : {},
|
|
3741
|
+
...opts.output !== undefined ? { outputPath: opts.output } : {}
|
|
3742
|
+
});
|
|
3743
|
+
} catch (error) {
|
|
3744
|
+
throw withContext(error, { idempotencyKey, runId: submitted.runId });
|
|
3745
|
+
}
|
|
3746
|
+
}));
|
|
3747
|
+
}
|
|
3748
|
+
async function finishRun(ctx, client, out, params) {
|
|
3749
|
+
const { submitted, recovered, idempotencyKey, outputPath } = params;
|
|
3750
|
+
const wait = params.waitSeconds !== undefined || outputPath !== undefined;
|
|
3751
|
+
if (!wait && !isTerminal(submitted.status)) {
|
|
3752
|
+
out.result({ ...submitted, idempotencyKey, recovered }, () => renderSubmitted(submitted.runId, submitted.status, idempotencyKey, recovered, out));
|
|
3753
|
+
return;
|
|
3754
|
+
}
|
|
3755
|
+
const finished = isTerminal(submitted.status) ? submitted : await waitForRun(client, submitted.runId, {
|
|
3756
|
+
timeoutMs: (params.waitSeconds ?? DEFAULT_WAIT_SECONDS) * 1000,
|
|
3757
|
+
now: () => ctx.now(),
|
|
3758
|
+
sleep: (ms) => ctx.sleep(ms),
|
|
3759
|
+
onPoll: (run) => out.progress.update(`Waiting for ${run.runId} (${run.status})…`)
|
|
3760
|
+
});
|
|
3761
|
+
const outputFile = outputPath ? saveResult(finished, outputPath) : undefined;
|
|
3762
|
+
out.result({ ...finished, idempotencyKey, recovered, ...outputFile ? { outputFile } : {} }, () => {
|
|
3763
|
+
const lines = [];
|
|
3764
|
+
if (recovered) {
|
|
3765
|
+
lines.push(out.style("dim", "Found the run already submitted with this idempotency key."));
|
|
3766
|
+
}
|
|
3767
|
+
lines.push(renderRun(finished, out.style, {
|
|
3768
|
+
fullResultHint: "use --json for the full run, or -o <file> to save the result"
|
|
3769
|
+
}));
|
|
3770
|
+
if (outputPath) {
|
|
3771
|
+
lines.push("", outputFile ? `Result written to ${outputFile}` : out.style("yellow", `The run has no result to write; ${outputPath} was not created.`));
|
|
3772
|
+
}
|
|
3773
|
+
return lines.join(`
|
|
3774
|
+
`);
|
|
3775
|
+
});
|
|
3776
|
+
if (finished.status !== "COMPLETED")
|
|
3777
|
+
ctx.exitCode = 1;
|
|
3778
|
+
}
|
|
3779
|
+
function renderSubmitted(runId, status, idempotencyKey, recovered, out) {
|
|
3780
|
+
return [
|
|
3781
|
+
recovered ? "Found the run already submitted with this idempotency key." : "Run submitted.",
|
|
3782
|
+
...renderFields([
|
|
3783
|
+
["Run ID", runId],
|
|
3784
|
+
["Status", formatStatus(status, out.style)],
|
|
3785
|
+
["Idempotency key", idempotencyKey]
|
|
3786
|
+
], out.style),
|
|
3787
|
+
"",
|
|
3788
|
+
out.style("dim", `Follow it with: solgrid runs get -r ${runId} --wait`)
|
|
3789
|
+
].join(`
|
|
3790
|
+
`);
|
|
3791
|
+
}
|
|
3792
|
+
function readInput(opts) {
|
|
3793
|
+
if (opts.input !== undefined && opts.inputFile !== undefined) {
|
|
3794
|
+
throw new CliError("USAGE", "Use either --input or --input-file, not both.");
|
|
3795
|
+
}
|
|
3796
|
+
let body;
|
|
3797
|
+
if (opts.input !== undefined)
|
|
3798
|
+
body = parseObject(opts.input, "--input");
|
|
3799
|
+
if (opts.inputFile !== undefined) {
|
|
3800
|
+
let text;
|
|
3801
|
+
try {
|
|
3802
|
+
text = readFileSync2(opts.inputFile, "utf8");
|
|
3803
|
+
} catch (cause) {
|
|
3804
|
+
throw new CliError("USAGE", `Cannot read ${opts.inputFile}.`, { cause });
|
|
3805
|
+
}
|
|
3806
|
+
body = parseObject(text, opts.inputFile);
|
|
3807
|
+
}
|
|
3808
|
+
const queryParams = opts.query !== undefined ? parseObject(opts.query, "--query") : undefined;
|
|
3809
|
+
const pathParams = opts.path !== undefined ? parseObject(opts.path, "--path") : undefined;
|
|
3810
|
+
const input = {
|
|
3811
|
+
...body && Object.keys(body).length > 0 ? { body } : {},
|
|
3812
|
+
...queryParams && Object.keys(queryParams).length > 0 ? { queryParams } : {},
|
|
3813
|
+
...pathParams && Object.keys(pathParams).length > 0 ? { pathParams } : {}
|
|
3814
|
+
};
|
|
3815
|
+
return Object.keys(input).length > 0 ? input : undefined;
|
|
3816
|
+
}
|
|
3817
|
+
function parseObject(text, source) {
|
|
3818
|
+
let value;
|
|
3819
|
+
try {
|
|
3820
|
+
value = JSON.parse(text);
|
|
3821
|
+
} catch (cause) {
|
|
3822
|
+
throw new CliError("USAGE", `${source} is not valid JSON.`, {
|
|
3823
|
+
hint: `Pass a JSON object, e.g. ${source.startsWith("--") ? `${source} '{"key": "value"}'` : '{"key": "value"}'}`,
|
|
3824
|
+
cause
|
|
3825
|
+
});
|
|
3826
|
+
}
|
|
3827
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
3828
|
+
throw new CliError("USAGE", `${source} must be a JSON object.`);
|
|
3829
|
+
}
|
|
3830
|
+
return value;
|
|
3831
|
+
}
|
|
3832
|
+
function saveResult(run, path) {
|
|
3833
|
+
const result = resultOf(run);
|
|
3834
|
+
if (result === undefined)
|
|
3835
|
+
return;
|
|
3836
|
+
try {
|
|
3837
|
+
writeFileSync2(path, `${JSON.stringify(result, null, 2)}
|
|
3838
|
+
`);
|
|
3839
|
+
} catch (cause) {
|
|
3840
|
+
throw new CliError("USAGE", `Cannot write ${path}.`, {
|
|
3841
|
+
hint: "Check that the directory exists and is writable.",
|
|
3842
|
+
data: { runId: run.runId },
|
|
3843
|
+
cause
|
|
3844
|
+
});
|
|
3845
|
+
}
|
|
3846
|
+
return path;
|
|
3847
|
+
}
|
|
3848
|
+
|
|
3849
|
+
// src/commands/session-id.ts
|
|
3850
|
+
import { randomInt } from "node:crypto";
|
|
3851
|
+
var ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
3852
|
+
var LENGTH = 12;
|
|
3853
|
+
function generateSessionId() {
|
|
3854
|
+
let id = "";
|
|
3855
|
+
for (let i = 0;i < LENGTH; i++)
|
|
3856
|
+
id += ALPHABET[randomInt(ALPHABET.length)];
|
|
3857
|
+
return id;
|
|
3858
|
+
}
|
|
3859
|
+
function registerSessionIdCommand(program, ctx) {
|
|
3860
|
+
program.command("generate_session_id").description("Print a short random ID. Agents can generate one per task step and pass it to `run --idempotency-key` so retries of that step never run twice.").option(...JSON_FLAG).action((opts) => runAction(ctx, opts.json, (out) => {
|
|
3861
|
+
const sessionId = generateSessionId();
|
|
3862
|
+
out.result({ sessionId }, () => sessionId);
|
|
3863
|
+
}));
|
|
3864
|
+
}
|
|
3865
|
+
|
|
3866
|
+
// src/output/price.ts
|
|
3867
|
+
function periodText(period) {
|
|
3868
|
+
const unit = period.unit.toLowerCase();
|
|
3869
|
+
return period.count === 1 ? `per ${unit}` : `per ${period.count} ${unit}s`;
|
|
3870
|
+
}
|
|
3871
|
+
function countText(count) {
|
|
3872
|
+
if (count >= 1e6 && count % 1e6 === 0)
|
|
3873
|
+
return `${count / 1e6}M`;
|
|
3874
|
+
if (count >= 1000 && count % 1000 === 0)
|
|
3875
|
+
return `${count / 1000}K`;
|
|
3876
|
+
return String(count);
|
|
3877
|
+
}
|
|
3878
|
+
function unitText(price) {
|
|
3879
|
+
switch (price.type) {
|
|
3880
|
+
case "PER_CALL":
|
|
3881
|
+
return "per call";
|
|
3882
|
+
case "PER_RESULT":
|
|
3883
|
+
return "per result";
|
|
3884
|
+
case "METERED":
|
|
3885
|
+
return price.per && typeof price.per === "object" ? periodText(price.per) : "per unit";
|
|
3886
|
+
case "PER_TOKEN": {
|
|
3887
|
+
const unit = price.unit ?? "token";
|
|
3888
|
+
const per = typeof price.per === "number" ? price.per : 1;
|
|
3889
|
+
return per === 1 ? `per ${unit}` : `per ${countText(per)} ${unit}s`;
|
|
3890
|
+
}
|
|
3891
|
+
default:
|
|
3892
|
+
return "";
|
|
3893
|
+
}
|
|
3894
|
+
}
|
|
3895
|
+
function withUnit(amount, unit) {
|
|
3896
|
+
return unit ? `${amount} ${unit}` : amount;
|
|
3897
|
+
}
|
|
3898
|
+
function describeLeaf(leaf) {
|
|
3899
|
+
const text = withUnit(formatAmount(leaf.amount), unitText(leaf));
|
|
3900
|
+
return leaf.flatFee ? `${text} + ${formatAmount(leaf.flatFee)} flat` : text;
|
|
3901
|
+
}
|
|
3902
|
+
function summarizePrice(price) {
|
|
3903
|
+
let text;
|
|
3904
|
+
switch (price.type) {
|
|
3905
|
+
case "PER_UNIT_MATRIX": {
|
|
3906
|
+
const first = price.variants?.[0]?.price;
|
|
3907
|
+
text = `from ${withUnit(formatAmount(price.amount), first ? unitText(first) : "")}`;
|
|
3908
|
+
break;
|
|
3909
|
+
}
|
|
3910
|
+
case "TIERED":
|
|
3911
|
+
text = price.default ? describeLeaf(price.default) : "varies";
|
|
3912
|
+
break;
|
|
3913
|
+
case "BY_PERIOD":
|
|
3914
|
+
text = withUnit(formatAmount(price.amount), price.period ? periodText(price.period) : "");
|
|
3915
|
+
break;
|
|
3916
|
+
default:
|
|
3917
|
+
text = withUnit(formatAmount(price.amount), unitText(price));
|
|
3918
|
+
}
|
|
3919
|
+
if (price.flatFee)
|
|
3920
|
+
text += ` + ${formatAmount(price.flatFee)} flat`;
|
|
3921
|
+
if (price.type === "TIERED" && price.tiers?.length)
|
|
3922
|
+
text += " + add-ons";
|
|
3923
|
+
return text;
|
|
3924
|
+
}
|
|
3925
|
+
function whenText(when) {
|
|
3926
|
+
return Object.entries(when).map(([key, value]) => `${key}=${String(value)}`).join(", ");
|
|
3927
|
+
}
|
|
3928
|
+
function priceDetail(price) {
|
|
3929
|
+
const rows = [];
|
|
3930
|
+
const lines = [];
|
|
3931
|
+
if (price.type === "PER_UNIT_MATRIX" && price.variants?.length) {
|
|
3932
|
+
const keys = price.selectors?.map((s) => s.key) ?? Object.keys(price.variants[0]?.when ?? {});
|
|
3933
|
+
const labels = keys.map((key) => price.selectors?.find((s) => s.key === key)?.label ?? key);
|
|
3934
|
+
rows.push([...labels, "PRICE"]);
|
|
3935
|
+
for (const variant of price.variants) {
|
|
3936
|
+
rows.push([
|
|
3937
|
+
...keys.map((key) => String(variant.when[key] ?? "")),
|
|
3938
|
+
describeLeaf(variant.price)
|
|
3939
|
+
]);
|
|
3940
|
+
}
|
|
3941
|
+
}
|
|
3942
|
+
for (const tier of price.tiers ?? []) {
|
|
3943
|
+
lines.push(`+ ${tier.label}: ${describeLeaf(tier.price)} (when ${whenText(tier.when)})`);
|
|
3944
|
+
}
|
|
3945
|
+
for (const note of price.notes ?? [])
|
|
3946
|
+
lines.push(note);
|
|
3947
|
+
return { summary: summarizePrice(price), rows, lines };
|
|
3948
|
+
}
|
|
3949
|
+
|
|
3950
|
+
// src/output/schema.ts
|
|
3951
|
+
var MAX_DEPTH = 2;
|
|
3952
|
+
var MAX_ENUM_VALUES = 6;
|
|
3953
|
+
function isSchema(value) {
|
|
3954
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3955
|
+
}
|
|
3956
|
+
function typeOf(schema) {
|
|
3957
|
+
if (Array.isArray(schema.enum)) {
|
|
3958
|
+
const values = schema.enum.map((v) => JSON.stringify(v));
|
|
3959
|
+
return values.length > MAX_ENUM_VALUES ? `${values.slice(0, MAX_ENUM_VALUES).join(" | ")} | …` : values.join(" | ");
|
|
3960
|
+
}
|
|
3961
|
+
const type = schema.type;
|
|
3962
|
+
if (Array.isArray(type))
|
|
3963
|
+
return type.join(" | ");
|
|
3964
|
+
if (type === "array")
|
|
3965
|
+
return isSchema(schema.items) ? `${typeOf(schema.items)}[]` : "array";
|
|
3966
|
+
if (typeof type === "string")
|
|
3967
|
+
return type;
|
|
3968
|
+
const variants = schema.oneOf ?? schema.anyOf;
|
|
3969
|
+
if (Array.isArray(variants))
|
|
3970
|
+
return variants.filter(isSchema).map(typeOf).join(" | ") || "any";
|
|
3971
|
+
return "any";
|
|
3972
|
+
}
|
|
3973
|
+
function describe(schema) {
|
|
3974
|
+
const parts = [];
|
|
3975
|
+
if (typeof schema.description === "string")
|
|
3976
|
+
parts.push(schema.description.trim());
|
|
3977
|
+
if (schema.default !== undefined)
|
|
3978
|
+
parts.push(`(default: ${JSON.stringify(schema.default)})`);
|
|
3979
|
+
return parts.join(" ");
|
|
3980
|
+
}
|
|
3981
|
+
function schemaFields(schema, prefix = "", depth = 0) {
|
|
3982
|
+
if (!isSchema(schema) || !isSchema(schema.properties))
|
|
3983
|
+
return [];
|
|
3984
|
+
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
3985
|
+
const fields = [];
|
|
3986
|
+
for (const [name, property] of Object.entries(schema.properties)) {
|
|
3987
|
+
if (!isSchema(property))
|
|
3988
|
+
continue;
|
|
3989
|
+
const path = `${prefix}${name}`;
|
|
3990
|
+
fields.push({
|
|
3991
|
+
name: path,
|
|
3992
|
+
type: typeOf(property),
|
|
3993
|
+
required: required.has(name),
|
|
3994
|
+
description: describe(property)
|
|
3995
|
+
});
|
|
3996
|
+
if (property.type === "object" && depth + 1 < MAX_DEPTH) {
|
|
3997
|
+
fields.push(...schemaFields(property, `${path}.`, depth + 1));
|
|
3998
|
+
}
|
|
3999
|
+
}
|
|
4000
|
+
return fields;
|
|
4001
|
+
}
|
|
4002
|
+
|
|
4003
|
+
// src/commands/tools.ts
|
|
4004
|
+
function registerToolCommands(program, ctx) {
|
|
4005
|
+
program.command("discover").description("Search Solgrid's tools with a plain-language description of what you need.").requiredOption("-q, --query <text>", 'what you want to do, e.g. "twitter user profile"').option("-l, --limit <n>", "maximum number of results (up to 50)", parsePositiveInt).option("--include-unavailable", "also list tools that are currently down").option(...JSON_FLAG).action((opts) => runAction(ctx, opts.json, async (out) => {
|
|
4006
|
+
const client = apiClient(ctx);
|
|
4007
|
+
out.progress.start(`Searching for "${opts.query}"…`);
|
|
4008
|
+
const data = await discover(client, {
|
|
4009
|
+
query: opts.query,
|
|
4010
|
+
...opts.limit !== undefined ? { limit: opts.limit } : {},
|
|
4011
|
+
includeUnavailable: opts.includeUnavailable === true
|
|
4012
|
+
});
|
|
4013
|
+
out.result(data, () => {
|
|
4014
|
+
if (data.results.length === 0) {
|
|
4015
|
+
return [
|
|
4016
|
+
`No tools found for "${data.query}". Try different or broader words.`,
|
|
4017
|
+
...renderHints(data.hints, out.style)
|
|
4018
|
+
].join(`
|
|
4019
|
+
`);
|
|
4020
|
+
}
|
|
4021
|
+
const table = renderTable(["PROVIDER", "ENDPOINT", "PRICE", "HEALTH", "DESCRIPTION"], data.results.map((r) => [
|
|
4022
|
+
r.provider,
|
|
4023
|
+
r.endpoint,
|
|
4024
|
+
summarizePrice(r.price),
|
|
4025
|
+
formatHealth(r.metrics, out.style),
|
|
4026
|
+
truncate(r.description, 60)
|
|
4027
|
+
]), { formatHeader: (text) => out.style("dim", text) });
|
|
4028
|
+
const first = data.results[0];
|
|
4029
|
+
return [
|
|
4030
|
+
`${data.count} tool${data.count === 1 ? "" : "s"} found for "${data.query}":`,
|
|
4031
|
+
"",
|
|
4032
|
+
table,
|
|
4033
|
+
"",
|
|
4034
|
+
out.style("dim", `Details and input schema: solgrid inspect -p ${first?.provider} -e ${first?.endpoint}`),
|
|
4035
|
+
...renderHints(data.hints, out.style)
|
|
4036
|
+
].join(`
|
|
4037
|
+
`);
|
|
4038
|
+
});
|
|
4039
|
+
}));
|
|
4040
|
+
program.command("inspect").description("Show a tool's description, price, health and input schema.").requiredOption("-p, --provider <provider>", "provider, as listed by discover").requiredOption("-e, --endpoint <endpoint>", "endpoint, as listed by discover").option(...JSON_FLAG).action((opts) => runAction(ctx, opts.json, async (out) => {
|
|
4041
|
+
const client = apiClient(ctx);
|
|
4042
|
+
out.progress.start(`Inspecting ${opts.provider} ${opts.endpoint}…`);
|
|
4043
|
+
const data = await inspect(client, { provider: opts.provider, endpoint: opts.endpoint });
|
|
4044
|
+
out.result(data, () => renderInspect(data, out.style));
|
|
4045
|
+
}));
|
|
4046
|
+
}
|
|
4047
|
+
function renderInspect(tool, style) {
|
|
4048
|
+
const price = priceDetail(tool.price);
|
|
4049
|
+
const runtime = tool.metrics?.runTimeMs;
|
|
4050
|
+
const lines = [
|
|
4051
|
+
style("bold", `${tool.providerName || tool.provider} · ${tool.endpoint}`),
|
|
4052
|
+
tool.summary ?? tool.description,
|
|
4053
|
+
"",
|
|
4054
|
+
...renderFields([
|
|
4055
|
+
["Provider", tool.provider],
|
|
4056
|
+
["Endpoint", tool.endpoint],
|
|
4057
|
+
["Price", price.summary],
|
|
4058
|
+
["Health", formatHealth(tool.metrics, style) || undefined],
|
|
4059
|
+
[
|
|
4060
|
+
"Run time",
|
|
4061
|
+
typeof runtime?.p50 === "number" ? `typically ${formatDuration(runtime.p50)}${typeof runtime.p95 === "number" ? `, up to ${formatDuration(runtime.p95)}` : ""}` : undefined
|
|
4062
|
+
],
|
|
4063
|
+
["Tags", tool.tags?.length ? tool.tags.join(", ") : undefined],
|
|
4064
|
+
["Docs", tool.docUrl]
|
|
4065
|
+
], style)
|
|
4066
|
+
];
|
|
4067
|
+
if (tool.summary && tool.description && tool.summary !== tool.description) {
|
|
4068
|
+
lines.push("", tool.description);
|
|
4069
|
+
}
|
|
4070
|
+
if (price.rows.length > 0 || price.lines.length > 0) {
|
|
4071
|
+
lines.push("", style("bold", "Pricing"));
|
|
4072
|
+
if (price.rows.length > 0) {
|
|
4073
|
+
const [header = [], ...rows] = price.rows;
|
|
4074
|
+
lines.push(renderTable(header, rows, { formatHeader: (text) => style("dim", text) }));
|
|
4075
|
+
}
|
|
4076
|
+
lines.push(...price.lines);
|
|
4077
|
+
}
|
|
4078
|
+
lines.push("", ...renderInput(tool.input, tool.inputSchema, style));
|
|
4079
|
+
if (tool.notes?.length) {
|
|
4080
|
+
lines.push("", style("bold", "Notes"), ...tool.notes.map((note) => `- ${note}`));
|
|
4081
|
+
}
|
|
4082
|
+
lines.push("", style("dim", `Run it: solgrid run -p ${tool.provider} -e ${tool.endpoint} -i '{...}'`), ...renderHints(tool.hints, style));
|
|
4083
|
+
return lines.join(`
|
|
4084
|
+
`);
|
|
4085
|
+
}
|
|
4086
|
+
var INPUT_LOCATIONS = [
|
|
4087
|
+
["body", "Body", "-i / -f"],
|
|
4088
|
+
["queryParams", "Query parameters", "--query"],
|
|
4089
|
+
["pathParams", "Path parameters", "--path"]
|
|
4090
|
+
];
|
|
4091
|
+
function renderInput(input, legacySchema, style) {
|
|
4092
|
+
const sections = input ? INPUT_LOCATIONS.filter(([key]) => input[key] !== undefined && input[key] !== null).map(([key, title, flag]) => [title, flag, input[key]]) : legacySchema ? [["Input", "-i / -f", legacySchema]] : [];
|
|
4093
|
+
if (sections.length === 0)
|
|
4094
|
+
return [style("dim", "This tool takes no input.")];
|
|
4095
|
+
const lines = [];
|
|
4096
|
+
for (const [title, flag, schema] of sections) {
|
|
4097
|
+
const fields = schemaFields(schema);
|
|
4098
|
+
if (lines.length > 0)
|
|
4099
|
+
lines.push("");
|
|
4100
|
+
lines.push(`${style("bold", title)} ${style("dim", `(pass with ${flag})`)}`);
|
|
4101
|
+
lines.push(fields.length === 0 ? style("dim", " (schema has no listed fields)") : renderTable(["FIELD", "TYPE", "REQUIRED", "DESCRIPTION"], fields.map((f) => [
|
|
4102
|
+
f.name,
|
|
4103
|
+
f.type,
|
|
4104
|
+
f.required ? "yes" : "",
|
|
4105
|
+
truncate(f.description, 80)
|
|
4106
|
+
]), { formatHeader: (text) => style("dim", text) }));
|
|
4107
|
+
}
|
|
4108
|
+
return lines;
|
|
4109
|
+
}
|
|
4110
|
+
|
|
4111
|
+
// src/program.ts
|
|
4112
|
+
function createProgram(ctx, options = {}) {
|
|
4113
|
+
const program = new Command().name("solgrid").description("Discover, inspect and run Solgrid tools from your terminal or agent.").version(buildInfo.version, "-v, --version", "Print the CLI version.").helpOption("-h, --help", "Show help.").configureOutput({
|
|
4114
|
+
writeOut: (text) => ctx.stdout.write(text),
|
|
4115
|
+
writeErr: (text) => options.jsonErrors ? undefined : ctx.stderr.write(text),
|
|
4116
|
+
outputError: (text, write) => options.jsonErrors ? undefined : write(text)
|
|
4117
|
+
}).showHelpAfterError("(run with --help for usage)").exitOverride();
|
|
4118
|
+
registerToolCommands(program, ctx);
|
|
4119
|
+
registerRunCommand(program, ctx);
|
|
4120
|
+
registerRunsCommand(program, ctx);
|
|
4121
|
+
registerFilesCommand(program, ctx);
|
|
4122
|
+
registerAccountCommands(program, ctx);
|
|
4123
|
+
registerKeysCommand(program, ctx);
|
|
4124
|
+
registerSessionIdCommand(program, ctx);
|
|
4125
|
+
for (const command of [program, ...program.commands])
|
|
4126
|
+
command.helpCommand(false);
|
|
4127
|
+
return program;
|
|
4128
|
+
}
|
|
4129
|
+
async function runCli(ctx, args) {
|
|
4130
|
+
const options = {};
|
|
4131
|
+
const program = createProgram(ctx, options);
|
|
4132
|
+
const json = wantsJson(program, args);
|
|
4133
|
+
options.jsonErrors = json;
|
|
4134
|
+
try {
|
|
4135
|
+
await program.parseAsync(withImplicitHelp(program, args), { from: "user" });
|
|
4136
|
+
} catch (error) {
|
|
4137
|
+
if (!(error instanceof CommanderError))
|
|
4138
|
+
throw error;
|
|
4139
|
+
if (error.exitCode !== 0 && json) {
|
|
4140
|
+
const [first = "", ...extra] = error.message.replace(/^error:\s*/i, "").split(`
|
|
4141
|
+
`);
|
|
4142
|
+
const suggestion = extra.map((line) => line.replace(/^\((.*)\)$/, "$1").trim()).filter(Boolean);
|
|
4143
|
+
createOutput(ctx, true).error(new CliError("USAGE", first.trim(), {
|
|
4144
|
+
hint: [...suggestion, "Run the command with --help for usage."].join(" ")
|
|
4145
|
+
}));
|
|
4146
|
+
}
|
|
4147
|
+
ctx.exitCode = error.exitCode;
|
|
4148
|
+
}
|
|
4149
|
+
return ctx.exitCode;
|
|
4150
|
+
}
|
|
4151
|
+
function wantsJson(program, args) {
|
|
4152
|
+
let command = program;
|
|
4153
|
+
let index = 0;
|
|
4154
|
+
for (;index < args.length; index++) {
|
|
4155
|
+
const arg = args[index] ?? "";
|
|
4156
|
+
const next = command.commands.find((sub) => sub.name() === arg || sub.aliases().includes(arg));
|
|
4157
|
+
if (!next)
|
|
4158
|
+
break;
|
|
4159
|
+
command = next;
|
|
4160
|
+
}
|
|
4161
|
+
const rest = args.slice(index);
|
|
4162
|
+
if (command.commands.length > 0) {
|
|
4163
|
+
const end = rest.indexOf("--");
|
|
4164
|
+
return (end === -1 ? rest : rest.slice(0, end)).some((arg) => arg === "--json" || /^-[A-Za-z]*j[A-Za-z]*$/.test(arg));
|
|
4165
|
+
}
|
|
4166
|
+
const find = (flag) => command.options.find((o) => o.short === flag || o.long === flag);
|
|
4167
|
+
const isJson = (option) => option?.attributeName() === "json";
|
|
4168
|
+
const looksLikeOption = (arg) => arg !== undefined && arg.length > 1 && arg.startsWith("-");
|
|
4169
|
+
let group;
|
|
4170
|
+
let i = 0;
|
|
4171
|
+
while (i < rest.length || group !== undefined) {
|
|
4172
|
+
const arg = group ?? rest[i++] ?? "";
|
|
4173
|
+
group = undefined;
|
|
4174
|
+
if (arg === "--")
|
|
4175
|
+
return false;
|
|
4176
|
+
const exact = looksLikeOption(arg) ? find(arg) : undefined;
|
|
4177
|
+
if (exact) {
|
|
4178
|
+
if (isJson(exact))
|
|
4179
|
+
return true;
|
|
4180
|
+
if (exact.required)
|
|
4181
|
+
i++;
|
|
4182
|
+
else if (exact.optional && !looksLikeOption(rest[i]))
|
|
4183
|
+
i++;
|
|
4184
|
+
continue;
|
|
4185
|
+
}
|
|
4186
|
+
if (arg.length > 2 && arg[0] === "-" && arg[1] !== "-") {
|
|
4187
|
+
const first = find(`-${arg[1]}`);
|
|
4188
|
+
if (first) {
|
|
4189
|
+
if (isJson(first))
|
|
4190
|
+
return true;
|
|
4191
|
+
if (!first.required && !first.optional)
|
|
4192
|
+
group = `-${arg.slice(2)}`;
|
|
4193
|
+
}
|
|
4194
|
+
}
|
|
4195
|
+
}
|
|
4196
|
+
return false;
|
|
4197
|
+
}
|
|
4198
|
+
function withImplicitHelp(program, args) {
|
|
4199
|
+
let command = program;
|
|
4200
|
+
for (const arg of args) {
|
|
4201
|
+
const next = command.commands.find((sub) => sub.name() === arg || sub.aliases().includes(arg));
|
|
4202
|
+
if (!next)
|
|
4203
|
+
return args;
|
|
4204
|
+
command = next;
|
|
4205
|
+
}
|
|
4206
|
+
return command.commands.length > 0 ? [...args, "--help"] : args;
|
|
4207
|
+
}
|
|
4208
|
+
|
|
4209
|
+
// src/index.ts
|
|
4210
|
+
var ctx = createProcessContext();
|
|
4211
|
+
try {
|
|
4212
|
+
process.exitCode = await runCli(ctx, process.argv.slice(2));
|
|
4213
|
+
} catch (error) {
|
|
4214
|
+
const message = error instanceof Error ? error.stack ?? error.message : String(error);
|
|
4215
|
+
ctx.stderr.write(`solgrid: unexpected error
|
|
4216
|
+
${message}
|
|
4217
|
+
`);
|
|
4218
|
+
process.exitCode = 1;
|
|
4219
|
+
}
|