@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/core.js CHANGED
@@ -4555,16 +4555,16 @@ function isScalar(value2) {
4555
4555
  return value2 === null || ["string", "number", "boolean"].includes(typeof value2);
4556
4556
  }
4557
4557
  function indent(text2) {
4558
- return text2.split("\n").map((line) => ` ${line}`).join("\n");
4558
+ return text2.split("\n").map((line2) => ` ${line2}`).join("\n");
4559
4559
  }
4560
4560
  function text(value2) {
4561
4561
  return clean(isScalar(value2) ? String(value2) : JSON.stringify(value2));
4562
4562
  }
4563
4563
  function cell(value2, options = {}) {
4564
4564
  if (value2 === null || value2 === void 0) return "";
4565
- const line = text(value2).replace(/\s+/g, " ");
4566
- if (options.full || line.length <= MAX_CELL) return line;
4567
- return `${line.slice(0, MAX_CELL - 1)}\u2026`;
4565
+ const line2 = text(value2).replace(/\s+/g, " ");
4566
+ if (options.full || line2.length <= MAX_CELL) return line2;
4567
+ return `${line2.slice(0, MAX_CELL - 1)}\u2026`;
4568
4568
  }
4569
4569
  function columns(rows, options) {
4570
4570
  const scalarKeys = [];
@@ -4612,8 +4612,8 @@ function table(input, options = {}) {
4612
4612
  const widths = keys.map(
4613
4613
  (key, i) => Math.max(key.length, ...cells.map((r) => r[i].length))
4614
4614
  );
4615
- const line = (values) => values.map((v, i) => v.padEnd(widths[i])).join(" ").trimEnd();
4616
- return [line(keys.map((k) => k.toUpperCase())), ...cells.map(line)].join(
4615
+ const line2 = (values) => values.map((v, i) => v.padEnd(widths[i])).join(" ").trimEnd();
4616
+ return [line2(keys.map((k) => k.toUpperCase())), ...cells.map(line2)].join(
4617
4617
  "\n"
4618
4618
  );
4619
4619
  }
@@ -4727,12 +4727,12 @@ var DOTTED = /^--([a-z0-9-]+\.[a-z0-9.-]+)(?:=(.*))?$/;
4727
4727
  function collect(value2, previous) {
4728
4728
  return [...previous ?? [], value2];
4729
4729
  }
4730
- function optionText(option) {
4731
- const text2 = option.description?.replace(/\s+/g, " ").trim() ?? "";
4730
+ function optionText(option2) {
4731
+ const text2 = option2.description?.replace(/\s+/g, " ").trim() ?? "";
4732
4732
  const parts = [text2 && !/[.!?]$/.test(text2) ? `${text2}.` : text2];
4733
- if (option.enum) parts.push(`One of: ${option.enum.join(", ")}.`);
4734
- if (option.type.endsWith("[]")) parts.push("Repeatable.");
4735
- if (option.required) parts.push("Required.");
4733
+ if (option2.enum) parts.push(`One of: ${option2.enum.join(", ")}.`);
4734
+ if (option2.type.endsWith("[]")) parts.push("Repeatable.");
4735
+ if (option2.required) parts.push("Required.");
4736
4736
  return parts.filter(Boolean).join(" ");
4737
4737
  }
4738
4738
  function splitDotted(argv) {
@@ -4759,18 +4759,20 @@ function group(parent, name, groups, path) {
4759
4759
  return created;
4760
4760
  }
4761
4761
  function leafOptions(leaf, command) {
4762
- for (const option of [...command.query, ...command.body?.fields ?? []]) {
4763
- if (option.type === "boolean") {
4764
- leaf.option(`--${option.flag}`, optionText(option));
4765
- leaf.option(`--no-${option.flag}`, `Set ${option.flag} to false.`);
4762
+ for (const option2 of [...command.query, ...command.body?.fields ?? []]) {
4763
+ if (option2.type === "boolean") {
4764
+ leaf.option(`--${option2.flag}`, optionText(option2));
4765
+ leaf.option(`--no-${option2.flag}`, `Set ${option2.flag} to false.`);
4766
4766
  continue;
4767
4767
  }
4768
- const placeholder = option.enum ? option.enum.join("|") : option.type.replace("[]", "");
4768
+ const placeholder = option2.enum ? option2.enum.join("|") : option2.type.replace("[]", "");
4769
4769
  leaf.option(
4770
- `--${option.flag} <${placeholder}>`,
4771
- optionText(option),
4770
+ `--${option2.flag} <${placeholder}>`,
4771
+ optionText(option2),
4772
4772
  collect
4773
4773
  );
4774
+ if (option2.negatable)
4775
+ leaf.option(`--no-${option2.flag}`, `Skip ${option2.flag}.`);
4774
4776
  }
4775
4777
  for (const file of command.body?.files ?? []) {
4776
4778
  leaf.option(`--${file} <path>`, "File to upload. Repeatable.", collect);
@@ -4780,11 +4782,11 @@ function toParsed(leaf, positionals, dotted) {
4780
4782
  const globals = leaf.optsWithGlobals();
4781
4783
  const own = leaf.opts();
4782
4784
  const flags = new Map(dotted);
4783
- for (const option of leaf.options) {
4784
- const flag = option.long?.replace(/^--(no-)?/, "");
4785
- const value2 = own[option.attributeName()];
4786
- if (!flag || value2 === void 0 || flags.has(flag)) continue;
4787
- flags.set(flag, Array.isArray(value2) ? value2.map(String) : [String(value2)]);
4785
+ for (const option2 of leaf.options) {
4786
+ const flag2 = option2.long?.replace(/^--(no-)?/, "");
4787
+ const value2 = own[option2.attributeName()];
4788
+ if (!flag2 || value2 === void 0 || flags.has(flag2)) continue;
4789
+ flags.set(flag2, Array.isArray(value2) ? value2.map(String) : [String(value2)]);
4788
4790
  }
4789
4791
  return {
4790
4792
  positionals,
@@ -4797,7 +4799,11 @@ function toParsed(leaf, positionals, dotted) {
4797
4799
  }
4798
4800
  function defineCommands(root, options, dotted = /* @__PURE__ */ new Map(), onInvocation = () => void 0) {
4799
4801
  if (options.description) root.description(options.description);
4800
- if (options.version) root.version(options.version, "-v, --version");
4802
+ if (options.version)
4803
+ root.addHelpText(
4804
+ "after",
4805
+ "\nRun `asteroid --version` to print the CLI version."
4806
+ );
4801
4807
  for (const [flags, text2] of GLOBAL_FLAGS) {
4802
4808
  if (!flags.startsWith("-h")) root.option(flags, text2);
4803
4809
  }
@@ -4859,8 +4865,8 @@ ${notes.join("\n")}`);
4859
4865
  );
4860
4866
  }
4861
4867
  const unknownDotted = [...dotted.keys()].filter(
4862
- (flag) => !(command.body?.fields ?? []).some(
4863
- (f) => flag.startsWith(`${f.flag}.`)
4868
+ (flag2) => !(command.body?.fields ?? []).some(
4869
+ (f) => flag2.startsWith(`${f.flag}.`)
4864
4870
  )
4865
4871
  );
4866
4872
  if (unknownDotted.length > 0) {
@@ -4881,6 +4887,15 @@ async function parseInvocation(argv, options) {
4881
4887
  let stdout = "";
4882
4888
  let stderr = "";
4883
4889
  let result;
4890
+ if (options.version && (argv[0] === "-v" || argv[0] === "--version")) {
4891
+ return {
4892
+ kind: "exit",
4893
+ code: 0,
4894
+ stdout: `${options.version}
4895
+ `,
4896
+ stderr: ""
4897
+ };
4898
+ }
4884
4899
  const { argv: rest, dotted } = splitDotted(argv);
4885
4900
  const root = new Command("asteroid").exitOverride().helpCommand(false).configureOutput({
4886
4901
  writeOut: (text2) => stdout += text2,
@@ -4915,34 +4930,34 @@ import { basename } from "node:path";
4915
4930
  function camel(segment) {
4916
4931
  return segment.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
4917
4932
  }
4918
- function scalar(option, raw) {
4919
- const type = option.type.replace("[]", "");
4920
- if (option.enum && !option.enum.includes(raw)) {
4933
+ function scalar(option2, raw) {
4934
+ const type = option2.type.replace("[]", "");
4935
+ if (option2.enum && !option2.enum.includes(raw)) {
4921
4936
  throw new UsageError(
4922
- `--${option.flag} must be one of ${option.enum.join(", ")}`
4937
+ `--${option2.flag} must be one of ${option2.enum.join(", ")}`
4923
4938
  );
4924
4939
  }
4925
4940
  if (type === "integer" || type === "number") {
4926
4941
  const value2 = Number(raw);
4927
4942
  if (raw.trim() === "" || Number.isNaN(value2) || type === "integer" && !Number.isInteger(value2)) {
4928
4943
  throw new UsageError(
4929
- `--${option.flag} must be ${type === "integer" ? "an integer" : "a number"}`
4944
+ `--${option2.flag} must be ${type === "integer" ? "an integer" : "a number"}`
4930
4945
  );
4931
4946
  }
4932
4947
  return value2;
4933
4948
  }
4934
4949
  if (type === "boolean") {
4935
4950
  if (raw !== "true" && raw !== "false") {
4936
- throw new UsageError(`--${option.flag} must be true or false`);
4951
+ throw new UsageError(`--${option2.flag} must be true or false`);
4937
4952
  }
4938
4953
  return raw === "true";
4939
4954
  }
4940
- if (type === "json") return parseJson(raw, `--${option.flag}`);
4955
+ if (type === "json") return parseJson(raw, `--${option2.flag}`);
4941
4956
  return raw;
4942
4957
  }
4943
- function value(option, raws) {
4944
- if (option.type.endsWith("[]")) return raws.map((raw) => scalar(option, raw));
4945
- return scalar(option, raws.at(-1));
4958
+ function value(option2, raws) {
4959
+ if (option2.type.endsWith("[]")) return raws.map((raw) => scalar(option2, raw));
4960
+ return scalar(option2, raws.at(-1));
4946
4961
  }
4947
4962
  var nodeReader = {
4948
4963
  text: async (path) => readFileSync(path, "utf8"),
@@ -4954,12 +4969,12 @@ async function readInput(raw, io) {
4954
4969
  return raw;
4955
4970
  }
4956
4971
  async function resolveInputs(command, parsed, io) {
4957
- const jsonFlags = [...command.query, ...command.body?.fields ?? []].filter((option) => option.type === "json").map((option) => option.flag);
4972
+ const jsonFlags = [...command.query, ...command.body?.fields ?? []].filter((option2) => option2.type === "json").map((option2) => option2.flag);
4958
4973
  const flags = new Map(parsed.flags);
4959
- for (const flag of jsonFlags) {
4960
- const raws = flags.get(flag);
4974
+ for (const flag2 of jsonFlags) {
4975
+ const raws = flags.get(flag2);
4961
4976
  if (raws)
4962
- flags.set(flag, await Promise.all(raws.map((raw) => readInput(raw, io))));
4977
+ flags.set(flag2, await Promise.all(raws.map((raw) => readInput(raw, io))));
4963
4978
  }
4964
4979
  const body = parsed.body === void 0 ? void 0 : await readInput(parsed.body, io);
4965
4980
  return { ...parsed, flags, body };
@@ -5009,18 +5024,18 @@ async function buildRequest(command, input, context = {}) {
5009
5024
  });
5010
5025
  const flags = new Map(parsed.flags);
5011
5026
  const query = new URLSearchParams();
5012
- for (const option of command.query) {
5013
- const raws = flags.get(option.flag);
5014
- flags.delete(option.flag);
5027
+ for (const option2 of command.query) {
5028
+ const raws = flags.get(option2.flag);
5029
+ flags.delete(option2.flag);
5015
5030
  if (!raws) {
5016
- if (option.required) throw new UsageError(`--${option.flag} is required`);
5031
+ if (option2.required) throw new UsageError(`--${option2.flag} is required`);
5017
5032
  continue;
5018
5033
  }
5019
- const parsedValue = value(option, raws);
5034
+ const parsedValue = value(option2, raws);
5020
5035
  const values = Array.isArray(parsedValue) ? parsedValue : [parsedValue];
5021
- if (option.explode === false)
5022
- query.append(option.name, values.map(String).join(","));
5023
- else for (const v of values) query.append(option.name, String(v));
5036
+ if (option2.explode === false)
5037
+ query.append(option2.name, values.map(String).join(","));
5038
+ else for (const v of values) query.append(option2.name, String(v));
5024
5039
  }
5025
5040
  const built = { method: command.method, path, query };
5026
5041
  const body = command.body;
@@ -5070,11 +5085,11 @@ async function buildRequest(command, input, context = {}) {
5070
5085
  flags.delete(field.flag);
5071
5086
  json[field.name] = value(field, raws);
5072
5087
  }
5073
- for (const [flag, raws] of [...flags]) {
5074
- const [head, ...rest] = flag.split(".");
5088
+ for (const [flag2, raws] of [...flags]) {
5089
+ const [head, ...rest] = flag2.split(".");
5075
5090
  const field = body.fields.find((f) => f.flag === head);
5076
5091
  if (!field || rest.length === 0) continue;
5077
- flags.delete(flag);
5092
+ flags.delete(flag2);
5078
5093
  const current = json[field.name];
5079
5094
  json[field.name] = typeof current === "object" && current !== null ? current : {};
5080
5095
  setPath(json, [field.name, ...rest.map(camel)], loose(raws.at(-1)));
@@ -5105,12 +5120,806 @@ function rejectUnknown(flags) {
5105
5120
  );
5106
5121
  }
5107
5122
  }
5123
+
5124
+ // src/sync/commands.ts
5125
+ function option(name, type, description, extra = {}) {
5126
+ return { name, flag: name, type, required: false, description, ...extra };
5127
+ }
5128
+ var DIR = option(
5129
+ "dir",
5130
+ "string",
5131
+ "Write here instead of `<workflow-name>/...` under the current directory."
5132
+ );
5133
+ var local = {
5134
+ method: "GET",
5135
+ path: "",
5136
+ binary: false,
5137
+ destructive: false,
5138
+ local: true
5139
+ };
5140
+ var SYNC_COMMANDS = [
5141
+ {
5142
+ ...local,
5143
+ command: "workflow pull",
5144
+ tool: "workflow_pull",
5145
+ title: "Pull a workflow to disk",
5146
+ 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.",
5147
+ readOnly: true,
5148
+ args: [
5149
+ {
5150
+ name: "workflowId",
5151
+ flag: "workflow-id",
5152
+ type: "string",
5153
+ required: true
5154
+ }
5155
+ ],
5156
+ query: [
5157
+ option(
5158
+ "version-id",
5159
+ "string",
5160
+ "A workflow version ID. Omit for the editable head."
5161
+ ),
5162
+ DIR
5163
+ ]
5164
+ },
5165
+ {
5166
+ ...local,
5167
+ command: "workflow push",
5168
+ tool: "workflow_push",
5169
+ title: "Push local edits to a workflow head",
5170
+ 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.",
5171
+ readOnly: false,
5172
+ args: [],
5173
+ query: [
5174
+ option(
5175
+ "dir",
5176
+ "string",
5177
+ "The pulled head folder. Defaults to the current directory."
5178
+ )
5179
+ ]
5180
+ },
5181
+ {
5182
+ ...local,
5183
+ command: "execution pull",
5184
+ tool: "execution_pull",
5185
+ title: "Pull an execution to disk",
5186
+ description: "Write an execution to `<workflow-name>/executions/<id>/`: summary.json, activities.md (the step-by-step log), and the files the run produced.",
5187
+ readOnly: true,
5188
+ args: [
5189
+ {
5190
+ name: "executionId",
5191
+ flag: "execution-id",
5192
+ type: "string",
5193
+ required: true
5194
+ }
5195
+ ],
5196
+ query: [
5197
+ option(
5198
+ "files",
5199
+ "string",
5200
+ "Only download files whose path matches this glob. --no-files skips downloads.",
5201
+ {
5202
+ negatable: true
5203
+ }
5204
+ ),
5205
+ option(
5206
+ "summary-only",
5207
+ "boolean",
5208
+ "Write summary.json only: no activities, no files."
5209
+ ),
5210
+ option(
5211
+ "max-file-size",
5212
+ "integer",
5213
+ "Skip files larger than this many bytes."
5214
+ ),
5215
+ option(
5216
+ "dir",
5217
+ "string",
5218
+ "Write here instead of `<workflow-name>/executions/<id>/` under the current directory."
5219
+ )
5220
+ ]
5221
+ }
5222
+ ];
5223
+
5224
+ // src/commands.ts
5225
+ function withSyncCommands(commands) {
5226
+ return [...commands, ...SYNC_COMMANDS].sort(
5227
+ (a, b) => a.command.localeCompare(b.command)
5228
+ );
5229
+ }
5230
+ var ALL_COMMANDS = withSyncCommands(COMMANDS);
5231
+
5232
+ // src/sync/activities.ts
5233
+ function indentJson(data) {
5234
+ return JSON.stringify(data, null, 2).split("\n").map((line2) => ` ${line2}`).join("\n");
5235
+ }
5236
+ function action(label, payload) {
5237
+ const info = payload.info;
5238
+ const detail = info && Object.keys(info).length > 0 ? `
5239
+ info:
5240
+ ${indentJson(info)}` : "";
5241
+ return `[${label}: ${payload.actionName}]:${detail}
5242
+ `;
5243
+ }
5244
+ function line(payload) {
5245
+ switch (payload.activityType) {
5246
+ case "reasoning":
5247
+ return `[reasoning]:
5248
+ ${payload.reasoning}
5249
+ `;
5250
+ case "generic":
5251
+ return `[message]:
5252
+ ${payload.message}
5253
+ `;
5254
+ case "user_message_received":
5255
+ return `[user_message]:
5256
+ ${payload.message}
5257
+ `;
5258
+ case "action_started":
5259
+ return action("action_started", payload);
5260
+ case "action_completed":
5261
+ return action("action_completed", payload);
5262
+ case "action_failed":
5263
+ return `[action_failed: ${payload.actionName}]:
5264
+ failure: ${payload.message}
5265
+ `;
5266
+ case "compaction_performed":
5267
+ return `[compaction]: summary_tokens: ${payload.summaryTokens}, duration_ms: ${payload.durationMs}
5268
+ `;
5269
+ case "compaction_started":
5270
+ return "[compaction_started]\n";
5271
+ default: {
5272
+ const summary2 = payload.display?.summaryText;
5273
+ return summary2 ? `[${payload.activityType}]: ${summary2}
5274
+ ` : `[${payload.activityType}]
5275
+ `;
5276
+ }
5277
+ }
5278
+ }
5279
+ function formatActivitiesMarkdown(activities, status, createdAt) {
5280
+ const lines = [
5281
+ "# Execution Activities\n",
5282
+ `Status: ${status}`,
5283
+ `Started: ${createdAt}`
5284
+ ];
5285
+ let currentTurn = "";
5286
+ for (const { payload } of activities) {
5287
+ const turnId = payload.display?.turnId ?? "";
5288
+ if (turnId && turnId !== currentTurn) {
5289
+ currentTurn = turnId;
5290
+ lines.push(`
5291
+ ---
5292
+
5293
+ ## ${turnId}
5294
+ `);
5295
+ } else if (!currentTurn && !turnId) {
5296
+ currentTurn = "_started";
5297
+ lines.push("\n---\n");
5298
+ }
5299
+ lines.push(line(payload));
5300
+ }
5301
+ return lines.join("\n");
5302
+ }
5303
+
5304
+ // ../workflow-files/src/representation/name-charset.ts
5305
+ var NAME_CHARSET_RE = /^[A-Za-z0-9 !#$%&'()+,.;=@^`{}[\]~-]+$/;
5306
+ var WINDOWS_RESERVED = /* @__PURE__ */ new Set([
5307
+ "con",
5308
+ "prn",
5309
+ "aux",
5310
+ "nul",
5311
+ ...Array.from({ length: 9 }, (_, i) => `com${i + 1}`),
5312
+ ...Array.from({ length: 9 }, (_, i) => `lpt${i + 1}`)
5313
+ ]);
5314
+ function isNameCharsetConforming(name) {
5315
+ return NAME_CHARSET_RE.test(name);
5316
+ }
5317
+
5318
+ // ../workflow-files/src/representation/types.ts
5319
+ function normalizeNodeName(name) {
5320
+ let result = name.toLowerCase();
5321
+ result = result.replace(/[^a-z0-9]+/g, "_");
5322
+ result = result.replace(/^_+|_+$/g, "");
5323
+ if (result.length > 80) {
5324
+ result = result.slice(0, 80).replace(/_+$/, "");
5325
+ }
5326
+ if (result === "") {
5327
+ throw new Error(
5328
+ `representation: node name "${name}" produces an empty directory name after normalization`
5329
+ );
5330
+ }
5331
+ return result;
5332
+ }
5333
+ function nodeDirName(name) {
5334
+ if (isNameCharsetConforming(name)) {
5335
+ return name.replaceAll(" ", "_");
5336
+ }
5337
+ return normalizeNodeName(name);
5338
+ }
5339
+ function agentDirName(name) {
5340
+ try {
5341
+ return nodeDirName(name);
5342
+ } catch {
5343
+ return "agent";
5344
+ }
5345
+ }
5346
+ function executionDirName(executionId) {
5347
+ return executionId.slice(0, 8);
5348
+ }
5349
+ function workflowDirName(workflowId, version, parentId, parentVersion) {
5350
+ const short = workflowId.slice(0, 8);
5351
+ if (version !== void 0 && version !== null) {
5352
+ return `v${version}_${short}`;
5353
+ }
5354
+ if (parentId) {
5355
+ if (parentVersion !== void 0 && parentVersion !== null) {
5356
+ return `draft_${short}_from_v${parentVersion}`;
5357
+ }
5358
+ return `draft_${short}_from_draft_${parentId.slice(0, 8)}`;
5359
+ }
5360
+ return `draft_${short}`;
5361
+ }
5362
+
5363
+ // src/sync/paths.ts
5364
+ import { createHash } from "node:crypto";
5365
+ function join(...parts) {
5366
+ return parts.filter((part) => part !== "").join("/").replace(/\/{2,}/g, "/");
5367
+ }
5368
+ function workflowRoot(root, workflowName) {
5369
+ return join(root, agentDirName(workflowName));
5370
+ }
5371
+ function isContained(path) {
5372
+ if (path === "" || path.startsWith("/") || path.includes("\\")) return false;
5373
+ return path.split("/").every((segment) => segment !== ".." && segment !== "");
5374
+ }
5375
+ function sha256(bytes) {
5376
+ return createHash("sha256").update(bytes).digest("hex");
5377
+ }
5378
+ function matchesGlob(path, pattern) {
5379
+ let source = "";
5380
+ for (let i = 0; i < pattern.length; i++) {
5381
+ const ch = pattern[i];
5382
+ if (ch === "*" && pattern[i + 1] === "*" && pattern[i + 2] === "/") {
5383
+ source += "(?:.*/)?";
5384
+ i += 2;
5385
+ } else if (ch === "*" && pattern[i + 1] === "*") {
5386
+ source += ".*";
5387
+ i += 1;
5388
+ } else if (ch === "*") source += "[^/]*";
5389
+ else if (ch === "?") source += "[^/]";
5390
+ else source += ch.replace(/[.+^${}()|[\]\\]/g, "\\$&");
5391
+ }
5392
+ return new RegExp(`^${source}$`).test(path);
5393
+ }
5394
+
5395
+ // src/sync/types.ts
5396
+ var MANIFEST = ".asteroid.json";
5397
+ function get(path, query = {}) {
5398
+ return { method: "GET", path, query: new URLSearchParams(query) };
5399
+ }
5400
+ function statusOf(error) {
5401
+ const status = error?.status;
5402
+ return typeof status === "number" ? status : void 0;
5403
+ }
5404
+
5405
+ // src/sync/execution-pull.ts
5406
+ var DIRECTORIES = ["workspace", "downloads", "shared"];
5407
+ function relativeFilePath(file) {
5408
+ const at = file.filePath.indexOf(`/${file.directory}/`);
5409
+ if (at === -1)
5410
+ return `${file.directory}/${file.filePath.split("/").pop() ?? "unknown"}`;
5411
+ return file.filePath.slice(at + 1).replace(/^workspace\/\.execution-context\//, "");
5412
+ }
5413
+ function executionDir(options, execution) {
5414
+ const chosen = options.dir?.(execution);
5415
+ if (chosen) return chosen;
5416
+ const root = workflowRoot(
5417
+ options.root,
5418
+ execution.workflowName ?? execution.workflowId
5419
+ );
5420
+ return join(root, "executions", executionDirName(execution.id));
5421
+ }
5422
+ async function writeText(sync, path, text2) {
5423
+ await sync.files.write(path, Buffer.from(text2, "utf8"));
5424
+ }
5425
+ async function writeActivities(sync, dir, execution) {
5426
+ try {
5427
+ const activities = await sync.remote.json(
5428
+ get(`/agents/v2/executions/${execution.id}/activities`, { order: "asc" })
5429
+ );
5430
+ if (activities.length === 0) return false;
5431
+ const markdown = formatActivitiesMarkdown(
5432
+ activities,
5433
+ execution.status,
5434
+ execution.createdAt
5435
+ );
5436
+ await writeText(sync, join(dir, "activities.md"), markdown);
5437
+ return true;
5438
+ } catch {
5439
+ return false;
5440
+ }
5441
+ }
5442
+ async function listFiles(sync, executionId) {
5443
+ const listing = await sync.remote.json(
5444
+ get(`/agents/v2/executions/${executionId}/files`)
5445
+ );
5446
+ return DIRECTORIES.flatMap((name) => listing.directories[name] ?? []);
5447
+ }
5448
+ async function download(sync, executionId, file) {
5449
+ const path = `/agents/v2/executions/${executionId}/files/${file.id}/signed-url`;
5450
+ const { url } = await sync.remote.json(get(path));
5451
+ return sync.remote.download(url);
5452
+ }
5453
+ function selectFiles(files, options, skipped) {
5454
+ return files.filter((file) => {
5455
+ const path = relativeFilePath(file);
5456
+ if (!isContained(path)) skipped.push(`${path} (unsafe path)`);
5457
+ else if (options.glob !== void 0 && !matchesGlob(path, options.glob))
5458
+ skipped.push(`${path} (excluded by --files)`);
5459
+ else if (options.maxFileBytes !== void 0 && file.fileSize > options.maxFileBytes)
5460
+ skipped.push(`${path} (${file.fileSize} bytes, over the size limit)`);
5461
+ else return true;
5462
+ return false;
5463
+ });
5464
+ }
5465
+ async function pullFiles(sync, dir, options) {
5466
+ const skipped = [];
5467
+ const failed = [];
5468
+ let files;
5469
+ try {
5470
+ files = await listFiles(sync, options.executionId);
5471
+ } catch (error) {
5472
+ const reason = error instanceof Error ? error.message : String(error);
5473
+ return {
5474
+ downloaded: 0,
5475
+ candidates: 0,
5476
+ skipped,
5477
+ failed: [`file listing (${reason})`]
5478
+ };
5479
+ }
5480
+ let downloaded = 0;
5481
+ await Promise.all(
5482
+ selectFiles(files, options, skipped).map(async (file) => {
5483
+ try {
5484
+ const bytes = await download(sync, options.executionId, file);
5485
+ await sync.files.write(join(dir, relativeFilePath(file)), bytes);
5486
+ downloaded++;
5487
+ } catch (error) {
5488
+ failed.push(
5489
+ `${file.fileName} (${error instanceof Error ? error.message : String(error)})`
5490
+ );
5491
+ }
5492
+ })
5493
+ );
5494
+ return { downloaded, candidates: files.length, skipped, failed };
5495
+ }
5496
+ async function pullExecution(sync, options) {
5497
+ const execution = await sync.remote.json(
5498
+ get(`/agents/v2/executions/${options.executionId}`)
5499
+ );
5500
+ const dir = executionDir(options, execution);
5501
+ await writeText(
5502
+ sync,
5503
+ join(dir, "summary.json"),
5504
+ JSON.stringify(execution, null, 2)
5505
+ );
5506
+ const result = {
5507
+ execution,
5508
+ dir,
5509
+ written: ["summary.json"],
5510
+ downloaded: 0,
5511
+ candidates: 0,
5512
+ skipped: [],
5513
+ failed: []
5514
+ };
5515
+ if (execution.purgedAt) return result;
5516
+ if (options.activities !== false && await writeActivities(sync, dir, execution))
5517
+ result.written.push("activities.md");
5518
+ if (options.files === false) {
5519
+ return {
5520
+ ...result,
5521
+ skippedBy: options.activities === false ? "--summary-only" : "--no-files"
5522
+ };
5523
+ }
5524
+ return { ...result, ...await pullFiles(sync, dir, options) };
5525
+ }
5526
+ function filesLine(result) {
5527
+ const parts = [...result.written];
5528
+ if (result.downloaded > 0) parts.push(`${result.downloaded} downloaded`);
5529
+ const counted = `Files: ${result.written.length + result.downloaded} written (${parts.join(", ")})`;
5530
+ if (result.skippedBy)
5531
+ return `${counted}; downloads skipped (${result.skippedBy})`;
5532
+ if (result.candidates === 0) return `${counted}; no execution files matched`;
5533
+ return counted;
5534
+ }
5535
+ function failures(execution) {
5536
+ const list = execution.scriptFailures ?? [];
5537
+ if (list.length === 0) return null;
5538
+ const lines = list.map(
5539
+ (f) => ` - ${f.nodeName ?? "(unknown node)"}: ${(f.error ?? "").split("\n")[0]}`
5540
+ );
5541
+ return `Script failures: ${list.length}
5542
+ ${lines.join("\n")}`;
5543
+ }
5544
+ function purged(result) {
5545
+ const { execution } = result;
5546
+ return [
5547
+ `Execution: ${execution.id}`,
5548
+ `Status: ${execution.status}`,
5549
+ `Purged: ${execution.purgedAt}`,
5550
+ "This run was retention-purged. Activities, files, inputs and result are gone. Do not diagnose it.",
5551
+ `Written to: ${result.dir}/`
5552
+ ].join("\n");
5553
+ }
5554
+ function formatExecutionPull(result) {
5555
+ const { execution } = result;
5556
+ if (execution.purgedAt) return purged(result);
5557
+ const outcome = execution.executionResult;
5558
+ return [
5559
+ `Execution: ${execution.id}`,
5560
+ `Status: ${execution.status}`,
5561
+ `Workflow: ${execution.workflowName ?? execution.workflowId}`,
5562
+ execution.workflowVersionId ? `Workflow version: ${execution.workflowVersionId}` : null,
5563
+ execution.inputs ? `Inputs: ${JSON.stringify(execution.inputs)}` : null,
5564
+ outcome ? `Result: ${outcome.outcome}: ${outcome.reasoning}` : null,
5565
+ `Created: ${execution.createdAt}`,
5566
+ execution.terminalAt ? `Completed: ${execution.terminalAt}` : null,
5567
+ execution.duration != null ? `Duration: ${execution.duration.toFixed(1)}s` : null,
5568
+ failures(execution),
5569
+ filesLine(result),
5570
+ result.skipped.length > 0 ? `Skipped:
5571
+ ${result.skipped.map((s) => ` - ${s}`).join("\n")}` : null,
5572
+ result.failed.length > 0 ? `Failed: ${result.failed.join(", ")}` : null,
5573
+ `Written to: ${result.dir}/`
5574
+ ].filter(Boolean).join("\n");
5575
+ }
5576
+
5577
+ // src/sync/node-files.ts
5578
+ import { mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
5579
+ import { dirname, relative, resolve, sep } from "node:path";
5580
+ function missing(error) {
5581
+ return error.code === "ENOENT";
5582
+ }
5583
+ async function walk(root, dir, out) {
5584
+ for (const entry of await readdir(dir, { withFileTypes: true })) {
5585
+ const path = resolve(dir, entry.name);
5586
+ if (entry.isDirectory()) await walk(root, path, out);
5587
+ else if (entry.isFile())
5588
+ out.push(relative(root, path).split(sep).join("/"));
5589
+ }
5590
+ }
5591
+ function nodeFiles(cwd) {
5592
+ const at = (path) => resolve(cwd, path);
5593
+ return {
5594
+ async read(path) {
5595
+ try {
5596
+ return new Uint8Array(await readFile(at(path)));
5597
+ } catch (error) {
5598
+ if (missing(error)) return void 0;
5599
+ throw error;
5600
+ }
5601
+ },
5602
+ async write(path, bytes) {
5603
+ await mkdir(dirname(at(path)), { recursive: true });
5604
+ await writeFile(at(path), bytes);
5605
+ },
5606
+ async remove(path) {
5607
+ await rm(at(path), { force: true });
5608
+ },
5609
+ async list(dir) {
5610
+ const out = [];
5611
+ try {
5612
+ await walk(at(dir), at(dir), out);
5613
+ } catch (error) {
5614
+ if (!missing(error)) throw error;
5615
+ }
5616
+ return out.sort();
5617
+ }
5618
+ };
5619
+ }
5620
+
5621
+ // src/sync/workflow-pull.ts
5622
+ var CONCURRENCY = 16;
5623
+ function treeRequest(options) {
5624
+ const base = `/agents/v2/workflows/${options.workflowId}`;
5625
+ const path = options.versionId ? `${base}/versions/${options.versionId}/files` : `${base}/head/files`;
5626
+ return get(path, { contents: "all" });
5627
+ }
5628
+ function pullDir(options, tree) {
5629
+ if (options.dir) return options.dir;
5630
+ const root = workflowRoot(options.root, tree.workflowName);
5631
+ if (!options.versionId) return join(root, "head");
5632
+ return join(root, ".versions", workflowDirName(tree.versionId, tree.version));
5633
+ }
5634
+ async function agentFileBytes(sync, workflowId, tree, entry) {
5635
+ const path = `/agents/v2/workflows/${workflowId}/versions/${tree.versionId}/files/${entry.fileId}/signed-url`;
5636
+ const { url } = await sync.remote.json(get(path));
5637
+ return sync.remote.download(url);
5638
+ }
5639
+ async function entryBytes(sync, workflowId, tree, entry) {
5640
+ if (entry.contentBase64 !== void 0)
5641
+ return Buffer.from(entry.contentBase64, "base64");
5642
+ if (entry.content !== void 0) return Buffer.from(entry.content, "utf8");
5643
+ if (!entry.fileId) return void 0;
5644
+ return agentFileBytes(sync, workflowId, tree, entry);
5645
+ }
5646
+ async function writeEntries(sync, options, tree, dir) {
5647
+ const hashes = {};
5648
+ const skipped = [];
5649
+ let shared = 0;
5650
+ const wanted = tree.files.filter((entry) => {
5651
+ if (!isContained(entry.path)) skipped.push(`${entry.path} (unsafe path)`);
5652
+ else if (options.maxFileBytes !== void 0 && entry.size > options.maxFileBytes)
5653
+ skipped.push(`${entry.path} (${entry.size} bytes, over the size limit)`);
5654
+ else return true;
5655
+ return false;
5656
+ });
5657
+ for (let i = 0; i < wanted.length; i += CONCURRENCY) {
5658
+ await Promise.all(
5659
+ wanted.slice(i, i + CONCURRENCY).map(async (entry) => {
5660
+ const bytes = await entryBytes(sync, options.workflowId, tree, entry);
5661
+ if (!bytes) return void skipped.push(`${entry.path} (no content)`);
5662
+ await sync.files.write(join(dir, entry.path), bytes);
5663
+ hashes[entry.path] = sha256(bytes);
5664
+ if (entry.kind === "agent_file") shared++;
5665
+ })
5666
+ );
5667
+ }
5668
+ return { hashes, skipped, shared };
5669
+ }
5670
+ async function removeStale(sync, dir, keep) {
5671
+ for (const path of await sync.files.list(dir)) {
5672
+ if (!keep.has(path) && path !== MANIFEST)
5673
+ await sync.files.remove(join(dir, path));
5674
+ }
5675
+ }
5676
+ async function writeManifest(sync, dir, manifest) {
5677
+ const text2 = `${JSON.stringify(manifest, null, 2)}
5678
+ `;
5679
+ await sync.files.write(join(dir, MANIFEST), Buffer.from(text2, "utf8"));
5680
+ }
5681
+ async function pullWorkflow(sync, options) {
5682
+ const tree = await sync.remote.json(treeRequest(options));
5683
+ const dir = pullDir(options, tree);
5684
+ const { hashes, skipped, shared } = await writeEntries(
5685
+ sync,
5686
+ options,
5687
+ tree,
5688
+ dir
5689
+ );
5690
+ await removeStale(sync, dir, new Set(Object.keys(hashes)));
5691
+ if (options.manifest !== false) {
5692
+ await writeManifest(sync, dir, {
5693
+ workflowId: options.workflowId,
5694
+ versionId: tree.versionId,
5695
+ head: !options.versionId,
5696
+ rev: tree.rev,
5697
+ files: hashes
5698
+ });
5699
+ }
5700
+ return {
5701
+ workflowId: options.workflowId,
5702
+ workflowName: tree.workflowName,
5703
+ versionId: tree.versionId,
5704
+ version: tree.version,
5705
+ rev: tree.rev,
5706
+ dir,
5707
+ written: Object.keys(hashes).length,
5708
+ shared,
5709
+ skipped
5710
+ };
5711
+ }
5712
+ function formatWorkflowPull(result) {
5713
+ const definition = result.written - result.shared;
5714
+ 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)`;
5715
+ return [
5716
+ `Workflow: ${result.workflowName} (${result.workflowId})`,
5717
+ `Workflow version: ${result.versionId}${result.version ? ` (v${result.version})` : " (draft)"}`,
5718
+ `Written to: ${result.dir}/`,
5719
+ files,
5720
+ result.skipped.length > 0 ? `Skipped:
5721
+ ${result.skipped.map((s) => ` - ${s}`).join("\n")}` : null
5722
+ ].filter(Boolean).join("\n");
5723
+ }
5724
+
5725
+ // src/sync/workflow-push.ts
5726
+ async function readManifest(sync, dir) {
5727
+ const bytes = await sync.files.read(join(dir, MANIFEST));
5728
+ if (!bytes) {
5729
+ throw new UsageError(
5730
+ `no pulled workflow in ${dir}. Run \`asteroid workflow pull <workflow-id>\`, then push from the head folder it writes.`
5731
+ );
5732
+ }
5733
+ const manifest = JSON.parse(Buffer.from(bytes).toString("utf8"));
5734
+ if (!manifest.head) {
5735
+ throw new UsageError(
5736
+ `${dir} holds a pinned workflow version, which is read-only. Push from a pulled head.`
5737
+ );
5738
+ }
5739
+ return manifest;
5740
+ }
5741
+ async function localChanges(sync, dir, manifest) {
5742
+ const local2 = (await sync.files.list(dir)).filter(
5743
+ (path) => path !== MANIFEST
5744
+ );
5745
+ const writes = [];
5746
+ for (const path of local2) {
5747
+ const bytes = await sync.files.read(join(dir, path));
5748
+ if (!bytes) continue;
5749
+ const hash = sha256(bytes);
5750
+ if (manifest.files[path] === hash) continue;
5751
+ writes.push({
5752
+ path,
5753
+ contentBase64: Buffer.from(bytes).toString("base64"),
5754
+ hash
5755
+ });
5756
+ }
5757
+ const present = new Set(local2);
5758
+ const deletes = Object.keys(manifest.files).filter(
5759
+ (path) => !present.has(path)
5760
+ );
5761
+ return { writes, deletes };
5762
+ }
5763
+ async function patch(sync, manifest, writes, deletes) {
5764
+ try {
5765
+ await sync.remote.json({
5766
+ method: "PATCH",
5767
+ path: `/agents/v2/workflows/${manifest.workflowId}/head/files`,
5768
+ query: new URLSearchParams(),
5769
+ json: { baseRev: manifest.rev, writes, deletes }
5770
+ });
5771
+ } catch (error) {
5772
+ if (statusOf(error) !== 409) throw error;
5773
+ throw new UsageError(
5774
+ `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.`
5775
+ );
5776
+ }
5777
+ }
5778
+ async function refresh(sync, dir, manifest, pushed) {
5779
+ const tree = await sync.remote.json(
5780
+ get(`/agents/v2/workflows/${manifest.workflowId}/head/files`)
5781
+ );
5782
+ const files = {};
5783
+ for (const entry of tree.files) {
5784
+ if (entry.content !== void 0) {
5785
+ const bytes = Buffer.from(entry.content, "utf8");
5786
+ files[entry.path] = sha256(bytes);
5787
+ if (pushed.get(entry.path) !== files[entry.path])
5788
+ await sync.files.write(join(dir, entry.path), bytes);
5789
+ } else {
5790
+ const local2 = await sync.files.read(join(dir, entry.path));
5791
+ const hash = local2 ? sha256(local2) : pushed.get(entry.path) ?? entry.checksum;
5792
+ if (hash) files[entry.path] = hash;
5793
+ }
5794
+ }
5795
+ for (const path of await sync.files.list(dir)) {
5796
+ if (path !== MANIFEST && !(path in files))
5797
+ await sync.files.remove(join(dir, path));
5798
+ }
5799
+ const next = { ...manifest, versionId: tree.versionId, rev: tree.rev, files };
5800
+ await writeManifest(sync, dir, next);
5801
+ return next;
5802
+ }
5803
+ async function pushWorkflow(sync, dir) {
5804
+ const manifest = await readManifest(sync, dir);
5805
+ const { writes, deletes } = await localChanges(sync, dir, manifest);
5806
+ const result = {
5807
+ workflowId: manifest.workflowId,
5808
+ dir,
5809
+ written: writes.map((w) => w.path),
5810
+ deleted: deletes
5811
+ };
5812
+ if (writes.length === 0 && deletes.length === 0) {
5813
+ return { ...result, rev: manifest.rev, versionId: manifest.versionId };
5814
+ }
5815
+ await patch(
5816
+ sync,
5817
+ manifest,
5818
+ writes.map(({ path, contentBase64 }) => ({ path, contentBase64 })),
5819
+ deletes
5820
+ );
5821
+ const next = await refresh(
5822
+ sync,
5823
+ dir,
5824
+ manifest,
5825
+ new Map(writes.map((w) => [w.path, w.hash]))
5826
+ );
5827
+ return { ...result, rev: next.rev, versionId: next.versionId };
5828
+ }
5829
+ function formatWorkflowPush(result) {
5830
+ if (result.written.length === 0 && result.deleted.length === 0) {
5831
+ return `Nothing to push: ${result.dir}/ matches the head you pulled (rev ${result.rev}).`;
5832
+ }
5833
+ return [
5834
+ `Pushed to workflow ${result.workflowId}: ${result.written.length} written, ${result.deleted.length} deleted.`,
5835
+ ...result.written.map((path) => ` + ${path}`),
5836
+ ...result.deleted.map((path) => ` - ${path}`),
5837
+ `Head is now rev ${result.rev} (version ${result.versionId}).`
5838
+ ].join("\n");
5839
+ }
5840
+
5841
+ // src/sync/run.ts
5842
+ function flag(parsed, name) {
5843
+ return parsed.flags.get(name)?.at(-1);
5844
+ }
5845
+ function integer(parsed, name) {
5846
+ const raw = flag(parsed, name);
5847
+ if (raw === void 0) return void 0;
5848
+ if (!/^\d+$/.test(raw))
5849
+ throw new UsageError(`--${name} must be a whole number`);
5850
+ return Number(raw);
5851
+ }
5852
+ function executionOptions(parsed) {
5853
+ const summaryOnly = flag(parsed, "summary-only") === "true";
5854
+ const glob = flag(parsed, "files");
5855
+ const dir = flag(parsed, "dir");
5856
+ return {
5857
+ executionId: parsed.positionals[0],
5858
+ activities: !summaryOnly,
5859
+ files: !summaryOnly && glob !== "false",
5860
+ glob: glob === "false" ? void 0 : glob,
5861
+ maxFileBytes: integer(parsed, "max-file-size"),
5862
+ dir: dir ? () => dir : void 0
5863
+ };
5864
+ }
5865
+ async function execute(command, parsed, sync, deps) {
5866
+ const root = deps.root ?? "";
5867
+ switch (command.command) {
5868
+ case "workflow pull": {
5869
+ const result = await pullWorkflow(sync, {
5870
+ workflowId: parsed.positionals[0],
5871
+ versionId: flag(parsed, "version-id"),
5872
+ dir: flag(parsed, "dir"),
5873
+ root,
5874
+ ...deps.workflowPull
5875
+ });
5876
+ return { result, text: formatWorkflowPull(result) };
5877
+ }
5878
+ case "workflow push": {
5879
+ const result = await pushWorkflow(sync, flag(parsed, "dir") ?? ".");
5880
+ return { result, text: formatWorkflowPush(result) };
5881
+ }
5882
+ case "execution pull": {
5883
+ const result = await pullExecution(sync, {
5884
+ ...executionOptions(parsed),
5885
+ root,
5886
+ ...deps.executionPull
5887
+ });
5888
+ return { result, text: formatExecutionPull(result) };
5889
+ }
5890
+ default:
5891
+ throw new UsageError(`unknown command ${command.command}`);
5892
+ }
5893
+ }
5894
+ async function runSync(command, parsed, deps) {
5895
+ const sync = {
5896
+ remote: deps.remote,
5897
+ files: deps.files ?? nodeFiles(deps.cwd)
5898
+ };
5899
+ const { result, text: text2 } = await execute(command, parsed, sync, deps);
5900
+ deps.stdout(`${parsed.json ? JSON.stringify(result, null, 2) : text2}
5901
+ `);
5902
+ return 0;
5903
+ }
5108
5904
  export {
5905
+ ALL_COMMANDS,
5109
5906
  COMMANDS,
5907
+ MANIFEST,
5908
+ SYNC_COMMANDS,
5110
5909
  UsageError,
5111
5910
  buildRequest,
5911
+ formatActivitiesMarkdown,
5912
+ formatExecutionPull,
5913
+ formatWorkflowPull,
5914
+ formatWorkflowPush,
5112
5915
  leadingWords,
5113
5916
  matchCommand,
5917
+ matchesGlob,
5114
5918
  parseInvocation,
5115
- render
5919
+ pullExecution,
5920
+ pullWorkflow,
5921
+ pushWorkflow,
5922
+ render,
5923
+ runSync,
5924
+ withSyncCommands
5116
5925
  };