@asteroidai/cli 1.9.2 → 1.9.11
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 +1140 -316
- package/dist/core.js +869 -59
- package/package.json +3 -2
- package/src/commands.ts +13 -0
- package/src/core.ts +31 -0
- package/src/main.ts +2 -2
- package/src/output.test.ts +19 -0
- package/src/output.ts +11 -9
- package/src/program.test.ts +19 -0
- package/src/program.ts +15 -1
- package/src/reference.ts +1 -0
- package/src/run.test.ts +55 -2
- package/src/run.ts +62 -21
- package/src/sync/activities.ts +80 -0
- package/src/sync/commands.ts +111 -0
- package/src/sync/execution-pull.ts +273 -0
- package/src/sync/node-files.ts +47 -0
- package/src/sync/paths.ts +45 -0
- package/src/sync/run.ts +102 -0
- package/src/sync/sync.test.ts +463 -0
- package/src/sync/types.ts +58 -0
- package/src/sync/workflow-pull.ts +182 -0
- package/src/sync/workflow-push.ts +155 -0
- package/src/types.ts +2 -0
package/dist/asteroid.js
CHANGED
|
@@ -3,225 +3,6 @@
|
|
|
3
3
|
// src/main.ts
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
5
|
|
|
6
|
-
// src/config.ts
|
|
7
|
-
import envPaths from "env-paths";
|
|
8
|
-
import {
|
|
9
|
-
chmodSync,
|
|
10
|
-
existsSync,
|
|
11
|
-
mkdirSync,
|
|
12
|
-
readFileSync,
|
|
13
|
-
renameSync,
|
|
14
|
-
writeFileSync
|
|
15
|
-
} from "node:fs";
|
|
16
|
-
import { dirname, join } from "node:path";
|
|
17
|
-
|
|
18
|
-
// src/output.ts
|
|
19
|
-
import Table from "cli-table3";
|
|
20
|
-
import { styleText } from "node:util";
|
|
21
|
-
var PREFERRED = [
|
|
22
|
-
"id",
|
|
23
|
-
"name",
|
|
24
|
-
"title",
|
|
25
|
-
"workflowName",
|
|
26
|
-
"status",
|
|
27
|
-
"outcome",
|
|
28
|
-
"version",
|
|
29
|
-
"createdAt",
|
|
30
|
-
"updatedAt"
|
|
31
|
-
];
|
|
32
|
-
var HIDDEN = /^agent[A-Z]|Url$/;
|
|
33
|
-
var MAX_COLUMNS = 6;
|
|
34
|
-
var MAX_CELL = 60;
|
|
35
|
-
var CONTROL = /\x1b\[[0-?]*[ -/]*[@-~]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)?|\x1b[@-_]?|[\x00-\x08\x0b-\x1f\x7f-\x9f]/g;
|
|
36
|
-
function clean(text2) {
|
|
37
|
-
return text2.replace(CONTROL, "");
|
|
38
|
-
}
|
|
39
|
-
function isObject(value2) {
|
|
40
|
-
return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
|
|
41
|
-
}
|
|
42
|
-
function isScalar(value2) {
|
|
43
|
-
return value2 === null || ["string", "number", "boolean"].includes(typeof value2);
|
|
44
|
-
}
|
|
45
|
-
function indent(text2) {
|
|
46
|
-
return text2.split("\n").map((line) => ` ${line}`).join("\n");
|
|
47
|
-
}
|
|
48
|
-
function text(value2) {
|
|
49
|
-
return clean(isScalar(value2) ? String(value2) : JSON.stringify(value2));
|
|
50
|
-
}
|
|
51
|
-
function cell(value2) {
|
|
52
|
-
if (value2 === null || value2 === void 0) return "";
|
|
53
|
-
const line = text(value2).replace(/\s+/g, " ");
|
|
54
|
-
return line.length > MAX_CELL ? `${line.slice(0, MAX_CELL - 1)}\u2026` : line;
|
|
55
|
-
}
|
|
56
|
-
function columns(rows) {
|
|
57
|
-
const scalarKeys = [];
|
|
58
|
-
for (const row of rows) {
|
|
59
|
-
for (const [key, value2] of Object.entries(row)) {
|
|
60
|
-
if (isScalar(value2) && !HIDDEN.test(key.split(".").at(-1) ?? key) && !scalarKeys.includes(key))
|
|
61
|
-
scalarKeys.push(key);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
const ordered = [
|
|
65
|
-
...PREFERRED.filter((key) => scalarKeys.includes(key)),
|
|
66
|
-
...scalarKeys.filter((key) => !PREFERRED.includes(key))
|
|
67
|
-
];
|
|
68
|
-
return ordered.slice(0, MAX_COLUMNS);
|
|
69
|
-
}
|
|
70
|
-
function style(format, value2, options) {
|
|
71
|
-
return options.styled ? styleText(format, value2, { validateStream: false }) : value2;
|
|
72
|
-
}
|
|
73
|
-
function flatten(row) {
|
|
74
|
-
const flat = {};
|
|
75
|
-
for (const [key, value2] of Object.entries(row)) {
|
|
76
|
-
if (!isObject(value2)) {
|
|
77
|
-
flat[key] = value2;
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
for (const [inner, v] of Object.entries(value2))
|
|
81
|
-
flat[`${key}.${inner}`] = v;
|
|
82
|
-
}
|
|
83
|
-
return flat;
|
|
84
|
-
}
|
|
85
|
-
function table(input, options = {}) {
|
|
86
|
-
if (input.length === 0) return "No results.";
|
|
87
|
-
const rows = input.map(flatten);
|
|
88
|
-
const keys = columns(rows);
|
|
89
|
-
if (keys.length === 0) return rows.map((row) => text(row)).join("\n");
|
|
90
|
-
const cells = rows.map((row) => keys.map((key) => cell(row[key])));
|
|
91
|
-
if (options.styled) {
|
|
92
|
-
const grid = new Table({
|
|
93
|
-
head: keys.map((key) => style("bold", key, options)),
|
|
94
|
-
style: { head: [], border: [] }
|
|
95
|
-
});
|
|
96
|
-
grid.push(...cells);
|
|
97
|
-
return grid.toString();
|
|
98
|
-
}
|
|
99
|
-
const widths = keys.map(
|
|
100
|
-
(key, i) => Math.max(key.length, ...cells.map((r) => r[i].length))
|
|
101
|
-
);
|
|
102
|
-
const line = (values) => values.map((v, i) => v.padEnd(widths[i])).join(" ").trimEnd();
|
|
103
|
-
return [line(keys.map((k) => k.toUpperCase())), ...cells.map(line)].join(
|
|
104
|
-
"\n"
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
|
-
function summary(value2) {
|
|
108
|
-
if (Array.isArray(value2))
|
|
109
|
-
return `[${value2.length} item${value2.length === 1 ? "" : "s"}]`;
|
|
110
|
-
if (value2 && typeof value2 === "object") return cell(value2);
|
|
111
|
-
return text(value2);
|
|
112
|
-
}
|
|
113
|
-
function record(value2, options) {
|
|
114
|
-
const width = Math.max(...Object.keys(value2).map((key) => key.length));
|
|
115
|
-
const label = (key) => style("bold", key.padEnd(width), options);
|
|
116
|
-
return Object.entries(value2).map(([key, v]) => {
|
|
117
|
-
if (Array.isArray(v) && v.length > 0 && v.every(isObject)) {
|
|
118
|
-
return `${style("bold", key, options)}:
|
|
119
|
-
${indent(table(v, options))}`;
|
|
120
|
-
}
|
|
121
|
-
if (Array.isArray(v) && v.length > 0 && v.every(isScalar)) {
|
|
122
|
-
return `${label(key)} ${v.map(text).join(`
|
|
123
|
-
${" ".repeat(width + 2)}`)}`;
|
|
124
|
-
}
|
|
125
|
-
if (isObject(v) && Object.keys(v).length > 0) {
|
|
126
|
-
return `${style("bold", key, options)}:
|
|
127
|
-
${indent(record(v, options))}`;
|
|
128
|
-
}
|
|
129
|
-
return `${label(key)} ${isScalar(v) ? text(v) : summary(v)}`;
|
|
130
|
-
}).join("\n");
|
|
131
|
-
}
|
|
132
|
-
function render(value2, options = {}) {
|
|
133
|
-
if (value2 === void 0) return "";
|
|
134
|
-
if (Array.isArray(value2)) {
|
|
135
|
-
return value2.every(isObject) ? table(value2, options) : value2.map(text).join("\n");
|
|
136
|
-
}
|
|
137
|
-
if (isObject(value2)) {
|
|
138
|
-
const row = value2;
|
|
139
|
-
if (Array.isArray(row.items)) {
|
|
140
|
-
const rest = Object.entries(row).filter(
|
|
141
|
-
([k, v]) => k !== "items" && isScalar(v)
|
|
142
|
-
);
|
|
143
|
-
const footer = rest.length ? `
|
|
144
|
-
|
|
145
|
-
${style("dim", rest.map(([k, v]) => `${k}: ${text(v)}`).join(" "), options)}` : "";
|
|
146
|
-
return `${table(row.items, options)}${footer}`;
|
|
147
|
-
}
|
|
148
|
-
return record(row, options);
|
|
149
|
-
}
|
|
150
|
-
return text(value2);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// src/config.ts
|
|
154
|
-
var DEFAULT_API_URL = "https://odyssey.asteroid.ai";
|
|
155
|
-
var DEFAULT_WORKOS_CLIENT_ID = "client_01K3NXPE8X6EJZXZSYCNT5M4P0";
|
|
156
|
-
var DEFAULT_WORKOS_API_URL = "https://api.workos.com";
|
|
157
|
-
function readEnv(env = process.env) {
|
|
158
|
-
return {
|
|
159
|
-
apiUrl: (env.ASTEROID_API_URL || DEFAULT_API_URL).replace(/\/+$/, ""),
|
|
160
|
-
apiKey: env.ASTEROID_API_KEY || void 0,
|
|
161
|
-
org: env.ASTEROID_ORG || void 0,
|
|
162
|
-
workosClientId: env.ASTEROID_WORKOS_CLIENT_ID || DEFAULT_WORKOS_CLIENT_ID,
|
|
163
|
-
workosApiUrl: (env.ASTEROID_WORKOS_API_URL || DEFAULT_WORKOS_API_URL).replace(/\/+$/, ""),
|
|
164
|
-
configDir: env.ASTEROID_CONFIG_DIR || envPaths("asteroid", { suffix: "" }).config
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
function readJson(path, fallback) {
|
|
168
|
-
if (!existsSync(path)) return fallback;
|
|
169
|
-
try {
|
|
170
|
-
return JSON.parse(readFileSync(path, "utf8"));
|
|
171
|
-
} catch {
|
|
172
|
-
return fallback;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
function writePrivateJson(path, value2) {
|
|
176
|
-
mkdirSync(dirname(path), { recursive: true, mode: 448 });
|
|
177
|
-
const temp = `${path}.${process.pid}.tmp`;
|
|
178
|
-
writeFileSync(temp, `${JSON.stringify(value2, null, 2)}
|
|
179
|
-
`, { mode: 384 });
|
|
180
|
-
chmodSync(temp, 384);
|
|
181
|
-
renameSync(temp, path);
|
|
182
|
-
}
|
|
183
|
-
function loadConfig(env) {
|
|
184
|
-
return readJson(join(env.configDir, "config.json"), {});
|
|
185
|
-
}
|
|
186
|
-
function saveConfig(env, config) {
|
|
187
|
-
writePrivateJson(join(env.configDir, "config.json"), config);
|
|
188
|
-
}
|
|
189
|
-
function repoOrg(cwd) {
|
|
190
|
-
let dir = cwd;
|
|
191
|
-
for (; ; ) {
|
|
192
|
-
const file = join(dir, ".asteroid", "org");
|
|
193
|
-
if (existsSync(file)) return readFileSync(file, "utf8").trim() || void 0;
|
|
194
|
-
const parent = dirname(dir);
|
|
195
|
-
if (parent === dir) return void 0;
|
|
196
|
-
dir = parent;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
function requestedOrg(flag, env, cwd, config) {
|
|
200
|
-
return flag ?? env.org ?? repoOrg(cwd) ?? config.org?.workosId;
|
|
201
|
-
}
|
|
202
|
-
function findOrg(ref, orgs2) {
|
|
203
|
-
const lower = ref.toLowerCase();
|
|
204
|
-
return orgs2.find(
|
|
205
|
-
(org) => org.workosId.toLowerCase() === lower || org.id?.toLowerCase() === lower
|
|
206
|
-
) ?? orgs2.find((org) => org.name?.toLowerCase() === lower) ?? (/^org_[0-9a-z]+$/i.test(ref) ? { workosId: ref } : void 0);
|
|
207
|
-
}
|
|
208
|
-
function mergeOrgs(known = [], found = []) {
|
|
209
|
-
const byId = new Map(known.map((org) => [org.workosId, org]));
|
|
210
|
-
for (const org of found) {
|
|
211
|
-
const merged = { ...byId.get(org.workosId) };
|
|
212
|
-
for (const [key, value2] of Object.entries(org))
|
|
213
|
-
if (value2 !== void 0) Object.assign(merged, { [key]: value2 });
|
|
214
|
-
byId.set(org.workosId, merged);
|
|
215
|
-
}
|
|
216
|
-
return [...byId.values()].sort(
|
|
217
|
-
(a, b) => (a.name ?? a.workosId).localeCompare(b.name ?? b.workosId)
|
|
218
|
-
);
|
|
219
|
-
}
|
|
220
|
-
function orgLabel(org) {
|
|
221
|
-
const id = clean(org.id ?? org.workosId);
|
|
222
|
-
return org.name ? `${clean(org.name)} (${id})` : id;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
6
|
// src/generated/commands.ts
|
|
226
7
|
var COMMANDS = [
|
|
227
8
|
{
|
|
@@ -4725,50 +4506,378 @@ var COMMANDS = [
|
|
|
4725
4506
|
}
|
|
4726
4507
|
];
|
|
4727
4508
|
|
|
4728
|
-
// src/
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
var
|
|
4509
|
+
// src/sync/commands.ts
|
|
4510
|
+
function option(name, type, description, extra = {}) {
|
|
4511
|
+
return { name, flag: name, type, required: false, description, ...extra };
|
|
4512
|
+
}
|
|
4513
|
+
var DIR = option(
|
|
4514
|
+
"dir",
|
|
4515
|
+
"string",
|
|
4516
|
+
"Write here instead of `<workflow-name>/...` under the current directory."
|
|
4517
|
+
);
|
|
4518
|
+
var local = {
|
|
4519
|
+
method: "GET",
|
|
4520
|
+
path: "",
|
|
4521
|
+
binary: false,
|
|
4522
|
+
destructive: false,
|
|
4523
|
+
local: true
|
|
4733
4524
|
};
|
|
4734
|
-
|
|
4735
|
-
// src/reference.ts
|
|
4736
|
-
var BUILTINS = [
|
|
4737
|
-
{
|
|
4738
|
-
command: "login",
|
|
4739
|
-
usage: "asteroid login",
|
|
4740
|
-
description: "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.",
|
|
4741
|
-
args: 0
|
|
4742
|
-
},
|
|
4743
|
-
{
|
|
4744
|
-
command: "logout",
|
|
4745
|
-
usage: "asteroid logout",
|
|
4746
|
-
description: "Forget the stored sign-in and cached tokens.",
|
|
4747
|
-
args: 0
|
|
4748
|
-
},
|
|
4749
|
-
{
|
|
4750
|
-
command: "orgs",
|
|
4751
|
-
usage: "asteroid orgs",
|
|
4752
|
-
description: "List the organizations you can act in. The active one is marked.",
|
|
4753
|
-
args: 0
|
|
4754
|
-
},
|
|
4525
|
+
var SYNC_COMMANDS = [
|
|
4755
4526
|
{
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4527
|
+
...local,
|
|
4528
|
+
command: "workflow pull",
|
|
4529
|
+
tool: "workflow_pull",
|
|
4530
|
+
title: "Pull a workflow to disk",
|
|
4531
|
+
description: "Write a workflow's editable head to `<workflow-name>/head/` as files: settings, per-node instructions and scripts, and the workflow's own files. Pass --version-id for a pinned version, written read-only to `<workflow-name>/.versions/`. Edit the head files, then run `asteroid workflow push` from that folder.",
|
|
4532
|
+
readOnly: true,
|
|
4533
|
+
args: [
|
|
4534
|
+
{
|
|
4535
|
+
name: "workflowId",
|
|
4536
|
+
flag: "workflow-id",
|
|
4537
|
+
type: "string",
|
|
4538
|
+
required: true
|
|
4539
|
+
}
|
|
4540
|
+
],
|
|
4541
|
+
query: [
|
|
4542
|
+
option(
|
|
4543
|
+
"version-id",
|
|
4544
|
+
"string",
|
|
4545
|
+
"A workflow version ID. Omit for the editable head."
|
|
4546
|
+
),
|
|
4547
|
+
DIR
|
|
4548
|
+
]
|
|
4761
4549
|
},
|
|
4762
4550
|
{
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4551
|
+
...local,
|
|
4552
|
+
command: "workflow push",
|
|
4553
|
+
tool: "workflow_push",
|
|
4554
|
+
title: "Push local edits to a workflow head",
|
|
4555
|
+
description: "Send the edits in a pulled head folder to the workflow's editable head: changed and new files are written, removed files are deleted. Fails if the head changed since the pull; pull again and reapply. Push does not publish.",
|
|
4556
|
+
readOnly: false,
|
|
4557
|
+
args: [],
|
|
4558
|
+
query: [
|
|
4559
|
+
option(
|
|
4560
|
+
"dir",
|
|
4561
|
+
"string",
|
|
4562
|
+
"The pulled head folder. Defaults to the current directory."
|
|
4563
|
+
)
|
|
4564
|
+
]
|
|
4768
4565
|
},
|
|
4769
4566
|
{
|
|
4770
|
-
|
|
4771
|
-
|
|
4567
|
+
...local,
|
|
4568
|
+
command: "execution pull",
|
|
4569
|
+
tool: "execution_pull",
|
|
4570
|
+
title: "Pull an execution to disk",
|
|
4571
|
+
description: "Write an execution to `<workflow-name>/executions/<id>/`: summary.json, activities.md (the step-by-step log), and the files the run produced.",
|
|
4572
|
+
readOnly: true,
|
|
4573
|
+
args: [
|
|
4574
|
+
{
|
|
4575
|
+
name: "executionId",
|
|
4576
|
+
flag: "execution-id",
|
|
4577
|
+
type: "string",
|
|
4578
|
+
required: true
|
|
4579
|
+
}
|
|
4580
|
+
],
|
|
4581
|
+
query: [
|
|
4582
|
+
option(
|
|
4583
|
+
"files",
|
|
4584
|
+
"string",
|
|
4585
|
+
"Only download files whose path matches this glob. --no-files skips downloads.",
|
|
4586
|
+
{
|
|
4587
|
+
negatable: true
|
|
4588
|
+
}
|
|
4589
|
+
),
|
|
4590
|
+
option(
|
|
4591
|
+
"summary-only",
|
|
4592
|
+
"boolean",
|
|
4593
|
+
"Write summary.json only: no activities, no files."
|
|
4594
|
+
),
|
|
4595
|
+
option(
|
|
4596
|
+
"max-file-size",
|
|
4597
|
+
"integer",
|
|
4598
|
+
"Skip files larger than this many bytes."
|
|
4599
|
+
),
|
|
4600
|
+
option(
|
|
4601
|
+
"dir",
|
|
4602
|
+
"string",
|
|
4603
|
+
"Write here instead of `<workflow-name>/executions/<id>/` under the current directory."
|
|
4604
|
+
)
|
|
4605
|
+
]
|
|
4606
|
+
}
|
|
4607
|
+
];
|
|
4608
|
+
|
|
4609
|
+
// src/commands.ts
|
|
4610
|
+
function withSyncCommands(commands) {
|
|
4611
|
+
return [...commands, ...SYNC_COMMANDS].sort(
|
|
4612
|
+
(a, b) => a.command.localeCompare(b.command)
|
|
4613
|
+
);
|
|
4614
|
+
}
|
|
4615
|
+
var ALL_COMMANDS = withSyncCommands(COMMANDS);
|
|
4616
|
+
|
|
4617
|
+
// src/config.ts
|
|
4618
|
+
import envPaths from "env-paths";
|
|
4619
|
+
import {
|
|
4620
|
+
chmodSync,
|
|
4621
|
+
existsSync,
|
|
4622
|
+
mkdirSync,
|
|
4623
|
+
readFileSync,
|
|
4624
|
+
renameSync,
|
|
4625
|
+
writeFileSync
|
|
4626
|
+
} from "node:fs";
|
|
4627
|
+
import { dirname, join } from "node:path";
|
|
4628
|
+
|
|
4629
|
+
// src/output.ts
|
|
4630
|
+
import Table from "cli-table3";
|
|
4631
|
+
import { styleText } from "node:util";
|
|
4632
|
+
var PREFERRED = [
|
|
4633
|
+
"id",
|
|
4634
|
+
"name",
|
|
4635
|
+
"title",
|
|
4636
|
+
"workflowName",
|
|
4637
|
+
"status",
|
|
4638
|
+
"outcome",
|
|
4639
|
+
"version",
|
|
4640
|
+
"createdAt",
|
|
4641
|
+
"updatedAt"
|
|
4642
|
+
];
|
|
4643
|
+
var HIDDEN = /^agent[A-Z]|Url$/;
|
|
4644
|
+
var MAX_COLUMNS = 6;
|
|
4645
|
+
var MAX_CELL = 60;
|
|
4646
|
+
var CONTROL = /\x1b\[[0-?]*[ -/]*[@-~]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)?|\x1b[@-_]?|[\x00-\x08\x0b-\x1f\x7f-\x9f]/g;
|
|
4647
|
+
function clean(text2) {
|
|
4648
|
+
return text2.replace(CONTROL, "");
|
|
4649
|
+
}
|
|
4650
|
+
function isObject(value2) {
|
|
4651
|
+
return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
|
|
4652
|
+
}
|
|
4653
|
+
function isScalar(value2) {
|
|
4654
|
+
return value2 === null || ["string", "number", "boolean"].includes(typeof value2);
|
|
4655
|
+
}
|
|
4656
|
+
function indent(text2) {
|
|
4657
|
+
return text2.split("\n").map((line2) => ` ${line2}`).join("\n");
|
|
4658
|
+
}
|
|
4659
|
+
function text(value2) {
|
|
4660
|
+
return clean(isScalar(value2) ? String(value2) : JSON.stringify(value2));
|
|
4661
|
+
}
|
|
4662
|
+
function cell(value2, options = {}) {
|
|
4663
|
+
if (value2 === null || value2 === void 0) return "";
|
|
4664
|
+
const line2 = text(value2).replace(/\s+/g, " ");
|
|
4665
|
+
if (options.full || line2.length <= MAX_CELL) return line2;
|
|
4666
|
+
return `${line2.slice(0, MAX_CELL - 1)}\u2026`;
|
|
4667
|
+
}
|
|
4668
|
+
function columns(rows, options) {
|
|
4669
|
+
const scalarKeys = [];
|
|
4670
|
+
for (const row of rows) {
|
|
4671
|
+
for (const [key, value2] of Object.entries(row)) {
|
|
4672
|
+
if (isScalar(value2) && !HIDDEN.test(key.split(".").at(-1) ?? key) && !scalarKeys.includes(key))
|
|
4673
|
+
scalarKeys.push(key);
|
|
4674
|
+
}
|
|
4675
|
+
}
|
|
4676
|
+
const ordered = [
|
|
4677
|
+
...PREFERRED.filter((key) => scalarKeys.includes(key)),
|
|
4678
|
+
...scalarKeys.filter((key) => !PREFERRED.includes(key))
|
|
4679
|
+
];
|
|
4680
|
+
return options.full ? ordered : ordered.slice(0, MAX_COLUMNS);
|
|
4681
|
+
}
|
|
4682
|
+
function style(format, value2, options) {
|
|
4683
|
+
return options.styled ? styleText(format, value2, { validateStream: false }) : value2;
|
|
4684
|
+
}
|
|
4685
|
+
function flatten(row) {
|
|
4686
|
+
const flat = {};
|
|
4687
|
+
for (const [key, value2] of Object.entries(row)) {
|
|
4688
|
+
if (!isObject(value2)) {
|
|
4689
|
+
flat[key] = value2;
|
|
4690
|
+
continue;
|
|
4691
|
+
}
|
|
4692
|
+
for (const [inner, v] of Object.entries(value2))
|
|
4693
|
+
flat[`${key}.${inner}`] = v;
|
|
4694
|
+
}
|
|
4695
|
+
return flat;
|
|
4696
|
+
}
|
|
4697
|
+
function table(input, options = {}) {
|
|
4698
|
+
if (input.length === 0) return "No results.";
|
|
4699
|
+
const rows = input.map(flatten);
|
|
4700
|
+
const keys = columns(rows, options);
|
|
4701
|
+
if (keys.length === 0) return rows.map((row) => text(row)).join("\n");
|
|
4702
|
+
const cells = rows.map((row) => keys.map((key) => cell(row[key], options)));
|
|
4703
|
+
if (options.styled) {
|
|
4704
|
+
const grid = new Table({
|
|
4705
|
+
head: keys.map((key) => style("bold", key, options)),
|
|
4706
|
+
style: { head: [], border: [] }
|
|
4707
|
+
});
|
|
4708
|
+
grid.push(...cells);
|
|
4709
|
+
return grid.toString();
|
|
4710
|
+
}
|
|
4711
|
+
const widths = keys.map(
|
|
4712
|
+
(key, i) => Math.max(key.length, ...cells.map((r) => r[i].length))
|
|
4713
|
+
);
|
|
4714
|
+
const line2 = (values) => values.map((v, i) => v.padEnd(widths[i])).join(" ").trimEnd();
|
|
4715
|
+
return [line2(keys.map((k) => k.toUpperCase())), ...cells.map(line2)].join(
|
|
4716
|
+
"\n"
|
|
4717
|
+
);
|
|
4718
|
+
}
|
|
4719
|
+
function summary(value2, options) {
|
|
4720
|
+
if (Array.isArray(value2))
|
|
4721
|
+
return `[${value2.length} item${value2.length === 1 ? "" : "s"}]`;
|
|
4722
|
+
if (value2 && typeof value2 === "object") return cell(value2, options);
|
|
4723
|
+
return text(value2);
|
|
4724
|
+
}
|
|
4725
|
+
function record(value2, options) {
|
|
4726
|
+
const width = Math.max(...Object.keys(value2).map((key) => key.length));
|
|
4727
|
+
const label = (key) => style("bold", key.padEnd(width), options);
|
|
4728
|
+
return Object.entries(value2).map(([key, v]) => {
|
|
4729
|
+
if (Array.isArray(v) && v.length > 0 && v.every(isObject)) {
|
|
4730
|
+
return `${style("bold", key, options)}:
|
|
4731
|
+
${indent(table(v, options))}`;
|
|
4732
|
+
}
|
|
4733
|
+
if (Array.isArray(v) && v.length > 0 && v.every(isScalar)) {
|
|
4734
|
+
return `${label(key)} ${v.map(text).join(`
|
|
4735
|
+
${" ".repeat(width + 2)}`)}`;
|
|
4736
|
+
}
|
|
4737
|
+
if (isObject(v) && Object.keys(v).length > 0) {
|
|
4738
|
+
return `${style("bold", key, options)}:
|
|
4739
|
+
${indent(record(v, options))}`;
|
|
4740
|
+
}
|
|
4741
|
+
return `${label(key)} ${isScalar(v) ? text(v) : summary(v, options)}`;
|
|
4742
|
+
}).join("\n");
|
|
4743
|
+
}
|
|
4744
|
+
function render(value2, options = {}) {
|
|
4745
|
+
if (value2 === void 0) return "";
|
|
4746
|
+
if (Array.isArray(value2)) {
|
|
4747
|
+
return value2.every(isObject) ? table(value2, options) : value2.map(text).join("\n");
|
|
4748
|
+
}
|
|
4749
|
+
if (isObject(value2)) {
|
|
4750
|
+
const row = value2;
|
|
4751
|
+
if (Array.isArray(row.items)) {
|
|
4752
|
+
const rest = Object.entries(row).filter(
|
|
4753
|
+
([k, v]) => k !== "items" && isScalar(v)
|
|
4754
|
+
);
|
|
4755
|
+
const footer = rest.length ? `
|
|
4756
|
+
|
|
4757
|
+
${style("dim", rest.map(([k, v]) => `${k}: ${text(v)}`).join(" "), options)}` : "";
|
|
4758
|
+
return `${table(row.items, options)}${footer}`;
|
|
4759
|
+
}
|
|
4760
|
+
return record(row, options);
|
|
4761
|
+
}
|
|
4762
|
+
return text(value2);
|
|
4763
|
+
}
|
|
4764
|
+
|
|
4765
|
+
// src/config.ts
|
|
4766
|
+
var DEFAULT_API_URL = "https://odyssey.asteroid.ai";
|
|
4767
|
+
var DEFAULT_WORKOS_CLIENT_ID = "client_01K3NXPE8X6EJZXZSYCNT5M4P0";
|
|
4768
|
+
var DEFAULT_WORKOS_API_URL = "https://api.workos.com";
|
|
4769
|
+
function readEnv(env = process.env) {
|
|
4770
|
+
return {
|
|
4771
|
+
apiUrl: (env.ASTEROID_API_URL || DEFAULT_API_URL).replace(/\/+$/, ""),
|
|
4772
|
+
apiKey: env.ASTEROID_API_KEY || void 0,
|
|
4773
|
+
org: env.ASTEROID_ORG || void 0,
|
|
4774
|
+
workosClientId: env.ASTEROID_WORKOS_CLIENT_ID || DEFAULT_WORKOS_CLIENT_ID,
|
|
4775
|
+
workosApiUrl: (env.ASTEROID_WORKOS_API_URL || DEFAULT_WORKOS_API_URL).replace(/\/+$/, ""),
|
|
4776
|
+
configDir: env.ASTEROID_CONFIG_DIR || envPaths("asteroid", { suffix: "" }).config
|
|
4777
|
+
};
|
|
4778
|
+
}
|
|
4779
|
+
function readJson(path, fallback) {
|
|
4780
|
+
if (!existsSync(path)) return fallback;
|
|
4781
|
+
try {
|
|
4782
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
4783
|
+
} catch {
|
|
4784
|
+
return fallback;
|
|
4785
|
+
}
|
|
4786
|
+
}
|
|
4787
|
+
function writePrivateJson(path, value2) {
|
|
4788
|
+
mkdirSync(dirname(path), { recursive: true, mode: 448 });
|
|
4789
|
+
const temp = `${path}.${process.pid}.tmp`;
|
|
4790
|
+
writeFileSync(temp, `${JSON.stringify(value2, null, 2)}
|
|
4791
|
+
`, { mode: 384 });
|
|
4792
|
+
chmodSync(temp, 384);
|
|
4793
|
+
renameSync(temp, path);
|
|
4794
|
+
}
|
|
4795
|
+
function loadConfig(env) {
|
|
4796
|
+
return readJson(join(env.configDir, "config.json"), {});
|
|
4797
|
+
}
|
|
4798
|
+
function saveConfig(env, config) {
|
|
4799
|
+
writePrivateJson(join(env.configDir, "config.json"), config);
|
|
4800
|
+
}
|
|
4801
|
+
function repoOrg(cwd) {
|
|
4802
|
+
let dir = cwd;
|
|
4803
|
+
for (; ; ) {
|
|
4804
|
+
const file = join(dir, ".asteroid", "org");
|
|
4805
|
+
if (existsSync(file)) return readFileSync(file, "utf8").trim() || void 0;
|
|
4806
|
+
const parent = dirname(dir);
|
|
4807
|
+
if (parent === dir) return void 0;
|
|
4808
|
+
dir = parent;
|
|
4809
|
+
}
|
|
4810
|
+
}
|
|
4811
|
+
function requestedOrg(flag2, env, cwd, config) {
|
|
4812
|
+
return flag2 ?? env.org ?? repoOrg(cwd) ?? config.org?.workosId;
|
|
4813
|
+
}
|
|
4814
|
+
function findOrg(ref, orgs2) {
|
|
4815
|
+
const lower = ref.toLowerCase();
|
|
4816
|
+
return orgs2.find(
|
|
4817
|
+
(org) => org.workosId.toLowerCase() === lower || org.id?.toLowerCase() === lower
|
|
4818
|
+
) ?? orgs2.find((org) => org.name?.toLowerCase() === lower) ?? (/^org_[0-9a-z]+$/i.test(ref) ? { workosId: ref } : void 0);
|
|
4819
|
+
}
|
|
4820
|
+
function mergeOrgs(known = [], found = []) {
|
|
4821
|
+
const byId = new Map(known.map((org) => [org.workosId, org]));
|
|
4822
|
+
for (const org of found) {
|
|
4823
|
+
const merged = { ...byId.get(org.workosId) };
|
|
4824
|
+
for (const [key, value2] of Object.entries(org))
|
|
4825
|
+
if (value2 !== void 0) Object.assign(merged, { [key]: value2 });
|
|
4826
|
+
byId.set(org.workosId, merged);
|
|
4827
|
+
}
|
|
4828
|
+
return [...byId.values()].sort(
|
|
4829
|
+
(a, b) => (a.name ?? a.workosId).localeCompare(b.name ?? b.workosId)
|
|
4830
|
+
);
|
|
4831
|
+
}
|
|
4832
|
+
function orgLabel(org) {
|
|
4833
|
+
const id = clean(org.id ?? org.workosId);
|
|
4834
|
+
return org.name ? `${clean(org.name)} (${id})` : id;
|
|
4835
|
+
}
|
|
4836
|
+
|
|
4837
|
+
// src/program.ts
|
|
4838
|
+
import { Command, CommanderError } from "commander";
|
|
4839
|
+
|
|
4840
|
+
// src/args.ts
|
|
4841
|
+
var UsageError = class extends Error {
|
|
4842
|
+
};
|
|
4843
|
+
|
|
4844
|
+
// src/reference.ts
|
|
4845
|
+
var BUILTINS = [
|
|
4846
|
+
{
|
|
4847
|
+
command: "login",
|
|
4848
|
+
usage: "asteroid login",
|
|
4849
|
+
description: "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.",
|
|
4850
|
+
args: 0
|
|
4851
|
+
},
|
|
4852
|
+
{
|
|
4853
|
+
command: "logout",
|
|
4854
|
+
usage: "asteroid logout",
|
|
4855
|
+
description: "Forget the stored sign-in and cached tokens.",
|
|
4856
|
+
args: 0
|
|
4857
|
+
},
|
|
4858
|
+
{
|
|
4859
|
+
command: "orgs",
|
|
4860
|
+
usage: "asteroid orgs",
|
|
4861
|
+
description: "List the organizations you can act in. The active one is marked.",
|
|
4862
|
+
args: 0
|
|
4863
|
+
},
|
|
4864
|
+
{
|
|
4865
|
+
command: "use",
|
|
4866
|
+
usage: "asteroid use [org]",
|
|
4867
|
+
arg: "org",
|
|
4868
|
+
description: "Make an organization, by id or name, the default for later commands. Without one, pick from a list.",
|
|
4869
|
+
args: 1
|
|
4870
|
+
},
|
|
4871
|
+
{
|
|
4872
|
+
command: "complete",
|
|
4873
|
+
usage: "asteroid complete <zsh|bash|fish|powershell>",
|
|
4874
|
+
arg: "shell",
|
|
4875
|
+
description: "Print a shell completion script. For zsh: `asteroid complete zsh > ~/.zfunc/_asteroid`.",
|
|
4876
|
+
args: 1
|
|
4877
|
+
},
|
|
4878
|
+
{
|
|
4879
|
+
command: "whoami",
|
|
4880
|
+
usage: "asteroid whoami",
|
|
4772
4881
|
description: "Show who you act as, in which organization, and how long the token lasts.",
|
|
4773
4882
|
args: 0
|
|
4774
4883
|
}
|
|
@@ -4793,12 +4902,12 @@ var DOTTED = /^--([a-z0-9-]+\.[a-z0-9.-]+)(?:=(.*))?$/;
|
|
|
4793
4902
|
function collect(value2, previous) {
|
|
4794
4903
|
return [...previous ?? [], value2];
|
|
4795
4904
|
}
|
|
4796
|
-
function optionText(
|
|
4797
|
-
const text2 =
|
|
4905
|
+
function optionText(option2) {
|
|
4906
|
+
const text2 = option2.description?.replace(/\s+/g, " ").trim() ?? "";
|
|
4798
4907
|
const parts = [text2 && !/[.!?]$/.test(text2) ? `${text2}.` : text2];
|
|
4799
|
-
if (
|
|
4800
|
-
if (
|
|
4801
|
-
if (
|
|
4908
|
+
if (option2.enum) parts.push(`One of: ${option2.enum.join(", ")}.`);
|
|
4909
|
+
if (option2.type.endsWith("[]")) parts.push("Repeatable.");
|
|
4910
|
+
if (option2.required) parts.push("Required.");
|
|
4802
4911
|
return parts.filter(Boolean).join(" ");
|
|
4803
4912
|
}
|
|
4804
4913
|
function splitDotted(argv2) {
|
|
@@ -4825,18 +4934,20 @@ function group(parent, name, groups, path) {
|
|
|
4825
4934
|
return created;
|
|
4826
4935
|
}
|
|
4827
4936
|
function leafOptions(leaf, command) {
|
|
4828
|
-
for (const
|
|
4829
|
-
if (
|
|
4830
|
-
leaf.option(`--${
|
|
4831
|
-
leaf.option(`--no-${
|
|
4937
|
+
for (const option2 of [...command.query, ...command.body?.fields ?? []]) {
|
|
4938
|
+
if (option2.type === "boolean") {
|
|
4939
|
+
leaf.option(`--${option2.flag}`, optionText(option2));
|
|
4940
|
+
leaf.option(`--no-${option2.flag}`, `Set ${option2.flag} to false.`);
|
|
4832
4941
|
continue;
|
|
4833
4942
|
}
|
|
4834
|
-
const placeholder =
|
|
4943
|
+
const placeholder = option2.enum ? option2.enum.join("|") : option2.type.replace("[]", "");
|
|
4835
4944
|
leaf.option(
|
|
4836
|
-
`--${
|
|
4837
|
-
optionText(
|
|
4945
|
+
`--${option2.flag} <${placeholder}>`,
|
|
4946
|
+
optionText(option2),
|
|
4838
4947
|
collect
|
|
4839
4948
|
);
|
|
4949
|
+
if (option2.negatable)
|
|
4950
|
+
leaf.option(`--no-${option2.flag}`, `Skip ${option2.flag}.`);
|
|
4840
4951
|
}
|
|
4841
4952
|
for (const file of command.body?.files ?? []) {
|
|
4842
4953
|
leaf.option(`--${file} <path>`, "File to upload. Repeatable.", collect);
|
|
@@ -4846,11 +4957,11 @@ function toParsed(leaf, positionals, dotted) {
|
|
|
4846
4957
|
const globals = leaf.optsWithGlobals();
|
|
4847
4958
|
const own = leaf.opts();
|
|
4848
4959
|
const flags = new Map(dotted);
|
|
4849
|
-
for (const
|
|
4850
|
-
const
|
|
4851
|
-
const value2 = own[
|
|
4852
|
-
if (!
|
|
4853
|
-
flags.set(
|
|
4960
|
+
for (const option2 of leaf.options) {
|
|
4961
|
+
const flag2 = option2.long?.replace(/^--(no-)?/, "");
|
|
4962
|
+
const value2 = own[option2.attributeName()];
|
|
4963
|
+
if (!flag2 || value2 === void 0 || flags.has(flag2)) continue;
|
|
4964
|
+
flags.set(flag2, Array.isArray(value2) ? value2.map(String) : [String(value2)]);
|
|
4854
4965
|
}
|
|
4855
4966
|
return {
|
|
4856
4967
|
positionals,
|
|
@@ -4863,7 +4974,11 @@ function toParsed(leaf, positionals, dotted) {
|
|
|
4863
4974
|
}
|
|
4864
4975
|
function defineCommands(root, options, dotted = /* @__PURE__ */ new Map(), onInvocation = () => void 0) {
|
|
4865
4976
|
if (options.description) root.description(options.description);
|
|
4866
|
-
if (options.version)
|
|
4977
|
+
if (options.version)
|
|
4978
|
+
root.addHelpText(
|
|
4979
|
+
"after",
|
|
4980
|
+
"\nRun `asteroid --version` to print the CLI version."
|
|
4981
|
+
);
|
|
4867
4982
|
for (const [flags, text2] of GLOBAL_FLAGS) {
|
|
4868
4983
|
if (!flags.startsWith("-h")) root.option(flags, text2);
|
|
4869
4984
|
}
|
|
@@ -4925,8 +5040,8 @@ ${notes.join("\n")}`);
|
|
|
4925
5040
|
);
|
|
4926
5041
|
}
|
|
4927
5042
|
const unknownDotted = [...dotted.keys()].filter(
|
|
4928
|
-
(
|
|
4929
|
-
(f) =>
|
|
5043
|
+
(flag2) => !(command.body?.fields ?? []).some(
|
|
5044
|
+
(f) => flag2.startsWith(`${f.flag}.`)
|
|
4930
5045
|
)
|
|
4931
5046
|
);
|
|
4932
5047
|
if (unknownDotted.length > 0) {
|
|
@@ -4947,6 +5062,15 @@ async function parseInvocation(argv2, options) {
|
|
|
4947
5062
|
let stdout = "";
|
|
4948
5063
|
let stderr = "";
|
|
4949
5064
|
let result;
|
|
5065
|
+
if (options.version && (argv2[0] === "-v" || argv2[0] === "--version")) {
|
|
5066
|
+
return {
|
|
5067
|
+
kind: "exit",
|
|
5068
|
+
code: 0,
|
|
5069
|
+
stdout: `${options.version}
|
|
5070
|
+
`,
|
|
5071
|
+
stderr: ""
|
|
5072
|
+
};
|
|
5073
|
+
}
|
|
4950
5074
|
const { argv: rest, dotted } = splitDotted(argv2);
|
|
4951
5075
|
const root = new Command("asteroid").exitOverride().helpCommand(false).configureOutput({
|
|
4952
5076
|
writeOut: (text2) => stdout += text2,
|
|
@@ -5329,7 +5453,7 @@ var ApiError = class extends Error {
|
|
|
5329
5453
|
status;
|
|
5330
5454
|
body;
|
|
5331
5455
|
};
|
|
5332
|
-
var VERSION = "1.9.
|
|
5456
|
+
var VERSION = "1.9.11";
|
|
5333
5457
|
function problem(status, body, fallback) {
|
|
5334
5458
|
if (body && typeof body === "object") {
|
|
5335
5459
|
const record2 = body;
|
|
@@ -5602,34 +5726,34 @@ import { basename } from "node:path";
|
|
|
5602
5726
|
function camel(segment) {
|
|
5603
5727
|
return segment.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
|
|
5604
5728
|
}
|
|
5605
|
-
function scalar(
|
|
5606
|
-
const type =
|
|
5607
|
-
if (
|
|
5729
|
+
function scalar(option2, raw) {
|
|
5730
|
+
const type = option2.type.replace("[]", "");
|
|
5731
|
+
if (option2.enum && !option2.enum.includes(raw)) {
|
|
5608
5732
|
throw new UsageError(
|
|
5609
|
-
`--${
|
|
5733
|
+
`--${option2.flag} must be one of ${option2.enum.join(", ")}`
|
|
5610
5734
|
);
|
|
5611
5735
|
}
|
|
5612
5736
|
if (type === "integer" || type === "number") {
|
|
5613
5737
|
const value2 = Number(raw);
|
|
5614
5738
|
if (raw.trim() === "" || Number.isNaN(value2) || type === "integer" && !Number.isInteger(value2)) {
|
|
5615
5739
|
throw new UsageError(
|
|
5616
|
-
`--${
|
|
5740
|
+
`--${option2.flag} must be ${type === "integer" ? "an integer" : "a number"}`
|
|
5617
5741
|
);
|
|
5618
5742
|
}
|
|
5619
5743
|
return value2;
|
|
5620
5744
|
}
|
|
5621
5745
|
if (type === "boolean") {
|
|
5622
5746
|
if (raw !== "true" && raw !== "false") {
|
|
5623
|
-
throw new UsageError(`--${
|
|
5747
|
+
throw new UsageError(`--${option2.flag} must be true or false`);
|
|
5624
5748
|
}
|
|
5625
5749
|
return raw === "true";
|
|
5626
5750
|
}
|
|
5627
|
-
if (type === "json") return parseJson(raw, `--${
|
|
5751
|
+
if (type === "json") return parseJson(raw, `--${option2.flag}`);
|
|
5628
5752
|
return raw;
|
|
5629
5753
|
}
|
|
5630
|
-
function value(
|
|
5631
|
-
if (
|
|
5632
|
-
return scalar(
|
|
5754
|
+
function value(option2, raws) {
|
|
5755
|
+
if (option2.type.endsWith("[]")) return raws.map((raw) => scalar(option2, raw));
|
|
5756
|
+
return scalar(option2, raws.at(-1));
|
|
5633
5757
|
}
|
|
5634
5758
|
var nodeReader = {
|
|
5635
5759
|
text: async (path) => readFileSync2(path, "utf8"),
|
|
@@ -5641,12 +5765,12 @@ async function readInput(raw, io) {
|
|
|
5641
5765
|
return raw;
|
|
5642
5766
|
}
|
|
5643
5767
|
async function resolveInputs(command, parsed, io) {
|
|
5644
|
-
const jsonFlags = [...command.query, ...command.body?.fields ?? []].filter((
|
|
5768
|
+
const jsonFlags = [...command.query, ...command.body?.fields ?? []].filter((option2) => option2.type === "json").map((option2) => option2.flag);
|
|
5645
5769
|
const flags = new Map(parsed.flags);
|
|
5646
|
-
for (const
|
|
5647
|
-
const raws = flags.get(
|
|
5770
|
+
for (const flag2 of jsonFlags) {
|
|
5771
|
+
const raws = flags.get(flag2);
|
|
5648
5772
|
if (raws)
|
|
5649
|
-
flags.set(
|
|
5773
|
+
flags.set(flag2, await Promise.all(raws.map((raw) => readInput(raw, io))));
|
|
5650
5774
|
}
|
|
5651
5775
|
const body = parsed.body === void 0 ? void 0 : await readInput(parsed.body, io);
|
|
5652
5776
|
return { ...parsed, flags, body };
|
|
@@ -5696,18 +5820,18 @@ async function buildRequest(command, input, context = {}) {
|
|
|
5696
5820
|
});
|
|
5697
5821
|
const flags = new Map(parsed.flags);
|
|
5698
5822
|
const query = new URLSearchParams();
|
|
5699
|
-
for (const
|
|
5700
|
-
const raws = flags.get(
|
|
5701
|
-
flags.delete(
|
|
5823
|
+
for (const option2 of command.query) {
|
|
5824
|
+
const raws = flags.get(option2.flag);
|
|
5825
|
+
flags.delete(option2.flag);
|
|
5702
5826
|
if (!raws) {
|
|
5703
|
-
if (
|
|
5827
|
+
if (option2.required) throw new UsageError(`--${option2.flag} is required`);
|
|
5704
5828
|
continue;
|
|
5705
5829
|
}
|
|
5706
|
-
const parsedValue = value(
|
|
5830
|
+
const parsedValue = value(option2, raws);
|
|
5707
5831
|
const values = Array.isArray(parsedValue) ? parsedValue : [parsedValue];
|
|
5708
|
-
if (
|
|
5709
|
-
query.append(
|
|
5710
|
-
else for (const v of values) query.append(
|
|
5832
|
+
if (option2.explode === false)
|
|
5833
|
+
query.append(option2.name, values.map(String).join(","));
|
|
5834
|
+
else for (const v of values) query.append(option2.name, String(v));
|
|
5711
5835
|
}
|
|
5712
5836
|
const built = { method: command.method, path, query };
|
|
5713
5837
|
const body = command.body;
|
|
@@ -5757,11 +5881,11 @@ async function buildRequest(command, input, context = {}) {
|
|
|
5757
5881
|
flags.delete(field.flag);
|
|
5758
5882
|
json[field.name] = value(field, raws);
|
|
5759
5883
|
}
|
|
5760
|
-
for (const [
|
|
5761
|
-
const [head, ...rest] =
|
|
5884
|
+
for (const [flag2, raws] of [...flags]) {
|
|
5885
|
+
const [head, ...rest] = flag2.split(".");
|
|
5762
5886
|
const field = body.fields.find((f) => f.flag === head);
|
|
5763
5887
|
if (!field || rest.length === 0) continue;
|
|
5764
|
-
flags.delete(
|
|
5888
|
+
flags.delete(flag2);
|
|
5765
5889
|
const current = json[field.name];
|
|
5766
5890
|
json[field.name] = typeof current === "object" && current !== null ? current : {};
|
|
5767
5891
|
setPath(json, [field.name, ...rest.map(camel)], loose(raws.at(-1)));
|
|
@@ -5793,6 +5917,679 @@ function rejectUnknown(flags) {
|
|
|
5793
5917
|
}
|
|
5794
5918
|
}
|
|
5795
5919
|
|
|
5920
|
+
// src/sync/activities.ts
|
|
5921
|
+
function indentJson(data) {
|
|
5922
|
+
return JSON.stringify(data, null, 2).split("\n").map((line2) => ` ${line2}`).join("\n");
|
|
5923
|
+
}
|
|
5924
|
+
function action(label, payload) {
|
|
5925
|
+
const info = payload.info;
|
|
5926
|
+
const detail = info && Object.keys(info).length > 0 ? `
|
|
5927
|
+
info:
|
|
5928
|
+
${indentJson(info)}` : "";
|
|
5929
|
+
return `[${label}: ${payload.actionName}]:${detail}
|
|
5930
|
+
`;
|
|
5931
|
+
}
|
|
5932
|
+
function line(payload) {
|
|
5933
|
+
switch (payload.activityType) {
|
|
5934
|
+
case "reasoning":
|
|
5935
|
+
return `[reasoning]:
|
|
5936
|
+
${payload.reasoning}
|
|
5937
|
+
`;
|
|
5938
|
+
case "generic":
|
|
5939
|
+
return `[message]:
|
|
5940
|
+
${payload.message}
|
|
5941
|
+
`;
|
|
5942
|
+
case "user_message_received":
|
|
5943
|
+
return `[user_message]:
|
|
5944
|
+
${payload.message}
|
|
5945
|
+
`;
|
|
5946
|
+
case "action_started":
|
|
5947
|
+
return action("action_started", payload);
|
|
5948
|
+
case "action_completed":
|
|
5949
|
+
return action("action_completed", payload);
|
|
5950
|
+
case "action_failed":
|
|
5951
|
+
return `[action_failed: ${payload.actionName}]:
|
|
5952
|
+
failure: ${payload.message}
|
|
5953
|
+
`;
|
|
5954
|
+
case "compaction_performed":
|
|
5955
|
+
return `[compaction]: summary_tokens: ${payload.summaryTokens}, duration_ms: ${payload.durationMs}
|
|
5956
|
+
`;
|
|
5957
|
+
case "compaction_started":
|
|
5958
|
+
return "[compaction_started]\n";
|
|
5959
|
+
default: {
|
|
5960
|
+
const summary2 = payload.display?.summaryText;
|
|
5961
|
+
return summary2 ? `[${payload.activityType}]: ${summary2}
|
|
5962
|
+
` : `[${payload.activityType}]
|
|
5963
|
+
`;
|
|
5964
|
+
}
|
|
5965
|
+
}
|
|
5966
|
+
}
|
|
5967
|
+
function formatActivitiesMarkdown(activities, status, createdAt) {
|
|
5968
|
+
const lines = [
|
|
5969
|
+
"# Execution Activities\n",
|
|
5970
|
+
`Status: ${status}`,
|
|
5971
|
+
`Started: ${createdAt}`
|
|
5972
|
+
];
|
|
5973
|
+
let currentTurn = "";
|
|
5974
|
+
for (const { payload } of activities) {
|
|
5975
|
+
const turnId = payload.display?.turnId ?? "";
|
|
5976
|
+
if (turnId && turnId !== currentTurn) {
|
|
5977
|
+
currentTurn = turnId;
|
|
5978
|
+
lines.push(`
|
|
5979
|
+
---
|
|
5980
|
+
|
|
5981
|
+
## ${turnId}
|
|
5982
|
+
`);
|
|
5983
|
+
} else if (!currentTurn && !turnId) {
|
|
5984
|
+
currentTurn = "_started";
|
|
5985
|
+
lines.push("\n---\n");
|
|
5986
|
+
}
|
|
5987
|
+
lines.push(line(payload));
|
|
5988
|
+
}
|
|
5989
|
+
return lines.join("\n");
|
|
5990
|
+
}
|
|
5991
|
+
|
|
5992
|
+
// ../workflow-files/src/representation/name-charset.ts
|
|
5993
|
+
var NAME_CHARSET_RE = /^[A-Za-z0-9 !#$%&'()+,.;=@^`{}[\]~-]+$/;
|
|
5994
|
+
var WINDOWS_RESERVED = /* @__PURE__ */ new Set([
|
|
5995
|
+
"con",
|
|
5996
|
+
"prn",
|
|
5997
|
+
"aux",
|
|
5998
|
+
"nul",
|
|
5999
|
+
...Array.from({ length: 9 }, (_, i) => `com${i + 1}`),
|
|
6000
|
+
...Array.from({ length: 9 }, (_, i) => `lpt${i + 1}`)
|
|
6001
|
+
]);
|
|
6002
|
+
function isNameCharsetConforming(name) {
|
|
6003
|
+
return NAME_CHARSET_RE.test(name);
|
|
6004
|
+
}
|
|
6005
|
+
|
|
6006
|
+
// ../workflow-files/src/representation/types.ts
|
|
6007
|
+
function normalizeNodeName(name) {
|
|
6008
|
+
let result = name.toLowerCase();
|
|
6009
|
+
result = result.replace(/[^a-z0-9]+/g, "_");
|
|
6010
|
+
result = result.replace(/^_+|_+$/g, "");
|
|
6011
|
+
if (result.length > 80) {
|
|
6012
|
+
result = result.slice(0, 80).replace(/_+$/, "");
|
|
6013
|
+
}
|
|
6014
|
+
if (result === "") {
|
|
6015
|
+
throw new Error(
|
|
6016
|
+
`representation: node name "${name}" produces an empty directory name after normalization`
|
|
6017
|
+
);
|
|
6018
|
+
}
|
|
6019
|
+
return result;
|
|
6020
|
+
}
|
|
6021
|
+
function nodeDirName(name) {
|
|
6022
|
+
if (isNameCharsetConforming(name)) {
|
|
6023
|
+
return name.replaceAll(" ", "_");
|
|
6024
|
+
}
|
|
6025
|
+
return normalizeNodeName(name);
|
|
6026
|
+
}
|
|
6027
|
+
function agentDirName(name) {
|
|
6028
|
+
try {
|
|
6029
|
+
return nodeDirName(name);
|
|
6030
|
+
} catch {
|
|
6031
|
+
return "agent";
|
|
6032
|
+
}
|
|
6033
|
+
}
|
|
6034
|
+
function executionDirName(executionId) {
|
|
6035
|
+
return executionId.slice(0, 8);
|
|
6036
|
+
}
|
|
6037
|
+
function workflowDirName(workflowId, version, parentId, parentVersion) {
|
|
6038
|
+
const short = workflowId.slice(0, 8);
|
|
6039
|
+
if (version !== void 0 && version !== null) {
|
|
6040
|
+
return `v${version}_${short}`;
|
|
6041
|
+
}
|
|
6042
|
+
if (parentId) {
|
|
6043
|
+
if (parentVersion !== void 0 && parentVersion !== null) {
|
|
6044
|
+
return `draft_${short}_from_v${parentVersion}`;
|
|
6045
|
+
}
|
|
6046
|
+
return `draft_${short}_from_draft_${parentId.slice(0, 8)}`;
|
|
6047
|
+
}
|
|
6048
|
+
return `draft_${short}`;
|
|
6049
|
+
}
|
|
6050
|
+
|
|
6051
|
+
// src/sync/paths.ts
|
|
6052
|
+
import { createHash } from "node:crypto";
|
|
6053
|
+
function join3(...parts) {
|
|
6054
|
+
return parts.filter((part) => part !== "").join("/").replace(/\/{2,}/g, "/");
|
|
6055
|
+
}
|
|
6056
|
+
function workflowRoot(root, workflowName) {
|
|
6057
|
+
return join3(root, agentDirName(workflowName));
|
|
6058
|
+
}
|
|
6059
|
+
function isContained(path) {
|
|
6060
|
+
if (path === "" || path.startsWith("/") || path.includes("\\")) return false;
|
|
6061
|
+
return path.split("/").every((segment) => segment !== ".." && segment !== "");
|
|
6062
|
+
}
|
|
6063
|
+
function sha256(bytes) {
|
|
6064
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
6065
|
+
}
|
|
6066
|
+
function matchesGlob(path, pattern) {
|
|
6067
|
+
let source = "";
|
|
6068
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
6069
|
+
const ch = pattern[i];
|
|
6070
|
+
if (ch === "*" && pattern[i + 1] === "*" && pattern[i + 2] === "/") {
|
|
6071
|
+
source += "(?:.*/)?";
|
|
6072
|
+
i += 2;
|
|
6073
|
+
} else if (ch === "*" && pattern[i + 1] === "*") {
|
|
6074
|
+
source += ".*";
|
|
6075
|
+
i += 1;
|
|
6076
|
+
} else if (ch === "*") source += "[^/]*";
|
|
6077
|
+
else if (ch === "?") source += "[^/]";
|
|
6078
|
+
else source += ch.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
6079
|
+
}
|
|
6080
|
+
return new RegExp(`^${source}$`).test(path);
|
|
6081
|
+
}
|
|
6082
|
+
|
|
6083
|
+
// src/sync/types.ts
|
|
6084
|
+
var MANIFEST = ".asteroid.json";
|
|
6085
|
+
function get(path, query = {}) {
|
|
6086
|
+
return { method: "GET", path, query: new URLSearchParams(query) };
|
|
6087
|
+
}
|
|
6088
|
+
function statusOf(error) {
|
|
6089
|
+
const status = error?.status;
|
|
6090
|
+
return typeof status === "number" ? status : void 0;
|
|
6091
|
+
}
|
|
6092
|
+
|
|
6093
|
+
// src/sync/execution-pull.ts
|
|
6094
|
+
var DIRECTORIES = ["workspace", "downloads", "shared"];
|
|
6095
|
+
function relativeFilePath(file) {
|
|
6096
|
+
const at = file.filePath.indexOf(`/${file.directory}/`);
|
|
6097
|
+
if (at === -1)
|
|
6098
|
+
return `${file.directory}/${file.filePath.split("/").pop() ?? "unknown"}`;
|
|
6099
|
+
return file.filePath.slice(at + 1).replace(/^workspace\/\.execution-context\//, "");
|
|
6100
|
+
}
|
|
6101
|
+
function executionDir(options, execution) {
|
|
6102
|
+
const chosen = options.dir?.(execution);
|
|
6103
|
+
if (chosen) return chosen;
|
|
6104
|
+
const root = workflowRoot(
|
|
6105
|
+
options.root,
|
|
6106
|
+
execution.workflowName ?? execution.workflowId
|
|
6107
|
+
);
|
|
6108
|
+
return join3(root, "executions", executionDirName(execution.id));
|
|
6109
|
+
}
|
|
6110
|
+
async function writeText(sync, path, text2) {
|
|
6111
|
+
await sync.files.write(path, Buffer.from(text2, "utf8"));
|
|
6112
|
+
}
|
|
6113
|
+
async function writeActivities(sync, dir, execution) {
|
|
6114
|
+
try {
|
|
6115
|
+
const activities = await sync.remote.json(
|
|
6116
|
+
get(`/agents/v2/executions/${execution.id}/activities`, { order: "asc" })
|
|
6117
|
+
);
|
|
6118
|
+
if (activities.length === 0) return false;
|
|
6119
|
+
const markdown = formatActivitiesMarkdown(
|
|
6120
|
+
activities,
|
|
6121
|
+
execution.status,
|
|
6122
|
+
execution.createdAt
|
|
6123
|
+
);
|
|
6124
|
+
await writeText(sync, join3(dir, "activities.md"), markdown);
|
|
6125
|
+
return true;
|
|
6126
|
+
} catch {
|
|
6127
|
+
return false;
|
|
6128
|
+
}
|
|
6129
|
+
}
|
|
6130
|
+
async function listFiles(sync, executionId) {
|
|
6131
|
+
const listing = await sync.remote.json(
|
|
6132
|
+
get(`/agents/v2/executions/${executionId}/files`)
|
|
6133
|
+
);
|
|
6134
|
+
return DIRECTORIES.flatMap((name) => listing.directories[name] ?? []);
|
|
6135
|
+
}
|
|
6136
|
+
async function download(sync, executionId, file) {
|
|
6137
|
+
const path = `/agents/v2/executions/${executionId}/files/${file.id}/signed-url`;
|
|
6138
|
+
const { url } = await sync.remote.json(get(path));
|
|
6139
|
+
return sync.remote.download(url);
|
|
6140
|
+
}
|
|
6141
|
+
function selectFiles(files, options, skipped) {
|
|
6142
|
+
return files.filter((file) => {
|
|
6143
|
+
const path = relativeFilePath(file);
|
|
6144
|
+
if (!isContained(path)) skipped.push(`${path} (unsafe path)`);
|
|
6145
|
+
else if (options.glob !== void 0 && !matchesGlob(path, options.glob))
|
|
6146
|
+
skipped.push(`${path} (excluded by --files)`);
|
|
6147
|
+
else if (options.maxFileBytes !== void 0 && file.fileSize > options.maxFileBytes)
|
|
6148
|
+
skipped.push(`${path} (${file.fileSize} bytes, over the size limit)`);
|
|
6149
|
+
else return true;
|
|
6150
|
+
return false;
|
|
6151
|
+
});
|
|
6152
|
+
}
|
|
6153
|
+
async function pullFiles(sync, dir, options) {
|
|
6154
|
+
const skipped = [];
|
|
6155
|
+
const failed = [];
|
|
6156
|
+
let files;
|
|
6157
|
+
try {
|
|
6158
|
+
files = await listFiles(sync, options.executionId);
|
|
6159
|
+
} catch (error) {
|
|
6160
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
6161
|
+
return {
|
|
6162
|
+
downloaded: 0,
|
|
6163
|
+
candidates: 0,
|
|
6164
|
+
skipped,
|
|
6165
|
+
failed: [`file listing (${reason})`]
|
|
6166
|
+
};
|
|
6167
|
+
}
|
|
6168
|
+
let downloaded = 0;
|
|
6169
|
+
await Promise.all(
|
|
6170
|
+
selectFiles(files, options, skipped).map(async (file) => {
|
|
6171
|
+
try {
|
|
6172
|
+
const bytes = await download(sync, options.executionId, file);
|
|
6173
|
+
await sync.files.write(join3(dir, relativeFilePath(file)), bytes);
|
|
6174
|
+
downloaded++;
|
|
6175
|
+
} catch (error) {
|
|
6176
|
+
failed.push(
|
|
6177
|
+
`${file.fileName} (${error instanceof Error ? error.message : String(error)})`
|
|
6178
|
+
);
|
|
6179
|
+
}
|
|
6180
|
+
})
|
|
6181
|
+
);
|
|
6182
|
+
return { downloaded, candidates: files.length, skipped, failed };
|
|
6183
|
+
}
|
|
6184
|
+
async function pullExecution(sync, options) {
|
|
6185
|
+
const execution = await sync.remote.json(
|
|
6186
|
+
get(`/agents/v2/executions/${options.executionId}`)
|
|
6187
|
+
);
|
|
6188
|
+
const dir = executionDir(options, execution);
|
|
6189
|
+
await writeText(
|
|
6190
|
+
sync,
|
|
6191
|
+
join3(dir, "summary.json"),
|
|
6192
|
+
JSON.stringify(execution, null, 2)
|
|
6193
|
+
);
|
|
6194
|
+
const result = {
|
|
6195
|
+
execution,
|
|
6196
|
+
dir,
|
|
6197
|
+
written: ["summary.json"],
|
|
6198
|
+
downloaded: 0,
|
|
6199
|
+
candidates: 0,
|
|
6200
|
+
skipped: [],
|
|
6201
|
+
failed: []
|
|
6202
|
+
};
|
|
6203
|
+
if (execution.purgedAt) return result;
|
|
6204
|
+
if (options.activities !== false && await writeActivities(sync, dir, execution))
|
|
6205
|
+
result.written.push("activities.md");
|
|
6206
|
+
if (options.files === false) {
|
|
6207
|
+
return {
|
|
6208
|
+
...result,
|
|
6209
|
+
skippedBy: options.activities === false ? "--summary-only" : "--no-files"
|
|
6210
|
+
};
|
|
6211
|
+
}
|
|
6212
|
+
return { ...result, ...await pullFiles(sync, dir, options) };
|
|
6213
|
+
}
|
|
6214
|
+
function filesLine(result) {
|
|
6215
|
+
const parts = [...result.written];
|
|
6216
|
+
if (result.downloaded > 0) parts.push(`${result.downloaded} downloaded`);
|
|
6217
|
+
const counted = `Files: ${result.written.length + result.downloaded} written (${parts.join(", ")})`;
|
|
6218
|
+
if (result.skippedBy)
|
|
6219
|
+
return `${counted}; downloads skipped (${result.skippedBy})`;
|
|
6220
|
+
if (result.candidates === 0) return `${counted}; no execution files matched`;
|
|
6221
|
+
return counted;
|
|
6222
|
+
}
|
|
6223
|
+
function failures(execution) {
|
|
6224
|
+
const list = execution.scriptFailures ?? [];
|
|
6225
|
+
if (list.length === 0) return null;
|
|
6226
|
+
const lines = list.map(
|
|
6227
|
+
(f) => ` - ${f.nodeName ?? "(unknown node)"}: ${(f.error ?? "").split("\n")[0]}`
|
|
6228
|
+
);
|
|
6229
|
+
return `Script failures: ${list.length}
|
|
6230
|
+
${lines.join("\n")}`;
|
|
6231
|
+
}
|
|
6232
|
+
function purged(result) {
|
|
6233
|
+
const { execution } = result;
|
|
6234
|
+
return [
|
|
6235
|
+
`Execution: ${execution.id}`,
|
|
6236
|
+
`Status: ${execution.status}`,
|
|
6237
|
+
`Purged: ${execution.purgedAt}`,
|
|
6238
|
+
"This run was retention-purged. Activities, files, inputs and result are gone. Do not diagnose it.",
|
|
6239
|
+
`Written to: ${result.dir}/`
|
|
6240
|
+
].join("\n");
|
|
6241
|
+
}
|
|
6242
|
+
function formatExecutionPull(result) {
|
|
6243
|
+
const { execution } = result;
|
|
6244
|
+
if (execution.purgedAt) return purged(result);
|
|
6245
|
+
const outcome = execution.executionResult;
|
|
6246
|
+
return [
|
|
6247
|
+
`Execution: ${execution.id}`,
|
|
6248
|
+
`Status: ${execution.status}`,
|
|
6249
|
+
`Workflow: ${execution.workflowName ?? execution.workflowId}`,
|
|
6250
|
+
execution.workflowVersionId ? `Workflow version: ${execution.workflowVersionId}` : null,
|
|
6251
|
+
execution.inputs ? `Inputs: ${JSON.stringify(execution.inputs)}` : null,
|
|
6252
|
+
outcome ? `Result: ${outcome.outcome}: ${outcome.reasoning}` : null,
|
|
6253
|
+
`Created: ${execution.createdAt}`,
|
|
6254
|
+
execution.terminalAt ? `Completed: ${execution.terminalAt}` : null,
|
|
6255
|
+
execution.duration != null ? `Duration: ${execution.duration.toFixed(1)}s` : null,
|
|
6256
|
+
failures(execution),
|
|
6257
|
+
filesLine(result),
|
|
6258
|
+
result.skipped.length > 0 ? `Skipped:
|
|
6259
|
+
${result.skipped.map((s) => ` - ${s}`).join("\n")}` : null,
|
|
6260
|
+
result.failed.length > 0 ? `Failed: ${result.failed.join(", ")}` : null,
|
|
6261
|
+
`Written to: ${result.dir}/`
|
|
6262
|
+
].filter(Boolean).join("\n");
|
|
6263
|
+
}
|
|
6264
|
+
|
|
6265
|
+
// src/sync/node-files.ts
|
|
6266
|
+
import { mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
6267
|
+
import { dirname as dirname2, relative, resolve, sep } from "node:path";
|
|
6268
|
+
function missing(error) {
|
|
6269
|
+
return error.code === "ENOENT";
|
|
6270
|
+
}
|
|
6271
|
+
async function walk(root, dir, out) {
|
|
6272
|
+
for (const entry of await readdir(dir, { withFileTypes: true })) {
|
|
6273
|
+
const path = resolve(dir, entry.name);
|
|
6274
|
+
if (entry.isDirectory()) await walk(root, path, out);
|
|
6275
|
+
else if (entry.isFile())
|
|
6276
|
+
out.push(relative(root, path).split(sep).join("/"));
|
|
6277
|
+
}
|
|
6278
|
+
}
|
|
6279
|
+
function nodeFiles(cwd) {
|
|
6280
|
+
const at = (path) => resolve(cwd, path);
|
|
6281
|
+
return {
|
|
6282
|
+
async read(path) {
|
|
6283
|
+
try {
|
|
6284
|
+
return new Uint8Array(await readFile(at(path)));
|
|
6285
|
+
} catch (error) {
|
|
6286
|
+
if (missing(error)) return void 0;
|
|
6287
|
+
throw error;
|
|
6288
|
+
}
|
|
6289
|
+
},
|
|
6290
|
+
async write(path, bytes) {
|
|
6291
|
+
await mkdir(dirname2(at(path)), { recursive: true });
|
|
6292
|
+
await writeFile(at(path), bytes);
|
|
6293
|
+
},
|
|
6294
|
+
async remove(path) {
|
|
6295
|
+
await rm(at(path), { force: true });
|
|
6296
|
+
},
|
|
6297
|
+
async list(dir) {
|
|
6298
|
+
const out = [];
|
|
6299
|
+
try {
|
|
6300
|
+
await walk(at(dir), at(dir), out);
|
|
6301
|
+
} catch (error) {
|
|
6302
|
+
if (!missing(error)) throw error;
|
|
6303
|
+
}
|
|
6304
|
+
return out.sort();
|
|
6305
|
+
}
|
|
6306
|
+
};
|
|
6307
|
+
}
|
|
6308
|
+
|
|
6309
|
+
// src/sync/workflow-pull.ts
|
|
6310
|
+
var CONCURRENCY = 16;
|
|
6311
|
+
function treeRequest(options) {
|
|
6312
|
+
const base = `/agents/v2/workflows/${options.workflowId}`;
|
|
6313
|
+
const path = options.versionId ? `${base}/versions/${options.versionId}/files` : `${base}/head/files`;
|
|
6314
|
+
return get(path, { contents: "all" });
|
|
6315
|
+
}
|
|
6316
|
+
function pullDir(options, tree) {
|
|
6317
|
+
if (options.dir) return options.dir;
|
|
6318
|
+
const root = workflowRoot(options.root, tree.workflowName);
|
|
6319
|
+
if (!options.versionId) return join3(root, "head");
|
|
6320
|
+
return join3(root, ".versions", workflowDirName(tree.versionId, tree.version));
|
|
6321
|
+
}
|
|
6322
|
+
async function agentFileBytes(sync, workflowId, tree, entry) {
|
|
6323
|
+
const path = `/agents/v2/workflows/${workflowId}/versions/${tree.versionId}/files/${entry.fileId}/signed-url`;
|
|
6324
|
+
const { url } = await sync.remote.json(get(path));
|
|
6325
|
+
return sync.remote.download(url);
|
|
6326
|
+
}
|
|
6327
|
+
async function entryBytes(sync, workflowId, tree, entry) {
|
|
6328
|
+
if (entry.contentBase64 !== void 0)
|
|
6329
|
+
return Buffer.from(entry.contentBase64, "base64");
|
|
6330
|
+
if (entry.content !== void 0) return Buffer.from(entry.content, "utf8");
|
|
6331
|
+
if (!entry.fileId) return void 0;
|
|
6332
|
+
return agentFileBytes(sync, workflowId, tree, entry);
|
|
6333
|
+
}
|
|
6334
|
+
async function writeEntries(sync, options, tree, dir) {
|
|
6335
|
+
const hashes = {};
|
|
6336
|
+
const skipped = [];
|
|
6337
|
+
let shared = 0;
|
|
6338
|
+
const wanted = tree.files.filter((entry) => {
|
|
6339
|
+
if (!isContained(entry.path)) skipped.push(`${entry.path} (unsafe path)`);
|
|
6340
|
+
else if (options.maxFileBytes !== void 0 && entry.size > options.maxFileBytes)
|
|
6341
|
+
skipped.push(`${entry.path} (${entry.size} bytes, over the size limit)`);
|
|
6342
|
+
else return true;
|
|
6343
|
+
return false;
|
|
6344
|
+
});
|
|
6345
|
+
for (let i = 0; i < wanted.length; i += CONCURRENCY) {
|
|
6346
|
+
await Promise.all(
|
|
6347
|
+
wanted.slice(i, i + CONCURRENCY).map(async (entry) => {
|
|
6348
|
+
const bytes = await entryBytes(sync, options.workflowId, tree, entry);
|
|
6349
|
+
if (!bytes) return void skipped.push(`${entry.path} (no content)`);
|
|
6350
|
+
await sync.files.write(join3(dir, entry.path), bytes);
|
|
6351
|
+
hashes[entry.path] = sha256(bytes);
|
|
6352
|
+
if (entry.kind === "agent_file") shared++;
|
|
6353
|
+
})
|
|
6354
|
+
);
|
|
6355
|
+
}
|
|
6356
|
+
return { hashes, skipped, shared };
|
|
6357
|
+
}
|
|
6358
|
+
async function removeStale(sync, dir, keep) {
|
|
6359
|
+
for (const path of await sync.files.list(dir)) {
|
|
6360
|
+
if (!keep.has(path) && path !== MANIFEST)
|
|
6361
|
+
await sync.files.remove(join3(dir, path));
|
|
6362
|
+
}
|
|
6363
|
+
}
|
|
6364
|
+
async function writeManifest(sync, dir, manifest) {
|
|
6365
|
+
const text2 = `${JSON.stringify(manifest, null, 2)}
|
|
6366
|
+
`;
|
|
6367
|
+
await sync.files.write(join3(dir, MANIFEST), Buffer.from(text2, "utf8"));
|
|
6368
|
+
}
|
|
6369
|
+
async function pullWorkflow(sync, options) {
|
|
6370
|
+
const tree = await sync.remote.json(treeRequest(options));
|
|
6371
|
+
const dir = pullDir(options, tree);
|
|
6372
|
+
const { hashes, skipped, shared } = await writeEntries(
|
|
6373
|
+
sync,
|
|
6374
|
+
options,
|
|
6375
|
+
tree,
|
|
6376
|
+
dir
|
|
6377
|
+
);
|
|
6378
|
+
await removeStale(sync, dir, new Set(Object.keys(hashes)));
|
|
6379
|
+
if (options.manifest !== false) {
|
|
6380
|
+
await writeManifest(sync, dir, {
|
|
6381
|
+
workflowId: options.workflowId,
|
|
6382
|
+
versionId: tree.versionId,
|
|
6383
|
+
head: !options.versionId,
|
|
6384
|
+
rev: tree.rev,
|
|
6385
|
+
files: hashes
|
|
6386
|
+
});
|
|
6387
|
+
}
|
|
6388
|
+
return {
|
|
6389
|
+
workflowId: options.workflowId,
|
|
6390
|
+
workflowName: tree.workflowName,
|
|
6391
|
+
versionId: tree.versionId,
|
|
6392
|
+
version: tree.version,
|
|
6393
|
+
rev: tree.rev,
|
|
6394
|
+
dir,
|
|
6395
|
+
written: Object.keys(hashes).length,
|
|
6396
|
+
shared,
|
|
6397
|
+
skipped
|
|
6398
|
+
};
|
|
6399
|
+
}
|
|
6400
|
+
function formatWorkflowPull(result) {
|
|
6401
|
+
const definition = result.written - result.shared;
|
|
6402
|
+
const files = result.written === 0 ? "Files: none written" : result.shared === 0 ? `Files: ${result.written} written` : `Files: ${result.written} written (${definition} workflow definition, ${result.shared} shared)`;
|
|
6403
|
+
return [
|
|
6404
|
+
`Workflow: ${result.workflowName} (${result.workflowId})`,
|
|
6405
|
+
`Workflow version: ${result.versionId}${result.version ? ` (v${result.version})` : " (draft)"}`,
|
|
6406
|
+
`Written to: ${result.dir}/`,
|
|
6407
|
+
files,
|
|
6408
|
+
result.skipped.length > 0 ? `Skipped:
|
|
6409
|
+
${result.skipped.map((s) => ` - ${s}`).join("\n")}` : null
|
|
6410
|
+
].filter(Boolean).join("\n");
|
|
6411
|
+
}
|
|
6412
|
+
|
|
6413
|
+
// src/sync/workflow-push.ts
|
|
6414
|
+
async function readManifest(sync, dir) {
|
|
6415
|
+
const bytes = await sync.files.read(join3(dir, MANIFEST));
|
|
6416
|
+
if (!bytes) {
|
|
6417
|
+
throw new UsageError(
|
|
6418
|
+
`no pulled workflow in ${dir}. Run \`asteroid workflow pull <workflow-id>\`, then push from the head folder it writes.`
|
|
6419
|
+
);
|
|
6420
|
+
}
|
|
6421
|
+
const manifest = JSON.parse(Buffer.from(bytes).toString("utf8"));
|
|
6422
|
+
if (!manifest.head) {
|
|
6423
|
+
throw new UsageError(
|
|
6424
|
+
`${dir} holds a pinned workflow version, which is read-only. Push from a pulled head.`
|
|
6425
|
+
);
|
|
6426
|
+
}
|
|
6427
|
+
return manifest;
|
|
6428
|
+
}
|
|
6429
|
+
async function localChanges(sync, dir, manifest) {
|
|
6430
|
+
const local2 = (await sync.files.list(dir)).filter(
|
|
6431
|
+
(path) => path !== MANIFEST
|
|
6432
|
+
);
|
|
6433
|
+
const writes = [];
|
|
6434
|
+
for (const path of local2) {
|
|
6435
|
+
const bytes = await sync.files.read(join3(dir, path));
|
|
6436
|
+
if (!bytes) continue;
|
|
6437
|
+
const hash = sha256(bytes);
|
|
6438
|
+
if (manifest.files[path] === hash) continue;
|
|
6439
|
+
writes.push({
|
|
6440
|
+
path,
|
|
6441
|
+
contentBase64: Buffer.from(bytes).toString("base64"),
|
|
6442
|
+
hash
|
|
6443
|
+
});
|
|
6444
|
+
}
|
|
6445
|
+
const present = new Set(local2);
|
|
6446
|
+
const deletes = Object.keys(manifest.files).filter(
|
|
6447
|
+
(path) => !present.has(path)
|
|
6448
|
+
);
|
|
6449
|
+
return { writes, deletes };
|
|
6450
|
+
}
|
|
6451
|
+
async function patch(sync, manifest, writes, deletes) {
|
|
6452
|
+
try {
|
|
6453
|
+
await sync.remote.json({
|
|
6454
|
+
method: "PATCH",
|
|
6455
|
+
path: `/agents/v2/workflows/${manifest.workflowId}/head/files`,
|
|
6456
|
+
query: new URLSearchParams(),
|
|
6457
|
+
json: { baseRev: manifest.rev, writes, deletes }
|
|
6458
|
+
});
|
|
6459
|
+
} catch (error) {
|
|
6460
|
+
if (statusOf(error) !== 409) throw error;
|
|
6461
|
+
throw new UsageError(
|
|
6462
|
+
`the workflow head changed since your pull (rev ${manifest.rev}). Copy your edits aside, run \`asteroid workflow pull ${manifest.workflowId}\`, reapply them, then push again.`
|
|
6463
|
+
);
|
|
6464
|
+
}
|
|
6465
|
+
}
|
|
6466
|
+
async function refresh2(sync, dir, manifest, pushed) {
|
|
6467
|
+
const tree = await sync.remote.json(
|
|
6468
|
+
get(`/agents/v2/workflows/${manifest.workflowId}/head/files`)
|
|
6469
|
+
);
|
|
6470
|
+
const files = {};
|
|
6471
|
+
for (const entry of tree.files) {
|
|
6472
|
+
if (entry.content !== void 0) {
|
|
6473
|
+
const bytes = Buffer.from(entry.content, "utf8");
|
|
6474
|
+
files[entry.path] = sha256(bytes);
|
|
6475
|
+
if (pushed.get(entry.path) !== files[entry.path])
|
|
6476
|
+
await sync.files.write(join3(dir, entry.path), bytes);
|
|
6477
|
+
} else {
|
|
6478
|
+
const local2 = await sync.files.read(join3(dir, entry.path));
|
|
6479
|
+
const hash = local2 ? sha256(local2) : pushed.get(entry.path) ?? entry.checksum;
|
|
6480
|
+
if (hash) files[entry.path] = hash;
|
|
6481
|
+
}
|
|
6482
|
+
}
|
|
6483
|
+
for (const path of await sync.files.list(dir)) {
|
|
6484
|
+
if (path !== MANIFEST && !(path in files))
|
|
6485
|
+
await sync.files.remove(join3(dir, path));
|
|
6486
|
+
}
|
|
6487
|
+
const next = { ...manifest, versionId: tree.versionId, rev: tree.rev, files };
|
|
6488
|
+
await writeManifest(sync, dir, next);
|
|
6489
|
+
return next;
|
|
6490
|
+
}
|
|
6491
|
+
async function pushWorkflow(sync, dir) {
|
|
6492
|
+
const manifest = await readManifest(sync, dir);
|
|
6493
|
+
const { writes, deletes } = await localChanges(sync, dir, manifest);
|
|
6494
|
+
const result = {
|
|
6495
|
+
workflowId: manifest.workflowId,
|
|
6496
|
+
dir,
|
|
6497
|
+
written: writes.map((w) => w.path),
|
|
6498
|
+
deleted: deletes
|
|
6499
|
+
};
|
|
6500
|
+
if (writes.length === 0 && deletes.length === 0) {
|
|
6501
|
+
return { ...result, rev: manifest.rev, versionId: manifest.versionId };
|
|
6502
|
+
}
|
|
6503
|
+
await patch(
|
|
6504
|
+
sync,
|
|
6505
|
+
manifest,
|
|
6506
|
+
writes.map(({ path, contentBase64 }) => ({ path, contentBase64 })),
|
|
6507
|
+
deletes
|
|
6508
|
+
);
|
|
6509
|
+
const next = await refresh2(
|
|
6510
|
+
sync,
|
|
6511
|
+
dir,
|
|
6512
|
+
manifest,
|
|
6513
|
+
new Map(writes.map((w) => [w.path, w.hash]))
|
|
6514
|
+
);
|
|
6515
|
+
return { ...result, rev: next.rev, versionId: next.versionId };
|
|
6516
|
+
}
|
|
6517
|
+
function formatWorkflowPush(result) {
|
|
6518
|
+
if (result.written.length === 0 && result.deleted.length === 0) {
|
|
6519
|
+
return `Nothing to push: ${result.dir}/ matches the head you pulled (rev ${result.rev}).`;
|
|
6520
|
+
}
|
|
6521
|
+
return [
|
|
6522
|
+
`Pushed to workflow ${result.workflowId}: ${result.written.length} written, ${result.deleted.length} deleted.`,
|
|
6523
|
+
...result.written.map((path) => ` + ${path}`),
|
|
6524
|
+
...result.deleted.map((path) => ` - ${path}`),
|
|
6525
|
+
`Head is now rev ${result.rev} (version ${result.versionId}).`
|
|
6526
|
+
].join("\n");
|
|
6527
|
+
}
|
|
6528
|
+
|
|
6529
|
+
// src/sync/run.ts
|
|
6530
|
+
function flag(parsed, name) {
|
|
6531
|
+
return parsed.flags.get(name)?.at(-1);
|
|
6532
|
+
}
|
|
6533
|
+
function integer(parsed, name) {
|
|
6534
|
+
const raw = flag(parsed, name);
|
|
6535
|
+
if (raw === void 0) return void 0;
|
|
6536
|
+
if (!/^\d+$/.test(raw))
|
|
6537
|
+
throw new UsageError(`--${name} must be a whole number`);
|
|
6538
|
+
return Number(raw);
|
|
6539
|
+
}
|
|
6540
|
+
function executionOptions(parsed) {
|
|
6541
|
+
const summaryOnly = flag(parsed, "summary-only") === "true";
|
|
6542
|
+
const glob = flag(parsed, "files");
|
|
6543
|
+
const dir = flag(parsed, "dir");
|
|
6544
|
+
return {
|
|
6545
|
+
executionId: parsed.positionals[0],
|
|
6546
|
+
activities: !summaryOnly,
|
|
6547
|
+
files: !summaryOnly && glob !== "false",
|
|
6548
|
+
glob: glob === "false" ? void 0 : glob,
|
|
6549
|
+
maxFileBytes: integer(parsed, "max-file-size"),
|
|
6550
|
+
dir: dir ? () => dir : void 0
|
|
6551
|
+
};
|
|
6552
|
+
}
|
|
6553
|
+
async function execute(command, parsed, sync, deps) {
|
|
6554
|
+
const root = deps.root ?? "";
|
|
6555
|
+
switch (command.command) {
|
|
6556
|
+
case "workflow pull": {
|
|
6557
|
+
const result = await pullWorkflow(sync, {
|
|
6558
|
+
workflowId: parsed.positionals[0],
|
|
6559
|
+
versionId: flag(parsed, "version-id"),
|
|
6560
|
+
dir: flag(parsed, "dir"),
|
|
6561
|
+
root,
|
|
6562
|
+
...deps.workflowPull
|
|
6563
|
+
});
|
|
6564
|
+
return { result, text: formatWorkflowPull(result) };
|
|
6565
|
+
}
|
|
6566
|
+
case "workflow push": {
|
|
6567
|
+
const result = await pushWorkflow(sync, flag(parsed, "dir") ?? ".");
|
|
6568
|
+
return { result, text: formatWorkflowPush(result) };
|
|
6569
|
+
}
|
|
6570
|
+
case "execution pull": {
|
|
6571
|
+
const result = await pullExecution(sync, {
|
|
6572
|
+
...executionOptions(parsed),
|
|
6573
|
+
root,
|
|
6574
|
+
...deps.executionPull
|
|
6575
|
+
});
|
|
6576
|
+
return { result, text: formatExecutionPull(result) };
|
|
6577
|
+
}
|
|
6578
|
+
default:
|
|
6579
|
+
throw new UsageError(`unknown command ${command.command}`);
|
|
6580
|
+
}
|
|
6581
|
+
}
|
|
6582
|
+
async function runSync(command, parsed, deps) {
|
|
6583
|
+
const sync = {
|
|
6584
|
+
remote: deps.remote,
|
|
6585
|
+
files: deps.files ?? nodeFiles(deps.cwd)
|
|
6586
|
+
};
|
|
6587
|
+
const { result, text: text2 } = await execute(command, parsed, sync, deps);
|
|
6588
|
+
deps.stdout(`${parsed.json ? JSON.stringify(result, null, 2) : text2}
|
|
6589
|
+
`);
|
|
6590
|
+
return 0;
|
|
6591
|
+
}
|
|
6592
|
+
|
|
5796
6593
|
// src/run.ts
|
|
5797
6594
|
function rejectOrgWithApiKey(deps, flagOrg) {
|
|
5798
6595
|
if (deps.env.apiKey && (flagOrg || deps.env.org)) {
|
|
@@ -5832,25 +6629,30 @@ async function credentialOrganization(deps, auth) {
|
|
|
5832
6629
|
);
|
|
5833
6630
|
return response.json?.organizationId;
|
|
5834
6631
|
}
|
|
5835
|
-
async function
|
|
5836
|
-
const needsOrganization = command.body?.credentialOrganization === true;
|
|
5837
|
-
let request = needsOrganization ? void 0 : await buildRequest(command, parsed);
|
|
6632
|
+
async function authFor(parsed, deps) {
|
|
5838
6633
|
rejectOrgWithApiKey(deps, parsed.org);
|
|
5839
6634
|
const config = loadConfig(deps.env);
|
|
5840
|
-
|
|
6635
|
+
return resolveAuth(
|
|
5841
6636
|
deps.env,
|
|
5842
6637
|
config,
|
|
5843
6638
|
requestedOrg(parsed.org, deps.env, deps.cwd, config),
|
|
5844
6639
|
deps.fetch
|
|
5845
6640
|
);
|
|
6641
|
+
}
|
|
6642
|
+
function announce(command, auth, deps) {
|
|
6643
|
+
if (command.readOnly) return;
|
|
6644
|
+
const org = auth.kind === "api-key" ? "the API key's organization" : auth.org ? orgLabel(auth.org) : "your default organization";
|
|
6645
|
+
deps.stderr(`Acting in ${org}.
|
|
6646
|
+
`);
|
|
6647
|
+
}
|
|
6648
|
+
async function runCommand(command, parsed, deps) {
|
|
6649
|
+
const needsOrganization = command.body?.credentialOrganization === true;
|
|
6650
|
+
let request = needsOrganization ? void 0 : await buildRequest(command, parsed);
|
|
6651
|
+
const auth = await authFor(parsed, deps);
|
|
5846
6652
|
request ??= await buildRequest(command, parsed, {
|
|
5847
6653
|
organizationId: await credentialOrganization(deps, auth)
|
|
5848
6654
|
});
|
|
5849
|
-
|
|
5850
|
-
const org = auth.kind === "api-key" ? "the API key's organization" : auth.org ? orgLabel(auth.org) : "your default organization";
|
|
5851
|
-
deps.stderr(`Acting in ${org}.
|
|
5852
|
-
`);
|
|
5853
|
-
}
|
|
6655
|
+
announce(command, auth, deps);
|
|
5854
6656
|
const response = await send(
|
|
5855
6657
|
deps.env.apiUrl,
|
|
5856
6658
|
auth,
|
|
@@ -5878,7 +6680,10 @@ async function runCommand(command, parsed, deps) {
|
|
|
5878
6680
|
}
|
|
5879
6681
|
if (response.json !== void 0) {
|
|
5880
6682
|
deps.stdout(
|
|
5881
|
-
`${parsed.json ? JSON.stringify(response.json, null, 2) : render(response.json, {
|
|
6683
|
+
`${parsed.json ? JSON.stringify(response.json, null, 2) : render(response.json, {
|
|
6684
|
+
styled: deps.styled,
|
|
6685
|
+
full: !deps.stdoutIsTTY
|
|
6686
|
+
})}
|
|
5882
6687
|
`
|
|
5883
6688
|
);
|
|
5884
6689
|
return 0;
|
|
@@ -5887,11 +6692,22 @@ async function runCommand(command, parsed, deps) {
|
|
|
5887
6692
|
`);
|
|
5888
6693
|
return 0;
|
|
5889
6694
|
}
|
|
5890
|
-
|
|
6695
|
+
function nodeRemote(apiUrl, auth, fetchImpl) {
|
|
6696
|
+
return {
|
|
6697
|
+
json: async (request) => (await send(apiUrl, auth, request, false, fetchImpl)).json,
|
|
6698
|
+
async download(url) {
|
|
6699
|
+
const response = await fetchImpl(url);
|
|
6700
|
+
if (!response.ok)
|
|
6701
|
+
throw new Error(`download failed: HTTP ${response.status}`);
|
|
6702
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
6703
|
+
}
|
|
6704
|
+
};
|
|
6705
|
+
}
|
|
6706
|
+
var DESCRIPTION = "The Asteroid CLI. Sign in with `asteroid login`, or set ASTEROID_API_KEY.\nAt 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.";
|
|
5891
6707
|
async function run(argv2, deps) {
|
|
5892
6708
|
try {
|
|
5893
6709
|
const invocation = await parseInvocation(argv2, {
|
|
5894
|
-
commands:
|
|
6710
|
+
commands: ALL_COMMANDS,
|
|
5895
6711
|
builtins: BUILTINS,
|
|
5896
6712
|
version: VERSION,
|
|
5897
6713
|
description: DESCRIPTION
|
|
@@ -5904,6 +6720,14 @@ async function run(argv2, deps) {
|
|
|
5904
6720
|
if (invocation.kind === "builtin") {
|
|
5905
6721
|
return await runBuiltin(invocation.name, invocation.parsed, deps);
|
|
5906
6722
|
}
|
|
6723
|
+
if (invocation.command.local) {
|
|
6724
|
+
const auth = await authFor(invocation.parsed, deps);
|
|
6725
|
+
announce(invocation.command, auth, deps);
|
|
6726
|
+
return await runSync(invocation.command, invocation.parsed, {
|
|
6727
|
+
...deps,
|
|
6728
|
+
remote: nodeRemote(deps.env.apiUrl, auth, deps.fetch)
|
|
6729
|
+
});
|
|
6730
|
+
}
|
|
5907
6731
|
return await runCommand(invocation.command, invocation.parsed, deps);
|
|
5908
6732
|
} catch (error) {
|
|
5909
6733
|
if (error instanceof UsageError) {
|
|
@@ -5959,7 +6783,7 @@ function openUrl(url) {
|
|
|
5959
6783
|
}
|
|
5960
6784
|
var argv = process.argv.slice(2);
|
|
5961
6785
|
if (argv[0] === "complete") {
|
|
5962
|
-
await runCompletion(argv, { commands:
|
|
6786
|
+
await runCompletion(argv, { commands: ALL_COMMANDS, builtins: BUILTINS });
|
|
5963
6787
|
process.exit(0);
|
|
5964
6788
|
}
|
|
5965
6789
|
var code = await run(argv, {
|