@agent-compose/cli 0.3.4 → 0.3.5

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/index.js CHANGED
@@ -189,7 +189,7 @@ var registerCommand = new Command("register").description("Register a workflow w
189
189
  console.error(`Error: workflow not found at ${workflowPath}`);
190
190
  process.exit(1);
191
191
  }
192
- const { source, manifest, description, networkPolicy, placeholders, snapshots, inputSchema, outputSchema, workflowPlan, connectors, connectorOperation, invokePolicy } = await bundleWorkflow(workflowPath);
192
+ const { source, manifest, description, networkPolicy, placeholders, snapshots, resources, inputSchema, outputSchema, workflowPlan, connectors, connectorOperation, invokePolicy } = await bundleWorkflow(workflowPath);
193
193
  if (connectors)
194
194
  console.log(`[register] Connectors required: ${Object.keys(connectors).join(", ")} — tokens are injected at the network layer at dispatch`);
195
195
  if (networkPolicy)
@@ -198,6 +198,8 @@ var registerCommand = new Command("register").description("Register a workflow w
198
198
  console.log(`[register] Boots from: ${formatBootFrom(snapshots.bootFrom)}`);
199
199
  if (snapshots?.saveLatest)
200
200
  console.log(`[register] Captures snapshot per step${snapshots.retainSteps ? " (retain: every step)" : ""}`);
201
+ if (resources?.size)
202
+ console.log(`[register] Sandbox size: ${resources.size}`);
201
203
  const client = makeClient(opts);
202
204
  console.log(`[register] Registering "${name}" → factory "${opts.factory}"…`);
203
205
  const result = await client.register({
@@ -212,6 +214,7 @@ var registerCommand = new Command("register").description("Register a workflow w
212
214
  ...networkPolicy ? { networkPolicy } : {},
213
215
  ...placeholders ? { placeholders } : {},
214
216
  ...snapshots !== undefined ? { snapshots } : {},
217
+ ...resources !== undefined ? { resources } : {},
215
218
  ...inputSchema !== undefined ? { inputSchema } : {},
216
219
  ...outputSchema !== undefined ? { outputSchema } : {},
217
220
  ...connectors !== undefined ? { connectors } : {},
@@ -522,7 +525,8 @@ var logsCommand = new Command2("logs").description("Stream logs for a run").argu
522
525
  });
523
526
 
524
527
  // src/commands/invoke.ts
525
- var invokeCommand = new Command3("invoke").description("Invoke a registered workflow template").argument("<template>", "Template name").option("-i, --input <json>", "Input as JSON string").option("-f, --follow", "Stream logs after invoking").option("--factory <slug>", `Factory the template lives in (default: ${defaultFactory})`, defaultFactory).option("--url <url>", "Server URL", parseUrlFlag, defaultUrl).option("--api-key <key>", "API key", defaultApiKey).action(async (template, opts) => {
528
+ var SANDBOX_SIZES = ["2vcpu-4gb", "4vcpu-8gb", "8vcpu-16gb", "32vcpu-64gb"];
529
+ var invokeCommand = new Command3("invoke").description("Invoke a registered workflow template").argument("<template>", "Template name").option("-i, --input <json>", "Input as JSON string").option("-f, --follow", "Stream logs after invoking").option("--factory <slug>", `Factory the template lives in (default: ${defaultFactory})`, defaultFactory).option("--size <size>", `Sandbox machine size for this run: ${SANDBOX_SIZES.join(" | ")} (overrides the template default)`).option("--url <url>", "Server URL", parseUrlFlag, defaultUrl).option("--api-key <key>", "API key", defaultApiKey).action(async (template, opts) => {
526
530
  let input;
527
531
  if (opts.input) {
528
532
  try {
@@ -532,8 +536,15 @@ var invokeCommand = new Command3("invoke").description("Invoke a registered work
532
536
  process.exit(1);
533
537
  }
534
538
  }
539
+ if (opts.size && !SANDBOX_SIZES.includes(opts.size)) {
540
+ console.error(`Error: --size must be one of: ${SANDBOX_SIZES.join(", ")}`);
541
+ process.exit(1);
542
+ }
535
543
  const client = makeClient(opts);
536
- const { id } = await client.invoke(template, input, { factorySlug: opts.factory });
544
+ const { id } = await client.invoke(template, input, {
545
+ factorySlug: opts.factory,
546
+ ...opts.size ? { size: opts.size } : {}
547
+ });
537
548
  console.log(`Run started: ${id}`);
538
549
  if (opts.follow)
539
550
  await streamLogs(id, opts);
@@ -553,8 +564,108 @@ var listCommand = new Command4("list").description("List registered workflow tem
553
564
  console.log(`${t.name} ${t.version}`);
554
565
  });
555
566
 
567
+ // src/commands/pause.ts
568
+ import { Command as Command5, InvalidArgumentError as InvalidArgumentError2 } from "commander";
569
+ import pc2 from "picocolors";
570
+ function readPauseEnv() {
571
+ const runId = process.env.RUN_ID;
572
+ const url = process.env.AGENT_COMPOSE_URL;
573
+ const apiKey = process.env.AGENT_COMPOSE_API_KEY;
574
+ const agentId = process.env.AGENT_COMPOSE_AGENT_ID || undefined;
575
+ const missing = [
576
+ !runId && "RUN_ID",
577
+ !url && "AGENT_COMPOSE_URL",
578
+ !apiKey && "AGENT_COMPOSE_API_KEY"
579
+ ].filter(Boolean);
580
+ if (missing.length) {
581
+ console.error(`Error: agentc pause only works inside a run sandbox — missing ${missing.join(", ")}.`);
582
+ process.exit(1);
583
+ }
584
+ return { runId, url, apiKey, agentId };
585
+ }
586
+ function parseMs(raw) {
587
+ const n = Number(raw);
588
+ if (!Number.isFinite(n) || n <= 0) {
589
+ throw new InvalidArgumentError2(`expected a positive number of milliseconds, got "${raw}".`);
590
+ }
591
+ return n;
592
+ }
593
+ var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
594
+ var pauseCommand = new Command5("pause").description("Ask the human a question and block until they answer (run-sandbox only)").requiredOption("--reason <text>", "The question / ask to show the human").option("--option <value>", "A typed choice (repeatable; omit for a free-form answer)", (v, acc) => [...acc, v], []).option("--ttl <ms>", "Auto-expire the pause after this many milliseconds", parseMs).option("--poll-interval <ms>", "How often to poll for an answer", parseMs, 3000).action(async (opts) => {
595
+ const env = readPauseEnv();
596
+ const base = env.url.replace(/\/+$/, "");
597
+ const createBody = { reason: opts.reason };
598
+ if (opts.option.length)
599
+ createBody.options = opts.option;
600
+ if (env.agentId)
601
+ createBody.agentId = env.agentId;
602
+ if (opts.ttl !== undefined)
603
+ createBody.ttlMs = opts.ttl;
604
+ let createRes;
605
+ try {
606
+ createRes = await fetch(`${base}/api/v1/runs/${env.runId}/pauses`, {
607
+ method: "POST",
608
+ headers: {
609
+ Authorization: `Bearer ${env.apiKey}`,
610
+ "Content-Type": "application/json"
611
+ },
612
+ body: JSON.stringify(createBody)
613
+ });
614
+ } catch (err) {
615
+ console.error(`Error: failed to create pause: ${err instanceof Error ? err.message : String(err)}`);
616
+ process.exit(1);
617
+ }
618
+ if (!createRes.ok) {
619
+ const text = await createRes.text().catch(() => "");
620
+ console.error(`Error: create pause returned ${createRes.status}${text ? `: ${text}` : ""}`);
621
+ process.exit(1);
622
+ }
623
+ const { id } = await createRes.json();
624
+ console.error(`${pc2.yellow("⏸")} paused for human input (pause ${id}) — waiting…`);
625
+ for (;; ) {
626
+ await sleep(opts.pollInterval);
627
+ let pause;
628
+ let terminalError;
629
+ try {
630
+ const res = await fetch(`${base}/api/v1/runs/${env.runId}/pauses/${id}`, {
631
+ headers: { Authorization: `Bearer ${env.apiKey}` }
632
+ });
633
+ if (res.ok) {
634
+ pause = await res.json();
635
+ } else {
636
+ const text = await res.text().catch(() => "");
637
+ const retryable = res.status >= 500 || res.status === 408 || res.status === 429;
638
+ if (retryable) {
639
+ console.error(`${pc2.dim(`poll ${id} returned ${res.status}${text ? `: ${text}` : ""} — retrying…`)}`);
640
+ } else {
641
+ terminalError = `poll ${id} returned ${res.status}${text ? `: ${text}` : ""} — giving up.`;
642
+ }
643
+ }
644
+ } catch (err) {
645
+ console.error(`${pc2.dim(`poll ${id} failed (${err instanceof Error ? err.message : String(err)}) — retrying…`)}`);
646
+ }
647
+ if (terminalError) {
648
+ console.error(`Error: ${terminalError}`);
649
+ process.exit(1);
650
+ }
651
+ if (!pause)
652
+ continue;
653
+ if (pause.status === "pending")
654
+ continue;
655
+ if (pause.status === "resolved") {
656
+ const payload = pause.resumePayload;
657
+ const decision = payload && typeof payload.decision === "string" ? payload.decision : undefined;
658
+ process.stdout.write(`${decision ?? JSON.stringify(payload)}
659
+ `);
660
+ process.exit(0);
661
+ }
662
+ console.error(`Error: pause ${id} ${pause.status} before it was answered.`);
663
+ process.exit(1);
664
+ }
665
+ });
666
+
556
667
  // src/commands/init.ts
557
- import { Command as Command5 } from "commander";
668
+ import { Command as Command6 } from "commander";
558
669
  import {
559
670
  existsSync as existsSync3,
560
671
  mkdirSync,
@@ -633,7 +744,7 @@ function loadSource(dir) {
633
744
  const files = readdirSync(dir).filter((f) => f.endsWith(".md"));
634
745
  return { dir, files };
635
746
  }
636
- var initCommand = new Command5("init").description("Install agentc Claude Code skills and ensure PATH is configured").option("--dev", "Also install dev-only skills from cli/skills-dev/ (clone install only)").action((opts) => {
747
+ var initCommand = new Command6("init").description("Install agentc Claude Code skills and ensure PATH is configured").option("--dev", "Also install dev-only skills from cli/skills-dev/ (clone install only)").action((opts) => {
637
748
  ensureBunInPath();
638
749
  const publicSrc = loadSource(locateSkillsDir("skills"));
639
750
  if (!publicSrc) {
@@ -686,7 +797,7 @@ ${wanted.length} skill(s) installed to ${targetRoot}${note}`);
686
797
  });
687
798
 
688
799
  // src/commands/keys.ts
689
- import { Command as Command6 } from "commander";
800
+ import { Command as Command7 } from "commander";
690
801
  import { AgentComposeClient as AgentComposeClient2 } from "@agent-compose/sdk";
691
802
  var DURATION_UNITS = {
692
803
  s: 1000,
@@ -712,7 +823,7 @@ function requireAdminKey(adminKey) {
712
823
  ` + "Mint your first admin key by signing into the dashboard.");
713
824
  process.exit(1);
714
825
  }
715
- var keysCommand = new Command6("keys").description("Manage API keys");
826
+ var keysCommand = new Command7("keys").description("Manage API keys");
716
827
  keysCommand.command("create <name>").description("Create a new API key (requires admin-scoped ac_… key)").option("--url <url>", "Server URL", parseUrlFlag, defaultUrl).option("--admin-key <key>", "Admin-scoped ac_… key (or AGENT_COMPOSE_ADMIN_KEY env var)", process.env.AGENT_COMPOSE_ADMIN_KEY ?? "").option("--scopes <list>", "Comma-separated scope list: read, invoke, admin, events:read, events:write. Default (empty) = full team access.").option("--expires-in <duration>", `Expire the key after this duration — e.g. "30d", "2h", "7d12h", "1w", "90m". Default: never.`).option("--factory <slug>", "Restrict the key to a single factory (slug). Default: team-wide.").action(async (name, opts) => {
717
828
  requireAdminKey(opts.adminKey);
718
829
  const scopes = opts.scopes ? opts.scopes.split(",").map((s) => s.trim()).filter(Boolean) : undefined;
@@ -766,8 +877,8 @@ keysCommand.command("list").description("List API keys for your team (requires a
766
877
  });
767
878
 
768
879
  // src/commands/auth.ts
769
- import { Command as Command7 } from "commander";
770
- var authCommand = new Command7("auth").description("Manage authentication");
880
+ import { Command as Command8 } from "commander";
881
+ var authCommand = new Command8("auth").description("Manage authentication");
771
882
  authCommand.command("login <key>").description("Save an API key for the active environment").action(async (key) => {
772
883
  if (projectDir && activeEnv) {
773
884
  if (!projectSettings.envs?.[activeEnv]) {
@@ -848,9 +959,9 @@ Run: agentc auth login YOUR_KEY`);
848
959
  });
849
960
 
850
961
  // src/commands/secrets.ts
851
- import { Command as Command8 } from "commander";
962
+ import { Command as Command9 } from "commander";
852
963
  var sharedOpts = (cmd) => cmd.option("--factory <slug>", `Factory the workflow lives in (default: ${defaultFactory})`, defaultFactory).option("--url <url>", "Server URL", parseUrlFlag, defaultUrl).option("--api-key <key>", "API key", defaultApiKey);
853
- var secretsCommand = new Command8("secrets").description("Manage workflow secrets stored in GCP Secret Manager");
964
+ var secretsCommand = new Command9("secrets").description("Manage workflow secrets stored in GCP Secret Manager");
854
965
  sharedOpts(secretsCommand.command("set <workflow> <key> <value>").description("Create or update a secret for a workflow")).action(async (workflow, key, value, opts) => {
855
966
  const client = makeClient(opts);
856
967
  await client.setSecret(workflow, key, value, { factorySlug: opts.factory });
@@ -875,7 +986,7 @@ sharedOpts(secretsCommand.command("delete <workflow> <key>").description("Delete
875
986
  });
876
987
 
877
988
  // src/commands/usage.ts
878
- import { Command as Command9 } from "commander";
989
+ import { Command as Command10 } from "commander";
879
990
  function parseMonth(s) {
880
991
  const m = /^(\d{4})-(\d{1,2})$/.exec(s);
881
992
  if (!m)
@@ -894,7 +1005,7 @@ function fmtSeconds(s) {
894
1005
  const h = Math.floor(s / 3600);
895
1006
  return `${h}h ${Math.floor(s % 3600 / 60)}m`;
896
1007
  }
897
- var usageCommand = new Command9("usage").description("Show billable usage — runs + sandbox seconds — for the current team").option("--url <url>", "Server URL", parseUrlFlag, defaultUrl).option("--api-key <key>", "API key", defaultApiKey).option("--month <yyyy-mm>", "Calendar month to report (default: current month, UTC)").option("--from <iso>", "Window start (ISO 8601) — overrides --month").option("--to <iso>", "Window end (ISO 8601) — overrides --month").action(async (opts) => {
1008
+ var usageCommand = new Command10("usage").description("Show billable usage — runs + sandbox seconds — for the current team").option("--url <url>", "Server URL", parseUrlFlag, defaultUrl).option("--api-key <key>", "API key", defaultApiKey).option("--month <yyyy-mm>", "Calendar month to report (default: current month, UTC)").option("--from <iso>", "Window start (ISO 8601) — overrides --month").option("--to <iso>", "Window end (ISO 8601) — overrides --month").action(async (opts) => {
898
1009
  let from, to;
899
1010
  if (opts.from || opts.to) {
900
1011
  if (!opts.from || !opts.to) {
@@ -949,8 +1060,8 @@ Usage ${from.toISOString().slice(0, 10)} → ${to.toISOString().slice(0, 10)}
949
1060
  });
950
1061
 
951
1062
  // src/commands/snapshot.ts
952
- import { Command as Command10 } from "commander";
953
- var snapshotCommand = new Command10("snapshot").description("Manage captured sandbox snapshots");
1063
+ import { Command as Command11 } from "commander";
1064
+ var snapshotCommand = new Command11("snapshot").description("Manage captured sandbox snapshots");
954
1065
  snapshotCommand.command("list").description("List captured sandbox snapshots in a factory").option("-w, --workflow <name>", "Filter to a single workflow").option("--limit <n>", "Max rows to show (default 50, max 500)", (v) => parseInt(v, 10)).option("--before <cursor>", "Pagination cursor from a previous page").option("--factory <slug>", "Factory slug", defaultFactory).option("--url <url>", "Server URL", parseUrlFlag, defaultUrl).option("--api-key <key>", "API key", defaultApiKey).action(async (opts) => {
955
1066
  const page = await makeClient(opts).listSnapshotsPage({
956
1067
  factorySlug: opts.factory,
@@ -997,9 +1108,9 @@ snapshotCommand.command("show").description("List every snapshot held for a run
997
1108
  });
998
1109
 
999
1110
  // src/commands/factory.ts
1000
- import { Command as Command11 } from "commander";
1111
+ import { Command as Command12 } from "commander";
1001
1112
  import { AgentComposeError as AgentComposeError3 } from "@agent-compose/sdk";
1002
- var factoryCommand = new Command11("factory").description("Manage factories (projects within your team)");
1113
+ var factoryCommand = new Command12("factory").description("Manage factories (projects within your team)");
1003
1114
  var SLUG_PATTERN = /^[a-z][a-z0-9-]*$/;
1004
1115
  var SLUG_MAX_LEN = 64;
1005
1116
  function validateSlugOrExit(slug) {
@@ -1087,25 +1198,79 @@ factoryCommand.command("show").description("Show the active factory + how it's r
1087
1198
  console.log(`Active factory: ${defaultFactory} (default fallback)`);
1088
1199
  });
1089
1200
 
1201
+ // src/commands/members.ts
1202
+ import { Command as Command13 } from "commander";
1203
+ var membersCommand = new Command13("members").description("List teammates and flag them for attention (@-mentions)");
1204
+ membersCommand.command("list").description("List the human members of your team (name, email, role, user id)").option("--url <url>", "Server URL", parseUrlFlag, defaultUrl).option("--api-key <key>", "API key", defaultApiKey).action(async (opts) => {
1205
+ const client = makeClient(opts);
1206
+ const members = await client.listMembers().catch(reportSdkError);
1207
+ if (members.length === 0) {
1208
+ console.log("No members found.");
1209
+ return;
1210
+ }
1211
+ const nameW = Math.max(4, ...members.map((m) => (m.name || "").length));
1212
+ const emailW = Math.max(5, ...members.map((m) => m.email.length));
1213
+ const roleW = Math.max(4, ...members.map((m) => m.role.length));
1214
+ console.log(`${"NAME".padEnd(nameW)} ${"EMAIL".padEnd(emailW)} ${"ROLE".padEnd(roleW)} USER ID`);
1215
+ for (const m of members) {
1216
+ console.log(`${(m.name || "—").padEnd(nameW)} ${m.email.padEnd(emailW)} ${m.role.padEnd(roleW)} ${m.userId}`);
1217
+ }
1218
+ });
1219
+ var collect = (val, acc) => [...acc ?? [], val];
1220
+ membersCommand.command("notify").description("Flag teammates for attention — a ping in their Workbench").requiredOption("-u, --user <id-or-email>", "Teammate to flag (repeatable)", collect).requiredOption("-m, --text <message>", "What to flag them about").option("-k, --context-kind <kind>", "doc | comment | plan | run", "plan").option("--context-path <path>", "Factory-relative file path or thread id").option("--context-url <url>", "Dashboard URL the Workbench card links to").option("--factory <slug>", `Factory the ping belongs to (default: ${defaultFactory})`, defaultFactory).option("--url <url>", "Server URL", parseUrlFlag, defaultUrl).option("--api-key <key>", "API key", defaultApiKey).action(async (opts) => {
1221
+ const kind = opts.contextKind;
1222
+ if (!["doc", "comment", "plan", "run"].includes(kind)) {
1223
+ console.error(`Error: --context-kind must be one of doc | comment | plan | run (got "${kind}")`);
1224
+ process.exit(1);
1225
+ }
1226
+ const client = makeClient(opts);
1227
+ const members = await client.listMembers().catch(reportSdkError);
1228
+ const byEmail = new Map(members.map((m) => [m.email.toLowerCase(), m.userId]));
1229
+ const ids = new Set(members.map((m) => m.userId));
1230
+ const resolved = [];
1231
+ const unknown = [];
1232
+ for (const u of opts.user) {
1233
+ if (ids.has(u))
1234
+ resolved.push(u);
1235
+ else if (byEmail.has(u.toLowerCase()))
1236
+ resolved.push(byEmail.get(u.toLowerCase()));
1237
+ else
1238
+ unknown.push(u);
1239
+ }
1240
+ if (unknown.length > 0) {
1241
+ console.error(`Error: not on this team: ${unknown.join(", ")}. Run \`agentc members list\`.`);
1242
+ process.exit(1);
1243
+ }
1244
+ const mentions = await client.createMentions({
1245
+ mentionedUserIds: [...new Set(resolved)],
1246
+ text: opts.text,
1247
+ contextKind: kind,
1248
+ ...opts.contextPath ? { contextPath: opts.contextPath } : {},
1249
+ ...opts.contextUrl ? { contextUrl: opts.contextUrl } : {},
1250
+ factorySlug: opts.factory
1251
+ }).catch(reportSdkError);
1252
+ console.log(`✓ Flagged ${mentions.length} teammate${mentions.length === 1 ? "" : "s"} in factory "${opts.factory}".`);
1253
+ });
1254
+
1090
1255
  // src/commands/cancel.ts
1091
- import { Command as Command12 } from "commander";
1092
- import pc2 from "picocolors";
1093
- var cancelCommand = new Command12("cancel").description("Cancel an in-progress run").argument("<run-id>", "Run ID").option("--url <url>", "Server URL", parseUrlFlag, defaultUrl).option("--api-key <key>", "API key", defaultApiKey).option("--wait", "Stream the run's events until it terminates").action(async (runId, opts) => {
1256
+ import { Command as Command14 } from "commander";
1257
+ import pc3 from "picocolors";
1258
+ var cancelCommand = new Command14("cancel").description("Cancel an in-progress run").argument("<run-id>", "Run ID").option("--url <url>", "Server URL", parseUrlFlag, defaultUrl).option("--api-key <key>", "API key", defaultApiKey).option("--wait", "Stream the run's events until it terminates").action(async (runId, opts) => {
1094
1259
  const client = makeClient(opts);
1095
1260
  const res = await client.cancelRun(runId).catch(reportSdkError);
1096
1261
  if (res.status === "canceled") {
1097
- console.log(`${pc2.yellow("⊘")} canceled ${runId} at ${new Date(res.canceledAt).toISOString()}`);
1262
+ console.log(`${pc3.yellow("⊘")} canceled ${runId} at ${new Date(res.canceledAt).toISOString()}`);
1098
1263
  } else {
1099
- console.log(`${pc2.dim("⊘")} run ${runId} already ${res.status} at ${new Date(res.canceledAt).toISOString()}`);
1264
+ console.log(`${pc3.dim("⊘")} run ${runId} already ${res.status} at ${new Date(res.canceledAt).toISOString()}`);
1100
1265
  }
1101
1266
  if (opts.wait)
1102
1267
  await streamLogs(runId, opts);
1103
1268
  });
1104
1269
 
1105
1270
  // src/commands/events.ts
1106
- import { Command as Command13 } from "commander";
1271
+ import { Command as Command15 } from "commander";
1107
1272
  import { readFileSync as readFileSync3 } from "node:fs";
1108
- import pc3 from "picocolors";
1273
+ import pc4 from "picocolors";
1109
1274
  function parseBody(raw) {
1110
1275
  if (raw === undefined || raw === "")
1111
1276
  return {};
@@ -1147,7 +1312,7 @@ function parseLimit(raw) {
1147
1312
  }
1148
1313
  function fmtBodyPreview(body) {
1149
1314
  if (body === null || body === undefined)
1150
- return pc3.dim("—");
1315
+ return pc4.dim("—");
1151
1316
  if (typeof body === "string")
1152
1317
  return body.length > 60 ? body.slice(0, 57) + "…" : body;
1153
1318
  try {
@@ -1157,7 +1322,7 @@ function fmtBodyPreview(body) {
1157
1322
  return String(body);
1158
1323
  }
1159
1324
  }
1160
- var sendCommand = new Command13("send").description("Send an event to an existing run (requires events:write or invoke scope)").argument("<run-id>", "Target run id (uuid)").argument("<name>", "Event name (e.g. quality.accepted, deploy.completed)").option("--url <url>", "Server URL", parseUrlFlag, defaultUrl).option("--api-key <key>", "API key", defaultApiKey).option("--body <json>", "Event body — inline JSON or @file (default: {})").option("--summary <s>", "One-line summary (≤2000 chars)").option("--confidence <0..1>", "Confidence score, [0, 1]", parseConfidence).option("-a, --attribute <key=value>", "Attribute (repeatable; value parsed as JSON if possible)", parseAttribute, {}).option("--idempotency-key <k>", "Replay-safe key: identical (run, name, key) returns the original event").option("--no-propagate", "Mark event as non-propagating").option("--timestamp <iso>", "Event timestamp (ISO 8601, defaults to server-now)").option("--json", "Output the raw event JSON instead of the one-line summary").action(async (runId, name, opts) => {
1325
+ var sendCommand = new Command15("send").description("Send an event to an existing run (requires events:write or invoke scope)").argument("<run-id>", "Target run id (uuid)").argument("<name>", "Event name (e.g. quality.accepted, deploy.completed)").option("--url <url>", "Server URL", parseUrlFlag, defaultUrl).option("--api-key <key>", "API key", defaultApiKey).option("--body <json>", "Event body — inline JSON or @file (default: {})").option("--summary <s>", "One-line summary (≤2000 chars)").option("--confidence <0..1>", "Confidence score, [0, 1]", parseConfidence).option("-a, --attribute <key=value>", "Attribute (repeatable; value parsed as JSON if possible)", parseAttribute, {}).option("--idempotency-key <k>", "Replay-safe key: identical (run, name, key) returns the original event").option("--no-propagate", "Mark event as non-propagating").option("--timestamp <iso>", "Event timestamp (ISO 8601, defaults to server-now)").option("--json", "Output the raw event JSON instead of the one-line summary").action(async (runId, name, opts) => {
1161
1326
  let body;
1162
1327
  try {
1163
1328
  body = parseBody(opts.body);
@@ -1180,9 +1345,9 @@ var sendCommand = new Command13("send").description("Send an event to an existin
1180
1345
  console.log(JSON.stringify(event, null, 2));
1181
1346
  return;
1182
1347
  }
1183
- console.log(`${pc3.green("✓")} event ${pc3.bold(event.id.slice(0, 8))} ${event.name} ${pc3.dim(`run ${runId.slice(0, 8)}`)}`);
1348
+ console.log(`${pc4.green("✓")} event ${pc4.bold(event.id.slice(0, 8))} ${event.name} ${pc4.dim(`run ${runId.slice(0, 8)}`)}`);
1184
1349
  });
1185
- var listCommand2 = new Command13("list").description("List events for a run, or factory-wide with --factory <slug>").argument("[run-id]", "Run id (uuid). If omitted, requires --factory.").option("--url <url>", "Server URL", parseUrlFlag, defaultUrl).option("--api-key <key>", "API key", defaultApiKey).option("--factory <slug>", "List events across a factory instead of a single run").option("--limit <n>", "Max rows (factory mode only; server clamps)", parseLimit).option("--name <pattern>", "Filter by event name (factory mode only)").option("--json", "Output raw JSON instead of the columnar view").action(async (runId, opts) => {
1350
+ var listCommand2 = new Command15("list").description("List events for a run, or factory-wide with --factory <slug>").argument("[run-id]", "Run id (uuid). If omitted, requires --factory.").option("--url <url>", "Server URL", parseUrlFlag, defaultUrl).option("--api-key <key>", "API key", defaultApiKey).option("--factory <slug>", "List events across a factory instead of a single run").option("--limit <n>", "Max rows (factory mode only; server clamps)", parseLimit).option("--name <pattern>", "Filter by event name (factory mode only)").option("--json", "Output raw JSON instead of the columnar view").action(async (runId, opts) => {
1186
1351
  if (!runId && !opts.factory) {
1187
1352
  console.error("Error: provide either <run-id> or --factory <slug>");
1188
1353
  process.exit(1);
@@ -1210,17 +1375,17 @@ var listCommand2 = new Command13("list").description("List events for a run, or
1210
1375
  for (const ev of sorted) {
1211
1376
  const ts = ev.timestamp.replace(/\.\d+Z$/, "Z");
1212
1377
  const detail = ev.summary ?? fmtBodyPreview(ev.body);
1213
- const runHint = runId ? "" : pc3.dim(` run ${(ev.runId ?? "—").slice(0, 8)}`);
1214
- console.log(`${pc3.dim(ts)} ${pc3.bold(ev.name.padEnd(28))} ${detail}${runHint}`);
1378
+ const runHint = runId ? "" : pc4.dim(` run ${(ev.runId ?? "—").slice(0, 8)}`);
1379
+ console.log(`${pc4.dim(ts)} ${pc4.bold(ev.name.padEnd(28))} ${detail}${runHint}`);
1215
1380
  }
1216
1381
  });
1217
- var eventsCommand = new Command13("events").description("Send or list run events (Events API)").addCommand(sendCommand).addCommand(listCommand2);
1382
+ var eventsCommand = new Command15("events").description("Send or list run events (Events API)").addCommand(sendCommand).addCommand(listCommand2);
1218
1383
 
1219
1384
  // src/commands/files.ts
1220
1385
  import { readFileSync as readFileSync4, writeFileSync } from "node:fs";
1221
- import { Command as Command14 } from "commander";
1386
+ import { Command as Command16 } from "commander";
1222
1387
  var sharedOpts2 = (cmd) => cmd.option("--factory <slug>", `Factory whose drive to target (default: ${defaultFactory})`, defaultFactory).option("--url <url>", "Server URL", parseUrlFlag, defaultUrl).option("--api-key <key>", "API key", defaultApiKey);
1223
- var filesCommand = new Command14("files").description("Read/write documents on a factory's drive");
1388
+ var filesCommand = new Command16("files").description("Read/write documents on a factory's drive");
1224
1389
  sharedOpts2(filesCommand.command("put <local-file> <drive-path>").description("Upload a local file to the factory drive (creates or overwrites)").option("--content-type <mime>", "Content type", "text/plain; charset=utf-8")).action(async (localFile, drivePath, opts) => {
1225
1390
  const client = makeClient(opts);
1226
1391
  const result = await client.putFactoryFile(drivePath, readFileSync4(localFile), {
@@ -1244,9 +1409,9 @@ sharedOpts2(filesCommand.command("get <drive-path>").description("Print a drive
1244
1409
  });
1245
1410
 
1246
1411
  // src/commands/schedule.ts
1247
- import { Command as Command15 } from "commander";
1412
+ import { Command as Command17 } from "commander";
1248
1413
  var sharedOpts3 = (cmd) => cmd.option("--factory <slug>", `Factory the schedule lives in (default: ${defaultFactory})`, defaultFactory).option("--url <url>", "Server URL", parseUrlFlag, defaultUrl).option("--api-key <key>", "API key", defaultApiKey);
1249
- var scheduleCommand = new Command15("schedule").description("Manage cron schedules attached to registered workflows");
1414
+ var scheduleCommand = new Command17("schedule").description("Manage cron schedules attached to registered workflows");
1250
1415
  sharedOpts3(scheduleCommand.command("create <name>").description("Create a new schedule for a workflow").requiredOption("--workflow <name>", "The registered workflow this schedule should fire").requiredOption("--cron <expr>", "Cron expression (UTC) — e.g. '0 9 * * *'")).action(async (name, opts) => {
1251
1416
  const client = makeClient(opts);
1252
1417
  const created = await client.createSchedule({
@@ -1277,13 +1442,13 @@ sharedOpts3(scheduleCommand.command("delete <id>").description("Delete a schedul
1277
1442
  });
1278
1443
 
1279
1444
  // src/commands/upgrade.ts
1280
- import { Command as Command16 } from "commander";
1445
+ import { Command as Command18 } from "commander";
1281
1446
  import { spawnSync } from "node:child_process";
1282
1447
  import { readFileSync as readFileSync5 } from "node:fs";
1283
1448
  import { fileURLToPath as fileURLToPath2 } from "node:url";
1284
- import pc4 from "picocolors";
1449
+ import pc5 from "picocolors";
1285
1450
  var PACKAGE_NAME = "@agent-compose/cli";
1286
- var upgradeCommand = new Command16("upgrade").description("Update the agentc CLI to the latest published version").option("--check", "Check for updates without installing", false).action(async (opts) => {
1451
+ var upgradeCommand = new Command18("upgrade").description("Update the agentc CLI to the latest published version").option("--check", "Check for updates without installing", false).action(async (opts) => {
1287
1452
  const pkg = JSON.parse(readFileSync5(fileURLToPath2(new URL("../../package.json", import.meta.url)), "utf8"));
1288
1453
  const current = pkg.version;
1289
1454
  const controller = new AbortController;
@@ -1303,25 +1468,25 @@ var upgradeCommand = new Command16("upgrade").description("Update the agentc CLI
1303
1468
  clearTimeout(timeout);
1304
1469
  }
1305
1470
  if (!latest) {
1306
- console.error(`${pc4.red("✗")} Couldn't reach the npm registry. Check your connection and try again.`);
1471
+ console.error(`${pc5.red("✗")} Couldn't reach the npm registry. Check your connection and try again.`);
1307
1472
  process.exit(1);
1308
1473
  }
1309
1474
  if (latest === current) {
1310
- console.log(`${pc4.green("✓")} You're on the latest version (${pc4.bold(current)}).`);
1475
+ console.log(`${pc5.green("✓")} You're on the latest version (${pc5.bold(current)}).`);
1311
1476
  return;
1312
1477
  }
1313
- console.log(`${pc4.yellow("●")} agentc ${pc4.bold(latest)} is available (you have ${current}).`);
1478
+ console.log(`${pc5.yellow("●")} agentc ${pc5.bold(latest)} is available (you have ${current}).`);
1314
1479
  if (opts.check) {
1315
- console.log(` Run ${pc4.cyan("agentc upgrade")} to install, or ${pc4.cyan("npm install -g " + PACKAGE_NAME + "@latest")} manually.`);
1480
+ console.log(` Run ${pc5.cyan("agentc upgrade")} to install, or ${pc5.cyan("npm install -g " + PACKAGE_NAME + "@latest")} manually.`);
1316
1481
  return;
1317
1482
  }
1318
- console.log(` Running ${pc4.cyan("npm install -g " + PACKAGE_NAME + "@latest")} …`);
1483
+ console.log(` Running ${pc5.cyan("npm install -g " + PACKAGE_NAME + "@latest")} …`);
1319
1484
  const result = spawnSync("npm", ["install", "-g", `${PACKAGE_NAME}@latest`], { stdio: "inherit" });
1320
1485
  if (result.status !== 0) {
1321
- console.error(`${pc4.red("✗")} Install failed. Run the command above manually, or check for permission issues (npm prefix / sudo).`);
1486
+ console.error(`${pc5.red("✗")} Install failed. Run the command above manually, or check for permission issues (npm prefix / sudo).`);
1322
1487
  process.exit(1);
1323
1488
  }
1324
- console.log(`${pc4.green("✓")} Installed ${pc4.bold(`agentc ${latest}`)}.`);
1489
+ console.log(`${pc5.green("✓")} Installed ${pc5.bold(`agentc ${latest}`)}.`);
1325
1490
  });
1326
1491
 
1327
1492
  // src/update-check.ts
@@ -1330,7 +1495,7 @@ import { mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync a
1330
1495
  import { homedir as homedir3 } from "node:os";
1331
1496
  import { join as join3 } from "node:path";
1332
1497
  import { createInterface } from "node:readline/promises";
1333
- import pc5 from "picocolors";
1498
+ import pc6 from "picocolors";
1334
1499
  var PACKAGE_NAME2 = "@agent-compose/cli";
1335
1500
  var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
1336
1501
  var FETCH_TIMEOUT_MS = 2000;
@@ -1428,28 +1593,28 @@ async function checkForUpdate(currentVersion) {
1428
1593
  const latest = await resolveLatest();
1429
1594
  if (!latest || !isNewer(latest, currentVersion))
1430
1595
  return;
1431
- const headline = `${pc5.yellow("●")} agentc ${pc5.bold(latest)} is available (you have ${currentVersion}).`;
1596
+ const headline = `${pc6.yellow("●")} agentc ${pc6.bold(latest)} is available (you have ${currentVersion}).`;
1432
1597
  process.stderr.write(`${headline}
1433
1598
  `);
1434
1599
  if (!process.stdin.isTTY) {
1435
- process.stderr.write(` Run ${pc5.cyan("npm install -g " + PACKAGE_NAME2 + "@latest")} to update.
1600
+ process.stderr.write(` Run ${pc6.cyan("npm install -g " + PACKAGE_NAME2 + "@latest")} to update.
1436
1601
  `);
1437
1602
  return;
1438
1603
  }
1439
- const confirm = await promptYesNo(` Install now? [y/${pc5.bold("N")}] `);
1604
+ const confirm = await promptYesNo(` Install now? [y/${pc6.bold("N")}] `);
1440
1605
  if (!confirm)
1441
1606
  return;
1442
- process.stderr.write(` Running ${pc5.cyan("npm install -g " + PACKAGE_NAME2 + "@latest")} …
1607
+ process.stderr.write(` Running ${pc6.cyan("npm install -g " + PACKAGE_NAME2 + "@latest")} …
1443
1608
  `);
1444
1609
  const result = spawnSync2("npm", ["install", "-g", `${PACKAGE_NAME2}@latest`], {
1445
1610
  stdio: "inherit"
1446
1611
  });
1447
1612
  if (result.status !== 0) {
1448
- process.stderr.write(`${pc5.red("✗")} Install failed. Run the command above manually.
1613
+ process.stderr.write(`${pc6.red("✗")} Install failed. Run the command above manually.
1449
1614
  `);
1450
1615
  return;
1451
1616
  }
1452
- process.stderr.write(`${pc5.green("✓")} Installed ${pc5.bold(`agentc ${latest}`)}. ` + `Re-run your command to use the new version.
1617
+ process.stderr.write(`${pc6.green("✓")} Installed ${pc6.bold(`agentc ${latest}`)}. ` + `Re-run your command to use the new version.
1453
1618
  `);
1454
1619
  process.exit(0);
1455
1620
  }
@@ -1466,11 +1631,13 @@ program.addCommand(registerCommand);
1466
1631
  program.addCommand(invokeCommand);
1467
1632
  program.addCommand(listCommand);
1468
1633
  program.addCommand(cancelCommand);
1634
+ program.addCommand(pauseCommand);
1469
1635
  program.addCommand(scheduleCommand);
1470
1636
  program.addCommand(logsCommand);
1471
1637
  program.addCommand(eventsCommand);
1472
1638
  program.addCommand(filesCommand);
1473
1639
  program.addCommand(factoryCommand);
1640
+ program.addCommand(membersCommand);
1474
1641
  program.addCommand(snapshotCommand);
1475
1642
  program.addCommand(secretsCommand);
1476
1643
  program.addCommand(keysCommand);
@@ -1484,7 +1651,7 @@ Topics:
1484
1651
  Scheduling schedule
1485
1652
  Run inspection logs, events
1486
1653
  Factory drive files
1487
- Resources factory, snapshot, secrets
1654
+ Resources factory, members, snapshot, secrets
1488
1655
  Account keys, auth, usage
1489
1656
  Setup init, upgrade
1490
1657
 
@@ -1498,4 +1665,4 @@ Examples:
1498
1665
  Docs:
1499
1666
  https://github.com/Layr-Labs/agent-compose
1500
1667
  `);
1501
- program.parse();
1668
+ await program.parseAsync();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-compose/cli",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "description": "Command-line interface for agent-compose — register, invoke, and monitor workflows from your terminal.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -44,7 +44,7 @@
44
44
  "prepublishOnly": "bun run build"
45
45
  },
46
46
  "dependencies": {
47
- "@agent-compose/sdk": "^0.5.2",
47
+ "@agent-compose/sdk": "^0.5.8",
48
48
  "commander": "^12.0.0",
49
49
  "picocolors": "^1.1.1"
50
50
  },
@@ -142,6 +142,21 @@ Most workflows DO NOT need this. Default to no sandbox env.
142
142
  IO panels; without them, fields show only their type.
143
143
  - `.describe("...")` on the root schema too where helpful — surfaces
144
144
  at the panel header.
145
+ - **Persist outputs to the factory drive — `$AGENT_COMPOSE_RUN_DIR`.**
146
+ Anything the workflow produces that should survive the run (the
147
+ deliverable, agent artifacts, reports) MUST be written under the run's
148
+ factory directory, exposed in the sandbox as `$AGENT_COMPOSE_RUN_DIR`
149
+ (`/factory/files/<workflow>/<version>/<run-id>/`). Files written only
150
+ to `/workspace` are **ephemeral** — they vanish when the sandbox is
151
+ reclaimed, never appear in the factory's files, and aren't available
152
+ as prior-run context to future runs. Read the dir from the sandbox
153
+ shell (it's injected there), not at module top-level (the workflow VM
154
+ has no `process.env`): e.g. in a step,
155
+ `const runDir = (await ctx.sandbox.commands.run('printf %s "${AGENT_COMPOSE_RUN_DIR:-/workspace}"')).stdout.trim()`,
156
+ then write the deliverable to `${runDir}/...` and tell any agents to do
157
+ the same. Use `/workspace` only for throwaway scratch. Sibling dirs
158
+ under `$AGENT_COMPOSE_FACTORY_DIR/<workflow>/` hold prior runs' outputs —
159
+ read them for continuity instead of re-deriving.
145
160
 
146
161
  ## Templates
147
162