@asteroidai/cli 1.9.2 → 1.9.11

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