@asteroidai/cli 1.9.111 → 1.9.122

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.
Files changed (3) hide show
  1. package/dist/asteroid.js +408 -51
  2. package/dist/core.js +415 -50
  3. package/package.json +3 -3
package/dist/asteroid.js CHANGED
@@ -1946,6 +1946,34 @@ var COMMANDS = [
1946
1946
  destructive: false,
1947
1947
  capability: "run_execution"
1948
1948
  },
1949
+ {
1950
+ command: "execution sync",
1951
+ tool: "execution_sync",
1952
+ title: "Wait for an execution to finish",
1953
+ description: "Wait up to `timeoutSeconds` (1-25, default 25) for a running execution to finish. Returns the terminal `status` and `executionResult` when it finishes in time. Otherwise returns the current status: call it again to keep waiting. A paused or `awaiting_confirmation` execution returns at once, since it needs a person. Use it after `workflow_execute_post`, or after `workflow_execute_sync_post` timed out, instead of polling `execution_get`.",
1954
+ method: "GET",
1955
+ path: "/agents/v2/executions/{executionId}/sync",
1956
+ args: [
1957
+ {
1958
+ name: "executionId",
1959
+ flag: "execution-id",
1960
+ type: "string",
1961
+ required: true
1962
+ }
1963
+ ],
1964
+ query: [
1965
+ {
1966
+ name: "timeoutSeconds",
1967
+ flag: "timeout-seconds",
1968
+ type: "integer",
1969
+ required: false,
1970
+ description: "Seconds to wait for the execution to finish before returning 202. Defaults to 25."
1971
+ }
1972
+ ],
1973
+ binary: false,
1974
+ readOnly: true,
1975
+ destructive: false
1976
+ },
1949
1977
  {
1950
1978
  command: "insight get",
1951
1979
  tool: "insight_get",
@@ -2206,7 +2234,7 @@ var COMMANDS = [
2206
2234
  command: "portal-skills add",
2207
2235
  tool: "portal_skills_add",
2208
2236
  title: "Add portal skills",
2209
- description: "Add a portal's skills to an organization, by a `portalId` from `portal_skills_list`. Needs a Startup plan or above. Astro loads the portal's skills once it is added. Adding one the organization already has is a no-op.",
2237
+ description: "Add a portal's skills to an organization, by the `portalId` of a portal skill from `skills_list`. Needs a Startup plan or above. Astro loads the portal's skills once it is added, and `skill_get` serves them. Adding one the organization already has is a no-op.",
2210
2238
  method: "POST",
2211
2239
  path: "/agents/v2/portal-skills/{portalId}",
2212
2240
  args: [
@@ -2222,27 +2250,6 @@ var COMMANDS = [
2222
2250
  readOnly: false,
2223
2251
  destructive: false
2224
2252
  },
2225
- {
2226
- command: "portal-skills list",
2227
- tool: "portal_skills_list",
2228
- title: "List portal skills",
2229
- description: "List the skills for third-party portals, or only for the portals matching a portal name or task, such as `caloptima eligibility`. A portal skill is general knowledge about how a portal works: sign-in, page quirks, field mappings. Each portal comes with its skill names, and whether the organization has added it. Add one with `portal_skills_add`.",
2230
- method: "GET",
2231
- path: "/astro/v2/portal-skills",
2232
- args: [],
2233
- query: [
2234
- {
2235
- name: "query",
2236
- flag: "query",
2237
- type: "string",
2238
- required: false,
2239
- description: "A portal name or task, at least three characters. Omit to list every portal with skills."
2240
- }
2241
- ],
2242
- binary: false,
2243
- readOnly: true,
2244
- destructive: false
2245
- },
2246
2253
  {
2247
2254
  command: "profile clear-cache",
2248
2255
  tool: "profile_clear_browser_cache",
@@ -4018,6 +4025,80 @@ var COMMANDS = [
4018
4025
  credentialOrganization: false
4019
4026
  }
4020
4027
  },
4028
+ {
4029
+ command: "skill get",
4030
+ tool: "skill_get",
4031
+ title: "Get skill",
4032
+ description: "Get one skill with the content of every file, SKILL.md first, in the Agent Skills format. A name resolves to the public skill, then a portal's, then the organization's; set `kind` to pick one. A portal skill needs its portal added with `portal_skills_add`. Pass `surface` `mcp` to spell the platform's commands as MCP tools.",
4033
+ method: "GET",
4034
+ path: "/astro/v2/skills/{name}",
4035
+ args: [
4036
+ {
4037
+ name: "name",
4038
+ flag: "name",
4039
+ type: "string",
4040
+ required: true
4041
+ }
4042
+ ],
4043
+ query: [
4044
+ {
4045
+ name: "kind",
4046
+ flag: "kind",
4047
+ type: "string",
4048
+ required: false,
4049
+ description: "Only a skill of this kind.",
4050
+ enum: ["public", "portal", "organization"]
4051
+ },
4052
+ {
4053
+ name: "surface",
4054
+ flag: "surface",
4055
+ type: "string",
4056
+ required: false,
4057
+ description: "The surface the skill is rendered for. Defaults to `cli`.",
4058
+ enum: ["cli", "mcp"]
4059
+ }
4060
+ ],
4061
+ binary: false,
4062
+ readOnly: true,
4063
+ destructive: false
4064
+ },
4065
+ {
4066
+ command: "skill list",
4067
+ tool: "skills_list",
4068
+ title: "List skills",
4069
+ description: "List the skills an organization can load, by kind then name: Asteroid's `public` skills, the `portal` skills (general knowledge of a third-party portal: sign-in, page quirks, field mappings) with whether the organization has added the portal, and the `organization`'s own. Filter with `kind`, or rank by a `query` such as `caloptima eligibility`. Each skill comes with its file paths and digests; `skill_get` returns the content. Add a portal with `portal_skills_add`.",
4070
+ method: "GET",
4071
+ path: "/astro/v2/skills",
4072
+ args: [],
4073
+ query: [
4074
+ {
4075
+ name: "kind",
4076
+ flag: "kind",
4077
+ type: "string",
4078
+ required: false,
4079
+ description: "Only skills of this kind.",
4080
+ enum: ["public", "portal", "organization"]
4081
+ },
4082
+ {
4083
+ name: "query",
4084
+ flag: "query",
4085
+ type: "string",
4086
+ required: false,
4087
+ description: "A skill name, portal name or task, at least three characters. Best matches first."
4088
+ },
4089
+ {
4090
+ name: "surface",
4091
+ flag: "surface",
4092
+ type: "string",
4093
+ required: false,
4094
+ description: "The surface the skills are rendered for. Defaults to `cli`.",
4095
+ enum: ["cli", "mcp"]
4096
+ }
4097
+ ],
4098
+ binary: false,
4099
+ readOnly: true,
4100
+ destructive: false
4101
+ },
4021
4102
  {
4022
4103
  command: "tag create",
4023
4104
  tool: "workflow_tags_create",
@@ -6147,6 +6228,85 @@ var COMMANDS = [
6147
6228
  }
6148
6229
  ];
6149
6230
 
6231
+ // src/sync/types.ts
6232
+ var MANIFEST = ".asteroid.json";
6233
+ function get(path, query = {}) {
6234
+ return { method: "GET", path, query: new URLSearchParams(query) };
6235
+ }
6236
+ function statusOf(error) {
6237
+ const status = error?.status;
6238
+ return typeof status === "number" ? status : void 0;
6239
+ }
6240
+
6241
+ // src/sync/execution-wait.ts
6242
+ var WAIT_TIMEOUT_SECONDS = 600;
6243
+ var WAIT_INTERVAL_SECONDS = 15;
6244
+ var MAX_WAIT_TIMEOUT_SECONDS = 3600;
6245
+ var MAX_WAIT_INTERVAL_SECONDS = 120;
6246
+ var TERMINAL = /* @__PURE__ */ new Set(["completed", "cancelled", "failed"]);
6247
+ function isTerminalExecutionStatus(status) {
6248
+ return TERMINAL.has(status);
6249
+ }
6250
+ var SYNC_TIMEOUT_SECONDS = 25;
6251
+ function pause(ms) {
6252
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
6253
+ }
6254
+ async function readStatus(sync, executionId2, timeoutMs) {
6255
+ if (timeoutMs <= 0)
6256
+ return await sync.remote.json(
6257
+ get(`/agents/v2/executions/${executionId2}`)
6258
+ );
6259
+ const timeoutSeconds = Math.min(
6260
+ SYNC_TIMEOUT_SECONDS,
6261
+ Math.max(1, Math.ceil(timeoutMs / 1e3))
6262
+ );
6263
+ const response = await sync.remote.json(
6264
+ get(`/agents/v2/executions/${executionId2}/sync`, {
6265
+ timeoutSeconds: String(timeoutSeconds)
6266
+ })
6267
+ );
6268
+ return {
6269
+ id: response.executionId,
6270
+ status: response.status,
6271
+ executionResult: response.executionResult
6272
+ };
6273
+ }
6274
+ async function waitForExecution(sync, options) {
6275
+ const timeoutMs = (options.timeoutSeconds ?? WAIT_TIMEOUT_SECONDS) * 1e3;
6276
+ const intervalMs = (options.intervalSeconds ?? WAIT_INTERVAL_SECONDS) * 1e3;
6277
+ const now = options.now ?? Date.now;
6278
+ const sleep = options.sleep ?? pause;
6279
+ const started = now();
6280
+ let polls = 0;
6281
+ for (; ; ) {
6282
+ const called = now();
6283
+ const execution = await readStatus(
6284
+ sync,
6285
+ options.executionId,
6286
+ timeoutMs - (called - started)
6287
+ );
6288
+ polls++;
6289
+ const elapsed = now() - started;
6290
+ const waitedSeconds = Math.round(elapsed / 1e3);
6291
+ if (isTerminalExecutionStatus(execution.status))
6292
+ return { execution, terminal: true, waitedSeconds, polls };
6293
+ const remaining = timeoutMs - elapsed;
6294
+ if (remaining <= 0)
6295
+ return { execution, terminal: false, waitedSeconds, polls };
6296
+ const early = intervalMs - (now() - called);
6297
+ if (early > 0) await sleep(Math.min(early, remaining));
6298
+ }
6299
+ }
6300
+ function formatExecutionWait(result) {
6301
+ const { execution } = result;
6302
+ if (!result.terminal) {
6303
+ return `Execution ${execution.id} still ${execution.status} after ${result.waitedSeconds}s. Wait again, or read what it has so far with \`asteroid execution pull ${execution.id}\`.`;
6304
+ }
6305
+ const line2 = `Execution ${execution.id} ${execution.status}.`;
6306
+ const outcome = execution.executionResult;
6307
+ return outcome ? `${line2} Result: ${outcome.outcome}: ${outcome.reasoning}` : line2;
6308
+ }
6309
+
6150
6310
  // src/sync/commands.ts
6151
6311
  function option(name, type, description, extra = {}) {
6152
6312
  return { name, flag: name, type, required: false, description, ...extra };
@@ -6158,6 +6318,12 @@ var local = {
6158
6318
  destructive: false,
6159
6319
  local: true
6160
6320
  };
6321
+ var executionId = {
6322
+ name: "executionId",
6323
+ flag: "execution-id",
6324
+ type: "string",
6325
+ required: true
6326
+ };
6161
6327
  var SYNC_COMMANDS = [
6162
6328
  {
6163
6329
  ...local,
@@ -6166,14 +6332,7 @@ var SYNC_COMMANDS = [
6166
6332
  title: "Pull an execution to disk",
6167
6333
  description: "Write an execution to `<workflow-name>/executions/<id>/`: summary.json, activities.md (the step-by-step log), and the files the run produced.",
6168
6334
  readOnly: true,
6169
- args: [
6170
- {
6171
- name: "executionId",
6172
- flag: "execution-id",
6173
- type: "string",
6174
- required: true
6175
- }
6176
- ],
6335
+ args: [executionId],
6177
6336
  query: [
6178
6337
  option(
6179
6338
  "files",
@@ -6199,6 +6358,54 @@ var SYNC_COMMANDS = [
6199
6358
  "Write here instead of `<workflow-name>/executions/<id>/` under the current directory."
6200
6359
  )
6201
6360
  ]
6361
+ },
6362
+ {
6363
+ ...local,
6364
+ command: "execution wait",
6365
+ tool: "execution_wait",
6366
+ title: "Wait for an execution to end",
6367
+ description: "Poll an execution until it is completed, failed or cancelled, then print its status and, when the run produced one, its result. When the timeout passes first, print the status it still has, so you can wait again.",
6368
+ readOnly: true,
6369
+ args: [executionId],
6370
+ query: [
6371
+ option(
6372
+ "timeout",
6373
+ "integer",
6374
+ `Give up after this many seconds, ${WAIT_TIMEOUT_SECONDS} unless set, ${MAX_WAIT_TIMEOUT_SECONDS} at most.`
6375
+ ),
6376
+ option(
6377
+ "interval",
6378
+ "integer",
6379
+ `Poll every this many seconds, ${WAIT_INTERVAL_SECONDS} unless set, ${MAX_WAIT_INTERVAL_SECONDS} at most.`
6380
+ )
6381
+ ]
6382
+ },
6383
+ {
6384
+ ...local,
6385
+ command: "insight pull",
6386
+ tool: "insight_pull",
6387
+ title: "Pull insights to disk",
6388
+ description: "Write insights to `insights/<name>.md`, one markdown file each with YAML frontmatter (id, type, status, name, next_steps). Name an insight to pull that one. A file another insight or an unpushed draft already holds keeps its name; the pulled one gets a -2, -3 suffix.",
6389
+ readOnly: true,
6390
+ args: [
6391
+ {
6392
+ name: "insightId",
6393
+ flag: "insight-id",
6394
+ type: "string",
6395
+ required: false,
6396
+ description: "One insight to pull. Without it, every insight, or one workflow's with --workflow-id."
6397
+ }
6398
+ ],
6399
+ query: [
6400
+ option("workflowId", "string", "Only this workflow's insights.", {
6401
+ flag: "workflow-id"
6402
+ }),
6403
+ option(
6404
+ "dir",
6405
+ "string",
6406
+ "Write here instead of `insights/` under the current directory."
6407
+ )
6408
+ ]
6202
6409
  }
6203
6410
  ];
6204
6411
 
@@ -6657,15 +6864,16 @@ function defineCommands(root, options, dotted2 = /* @__PURE__ */ new Map(), onIn
6657
6864
  (arg) => options.defaultArg?.(command, arg)
6658
6865
  );
6659
6866
  const defaulted = defaults[0] !== void 0;
6867
+ const optional = (arg, i) => i === 0 && defaulted || !arg.required;
6660
6868
  command.args.forEach((arg) => {
6661
6869
  leaf.argument(
6662
- defaulted ? `[${arg.flag}]` : `<${arg.flag}>`,
6870
+ defaulted || !arg.required ? `[${arg.flag}]` : `<${arg.flag}>`,
6663
6871
  arg.description ?? ""
6664
6872
  );
6665
6873
  });
6666
6874
  if (defaulted) {
6667
6875
  const names = command.args.map(
6668
- (arg, i) => i === 0 ? `[${arg.flag}]` : `<${arg.flag}>`
6876
+ (arg, i) => optional(arg, i) ? `[${arg.flag}]` : `<${arg.flag}>`
6669
6877
  );
6670
6878
  leaf.usage(`[options] ${names.join(" ")}`);
6671
6879
  }
@@ -6682,8 +6890,9 @@ ${notes.join("\n")}`);
6682
6890
  if (positionals.length === command.args.length - 1 && defaults[0] !== void 0) {
6683
6891
  positionals = [defaults[0], ...positionals];
6684
6892
  }
6685
- if (positionals.length !== command.args.length) {
6686
- const expected = command.args.map((a, i) => i === 0 && defaulted ? `[${a.flag}]` : `<${a.flag}>`).join(" ");
6893
+ const required = command.args.filter((arg) => arg.required).length;
6894
+ if (positionals.length < required || positionals.length > command.args.length) {
6895
+ const expected = command.args.map((a, i) => optional(a, i) ? `[${a.flag}]` : `<${a.flag}>`).join(" ");
6687
6896
  throw new UsageError(
6688
6897
  `expected ${expected}, got ${cmd.args.length} argument(s)`
6689
6898
  );
@@ -7205,7 +7414,7 @@ var ApiError = class extends Error {
7205
7414
  status;
7206
7415
  body;
7207
7416
  };
7208
- var VERSION = "1.9.111";
7417
+ var VERSION = "1.9.122";
7209
7418
  function problem(status, body, fallback) {
7210
7419
  if (body && typeof body === "object") {
7211
7420
  const record2 = body;
@@ -8017,6 +8226,13 @@ function normalizeNodeName(name) {
8017
8226
  }
8018
8227
  return result;
8019
8228
  }
8229
+ function lossySlugOr(name, fallback) {
8230
+ try {
8231
+ return normalizeNodeName(name);
8232
+ } catch {
8233
+ return fallback;
8234
+ }
8235
+ }
8020
8236
  function nodeDirName(name) {
8021
8237
  if (isNameCharsetConforming(name)) {
8022
8238
  return name.replaceAll(" ", "_");
@@ -8030,8 +8246,8 @@ function agentDirName(name) {
8030
8246
  return "agent";
8031
8247
  }
8032
8248
  }
8033
- function executionDirName(executionId) {
8034
- return executionId.slice(0, 8);
8249
+ function executionDirName(executionId2) {
8250
+ return executionId2.slice(0, 8);
8035
8251
  }
8036
8252
  function workflowDirName(workflowId, version, parentId, parentVersion) {
8037
8253
  const short = workflowId.slice(0, 8);
@@ -8079,16 +8295,6 @@ function matchesGlob(path, pattern) {
8079
8295
  return new RegExp(`^${source}$`).test(path);
8080
8296
  }
8081
8297
 
8082
- // src/sync/types.ts
8083
- var MANIFEST = ".asteroid.json";
8084
- function get(path, query = {}) {
8085
- return { method: "GET", path, query: new URLSearchParams(query) };
8086
- }
8087
- function statusOf(error) {
8088
- const status = error?.status;
8089
- return typeof status === "number" ? status : void 0;
8090
- }
8091
-
8092
8298
  // src/sync/execution-pull.ts
8093
8299
  var DIRECTORIES = ["workspace", "downloads", "shared"];
8094
8300
  function relativeFilePath(file) {
@@ -8126,14 +8332,14 @@ async function writeActivities(sync, dir, execution) {
8126
8332
  return false;
8127
8333
  }
8128
8334
  }
8129
- async function listFiles(sync, executionId) {
8335
+ async function listFiles(sync, executionId2) {
8130
8336
  const listing = await sync.remote.json(
8131
- get(`/agents/v2/executions/${executionId}/files`)
8337
+ get(`/agents/v2/executions/${executionId2}/files`)
8132
8338
  );
8133
8339
  return DIRECTORIES.flatMap((name) => listing.directories[name] ?? []);
8134
8340
  }
8135
- async function download(sync, executionId, file) {
8136
- const path = `/agents/v2/executions/${executionId}/files/${file.id}/signed-url`;
8341
+ async function download(sync, executionId2, file) {
8342
+ const path = `/agents/v2/executions/${executionId2}/files/${file.id}/signed-url`;
8137
8343
  const { url } = await sync.remote.json(get(path));
8138
8344
  return sync.remote.download(url);
8139
8345
  }
@@ -8261,6 +8467,114 @@ ${result.skipped.map((s) => ` - ${s}`).join("\n")}` : null,
8261
8467
  ].filter(Boolean).join("\n");
8262
8468
  }
8263
8469
 
8470
+ // src/sync/insight-file.ts
8471
+ import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
8472
+ var FRONTMATTER = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)(?:\r?\n)?/;
8473
+ function serializeInsightFile(meta, body) {
8474
+ const front = {};
8475
+ if (meta.id) front.id = meta.id;
8476
+ front.type = meta.type;
8477
+ if (meta.status) front.status = meta.status;
8478
+ front.name = meta.name;
8479
+ if (meta.nextSteps) front.next_steps = meta.nextSteps;
8480
+ const yaml = stringifyYaml(front).trimEnd();
8481
+ return `---
8482
+ ${yaml}
8483
+ ---
8484
+
8485
+ ${body}`;
8486
+ }
8487
+ function insightFileId(text3) {
8488
+ const match = FRONTMATTER.exec(text3);
8489
+ if (!match) return void 0;
8490
+ try {
8491
+ const id = parseYaml(match[1])?.id;
8492
+ return typeof id === "string" || typeof id === "number" ? String(id) : void 0;
8493
+ } catch {
8494
+ return void 0;
8495
+ }
8496
+ }
8497
+
8498
+ // src/sync/insight-pull.ts
8499
+ var INSIGHTS_DIR = "insights";
8500
+ var PAGE_SIZE = 100;
8501
+ async function listInsights(sync, workflowId) {
8502
+ const all = [];
8503
+ for (let page = 1; ; page++) {
8504
+ const query = {
8505
+ page: String(page),
8506
+ pageSize: String(PAGE_SIZE)
8507
+ };
8508
+ if (workflowId) query.workflowId = workflowId;
8509
+ const { items, total } = await sync.remote.json(
8510
+ get("/astro/v2/insights", query)
8511
+ );
8512
+ all.push(...items);
8513
+ if (items.length === 0 || all.length >= total) return all;
8514
+ }
8515
+ }
8516
+ async function stemOwners(sync, dir) {
8517
+ const owners = /* @__PURE__ */ new Map();
8518
+ for (const name of await sync.files.list(dir)) {
8519
+ if (!name.endsWith(".md") || name.includes("/")) continue;
8520
+ const bytes = await sync.files.read(join3(dir, name));
8521
+ const text3 = bytes ? Buffer.from(bytes).toString("utf8") : "";
8522
+ owners.set(name.slice(0, -".md".length).toLowerCase(), insightFileId(text3));
8523
+ }
8524
+ return owners;
8525
+ }
8526
+ function assignStems(owners, insights) {
8527
+ const used = /* @__PURE__ */ new Set();
8528
+ const heldByOther = (stem, id) => owners.has(stem) && owners.get(stem) !== id;
8529
+ return insights.map((insight) => {
8530
+ const base = lossySlugOr(insight.name, "insight");
8531
+ let stem = base;
8532
+ for (let n = 2; used.has(stem) || heldByOther(stem, insight.id); n++) {
8533
+ stem = `${base}-${n}`;
8534
+ }
8535
+ used.add(stem);
8536
+ return [insight, stem];
8537
+ });
8538
+ }
8539
+ async function pullInsights(sync, options = {}) {
8540
+ const dir = options.dir ?? INSIGHTS_DIR;
8541
+ const insights = options.insightId ? [
8542
+ await sync.remote.json(
8543
+ get(`/astro/v2/insights/${options.insightId}`)
8544
+ )
8545
+ ] : await listInsights(sync, options.workflowId);
8546
+ const pulled = [];
8547
+ for (const [insight, stem] of assignStems(
8548
+ await stemOwners(sync, dir),
8549
+ insights
8550
+ )) {
8551
+ const path = join3(dir, `${stem}.md`);
8552
+ const text3 = serializeInsightFile(
8553
+ {
8554
+ id: insight.id,
8555
+ type: insight.type,
8556
+ status: insight.status,
8557
+ name: insight.name,
8558
+ nextSteps: insight.nextSteps ?? void 0
8559
+ },
8560
+ insight.content
8561
+ );
8562
+ await sync.files.write(path, Buffer.from(text3, "utf8"));
8563
+ const { id, name, type, status } = insight;
8564
+ pulled.push({ id, name, type, status, path });
8565
+ }
8566
+ return { dir, insights: pulled };
8567
+ }
8568
+ function formatInsightPull(result) {
8569
+ const count = result.insights.length;
8570
+ return [
8571
+ `Pulled ${count} insight${count === 1 ? "" : "s"} into ${result.dir}/.`,
8572
+ ...result.insights.map(
8573
+ (insight) => ` - ${insight.path}: ${insight.name} (${insight.type}, ${insight.status})`
8574
+ )
8575
+ ].join("\n");
8576
+ }
8577
+
8264
8578
  // src/sync/node-files.ts
8265
8579
  import { mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
8266
8580
  import { dirname as dirname2, relative, resolve, sep } from "node:path";
@@ -8673,6 +8987,35 @@ function integer(parsed, name) {
8673
8987
  throw new UsageError(`--${name} must be a whole number`);
8674
8988
  return Number(raw);
8675
8989
  }
8990
+ function bounded(parsed, name, min, max) {
8991
+ const value2 = integer(parsed, name);
8992
+ if (value2 === void 0) return void 0;
8993
+ if (value2 < min || value2 > max)
8994
+ throw new UsageError(`--${name} must be between ${min} and ${max}`);
8995
+ return value2;
8996
+ }
8997
+ function executionWaitOptions(parsed) {
8998
+ return {
8999
+ executionId: parsed.positionals[0],
9000
+ timeoutSeconds: bounded(parsed, "timeout", 0, MAX_WAIT_TIMEOUT_SECONDS),
9001
+ intervalSeconds: bounded(parsed, "interval", 1, MAX_WAIT_INTERVAL_SECONDS)
9002
+ };
9003
+ }
9004
+ function given(value2, name) {
9005
+ if (value2 === void 0) return void 0;
9006
+ if (value2 === "") throw new UsageError(`${name} must not be empty`);
9007
+ return value2;
9008
+ }
9009
+ function insightPullOptions(parsed) {
9010
+ const options = {};
9011
+ const insightId = given(parsed.positionals[0], "<insight-id>");
9012
+ if (insightId) options.insightId = insightId;
9013
+ const workflowId = given(flag(parsed, "workflow-id"), "--workflow-id");
9014
+ if (workflowId) options.workflowId = workflowId;
9015
+ const dir = given(flag(parsed, "dir"), "--dir");
9016
+ if (dir) options.dir = dir;
9017
+ return options;
9018
+ }
8676
9019
  function executionOptions(parsed) {
8677
9020
  const summaryOnly = flag(parsed, "summary-only") === "true";
8678
9021
  const glob = flag(parsed, "files");
@@ -8734,6 +9077,20 @@ async function execute(command, parsed, sync, deps) {
8734
9077
  );
8735
9078
  return { result: result2, text: formatWorkflowPush(result2) };
8736
9079
  }
9080
+ if (command.command === "execution wait") {
9081
+ const result2 = await waitForExecution(sync, {
9082
+ ...executionWaitOptions(parsed),
9083
+ ...deps.executionWait
9084
+ });
9085
+ return { result: result2, text: formatExecutionWait(result2) };
9086
+ }
9087
+ if (command.command === "insight pull") {
9088
+ const result2 = await pullInsights(sync, {
9089
+ ...deps.insightPull,
9090
+ ...insightPullOptions(parsed)
9091
+ });
9092
+ return { result: result2, text: formatInsightPull(result2) };
9093
+ }
8737
9094
  if (command.command !== "execution pull")
8738
9095
  throw new UsageError(`unknown command ${command.command}`);
8739
9096
  const result = await pullExecution(sync, {
package/dist/core.js CHANGED
@@ -1967,6 +1967,34 @@ var COMMANDS = [
1967
1967
  destructive: false,
1968
1968
  capability: "run_execution"
1969
1969
  },
1970
+ {
1971
+ command: "execution sync",
1972
+ tool: "execution_sync",
1973
+ title: "Wait for an execution to finish",
1974
+ description: "Wait up to `timeoutSeconds` (1-25, default 25) for a running execution to finish. Returns the terminal `status` and `executionResult` when it finishes in time. Otherwise returns the current status: call it again to keep waiting. A paused or `awaiting_confirmation` execution returns at once, since it needs a person. Use it after `workflow_execute_post`, or after `workflow_execute_sync_post` timed out, instead of polling `execution_get`.",
1975
+ method: "GET",
1976
+ path: "/agents/v2/executions/{executionId}/sync",
1977
+ args: [
1978
+ {
1979
+ name: "executionId",
1980
+ flag: "execution-id",
1981
+ type: "string",
1982
+ required: true
1983
+ }
1984
+ ],
1985
+ query: [
1986
+ {
1987
+ name: "timeoutSeconds",
1988
+ flag: "timeout-seconds",
1989
+ type: "integer",
1990
+ required: false,
1991
+ description: "Seconds to wait for the execution to finish before returning 202. Defaults to 25."
1992
+ }
1993
+ ],
1994
+ binary: false,
1995
+ readOnly: true,
1996
+ destructive: false
1997
+ },
1970
1998
  {
1971
1999
  command: "insight get",
1972
2000
  tool: "insight_get",
@@ -2227,7 +2255,7 @@ var COMMANDS = [
2227
2255
  command: "portal-skills add",
2228
2256
  tool: "portal_skills_add",
2229
2257
  title: "Add portal skills",
2230
- description: "Add a portal's skills to an organization, by a `portalId` from `portal_skills_list`. Needs a Startup plan or above. Astro loads the portal's skills once it is added. Adding one the organization already has is a no-op.",
2258
+ description: "Add a portal's skills to an organization, by the `portalId` of a portal skill from `skills_list`. Needs a Startup plan or above. Astro loads the portal's skills once it is added, and `skill_get` serves them. Adding one the organization already has is a no-op.",
2231
2259
  method: "POST",
2232
2260
  path: "/agents/v2/portal-skills/{portalId}",
2233
2261
  args: [
@@ -2243,27 +2271,6 @@ var COMMANDS = [
2243
2271
  readOnly: false,
2244
2272
  destructive: false
2245
2273
  },
2246
- {
2247
- command: "portal-skills list",
2248
- tool: "portal_skills_list",
2249
- title: "List portal skills",
2250
- description: "List the skills for third-party portals, or only for the portals matching a portal name or task, such as `caloptima eligibility`. A portal skill is general knowledge about how a portal works: sign-in, page quirks, field mappings. Each portal comes with its skill names, and whether the organization has added it. Add one with `portal_skills_add`.",
2251
- method: "GET",
2252
- path: "/astro/v2/portal-skills",
2253
- args: [],
2254
- query: [
2255
- {
2256
- name: "query",
2257
- flag: "query",
2258
- type: "string",
2259
- required: false,
2260
- description: "A portal name or task, at least three characters. Omit to list every portal with skills."
2261
- }
2262
- ],
2263
- binary: false,
2264
- readOnly: true,
2265
- destructive: false
2266
- },
2267
2274
  {
2268
2275
  command: "profile clear-cache",
2269
2276
  tool: "profile_clear_browser_cache",
@@ -4039,6 +4046,80 @@ var COMMANDS = [
4039
4046
  credentialOrganization: false
4040
4047
  }
4041
4048
  },
4049
+ {
4050
+ command: "skill get",
4051
+ tool: "skill_get",
4052
+ title: "Get skill",
4053
+ description: "Get one skill with the content of every file, SKILL.md first, in the Agent Skills format. A name resolves to the public skill, then a portal's, then the organization's; set `kind` to pick one. A portal skill needs its portal added with `portal_skills_add`. Pass `surface` `mcp` to spell the platform's commands as MCP tools.",
4054
+ method: "GET",
4055
+ path: "/astro/v2/skills/{name}",
4056
+ args: [
4057
+ {
4058
+ name: "name",
4059
+ flag: "name",
4060
+ type: "string",
4061
+ required: true
4062
+ }
4063
+ ],
4064
+ query: [
4065
+ {
4066
+ name: "kind",
4067
+ flag: "kind",
4068
+ type: "string",
4069
+ required: false,
4070
+ description: "Only a skill of this kind.",
4071
+ enum: ["public", "portal", "organization"]
4072
+ },
4073
+ {
4074
+ name: "surface",
4075
+ flag: "surface",
4076
+ type: "string",
4077
+ required: false,
4078
+ description: "The surface the skill is rendered for. Defaults to `cli`.",
4079
+ enum: ["cli", "mcp"]
4080
+ }
4081
+ ],
4082
+ binary: false,
4083
+ readOnly: true,
4084
+ destructive: false
4085
+ },
4086
+ {
4087
+ command: "skill list",
4088
+ tool: "skills_list",
4089
+ title: "List skills",
4090
+ description: "List the skills an organization can load, by kind then name: Asteroid's `public` skills, the `portal` skills (general knowledge of a third-party portal: sign-in, page quirks, field mappings) with whether the organization has added the portal, and the `organization`'s own. Filter with `kind`, or rank by a `query` such as `caloptima eligibility`. Each skill comes with its file paths and digests; `skill_get` returns the content. Add a portal with `portal_skills_add`.",
4091
+ method: "GET",
4092
+ path: "/astro/v2/skills",
4093
+ args: [],
4094
+ query: [
4095
+ {
4096
+ name: "kind",
4097
+ flag: "kind",
4098
+ type: "string",
4099
+ required: false,
4100
+ description: "Only skills of this kind.",
4101
+ enum: ["public", "portal", "organization"]
4102
+ },
4103
+ {
4104
+ name: "query",
4105
+ flag: "query",
4106
+ type: "string",
4107
+ required: false,
4108
+ description: "A skill name, portal name or task, at least three characters. Best matches first."
4109
+ },
4110
+ {
4111
+ name: "surface",
4112
+ flag: "surface",
4113
+ type: "string",
4114
+ required: false,
4115
+ description: "The surface the skills are rendered for. Defaults to `cli`.",
4116
+ enum: ["cli", "mcp"]
4117
+ }
4118
+ ],
4119
+ binary: false,
4120
+ readOnly: true,
4121
+ destructive: false
4122
+ },
4042
4123
  {
4043
4124
  command: "tag create",
4044
4125
  tool: "workflow_tags_create",
@@ -6527,15 +6608,16 @@ function defineCommands(root, options, dotted2 = /* @__PURE__ */ new Map(), onIn
6527
6608
  (arg) => options.defaultArg?.(command, arg)
6528
6609
  );
6529
6610
  const defaulted = defaults[0] !== void 0;
6611
+ const optional = (arg, i) => i === 0 && defaulted || !arg.required;
6530
6612
  command.args.forEach((arg) => {
6531
6613
  leaf.argument(
6532
- defaulted ? `[${arg.flag}]` : `<${arg.flag}>`,
6614
+ defaulted || !arg.required ? `[${arg.flag}]` : `<${arg.flag}>`,
6533
6615
  arg.description ?? ""
6534
6616
  );
6535
6617
  });
6536
6618
  if (defaulted) {
6537
6619
  const names = command.args.map(
6538
- (arg, i) => i === 0 ? `[${arg.flag}]` : `<${arg.flag}>`
6620
+ (arg, i) => optional(arg, i) ? `[${arg.flag}]` : `<${arg.flag}>`
6539
6621
  );
6540
6622
  leaf.usage(`[options] ${names.join(" ")}`);
6541
6623
  }
@@ -6552,8 +6634,9 @@ ${notes.join("\n")}`);
6552
6634
  if (positionals.length === command.args.length - 1 && defaults[0] !== void 0) {
6553
6635
  positionals = [defaults[0], ...positionals];
6554
6636
  }
6555
- if (positionals.length !== command.args.length) {
6556
- const expected = command.args.map((a, i) => i === 0 && defaulted ? `[${a.flag}]` : `<${a.flag}>`).join(" ");
6637
+ const required = command.args.filter((arg) => arg.required).length;
6638
+ if (positionals.length < required || positionals.length > command.args.length) {
6639
+ const expected = command.args.map((a, i) => optional(a, i) ? `[${a.flag}]` : `<${a.flag}>`).join(" ");
6557
6640
  throw new UsageError(
6558
6641
  `expected ${expected}, got ${cmd.args.length} argument(s)`
6559
6642
  );
@@ -6851,6 +6934,85 @@ function rejectUnknown(flags) {
6851
6934
  }
6852
6935
  }
6853
6936
 
6937
+ // src/sync/types.ts
6938
+ var MANIFEST = ".asteroid.json";
6939
+ function get(path, query = {}) {
6940
+ return { method: "GET", path, query: new URLSearchParams(query) };
6941
+ }
6942
+ function statusOf(error) {
6943
+ const status = error?.status;
6944
+ return typeof status === "number" ? status : void 0;
6945
+ }
6946
+
6947
+ // src/sync/execution-wait.ts
6948
+ var WAIT_TIMEOUT_SECONDS = 600;
6949
+ var WAIT_INTERVAL_SECONDS = 15;
6950
+ var MAX_WAIT_TIMEOUT_SECONDS = 3600;
6951
+ var MAX_WAIT_INTERVAL_SECONDS = 120;
6952
+ var TERMINAL = /* @__PURE__ */ new Set(["completed", "cancelled", "failed"]);
6953
+ function isTerminalExecutionStatus(status) {
6954
+ return TERMINAL.has(status);
6955
+ }
6956
+ var SYNC_TIMEOUT_SECONDS = 25;
6957
+ function pause(ms) {
6958
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
6959
+ }
6960
+ async function readStatus(sync, executionId2, timeoutMs) {
6961
+ if (timeoutMs <= 0)
6962
+ return await sync.remote.json(
6963
+ get(`/agents/v2/executions/${executionId2}`)
6964
+ );
6965
+ const timeoutSeconds = Math.min(
6966
+ SYNC_TIMEOUT_SECONDS,
6967
+ Math.max(1, Math.ceil(timeoutMs / 1e3))
6968
+ );
6969
+ const response = await sync.remote.json(
6970
+ get(`/agents/v2/executions/${executionId2}/sync`, {
6971
+ timeoutSeconds: String(timeoutSeconds)
6972
+ })
6973
+ );
6974
+ return {
6975
+ id: response.executionId,
6976
+ status: response.status,
6977
+ executionResult: response.executionResult
6978
+ };
6979
+ }
6980
+ async function waitForExecution(sync, options) {
6981
+ const timeoutMs = (options.timeoutSeconds ?? WAIT_TIMEOUT_SECONDS) * 1e3;
6982
+ const intervalMs = (options.intervalSeconds ?? WAIT_INTERVAL_SECONDS) * 1e3;
6983
+ const now = options.now ?? Date.now;
6984
+ const sleep = options.sleep ?? pause;
6985
+ const started = now();
6986
+ let polls = 0;
6987
+ for (; ; ) {
6988
+ const called = now();
6989
+ const execution = await readStatus(
6990
+ sync,
6991
+ options.executionId,
6992
+ timeoutMs - (called - started)
6993
+ );
6994
+ polls++;
6995
+ const elapsed = now() - started;
6996
+ const waitedSeconds = Math.round(elapsed / 1e3);
6997
+ if (isTerminalExecutionStatus(execution.status))
6998
+ return { execution, terminal: true, waitedSeconds, polls };
6999
+ const remaining = timeoutMs - elapsed;
7000
+ if (remaining <= 0)
7001
+ return { execution, terminal: false, waitedSeconds, polls };
7002
+ const early = intervalMs - (now() - called);
7003
+ if (early > 0) await sleep(Math.min(early, remaining));
7004
+ }
7005
+ }
7006
+ function formatExecutionWait(result) {
7007
+ const { execution } = result;
7008
+ if (!result.terminal) {
7009
+ return `Execution ${execution.id} still ${execution.status} after ${result.waitedSeconds}s. Wait again, or read what it has so far with \`asteroid execution pull ${execution.id}\`.`;
7010
+ }
7011
+ const line2 = `Execution ${execution.id} ${execution.status}.`;
7012
+ const outcome = execution.executionResult;
7013
+ return outcome ? `${line2} Result: ${outcome.outcome}: ${outcome.reasoning}` : line2;
7014
+ }
7015
+
6854
7016
  // src/sync/commands.ts
6855
7017
  function option(name, type, description, extra = {}) {
6856
7018
  return { name, flag: name, type, required: false, description, ...extra };
@@ -6862,6 +7024,12 @@ var local = {
6862
7024
  destructive: false,
6863
7025
  local: true
6864
7026
  };
7027
+ var executionId = {
7028
+ name: "executionId",
7029
+ flag: "execution-id",
7030
+ type: "string",
7031
+ required: true
7032
+ };
6865
7033
  var SYNC_COMMANDS = [
6866
7034
  {
6867
7035
  ...local,
@@ -6870,14 +7038,7 @@ var SYNC_COMMANDS = [
6870
7038
  title: "Pull an execution to disk",
6871
7039
  description: "Write an execution to `<workflow-name>/executions/<id>/`: summary.json, activities.md (the step-by-step log), and the files the run produced.",
6872
7040
  readOnly: true,
6873
- args: [
6874
- {
6875
- name: "executionId",
6876
- flag: "execution-id",
6877
- type: "string",
6878
- required: true
6879
- }
6880
- ],
7041
+ args: [executionId],
6881
7042
  query: [
6882
7043
  option(
6883
7044
  "files",
@@ -6903,6 +7064,54 @@ var SYNC_COMMANDS = [
6903
7064
  "Write here instead of `<workflow-name>/executions/<id>/` under the current directory."
6904
7065
  )
6905
7066
  ]
7067
+ },
7068
+ {
7069
+ ...local,
7070
+ command: "execution wait",
7071
+ tool: "execution_wait",
7072
+ title: "Wait for an execution to end",
7073
+ description: "Poll an execution until it is completed, failed or cancelled, then print its status and, when the run produced one, its result. When the timeout passes first, print the status it still has, so you can wait again.",
7074
+ readOnly: true,
7075
+ args: [executionId],
7076
+ query: [
7077
+ option(
7078
+ "timeout",
7079
+ "integer",
7080
+ `Give up after this many seconds, ${WAIT_TIMEOUT_SECONDS} unless set, ${MAX_WAIT_TIMEOUT_SECONDS} at most.`
7081
+ ),
7082
+ option(
7083
+ "interval",
7084
+ "integer",
7085
+ `Poll every this many seconds, ${WAIT_INTERVAL_SECONDS} unless set, ${MAX_WAIT_INTERVAL_SECONDS} at most.`
7086
+ )
7087
+ ]
7088
+ },
7089
+ {
7090
+ ...local,
7091
+ command: "insight pull",
7092
+ tool: "insight_pull",
7093
+ title: "Pull insights to disk",
7094
+ description: "Write insights to `insights/<name>.md`, one markdown file each with YAML frontmatter (id, type, status, name, next_steps). Name an insight to pull that one. A file another insight or an unpushed draft already holds keeps its name; the pulled one gets a -2, -3 suffix.",
7095
+ readOnly: true,
7096
+ args: [
7097
+ {
7098
+ name: "insightId",
7099
+ flag: "insight-id",
7100
+ type: "string",
7101
+ required: false,
7102
+ description: "One insight to pull. Without it, every insight, or one workflow's with --workflow-id."
7103
+ }
7104
+ ],
7105
+ query: [
7106
+ option("workflowId", "string", "Only this workflow's insights.", {
7107
+ flag: "workflow-id"
7108
+ }),
7109
+ option(
7110
+ "dir",
7111
+ "string",
7112
+ "Write here instead of `insights/` under the current directory."
7113
+ )
7114
+ ]
6906
7115
  }
6907
7116
  ];
6908
7117
 
@@ -7015,6 +7224,13 @@ function normalizeNodeName(name) {
7015
7224
  }
7016
7225
  return result;
7017
7226
  }
7227
+ function lossySlugOr(name, fallback) {
7228
+ try {
7229
+ return normalizeNodeName(name);
7230
+ } catch {
7231
+ return fallback;
7232
+ }
7233
+ }
7018
7234
  function nodeDirName(name) {
7019
7235
  if (isNameCharsetConforming(name)) {
7020
7236
  return name.replaceAll(" ", "_");
@@ -7028,8 +7244,8 @@ function agentDirName(name) {
7028
7244
  return "agent";
7029
7245
  }
7030
7246
  }
7031
- function executionDirName(executionId) {
7032
- return executionId.slice(0, 8);
7247
+ function executionDirName(executionId2) {
7248
+ return executionId2.slice(0, 8);
7033
7249
  }
7034
7250
  function workflowDirName(workflowId, version, parentId, parentVersion) {
7035
7251
  const short = workflowId.slice(0, 8);
@@ -7077,16 +7293,6 @@ function matchesGlob(path, pattern) {
7077
7293
  return new RegExp(`^${source}$`).test(path);
7078
7294
  }
7079
7295
 
7080
- // src/sync/types.ts
7081
- var MANIFEST = ".asteroid.json";
7082
- function get(path, query = {}) {
7083
- return { method: "GET", path, query: new URLSearchParams(query) };
7084
- }
7085
- function statusOf(error) {
7086
- const status = error?.status;
7087
- return typeof status === "number" ? status : void 0;
7088
- }
7089
-
7090
7296
  // src/sync/execution-pull.ts
7091
7297
  var DIRECTORIES = ["workspace", "downloads", "shared"];
7092
7298
  function relativeFilePath(file) {
@@ -7124,14 +7330,14 @@ async function writeActivities(sync, dir, execution) {
7124
7330
  return false;
7125
7331
  }
7126
7332
  }
7127
- async function listFiles(sync, executionId) {
7333
+ async function listFiles(sync, executionId2) {
7128
7334
  const listing = await sync.remote.json(
7129
- get(`/agents/v2/executions/${executionId}/files`)
7335
+ get(`/agents/v2/executions/${executionId2}/files`)
7130
7336
  );
7131
7337
  return DIRECTORIES.flatMap((name) => listing.directories[name] ?? []);
7132
7338
  }
7133
- async function download(sync, executionId, file) {
7134
- const path = `/agents/v2/executions/${executionId}/files/${file.id}/signed-url`;
7339
+ async function download(sync, executionId2, file) {
7340
+ const path = `/agents/v2/executions/${executionId2}/files/${file.id}/signed-url`;
7135
7341
  const { url } = await sync.remote.json(get(path));
7136
7342
  return sync.remote.download(url);
7137
7343
  }
@@ -7259,6 +7465,114 @@ ${result.skipped.map((s) => ` - ${s}`).join("\n")}` : null,
7259
7465
  ].filter(Boolean).join("\n");
7260
7466
  }
7261
7467
 
7468
+ // src/sync/insight-file.ts
7469
+ import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
7470
+ var FRONTMATTER = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)(?:\r?\n)?/;
7471
+ function serializeInsightFile(meta, body) {
7472
+ const front = {};
7473
+ if (meta.id) front.id = meta.id;
7474
+ front.type = meta.type;
7475
+ if (meta.status) front.status = meta.status;
7476
+ front.name = meta.name;
7477
+ if (meta.nextSteps) front.next_steps = meta.nextSteps;
7478
+ const yaml = stringifyYaml(front).trimEnd();
7479
+ return `---
7480
+ ${yaml}
7481
+ ---
7482
+
7483
+ ${body}`;
7484
+ }
7485
+ function insightFileId(text2) {
7486
+ const match = FRONTMATTER.exec(text2);
7487
+ if (!match) return void 0;
7488
+ try {
7489
+ const id = parseYaml(match[1])?.id;
7490
+ return typeof id === "string" || typeof id === "number" ? String(id) : void 0;
7491
+ } catch {
7492
+ return void 0;
7493
+ }
7494
+ }
7495
+
7496
+ // src/sync/insight-pull.ts
7497
+ var INSIGHTS_DIR = "insights";
7498
+ var PAGE_SIZE = 100;
7499
+ async function listInsights(sync, workflowId) {
7500
+ const all = [];
7501
+ for (let page = 1; ; page++) {
7502
+ const query = {
7503
+ page: String(page),
7504
+ pageSize: String(PAGE_SIZE)
7505
+ };
7506
+ if (workflowId) query.workflowId = workflowId;
7507
+ const { items, total } = await sync.remote.json(
7508
+ get("/astro/v2/insights", query)
7509
+ );
7510
+ all.push(...items);
7511
+ if (items.length === 0 || all.length >= total) return all;
7512
+ }
7513
+ }
7514
+ async function stemOwners(sync, dir) {
7515
+ const owners = /* @__PURE__ */ new Map();
7516
+ for (const name of await sync.files.list(dir)) {
7517
+ if (!name.endsWith(".md") || name.includes("/")) continue;
7518
+ const bytes = await sync.files.read(join(dir, name));
7519
+ const text2 = bytes ? Buffer.from(bytes).toString("utf8") : "";
7520
+ owners.set(name.slice(0, -".md".length).toLowerCase(), insightFileId(text2));
7521
+ }
7522
+ return owners;
7523
+ }
7524
+ function assignStems(owners, insights) {
7525
+ const used = /* @__PURE__ */ new Set();
7526
+ const heldByOther = (stem, id) => owners.has(stem) && owners.get(stem) !== id;
7527
+ return insights.map((insight) => {
7528
+ const base = lossySlugOr(insight.name, "insight");
7529
+ let stem = base;
7530
+ for (let n = 2; used.has(stem) || heldByOther(stem, insight.id); n++) {
7531
+ stem = `${base}-${n}`;
7532
+ }
7533
+ used.add(stem);
7534
+ return [insight, stem];
7535
+ });
7536
+ }
7537
+ async function pullInsights(sync, options = {}) {
7538
+ const dir = options.dir ?? INSIGHTS_DIR;
7539
+ const insights = options.insightId ? [
7540
+ await sync.remote.json(
7541
+ get(`/astro/v2/insights/${options.insightId}`)
7542
+ )
7543
+ ] : await listInsights(sync, options.workflowId);
7544
+ const pulled = [];
7545
+ for (const [insight, stem] of assignStems(
7546
+ await stemOwners(sync, dir),
7547
+ insights
7548
+ )) {
7549
+ const path = join(dir, `${stem}.md`);
7550
+ const text2 = serializeInsightFile(
7551
+ {
7552
+ id: insight.id,
7553
+ type: insight.type,
7554
+ status: insight.status,
7555
+ name: insight.name,
7556
+ nextSteps: insight.nextSteps ?? void 0
7557
+ },
7558
+ insight.content
7559
+ );
7560
+ await sync.files.write(path, Buffer.from(text2, "utf8"));
7561
+ const { id, name, type, status } = insight;
7562
+ pulled.push({ id, name, type, status, path });
7563
+ }
7564
+ return { dir, insights: pulled };
7565
+ }
7566
+ function formatInsightPull(result) {
7567
+ const count = result.insights.length;
7568
+ return [
7569
+ `Pulled ${count} insight${count === 1 ? "" : "s"} into ${result.dir}/.`,
7570
+ ...result.insights.map(
7571
+ (insight) => ` - ${insight.path}: ${insight.name} (${insight.type}, ${insight.status})`
7572
+ )
7573
+ ].join("\n");
7574
+ }
7575
+
7262
7576
  // src/sync/manifest.ts
7263
7577
  async function loadManifest(sync, dir) {
7264
7578
  const bytes = await sync.files.read(join(dir, MANIFEST));
@@ -7671,6 +7985,35 @@ function integer(parsed, name) {
7671
7985
  throw new UsageError(`--${name} must be a whole number`);
7672
7986
  return Number(raw);
7673
7987
  }
7988
+ function bounded(parsed, name, min, max) {
7989
+ const value2 = integer(parsed, name);
7990
+ if (value2 === void 0) return void 0;
7991
+ if (value2 < min || value2 > max)
7992
+ throw new UsageError(`--${name} must be between ${min} and ${max}`);
7993
+ return value2;
7994
+ }
7995
+ function executionWaitOptions(parsed) {
7996
+ return {
7997
+ executionId: parsed.positionals[0],
7998
+ timeoutSeconds: bounded(parsed, "timeout", 0, MAX_WAIT_TIMEOUT_SECONDS),
7999
+ intervalSeconds: bounded(parsed, "interval", 1, MAX_WAIT_INTERVAL_SECONDS)
8000
+ };
8001
+ }
8002
+ function given(value2, name) {
8003
+ if (value2 === void 0) return void 0;
8004
+ if (value2 === "") throw new UsageError(`${name} must not be empty`);
8005
+ return value2;
8006
+ }
8007
+ function insightPullOptions(parsed) {
8008
+ const options = {};
8009
+ const insightId = given(parsed.positionals[0], "<insight-id>");
8010
+ if (insightId) options.insightId = insightId;
8011
+ const workflowId = given(flag(parsed, "workflow-id"), "--workflow-id");
8012
+ if (workflowId) options.workflowId = workflowId;
8013
+ const dir = given(flag(parsed, "dir"), "--dir");
8014
+ if (dir) options.dir = dir;
8015
+ return options;
8016
+ }
7674
8017
  function executionOptions(parsed) {
7675
8018
  const summaryOnly = flag(parsed, "summary-only") === "true";
7676
8019
  const glob = flag(parsed, "files");
@@ -7732,6 +8075,20 @@ async function execute(command, parsed, sync, deps) {
7732
8075
  );
7733
8076
  return { result: result2, text: formatWorkflowPush(result2) };
7734
8077
  }
8078
+ if (command.command === "execution wait") {
8079
+ const result2 = await waitForExecution(sync, {
8080
+ ...executionWaitOptions(parsed),
8081
+ ...deps.executionWait
8082
+ });
8083
+ return { result: result2, text: formatExecutionWait(result2) };
8084
+ }
8085
+ if (command.command === "insight pull") {
8086
+ const result2 = await pullInsights(sync, {
8087
+ ...deps.insightPull,
8088
+ ...insightPullOptions(parsed)
8089
+ });
8090
+ return { result: result2, text: formatInsightPull(result2) };
8091
+ }
7735
8092
  if (command.command !== "execution pull")
7736
8093
  throw new UsageError(`unknown command ${command.command}`);
7737
8094
  const result = await pullExecution(sync, {
@@ -7754,15 +8111,20 @@ async function runSync(command, parsed, deps) {
7754
8111
  export {
7755
8112
  ALL_COMMANDS,
7756
8113
  COMMANDS,
8114
+ INSIGHTS_DIR,
7757
8115
  MANIFEST,
7758
8116
  SYNC_COMMANDS,
7759
8117
  UsageError,
7760
8118
  buildRequest,
7761
8119
  formatActivitiesMarkdown,
7762
8120
  formatExecutionPull,
8121
+ formatExecutionWait,
8122
+ formatInsightPull,
7763
8123
  formatWorkflowPull,
7764
8124
  formatWorkflowPush,
7765
8125
  headFilesRequest,
8126
+ insightFileId,
8127
+ isTerminalExecutionStatus,
7766
8128
  leadingWords,
7767
8129
  loadManifest,
7768
8130
  matchCommand,
@@ -7770,12 +8132,15 @@ export {
7770
8132
  parseInvocation,
7771
8133
  pullDir,
7772
8134
  pullExecution,
8135
+ pullInsights,
7773
8136
  pullWorkflow,
7774
8137
  pushWorkflow,
7775
8138
  readManifest,
7776
8139
  render,
7777
8140
  runSync,
7778
8141
  runsLocally,
8142
+ serializeInsightFile,
8143
+ waitForExecution,
7779
8144
  withSyncCommands,
7780
8145
  writeManifest
7781
8146
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asteroidai/cli",
3
- "version": "1.9.111",
3
+ "version": "1.9.122",
4
4
  "description": "The asteroid command-line interface for the Asteroid platform.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -23,7 +23,8 @@
23
23
  "cli-table3": "^0.6.5",
24
24
  "commander": "^15.0.0",
25
25
  "env-paths": "^4.0.0",
26
- "proper-lockfile": "^4.1.2"
26
+ "proper-lockfile": "^4.1.2",
27
+ "yaml": "^2.9.0"
27
28
  },
28
29
  "devDependencies": {
29
30
  "@types/node": "^22.20.1",
@@ -33,7 +34,6 @@
33
34
  "tsx": "^4.23.12",
34
35
  "typescript": "5.9.3",
35
36
  "vitest": "^4.1.11",
36
- "yaml": "^2.9.0",
37
37
  "@asteroid/workflow-files": "0.1.0"
38
38
  },
39
39
  "scripts": {