@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/core.js CHANGED
@@ -4555,17 +4555,18 @@ 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
- function cell(value2) {
4563
+ function cell(value2, options = {}) {
4564
4564
  if (value2 === null || value2 === void 0) return "";
4565
- const line = text(value2).replace(/\s+/g, " ");
4566
- return line.length > MAX_CELL ? `${line.slice(0, MAX_CELL - 1)}\u2026` : line;
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`;
4567
4568
  }
4568
- function columns(rows) {
4569
+ function columns(rows, options) {
4569
4570
  const scalarKeys = [];
4570
4571
  for (const row of rows) {
4571
4572
  for (const [key, value2] of Object.entries(row)) {
@@ -4577,7 +4578,7 @@ function columns(rows) {
4577
4578
  ...PREFERRED.filter((key) => scalarKeys.includes(key)),
4578
4579
  ...scalarKeys.filter((key) => !PREFERRED.includes(key))
4579
4580
  ];
4580
- return ordered.slice(0, MAX_COLUMNS);
4581
+ return options.full ? ordered : ordered.slice(0, MAX_COLUMNS);
4581
4582
  }
4582
4583
  function style(format, value2, options) {
4583
4584
  return options.styled ? styleText(format, value2, { validateStream: false }) : value2;
@@ -4597,9 +4598,9 @@ function flatten(row) {
4597
4598
  function table(input, options = {}) {
4598
4599
  if (input.length === 0) return "No results.";
4599
4600
  const rows = input.map(flatten);
4600
- const keys = columns(rows);
4601
+ const keys = columns(rows, options);
4601
4602
  if (keys.length === 0) return rows.map((row) => text(row)).join("\n");
4602
- const cells = rows.map((row) => keys.map((key) => cell(row[key])));
4603
+ const cells = rows.map((row) => keys.map((key) => cell(row[key], options)));
4603
4604
  if (options.styled) {
4604
4605
  const grid = new Table({
4605
4606
  head: keys.map((key) => style("bold", key, options)),
@@ -4611,15 +4612,15 @@ function table(input, options = {}) {
4611
4612
  const widths = keys.map(
4612
4613
  (key, i) => Math.max(key.length, ...cells.map((r) => r[i].length))
4613
4614
  );
4614
- const line = (values) => values.map((v, i) => v.padEnd(widths[i])).join(" ").trimEnd();
4615
- 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(
4616
4617
  "\n"
4617
4618
  );
4618
4619
  }
4619
- function summary(value2) {
4620
+ function summary(value2, options) {
4620
4621
  if (Array.isArray(value2))
4621
4622
  return `[${value2.length} item${value2.length === 1 ? "" : "s"}]`;
4622
- if (value2 && typeof value2 === "object") return cell(value2);
4623
+ if (value2 && typeof value2 === "object") return cell(value2, options);
4623
4624
  return text(value2);
4624
4625
  }
4625
4626
  function record(value2, options) {
@@ -4638,7 +4639,7 @@ ${" ".repeat(width + 2)}`)}`;
4638
4639
  return `${style("bold", key, options)}:
4639
4640
  ${indent(record(v, options))}`;
4640
4641
  }
4641
- return `${label(key)} ${isScalar(v) ? text(v) : summary(v)}`;
4642
+ return `${label(key)} ${isScalar(v) ? text(v) : summary(v, options)}`;
4642
4643
  }).join("\n");
4643
4644
  }
4644
4645
  function render(value2, options = {}) {
@@ -4726,12 +4727,12 @@ var DOTTED = /^--([a-z0-9-]+\.[a-z0-9.-]+)(?:=(.*))?$/;
4726
4727
  function collect(value2, previous) {
4727
4728
  return [...previous ?? [], value2];
4728
4729
  }
4729
- function optionText(option) {
4730
- const text2 = option.description?.replace(/\s+/g, " ").trim() ?? "";
4730
+ function optionText(option2) {
4731
+ const text2 = option2.description?.replace(/\s+/g, " ").trim() ?? "";
4731
4732
  const parts = [text2 && !/[.!?]$/.test(text2) ? `${text2}.` : text2];
4732
- if (option.enum) parts.push(`One of: ${option.enum.join(", ")}.`);
4733
- if (option.type.endsWith("[]")) parts.push("Repeatable.");
4734
- 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.");
4735
4736
  return parts.filter(Boolean).join(" ");
4736
4737
  }
4737
4738
  function splitDotted(argv) {
@@ -4758,18 +4759,20 @@ function group(parent, name, groups, path) {
4758
4759
  return created;
4759
4760
  }
4760
4761
  function leafOptions(leaf, command) {
4761
- for (const option of [...command.query, ...command.body?.fields ?? []]) {
4762
- if (option.type === "boolean") {
4763
- leaf.option(`--${option.flag}`, optionText(option));
4764
- 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.`);
4765
4766
  continue;
4766
4767
  }
4767
- const placeholder = option.enum ? option.enum.join("|") : option.type.replace("[]", "");
4768
+ const placeholder = option2.enum ? option2.enum.join("|") : option2.type.replace("[]", "");
4768
4769
  leaf.option(
4769
- `--${option.flag} <${placeholder}>`,
4770
- optionText(option),
4770
+ `--${option2.flag} <${placeholder}>`,
4771
+ optionText(option2),
4771
4772
  collect
4772
4773
  );
4774
+ if (option2.negatable)
4775
+ leaf.option(`--no-${option2.flag}`, `Skip ${option2.flag}.`);
4773
4776
  }
4774
4777
  for (const file of command.body?.files ?? []) {
4775
4778
  leaf.option(`--${file} <path>`, "File to upload. Repeatable.", collect);
@@ -4779,11 +4782,11 @@ function toParsed(leaf, positionals, dotted) {
4779
4782
  const globals = leaf.optsWithGlobals();
4780
4783
  const own = leaf.opts();
4781
4784
  const flags = new Map(dotted);
4782
- for (const option of leaf.options) {
4783
- const flag = option.long?.replace(/^--(no-)?/, "");
4784
- const value2 = own[option.attributeName()];
4785
- if (!flag || value2 === void 0 || flags.has(flag)) continue;
4786
- 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)]);
4787
4790
  }
4788
4791
  return {
4789
4792
  positionals,
@@ -4796,7 +4799,11 @@ function toParsed(leaf, positionals, dotted) {
4796
4799
  }
4797
4800
  function defineCommands(root, options, dotted = /* @__PURE__ */ new Map(), onInvocation = () => void 0) {
4798
4801
  if (options.description) root.description(options.description);
4799
- 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
+ );
4800
4807
  for (const [flags, text2] of GLOBAL_FLAGS) {
4801
4808
  if (!flags.startsWith("-h")) root.option(flags, text2);
4802
4809
  }
@@ -4858,8 +4865,8 @@ ${notes.join("\n")}`);
4858
4865
  );
4859
4866
  }
4860
4867
  const unknownDotted = [...dotted.keys()].filter(
4861
- (flag) => !(command.body?.fields ?? []).some(
4862
- (f) => flag.startsWith(`${f.flag}.`)
4868
+ (flag2) => !(command.body?.fields ?? []).some(
4869
+ (f) => flag2.startsWith(`${f.flag}.`)
4863
4870
  )
4864
4871
  );
4865
4872
  if (unknownDotted.length > 0) {
@@ -4880,6 +4887,15 @@ async function parseInvocation(argv, options) {
4880
4887
  let stdout = "";
4881
4888
  let stderr = "";
4882
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
+ }
4883
4899
  const { argv: rest, dotted } = splitDotted(argv);
4884
4900
  const root = new Command("asteroid").exitOverride().helpCommand(false).configureOutput({
4885
4901
  writeOut: (text2) => stdout += text2,
@@ -4914,34 +4930,34 @@ import { basename } from "node:path";
4914
4930
  function camel(segment) {
4915
4931
  return segment.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
4916
4932
  }
4917
- function scalar(option, raw) {
4918
- const type = option.type.replace("[]", "");
4919
- 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)) {
4920
4936
  throw new UsageError(
4921
- `--${option.flag} must be one of ${option.enum.join(", ")}`
4937
+ `--${option2.flag} must be one of ${option2.enum.join(", ")}`
4922
4938
  );
4923
4939
  }
4924
4940
  if (type === "integer" || type === "number") {
4925
4941
  const value2 = Number(raw);
4926
4942
  if (raw.trim() === "" || Number.isNaN(value2) || type === "integer" && !Number.isInteger(value2)) {
4927
4943
  throw new UsageError(
4928
- `--${option.flag} must be ${type === "integer" ? "an integer" : "a number"}`
4944
+ `--${option2.flag} must be ${type === "integer" ? "an integer" : "a number"}`
4929
4945
  );
4930
4946
  }
4931
4947
  return value2;
4932
4948
  }
4933
4949
  if (type === "boolean") {
4934
4950
  if (raw !== "true" && raw !== "false") {
4935
- throw new UsageError(`--${option.flag} must be true or false`);
4951
+ throw new UsageError(`--${option2.flag} must be true or false`);
4936
4952
  }
4937
4953
  return raw === "true";
4938
4954
  }
4939
- if (type === "json") return parseJson(raw, `--${option.flag}`);
4955
+ if (type === "json") return parseJson(raw, `--${option2.flag}`);
4940
4956
  return raw;
4941
4957
  }
4942
- function value(option, raws) {
4943
- if (option.type.endsWith("[]")) return raws.map((raw) => scalar(option, raw));
4944
- 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));
4945
4961
  }
4946
4962
  var nodeReader = {
4947
4963
  text: async (path) => readFileSync(path, "utf8"),
@@ -4953,12 +4969,12 @@ async function readInput(raw, io) {
4953
4969
  return raw;
4954
4970
  }
4955
4971
  async function resolveInputs(command, parsed, io) {
4956
- 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);
4957
4973
  const flags = new Map(parsed.flags);
4958
- for (const flag of jsonFlags) {
4959
- const raws = flags.get(flag);
4974
+ for (const flag2 of jsonFlags) {
4975
+ const raws = flags.get(flag2);
4960
4976
  if (raws)
4961
- 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))));
4962
4978
  }
4963
4979
  const body = parsed.body === void 0 ? void 0 : await readInput(parsed.body, io);
4964
4980
  return { ...parsed, flags, body };
@@ -5008,18 +5024,18 @@ async function buildRequest(command, input, context = {}) {
5008
5024
  });
5009
5025
  const flags = new Map(parsed.flags);
5010
5026
  const query = new URLSearchParams();
5011
- for (const option of command.query) {
5012
- const raws = flags.get(option.flag);
5013
- flags.delete(option.flag);
5027
+ for (const option2 of command.query) {
5028
+ const raws = flags.get(option2.flag);
5029
+ flags.delete(option2.flag);
5014
5030
  if (!raws) {
5015
- if (option.required) throw new UsageError(`--${option.flag} is required`);
5031
+ if (option2.required) throw new UsageError(`--${option2.flag} is required`);
5016
5032
  continue;
5017
5033
  }
5018
- const parsedValue = value(option, raws);
5034
+ const parsedValue = value(option2, raws);
5019
5035
  const values = Array.isArray(parsedValue) ? parsedValue : [parsedValue];
5020
- if (option.explode === false)
5021
- query.append(option.name, values.map(String).join(","));
5022
- 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));
5023
5039
  }
5024
5040
  const built = { method: command.method, path, query };
5025
5041
  const body = command.body;
@@ -5069,11 +5085,11 @@ async function buildRequest(command, input, context = {}) {
5069
5085
  flags.delete(field.flag);
5070
5086
  json[field.name] = value(field, raws);
5071
5087
  }
5072
- for (const [flag, raws] of [...flags]) {
5073
- const [head, ...rest] = flag.split(".");
5088
+ for (const [flag2, raws] of [...flags]) {
5089
+ const [head, ...rest] = flag2.split(".");
5074
5090
  const field = body.fields.find((f) => f.flag === head);
5075
5091
  if (!field || rest.length === 0) continue;
5076
- flags.delete(flag);
5092
+ flags.delete(flag2);
5077
5093
  const current = json[field.name];
5078
5094
  json[field.name] = typeof current === "object" && current !== null ? current : {};
5079
5095
  setPath(json, [field.name, ...rest.map(camel)], loose(raws.at(-1)));
@@ -5104,12 +5120,806 @@ function rejectUnknown(flags) {
5104
5120
  );
5105
5121
  }
5106
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
+ }
5107
5904
  export {
5905
+ ALL_COMMANDS,
5108
5906
  COMMANDS,
5907
+ MANIFEST,
5908
+ SYNC_COMMANDS,
5109
5909
  UsageError,
5110
5910
  buildRequest,
5911
+ formatActivitiesMarkdown,
5912
+ formatExecutionPull,
5913
+ formatWorkflowPull,
5914
+ formatWorkflowPush,
5111
5915
  leadingWords,
5112
5916
  matchCommand,
5917
+ matchesGlob,
5113
5918
  parseInvocation,
5114
- render
5919
+ pullExecution,
5920
+ pullWorkflow,
5921
+ pushWorkflow,
5922
+ render,
5923
+ runSync,
5924
+ withSyncCommands
5115
5925
  };