@asteroidai/cli 1.9.4 → 1.9.12
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 +1302 -469
- package/dist/core.js +1074 -260
- package/package.json +3 -2
- package/src/commands.ts +13 -0
- package/src/core.ts +31 -0
- package/src/generated/commands.ts +199 -182
- package/src/main.ts +2 -2
- package/src/program.test.ts +19 -0
- package/src/program.ts +25 -1
- package/src/reference.ts +13 -1
- package/src/run.test.ts +100 -3
- package/src/run.ts +57 -19
- package/src/sync/activities.ts +80 -0
- package/src/sync/commands.ts +63 -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 +136 -0
- package/src/sync/sync.test.ts +502 -0
- package/src/sync/types.ts +58 -0
- package/src/sync/workflow-pull.ts +185 -0
- package/src/sync/workflow-push.ts +161 -0
- package/src/types.ts +4 -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
|
{
|
|
@@ -344,7 +124,8 @@ var COMMANDS = [
|
|
|
344
124
|
query: [],
|
|
345
125
|
binary: false,
|
|
346
126
|
readOnly: false,
|
|
347
|
-
destructive: false
|
|
127
|
+
destructive: false,
|
|
128
|
+
capability: "run_execution"
|
|
348
129
|
},
|
|
349
130
|
{
|
|
350
131
|
command: "context",
|
|
@@ -1250,7 +1031,8 @@ var COMMANDS = [
|
|
|
1250
1031
|
],
|
|
1251
1032
|
files: [],
|
|
1252
1033
|
credentialOrganization: false
|
|
1253
|
-
}
|
|
1034
|
+
},
|
|
1035
|
+
capability: "run_execution"
|
|
1254
1036
|
},
|
|
1255
1037
|
{
|
|
1256
1038
|
command: "execution resume",
|
|
@@ -1270,7 +1052,8 @@ var COMMANDS = [
|
|
|
1270
1052
|
query: [],
|
|
1271
1053
|
binary: false,
|
|
1272
1054
|
readOnly: false,
|
|
1273
|
-
destructive: false
|
|
1055
|
+
destructive: false,
|
|
1056
|
+
capability: "run_execution"
|
|
1274
1057
|
},
|
|
1275
1058
|
{
|
|
1276
1059
|
command: "insight get",
|
|
@@ -2118,7 +1901,8 @@ var COMMANDS = [
|
|
|
2118
1901
|
],
|
|
2119
1902
|
files: [],
|
|
2120
1903
|
credentialOrganization: false
|
|
2121
|
-
}
|
|
1904
|
+
},
|
|
1905
|
+
capability: "run_execution"
|
|
2122
1906
|
},
|
|
2123
1907
|
{
|
|
2124
1908
|
command: "schedule delete",
|
|
@@ -2144,7 +1928,8 @@ var COMMANDS = [
|
|
|
2144
1928
|
query: [],
|
|
2145
1929
|
binary: false,
|
|
2146
1930
|
readOnly: false,
|
|
2147
|
-
destructive: true
|
|
1931
|
+
destructive: true,
|
|
1932
|
+
capability: "run_execution"
|
|
2148
1933
|
},
|
|
2149
1934
|
{
|
|
2150
1935
|
command: "schedule get",
|
|
@@ -2325,7 +2110,8 @@ var COMMANDS = [
|
|
|
2325
2110
|
],
|
|
2326
2111
|
files: [],
|
|
2327
2112
|
credentialOrganization: false
|
|
2328
|
-
}
|
|
2113
|
+
},
|
|
2114
|
+
capability: "run_execution"
|
|
2329
2115
|
},
|
|
2330
2116
|
{
|
|
2331
2117
|
command: "scheduled-execution cancel",
|
|
@@ -2345,7 +2131,8 @@ var COMMANDS = [
|
|
|
2345
2131
|
query: [],
|
|
2346
2132
|
binary: false,
|
|
2347
2133
|
readOnly: false,
|
|
2348
|
-
destructive: false
|
|
2134
|
+
destructive: false,
|
|
2135
|
+
capability: "run_execution"
|
|
2349
2136
|
},
|
|
2350
2137
|
{
|
|
2351
2138
|
command: "scheduled-execution create",
|
|
@@ -2413,7 +2200,8 @@ var COMMANDS = [
|
|
|
2413
2200
|
],
|
|
2414
2201
|
files: [],
|
|
2415
2202
|
credentialOrganization: false
|
|
2416
|
-
}
|
|
2203
|
+
},
|
|
2204
|
+
capability: "run_execution"
|
|
2417
2205
|
},
|
|
2418
2206
|
{
|
|
2419
2207
|
command: "scheduled-execution list",
|
|
@@ -2515,7 +2303,8 @@ var COMMANDS = [
|
|
|
2515
2303
|
],
|
|
2516
2304
|
files: [],
|
|
2517
2305
|
credentialOrganization: false
|
|
2518
|
-
}
|
|
2306
|
+
},
|
|
2307
|
+
capability: "run_execution"
|
|
2519
2308
|
},
|
|
2520
2309
|
{
|
|
2521
2310
|
command: "schema validate",
|
|
@@ -3655,7 +3444,8 @@ var COMMANDS = [
|
|
|
3655
3444
|
],
|
|
3656
3445
|
files: [],
|
|
3657
3446
|
credentialOrganization: false
|
|
3658
|
-
}
|
|
3447
|
+
},
|
|
3448
|
+
capability: "run_execution"
|
|
3659
3449
|
},
|
|
3660
3450
|
{
|
|
3661
3451
|
command: "workflow execute-sync",
|
|
@@ -3754,15 +3544,16 @@ var COMMANDS = [
|
|
|
3754
3544
|
],
|
|
3755
3545
|
files: [],
|
|
3756
3546
|
credentialOrganization: false
|
|
3757
|
-
}
|
|
3547
|
+
},
|
|
3548
|
+
capability: "run_execution"
|
|
3758
3549
|
},
|
|
3759
3550
|
{
|
|
3760
|
-
command: "workflow
|
|
3761
|
-
tool: "
|
|
3762
|
-
title: "
|
|
3763
|
-
description: "
|
|
3551
|
+
command: "workflow get",
|
|
3552
|
+
tool: "workflow_get",
|
|
3553
|
+
title: "Get workflow",
|
|
3554
|
+
description: "Get one workflow by `workflowId`: the workflow record, its published version (or the version named by `version`), and metadata for every version. Use this instead of paging `workflow_list` when you already know the ID.",
|
|
3764
3555
|
method: "GET",
|
|
3765
|
-
path: "/agents/v2/workflows/{workflowId}
|
|
3556
|
+
path: "/agents/v2/workflows/{workflowId}",
|
|
3766
3557
|
args: [
|
|
3767
3558
|
{
|
|
3768
3559
|
name: "workflowId",
|
|
@@ -3773,114 +3564,9 @@ var COMMANDS = [
|
|
|
3773
3564
|
],
|
|
3774
3565
|
query: [
|
|
3775
3566
|
{
|
|
3776
|
-
name: "
|
|
3777
|
-
flag: "
|
|
3778
|
-
type: "
|
|
3779
|
-
required: false,
|
|
3780
|
-
description: "Which contents come inline: none (a pure manifest), structural (the default), or all (structural plus file bytes within the response budget).",
|
|
3781
|
-
enum: ["none", "structural", "all"]
|
|
3782
|
-
},
|
|
3783
|
-
{
|
|
3784
|
-
name: "paths",
|
|
3785
|
-
flag: "paths",
|
|
3786
|
-
type: "string[]",
|
|
3787
|
-
required: false,
|
|
3788
|
-
description: "Return only the files at these exact tree paths. A path that names nothing yields no entry. The inline budget applies to the filtered set, so a narrow read can inline files a whole-tree read would have to skip."
|
|
3789
|
-
},
|
|
3790
|
-
{
|
|
3791
|
-
name: "variants",
|
|
3792
|
-
flag: "variants",
|
|
3793
|
-
type: "string",
|
|
3794
|
-
required: false,
|
|
3795
|
-
description: `Narrow which variants/<key>/ files the tree carries: "none" strips every variant, a comma-separated list keeps only those variant keys. Files outside variants/ are always kept, and the response's variantKeys still names every variant. Ignored unless the workflow has variant mode enabled; absent keeps every variant.`
|
|
3796
|
-
},
|
|
3797
|
-
{
|
|
3798
|
-
name: "maxFileBytes",
|
|
3799
|
-
flag: "max-file-bytes",
|
|
3800
|
-
type: "integer",
|
|
3801
|
-
required: false,
|
|
3802
|
-
description: "Per-file ceiling, in bytes, for inlined file contents. Clamped to the server maximum. Files above the ceiling stay references."
|
|
3803
|
-
},
|
|
3804
|
-
{
|
|
3805
|
-
name: "maxTotalBytes",
|
|
3806
|
-
flag: "max-total-bytes",
|
|
3807
|
-
type: "integer",
|
|
3808
|
-
required: false,
|
|
3809
|
-
description: "Ceiling, in bytes, on the total inlined file contents in one response. Clamped to the server maximum. Files are admitted in path order until the ceiling is reached; the rest stay references."
|
|
3810
|
-
}
|
|
3811
|
-
],
|
|
3812
|
-
binary: false,
|
|
3813
|
-
readOnly: true,
|
|
3814
|
-
destructive: false
|
|
3815
|
-
},
|
|
3816
|
-
{
|
|
3817
|
-
command: "workflow file patch",
|
|
3818
|
-
tool: "workflow_files_patch",
|
|
3819
|
-
title: "Write workflow files",
|
|
3820
|
-
description: "Write and delete files on the workflow's editable head in one patch. Structural files re-derive the graph; the workflow's own files go to its file store, you just write paths. Pass the rev from `workflow_files_list` as baseRev; a stale value is rejected with 409, so re-read and retry. Editing a published or already-executed version forks a new one instead of rewriting it. Each write carries `contentBase64`: base64-encode the file content, plain text is rejected with 400.",
|
|
3821
|
-
method: "PATCH",
|
|
3822
|
-
path: "/agents/v2/workflows/{workflowId}/head/files",
|
|
3823
|
-
args: [
|
|
3824
|
-
{
|
|
3825
|
-
name: "workflowId",
|
|
3826
|
-
flag: "workflow-id",
|
|
3827
|
-
type: "string",
|
|
3828
|
-
required: true
|
|
3829
|
-
}
|
|
3830
|
-
],
|
|
3831
|
-
query: [],
|
|
3832
|
-
binary: false,
|
|
3833
|
-
readOnly: false,
|
|
3834
|
-
destructive: true,
|
|
3835
|
-
body: {
|
|
3836
|
-
required: true,
|
|
3837
|
-
contentType: "application/json",
|
|
3838
|
-
fields: [
|
|
3839
|
-
{
|
|
3840
|
-
name: "baseRev",
|
|
3841
|
-
flag: "base-rev",
|
|
3842
|
-
type: "integer",
|
|
3843
|
-
required: true,
|
|
3844
|
-
description: "The revision this edit is based on. Agent-file-only patches overlay the current head even when this is stale. A structural-file patch is rejected with 409 when it does not match the stored head revision."
|
|
3845
|
-
},
|
|
3846
|
-
{
|
|
3847
|
-
name: "writes",
|
|
3848
|
-
flag: "writes",
|
|
3849
|
-
type: "json",
|
|
3850
|
-
required: true,
|
|
3851
|
-
description: "Files to upsert."
|
|
3852
|
-
},
|
|
3853
|
-
{
|
|
3854
|
-
name: "deletes",
|
|
3855
|
-
flag: "deletes",
|
|
3856
|
-
type: "string[]",
|
|
3857
|
-
required: true
|
|
3858
|
-
}
|
|
3859
|
-
],
|
|
3860
|
-
files: [],
|
|
3861
|
-
credentialOrganization: false
|
|
3862
|
-
}
|
|
3863
|
-
},
|
|
3864
|
-
{
|
|
3865
|
-
command: "workflow get",
|
|
3866
|
-
tool: "workflow_get",
|
|
3867
|
-
title: "Get workflow",
|
|
3868
|
-
description: "Get one workflow by `workflowId`: the workflow record, its published version (or the version named by `version`), and metadata for every version. Use this instead of paging `workflow_list` when you already know the ID.",
|
|
3869
|
-
method: "GET",
|
|
3870
|
-
path: "/agents/v2/workflows/{workflowId}",
|
|
3871
|
-
args: [
|
|
3872
|
-
{
|
|
3873
|
-
name: "workflowId",
|
|
3874
|
-
flag: "workflow-id",
|
|
3875
|
-
type: "string",
|
|
3876
|
-
required: true
|
|
3877
|
-
}
|
|
3878
|
-
],
|
|
3879
|
-
query: [
|
|
3880
|
-
{
|
|
3881
|
-
name: "version",
|
|
3882
|
-
flag: "version",
|
|
3883
|
-
type: "integer",
|
|
3567
|
+
name: "version",
|
|
3568
|
+
flag: "version",
|
|
3569
|
+
type: "integer",
|
|
3884
3570
|
required: false,
|
|
3885
3571
|
description: "Optional published version number to return"
|
|
3886
3572
|
}
|
|
@@ -3907,7 +3593,8 @@ var COMMANDS = [
|
|
|
3907
3593
|
query: [],
|
|
3908
3594
|
binary: false,
|
|
3909
3595
|
readOnly: false,
|
|
3910
|
-
destructive: false
|
|
3596
|
+
destructive: false,
|
|
3597
|
+
capability: "publish"
|
|
3911
3598
|
},
|
|
3912
3599
|
{
|
|
3913
3600
|
command: "workflow head revert",
|
|
@@ -4033,6 +3720,113 @@ var COMMANDS = [
|
|
|
4033
3720
|
readOnly: true,
|
|
4034
3721
|
destructive: false
|
|
4035
3722
|
},
|
|
3723
|
+
{
|
|
3724
|
+
command: "workflow pull",
|
|
3725
|
+
tool: "workflow_files_list",
|
|
3726
|
+
title: "List workflow files",
|
|
3727
|
+
description: "List the workflow's editable head as a directory of files: the graph rendered as structural files (settings.yaml, per-node instructions.md, scripts) alongside the workflow's own files. Those files are referenced by fileId unless contents=all is set. Pass paths to read named files only, or contents=none for a manifest with no bodies. The returned rev is what you pass as baseRev to write.",
|
|
3728
|
+
method: "GET",
|
|
3729
|
+
path: "/agents/v2/workflows/{workflowId}/head/files",
|
|
3730
|
+
args: [
|
|
3731
|
+
{
|
|
3732
|
+
name: "workflowId",
|
|
3733
|
+
flag: "workflow-id",
|
|
3734
|
+
type: "string",
|
|
3735
|
+
required: true
|
|
3736
|
+
}
|
|
3737
|
+
],
|
|
3738
|
+
query: [
|
|
3739
|
+
{
|
|
3740
|
+
name: "contents",
|
|
3741
|
+
flag: "contents",
|
|
3742
|
+
type: "string",
|
|
3743
|
+
required: false,
|
|
3744
|
+
description: "Which contents come inline: none (a pure manifest), structural (the default), or all (structural plus file bytes within the response budget).",
|
|
3745
|
+
enum: ["none", "structural", "all"]
|
|
3746
|
+
},
|
|
3747
|
+
{
|
|
3748
|
+
name: "paths",
|
|
3749
|
+
flag: "paths",
|
|
3750
|
+
type: "string[]",
|
|
3751
|
+
required: false,
|
|
3752
|
+
description: "Return only the files at these exact tree paths. A path that names nothing yields no entry. The inline budget applies to the filtered set, so a narrow read can inline files a whole-tree read would have to skip."
|
|
3753
|
+
},
|
|
3754
|
+
{
|
|
3755
|
+
name: "variants",
|
|
3756
|
+
flag: "variants",
|
|
3757
|
+
type: "string",
|
|
3758
|
+
required: false,
|
|
3759
|
+
description: `Narrow which variants/<key>/ files the tree carries: "none" strips every variant, a comma-separated list keeps only those variant keys. Files outside variants/ are always kept, and the response's variantKeys still names every variant. Ignored unless the workflow has variant mode enabled; absent keeps every variant.`
|
|
3760
|
+
},
|
|
3761
|
+
{
|
|
3762
|
+
name: "maxFileBytes",
|
|
3763
|
+
flag: "max-file-bytes",
|
|
3764
|
+
type: "integer",
|
|
3765
|
+
required: false,
|
|
3766
|
+
description: "Per-file ceiling, in bytes, for inlined file contents. Clamped to the server maximum. Files above the ceiling stay references."
|
|
3767
|
+
},
|
|
3768
|
+
{
|
|
3769
|
+
name: "maxTotalBytes",
|
|
3770
|
+
flag: "max-total-bytes",
|
|
3771
|
+
type: "integer",
|
|
3772
|
+
required: false,
|
|
3773
|
+
description: "Ceiling, in bytes, on the total inlined file contents in one response. Clamped to the server maximum. Files are admitted in path order until the ceiling is reached; the rest stay references."
|
|
3774
|
+
}
|
|
3775
|
+
],
|
|
3776
|
+
binary: false,
|
|
3777
|
+
readOnly: true,
|
|
3778
|
+
destructive: false,
|
|
3779
|
+
files: "pull"
|
|
3780
|
+
},
|
|
3781
|
+
{
|
|
3782
|
+
command: "workflow push",
|
|
3783
|
+
tool: "workflow_files_patch",
|
|
3784
|
+
title: "Write workflow files",
|
|
3785
|
+
description: "Write and delete files on the workflow's editable head in one patch. Structural files re-derive the graph; the workflow's own files go to its file store, you just write paths. Pass the rev from `workflow_files_list` as baseRev; a stale value is rejected with 409, so re-read and retry. Editing a published or already-executed version forks a new one instead of rewriting it. Each write carries `contentBase64`: base64-encode the file content, plain text is rejected with 400.",
|
|
3786
|
+
method: "PATCH",
|
|
3787
|
+
path: "/agents/v2/workflows/{workflowId}/head/files",
|
|
3788
|
+
args: [
|
|
3789
|
+
{
|
|
3790
|
+
name: "workflowId",
|
|
3791
|
+
flag: "workflow-id",
|
|
3792
|
+
type: "string",
|
|
3793
|
+
required: true
|
|
3794
|
+
}
|
|
3795
|
+
],
|
|
3796
|
+
query: [],
|
|
3797
|
+
binary: false,
|
|
3798
|
+
readOnly: false,
|
|
3799
|
+
destructive: true,
|
|
3800
|
+
body: {
|
|
3801
|
+
required: true,
|
|
3802
|
+
contentType: "application/json",
|
|
3803
|
+
fields: [
|
|
3804
|
+
{
|
|
3805
|
+
name: "baseRev",
|
|
3806
|
+
flag: "base-rev",
|
|
3807
|
+
type: "integer",
|
|
3808
|
+
required: true,
|
|
3809
|
+
description: "The revision this edit is based on. Agent-file-only patches overlay the current head even when this is stale. A structural-file patch is rejected with 409 when it does not match the stored head revision."
|
|
3810
|
+
},
|
|
3811
|
+
{
|
|
3812
|
+
name: "writes",
|
|
3813
|
+
flag: "writes",
|
|
3814
|
+
type: "json",
|
|
3815
|
+
required: true,
|
|
3816
|
+
description: "Files to upsert."
|
|
3817
|
+
},
|
|
3818
|
+
{
|
|
3819
|
+
name: "deletes",
|
|
3820
|
+
flag: "deletes",
|
|
3821
|
+
type: "string[]",
|
|
3822
|
+
required: true
|
|
3823
|
+
}
|
|
3824
|
+
],
|
|
3825
|
+
files: [],
|
|
3826
|
+
credentialOrganization: false
|
|
3827
|
+
},
|
|
3828
|
+
files: "push"
|
|
3829
|
+
},
|
|
4036
3830
|
{
|
|
4037
3831
|
command: "workflow rename",
|
|
4038
3832
|
tool: "workflow_rename",
|
|
@@ -4435,70 +4229,8 @@ var COMMANDS = [
|
|
|
4435
4229
|
],
|
|
4436
4230
|
files: [],
|
|
4437
4231
|
credentialOrganization: false
|
|
4438
|
-
}
|
|
4439
|
-
|
|
4440
|
-
{
|
|
4441
|
-
command: "workflow version file list",
|
|
4442
|
-
tool: "workflow_version_files_list",
|
|
4443
|
-
title: "List workflow version files",
|
|
4444
|
-
description: "List a version's files, like `workflow_files_list` does for the editable head. Versions are immutable, so this is read-only.",
|
|
4445
|
-
method: "GET",
|
|
4446
|
-
path: "/agents/v2/workflows/{workflowId}/versions/{versionId}/files",
|
|
4447
|
-
args: [
|
|
4448
|
-
{
|
|
4449
|
-
name: "workflowId",
|
|
4450
|
-
flag: "workflow-id",
|
|
4451
|
-
type: "string",
|
|
4452
|
-
required: true
|
|
4453
|
-
},
|
|
4454
|
-
{
|
|
4455
|
-
name: "versionId",
|
|
4456
|
-
flag: "version-id",
|
|
4457
|
-
type: "string",
|
|
4458
|
-
required: true
|
|
4459
|
-
}
|
|
4460
|
-
],
|
|
4461
|
-
query: [
|
|
4462
|
-
{
|
|
4463
|
-
name: "contents",
|
|
4464
|
-
flag: "contents",
|
|
4465
|
-
type: "string",
|
|
4466
|
-
required: false,
|
|
4467
|
-
description: "Which contents come inline: none (a pure manifest), structural (the default), or all (structural plus file bytes within the response budget).",
|
|
4468
|
-
enum: ["none", "structural", "all"]
|
|
4469
|
-
},
|
|
4470
|
-
{
|
|
4471
|
-
name: "paths",
|
|
4472
|
-
flag: "paths",
|
|
4473
|
-
type: "string[]",
|
|
4474
|
-
required: false,
|
|
4475
|
-
description: "Return only the files at these exact tree paths. A path that names nothing yields no entry. The inline budget applies to the filtered set, so a narrow read can inline files a whole-tree read would have to skip."
|
|
4476
|
-
},
|
|
4477
|
-
{
|
|
4478
|
-
name: "variants",
|
|
4479
|
-
flag: "variants",
|
|
4480
|
-
type: "string",
|
|
4481
|
-
required: false,
|
|
4482
|
-
description: `Narrow which variants/<key>/ files the tree carries: "none" strips every variant, a comma-separated list keeps only those variant keys. Files outside variants/ are always kept, and the response's variantKeys still names every variant. Ignored unless the workflow has variant mode enabled; absent keeps every variant.`
|
|
4483
|
-
},
|
|
4484
|
-
{
|
|
4485
|
-
name: "maxFileBytes",
|
|
4486
|
-
flag: "max-file-bytes",
|
|
4487
|
-
type: "integer",
|
|
4488
|
-
required: false,
|
|
4489
|
-
description: "Per-file ceiling, in bytes, for inlined file contents. Clamped to the server maximum. Files above the ceiling stay references."
|
|
4490
|
-
},
|
|
4491
|
-
{
|
|
4492
|
-
name: "maxTotalBytes",
|
|
4493
|
-
flag: "max-total-bytes",
|
|
4494
|
-
type: "integer",
|
|
4495
|
-
required: false,
|
|
4496
|
-
description: "Ceiling, in bytes, on the total inlined file contents in one response. Clamped to the server maximum. Files are admitted in path order until the ceiling is reached; the rest stay references."
|
|
4497
|
-
}
|
|
4498
|
-
],
|
|
4499
|
-
binary: false,
|
|
4500
|
-
readOnly: true,
|
|
4501
|
-
destructive: false
|
|
4232
|
+
},
|
|
4233
|
+
capability: "run_execution"
|
|
4502
4234
|
},
|
|
4503
4235
|
{
|
|
4504
4236
|
command: "workflow version file url",
|
|
@@ -4654,7 +4386,72 @@ var COMMANDS = [
|
|
|
4654
4386
|
query: [],
|
|
4655
4387
|
binary: false,
|
|
4656
4388
|
readOnly: false,
|
|
4657
|
-
destructive: false
|
|
4389
|
+
destructive: false,
|
|
4390
|
+
capability: "publish"
|
|
4391
|
+
},
|
|
4392
|
+
{
|
|
4393
|
+
command: "workflow version pull",
|
|
4394
|
+
tool: "workflow_version_files_list",
|
|
4395
|
+
title: "List workflow version files",
|
|
4396
|
+
description: "List a version's files, like `workflow_files_list` does for the editable head. Versions are immutable, so this is read-only.",
|
|
4397
|
+
method: "GET",
|
|
4398
|
+
path: "/agents/v2/workflows/{workflowId}/versions/{versionId}/files",
|
|
4399
|
+
args: [
|
|
4400
|
+
{
|
|
4401
|
+
name: "workflowId",
|
|
4402
|
+
flag: "workflow-id",
|
|
4403
|
+
type: "string",
|
|
4404
|
+
required: true
|
|
4405
|
+
},
|
|
4406
|
+
{
|
|
4407
|
+
name: "versionId",
|
|
4408
|
+
flag: "version-id",
|
|
4409
|
+
type: "string",
|
|
4410
|
+
required: true
|
|
4411
|
+
}
|
|
4412
|
+
],
|
|
4413
|
+
query: [
|
|
4414
|
+
{
|
|
4415
|
+
name: "contents",
|
|
4416
|
+
flag: "contents",
|
|
4417
|
+
type: "string",
|
|
4418
|
+
required: false,
|
|
4419
|
+
description: "Which contents come inline: none (a pure manifest), structural (the default), or all (structural plus file bytes within the response budget).",
|
|
4420
|
+
enum: ["none", "structural", "all"]
|
|
4421
|
+
},
|
|
4422
|
+
{
|
|
4423
|
+
name: "paths",
|
|
4424
|
+
flag: "paths",
|
|
4425
|
+
type: "string[]",
|
|
4426
|
+
required: false,
|
|
4427
|
+
description: "Return only the files at these exact tree paths. A path that names nothing yields no entry. The inline budget applies to the filtered set, so a narrow read can inline files a whole-tree read would have to skip."
|
|
4428
|
+
},
|
|
4429
|
+
{
|
|
4430
|
+
name: "variants",
|
|
4431
|
+
flag: "variants",
|
|
4432
|
+
type: "string",
|
|
4433
|
+
required: false,
|
|
4434
|
+
description: `Narrow which variants/<key>/ files the tree carries: "none" strips every variant, a comma-separated list keeps only those variant keys. Files outside variants/ are always kept, and the response's variantKeys still names every variant. Ignored unless the workflow has variant mode enabled; absent keeps every variant.`
|
|
4435
|
+
},
|
|
4436
|
+
{
|
|
4437
|
+
name: "maxFileBytes",
|
|
4438
|
+
flag: "max-file-bytes",
|
|
4439
|
+
type: "integer",
|
|
4440
|
+
required: false,
|
|
4441
|
+
description: "Per-file ceiling, in bytes, for inlined file contents. Clamped to the server maximum. Files above the ceiling stay references."
|
|
4442
|
+
},
|
|
4443
|
+
{
|
|
4444
|
+
name: "maxTotalBytes",
|
|
4445
|
+
flag: "max-total-bytes",
|
|
4446
|
+
type: "integer",
|
|
4447
|
+
required: false,
|
|
4448
|
+
description: "Ceiling, in bytes, on the total inlined file contents in one response. Clamped to the server maximum. Files are admitted in path order until the ceiling is reached; the rest stay references."
|
|
4449
|
+
}
|
|
4450
|
+
],
|
|
4451
|
+
binary: false,
|
|
4452
|
+
readOnly: true,
|
|
4453
|
+
destructive: false,
|
|
4454
|
+
files: "pull"
|
|
4658
4455
|
},
|
|
4659
4456
|
{
|
|
4660
4457
|
command: "workflow version validate",
|
|
@@ -4724,7 +4521,290 @@ var COMMANDS = [
|
|
|
4724
4521
|
credentialOrganization: false
|
|
4725
4522
|
}
|
|
4726
4523
|
}
|
|
4727
|
-
];
|
|
4524
|
+
];
|
|
4525
|
+
|
|
4526
|
+
// src/sync/commands.ts
|
|
4527
|
+
function option(name, type, description, extra = {}) {
|
|
4528
|
+
return { name, flag: name, type, required: false, description, ...extra };
|
|
4529
|
+
}
|
|
4530
|
+
var local = {
|
|
4531
|
+
method: "GET",
|
|
4532
|
+
path: "",
|
|
4533
|
+
binary: false,
|
|
4534
|
+
destructive: false,
|
|
4535
|
+
local: true
|
|
4536
|
+
};
|
|
4537
|
+
var SYNC_COMMANDS = [
|
|
4538
|
+
{
|
|
4539
|
+
...local,
|
|
4540
|
+
command: "execution pull",
|
|
4541
|
+
tool: "execution_pull",
|
|
4542
|
+
title: "Pull an execution to disk",
|
|
4543
|
+
description: "Write an execution to `<workflow-name>/executions/<id>/`: summary.json, activities.md (the step-by-step log), and the files the run produced.",
|
|
4544
|
+
readOnly: true,
|
|
4545
|
+
args: [
|
|
4546
|
+
{
|
|
4547
|
+
name: "executionId",
|
|
4548
|
+
flag: "execution-id",
|
|
4549
|
+
type: "string",
|
|
4550
|
+
required: true
|
|
4551
|
+
}
|
|
4552
|
+
],
|
|
4553
|
+
query: [
|
|
4554
|
+
option(
|
|
4555
|
+
"files",
|
|
4556
|
+
"string",
|
|
4557
|
+
"Only download files whose path matches this glob. --no-files skips downloads.",
|
|
4558
|
+
{
|
|
4559
|
+
negatable: true
|
|
4560
|
+
}
|
|
4561
|
+
),
|
|
4562
|
+
option(
|
|
4563
|
+
"summary-only",
|
|
4564
|
+
"boolean",
|
|
4565
|
+
"Write summary.json only: no activities, no files."
|
|
4566
|
+
),
|
|
4567
|
+
option(
|
|
4568
|
+
"max-file-size",
|
|
4569
|
+
"integer",
|
|
4570
|
+
"Skip files larger than this many bytes."
|
|
4571
|
+
),
|
|
4572
|
+
option(
|
|
4573
|
+
"dir",
|
|
4574
|
+
"string",
|
|
4575
|
+
"Write here instead of `<workflow-name>/executions/<id>/` under the current directory."
|
|
4576
|
+
)
|
|
4577
|
+
]
|
|
4578
|
+
}
|
|
4579
|
+
];
|
|
4580
|
+
|
|
4581
|
+
// src/commands.ts
|
|
4582
|
+
function withSyncCommands(commands) {
|
|
4583
|
+
return [...commands, ...SYNC_COMMANDS].sort(
|
|
4584
|
+
(a, b) => a.command.localeCompare(b.command)
|
|
4585
|
+
);
|
|
4586
|
+
}
|
|
4587
|
+
var ALL_COMMANDS = withSyncCommands(COMMANDS);
|
|
4588
|
+
|
|
4589
|
+
// src/config.ts
|
|
4590
|
+
import envPaths from "env-paths";
|
|
4591
|
+
import {
|
|
4592
|
+
chmodSync,
|
|
4593
|
+
existsSync,
|
|
4594
|
+
mkdirSync,
|
|
4595
|
+
readFileSync,
|
|
4596
|
+
renameSync,
|
|
4597
|
+
writeFileSync
|
|
4598
|
+
} from "node:fs";
|
|
4599
|
+
import { dirname, join } from "node:path";
|
|
4600
|
+
|
|
4601
|
+
// src/output.ts
|
|
4602
|
+
import Table from "cli-table3";
|
|
4603
|
+
import { styleText } from "node:util";
|
|
4604
|
+
var PREFERRED = [
|
|
4605
|
+
"id",
|
|
4606
|
+
"name",
|
|
4607
|
+
"title",
|
|
4608
|
+
"workflowName",
|
|
4609
|
+
"status",
|
|
4610
|
+
"outcome",
|
|
4611
|
+
"version",
|
|
4612
|
+
"createdAt",
|
|
4613
|
+
"updatedAt"
|
|
4614
|
+
];
|
|
4615
|
+
var HIDDEN = /^agent[A-Z]|Url$/;
|
|
4616
|
+
var MAX_COLUMNS = 6;
|
|
4617
|
+
var MAX_CELL = 60;
|
|
4618
|
+
var CONTROL = /\x1b\[[0-?]*[ -/]*[@-~]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)?|\x1b[@-_]?|[\x00-\x08\x0b-\x1f\x7f-\x9f]/g;
|
|
4619
|
+
function clean(text2) {
|
|
4620
|
+
return text2.replace(CONTROL, "");
|
|
4621
|
+
}
|
|
4622
|
+
function isObject(value2) {
|
|
4623
|
+
return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
|
|
4624
|
+
}
|
|
4625
|
+
function isScalar(value2) {
|
|
4626
|
+
return value2 === null || ["string", "number", "boolean"].includes(typeof value2);
|
|
4627
|
+
}
|
|
4628
|
+
function indent(text2) {
|
|
4629
|
+
return text2.split("\n").map((line2) => ` ${line2}`).join("\n");
|
|
4630
|
+
}
|
|
4631
|
+
function text(value2) {
|
|
4632
|
+
return clean(isScalar(value2) ? String(value2) : JSON.stringify(value2));
|
|
4633
|
+
}
|
|
4634
|
+
function cell(value2, options = {}) {
|
|
4635
|
+
if (value2 === null || value2 === void 0) return "";
|
|
4636
|
+
const line2 = text(value2).replace(/\s+/g, " ");
|
|
4637
|
+
if (options.full || line2.length <= MAX_CELL) return line2;
|
|
4638
|
+
return `${line2.slice(0, MAX_CELL - 1)}\u2026`;
|
|
4639
|
+
}
|
|
4640
|
+
function columns(rows, options) {
|
|
4641
|
+
const scalarKeys = [];
|
|
4642
|
+
for (const row of rows) {
|
|
4643
|
+
for (const [key, value2] of Object.entries(row)) {
|
|
4644
|
+
if (isScalar(value2) && !HIDDEN.test(key.split(".").at(-1) ?? key) && !scalarKeys.includes(key))
|
|
4645
|
+
scalarKeys.push(key);
|
|
4646
|
+
}
|
|
4647
|
+
}
|
|
4648
|
+
const ordered = [
|
|
4649
|
+
...PREFERRED.filter((key) => scalarKeys.includes(key)),
|
|
4650
|
+
...scalarKeys.filter((key) => !PREFERRED.includes(key))
|
|
4651
|
+
];
|
|
4652
|
+
return options.full ? ordered : ordered.slice(0, MAX_COLUMNS);
|
|
4653
|
+
}
|
|
4654
|
+
function style(format, value2, options) {
|
|
4655
|
+
return options.styled ? styleText(format, value2, { validateStream: false }) : value2;
|
|
4656
|
+
}
|
|
4657
|
+
function flatten(row) {
|
|
4658
|
+
const flat = {};
|
|
4659
|
+
for (const [key, value2] of Object.entries(row)) {
|
|
4660
|
+
if (!isObject(value2)) {
|
|
4661
|
+
flat[key] = value2;
|
|
4662
|
+
continue;
|
|
4663
|
+
}
|
|
4664
|
+
for (const [inner, v] of Object.entries(value2))
|
|
4665
|
+
flat[`${key}.${inner}`] = v;
|
|
4666
|
+
}
|
|
4667
|
+
return flat;
|
|
4668
|
+
}
|
|
4669
|
+
function table(input, options = {}) {
|
|
4670
|
+
if (input.length === 0) return "No results.";
|
|
4671
|
+
const rows = input.map(flatten);
|
|
4672
|
+
const keys = columns(rows, options);
|
|
4673
|
+
if (keys.length === 0) return rows.map((row) => text(row)).join("\n");
|
|
4674
|
+
const cells = rows.map((row) => keys.map((key) => cell(row[key], options)));
|
|
4675
|
+
if (options.styled) {
|
|
4676
|
+
const grid = new Table({
|
|
4677
|
+
head: keys.map((key) => style("bold", key, options)),
|
|
4678
|
+
style: { head: [], border: [] }
|
|
4679
|
+
});
|
|
4680
|
+
grid.push(...cells);
|
|
4681
|
+
return grid.toString();
|
|
4682
|
+
}
|
|
4683
|
+
const widths = keys.map(
|
|
4684
|
+
(key, i) => Math.max(key.length, ...cells.map((r) => r[i].length))
|
|
4685
|
+
);
|
|
4686
|
+
const line2 = (values) => values.map((v, i) => v.padEnd(widths[i])).join(" ").trimEnd();
|
|
4687
|
+
return [line2(keys.map((k) => k.toUpperCase())), ...cells.map(line2)].join(
|
|
4688
|
+
"\n"
|
|
4689
|
+
);
|
|
4690
|
+
}
|
|
4691
|
+
function summary(value2, options) {
|
|
4692
|
+
if (Array.isArray(value2))
|
|
4693
|
+
return `[${value2.length} item${value2.length === 1 ? "" : "s"}]`;
|
|
4694
|
+
if (value2 && typeof value2 === "object") return cell(value2, options);
|
|
4695
|
+
return text(value2);
|
|
4696
|
+
}
|
|
4697
|
+
function record(value2, options) {
|
|
4698
|
+
const width = Math.max(...Object.keys(value2).map((key) => key.length));
|
|
4699
|
+
const label = (key) => style("bold", key.padEnd(width), options);
|
|
4700
|
+
return Object.entries(value2).map(([key, v]) => {
|
|
4701
|
+
if (Array.isArray(v) && v.length > 0 && v.every(isObject)) {
|
|
4702
|
+
return `${style("bold", key, options)}:
|
|
4703
|
+
${indent(table(v, options))}`;
|
|
4704
|
+
}
|
|
4705
|
+
if (Array.isArray(v) && v.length > 0 && v.every(isScalar)) {
|
|
4706
|
+
return `${label(key)} ${v.map(text).join(`
|
|
4707
|
+
${" ".repeat(width + 2)}`)}`;
|
|
4708
|
+
}
|
|
4709
|
+
if (isObject(v) && Object.keys(v).length > 0) {
|
|
4710
|
+
return `${style("bold", key, options)}:
|
|
4711
|
+
${indent(record(v, options))}`;
|
|
4712
|
+
}
|
|
4713
|
+
return `${label(key)} ${isScalar(v) ? text(v) : summary(v, options)}`;
|
|
4714
|
+
}).join("\n");
|
|
4715
|
+
}
|
|
4716
|
+
function render(value2, options = {}) {
|
|
4717
|
+
if (value2 === void 0) return "";
|
|
4718
|
+
if (Array.isArray(value2)) {
|
|
4719
|
+
return value2.every(isObject) ? table(value2, options) : value2.map(text).join("\n");
|
|
4720
|
+
}
|
|
4721
|
+
if (isObject(value2)) {
|
|
4722
|
+
const row = value2;
|
|
4723
|
+
if (Array.isArray(row.items)) {
|
|
4724
|
+
const rest = Object.entries(row).filter(
|
|
4725
|
+
([k, v]) => k !== "items" && isScalar(v)
|
|
4726
|
+
);
|
|
4727
|
+
const footer = rest.length ? `
|
|
4728
|
+
|
|
4729
|
+
${style("dim", rest.map(([k, v]) => `${k}: ${text(v)}`).join(" "), options)}` : "";
|
|
4730
|
+
return `${table(row.items, options)}${footer}`;
|
|
4731
|
+
}
|
|
4732
|
+
return record(row, options);
|
|
4733
|
+
}
|
|
4734
|
+
return text(value2);
|
|
4735
|
+
}
|
|
4736
|
+
|
|
4737
|
+
// src/config.ts
|
|
4738
|
+
var DEFAULT_API_URL = "https://odyssey.asteroid.ai";
|
|
4739
|
+
var DEFAULT_WORKOS_CLIENT_ID = "client_01K3NXPE8X6EJZXZSYCNT5M4P0";
|
|
4740
|
+
var DEFAULT_WORKOS_API_URL = "https://api.workos.com";
|
|
4741
|
+
function readEnv(env = process.env) {
|
|
4742
|
+
return {
|
|
4743
|
+
apiUrl: (env.ASTEROID_API_URL || DEFAULT_API_URL).replace(/\/+$/, ""),
|
|
4744
|
+
apiKey: env.ASTEROID_API_KEY || void 0,
|
|
4745
|
+
org: env.ASTEROID_ORG || void 0,
|
|
4746
|
+
workosClientId: env.ASTEROID_WORKOS_CLIENT_ID || DEFAULT_WORKOS_CLIENT_ID,
|
|
4747
|
+
workosApiUrl: (env.ASTEROID_WORKOS_API_URL || DEFAULT_WORKOS_API_URL).replace(/\/+$/, ""),
|
|
4748
|
+
configDir: env.ASTEROID_CONFIG_DIR || envPaths("asteroid", { suffix: "" }).config
|
|
4749
|
+
};
|
|
4750
|
+
}
|
|
4751
|
+
function readJson(path, fallback) {
|
|
4752
|
+
if (!existsSync(path)) return fallback;
|
|
4753
|
+
try {
|
|
4754
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
4755
|
+
} catch {
|
|
4756
|
+
return fallback;
|
|
4757
|
+
}
|
|
4758
|
+
}
|
|
4759
|
+
function writePrivateJson(path, value2) {
|
|
4760
|
+
mkdirSync(dirname(path), { recursive: true, mode: 448 });
|
|
4761
|
+
const temp = `${path}.${process.pid}.tmp`;
|
|
4762
|
+
writeFileSync(temp, `${JSON.stringify(value2, null, 2)}
|
|
4763
|
+
`, { mode: 384 });
|
|
4764
|
+
chmodSync(temp, 384);
|
|
4765
|
+
renameSync(temp, path);
|
|
4766
|
+
}
|
|
4767
|
+
function loadConfig(env) {
|
|
4768
|
+
return readJson(join(env.configDir, "config.json"), {});
|
|
4769
|
+
}
|
|
4770
|
+
function saveConfig(env, config) {
|
|
4771
|
+
writePrivateJson(join(env.configDir, "config.json"), config);
|
|
4772
|
+
}
|
|
4773
|
+
function repoOrg(cwd) {
|
|
4774
|
+
let dir = cwd;
|
|
4775
|
+
for (; ; ) {
|
|
4776
|
+
const file = join(dir, ".asteroid", "org");
|
|
4777
|
+
if (existsSync(file)) return readFileSync(file, "utf8").trim() || void 0;
|
|
4778
|
+
const parent = dirname(dir);
|
|
4779
|
+
if (parent === dir) return void 0;
|
|
4780
|
+
dir = parent;
|
|
4781
|
+
}
|
|
4782
|
+
}
|
|
4783
|
+
function requestedOrg(flag2, env, cwd, config) {
|
|
4784
|
+
return flag2 ?? env.org ?? repoOrg(cwd) ?? config.org?.workosId;
|
|
4785
|
+
}
|
|
4786
|
+
function findOrg(ref, orgs2) {
|
|
4787
|
+
const lower = ref.toLowerCase();
|
|
4788
|
+
return orgs2.find(
|
|
4789
|
+
(org) => org.workosId.toLowerCase() === lower || org.id?.toLowerCase() === lower
|
|
4790
|
+
) ?? orgs2.find((org) => org.name?.toLowerCase() === lower) ?? (/^org_[0-9a-z]+$/i.test(ref) ? { workosId: ref } : void 0);
|
|
4791
|
+
}
|
|
4792
|
+
function mergeOrgs(known = [], found = []) {
|
|
4793
|
+
const byId = new Map(known.map((org) => [org.workosId, org]));
|
|
4794
|
+
for (const org of found) {
|
|
4795
|
+
const merged = { ...byId.get(org.workosId) };
|
|
4796
|
+
for (const [key, value2] of Object.entries(org))
|
|
4797
|
+
if (value2 !== void 0) Object.assign(merged, { [key]: value2 });
|
|
4798
|
+
byId.set(org.workosId, merged);
|
|
4799
|
+
}
|
|
4800
|
+
return [...byId.values()].sort(
|
|
4801
|
+
(a, b) => (a.name ?? a.workosId).localeCompare(b.name ?? b.workosId)
|
|
4802
|
+
);
|
|
4803
|
+
}
|
|
4804
|
+
function orgLabel(org) {
|
|
4805
|
+
const id = clean(org.id ?? org.workosId);
|
|
4806
|
+
return org.name ? `${clean(org.name)} (${id})` : id;
|
|
4807
|
+
}
|
|
4728
4808
|
|
|
4729
4809
|
// src/program.ts
|
|
4730
4810
|
import { Command, CommanderError } from "commander";
|
|
@@ -4794,12 +4874,12 @@ var DOTTED = /^--([a-z0-9-]+\.[a-z0-9.-]+)(?:=(.*))?$/;
|
|
|
4794
4874
|
function collect(value2, previous) {
|
|
4795
4875
|
return [...previous ?? [], value2];
|
|
4796
4876
|
}
|
|
4797
|
-
function optionText(
|
|
4798
|
-
const text2 =
|
|
4877
|
+
function optionText(option2) {
|
|
4878
|
+
const text2 = option2.description?.replace(/\s+/g, " ").trim() ?? "";
|
|
4799
4879
|
const parts = [text2 && !/[.!?]$/.test(text2) ? `${text2}.` : text2];
|
|
4800
|
-
if (
|
|
4801
|
-
if (
|
|
4802
|
-
if (
|
|
4880
|
+
if (option2.enum) parts.push(`One of: ${option2.enum.join(", ")}.`);
|
|
4881
|
+
if (option2.type.endsWith("[]")) parts.push("Repeatable.");
|
|
4882
|
+
if (option2.required) parts.push("Required.");
|
|
4803
4883
|
return parts.filter(Boolean).join(" ");
|
|
4804
4884
|
}
|
|
4805
4885
|
function splitDotted(argv2) {
|
|
@@ -4826,32 +4906,44 @@ function group(parent, name, groups, path) {
|
|
|
4826
4906
|
return created;
|
|
4827
4907
|
}
|
|
4828
4908
|
function leafOptions(leaf, command) {
|
|
4829
|
-
for (const
|
|
4830
|
-
if (
|
|
4831
|
-
leaf.option(`--${
|
|
4832
|
-
leaf.option(`--no-${
|
|
4909
|
+
for (const option2 of [...command.query, ...command.body?.fields ?? []]) {
|
|
4910
|
+
if (option2.type === "boolean") {
|
|
4911
|
+
leaf.option(`--${option2.flag}`, optionText(option2));
|
|
4912
|
+
leaf.option(`--no-${option2.flag}`, `Set ${option2.flag} to false.`);
|
|
4833
4913
|
continue;
|
|
4834
4914
|
}
|
|
4835
|
-
const placeholder =
|
|
4915
|
+
const placeholder = option2.enum ? option2.enum.join("|") : option2.type.replace("[]", "");
|
|
4836
4916
|
leaf.option(
|
|
4837
|
-
`--${
|
|
4838
|
-
optionText(
|
|
4917
|
+
`--${option2.flag} <${placeholder}>`,
|
|
4918
|
+
optionText(option2),
|
|
4839
4919
|
collect
|
|
4840
4920
|
);
|
|
4921
|
+
if (option2.negatable)
|
|
4922
|
+
leaf.option(`--no-${option2.flag}`, `Skip ${option2.flag}.`);
|
|
4841
4923
|
}
|
|
4842
4924
|
for (const file of command.body?.files ?? []) {
|
|
4843
4925
|
leaf.option(`--${file} <path>`, "File to upload. Repeatable.", collect);
|
|
4844
4926
|
}
|
|
4927
|
+
if (command.files === "pull")
|
|
4928
|
+
leaf.option(
|
|
4929
|
+
"--out <dir>",
|
|
4930
|
+
"Write here instead of ./<workflow-name>/head (a version: ./<workflow-name>/.versions/...). --json prints the file listing instead."
|
|
4931
|
+
);
|
|
4932
|
+
if (command.files === "push")
|
|
4933
|
+
leaf.option(
|
|
4934
|
+
"--from <dir>",
|
|
4935
|
+
"The pulled head folder to push. Defaults to the current folder."
|
|
4936
|
+
);
|
|
4845
4937
|
}
|
|
4846
4938
|
function toParsed(leaf, positionals, dotted) {
|
|
4847
4939
|
const globals = leaf.optsWithGlobals();
|
|
4848
4940
|
const own = leaf.opts();
|
|
4849
4941
|
const flags = new Map(dotted);
|
|
4850
|
-
for (const
|
|
4851
|
-
const
|
|
4852
|
-
const value2 = own[
|
|
4853
|
-
if (!
|
|
4854
|
-
flags.set(
|
|
4942
|
+
for (const option2 of leaf.options) {
|
|
4943
|
+
const flag2 = option2.long?.replace(/^--(no-)?/, "");
|
|
4944
|
+
const value2 = own[option2.attributeName()];
|
|
4945
|
+
if (!flag2 || value2 === void 0 || flags.has(flag2)) continue;
|
|
4946
|
+
flags.set(flag2, Array.isArray(value2) ? value2.map(String) : [String(value2)]);
|
|
4855
4947
|
}
|
|
4856
4948
|
return {
|
|
4857
4949
|
positionals,
|
|
@@ -4864,7 +4956,11 @@ function toParsed(leaf, positionals, dotted) {
|
|
|
4864
4956
|
}
|
|
4865
4957
|
function defineCommands(root, options, dotted = /* @__PURE__ */ new Map(), onInvocation = () => void 0) {
|
|
4866
4958
|
if (options.description) root.description(options.description);
|
|
4867
|
-
if (options.version)
|
|
4959
|
+
if (options.version)
|
|
4960
|
+
root.addHelpText(
|
|
4961
|
+
"after",
|
|
4962
|
+
"\nRun `asteroid --version` to print the CLI version."
|
|
4963
|
+
);
|
|
4868
4964
|
for (const [flags, text2] of GLOBAL_FLAGS) {
|
|
4869
4965
|
if (!flags.startsWith("-h")) root.option(flags, text2);
|
|
4870
4966
|
}
|
|
@@ -4926,8 +5022,8 @@ ${notes.join("\n")}`);
|
|
|
4926
5022
|
);
|
|
4927
5023
|
}
|
|
4928
5024
|
const unknownDotted = [...dotted.keys()].filter(
|
|
4929
|
-
(
|
|
4930
|
-
(f) =>
|
|
5025
|
+
(flag2) => !(command.body?.fields ?? []).some(
|
|
5026
|
+
(f) => flag2.startsWith(`${f.flag}.`)
|
|
4931
5027
|
)
|
|
4932
5028
|
);
|
|
4933
5029
|
if (unknownDotted.length > 0) {
|
|
@@ -4948,6 +5044,15 @@ async function parseInvocation(argv2, options) {
|
|
|
4948
5044
|
let stdout = "";
|
|
4949
5045
|
let stderr = "";
|
|
4950
5046
|
let result;
|
|
5047
|
+
if (options.version && (argv2[0] === "-v" || argv2[0] === "--version")) {
|
|
5048
|
+
return {
|
|
5049
|
+
kind: "exit",
|
|
5050
|
+
code: 0,
|
|
5051
|
+
stdout: `${options.version}
|
|
5052
|
+
`,
|
|
5053
|
+
stderr: ""
|
|
5054
|
+
};
|
|
5055
|
+
}
|
|
4951
5056
|
const { argv: rest, dotted } = splitDotted(argv2);
|
|
4952
5057
|
const root = new Command("asteroid").exitOverride().helpCommand(false).configureOutput({
|
|
4953
5058
|
writeOut: (text2) => stdout += text2,
|
|
@@ -5330,7 +5435,7 @@ var ApiError = class extends Error {
|
|
|
5330
5435
|
status;
|
|
5331
5436
|
body;
|
|
5332
5437
|
};
|
|
5333
|
-
var VERSION = "1.9.
|
|
5438
|
+
var VERSION = "1.9.12";
|
|
5334
5439
|
function problem(status, body, fallback) {
|
|
5335
5440
|
if (body && typeof body === "object") {
|
|
5336
5441
|
const record2 = body;
|
|
@@ -5603,34 +5708,34 @@ import { basename } from "node:path";
|
|
|
5603
5708
|
function camel(segment) {
|
|
5604
5709
|
return segment.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
|
|
5605
5710
|
}
|
|
5606
|
-
function scalar(
|
|
5607
|
-
const type =
|
|
5608
|
-
if (
|
|
5711
|
+
function scalar(option2, raw) {
|
|
5712
|
+
const type = option2.type.replace("[]", "");
|
|
5713
|
+
if (option2.enum && !option2.enum.includes(raw)) {
|
|
5609
5714
|
throw new UsageError(
|
|
5610
|
-
`--${
|
|
5715
|
+
`--${option2.flag} must be one of ${option2.enum.join(", ")}`
|
|
5611
5716
|
);
|
|
5612
5717
|
}
|
|
5613
5718
|
if (type === "integer" || type === "number") {
|
|
5614
5719
|
const value2 = Number(raw);
|
|
5615
5720
|
if (raw.trim() === "" || Number.isNaN(value2) || type === "integer" && !Number.isInteger(value2)) {
|
|
5616
5721
|
throw new UsageError(
|
|
5617
|
-
`--${
|
|
5722
|
+
`--${option2.flag} must be ${type === "integer" ? "an integer" : "a number"}`
|
|
5618
5723
|
);
|
|
5619
5724
|
}
|
|
5620
5725
|
return value2;
|
|
5621
5726
|
}
|
|
5622
5727
|
if (type === "boolean") {
|
|
5623
5728
|
if (raw !== "true" && raw !== "false") {
|
|
5624
|
-
throw new UsageError(`--${
|
|
5729
|
+
throw new UsageError(`--${option2.flag} must be true or false`);
|
|
5625
5730
|
}
|
|
5626
5731
|
return raw === "true";
|
|
5627
5732
|
}
|
|
5628
|
-
if (type === "json") return parseJson(raw, `--${
|
|
5733
|
+
if (type === "json") return parseJson(raw, `--${option2.flag}`);
|
|
5629
5734
|
return raw;
|
|
5630
5735
|
}
|
|
5631
|
-
function value(
|
|
5632
|
-
if (
|
|
5633
|
-
return scalar(
|
|
5736
|
+
function value(option2, raws) {
|
|
5737
|
+
if (option2.type.endsWith("[]")) return raws.map((raw) => scalar(option2, raw));
|
|
5738
|
+
return scalar(option2, raws.at(-1));
|
|
5634
5739
|
}
|
|
5635
5740
|
var nodeReader = {
|
|
5636
5741
|
text: async (path) => readFileSync2(path, "utf8"),
|
|
@@ -5642,12 +5747,12 @@ async function readInput(raw, io) {
|
|
|
5642
5747
|
return raw;
|
|
5643
5748
|
}
|
|
5644
5749
|
async function resolveInputs(command, parsed, io) {
|
|
5645
|
-
const jsonFlags = [...command.query, ...command.body?.fields ?? []].filter((
|
|
5750
|
+
const jsonFlags = [...command.query, ...command.body?.fields ?? []].filter((option2) => option2.type === "json").map((option2) => option2.flag);
|
|
5646
5751
|
const flags = new Map(parsed.flags);
|
|
5647
|
-
for (const
|
|
5648
|
-
const raws = flags.get(
|
|
5752
|
+
for (const flag2 of jsonFlags) {
|
|
5753
|
+
const raws = flags.get(flag2);
|
|
5649
5754
|
if (raws)
|
|
5650
|
-
flags.set(
|
|
5755
|
+
flags.set(flag2, await Promise.all(raws.map((raw) => readInput(raw, io))));
|
|
5651
5756
|
}
|
|
5652
5757
|
const body = parsed.body === void 0 ? void 0 : await readInput(parsed.body, io);
|
|
5653
5758
|
return { ...parsed, flags, body };
|
|
@@ -5697,18 +5802,18 @@ async function buildRequest(command, input, context = {}) {
|
|
|
5697
5802
|
});
|
|
5698
5803
|
const flags = new Map(parsed.flags);
|
|
5699
5804
|
const query = new URLSearchParams();
|
|
5700
|
-
for (const
|
|
5701
|
-
const raws = flags.get(
|
|
5702
|
-
flags.delete(
|
|
5805
|
+
for (const option2 of command.query) {
|
|
5806
|
+
const raws = flags.get(option2.flag);
|
|
5807
|
+
flags.delete(option2.flag);
|
|
5703
5808
|
if (!raws) {
|
|
5704
|
-
if (
|
|
5809
|
+
if (option2.required) throw new UsageError(`--${option2.flag} is required`);
|
|
5705
5810
|
continue;
|
|
5706
5811
|
}
|
|
5707
|
-
const parsedValue = value(
|
|
5812
|
+
const parsedValue = value(option2, raws);
|
|
5708
5813
|
const values = Array.isArray(parsedValue) ? parsedValue : [parsedValue];
|
|
5709
|
-
if (
|
|
5710
|
-
query.append(
|
|
5711
|
-
else for (const v of values) query.append(
|
|
5814
|
+
if (option2.explode === false)
|
|
5815
|
+
query.append(option2.name, values.map(String).join(","));
|
|
5816
|
+
else for (const v of values) query.append(option2.name, String(v));
|
|
5712
5817
|
}
|
|
5713
5818
|
const built = { method: command.method, path, query };
|
|
5714
5819
|
const body = command.body;
|
|
@@ -5758,11 +5863,11 @@ async function buildRequest(command, input, context = {}) {
|
|
|
5758
5863
|
flags.delete(field.flag);
|
|
5759
5864
|
json[field.name] = value(field, raws);
|
|
5760
5865
|
}
|
|
5761
|
-
for (const [
|
|
5762
|
-
const [head, ...rest] =
|
|
5866
|
+
for (const [flag2, raws] of [...flags]) {
|
|
5867
|
+
const [head, ...rest] = flag2.split(".");
|
|
5763
5868
|
const field = body.fields.find((f) => f.flag === head);
|
|
5764
5869
|
if (!field || rest.length === 0) continue;
|
|
5765
|
-
flags.delete(
|
|
5870
|
+
flags.delete(flag2);
|
|
5766
5871
|
const current = json[field.name];
|
|
5767
5872
|
json[field.name] = typeof current === "object" && current !== null ? current : {};
|
|
5768
5873
|
setPath(json, [field.name, ...rest.map(camel)], loose(raws.at(-1)));
|
|
@@ -5794,6 +5899,708 @@ function rejectUnknown(flags) {
|
|
|
5794
5899
|
}
|
|
5795
5900
|
}
|
|
5796
5901
|
|
|
5902
|
+
// src/sync/activities.ts
|
|
5903
|
+
function indentJson(data) {
|
|
5904
|
+
return JSON.stringify(data, null, 2).split("\n").map((line2) => ` ${line2}`).join("\n");
|
|
5905
|
+
}
|
|
5906
|
+
function action(label, payload) {
|
|
5907
|
+
const info = payload.info;
|
|
5908
|
+
const detail = info && Object.keys(info).length > 0 ? `
|
|
5909
|
+
info:
|
|
5910
|
+
${indentJson(info)}` : "";
|
|
5911
|
+
return `[${label}: ${payload.actionName}]:${detail}
|
|
5912
|
+
`;
|
|
5913
|
+
}
|
|
5914
|
+
function line(payload) {
|
|
5915
|
+
switch (payload.activityType) {
|
|
5916
|
+
case "reasoning":
|
|
5917
|
+
return `[reasoning]:
|
|
5918
|
+
${payload.reasoning}
|
|
5919
|
+
`;
|
|
5920
|
+
case "generic":
|
|
5921
|
+
return `[message]:
|
|
5922
|
+
${payload.message}
|
|
5923
|
+
`;
|
|
5924
|
+
case "user_message_received":
|
|
5925
|
+
return `[user_message]:
|
|
5926
|
+
${payload.message}
|
|
5927
|
+
`;
|
|
5928
|
+
case "action_started":
|
|
5929
|
+
return action("action_started", payload);
|
|
5930
|
+
case "action_completed":
|
|
5931
|
+
return action("action_completed", payload);
|
|
5932
|
+
case "action_failed":
|
|
5933
|
+
return `[action_failed: ${payload.actionName}]:
|
|
5934
|
+
failure: ${payload.message}
|
|
5935
|
+
`;
|
|
5936
|
+
case "compaction_performed":
|
|
5937
|
+
return `[compaction]: summary_tokens: ${payload.summaryTokens}, duration_ms: ${payload.durationMs}
|
|
5938
|
+
`;
|
|
5939
|
+
case "compaction_started":
|
|
5940
|
+
return "[compaction_started]\n";
|
|
5941
|
+
default: {
|
|
5942
|
+
const summary2 = payload.display?.summaryText;
|
|
5943
|
+
return summary2 ? `[${payload.activityType}]: ${summary2}
|
|
5944
|
+
` : `[${payload.activityType}]
|
|
5945
|
+
`;
|
|
5946
|
+
}
|
|
5947
|
+
}
|
|
5948
|
+
}
|
|
5949
|
+
function formatActivitiesMarkdown(activities, status, createdAt) {
|
|
5950
|
+
const lines = [
|
|
5951
|
+
"# Execution Activities\n",
|
|
5952
|
+
`Status: ${status}`,
|
|
5953
|
+
`Started: ${createdAt}`
|
|
5954
|
+
];
|
|
5955
|
+
let currentTurn = "";
|
|
5956
|
+
for (const { payload } of activities) {
|
|
5957
|
+
const turnId = payload.display?.turnId ?? "";
|
|
5958
|
+
if (turnId && turnId !== currentTurn) {
|
|
5959
|
+
currentTurn = turnId;
|
|
5960
|
+
lines.push(`
|
|
5961
|
+
---
|
|
5962
|
+
|
|
5963
|
+
## ${turnId}
|
|
5964
|
+
`);
|
|
5965
|
+
} else if (!currentTurn && !turnId) {
|
|
5966
|
+
currentTurn = "_started";
|
|
5967
|
+
lines.push("\n---\n");
|
|
5968
|
+
}
|
|
5969
|
+
lines.push(line(payload));
|
|
5970
|
+
}
|
|
5971
|
+
return lines.join("\n");
|
|
5972
|
+
}
|
|
5973
|
+
|
|
5974
|
+
// ../workflow-files/src/representation/name-charset.ts
|
|
5975
|
+
var NAME_CHARSET_RE = /^[A-Za-z0-9 !#$%&'()+,.;=@^`{}[\]~-]+$/;
|
|
5976
|
+
var WINDOWS_RESERVED = /* @__PURE__ */ new Set([
|
|
5977
|
+
"con",
|
|
5978
|
+
"prn",
|
|
5979
|
+
"aux",
|
|
5980
|
+
"nul",
|
|
5981
|
+
...Array.from({ length: 9 }, (_, i) => `com${i + 1}`),
|
|
5982
|
+
...Array.from({ length: 9 }, (_, i) => `lpt${i + 1}`)
|
|
5983
|
+
]);
|
|
5984
|
+
function isNameCharsetConforming(name) {
|
|
5985
|
+
return NAME_CHARSET_RE.test(name);
|
|
5986
|
+
}
|
|
5987
|
+
|
|
5988
|
+
// ../workflow-files/src/representation/types.ts
|
|
5989
|
+
function normalizeNodeName(name) {
|
|
5990
|
+
let result = name.toLowerCase();
|
|
5991
|
+
result = result.replace(/[^a-z0-9]+/g, "_");
|
|
5992
|
+
result = result.replace(/^_+|_+$/g, "");
|
|
5993
|
+
if (result.length > 80) {
|
|
5994
|
+
result = result.slice(0, 80).replace(/_+$/, "");
|
|
5995
|
+
}
|
|
5996
|
+
if (result === "") {
|
|
5997
|
+
throw new Error(
|
|
5998
|
+
`representation: node name "${name}" produces an empty directory name after normalization`
|
|
5999
|
+
);
|
|
6000
|
+
}
|
|
6001
|
+
return result;
|
|
6002
|
+
}
|
|
6003
|
+
function nodeDirName(name) {
|
|
6004
|
+
if (isNameCharsetConforming(name)) {
|
|
6005
|
+
return name.replaceAll(" ", "_");
|
|
6006
|
+
}
|
|
6007
|
+
return normalizeNodeName(name);
|
|
6008
|
+
}
|
|
6009
|
+
function agentDirName(name) {
|
|
6010
|
+
try {
|
|
6011
|
+
return nodeDirName(name);
|
|
6012
|
+
} catch {
|
|
6013
|
+
return "agent";
|
|
6014
|
+
}
|
|
6015
|
+
}
|
|
6016
|
+
function executionDirName(executionId) {
|
|
6017
|
+
return executionId.slice(0, 8);
|
|
6018
|
+
}
|
|
6019
|
+
function workflowDirName(workflowId, version, parentId, parentVersion) {
|
|
6020
|
+
const short = workflowId.slice(0, 8);
|
|
6021
|
+
if (version !== void 0 && version !== null) {
|
|
6022
|
+
return `v${version}_${short}`;
|
|
6023
|
+
}
|
|
6024
|
+
if (parentId) {
|
|
6025
|
+
if (parentVersion !== void 0 && parentVersion !== null) {
|
|
6026
|
+
return `draft_${short}_from_v${parentVersion}`;
|
|
6027
|
+
}
|
|
6028
|
+
return `draft_${short}_from_draft_${parentId.slice(0, 8)}`;
|
|
6029
|
+
}
|
|
6030
|
+
return `draft_${short}`;
|
|
6031
|
+
}
|
|
6032
|
+
|
|
6033
|
+
// src/sync/paths.ts
|
|
6034
|
+
import { createHash } from "node:crypto";
|
|
6035
|
+
function join3(...parts) {
|
|
6036
|
+
return parts.filter((part) => part !== "").join("/").replace(/\/{2,}/g, "/");
|
|
6037
|
+
}
|
|
6038
|
+
function workflowRoot(root, workflowName) {
|
|
6039
|
+
return join3(root, agentDirName(workflowName));
|
|
6040
|
+
}
|
|
6041
|
+
function isContained(path) {
|
|
6042
|
+
if (path === "" || path.startsWith("/") || path.includes("\\")) return false;
|
|
6043
|
+
return path.split("/").every((segment) => segment !== ".." && segment !== "");
|
|
6044
|
+
}
|
|
6045
|
+
function sha256(bytes) {
|
|
6046
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
6047
|
+
}
|
|
6048
|
+
function matchesGlob(path, pattern) {
|
|
6049
|
+
let source = "";
|
|
6050
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
6051
|
+
const ch = pattern[i];
|
|
6052
|
+
if (ch === "*" && pattern[i + 1] === "*" && pattern[i + 2] === "/") {
|
|
6053
|
+
source += "(?:.*/)?";
|
|
6054
|
+
i += 2;
|
|
6055
|
+
} else if (ch === "*" && pattern[i + 1] === "*") {
|
|
6056
|
+
source += ".*";
|
|
6057
|
+
i += 1;
|
|
6058
|
+
} else if (ch === "*") source += "[^/]*";
|
|
6059
|
+
else if (ch === "?") source += "[^/]";
|
|
6060
|
+
else source += ch.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
6061
|
+
}
|
|
6062
|
+
return new RegExp(`^${source}$`).test(path);
|
|
6063
|
+
}
|
|
6064
|
+
|
|
6065
|
+
// src/sync/types.ts
|
|
6066
|
+
var MANIFEST = ".asteroid.json";
|
|
6067
|
+
function get(path, query = {}) {
|
|
6068
|
+
return { method: "GET", path, query: new URLSearchParams(query) };
|
|
6069
|
+
}
|
|
6070
|
+
function statusOf(error) {
|
|
6071
|
+
const status = error?.status;
|
|
6072
|
+
return typeof status === "number" ? status : void 0;
|
|
6073
|
+
}
|
|
6074
|
+
|
|
6075
|
+
// src/sync/execution-pull.ts
|
|
6076
|
+
var DIRECTORIES = ["workspace", "downloads", "shared"];
|
|
6077
|
+
function relativeFilePath(file) {
|
|
6078
|
+
const at = file.filePath.indexOf(`/${file.directory}/`);
|
|
6079
|
+
if (at === -1)
|
|
6080
|
+
return `${file.directory}/${file.filePath.split("/").pop() ?? "unknown"}`;
|
|
6081
|
+
return file.filePath.slice(at + 1).replace(/^workspace\/\.execution-context\//, "");
|
|
6082
|
+
}
|
|
6083
|
+
function executionDir(options, execution) {
|
|
6084
|
+
const chosen = options.dir?.(execution);
|
|
6085
|
+
if (chosen) return chosen;
|
|
6086
|
+
const root = workflowRoot(
|
|
6087
|
+
options.root,
|
|
6088
|
+
execution.workflowName ?? execution.workflowId
|
|
6089
|
+
);
|
|
6090
|
+
return join3(root, "executions", executionDirName(execution.id));
|
|
6091
|
+
}
|
|
6092
|
+
async function writeText(sync, path, text2) {
|
|
6093
|
+
await sync.files.write(path, Buffer.from(text2, "utf8"));
|
|
6094
|
+
}
|
|
6095
|
+
async function writeActivities(sync, dir, execution) {
|
|
6096
|
+
try {
|
|
6097
|
+
const activities = await sync.remote.json(
|
|
6098
|
+
get(`/agents/v2/executions/${execution.id}/activities`, { order: "asc" })
|
|
6099
|
+
);
|
|
6100
|
+
if (activities.length === 0) return false;
|
|
6101
|
+
const markdown = formatActivitiesMarkdown(
|
|
6102
|
+
activities,
|
|
6103
|
+
execution.status,
|
|
6104
|
+
execution.createdAt
|
|
6105
|
+
);
|
|
6106
|
+
await writeText(sync, join3(dir, "activities.md"), markdown);
|
|
6107
|
+
return true;
|
|
6108
|
+
} catch {
|
|
6109
|
+
return false;
|
|
6110
|
+
}
|
|
6111
|
+
}
|
|
6112
|
+
async function listFiles(sync, executionId) {
|
|
6113
|
+
const listing = await sync.remote.json(
|
|
6114
|
+
get(`/agents/v2/executions/${executionId}/files`)
|
|
6115
|
+
);
|
|
6116
|
+
return DIRECTORIES.flatMap((name) => listing.directories[name] ?? []);
|
|
6117
|
+
}
|
|
6118
|
+
async function download(sync, executionId, file) {
|
|
6119
|
+
const path = `/agents/v2/executions/${executionId}/files/${file.id}/signed-url`;
|
|
6120
|
+
const { url } = await sync.remote.json(get(path));
|
|
6121
|
+
return sync.remote.download(url);
|
|
6122
|
+
}
|
|
6123
|
+
function selectFiles(files, options, skipped) {
|
|
6124
|
+
return files.filter((file) => {
|
|
6125
|
+
const path = relativeFilePath(file);
|
|
6126
|
+
if (!isContained(path)) skipped.push(`${path} (unsafe path)`);
|
|
6127
|
+
else if (options.glob !== void 0 && !matchesGlob(path, options.glob))
|
|
6128
|
+
skipped.push(`${path} (excluded by --files)`);
|
|
6129
|
+
else if (options.maxFileBytes !== void 0 && file.fileSize > options.maxFileBytes)
|
|
6130
|
+
skipped.push(`${path} (${file.fileSize} bytes, over the size limit)`);
|
|
6131
|
+
else return true;
|
|
6132
|
+
return false;
|
|
6133
|
+
});
|
|
6134
|
+
}
|
|
6135
|
+
async function pullFiles(sync, dir, options) {
|
|
6136
|
+
const skipped = [];
|
|
6137
|
+
const failed = [];
|
|
6138
|
+
let files;
|
|
6139
|
+
try {
|
|
6140
|
+
files = await listFiles(sync, options.executionId);
|
|
6141
|
+
} catch (error) {
|
|
6142
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
6143
|
+
return {
|
|
6144
|
+
downloaded: 0,
|
|
6145
|
+
candidates: 0,
|
|
6146
|
+
skipped,
|
|
6147
|
+
failed: [`file listing (${reason})`]
|
|
6148
|
+
};
|
|
6149
|
+
}
|
|
6150
|
+
let downloaded = 0;
|
|
6151
|
+
await Promise.all(
|
|
6152
|
+
selectFiles(files, options, skipped).map(async (file) => {
|
|
6153
|
+
try {
|
|
6154
|
+
const bytes = await download(sync, options.executionId, file);
|
|
6155
|
+
await sync.files.write(join3(dir, relativeFilePath(file)), bytes);
|
|
6156
|
+
downloaded++;
|
|
6157
|
+
} catch (error) {
|
|
6158
|
+
failed.push(
|
|
6159
|
+
`${file.fileName} (${error instanceof Error ? error.message : String(error)})`
|
|
6160
|
+
);
|
|
6161
|
+
}
|
|
6162
|
+
})
|
|
6163
|
+
);
|
|
6164
|
+
return { downloaded, candidates: files.length, skipped, failed };
|
|
6165
|
+
}
|
|
6166
|
+
async function pullExecution(sync, options) {
|
|
6167
|
+
const execution = await sync.remote.json(
|
|
6168
|
+
get(`/agents/v2/executions/${options.executionId}`)
|
|
6169
|
+
);
|
|
6170
|
+
const dir = executionDir(options, execution);
|
|
6171
|
+
await writeText(
|
|
6172
|
+
sync,
|
|
6173
|
+
join3(dir, "summary.json"),
|
|
6174
|
+
JSON.stringify(execution, null, 2)
|
|
6175
|
+
);
|
|
6176
|
+
const result = {
|
|
6177
|
+
execution,
|
|
6178
|
+
dir,
|
|
6179
|
+
written: ["summary.json"],
|
|
6180
|
+
downloaded: 0,
|
|
6181
|
+
candidates: 0,
|
|
6182
|
+
skipped: [],
|
|
6183
|
+
failed: []
|
|
6184
|
+
};
|
|
6185
|
+
if (execution.purgedAt) return result;
|
|
6186
|
+
if (options.activities !== false && await writeActivities(sync, dir, execution))
|
|
6187
|
+
result.written.push("activities.md");
|
|
6188
|
+
if (options.files === false) {
|
|
6189
|
+
return {
|
|
6190
|
+
...result,
|
|
6191
|
+
skippedBy: options.activities === false ? "--summary-only" : "--no-files"
|
|
6192
|
+
};
|
|
6193
|
+
}
|
|
6194
|
+
return { ...result, ...await pullFiles(sync, dir, options) };
|
|
6195
|
+
}
|
|
6196
|
+
function filesLine(result) {
|
|
6197
|
+
const parts = [...result.written];
|
|
6198
|
+
if (result.downloaded > 0) parts.push(`${result.downloaded} downloaded`);
|
|
6199
|
+
const counted = `Files: ${result.written.length + result.downloaded} written (${parts.join(", ")})`;
|
|
6200
|
+
if (result.skippedBy)
|
|
6201
|
+
return `${counted}; downloads skipped (${result.skippedBy})`;
|
|
6202
|
+
if (result.candidates === 0) return `${counted}; no execution files matched`;
|
|
6203
|
+
return counted;
|
|
6204
|
+
}
|
|
6205
|
+
function failures(execution) {
|
|
6206
|
+
const list = execution.scriptFailures ?? [];
|
|
6207
|
+
if (list.length === 0) return null;
|
|
6208
|
+
const lines = list.map(
|
|
6209
|
+
(f) => ` - ${f.nodeName ?? "(unknown node)"}: ${(f.error ?? "").split("\n")[0]}`
|
|
6210
|
+
);
|
|
6211
|
+
return `Script failures: ${list.length}
|
|
6212
|
+
${lines.join("\n")}`;
|
|
6213
|
+
}
|
|
6214
|
+
function purged(result) {
|
|
6215
|
+
const { execution } = result;
|
|
6216
|
+
return [
|
|
6217
|
+
`Execution: ${execution.id}`,
|
|
6218
|
+
`Status: ${execution.status}`,
|
|
6219
|
+
`Purged: ${execution.purgedAt}`,
|
|
6220
|
+
"This run was retention-purged. Activities, files, inputs and result are gone. Do not diagnose it.",
|
|
6221
|
+
`Written to: ${result.dir}/`
|
|
6222
|
+
].join("\n");
|
|
6223
|
+
}
|
|
6224
|
+
function formatExecutionPull(result) {
|
|
6225
|
+
const { execution } = result;
|
|
6226
|
+
if (execution.purgedAt) return purged(result);
|
|
6227
|
+
const outcome = execution.executionResult;
|
|
6228
|
+
return [
|
|
6229
|
+
`Execution: ${execution.id}`,
|
|
6230
|
+
`Status: ${execution.status}`,
|
|
6231
|
+
`Workflow: ${execution.workflowName ?? execution.workflowId}`,
|
|
6232
|
+
execution.workflowVersionId ? `Workflow version: ${execution.workflowVersionId}` : null,
|
|
6233
|
+
execution.inputs ? `Inputs: ${JSON.stringify(execution.inputs)}` : null,
|
|
6234
|
+
outcome ? `Result: ${outcome.outcome}: ${outcome.reasoning}` : null,
|
|
6235
|
+
`Created: ${execution.createdAt}`,
|
|
6236
|
+
execution.terminalAt ? `Completed: ${execution.terminalAt}` : null,
|
|
6237
|
+
execution.duration != null ? `Duration: ${execution.duration.toFixed(1)}s` : null,
|
|
6238
|
+
failures(execution),
|
|
6239
|
+
filesLine(result),
|
|
6240
|
+
result.skipped.length > 0 ? `Skipped:
|
|
6241
|
+
${result.skipped.map((s) => ` - ${s}`).join("\n")}` : null,
|
|
6242
|
+
result.failed.length > 0 ? `Failed: ${result.failed.join(", ")}` : null,
|
|
6243
|
+
`Written to: ${result.dir}/`
|
|
6244
|
+
].filter(Boolean).join("\n");
|
|
6245
|
+
}
|
|
6246
|
+
|
|
6247
|
+
// src/sync/node-files.ts
|
|
6248
|
+
import { mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
6249
|
+
import { dirname as dirname2, relative, resolve, sep } from "node:path";
|
|
6250
|
+
function missing(error) {
|
|
6251
|
+
return error.code === "ENOENT";
|
|
6252
|
+
}
|
|
6253
|
+
async function walk(root, dir, out) {
|
|
6254
|
+
for (const entry of await readdir(dir, { withFileTypes: true })) {
|
|
6255
|
+
const path = resolve(dir, entry.name);
|
|
6256
|
+
if (entry.isDirectory()) await walk(root, path, out);
|
|
6257
|
+
else if (entry.isFile())
|
|
6258
|
+
out.push(relative(root, path).split(sep).join("/"));
|
|
6259
|
+
}
|
|
6260
|
+
}
|
|
6261
|
+
function nodeFiles(cwd) {
|
|
6262
|
+
const at = (path) => resolve(cwd, path);
|
|
6263
|
+
return {
|
|
6264
|
+
async read(path) {
|
|
6265
|
+
try {
|
|
6266
|
+
return new Uint8Array(await readFile(at(path)));
|
|
6267
|
+
} catch (error) {
|
|
6268
|
+
if (missing(error)) return void 0;
|
|
6269
|
+
throw error;
|
|
6270
|
+
}
|
|
6271
|
+
},
|
|
6272
|
+
async write(path, bytes) {
|
|
6273
|
+
await mkdir(dirname2(at(path)), { recursive: true });
|
|
6274
|
+
await writeFile(at(path), bytes);
|
|
6275
|
+
},
|
|
6276
|
+
async remove(path) {
|
|
6277
|
+
await rm(at(path), { force: true });
|
|
6278
|
+
},
|
|
6279
|
+
async list(dir) {
|
|
6280
|
+
const out = [];
|
|
6281
|
+
try {
|
|
6282
|
+
await walk(at(dir), at(dir), out);
|
|
6283
|
+
} catch (error) {
|
|
6284
|
+
if (!missing(error)) throw error;
|
|
6285
|
+
}
|
|
6286
|
+
return out.sort();
|
|
6287
|
+
}
|
|
6288
|
+
};
|
|
6289
|
+
}
|
|
6290
|
+
|
|
6291
|
+
// src/sync/workflow-pull.ts
|
|
6292
|
+
var CONCURRENCY = 16;
|
|
6293
|
+
function pullDir(root, tree, head) {
|
|
6294
|
+
const base = workflowRoot(root, tree.workflowName);
|
|
6295
|
+
if (head) return join3(base, "head");
|
|
6296
|
+
return join3(base, ".versions", workflowDirName(tree.versionId, tree.version));
|
|
6297
|
+
}
|
|
6298
|
+
async function agentFileBytes(sync, workflowId, tree, entry) {
|
|
6299
|
+
const path = `/agents/v2/workflows/${workflowId}/versions/${tree.versionId}/files/${entry.fileId}/signed-url`;
|
|
6300
|
+
const { url } = await sync.remote.json(get(path));
|
|
6301
|
+
return sync.remote.download(url);
|
|
6302
|
+
}
|
|
6303
|
+
async function entryBytes(sync, workflowId, tree, entry) {
|
|
6304
|
+
if (entry.contentBase64 !== void 0)
|
|
6305
|
+
return Buffer.from(entry.contentBase64, "base64");
|
|
6306
|
+
if (entry.content !== void 0) return Buffer.from(entry.content, "utf8");
|
|
6307
|
+
if (!entry.fileId) return void 0;
|
|
6308
|
+
return agentFileBytes(sync, workflowId, tree, entry);
|
|
6309
|
+
}
|
|
6310
|
+
async function writeEntries(sync, options, tree, dir) {
|
|
6311
|
+
const hashes = {};
|
|
6312
|
+
const skipped = [];
|
|
6313
|
+
let shared = 0;
|
|
6314
|
+
const wanted = tree.files.filter((entry) => {
|
|
6315
|
+
if (!isContained(entry.path)) skipped.push(`${entry.path} (unsafe path)`);
|
|
6316
|
+
else if (options.maxFileBytes !== void 0 && entry.size > options.maxFileBytes)
|
|
6317
|
+
skipped.push(`${entry.path} (${entry.size} bytes, over the size limit)`);
|
|
6318
|
+
else return true;
|
|
6319
|
+
return false;
|
|
6320
|
+
});
|
|
6321
|
+
for (let i = 0; i < wanted.length; i += CONCURRENCY) {
|
|
6322
|
+
await Promise.all(
|
|
6323
|
+
wanted.slice(i, i + CONCURRENCY).map(async (entry) => {
|
|
6324
|
+
const bytes = await entryBytes(sync, options.workflowId, tree, entry);
|
|
6325
|
+
if (!bytes) return void skipped.push(`${entry.path} (no content)`);
|
|
6326
|
+
await sync.files.write(join3(dir, entry.path), bytes);
|
|
6327
|
+
hashes[entry.path] = sha256(bytes);
|
|
6328
|
+
if (entry.kind === "agent_file") shared++;
|
|
6329
|
+
})
|
|
6330
|
+
);
|
|
6331
|
+
}
|
|
6332
|
+
return { hashes, skipped, shared };
|
|
6333
|
+
}
|
|
6334
|
+
async function removeStale(sync, dir, keep) {
|
|
6335
|
+
for (const path of await sync.files.list(dir)) {
|
|
6336
|
+
if (!keep.has(path) && path !== MANIFEST)
|
|
6337
|
+
await sync.files.remove(join3(dir, path));
|
|
6338
|
+
}
|
|
6339
|
+
}
|
|
6340
|
+
async function writeManifest(sync, dir, manifest) {
|
|
6341
|
+
const text2 = `${JSON.stringify(manifest, null, 2)}
|
|
6342
|
+
`;
|
|
6343
|
+
await sync.files.write(join3(dir, MANIFEST), Buffer.from(text2, "utf8"));
|
|
6344
|
+
}
|
|
6345
|
+
async function pullWorkflow(sync, options) {
|
|
6346
|
+
const request = {
|
|
6347
|
+
...options.request,
|
|
6348
|
+
query: new URLSearchParams(options.request.query)
|
|
6349
|
+
};
|
|
6350
|
+
request.query.set("contents", "all");
|
|
6351
|
+
const tree = await sync.remote.json(request);
|
|
6352
|
+
const head = request.path.endsWith("/head/files");
|
|
6353
|
+
const dir = options.dir ?? pullDir(options.root ?? "", tree, head);
|
|
6354
|
+
const { hashes, skipped, shared } = await writeEntries(
|
|
6355
|
+
sync,
|
|
6356
|
+
options,
|
|
6357
|
+
tree,
|
|
6358
|
+
dir
|
|
6359
|
+
);
|
|
6360
|
+
await removeStale(sync, dir, new Set(Object.keys(hashes)));
|
|
6361
|
+
if (options.manifest !== false) {
|
|
6362
|
+
await writeManifest(sync, dir, {
|
|
6363
|
+
workflowId: options.workflowId,
|
|
6364
|
+
versionId: tree.versionId,
|
|
6365
|
+
head,
|
|
6366
|
+
rev: tree.rev,
|
|
6367
|
+
files: hashes
|
|
6368
|
+
});
|
|
6369
|
+
}
|
|
6370
|
+
return {
|
|
6371
|
+
workflowId: options.workflowId,
|
|
6372
|
+
workflowName: tree.workflowName,
|
|
6373
|
+
versionId: tree.versionId,
|
|
6374
|
+
version: tree.version,
|
|
6375
|
+
rev: tree.rev,
|
|
6376
|
+
dir,
|
|
6377
|
+
head,
|
|
6378
|
+
written: Object.keys(hashes).length,
|
|
6379
|
+
shared,
|
|
6380
|
+
skipped
|
|
6381
|
+
};
|
|
6382
|
+
}
|
|
6383
|
+
function formatWorkflowPull(result) {
|
|
6384
|
+
const definition = result.written - result.shared;
|
|
6385
|
+
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)`;
|
|
6386
|
+
return [
|
|
6387
|
+
`Workflow: ${result.workflowName} (${result.workflowId})`,
|
|
6388
|
+
`Workflow version: ${result.versionId}${result.version ? ` (v${result.version})` : " (draft)"}`,
|
|
6389
|
+
`Written to: ${result.dir}/`,
|
|
6390
|
+
files,
|
|
6391
|
+
result.skipped.length > 0 ? `Skipped:
|
|
6392
|
+
${result.skipped.map((s) => ` - ${s}`).join("\n")}` : null,
|
|
6393
|
+
result.head ? `Edit the files, then run \`asteroid workflow push\` from ${result.dir}/.` : "This is a pinned version. It is read-only."
|
|
6394
|
+
].filter(Boolean).join("\n");
|
|
6395
|
+
}
|
|
6396
|
+
|
|
6397
|
+
// src/sync/workflow-push.ts
|
|
6398
|
+
async function readManifest(sync, dir) {
|
|
6399
|
+
const bytes = await sync.files.read(join3(dir, MANIFEST));
|
|
6400
|
+
if (!bytes) {
|
|
6401
|
+
throw new UsageError(
|
|
6402
|
+
`no pulled workflow in ${dir}. Run \`asteroid workflow pull <workflow-id>\`, then push from the head folder it writes.`
|
|
6403
|
+
);
|
|
6404
|
+
}
|
|
6405
|
+
const manifest = JSON.parse(Buffer.from(bytes).toString("utf8"));
|
|
6406
|
+
if (!manifest.head) {
|
|
6407
|
+
throw new UsageError(
|
|
6408
|
+
`${dir} holds a pinned workflow version, which is read-only. Push from a pulled head.`
|
|
6409
|
+
);
|
|
6410
|
+
}
|
|
6411
|
+
return manifest;
|
|
6412
|
+
}
|
|
6413
|
+
async function localChanges(sync, dir, manifest) {
|
|
6414
|
+
const local2 = (await sync.files.list(dir)).filter(
|
|
6415
|
+
(path) => path !== MANIFEST
|
|
6416
|
+
);
|
|
6417
|
+
const writes = [];
|
|
6418
|
+
for (const path of local2) {
|
|
6419
|
+
const bytes = await sync.files.read(join3(dir, path));
|
|
6420
|
+
if (!bytes) continue;
|
|
6421
|
+
const hash = sha256(bytes);
|
|
6422
|
+
if (manifest.files[path] === hash) continue;
|
|
6423
|
+
writes.push({
|
|
6424
|
+
path,
|
|
6425
|
+
contentBase64: Buffer.from(bytes).toString("base64"),
|
|
6426
|
+
hash
|
|
6427
|
+
});
|
|
6428
|
+
}
|
|
6429
|
+
const present = new Set(local2);
|
|
6430
|
+
const deletes = Object.keys(manifest.files).filter(
|
|
6431
|
+
(path) => !present.has(path)
|
|
6432
|
+
);
|
|
6433
|
+
return { writes, deletes };
|
|
6434
|
+
}
|
|
6435
|
+
async function patch(sync, manifest, writes, deletes) {
|
|
6436
|
+
try {
|
|
6437
|
+
await sync.remote.json({
|
|
6438
|
+
method: "PATCH",
|
|
6439
|
+
path: `/agents/v2/workflows/${manifest.workflowId}/head/files`,
|
|
6440
|
+
query: new URLSearchParams(),
|
|
6441
|
+
json: { baseRev: manifest.rev, writes, deletes }
|
|
6442
|
+
});
|
|
6443
|
+
} catch (error) {
|
|
6444
|
+
if (statusOf(error) !== 409) throw error;
|
|
6445
|
+
throw new UsageError(
|
|
6446
|
+
`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.`
|
|
6447
|
+
);
|
|
6448
|
+
}
|
|
6449
|
+
}
|
|
6450
|
+
async function refresh2(sync, dir, manifest, pushed) {
|
|
6451
|
+
const tree = await sync.remote.json(
|
|
6452
|
+
get(`/agents/v2/workflows/${manifest.workflowId}/head/files`)
|
|
6453
|
+
);
|
|
6454
|
+
const files = {};
|
|
6455
|
+
for (const entry of tree.files) {
|
|
6456
|
+
if (entry.content !== void 0) {
|
|
6457
|
+
const bytes = Buffer.from(entry.content, "utf8");
|
|
6458
|
+
files[entry.path] = sha256(bytes);
|
|
6459
|
+
if (pushed.get(entry.path) !== files[entry.path])
|
|
6460
|
+
await sync.files.write(join3(dir, entry.path), bytes);
|
|
6461
|
+
} else {
|
|
6462
|
+
const local2 = await sync.files.read(join3(dir, entry.path));
|
|
6463
|
+
const hash = local2 ? sha256(local2) : pushed.get(entry.path) ?? entry.checksum;
|
|
6464
|
+
if (hash) files[entry.path] = hash;
|
|
6465
|
+
}
|
|
6466
|
+
}
|
|
6467
|
+
for (const path of await sync.files.list(dir)) {
|
|
6468
|
+
if (path !== MANIFEST && !(path in files))
|
|
6469
|
+
await sync.files.remove(join3(dir, path));
|
|
6470
|
+
}
|
|
6471
|
+
const next = { ...manifest, versionId: tree.versionId, rev: tree.rev, files };
|
|
6472
|
+
await writeManifest(sync, dir, next);
|
|
6473
|
+
return next;
|
|
6474
|
+
}
|
|
6475
|
+
async function pushWorkflow(sync, dir, workflowId) {
|
|
6476
|
+
const manifest = await readManifest(sync, dir);
|
|
6477
|
+
if (workflowId !== void 0 && manifest.workflowId !== workflowId) {
|
|
6478
|
+
throw new UsageError(
|
|
6479
|
+
`${dir} holds workflow ${manifest.workflowId}, not ${workflowId}.`
|
|
6480
|
+
);
|
|
6481
|
+
}
|
|
6482
|
+
const { writes, deletes } = await localChanges(sync, dir, manifest);
|
|
6483
|
+
const result = {
|
|
6484
|
+
workflowId: manifest.workflowId,
|
|
6485
|
+
dir,
|
|
6486
|
+
written: writes.map((w) => w.path),
|
|
6487
|
+
deleted: deletes
|
|
6488
|
+
};
|
|
6489
|
+
if (writes.length === 0 && deletes.length === 0) {
|
|
6490
|
+
return { ...result, rev: manifest.rev, versionId: manifest.versionId };
|
|
6491
|
+
}
|
|
6492
|
+
await patch(
|
|
6493
|
+
sync,
|
|
6494
|
+
manifest,
|
|
6495
|
+
writes.map(({ path, contentBase64 }) => ({ path, contentBase64 })),
|
|
6496
|
+
deletes
|
|
6497
|
+
);
|
|
6498
|
+
const next = await refresh2(
|
|
6499
|
+
sync,
|
|
6500
|
+
dir,
|
|
6501
|
+
manifest,
|
|
6502
|
+
new Map(writes.map((w) => [w.path, w.hash]))
|
|
6503
|
+
);
|
|
6504
|
+
return { ...result, rev: next.rev, versionId: next.versionId };
|
|
6505
|
+
}
|
|
6506
|
+
function formatWorkflowPush(result) {
|
|
6507
|
+
if (result.written.length === 0 && result.deleted.length === 0) {
|
|
6508
|
+
return `Nothing to push: ${result.dir}/ matches the head you pulled (rev ${result.rev}).`;
|
|
6509
|
+
}
|
|
6510
|
+
return [
|
|
6511
|
+
`Pushed to workflow ${result.workflowId}: ${result.written.length} written, ${result.deleted.length} deleted.`,
|
|
6512
|
+
...result.written.map((path) => ` + ${path}`),
|
|
6513
|
+
...result.deleted.map((path) => ` - ${path}`),
|
|
6514
|
+
`Head is now rev ${result.rev} (version ${result.versionId}).`
|
|
6515
|
+
].join("\n");
|
|
6516
|
+
}
|
|
6517
|
+
|
|
6518
|
+
// src/sync/run.ts
|
|
6519
|
+
function flag(parsed, name) {
|
|
6520
|
+
return parsed.flags.get(name)?.at(-1);
|
|
6521
|
+
}
|
|
6522
|
+
function integer(parsed, name) {
|
|
6523
|
+
const raw = flag(parsed, name);
|
|
6524
|
+
if (raw === void 0) return void 0;
|
|
6525
|
+
if (!/^\d+$/.test(raw))
|
|
6526
|
+
throw new UsageError(`--${name} must be a whole number`);
|
|
6527
|
+
return Number(raw);
|
|
6528
|
+
}
|
|
6529
|
+
function executionOptions(parsed) {
|
|
6530
|
+
const summaryOnly = flag(parsed, "summary-only") === "true";
|
|
6531
|
+
const glob = flag(parsed, "files");
|
|
6532
|
+
const dir = flag(parsed, "dir");
|
|
6533
|
+
return {
|
|
6534
|
+
executionId: parsed.positionals[0],
|
|
6535
|
+
activities: !summaryOnly,
|
|
6536
|
+
files: !summaryOnly && glob !== "false",
|
|
6537
|
+
glob: glob === "false" ? void 0 : glob,
|
|
6538
|
+
maxFileBytes: integer(parsed, "max-file-size"),
|
|
6539
|
+
dir: dir ? () => dir : void 0
|
|
6540
|
+
};
|
|
6541
|
+
}
|
|
6542
|
+
function withoutFlag(parsed, name) {
|
|
6543
|
+
const flags = new Map(parsed.flags);
|
|
6544
|
+
flags.delete(name);
|
|
6545
|
+
return { ...parsed, flags };
|
|
6546
|
+
}
|
|
6547
|
+
function runsLocally(command, parsed) {
|
|
6548
|
+
if (command.local) return true;
|
|
6549
|
+
if (command.files === "pull") return !parsed.json;
|
|
6550
|
+
if (command.files === "push") return parsed.body === void 0;
|
|
6551
|
+
return false;
|
|
6552
|
+
}
|
|
6553
|
+
var PARTIAL = ["paths", "variants"];
|
|
6554
|
+
function rejectPartial(parsed) {
|
|
6555
|
+
const used = PARTIAL.filter((name) => parsed.flags.has(name));
|
|
6556
|
+
if (used.length === 0) return;
|
|
6557
|
+
throw new UsageError(
|
|
6558
|
+
`pull writes the whole tree, so it cannot take ${used.map((name) => `--${name}`).join(" or ")}. Add --json to list those files instead.`
|
|
6559
|
+
);
|
|
6560
|
+
}
|
|
6561
|
+
async function pull(command, parsed, sync, deps) {
|
|
6562
|
+
rejectPartial(parsed);
|
|
6563
|
+
const result = await pullWorkflow(sync, {
|
|
6564
|
+
workflowId: parsed.positionals[0],
|
|
6565
|
+
request: await buildRequest(command, withoutFlag(parsed, "out"), {
|
|
6566
|
+
io: deps.io
|
|
6567
|
+
}),
|
|
6568
|
+
dir: flag(parsed, "out"),
|
|
6569
|
+
root: deps.root ?? "",
|
|
6570
|
+
...deps.workflowPull
|
|
6571
|
+
});
|
|
6572
|
+
return { result, text: formatWorkflowPull(result) };
|
|
6573
|
+
}
|
|
6574
|
+
async function execute(command, parsed, sync, deps) {
|
|
6575
|
+
if (command.files === "pull") return pull(command, parsed, sync, deps);
|
|
6576
|
+
if (command.files === "push") {
|
|
6577
|
+
const result2 = await pushWorkflow(
|
|
6578
|
+
sync,
|
|
6579
|
+
flag(parsed, "from") ?? ".",
|
|
6580
|
+
parsed.positionals[0] || void 0
|
|
6581
|
+
);
|
|
6582
|
+
return { result: result2, text: formatWorkflowPush(result2) };
|
|
6583
|
+
}
|
|
6584
|
+
if (command.command !== "execution pull")
|
|
6585
|
+
throw new UsageError(`unknown command ${command.command}`);
|
|
6586
|
+
const result = await pullExecution(sync, {
|
|
6587
|
+
...executionOptions(parsed),
|
|
6588
|
+
root: deps.root ?? "",
|
|
6589
|
+
...deps.executionPull
|
|
6590
|
+
});
|
|
6591
|
+
return { result, text: formatExecutionPull(result) };
|
|
6592
|
+
}
|
|
6593
|
+
async function runSync(command, parsed, deps) {
|
|
6594
|
+
const sync = {
|
|
6595
|
+
remote: deps.remote,
|
|
6596
|
+
files: deps.files ?? nodeFiles(deps.cwd)
|
|
6597
|
+
};
|
|
6598
|
+
const { result, text: text2 } = await execute(command, parsed, sync, deps);
|
|
6599
|
+
deps.stdout(`${parsed.json ? JSON.stringify(result, null, 2) : text2}
|
|
6600
|
+
`);
|
|
6601
|
+
return 0;
|
|
6602
|
+
}
|
|
6603
|
+
|
|
5797
6604
|
// src/run.ts
|
|
5798
6605
|
function rejectOrgWithApiKey(deps, flagOrg) {
|
|
5799
6606
|
if (deps.env.apiKey && (flagOrg || deps.env.org)) {
|
|
@@ -5833,25 +6640,30 @@ async function credentialOrganization(deps, auth) {
|
|
|
5833
6640
|
);
|
|
5834
6641
|
return response.json?.organizationId;
|
|
5835
6642
|
}
|
|
5836
|
-
async function
|
|
5837
|
-
const needsOrganization = command.body?.credentialOrganization === true;
|
|
5838
|
-
let request = needsOrganization ? void 0 : await buildRequest(command, parsed);
|
|
6643
|
+
async function authFor(parsed, deps) {
|
|
5839
6644
|
rejectOrgWithApiKey(deps, parsed.org);
|
|
5840
6645
|
const config = loadConfig(deps.env);
|
|
5841
|
-
|
|
6646
|
+
return resolveAuth(
|
|
5842
6647
|
deps.env,
|
|
5843
6648
|
config,
|
|
5844
6649
|
requestedOrg(parsed.org, deps.env, deps.cwd, config),
|
|
5845
6650
|
deps.fetch
|
|
5846
6651
|
);
|
|
6652
|
+
}
|
|
6653
|
+
function announce(command, auth, deps) {
|
|
6654
|
+
if (command.readOnly) return;
|
|
6655
|
+
const org = auth.kind === "api-key" ? "the API key's organization" : auth.org ? orgLabel(auth.org) : "your default organization";
|
|
6656
|
+
deps.stderr(`Acting in ${org}.
|
|
6657
|
+
`);
|
|
6658
|
+
}
|
|
6659
|
+
async function runCommand(command, parsed, deps) {
|
|
6660
|
+
const needsOrganization = command.body?.credentialOrganization === true;
|
|
6661
|
+
let request = needsOrganization ? void 0 : await buildRequest(command, parsed);
|
|
6662
|
+
const auth = await authFor(parsed, deps);
|
|
5847
6663
|
request ??= await buildRequest(command, parsed, {
|
|
5848
6664
|
organizationId: await credentialOrganization(deps, auth)
|
|
5849
6665
|
});
|
|
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
|
-
}
|
|
6666
|
+
announce(command, auth, deps);
|
|
5855
6667
|
const response = await send(
|
|
5856
6668
|
deps.env.apiUrl,
|
|
5857
6669
|
auth,
|
|
@@ -5891,14 +6703,27 @@ async function runCommand(command, parsed, deps) {
|
|
|
5891
6703
|
`);
|
|
5892
6704
|
return 0;
|
|
5893
6705
|
}
|
|
6706
|
+
function nodeRemote(apiUrl, auth, fetchImpl) {
|
|
6707
|
+
return {
|
|
6708
|
+
json: async (request) => (await send(apiUrl, auth, request, false, fetchImpl)).json,
|
|
6709
|
+
async download(url) {
|
|
6710
|
+
const response = await fetchImpl(url);
|
|
6711
|
+
if (!response.ok)
|
|
6712
|
+
throw new Error(`download failed: HTTP ${response.status}`);
|
|
6713
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
6714
|
+
}
|
|
6715
|
+
};
|
|
6716
|
+
}
|
|
5894
6717
|
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
6718
|
async function run(argv2, deps) {
|
|
5896
6719
|
try {
|
|
5897
6720
|
const invocation = await parseInvocation(argv2, {
|
|
5898
|
-
commands:
|
|
6721
|
+
commands: ALL_COMMANDS,
|
|
5899
6722
|
builtins: BUILTINS,
|
|
5900
6723
|
version: VERSION,
|
|
5901
|
-
description: DESCRIPTION
|
|
6724
|
+
description: DESCRIPTION,
|
|
6725
|
+
defaultArg: (command, arg) => command.files === "push" && arg.name === "workflowId" ? "" : void 0,
|
|
6726
|
+
defaultArgNote: "<workflow-id> defaults to the workflow the pushed folder was pulled from."
|
|
5902
6727
|
});
|
|
5903
6728
|
if (invocation.kind === "exit") {
|
|
5904
6729
|
if (invocation.stdout) deps.stdout(invocation.stdout);
|
|
@@ -5908,6 +6733,14 @@ async function run(argv2, deps) {
|
|
|
5908
6733
|
if (invocation.kind === "builtin") {
|
|
5909
6734
|
return await runBuiltin(invocation.name, invocation.parsed, deps);
|
|
5910
6735
|
}
|
|
6736
|
+
if (runsLocally(invocation.command, invocation.parsed)) {
|
|
6737
|
+
const auth = await authFor(invocation.parsed, deps);
|
|
6738
|
+
announce(invocation.command, auth, deps);
|
|
6739
|
+
return await runSync(invocation.command, invocation.parsed, {
|
|
6740
|
+
...deps,
|
|
6741
|
+
remote: nodeRemote(deps.env.apiUrl, auth, deps.fetch)
|
|
6742
|
+
});
|
|
6743
|
+
}
|
|
5911
6744
|
return await runCommand(invocation.command, invocation.parsed, deps);
|
|
5912
6745
|
} catch (error) {
|
|
5913
6746
|
if (error instanceof UsageError) {
|
|
@@ -5963,7 +6796,7 @@ function openUrl(url) {
|
|
|
5963
6796
|
}
|
|
5964
6797
|
var argv = process.argv.slice(2);
|
|
5965
6798
|
if (argv[0] === "complete") {
|
|
5966
|
-
await runCompletion(argv, { commands:
|
|
6799
|
+
await runCompletion(argv, { commands: ALL_COMMANDS, builtins: BUILTINS });
|
|
5967
6800
|
process.exit(0);
|
|
5968
6801
|
}
|
|
5969
6802
|
var code = await run(argv, {
|