@asteroidai/cli 1.9.4 → 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 +1127 -307
- package/dist/core.js +861 -52
- 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/program.test.ts +19 -0
- package/src/program.ts +15 -1
- package/src/run.test.ts +35 -2
- package/src/run.ts +53 -19
- 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,226 +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, options = {}) {
|
|
52
|
-
if (value2 === null || value2 === void 0) return "";
|
|
53
|
-
const line = text(value2).replace(/\s+/g, " ");
|
|
54
|
-
if (options.full || line.length <= MAX_CELL) return line;
|
|
55
|
-
return `${line.slice(0, MAX_CELL - 1)}\u2026`;
|
|
56
|
-
}
|
|
57
|
-
function columns(rows, options) {
|
|
58
|
-
const scalarKeys = [];
|
|
59
|
-
for (const row of rows) {
|
|
60
|
-
for (const [key, value2] of Object.entries(row)) {
|
|
61
|
-
if (isScalar(value2) && !HIDDEN.test(key.split(".").at(-1) ?? key) && !scalarKeys.includes(key))
|
|
62
|
-
scalarKeys.push(key);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
const ordered = [
|
|
66
|
-
...PREFERRED.filter((key) => scalarKeys.includes(key)),
|
|
67
|
-
...scalarKeys.filter((key) => !PREFERRED.includes(key))
|
|
68
|
-
];
|
|
69
|
-
return options.full ? ordered : ordered.slice(0, MAX_COLUMNS);
|
|
70
|
-
}
|
|
71
|
-
function style(format, value2, options) {
|
|
72
|
-
return options.styled ? styleText(format, value2, { validateStream: false }) : value2;
|
|
73
|
-
}
|
|
74
|
-
function flatten(row) {
|
|
75
|
-
const flat = {};
|
|
76
|
-
for (const [key, value2] of Object.entries(row)) {
|
|
77
|
-
if (!isObject(value2)) {
|
|
78
|
-
flat[key] = value2;
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
for (const [inner, v] of Object.entries(value2))
|
|
82
|
-
flat[`${key}.${inner}`] = v;
|
|
83
|
-
}
|
|
84
|
-
return flat;
|
|
85
|
-
}
|
|
86
|
-
function table(input, options = {}) {
|
|
87
|
-
if (input.length === 0) return "No results.";
|
|
88
|
-
const rows = input.map(flatten);
|
|
89
|
-
const keys = columns(rows, options);
|
|
90
|
-
if (keys.length === 0) return rows.map((row) => text(row)).join("\n");
|
|
91
|
-
const cells = rows.map((row) => keys.map((key) => cell(row[key], options)));
|
|
92
|
-
if (options.styled) {
|
|
93
|
-
const grid = new Table({
|
|
94
|
-
head: keys.map((key) => style("bold", key, options)),
|
|
95
|
-
style: { head: [], border: [] }
|
|
96
|
-
});
|
|
97
|
-
grid.push(...cells);
|
|
98
|
-
return grid.toString();
|
|
99
|
-
}
|
|
100
|
-
const widths = keys.map(
|
|
101
|
-
(key, i) => Math.max(key.length, ...cells.map((r) => r[i].length))
|
|
102
|
-
);
|
|
103
|
-
const line = (values) => values.map((v, i) => v.padEnd(widths[i])).join(" ").trimEnd();
|
|
104
|
-
return [line(keys.map((k) => k.toUpperCase())), ...cells.map(line)].join(
|
|
105
|
-
"\n"
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
function summary(value2, options) {
|
|
109
|
-
if (Array.isArray(value2))
|
|
110
|
-
return `[${value2.length} item${value2.length === 1 ? "" : "s"}]`;
|
|
111
|
-
if (value2 && typeof value2 === "object") return cell(value2, options);
|
|
112
|
-
return text(value2);
|
|
113
|
-
}
|
|
114
|
-
function record(value2, options) {
|
|
115
|
-
const width = Math.max(...Object.keys(value2).map((key) => key.length));
|
|
116
|
-
const label = (key) => style("bold", key.padEnd(width), options);
|
|
117
|
-
return Object.entries(value2).map(([key, v]) => {
|
|
118
|
-
if (Array.isArray(v) && v.length > 0 && v.every(isObject)) {
|
|
119
|
-
return `${style("bold", key, options)}:
|
|
120
|
-
${indent(table(v, options))}`;
|
|
121
|
-
}
|
|
122
|
-
if (Array.isArray(v) && v.length > 0 && v.every(isScalar)) {
|
|
123
|
-
return `${label(key)} ${v.map(text).join(`
|
|
124
|
-
${" ".repeat(width + 2)}`)}`;
|
|
125
|
-
}
|
|
126
|
-
if (isObject(v) && Object.keys(v).length > 0) {
|
|
127
|
-
return `${style("bold", key, options)}:
|
|
128
|
-
${indent(record(v, options))}`;
|
|
129
|
-
}
|
|
130
|
-
return `${label(key)} ${isScalar(v) ? text(v) : summary(v, options)}`;
|
|
131
|
-
}).join("\n");
|
|
132
|
-
}
|
|
133
|
-
function render(value2, options = {}) {
|
|
134
|
-
if (value2 === void 0) return "";
|
|
135
|
-
if (Array.isArray(value2)) {
|
|
136
|
-
return value2.every(isObject) ? table(value2, options) : value2.map(text).join("\n");
|
|
137
|
-
}
|
|
138
|
-
if (isObject(value2)) {
|
|
139
|
-
const row = value2;
|
|
140
|
-
if (Array.isArray(row.items)) {
|
|
141
|
-
const rest = Object.entries(row).filter(
|
|
142
|
-
([k, v]) => k !== "items" && isScalar(v)
|
|
143
|
-
);
|
|
144
|
-
const footer = rest.length ? `
|
|
145
|
-
|
|
146
|
-
${style("dim", rest.map(([k, v]) => `${k}: ${text(v)}`).join(" "), options)}` : "";
|
|
147
|
-
return `${table(row.items, options)}${footer}`;
|
|
148
|
-
}
|
|
149
|
-
return record(row, options);
|
|
150
|
-
}
|
|
151
|
-
return text(value2);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// src/config.ts
|
|
155
|
-
var DEFAULT_API_URL = "https://odyssey.asteroid.ai";
|
|
156
|
-
var DEFAULT_WORKOS_CLIENT_ID = "client_01K3NXPE8X6EJZXZSYCNT5M4P0";
|
|
157
|
-
var DEFAULT_WORKOS_API_URL = "https://api.workos.com";
|
|
158
|
-
function readEnv(env = process.env) {
|
|
159
|
-
return {
|
|
160
|
-
apiUrl: (env.ASTEROID_API_URL || DEFAULT_API_URL).replace(/\/+$/, ""),
|
|
161
|
-
apiKey: env.ASTEROID_API_KEY || void 0,
|
|
162
|
-
org: env.ASTEROID_ORG || void 0,
|
|
163
|
-
workosClientId: env.ASTEROID_WORKOS_CLIENT_ID || DEFAULT_WORKOS_CLIENT_ID,
|
|
164
|
-
workosApiUrl: (env.ASTEROID_WORKOS_API_URL || DEFAULT_WORKOS_API_URL).replace(/\/+$/, ""),
|
|
165
|
-
configDir: env.ASTEROID_CONFIG_DIR || envPaths("asteroid", { suffix: "" }).config
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
function readJson(path, fallback) {
|
|
169
|
-
if (!existsSync(path)) return fallback;
|
|
170
|
-
try {
|
|
171
|
-
return JSON.parse(readFileSync(path, "utf8"));
|
|
172
|
-
} catch {
|
|
173
|
-
return fallback;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
function writePrivateJson(path, value2) {
|
|
177
|
-
mkdirSync(dirname(path), { recursive: true, mode: 448 });
|
|
178
|
-
const temp = `${path}.${process.pid}.tmp`;
|
|
179
|
-
writeFileSync(temp, `${JSON.stringify(value2, null, 2)}
|
|
180
|
-
`, { mode: 384 });
|
|
181
|
-
chmodSync(temp, 384);
|
|
182
|
-
renameSync(temp, path);
|
|
183
|
-
}
|
|
184
|
-
function loadConfig(env) {
|
|
185
|
-
return readJson(join(env.configDir, "config.json"), {});
|
|
186
|
-
}
|
|
187
|
-
function saveConfig(env, config) {
|
|
188
|
-
writePrivateJson(join(env.configDir, "config.json"), config);
|
|
189
|
-
}
|
|
190
|
-
function repoOrg(cwd) {
|
|
191
|
-
let dir = cwd;
|
|
192
|
-
for (; ; ) {
|
|
193
|
-
const file = join(dir, ".asteroid", "org");
|
|
194
|
-
if (existsSync(file)) return readFileSync(file, "utf8").trim() || void 0;
|
|
195
|
-
const parent = dirname(dir);
|
|
196
|
-
if (parent === dir) return void 0;
|
|
197
|
-
dir = parent;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
function requestedOrg(flag, env, cwd, config) {
|
|
201
|
-
return flag ?? env.org ?? repoOrg(cwd) ?? config.org?.workosId;
|
|
202
|
-
}
|
|
203
|
-
function findOrg(ref, orgs2) {
|
|
204
|
-
const lower = ref.toLowerCase();
|
|
205
|
-
return orgs2.find(
|
|
206
|
-
(org) => org.workosId.toLowerCase() === lower || org.id?.toLowerCase() === lower
|
|
207
|
-
) ?? orgs2.find((org) => org.name?.toLowerCase() === lower) ?? (/^org_[0-9a-z]+$/i.test(ref) ? { workosId: ref } : void 0);
|
|
208
|
-
}
|
|
209
|
-
function mergeOrgs(known = [], found = []) {
|
|
210
|
-
const byId = new Map(known.map((org) => [org.workosId, org]));
|
|
211
|
-
for (const org of found) {
|
|
212
|
-
const merged = { ...byId.get(org.workosId) };
|
|
213
|
-
for (const [key, value2] of Object.entries(org))
|
|
214
|
-
if (value2 !== void 0) Object.assign(merged, { [key]: value2 });
|
|
215
|
-
byId.set(org.workosId, merged);
|
|
216
|
-
}
|
|
217
|
-
return [...byId.values()].sort(
|
|
218
|
-
(a, b) => (a.name ?? a.workosId).localeCompare(b.name ?? b.workosId)
|
|
219
|
-
);
|
|
220
|
-
}
|
|
221
|
-
function orgLabel(org) {
|
|
222
|
-
const id = clean(org.id ?? org.workosId);
|
|
223
|
-
return org.name ? `${clean(org.name)} (${id})` : id;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
6
|
// src/generated/commands.ts
|
|
227
7
|
var COMMANDS = [
|
|
228
8
|
{
|
|
@@ -4726,42 +4506,370 @@ var COMMANDS = [
|
|
|
4726
4506
|
}
|
|
4727
4507
|
];
|
|
4728
4508
|
|
|
4729
|
-
// src/
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
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
|
|
4734
4524
|
};
|
|
4735
|
-
|
|
4736
|
-
// src/reference.ts
|
|
4737
|
-
var BUILTINS = [
|
|
4738
|
-
{
|
|
4739
|
-
command: "login",
|
|
4740
|
-
usage: "asteroid login",
|
|
4741
|
-
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.",
|
|
4742
|
-
args: 0
|
|
4743
|
-
},
|
|
4744
|
-
{
|
|
4745
|
-
command: "logout",
|
|
4746
|
-
usage: "asteroid logout",
|
|
4747
|
-
description: "Forget the stored sign-in and cached tokens.",
|
|
4748
|
-
args: 0
|
|
4749
|
-
},
|
|
4525
|
+
var SYNC_COMMANDS = [
|
|
4750
4526
|
{
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
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
|
+
]
|
|
4755
4549
|
},
|
|
4756
4550
|
{
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
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
|
+
]
|
|
4762
4565
|
},
|
|
4763
4566
|
{
|
|
4764
|
-
|
|
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",
|
|
4765
4873
|
usage: "asteroid complete <zsh|bash|fish|powershell>",
|
|
4766
4874
|
arg: "shell",
|
|
4767
4875
|
description: "Print a shell completion script. For zsh: `asteroid complete zsh > ~/.zfunc/_asteroid`.",
|
|
@@ -4794,12 +4902,12 @@ var DOTTED = /^--([a-z0-9-]+\.[a-z0-9.-]+)(?:=(.*))?$/;
|
|
|
4794
4902
|
function collect(value2, previous) {
|
|
4795
4903
|
return [...previous ?? [], value2];
|
|
4796
4904
|
}
|
|
4797
|
-
function optionText(
|
|
4798
|
-
const text2 =
|
|
4905
|
+
function optionText(option2) {
|
|
4906
|
+
const text2 = option2.description?.replace(/\s+/g, " ").trim() ?? "";
|
|
4799
4907
|
const parts = [text2 && !/[.!?]$/.test(text2) ? `${text2}.` : text2];
|
|
4800
|
-
if (
|
|
4801
|
-
if (
|
|
4802
|
-
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.");
|
|
4803
4911
|
return parts.filter(Boolean).join(" ");
|
|
4804
4912
|
}
|
|
4805
4913
|
function splitDotted(argv2) {
|
|
@@ -4826,18 +4934,20 @@ function group(parent, name, groups, path) {
|
|
|
4826
4934
|
return created;
|
|
4827
4935
|
}
|
|
4828
4936
|
function leafOptions(leaf, command) {
|
|
4829
|
-
for (const
|
|
4830
|
-
if (
|
|
4831
|
-
leaf.option(`--${
|
|
4832
|
-
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.`);
|
|
4833
4941
|
continue;
|
|
4834
4942
|
}
|
|
4835
|
-
const placeholder =
|
|
4943
|
+
const placeholder = option2.enum ? option2.enum.join("|") : option2.type.replace("[]", "");
|
|
4836
4944
|
leaf.option(
|
|
4837
|
-
`--${
|
|
4838
|
-
optionText(
|
|
4945
|
+
`--${option2.flag} <${placeholder}>`,
|
|
4946
|
+
optionText(option2),
|
|
4839
4947
|
collect
|
|
4840
4948
|
);
|
|
4949
|
+
if (option2.negatable)
|
|
4950
|
+
leaf.option(`--no-${option2.flag}`, `Skip ${option2.flag}.`);
|
|
4841
4951
|
}
|
|
4842
4952
|
for (const file of command.body?.files ?? []) {
|
|
4843
4953
|
leaf.option(`--${file} <path>`, "File to upload. Repeatable.", collect);
|
|
@@ -4847,11 +4957,11 @@ function toParsed(leaf, positionals, dotted) {
|
|
|
4847
4957
|
const globals = leaf.optsWithGlobals();
|
|
4848
4958
|
const own = leaf.opts();
|
|
4849
4959
|
const flags = new Map(dotted);
|
|
4850
|
-
for (const
|
|
4851
|
-
const
|
|
4852
|
-
const value2 = own[
|
|
4853
|
-
if (!
|
|
4854
|
-
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)]);
|
|
4855
4965
|
}
|
|
4856
4966
|
return {
|
|
4857
4967
|
positionals,
|
|
@@ -4864,7 +4974,11 @@ function toParsed(leaf, positionals, dotted) {
|
|
|
4864
4974
|
}
|
|
4865
4975
|
function defineCommands(root, options, dotted = /* @__PURE__ */ new Map(), onInvocation = () => void 0) {
|
|
4866
4976
|
if (options.description) root.description(options.description);
|
|
4867
|
-
if (options.version)
|
|
4977
|
+
if (options.version)
|
|
4978
|
+
root.addHelpText(
|
|
4979
|
+
"after",
|
|
4980
|
+
"\nRun `asteroid --version` to print the CLI version."
|
|
4981
|
+
);
|
|
4868
4982
|
for (const [flags, text2] of GLOBAL_FLAGS) {
|
|
4869
4983
|
if (!flags.startsWith("-h")) root.option(flags, text2);
|
|
4870
4984
|
}
|
|
@@ -4926,8 +5040,8 @@ ${notes.join("\n")}`);
|
|
|
4926
5040
|
);
|
|
4927
5041
|
}
|
|
4928
5042
|
const unknownDotted = [...dotted.keys()].filter(
|
|
4929
|
-
(
|
|
4930
|
-
(f) =>
|
|
5043
|
+
(flag2) => !(command.body?.fields ?? []).some(
|
|
5044
|
+
(f) => flag2.startsWith(`${f.flag}.`)
|
|
4931
5045
|
)
|
|
4932
5046
|
);
|
|
4933
5047
|
if (unknownDotted.length > 0) {
|
|
@@ -4948,6 +5062,15 @@ async function parseInvocation(argv2, options) {
|
|
|
4948
5062
|
let stdout = "";
|
|
4949
5063
|
let stderr = "";
|
|
4950
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
|
+
}
|
|
4951
5074
|
const { argv: rest, dotted } = splitDotted(argv2);
|
|
4952
5075
|
const root = new Command("asteroid").exitOverride().helpCommand(false).configureOutput({
|
|
4953
5076
|
writeOut: (text2) => stdout += text2,
|
|
@@ -5330,7 +5453,7 @@ var ApiError = class extends Error {
|
|
|
5330
5453
|
status;
|
|
5331
5454
|
body;
|
|
5332
5455
|
};
|
|
5333
|
-
var VERSION = "1.9.
|
|
5456
|
+
var VERSION = "1.9.11";
|
|
5334
5457
|
function problem(status, body, fallback) {
|
|
5335
5458
|
if (body && typeof body === "object") {
|
|
5336
5459
|
const record2 = body;
|
|
@@ -5603,34 +5726,34 @@ import { basename } from "node:path";
|
|
|
5603
5726
|
function camel(segment) {
|
|
5604
5727
|
return segment.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
|
|
5605
5728
|
}
|
|
5606
|
-
function scalar(
|
|
5607
|
-
const type =
|
|
5608
|
-
if (
|
|
5729
|
+
function scalar(option2, raw) {
|
|
5730
|
+
const type = option2.type.replace("[]", "");
|
|
5731
|
+
if (option2.enum && !option2.enum.includes(raw)) {
|
|
5609
5732
|
throw new UsageError(
|
|
5610
|
-
`--${
|
|
5733
|
+
`--${option2.flag} must be one of ${option2.enum.join(", ")}`
|
|
5611
5734
|
);
|
|
5612
5735
|
}
|
|
5613
5736
|
if (type === "integer" || type === "number") {
|
|
5614
5737
|
const value2 = Number(raw);
|
|
5615
5738
|
if (raw.trim() === "" || Number.isNaN(value2) || type === "integer" && !Number.isInteger(value2)) {
|
|
5616
5739
|
throw new UsageError(
|
|
5617
|
-
`--${
|
|
5740
|
+
`--${option2.flag} must be ${type === "integer" ? "an integer" : "a number"}`
|
|
5618
5741
|
);
|
|
5619
5742
|
}
|
|
5620
5743
|
return value2;
|
|
5621
5744
|
}
|
|
5622
5745
|
if (type === "boolean") {
|
|
5623
5746
|
if (raw !== "true" && raw !== "false") {
|
|
5624
|
-
throw new UsageError(`--${
|
|
5747
|
+
throw new UsageError(`--${option2.flag} must be true or false`);
|
|
5625
5748
|
}
|
|
5626
5749
|
return raw === "true";
|
|
5627
5750
|
}
|
|
5628
|
-
if (type === "json") return parseJson(raw, `--${
|
|
5751
|
+
if (type === "json") return parseJson(raw, `--${option2.flag}`);
|
|
5629
5752
|
return raw;
|
|
5630
5753
|
}
|
|
5631
|
-
function value(
|
|
5632
|
-
if (
|
|
5633
|
-
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));
|
|
5634
5757
|
}
|
|
5635
5758
|
var nodeReader = {
|
|
5636
5759
|
text: async (path) => readFileSync2(path, "utf8"),
|
|
@@ -5642,12 +5765,12 @@ async function readInput(raw, io) {
|
|
|
5642
5765
|
return raw;
|
|
5643
5766
|
}
|
|
5644
5767
|
async function resolveInputs(command, parsed, io) {
|
|
5645
|
-
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);
|
|
5646
5769
|
const flags = new Map(parsed.flags);
|
|
5647
|
-
for (const
|
|
5648
|
-
const raws = flags.get(
|
|
5770
|
+
for (const flag2 of jsonFlags) {
|
|
5771
|
+
const raws = flags.get(flag2);
|
|
5649
5772
|
if (raws)
|
|
5650
|
-
flags.set(
|
|
5773
|
+
flags.set(flag2, await Promise.all(raws.map((raw) => readInput(raw, io))));
|
|
5651
5774
|
}
|
|
5652
5775
|
const body = parsed.body === void 0 ? void 0 : await readInput(parsed.body, io);
|
|
5653
5776
|
return { ...parsed, flags, body };
|
|
@@ -5697,18 +5820,18 @@ async function buildRequest(command, input, context = {}) {
|
|
|
5697
5820
|
});
|
|
5698
5821
|
const flags = new Map(parsed.flags);
|
|
5699
5822
|
const query = new URLSearchParams();
|
|
5700
|
-
for (const
|
|
5701
|
-
const raws = flags.get(
|
|
5702
|
-
flags.delete(
|
|
5823
|
+
for (const option2 of command.query) {
|
|
5824
|
+
const raws = flags.get(option2.flag);
|
|
5825
|
+
flags.delete(option2.flag);
|
|
5703
5826
|
if (!raws) {
|
|
5704
|
-
if (
|
|
5827
|
+
if (option2.required) throw new UsageError(`--${option2.flag} is required`);
|
|
5705
5828
|
continue;
|
|
5706
5829
|
}
|
|
5707
|
-
const parsedValue = value(
|
|
5830
|
+
const parsedValue = value(option2, raws);
|
|
5708
5831
|
const values = Array.isArray(parsedValue) ? parsedValue : [parsedValue];
|
|
5709
|
-
if (
|
|
5710
|
-
query.append(
|
|
5711
|
-
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));
|
|
5712
5835
|
}
|
|
5713
5836
|
const built = { method: command.method, path, query };
|
|
5714
5837
|
const body = command.body;
|
|
@@ -5758,11 +5881,11 @@ async function buildRequest(command, input, context = {}) {
|
|
|
5758
5881
|
flags.delete(field.flag);
|
|
5759
5882
|
json[field.name] = value(field, raws);
|
|
5760
5883
|
}
|
|
5761
|
-
for (const [
|
|
5762
|
-
const [head, ...rest] =
|
|
5884
|
+
for (const [flag2, raws] of [...flags]) {
|
|
5885
|
+
const [head, ...rest] = flag2.split(".");
|
|
5763
5886
|
const field = body.fields.find((f) => f.flag === head);
|
|
5764
5887
|
if (!field || rest.length === 0) continue;
|
|
5765
|
-
flags.delete(
|
|
5888
|
+
flags.delete(flag2);
|
|
5766
5889
|
const current = json[field.name];
|
|
5767
5890
|
json[field.name] = typeof current === "object" && current !== null ? current : {};
|
|
5768
5891
|
setPath(json, [field.name, ...rest.map(camel)], loose(raws.at(-1)));
|
|
@@ -5794,6 +5917,679 @@ function rejectUnknown(flags) {
|
|
|
5794
5917
|
}
|
|
5795
5918
|
}
|
|
5796
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
|
+
|
|
5797
6593
|
// src/run.ts
|
|
5798
6594
|
function rejectOrgWithApiKey(deps, flagOrg) {
|
|
5799
6595
|
if (deps.env.apiKey && (flagOrg || deps.env.org)) {
|
|
@@ -5833,25 +6629,30 @@ async function credentialOrganization(deps, auth) {
|
|
|
5833
6629
|
);
|
|
5834
6630
|
return response.json?.organizationId;
|
|
5835
6631
|
}
|
|
5836
|
-
async function
|
|
5837
|
-
const needsOrganization = command.body?.credentialOrganization === true;
|
|
5838
|
-
let request = needsOrganization ? void 0 : await buildRequest(command, parsed);
|
|
6632
|
+
async function authFor(parsed, deps) {
|
|
5839
6633
|
rejectOrgWithApiKey(deps, parsed.org);
|
|
5840
6634
|
const config = loadConfig(deps.env);
|
|
5841
|
-
|
|
6635
|
+
return resolveAuth(
|
|
5842
6636
|
deps.env,
|
|
5843
6637
|
config,
|
|
5844
6638
|
requestedOrg(parsed.org, deps.env, deps.cwd, config),
|
|
5845
6639
|
deps.fetch
|
|
5846
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);
|
|
5847
6652
|
request ??= await buildRequest(command, parsed, {
|
|
5848
6653
|
organizationId: await credentialOrganization(deps, auth)
|
|
5849
6654
|
});
|
|
5850
|
-
|
|
5851
|
-
const org = auth.kind === "api-key" ? "the API key's organization" : auth.org ? orgLabel(auth.org) : "your default organization";
|
|
5852
|
-
deps.stderr(`Acting in ${org}.
|
|
5853
|
-
`);
|
|
5854
|
-
}
|
|
6655
|
+
announce(command, auth, deps);
|
|
5855
6656
|
const response = await send(
|
|
5856
6657
|
deps.env.apiUrl,
|
|
5857
6658
|
auth,
|
|
@@ -5891,11 +6692,22 @@ async function runCommand(command, parsed, deps) {
|
|
|
5891
6692
|
`);
|
|
5892
6693
|
return 0;
|
|
5893
6694
|
}
|
|
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
|
+
}
|
|
5894
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.";
|
|
5895
6707
|
async function run(argv2, deps) {
|
|
5896
6708
|
try {
|
|
5897
6709
|
const invocation = await parseInvocation(argv2, {
|
|
5898
|
-
commands:
|
|
6710
|
+
commands: ALL_COMMANDS,
|
|
5899
6711
|
builtins: BUILTINS,
|
|
5900
6712
|
version: VERSION,
|
|
5901
6713
|
description: DESCRIPTION
|
|
@@ -5908,6 +6720,14 @@ async function run(argv2, deps) {
|
|
|
5908
6720
|
if (invocation.kind === "builtin") {
|
|
5909
6721
|
return await runBuiltin(invocation.name, invocation.parsed, deps);
|
|
5910
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
|
+
}
|
|
5911
6731
|
return await runCommand(invocation.command, invocation.parsed, deps);
|
|
5912
6732
|
} catch (error) {
|
|
5913
6733
|
if (error instanceof UsageError) {
|
|
@@ -5963,7 +6783,7 @@ function openUrl(url) {
|
|
|
5963
6783
|
}
|
|
5964
6784
|
var argv = process.argv.slice(2);
|
|
5965
6785
|
if (argv[0] === "complete") {
|
|
5966
|
-
await runCompletion(argv, { commands:
|
|
6786
|
+
await runCompletion(argv, { commands: ALL_COMMANDS, builtins: BUILTINS });
|
|
5967
6787
|
process.exit(0);
|
|
5968
6788
|
}
|
|
5969
6789
|
var code = await run(argv, {
|