@asteroidai/cli 1.9.4 → 1.9.11

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