@asteroidai/cli 1.9.27 → 1.9.28
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/dist/asteroid.js +1 -1
- package/package.json +3 -8
- package/src/account.ts +0 -501
- package/src/args.ts +0 -39
- package/src/auth/credentials.ts +0 -179
- package/src/auth/store.ts +0 -253
- package/src/auth/workos.ts +0 -171
- package/src/commands.ts +0 -13
- package/src/config.ts +0 -125
- package/src/core.ts +0 -41
- package/src/generated/commands.ts +0 -4772
- package/src/http.ts +0 -115
- package/src/main.ts +0 -49
- package/src/output.test.ts +0 -71
- package/src/output.ts +0 -176
- package/src/program.test.ts +0 -155
- package/src/program.ts +0 -293
- package/src/reference.ts +0 -195
- package/src/request.test.ts +0 -164
- package/src/request.ts +0 -252
- package/src/run.test.ts +0 -651
- package/src/run.ts +0 -235
- package/src/sync/activities.ts +0 -80
- package/src/sync/commands.ts +0 -63
- package/src/sync/execution-pull.ts +0 -273
- package/src/sync/node-files.ts +0 -47
- package/src/sync/paths.ts +0 -45
- package/src/sync/run.ts +0 -136
- package/src/sync/sync.test.ts +0 -502
- package/src/sync/types.ts +0 -58
- package/src/sync/workflow-pull.ts +0 -185
- package/src/sync/workflow-push.ts +0 -161
- package/src/types.ts +0 -46
- package/src/ui.ts +0 -35
package/src/program.ts
DELETED
|
@@ -1,293 +0,0 @@
|
|
|
1
|
-
import { Command, CommanderError, Option as Flag } from "commander";
|
|
2
|
-
|
|
3
|
-
import { type Parsed, UsageError } from "./args";
|
|
4
|
-
import { type BuiltinCommand, GLOBAL_FLAGS } from "./reference";
|
|
5
|
-
import type { CliCommand, Option } from "./types";
|
|
6
|
-
|
|
7
|
-
export type Invocation =
|
|
8
|
-
| { kind: "command"; command: CliCommand; parsed: Parsed }
|
|
9
|
-
| { kind: "builtin"; name: string; parsed: Parsed }
|
|
10
|
-
| { kind: "exit"; code: number; stdout: string; stderr: string };
|
|
11
|
-
|
|
12
|
-
export interface ProgramOptions {
|
|
13
|
-
commands: readonly CliCommand[];
|
|
14
|
-
builtins?: readonly BuiltinCommand[];
|
|
15
|
-
version?: string;
|
|
16
|
-
description?: string;
|
|
17
|
-
defaultArg?: (command: CliCommand, arg: Option) => string | undefined;
|
|
18
|
-
defaultArgNote?: string;
|
|
19
|
-
helpNote?: (command: CliCommand) => string | undefined;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const DOTTED = /^--([a-z0-9-]+\.[a-z0-9.-]+)(?:=(.*))?$/;
|
|
23
|
-
|
|
24
|
-
function collect(value: string, previous: string[] | undefined): string[] {
|
|
25
|
-
return [...(previous ?? []), value];
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function optionText(option: Option): string {
|
|
29
|
-
const text = option.description?.replace(/\s+/g, " ").trim() ?? "";
|
|
30
|
-
const parts = [text && !/[.!?]$/.test(text) ? `${text}.` : text];
|
|
31
|
-
if (option.enum) parts.push(`One of: ${option.enum.join(", ")}.`);
|
|
32
|
-
if (option.type.endsWith("[]")) parts.push("Repeatable.");
|
|
33
|
-
if (option.required) parts.push("Required.");
|
|
34
|
-
return parts.filter(Boolean).join(" ");
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function splitDotted(argv: string[]): {
|
|
38
|
-
argv: string[];
|
|
39
|
-
dotted: Map<string, string[]>;
|
|
40
|
-
} {
|
|
41
|
-
const rest: string[] = [];
|
|
42
|
-
const dotted = new Map<string, string[]>();
|
|
43
|
-
for (let i = 0; i < argv.length; i++) {
|
|
44
|
-
const match = DOTTED.exec(argv[i]);
|
|
45
|
-
if (!match) {
|
|
46
|
-
rest.push(argv[i]);
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
const value = match[2] ?? argv[++i];
|
|
50
|
-
if (value === undefined)
|
|
51
|
-
throw new UsageError(`--${match[1]} needs a value`);
|
|
52
|
-
dotted.set(match[1], [...(dotted.get(match[1]) ?? []), value]);
|
|
53
|
-
}
|
|
54
|
-
return { argv: rest, dotted };
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function group(
|
|
58
|
-
parent: Command,
|
|
59
|
-
name: string,
|
|
60
|
-
groups: Map<string, Command>,
|
|
61
|
-
path: string,
|
|
62
|
-
) {
|
|
63
|
-
const existing = groups.get(path);
|
|
64
|
-
if (existing) return existing;
|
|
65
|
-
const created = parent
|
|
66
|
-
.command(name)
|
|
67
|
-
.description(`${name} commands`)
|
|
68
|
-
.helpCommand(false);
|
|
69
|
-
groups.set(path, created);
|
|
70
|
-
return created;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function leafOptions(leaf: Command, command: CliCommand) {
|
|
74
|
-
for (const option of [...command.query, ...(command.body?.fields ?? [])]) {
|
|
75
|
-
if (option.type === "boolean") {
|
|
76
|
-
leaf.option(`--${option.flag}`, optionText(option));
|
|
77
|
-
leaf.option(`--no-${option.flag}`, `Set ${option.flag} to false.`);
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
const placeholder = option.enum
|
|
81
|
-
? option.enum.join("|")
|
|
82
|
-
: option.type.replace("[]", "");
|
|
83
|
-
leaf.option(
|
|
84
|
-
`--${option.flag} <${placeholder}>`,
|
|
85
|
-
optionText(option),
|
|
86
|
-
collect,
|
|
87
|
-
);
|
|
88
|
-
if (option.negatable)
|
|
89
|
-
leaf.option(`--no-${option.flag}`, `Skip ${option.flag}.`);
|
|
90
|
-
}
|
|
91
|
-
for (const file of command.body?.files ?? []) {
|
|
92
|
-
leaf.option(`--${file} <path>`, "File to upload. Repeatable.", collect);
|
|
93
|
-
}
|
|
94
|
-
if (command.files === "pull")
|
|
95
|
-
leaf.option(
|
|
96
|
-
"--out <dir>",
|
|
97
|
-
"Write here instead of ./<workflow-name>/head (a version: ./<workflow-name>/.versions/...). --json prints the file listing instead.",
|
|
98
|
-
);
|
|
99
|
-
if (command.files === "push")
|
|
100
|
-
leaf.option(
|
|
101
|
-
"--from <dir>",
|
|
102
|
-
"The pulled head folder to push. Defaults to the current folder.",
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function toParsed(
|
|
107
|
-
leaf: Command,
|
|
108
|
-
positionals: string[],
|
|
109
|
-
dotted: Map<string, string[]>,
|
|
110
|
-
): Parsed {
|
|
111
|
-
const globals = leaf.optsWithGlobals<Record<string, unknown>>();
|
|
112
|
-
const own = leaf.opts<Record<string, unknown>>();
|
|
113
|
-
const flags = new Map(dotted);
|
|
114
|
-
for (const option of leaf.options) {
|
|
115
|
-
const flag = option.long?.replace(/^--(no-)?/, "");
|
|
116
|
-
const value = own[option.attributeName()];
|
|
117
|
-
if (!flag || value === undefined || flags.has(flag)) continue;
|
|
118
|
-
flags.set(flag, Array.isArray(value) ? value.map(String) : [String(value)]);
|
|
119
|
-
}
|
|
120
|
-
return {
|
|
121
|
-
positionals,
|
|
122
|
-
flags,
|
|
123
|
-
json: globals.json === true,
|
|
124
|
-
org: globals.org as string | undefined,
|
|
125
|
-
body: globals.body as string | undefined,
|
|
126
|
-
output: globals.output as string | undefined,
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
export function defineCommands(
|
|
131
|
-
root: Command,
|
|
132
|
-
options: ProgramOptions,
|
|
133
|
-
dotted: Map<string, string[]> = new Map(),
|
|
134
|
-
onInvocation: (invocation: Invocation) => void = () => undefined,
|
|
135
|
-
): Command {
|
|
136
|
-
if (options.description) root.description(options.description);
|
|
137
|
-
if (options.version)
|
|
138
|
-
root.addHelpText(
|
|
139
|
-
"after",
|
|
140
|
-
"\nRun `asteroid --version` to print the CLI version.",
|
|
141
|
-
);
|
|
142
|
-
for (const [flags, text] of GLOBAL_FLAGS) {
|
|
143
|
-
if (!flags.startsWith("-h")) root.option(flags, text);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
for (const builtin of options.builtins ?? []) {
|
|
147
|
-
const leaf = root.command(builtin.command).description(builtin.description);
|
|
148
|
-
if (builtin.arg) leaf.argument(`[${builtin.arg}]`);
|
|
149
|
-
for (const [flags, text] of builtin.hiddenOptions ?? [])
|
|
150
|
-
leaf.addOption(new Flag(flags, text).hideHelp());
|
|
151
|
-
leaf.action((...args: unknown[]) => {
|
|
152
|
-
const cmd = args.at(-1) as Command;
|
|
153
|
-
onInvocation({
|
|
154
|
-
kind: "builtin",
|
|
155
|
-
name: builtin.command,
|
|
156
|
-
parsed: toParsed(cmd, cmd.args, new Map()),
|
|
157
|
-
});
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const reserved = new Set((options.builtins ?? []).map((b) => b.command));
|
|
162
|
-
const groups = new Map<string, Command>();
|
|
163
|
-
for (const command of options.commands) {
|
|
164
|
-
if (reserved.has(command.command)) continue;
|
|
165
|
-
const words = command.command.split(" ");
|
|
166
|
-
let parent = root;
|
|
167
|
-
for (let i = 0; i < words.length - 1; i++) {
|
|
168
|
-
parent = group(parent, words[i], groups, words.slice(0, i + 1).join(" "));
|
|
169
|
-
}
|
|
170
|
-
const leaf = parent
|
|
171
|
-
.command(words.at(-1)!)
|
|
172
|
-
.description(command.description)
|
|
173
|
-
.summary(command.title);
|
|
174
|
-
const defaults = command.args.map((arg) =>
|
|
175
|
-
options.defaultArg?.(command, arg),
|
|
176
|
-
);
|
|
177
|
-
const defaulted = defaults[0] !== undefined;
|
|
178
|
-
command.args.forEach((arg) => {
|
|
179
|
-
leaf.argument(
|
|
180
|
-
defaulted ? `[${arg.flag}]` : `<${arg.flag}>`,
|
|
181
|
-
arg.description ?? "",
|
|
182
|
-
);
|
|
183
|
-
});
|
|
184
|
-
if (defaulted) {
|
|
185
|
-
const names = command.args.map((arg, i) =>
|
|
186
|
-
i === 0 ? `[${arg.flag}]` : `<${arg.flag}>`,
|
|
187
|
-
);
|
|
188
|
-
leaf.usage(`[options] ${names.join(" ")}`);
|
|
189
|
-
}
|
|
190
|
-
const notes = [
|
|
191
|
-
defaults.some((value) => value !== undefined)
|
|
192
|
-
? options.defaultArgNote
|
|
193
|
-
: undefined,
|
|
194
|
-
options.helpNote?.(command),
|
|
195
|
-
].filter(Boolean);
|
|
196
|
-
if (notes.length > 0) leaf.addHelpText("after", `\n${notes.join("\n")}`);
|
|
197
|
-
leafOptions(leaf, command);
|
|
198
|
-
leaf.action((...args: unknown[]) => {
|
|
199
|
-
const cmd = args.at(-1) as Command;
|
|
200
|
-
let positionals = cmd.args;
|
|
201
|
-
if (
|
|
202
|
-
positionals.length === command.args.length - 1 &&
|
|
203
|
-
defaults[0] !== undefined
|
|
204
|
-
) {
|
|
205
|
-
positionals = [defaults[0], ...positionals];
|
|
206
|
-
}
|
|
207
|
-
if (positionals.length !== command.args.length) {
|
|
208
|
-
const expected = command.args
|
|
209
|
-
.map((a, i) => (i === 0 && defaulted ? `[${a.flag}]` : `<${a.flag}>`))
|
|
210
|
-
.join(" ");
|
|
211
|
-
throw new UsageError(
|
|
212
|
-
`expected ${expected}, got ${cmd.args.length} argument(s)`,
|
|
213
|
-
);
|
|
214
|
-
}
|
|
215
|
-
const unknownDotted = [...dotted.keys()].filter(
|
|
216
|
-
(flag) =>
|
|
217
|
-
!(command.body?.fields ?? []).some((f) =>
|
|
218
|
-
flag.startsWith(`${f.flag}.`),
|
|
219
|
-
),
|
|
220
|
-
);
|
|
221
|
-
if (unknownDotted.length > 0) {
|
|
222
|
-
throw new UsageError(
|
|
223
|
-
`unknown flag: ${unknownDotted.map((f) => `--${f}`).join(", ")}`,
|
|
224
|
-
);
|
|
225
|
-
}
|
|
226
|
-
onInvocation({
|
|
227
|
-
kind: "command",
|
|
228
|
-
command,
|
|
229
|
-
parsed: toParsed(cmd, positionals, dotted),
|
|
230
|
-
});
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
return root;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
export async function parseInvocation(
|
|
238
|
-
argv: string[],
|
|
239
|
-
options: ProgramOptions,
|
|
240
|
-
): Promise<Invocation> {
|
|
241
|
-
let stdout = "";
|
|
242
|
-
let stderr = "";
|
|
243
|
-
let result: Invocation | undefined;
|
|
244
|
-
|
|
245
|
-
if (options.version && (argv[0] === "-v" || argv[0] === "--version")) {
|
|
246
|
-
return {
|
|
247
|
-
kind: "exit",
|
|
248
|
-
code: 0,
|
|
249
|
-
stdout: `${options.version}\n`,
|
|
250
|
-
stderr: "",
|
|
251
|
-
};
|
|
252
|
-
}
|
|
253
|
-
const { argv: rest, dotted } = splitDotted(argv);
|
|
254
|
-
const root = new Command("asteroid")
|
|
255
|
-
.exitOverride()
|
|
256
|
-
.helpCommand(false)
|
|
257
|
-
.configureOutput({
|
|
258
|
-
writeOut: (text) => (stdout += text),
|
|
259
|
-
writeErr: (text) => (stderr += text),
|
|
260
|
-
});
|
|
261
|
-
defineCommands(root, options, dotted, (invocation) => {
|
|
262
|
-
result = invocation;
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
try {
|
|
266
|
-
await root.parseAsync(rest, { from: "user" });
|
|
267
|
-
} catch (error) {
|
|
268
|
-
if (!(error instanceof CommanderError)) throw error;
|
|
269
|
-
if (error.code === "commander.help") {
|
|
270
|
-
return { kind: "exit", code: 0, stdout: stdout + stderr, stderr: "" };
|
|
271
|
-
}
|
|
272
|
-
const shown = ["commander.helpDisplayed", "commander.version"];
|
|
273
|
-
return {
|
|
274
|
-
kind: "exit",
|
|
275
|
-
code: shown.includes(error.code) ? 0 : 2,
|
|
276
|
-
stdout,
|
|
277
|
-
stderr,
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
|
-
if (result) return result;
|
|
281
|
-
root.outputHelp();
|
|
282
|
-
return { kind: "exit", code: 0, stdout, stderr };
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
export async function runCompletion(argv: string[], options: ProgramOptions) {
|
|
286
|
-
const { default: tab } = await import("@bomb.sh/tab/commander");
|
|
287
|
-
const root = defineCommands(new Command("asteroid").helpCommand(false), {
|
|
288
|
-
...options,
|
|
289
|
-
builtins: options.builtins?.filter((b) => b.command !== "complete"),
|
|
290
|
-
});
|
|
291
|
-
tab(root);
|
|
292
|
-
await root.parseAsync(argv, { from: "user" });
|
|
293
|
-
}
|
package/src/reference.ts
DELETED
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
import type { CliCommand, Option } from "./types";
|
|
2
|
-
|
|
3
|
-
export interface BuiltinCommand {
|
|
4
|
-
command: string;
|
|
5
|
-
usage: string;
|
|
6
|
-
description: string;
|
|
7
|
-
args: number;
|
|
8
|
-
arg?: string;
|
|
9
|
-
/** Flags left out of `--help` and the reference: staff only. */
|
|
10
|
-
hiddenOptions?: [string, string][];
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export const BUILTINS: BuiltinCommand[] = [
|
|
14
|
-
{
|
|
15
|
-
command: "login",
|
|
16
|
-
usage: "asteroid login",
|
|
17
|
-
description:
|
|
18
|
-
"Sign in with your browser. Stores a refresh token in the OS keychain, or in a private file in the config directory when no keychain is available.",
|
|
19
|
-
args: 0,
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
command: "logout",
|
|
23
|
-
usage: "asteroid logout",
|
|
24
|
-
description: "Forget the stored sign-in and cached tokens.",
|
|
25
|
-
args: 0,
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
command: "orgs",
|
|
29
|
-
usage: "asteroid orgs",
|
|
30
|
-
description:
|
|
31
|
-
"List the organizations you can act in. The active one is marked.",
|
|
32
|
-
args: 0,
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
command: "use",
|
|
36
|
-
usage: "asteroid use [org]",
|
|
37
|
-
arg: "org",
|
|
38
|
-
description:
|
|
39
|
-
"Make an organization, by id or name, the default for later commands. Without one, pick from a list.",
|
|
40
|
-
args: 1,
|
|
41
|
-
hiddenOptions: [
|
|
42
|
-
[
|
|
43
|
-
"--reason <why>",
|
|
44
|
-
"Staff only. Why you need access to an organization you are not a member of, such as a ticket id. It is audited. Asked for in a terminal.",
|
|
45
|
-
],
|
|
46
|
-
[
|
|
47
|
-
"--exit",
|
|
48
|
-
"Staff only. Revoke every staff token now and go back to your own organization.",
|
|
49
|
-
],
|
|
50
|
-
],
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
command: "complete",
|
|
54
|
-
usage: "asteroid complete <zsh|bash|fish|powershell>",
|
|
55
|
-
arg: "shell",
|
|
56
|
-
description:
|
|
57
|
-
"Print a shell completion script. For zsh: `asteroid complete zsh > ~/.zfunc/_asteroid`.",
|
|
58
|
-
args: 1,
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
command: "whoami",
|
|
62
|
-
usage: "asteroid whoami",
|
|
63
|
-
description:
|
|
64
|
-
"Show who you act as, in which organization, and how long the token lasts.",
|
|
65
|
-
args: 0,
|
|
66
|
-
},
|
|
67
|
-
];
|
|
68
|
-
|
|
69
|
-
const BUILTIN_COMMANDS = new Set(BUILTINS.map((b) => b.command));
|
|
70
|
-
|
|
71
|
-
function generated(commands: CliCommand[]): CliCommand[] {
|
|
72
|
-
return commands.filter((c) => !BUILTIN_COMMANDS.has(c.command));
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export const GLOBAL_FLAGS: [string, string][] = [
|
|
76
|
-
["--json", "Print the response as JSON."],
|
|
77
|
-
[
|
|
78
|
-
"--org <id|name>",
|
|
79
|
-
"Act in this organization for one command. Also ASTEROID_ORG.",
|
|
80
|
-
],
|
|
81
|
-
[
|
|
82
|
-
"--body <json|@file|->",
|
|
83
|
-
"Request body as JSON, a file, or stdin. Flags override its fields.",
|
|
84
|
-
],
|
|
85
|
-
["--output <file>", "Write a downloaded file here, not to stdout."],
|
|
86
|
-
["-h, --help", "Show help."],
|
|
87
|
-
];
|
|
88
|
-
|
|
89
|
-
export function usage(command: CliCommand): string {
|
|
90
|
-
const args = command.args
|
|
91
|
-
.map((arg) =>
|
|
92
|
-
command.files === "push" ? `[${arg.flag}]` : `<${arg.flag}>`,
|
|
93
|
-
)
|
|
94
|
-
.join(" ");
|
|
95
|
-
return `asteroid ${command.command}${args ? ` ${args}` : ""} [flags]`;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function flagLine(option: Option, prefix = ""): string {
|
|
99
|
-
const value =
|
|
100
|
-
option.type === "boolean"
|
|
101
|
-
? ""
|
|
102
|
-
: option.enum
|
|
103
|
-
? ` <${option.enum.join("|")}>`
|
|
104
|
-
: ` <${option.type}>`;
|
|
105
|
-
const notes = [
|
|
106
|
-
option.required ? "required" : "",
|
|
107
|
-
option.type.endsWith("[]") ? "repeatable" : "",
|
|
108
|
-
].filter(Boolean);
|
|
109
|
-
const description = [
|
|
110
|
-
option.description?.replace(/\s+/g, " ").trim() ?? "",
|
|
111
|
-
notes.length ? `(${notes.join(", ")})` : "",
|
|
112
|
-
]
|
|
113
|
-
.filter(Boolean)
|
|
114
|
-
.join(" ");
|
|
115
|
-
const name =
|
|
116
|
-
option.type === "boolean"
|
|
117
|
-
? `[no-]${prefix}${option.flag}`
|
|
118
|
-
: `${prefix}${option.flag}`;
|
|
119
|
-
return `--${name}${value}${description ? ` ${description}` : ""}`;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export function flagLines(command: CliCommand): string[] {
|
|
123
|
-
const lines = command.query.map((option) => flagLine(option));
|
|
124
|
-
for (const field of command.body?.fields ?? []) lines.push(flagLine(field));
|
|
125
|
-
for (const file of command.body?.files ?? []) {
|
|
126
|
-
lines.push(`--${file} <path> File to upload (repeatable)`);
|
|
127
|
-
}
|
|
128
|
-
if (command.files === "pull")
|
|
129
|
-
lines.push(
|
|
130
|
-
"--out <dir> Write here instead of ./<workflow-name>/head (a version: ./<workflow-name>/.versions/...). --json prints the file listing instead",
|
|
131
|
-
);
|
|
132
|
-
if (command.files === "push")
|
|
133
|
-
lines.push(
|
|
134
|
-
"--from <dir> The pulled head folder to push. Defaults to the current folder",
|
|
135
|
-
);
|
|
136
|
-
return lines;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function mdx(text: string): string {
|
|
140
|
-
return text
|
|
141
|
-
.split("`")
|
|
142
|
-
.map((part, i) =>
|
|
143
|
-
i % 2 === 1
|
|
144
|
-
? part
|
|
145
|
-
: part
|
|
146
|
-
.replace(/</g, "<")
|
|
147
|
-
.replace(/\{/g, "{")
|
|
148
|
-
.replace(/\}/g, "}"),
|
|
149
|
-
)
|
|
150
|
-
.join("`");
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
export function renderReference(commands: CliCommand[]): string {
|
|
154
|
-
const lines = [
|
|
155
|
-
"---",
|
|
156
|
-
'title: "CLI reference"',
|
|
157
|
-
'description: "Every asteroid command, its arguments and its flags"',
|
|
158
|
-
"---",
|
|
159
|
-
"",
|
|
160
|
-
"{/* Generated by packages/asteroid-cli from the cli surface of the External API. Do not edit by hand. */}",
|
|
161
|
-
"",
|
|
162
|
-
"Sign in with `asteroid login`, or set `ASTEROID_API_KEY` for CI and agents.",
|
|
163
|
-
"Commands that return data print compact text. Add `--json` to get the response as JSON.",
|
|
164
|
-
"The text output may change between releases. Scripts should read `--json`.",
|
|
165
|
-
"At a terminal, tables show up to 6 columns and cut long cells to fit; when output is piped, they show every column in full. Use `--json` for every field.",
|
|
166
|
-
"Downloads write raw bytes. `login`, `logout` and `use` only report status on stderr.",
|
|
167
|
-
"Exit codes: 0 ok, 1 API error, 2 usage error.",
|
|
168
|
-
"",
|
|
169
|
-
"## Global flags",
|
|
170
|
-
"",
|
|
171
|
-
];
|
|
172
|
-
for (const [flag, text] of GLOBAL_FLAGS)
|
|
173
|
-
lines.push(`- \`${flag}\`: ${mdx(text)}`);
|
|
174
|
-
lines.push("", "## Account", "");
|
|
175
|
-
for (const builtin of BUILTINS) {
|
|
176
|
-
lines.push(`### \`${builtin.usage}\``, "", mdx(builtin.description), "");
|
|
177
|
-
}
|
|
178
|
-
lines.push("## Commands", "");
|
|
179
|
-
for (const command of generated(commands)) {
|
|
180
|
-
lines.push(`### \`${usage(command)}\``, "", mdx(command.description), "");
|
|
181
|
-
for (const arg of command.args) {
|
|
182
|
-
lines.push(
|
|
183
|
-
`- \`<${arg.flag}>\`${arg.description ? `: ${mdx(arg.description)}` : ""}`,
|
|
184
|
-
);
|
|
185
|
-
}
|
|
186
|
-
for (const flag of flagLines(command)) {
|
|
187
|
-
const [name, ...rest] = flag.split(" ");
|
|
188
|
-
lines.push(
|
|
189
|
-
`- \`${name}\`${rest.length ? `: ${mdx(rest.join(" "))}` : ""}`,
|
|
190
|
-
);
|
|
191
|
-
}
|
|
192
|
-
lines.push("");
|
|
193
|
-
}
|
|
194
|
-
return `${lines.join("\n").trimEnd()}\n`;
|
|
195
|
-
}
|
package/src/request.test.ts
DELETED
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
|
|
3
|
-
import { matchCommand, UsageError } from "./args";
|
|
4
|
-
import { COMMANDS } from "./generated/commands";
|
|
5
|
-
import { parseInvocation } from "./program";
|
|
6
|
-
import { buildRequest, type InputReader } from "./request";
|
|
7
|
-
|
|
8
|
-
async function build(argv: string[], io?: InputReader) {
|
|
9
|
-
const invocation = await parseInvocation(argv, { commands: COMMANDS });
|
|
10
|
-
if (invocation.kind !== "command") {
|
|
11
|
-
throw new UsageError(
|
|
12
|
-
invocation.kind === "exit" ? invocation.stderr : "no command",
|
|
13
|
-
);
|
|
14
|
-
}
|
|
15
|
-
return buildRequest(invocation.command, invocation.parsed, { io });
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
describe("matchCommand", () => {
|
|
19
|
-
it("takes the longest command and leaves ids as arguments", () => {
|
|
20
|
-
const { command } = matchCommand(
|
|
21
|
-
["workflow", "version", "get", "w1", "v1"],
|
|
22
|
-
COMMANDS,
|
|
23
|
-
);
|
|
24
|
-
expect(command?.tool).toBe("workflow_version_get");
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it("returns a group prefix when no command matches", () => {
|
|
28
|
-
const { command, prefix } = matchCommand(["workflow", "version"], COMMANDS);
|
|
29
|
-
expect(command).toBeUndefined();
|
|
30
|
-
expect(prefix).toEqual(["workflow", "version"]);
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
describe("buildRequest", async () => {
|
|
35
|
-
it("fills path arguments in order", async () => {
|
|
36
|
-
const request = await build(["workflow", "version", "get", "w-1", "v-2"]);
|
|
37
|
-
expect(request.method).toBe("GET");
|
|
38
|
-
expect(request.path).toBe("/agents/v2/workflows/w-1/versions/v-2");
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it("repeats array query flags and checks enums", async () => {
|
|
42
|
-
const request = await build([
|
|
43
|
-
"execution",
|
|
44
|
-
"list",
|
|
45
|
-
"--status",
|
|
46
|
-
"failed",
|
|
47
|
-
"--status=completed",
|
|
48
|
-
"--page-size",
|
|
49
|
-
"5",
|
|
50
|
-
]);
|
|
51
|
-
expect(request.query.getAll("status")).toEqual(["failed", "completed"]);
|
|
52
|
-
expect(request.query.get("pageSize")).toBe("5");
|
|
53
|
-
await expect(
|
|
54
|
-
build(["execution", "list", "--status", "nope"]),
|
|
55
|
-
).rejects.toThrow(UsageError);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it("rejects a non-integer for an integer flag", async () => {
|
|
59
|
-
await expect(
|
|
60
|
-
build(["execution", "list", "--page-size", "five"]),
|
|
61
|
-
).rejects.toThrow(/integer/);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it("nests dotted flags into the body", async () => {
|
|
65
|
-
const request = await build([
|
|
66
|
-
"workflow",
|
|
67
|
-
"execute",
|
|
68
|
-
"w-1",
|
|
69
|
-
"--execution-options.variant-key",
|
|
70
|
-
"canary",
|
|
71
|
-
"--inputs",
|
|
72
|
-
'{"name":"Ada"}',
|
|
73
|
-
]);
|
|
74
|
-
expect(request.json).toMatchObject({
|
|
75
|
-
executionOptions: { variantKey: "canary" },
|
|
76
|
-
inputs: { name: "Ada" },
|
|
77
|
-
});
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it("lets flags override --body fields", async () => {
|
|
81
|
-
const request = await build([
|
|
82
|
-
"schema",
|
|
83
|
-
"validate",
|
|
84
|
-
"--body",
|
|
85
|
-
'{"schema":{"type":"string"}}',
|
|
86
|
-
"--schema",
|
|
87
|
-
'{"type":"object"}',
|
|
88
|
-
]);
|
|
89
|
-
expect(request.json).toEqual({ schema: { type: "object" } });
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it("fails on unknown flags and missing arguments", async () => {
|
|
93
|
-
await expect(build(["workflow", "list", "--nope", "1"])).rejects.toThrow(
|
|
94
|
-
/unknown option '--nope'/,
|
|
95
|
-
);
|
|
96
|
-
await expect(build(["workflow", "get"])).rejects.toThrow(
|
|
97
|
-
/missing required argument 'workflow-id'/,
|
|
98
|
-
);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it("requires required body fields", async () => {
|
|
102
|
-
await expect(build(["schema", "validate"])).rejects.toThrow(
|
|
103
|
-
/--schema is required/,
|
|
104
|
-
);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it("does not take a following flag as a value", async () => {
|
|
108
|
-
await expect(
|
|
109
|
-
build(["workflow", "rename", "w1", "--name", "--json"]),
|
|
110
|
-
).rejects.toThrow(/'--name <string>' argument missing/);
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it("rejects a --body that is not an object", async () => {
|
|
114
|
-
await expect(
|
|
115
|
-
build(["schema", "validate", "--body", "null"]),
|
|
116
|
-
).rejects.toThrow(/--body must be a JSON object/);
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
it("requires files on an upload and refuses --body there", async () => {
|
|
120
|
-
await expect(build(["temp-file", "upload"])).rejects.toThrow(
|
|
121
|
-
/--files is required/,
|
|
122
|
-
);
|
|
123
|
-
await expect(
|
|
124
|
-
build(["temp-file", "upload", "--body", "{}"]),
|
|
125
|
-
).rejects.toThrow(/use the file flags/);
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it("reads @file and stdin through the reader it is given", async () => {
|
|
129
|
-
const io: InputReader = {
|
|
130
|
-
text: async (path) =>
|
|
131
|
-
path === 0 ? '{"schema":{"type":"string"}}' : '{"type":"object"}',
|
|
132
|
-
bytes: async () => new Uint8Array(),
|
|
133
|
-
};
|
|
134
|
-
const fromFile = await build(
|
|
135
|
-
["schema", "validate", "--schema", "@/workspace/s.json"],
|
|
136
|
-
io,
|
|
137
|
-
);
|
|
138
|
-
expect(fromFile.json).toEqual({ schema: { type: "object" } });
|
|
139
|
-
const fromStdin = await build(["schema", "validate", "--body", "-"], io);
|
|
140
|
-
expect(fromStdin.json).toEqual({ schema: { type: "string" } });
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it("takes the organization from the credential, never from flags or --body", async () => {
|
|
144
|
-
await expect(
|
|
145
|
-
build(["tag", "list", "--organization-id", "other"]),
|
|
146
|
-
).rejects.toThrow(/unknown option '--organization-id'/);
|
|
147
|
-
const invocation = await parseInvocation(
|
|
148
|
-
[
|
|
149
|
-
"workflow",
|
|
150
|
-
"create",
|
|
151
|
-
"--name",
|
|
152
|
-
"W",
|
|
153
|
-
"--body",
|
|
154
|
-
'{"organizationId":"other"}',
|
|
155
|
-
],
|
|
156
|
-
{ commands: COMMANDS },
|
|
157
|
-
);
|
|
158
|
-
if (invocation.kind !== "command") throw new Error("expected a command");
|
|
159
|
-
const request = await buildRequest(invocation.command, invocation.parsed, {
|
|
160
|
-
organizationId: "mine",
|
|
161
|
-
});
|
|
162
|
-
expect(request.json).toMatchObject({ organizationId: "mine", name: "W" });
|
|
163
|
-
});
|
|
164
|
-
});
|