@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 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 file list",
3761
- tool: "workflow_files_list",
3762
- title: "List workflow files",
3763
- 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.",
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}/head/files",
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: "contents",
3777
- flag: "contents",
3778
- type: "string",
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(option) {
4798
- const text2 = option.description?.replace(/\s+/g, " ").trim() ?? "";
4877
+ function optionText(option2) {
4878
+ const text2 = option2.description?.replace(/\s+/g, " ").trim() ?? "";
4799
4879
  const parts = [text2 && !/[.!?]$/.test(text2) ? `${text2}.` : text2];
4800
- if (option.enum) parts.push(`One of: ${option.enum.join(", ")}.`);
4801
- if (option.type.endsWith("[]")) parts.push("Repeatable.");
4802
- if (option.required) parts.push("Required.");
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 option of [...command.query, ...command.body?.fields ?? []]) {
4830
- if (option.type === "boolean") {
4831
- leaf.option(`--${option.flag}`, optionText(option));
4832
- leaf.option(`--no-${option.flag}`, `Set ${option.flag} to false.`);
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 = option.enum ? option.enum.join("|") : option.type.replace("[]", "");
4915
+ const placeholder = option2.enum ? option2.enum.join("|") : option2.type.replace("[]", "");
4836
4916
  leaf.option(
4837
- `--${option.flag} <${placeholder}>`,
4838
- optionText(option),
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 option of leaf.options) {
4851
- const flag = option.long?.replace(/^--(no-)?/, "");
4852
- const value2 = own[option.attributeName()];
4853
- if (!flag || value2 === void 0 || flags.has(flag)) continue;
4854
- flags.set(flag, Array.isArray(value2) ? value2.map(String) : [String(value2)]);
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) root.version(options.version, "-v, --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
- (flag) => !(command.body?.fields ?? []).some(
4930
- (f) => flag.startsWith(`${f.flag}.`)
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.4";
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(option, raw) {
5607
- const type = option.type.replace("[]", "");
5608
- if (option.enum && !option.enum.includes(raw)) {
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
- `--${option.flag} must be one of ${option.enum.join(", ")}`
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
- `--${option.flag} must be ${type === "integer" ? "an integer" : "a number"}`
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(`--${option.flag} must be true or false`);
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, `--${option.flag}`);
5733
+ if (type === "json") return parseJson(raw, `--${option2.flag}`);
5629
5734
  return raw;
5630
5735
  }
5631
- function value(option, raws) {
5632
- if (option.type.endsWith("[]")) return raws.map((raw) => scalar(option, raw));
5633
- return scalar(option, raws.at(-1));
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((option) => option.type === "json").map((option) => option.flag);
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 flag of jsonFlags) {
5648
- const raws = flags.get(flag);
5752
+ for (const flag2 of jsonFlags) {
5753
+ const raws = flags.get(flag2);
5649
5754
  if (raws)
5650
- flags.set(flag, await Promise.all(raws.map((raw) => readInput(raw, io))));
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 option of command.query) {
5701
- const raws = flags.get(option.flag);
5702
- flags.delete(option.flag);
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 (option.required) throw new UsageError(`--${option.flag} is required`);
5809
+ if (option2.required) throw new UsageError(`--${option2.flag} is required`);
5705
5810
  continue;
5706
5811
  }
5707
- const parsedValue = value(option, raws);
5812
+ const parsedValue = value(option2, raws);
5708
5813
  const values = Array.isArray(parsedValue) ? parsedValue : [parsedValue];
5709
- if (option.explode === false)
5710
- query.append(option.name, values.map(String).join(","));
5711
- else for (const v of values) query.append(option.name, String(v));
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 [flag, raws] of [...flags]) {
5762
- const [head, ...rest] = flag.split(".");
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(flag);
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 runCommand(command, parsed, deps) {
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
- const auth = await resolveAuth(
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
- if (!command.readOnly) {
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: 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: COMMANDS, builtins: BUILTINS });
6799
+ await runCompletion(argv, { commands: ALL_COMMANDS, builtins: BUILTINS });
5967
6800
  process.exit(0);
5968
6801
  }
5969
6802
  var code = await run(argv, {