@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 +221 -54
- package/package.json +2 -2
- package/skills/ac:generate-workflow.md +15 -0
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
|
|
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, {
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
770
|
-
var authCommand = new
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
953
|
-
var snapshotCommand = new
|
|
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
|
|
1111
|
+
import { Command as Command12 } from "commander";
|
|
1001
1112
|
import { AgentComposeError as AgentComposeError3 } from "@agent-compose/sdk";
|
|
1002
|
-
var factoryCommand = new
|
|
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
|
|
1092
|
-
import
|
|
1093
|
-
var cancelCommand = new
|
|
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(`${
|
|
1262
|
+
console.log(`${pc3.yellow("⊘")} canceled ${runId} at ${new Date(res.canceledAt).toISOString()}`);
|
|
1098
1263
|
} else {
|
|
1099
|
-
console.log(`${
|
|
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
|
|
1271
|
+
import { Command as Command15 } from "commander";
|
|
1107
1272
|
import { readFileSync as readFileSync3 } from "node:fs";
|
|
1108
|
-
import
|
|
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
|
|
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
|
|
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(`${
|
|
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
|
|
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 ? "" :
|
|
1214
|
-
console.log(`${
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1449
|
+
import pc5 from "picocolors";
|
|
1285
1450
|
var PACKAGE_NAME = "@agent-compose/cli";
|
|
1286
|
-
var upgradeCommand = new
|
|
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(`${
|
|
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(`${
|
|
1475
|
+
console.log(`${pc5.green("✓")} You're on the latest version (${pc5.bold(current)}).`);
|
|
1311
1476
|
return;
|
|
1312
1477
|
}
|
|
1313
|
-
console.log(`${
|
|
1478
|
+
console.log(`${pc5.yellow("●")} agentc ${pc5.bold(latest)} is available (you have ${current}).`);
|
|
1314
1479
|
if (opts.check) {
|
|
1315
|
-
console.log(` Run ${
|
|
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 ${
|
|
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(`${
|
|
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(`${
|
|
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
|
|
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 = `${
|
|
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 ${
|
|
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/${
|
|
1604
|
+
const confirm = await promptYesNo(` Install now? [y/${pc6.bold("N")}] `);
|
|
1440
1605
|
if (!confirm)
|
|
1441
1606
|
return;
|
|
1442
|
-
process.stderr.write(` Running ${
|
|
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(`${
|
|
1613
|
+
process.stderr.write(`${pc6.red("✗")} Install failed. Run the command above manually.
|
|
1449
1614
|
`);
|
|
1450
1615
|
return;
|
|
1451
1616
|
}
|
|
1452
|
-
process.stderr.write(`${
|
|
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.
|
|
1668
|
+
await program.parseAsync();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-compose/cli",
|
|
3
|
-
"version": "0.3.
|
|
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.
|
|
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
|
|