@agent-compose/cli 0.3.3 → 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 +272 -72
- package/package.json +2 -2
- package/skills/ac:files.md +84 -0
- package/skills/ac:generate-workflow.md +67 -51
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { createRequire } from "node:module";
|
|
|
3
3
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
4
4
|
|
|
5
5
|
// src/index.ts
|
|
6
|
-
import { readFileSync as
|
|
6
|
+
import { readFileSync as readFileSync7 } from "node:fs";
|
|
7
7
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
8
8
|
import { program } from "commander";
|
|
9
9
|
|
|
@@ -147,6 +147,7 @@ function fallbackDashboardUrl(env) {
|
|
|
147
147
|
return env === "local" ? "http://localhost:3000" : "https://platform.agentcompose.ai";
|
|
148
148
|
}
|
|
149
149
|
var defaultUrl = process.env.AGENT_COMPOSE_URL ?? _envUrl ?? _global.url ?? "https://api.agentcompose.ai";
|
|
150
|
+
var urlIsBuiltinFallback = !process.env.AGENT_COMPOSE_URL && !_envUrl && !_global.url;
|
|
150
151
|
var defaultDashboardUrl = process.env.AGENT_COMPOSE_DASHBOARD_URL ?? _envDashboardUrl ?? _global.dashboardUrl ?? fallbackDashboardUrl(_activeEnv);
|
|
151
152
|
var defaultApiKey = process.env.AGENT_COMPOSE_API_KEY ?? _envKey ?? _global.apiKey ?? "";
|
|
152
153
|
var defaultFactory = process.env.AGENT_COMPOSE_FACTORY ?? _envFactory ?? "default";
|
|
@@ -155,6 +156,10 @@ var projectSettings = _project;
|
|
|
155
156
|
var localSettings = _local;
|
|
156
157
|
var activeEnv = _activeEnv;
|
|
157
158
|
function makeClient({ url, apiKey }) {
|
|
159
|
+
if (urlIsBuiltinFallback && url === "https://api.agentcompose.ai") {
|
|
160
|
+
console.error(`[agentc] No server configured here — targeting https://api.agentcompose.ai (built-in default).
|
|
161
|
+
` + " If you meant another environment: pass --url, set AGENT_COMPOSE_URL, or run from a repo with .agentc settings.");
|
|
162
|
+
}
|
|
158
163
|
if (!apiKey) {
|
|
159
164
|
console.error(`API key required. Provide via:
|
|
160
165
|
` + ` • Project: .agentc/settings.local.json
|
|
@@ -184,13 +189,17 @@ var registerCommand = new Command("register").description("Register a workflow w
|
|
|
184
189
|
console.error(`Error: workflow not found at ${workflowPath}`);
|
|
185
190
|
process.exit(1);
|
|
186
191
|
}
|
|
187
|
-
const { source, manifest, description, networkPolicy, placeholders, snapshots,
|
|
192
|
+
const { source, manifest, description, networkPolicy, placeholders, snapshots, resources, inputSchema, outputSchema, workflowPlan, connectors, connectorOperation, invokePolicy } = await bundleWorkflow(workflowPath);
|
|
193
|
+
if (connectors)
|
|
194
|
+
console.log(`[register] Connectors required: ${Object.keys(connectors).join(", ")} — tokens are injected at the network layer at dispatch`);
|
|
188
195
|
if (networkPolicy)
|
|
189
196
|
console.log(`[register] Network policy detected — credentials will be brokered via Vercel firewall`);
|
|
190
197
|
if (snapshots?.bootFrom)
|
|
191
198
|
console.log(`[register] Boots from: ${formatBootFrom(snapshots.bootFrom)}`);
|
|
192
199
|
if (snapshots?.saveLatest)
|
|
193
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}`);
|
|
194
203
|
const client = makeClient(opts);
|
|
195
204
|
console.log(`[register] Registering "${name}" → factory "${opts.factory}"…`);
|
|
196
205
|
const result = await client.register({
|
|
@@ -205,18 +214,15 @@ var registerCommand = new Command("register").description("Register a workflow w
|
|
|
205
214
|
...networkPolicy ? { networkPolicy } : {},
|
|
206
215
|
...placeholders ? { placeholders } : {},
|
|
207
216
|
...snapshots !== undefined ? { snapshots } : {},
|
|
208
|
-
...
|
|
209
|
-
...postRunHooks !== undefined ? { postRunHooks } : {},
|
|
217
|
+
...resources !== undefined ? { resources } : {},
|
|
210
218
|
...inputSchema !== undefined ? { inputSchema } : {},
|
|
211
|
-
...outputSchema !== undefined ? { outputSchema } : {}
|
|
219
|
+
...outputSchema !== undefined ? { outputSchema } : {},
|
|
220
|
+
...connectors !== undefined ? { connectors } : {},
|
|
221
|
+
...connectorOperation !== undefined ? { connectorOperation } : {},
|
|
222
|
+
...invokePolicy !== undefined ? { invokePolicy } : {}
|
|
212
223
|
});
|
|
213
224
|
const scheduleNote = opts.schedule ? ` — schedule: ${opts.schedule}` : "";
|
|
214
225
|
console.log(`✓ Workflow: ${result.name}@${result.version} (${result.id})${scheduleNote}`);
|
|
215
|
-
if (result.warnings && result.warnings.length > 0) {
|
|
216
|
-
for (const w of result.warnings) {
|
|
217
|
-
console.warn(`! warning: ${w}`);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
226
|
if (!opts.build)
|
|
221
227
|
return;
|
|
222
228
|
console.log(`[build] Invoking "${name}" with snapshots:{ saveLatest: true } to capture a snapshot…`);
|
|
@@ -242,7 +248,7 @@ var registerCommand = new Command("register").description("Register a workflow w
|
|
|
242
248
|
process.exit(1);
|
|
243
249
|
});
|
|
244
250
|
function formatBootFrom(b) {
|
|
245
|
-
return `snapshot ${b.snapshotId}`;
|
|
251
|
+
return b === "reuse" ? "reuse (this workflow's own latest snapshot)" : `snapshot ${b.snapshotId}`;
|
|
246
252
|
}
|
|
247
253
|
|
|
248
254
|
// src/commands/invoke.ts
|
|
@@ -519,7 +525,8 @@ var logsCommand = new Command2("logs").description("Stream logs for a run").argu
|
|
|
519
525
|
});
|
|
520
526
|
|
|
521
527
|
// src/commands/invoke.ts
|
|
522
|
-
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) => {
|
|
523
530
|
let input;
|
|
524
531
|
if (opts.input) {
|
|
525
532
|
try {
|
|
@@ -529,8 +536,15 @@ var invokeCommand = new Command3("invoke").description("Invoke a registered work
|
|
|
529
536
|
process.exit(1);
|
|
530
537
|
}
|
|
531
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
|
+
}
|
|
532
543
|
const client = makeClient(opts);
|
|
533
|
-
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
|
+
});
|
|
534
548
|
console.log(`Run started: ${id}`);
|
|
535
549
|
if (opts.follow)
|
|
536
550
|
await streamLogs(id, opts);
|
|
@@ -550,8 +564,108 @@ var listCommand = new Command4("list").description("List registered workflow tem
|
|
|
550
564
|
console.log(`${t.name} ${t.version}`);
|
|
551
565
|
});
|
|
552
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
|
+
|
|
553
667
|
// src/commands/init.ts
|
|
554
|
-
import { Command as
|
|
668
|
+
import { Command as Command6 } from "commander";
|
|
555
669
|
import {
|
|
556
670
|
existsSync as existsSync3,
|
|
557
671
|
mkdirSync,
|
|
@@ -630,7 +744,7 @@ function loadSource(dir) {
|
|
|
630
744
|
const files = readdirSync(dir).filter((f) => f.endsWith(".md"));
|
|
631
745
|
return { dir, files };
|
|
632
746
|
}
|
|
633
|
-
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) => {
|
|
634
748
|
ensureBunInPath();
|
|
635
749
|
const publicSrc = loadSource(locateSkillsDir("skills"));
|
|
636
750
|
if (!publicSrc) {
|
|
@@ -683,7 +797,7 @@ ${wanted.length} skill(s) installed to ${targetRoot}${note}`);
|
|
|
683
797
|
});
|
|
684
798
|
|
|
685
799
|
// src/commands/keys.ts
|
|
686
|
-
import { Command as
|
|
800
|
+
import { Command as Command7 } from "commander";
|
|
687
801
|
import { AgentComposeClient as AgentComposeClient2 } from "@agent-compose/sdk";
|
|
688
802
|
var DURATION_UNITS = {
|
|
689
803
|
s: 1000,
|
|
@@ -709,7 +823,7 @@ function requireAdminKey(adminKey) {
|
|
|
709
823
|
` + "Mint your first admin key by signing into the dashboard.");
|
|
710
824
|
process.exit(1);
|
|
711
825
|
}
|
|
712
|
-
var keysCommand = new
|
|
826
|
+
var keysCommand = new Command7("keys").description("Manage API keys");
|
|
713
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) => {
|
|
714
828
|
requireAdminKey(opts.adminKey);
|
|
715
829
|
const scopes = opts.scopes ? opts.scopes.split(",").map((s) => s.trim()).filter(Boolean) : undefined;
|
|
@@ -763,8 +877,8 @@ keysCommand.command("list").description("List API keys for your team (requires a
|
|
|
763
877
|
});
|
|
764
878
|
|
|
765
879
|
// src/commands/auth.ts
|
|
766
|
-
import { Command as
|
|
767
|
-
var authCommand = new
|
|
880
|
+
import { Command as Command8 } from "commander";
|
|
881
|
+
var authCommand = new Command8("auth").description("Manage authentication");
|
|
768
882
|
authCommand.command("login <key>").description("Save an API key for the active environment").action(async (key) => {
|
|
769
883
|
if (projectDir && activeEnv) {
|
|
770
884
|
if (!projectSettings.envs?.[activeEnv]) {
|
|
@@ -845,9 +959,9 @@ Run: agentc auth login YOUR_KEY`);
|
|
|
845
959
|
});
|
|
846
960
|
|
|
847
961
|
// src/commands/secrets.ts
|
|
848
|
-
import { Command as
|
|
962
|
+
import { Command as Command9 } from "commander";
|
|
849
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);
|
|
850
|
-
var secretsCommand = new
|
|
964
|
+
var secretsCommand = new Command9("secrets").description("Manage workflow secrets stored in GCP Secret Manager");
|
|
851
965
|
sharedOpts(secretsCommand.command("set <workflow> <key> <value>").description("Create or update a secret for a workflow")).action(async (workflow, key, value, opts) => {
|
|
852
966
|
const client = makeClient(opts);
|
|
853
967
|
await client.setSecret(workflow, key, value, { factorySlug: opts.factory });
|
|
@@ -872,7 +986,7 @@ sharedOpts(secretsCommand.command("delete <workflow> <key>").description("Delete
|
|
|
872
986
|
});
|
|
873
987
|
|
|
874
988
|
// src/commands/usage.ts
|
|
875
|
-
import { Command as
|
|
989
|
+
import { Command as Command10 } from "commander";
|
|
876
990
|
function parseMonth(s) {
|
|
877
991
|
const m = /^(\d{4})-(\d{1,2})$/.exec(s);
|
|
878
992
|
if (!m)
|
|
@@ -891,7 +1005,7 @@ function fmtSeconds(s) {
|
|
|
891
1005
|
const h = Math.floor(s / 3600);
|
|
892
1006
|
return `${h}h ${Math.floor(s % 3600 / 60)}m`;
|
|
893
1007
|
}
|
|
894
|
-
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) => {
|
|
895
1009
|
let from, to;
|
|
896
1010
|
if (opts.from || opts.to) {
|
|
897
1011
|
if (!opts.from || !opts.to) {
|
|
@@ -946,8 +1060,8 @@ Usage ${from.toISOString().slice(0, 10)} → ${to.toISOString().slice(0, 10)}
|
|
|
946
1060
|
});
|
|
947
1061
|
|
|
948
1062
|
// src/commands/snapshot.ts
|
|
949
|
-
import { Command as
|
|
950
|
-
var snapshotCommand = new
|
|
1063
|
+
import { Command as Command11 } from "commander";
|
|
1064
|
+
var snapshotCommand = new Command11("snapshot").description("Manage captured sandbox snapshots");
|
|
951
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) => {
|
|
952
1066
|
const page = await makeClient(opts).listSnapshotsPage({
|
|
953
1067
|
factorySlug: opts.factory,
|
|
@@ -994,9 +1108,9 @@ snapshotCommand.command("show").description("List every snapshot held for a run
|
|
|
994
1108
|
});
|
|
995
1109
|
|
|
996
1110
|
// src/commands/factory.ts
|
|
997
|
-
import { Command as
|
|
1111
|
+
import { Command as Command12 } from "commander";
|
|
998
1112
|
import { AgentComposeError as AgentComposeError3 } from "@agent-compose/sdk";
|
|
999
|
-
var factoryCommand = new
|
|
1113
|
+
var factoryCommand = new Command12("factory").description("Manage factories (projects within your team)");
|
|
1000
1114
|
var SLUG_PATTERN = /^[a-z][a-z0-9-]*$/;
|
|
1001
1115
|
var SLUG_MAX_LEN = 64;
|
|
1002
1116
|
function validateSlugOrExit(slug) {
|
|
@@ -1084,25 +1198,79 @@ factoryCommand.command("show").description("Show the active factory + how it's r
|
|
|
1084
1198
|
console.log(`Active factory: ${defaultFactory} (default fallback)`);
|
|
1085
1199
|
});
|
|
1086
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
|
+
|
|
1087
1255
|
// src/commands/cancel.ts
|
|
1088
|
-
import { Command as
|
|
1089
|
-
import
|
|
1090
|
-
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) => {
|
|
1091
1259
|
const client = makeClient(opts);
|
|
1092
1260
|
const res = await client.cancelRun(runId).catch(reportSdkError);
|
|
1093
1261
|
if (res.status === "canceled") {
|
|
1094
|
-
console.log(`${
|
|
1262
|
+
console.log(`${pc3.yellow("⊘")} canceled ${runId} at ${new Date(res.canceledAt).toISOString()}`);
|
|
1095
1263
|
} else {
|
|
1096
|
-
console.log(`${
|
|
1264
|
+
console.log(`${pc3.dim("⊘")} run ${runId} already ${res.status} at ${new Date(res.canceledAt).toISOString()}`);
|
|
1097
1265
|
}
|
|
1098
1266
|
if (opts.wait)
|
|
1099
1267
|
await streamLogs(runId, opts);
|
|
1100
1268
|
});
|
|
1101
1269
|
|
|
1102
1270
|
// src/commands/events.ts
|
|
1103
|
-
import { Command as
|
|
1271
|
+
import { Command as Command15 } from "commander";
|
|
1104
1272
|
import { readFileSync as readFileSync3 } from "node:fs";
|
|
1105
|
-
import
|
|
1273
|
+
import pc4 from "picocolors";
|
|
1106
1274
|
function parseBody(raw) {
|
|
1107
1275
|
if (raw === undefined || raw === "")
|
|
1108
1276
|
return {};
|
|
@@ -1144,7 +1312,7 @@ function parseLimit(raw) {
|
|
|
1144
1312
|
}
|
|
1145
1313
|
function fmtBodyPreview(body) {
|
|
1146
1314
|
if (body === null || body === undefined)
|
|
1147
|
-
return
|
|
1315
|
+
return pc4.dim("—");
|
|
1148
1316
|
if (typeof body === "string")
|
|
1149
1317
|
return body.length > 60 ? body.slice(0, 57) + "…" : body;
|
|
1150
1318
|
try {
|
|
@@ -1154,7 +1322,7 @@ function fmtBodyPreview(body) {
|
|
|
1154
1322
|
return String(body);
|
|
1155
1323
|
}
|
|
1156
1324
|
}
|
|
1157
|
-
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) => {
|
|
1158
1326
|
let body;
|
|
1159
1327
|
try {
|
|
1160
1328
|
body = parseBody(opts.body);
|
|
@@ -1177,9 +1345,9 @@ var sendCommand = new Command13("send").description("Send an event to an existin
|
|
|
1177
1345
|
console.log(JSON.stringify(event, null, 2));
|
|
1178
1346
|
return;
|
|
1179
1347
|
}
|
|
1180
|
-
console.log(`${
|
|
1348
|
+
console.log(`${pc4.green("✓")} event ${pc4.bold(event.id.slice(0, 8))} ${event.name} ${pc4.dim(`run ${runId.slice(0, 8)}`)}`);
|
|
1181
1349
|
});
|
|
1182
|
-
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) => {
|
|
1183
1351
|
if (!runId && !opts.factory) {
|
|
1184
1352
|
console.error("Error: provide either <run-id> or --factory <slug>");
|
|
1185
1353
|
process.exit(1);
|
|
@@ -1207,17 +1375,44 @@ var listCommand2 = new Command13("list").description("List events for a run, or
|
|
|
1207
1375
|
for (const ev of sorted) {
|
|
1208
1376
|
const ts = ev.timestamp.replace(/\.\d+Z$/, "Z");
|
|
1209
1377
|
const detail = ev.summary ?? fmtBodyPreview(ev.body);
|
|
1210
|
-
const runHint = runId ? "" :
|
|
1211
|
-
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}`);
|
|
1380
|
+
}
|
|
1381
|
+
});
|
|
1382
|
+
var eventsCommand = new Command15("events").description("Send or list run events (Events API)").addCommand(sendCommand).addCommand(listCommand2);
|
|
1383
|
+
|
|
1384
|
+
// src/commands/files.ts
|
|
1385
|
+
import { readFileSync as readFileSync4, writeFileSync } from "node:fs";
|
|
1386
|
+
import { Command as Command16 } from "commander";
|
|
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);
|
|
1388
|
+
var filesCommand = new Command16("files").description("Read/write documents on a factory's drive");
|
|
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) => {
|
|
1390
|
+
const client = makeClient(opts);
|
|
1391
|
+
const result = await client.putFactoryFile(drivePath, readFileSync4(localFile), {
|
|
1392
|
+
factorySlug: opts.factory,
|
|
1393
|
+
contentType: opts.contentType
|
|
1394
|
+
});
|
|
1395
|
+
console.log(`✓ ${result.created ? "created" : "updated"} ${result.path} (${result.sizeBytes} bytes)`);
|
|
1396
|
+
});
|
|
1397
|
+
sharedOpts2(filesCommand.command("get <drive-path>").description("Print a drive file's content (or save with -o)").option("-o, --out <local-file>", "Write to a local file instead of stdout").option("--revision <n>", "Read a specific revision id")).action(async (drivePath, opts) => {
|
|
1398
|
+
const client = makeClient(opts);
|
|
1399
|
+
const content = await client.getFactoryFile(drivePath, {
|
|
1400
|
+
factorySlug: opts.factory,
|
|
1401
|
+
...opts.revision !== undefined ? { revision: Number(opts.revision) } : {}
|
|
1402
|
+
});
|
|
1403
|
+
if (opts.out) {
|
|
1404
|
+
writeFileSync(opts.out, content);
|
|
1405
|
+
console.log(`✓ ${drivePath} → ${opts.out}`);
|
|
1406
|
+
} else {
|
|
1407
|
+
process.stdout.write(content);
|
|
1212
1408
|
}
|
|
1213
1409
|
});
|
|
1214
|
-
var eventsCommand = new Command13("events").description("Send or list run events (Events API)").addCommand(sendCommand).addCommand(listCommand2);
|
|
1215
1410
|
|
|
1216
1411
|
// src/commands/schedule.ts
|
|
1217
|
-
import { Command as
|
|
1218
|
-
var
|
|
1219
|
-
var scheduleCommand = new
|
|
1220
|
-
|
|
1412
|
+
import { Command as Command17 } from "commander";
|
|
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);
|
|
1414
|
+
var scheduleCommand = new Command17("schedule").description("Manage cron schedules attached to registered workflows");
|
|
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) => {
|
|
1221
1416
|
const client = makeClient(opts);
|
|
1222
1417
|
const created = await client.createSchedule({
|
|
1223
1418
|
name,
|
|
@@ -1227,7 +1422,7 @@ sharedOpts2(scheduleCommand.command("create <name>").description("Create a new s
|
|
|
1227
1422
|
});
|
|
1228
1423
|
console.log(`✓ Schedule "${created.name}" → workflow "${created.workflow}" @ "${created.cron}" (${created.id})`);
|
|
1229
1424
|
});
|
|
1230
|
-
|
|
1425
|
+
sharedOpts3(scheduleCommand.command("list").description("List schedules in a factory")).action(async (opts) => {
|
|
1231
1426
|
const client = makeClient(opts);
|
|
1232
1427
|
const rows = await client.listSchedules(opts.factory);
|
|
1233
1428
|
if (rows.length === 0) {
|
|
@@ -1240,21 +1435,21 @@ sharedOpts2(scheduleCommand.command("list").description("List schedules in a fac
|
|
|
1240
1435
|
console.log(` ${s.name.padEnd(24)} ${s.workflowName.padEnd(20)} ${s.cron.padEnd(16)} next: ${next} ${s.id}`);
|
|
1241
1436
|
}
|
|
1242
1437
|
});
|
|
1243
|
-
|
|
1438
|
+
sharedOpts3(scheduleCommand.command("delete <id>").description("Delete a schedule by id")).action(async (id, opts) => {
|
|
1244
1439
|
const client = makeClient(opts);
|
|
1245
1440
|
await client.deleteSchedule(id, opts.factory);
|
|
1246
1441
|
console.log(`✓ Schedule ${id} deleted from factory "${opts.factory}"`);
|
|
1247
1442
|
});
|
|
1248
1443
|
|
|
1249
1444
|
// src/commands/upgrade.ts
|
|
1250
|
-
import { Command as
|
|
1445
|
+
import { Command as Command18 } from "commander";
|
|
1251
1446
|
import { spawnSync } from "node:child_process";
|
|
1252
|
-
import { readFileSync as
|
|
1447
|
+
import { readFileSync as readFileSync5 } from "node:fs";
|
|
1253
1448
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
1254
|
-
import
|
|
1449
|
+
import pc5 from "picocolors";
|
|
1255
1450
|
var PACKAGE_NAME = "@agent-compose/cli";
|
|
1256
|
-
var upgradeCommand = new
|
|
1257
|
-
const pkg = JSON.parse(
|
|
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) => {
|
|
1452
|
+
const pkg = JSON.parse(readFileSync5(fileURLToPath2(new URL("../../package.json", import.meta.url)), "utf8"));
|
|
1258
1453
|
const current = pkg.version;
|
|
1259
1454
|
const controller = new AbortController;
|
|
1260
1455
|
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
@@ -1273,34 +1468,34 @@ var upgradeCommand = new Command15("upgrade").description("Update the agentc CLI
|
|
|
1273
1468
|
clearTimeout(timeout);
|
|
1274
1469
|
}
|
|
1275
1470
|
if (!latest) {
|
|
1276
|
-
console.error(`${
|
|
1471
|
+
console.error(`${pc5.red("✗")} Couldn't reach the npm registry. Check your connection and try again.`);
|
|
1277
1472
|
process.exit(1);
|
|
1278
1473
|
}
|
|
1279
1474
|
if (latest === current) {
|
|
1280
|
-
console.log(`${
|
|
1475
|
+
console.log(`${pc5.green("✓")} You're on the latest version (${pc5.bold(current)}).`);
|
|
1281
1476
|
return;
|
|
1282
1477
|
}
|
|
1283
|
-
console.log(`${
|
|
1478
|
+
console.log(`${pc5.yellow("●")} agentc ${pc5.bold(latest)} is available (you have ${current}).`);
|
|
1284
1479
|
if (opts.check) {
|
|
1285
|
-
console.log(` Run ${
|
|
1480
|
+
console.log(` Run ${pc5.cyan("agentc upgrade")} to install, or ${pc5.cyan("npm install -g " + PACKAGE_NAME + "@latest")} manually.`);
|
|
1286
1481
|
return;
|
|
1287
1482
|
}
|
|
1288
|
-
console.log(` Running ${
|
|
1483
|
+
console.log(` Running ${pc5.cyan("npm install -g " + PACKAGE_NAME + "@latest")} …`);
|
|
1289
1484
|
const result = spawnSync("npm", ["install", "-g", `${PACKAGE_NAME}@latest`], { stdio: "inherit" });
|
|
1290
1485
|
if (result.status !== 0) {
|
|
1291
|
-
console.error(`${
|
|
1486
|
+
console.error(`${pc5.red("✗")} Install failed. Run the command above manually, or check for permission issues (npm prefix / sudo).`);
|
|
1292
1487
|
process.exit(1);
|
|
1293
1488
|
}
|
|
1294
|
-
console.log(`${
|
|
1489
|
+
console.log(`${pc5.green("✓")} Installed ${pc5.bold(`agentc ${latest}`)}.`);
|
|
1295
1490
|
});
|
|
1296
1491
|
|
|
1297
1492
|
// src/update-check.ts
|
|
1298
1493
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
1299
|
-
import { mkdirSync as mkdirSync2, readFileSync as
|
|
1494
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "node:fs";
|
|
1300
1495
|
import { homedir as homedir3 } from "node:os";
|
|
1301
1496
|
import { join as join3 } from "node:path";
|
|
1302
1497
|
import { createInterface } from "node:readline/promises";
|
|
1303
|
-
import
|
|
1498
|
+
import pc6 from "picocolors";
|
|
1304
1499
|
var PACKAGE_NAME2 = "@agent-compose/cli";
|
|
1305
1500
|
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
1306
1501
|
var FETCH_TIMEOUT_MS = 2000;
|
|
@@ -1336,7 +1531,7 @@ function isNewer(latest, current) {
|
|
|
1336
1531
|
}
|
|
1337
1532
|
function readCache() {
|
|
1338
1533
|
try {
|
|
1339
|
-
const raw =
|
|
1534
|
+
const raw = readFileSync6(cachePath(), "utf8");
|
|
1340
1535
|
const parsed = JSON.parse(raw);
|
|
1341
1536
|
if (typeof parsed.checkedAt !== "number")
|
|
1342
1537
|
return null;
|
|
@@ -1352,7 +1547,7 @@ function writeCache(entry) {
|
|
|
1352
1547
|
try {
|
|
1353
1548
|
const path = cachePath();
|
|
1354
1549
|
mkdirSync2(join3(path, ".."), { recursive: true });
|
|
1355
|
-
|
|
1550
|
+
writeFileSync2(path, JSON.stringify(entry, null, 2) + `
|
|
1356
1551
|
`, "utf8");
|
|
1357
1552
|
} catch {}
|
|
1358
1553
|
}
|
|
@@ -1398,34 +1593,34 @@ async function checkForUpdate(currentVersion) {
|
|
|
1398
1593
|
const latest = await resolveLatest();
|
|
1399
1594
|
if (!latest || !isNewer(latest, currentVersion))
|
|
1400
1595
|
return;
|
|
1401
|
-
const headline = `${
|
|
1596
|
+
const headline = `${pc6.yellow("●")} agentc ${pc6.bold(latest)} is available (you have ${currentVersion}).`;
|
|
1402
1597
|
process.stderr.write(`${headline}
|
|
1403
1598
|
`);
|
|
1404
1599
|
if (!process.stdin.isTTY) {
|
|
1405
|
-
process.stderr.write(` Run ${
|
|
1600
|
+
process.stderr.write(` Run ${pc6.cyan("npm install -g " + PACKAGE_NAME2 + "@latest")} to update.
|
|
1406
1601
|
`);
|
|
1407
1602
|
return;
|
|
1408
1603
|
}
|
|
1409
|
-
const confirm = await promptYesNo(` Install now? [y/${
|
|
1604
|
+
const confirm = await promptYesNo(` Install now? [y/${pc6.bold("N")}] `);
|
|
1410
1605
|
if (!confirm)
|
|
1411
1606
|
return;
|
|
1412
|
-
process.stderr.write(` Running ${
|
|
1607
|
+
process.stderr.write(` Running ${pc6.cyan("npm install -g " + PACKAGE_NAME2 + "@latest")} …
|
|
1413
1608
|
`);
|
|
1414
1609
|
const result = spawnSync2("npm", ["install", "-g", `${PACKAGE_NAME2}@latest`], {
|
|
1415
1610
|
stdio: "inherit"
|
|
1416
1611
|
});
|
|
1417
1612
|
if (result.status !== 0) {
|
|
1418
|
-
process.stderr.write(`${
|
|
1613
|
+
process.stderr.write(`${pc6.red("✗")} Install failed. Run the command above manually.
|
|
1419
1614
|
`);
|
|
1420
1615
|
return;
|
|
1421
1616
|
}
|
|
1422
|
-
process.stderr.write(`${
|
|
1617
|
+
process.stderr.write(`${pc6.green("✓")} Installed ${pc6.bold(`agentc ${latest}`)}. ` + `Re-run your command to use the new version.
|
|
1423
1618
|
`);
|
|
1424
1619
|
process.exit(0);
|
|
1425
1620
|
}
|
|
1426
1621
|
|
|
1427
1622
|
// src/index.ts
|
|
1428
|
-
var pkg = JSON.parse(
|
|
1623
|
+
var pkg = JSON.parse(readFileSync7(fileURLToPath3(new URL("../package.json", import.meta.url)), "utf8"));
|
|
1429
1624
|
program.name("agentc").description("Coordinate agent production lines.").version(pkg.version);
|
|
1430
1625
|
program.hook("preAction", async () => {
|
|
1431
1626
|
try {
|
|
@@ -1436,10 +1631,13 @@ program.addCommand(registerCommand);
|
|
|
1436
1631
|
program.addCommand(invokeCommand);
|
|
1437
1632
|
program.addCommand(listCommand);
|
|
1438
1633
|
program.addCommand(cancelCommand);
|
|
1634
|
+
program.addCommand(pauseCommand);
|
|
1439
1635
|
program.addCommand(scheduleCommand);
|
|
1440
1636
|
program.addCommand(logsCommand);
|
|
1441
1637
|
program.addCommand(eventsCommand);
|
|
1638
|
+
program.addCommand(filesCommand);
|
|
1442
1639
|
program.addCommand(factoryCommand);
|
|
1640
|
+
program.addCommand(membersCommand);
|
|
1443
1641
|
program.addCommand(snapshotCommand);
|
|
1444
1642
|
program.addCommand(secretsCommand);
|
|
1445
1643
|
program.addCommand(keysCommand);
|
|
@@ -1452,7 +1650,8 @@ Topics:
|
|
|
1452
1650
|
Workflows register, invoke, list, cancel
|
|
1453
1651
|
Scheduling schedule
|
|
1454
1652
|
Run inspection logs, events
|
|
1455
|
-
|
|
1653
|
+
Factory drive files
|
|
1654
|
+
Resources factory, members, snapshot, secrets
|
|
1456
1655
|
Account keys, auth, usage
|
|
1457
1656
|
Setup init, upgrade
|
|
1458
1657
|
|
|
@@ -1461,8 +1660,9 @@ Examples:
|
|
|
1461
1660
|
$ agentc invoke pipeline --follow
|
|
1462
1661
|
$ agentc schedule create nightly --workflow pipeline --cron '0 9 * * *'
|
|
1463
1662
|
$ agentc logs <run-id>
|
|
1663
|
+
$ agentc files put report.md runs/abc123/report.md
|
|
1464
1664
|
|
|
1465
1665
|
Docs:
|
|
1466
1666
|
https://github.com/Layr-Labs/agent-compose
|
|
1467
1667
|
`);
|
|
1468
|
-
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
|
},
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ac:files
|
|
3
|
+
description: Read and write documents on a factory's drive from the CLI.
|
|
4
|
+
allowed-tools: Bash(agentc *)
|
|
5
|
+
effort: low
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Files
|
|
9
|
+
|
|
10
|
+
Put files onto a factory's shared drive and read them back — all through
|
|
11
|
+
`agentc`, no raw HTTP. The drive is where agent-written documents, briefs,
|
|
12
|
+
and run artifacts live; the dashboard's **Files** tab renders them, and
|
|
13
|
+
writes are indexed (revisions, search, attribution). Two subcommands:
|
|
14
|
+
`agentc files put` and `agentc files get`.
|
|
15
|
+
|
|
16
|
+
The primary consumer is an **agent working inside a run sandbox**: publishing
|
|
17
|
+
work to the dashboard while the run is going. Inside a sandbox the env already
|
|
18
|
+
carries `AGENT_COMPOSE_URL` / `AGENT_COMPOSE_API_KEY` / `AGENT_COMPOSE_FACTORY`,
|
|
19
|
+
and the CLI attaches the run-callback token automatically so the write is
|
|
20
|
+
**attributed to the run** (it shows on the file and in the run's Artifacts
|
|
21
|
+
card). Works identically from a dev machine with saved credentials.
|
|
22
|
+
|
|
23
|
+
> Requires an API key with `invoke` (writes) / `read` (reads). See `/ac:setup`.
|
|
24
|
+
|
|
25
|
+
## When to use
|
|
26
|
+
|
|
27
|
+
- **Publish run output** — an agent saves a report/brief so a human sees it
|
|
28
|
+
in the dashboard mid-run.
|
|
29
|
+
- **Share between runs** — write a file other runs of the same factory read.
|
|
30
|
+
- **Pull a file locally** — fetch a drive document to inspect or edit.
|
|
31
|
+
|
|
32
|
+
## Upload a file
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# Create or overwrite a drive file from a local file.
|
|
36
|
+
# Convention: agents write under runs/<short-run-id>/...
|
|
37
|
+
agentc files put report.md runs/abc123/report.md
|
|
38
|
+
|
|
39
|
+
# Override the content type (default text/plain; charset=utf-8):
|
|
40
|
+
agentc files put index.html sites/demo/index.html --content-type "text/html; charset=utf-8"
|
|
41
|
+
|
|
42
|
+
# Target a non-default factory:
|
|
43
|
+
agentc files put notes.md briefs/notes.md --factory my-factory
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Download / read a file
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# Print a drive file to stdout:
|
|
50
|
+
agentc files get briefs/site-7b7954fc.md
|
|
51
|
+
|
|
52
|
+
# Save to a local file:
|
|
53
|
+
agentc files get runs/abc123/report.md -o ./report.md
|
|
54
|
+
|
|
55
|
+
# Read a specific historical revision:
|
|
56
|
+
agentc files get briefs/notes.md --revision 12
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Browsing the drive
|
|
60
|
+
|
|
61
|
+
There is no `agentc files list` — browse via the dashboard's **Files** tab,
|
|
62
|
+
or (inside a sandbox whose factory has a provisioned Archil disk)
|
|
63
|
+
`ls /factory` on the POSIX mount.
|
|
64
|
+
|
|
65
|
+
## Authoring from inside a workflow
|
|
66
|
+
|
|
67
|
+
The CLI is for agents + ad-hoc use. Workflow code uses the SDK, which attaches
|
|
68
|
+
the run token the same way:
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import { AgentComposeClient } from "@agent-compose/sdk";
|
|
72
|
+
const client = new AgentComposeClient({ apiKey, baseUrl });
|
|
73
|
+
await client.putFactoryFile("runs/abc123/report.md", contents, {
|
|
74
|
+
contentType: "text/markdown; charset=utf-8",
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## The drive vs. /factory mount
|
|
79
|
+
|
|
80
|
+
If the factory has a provisioned Archil disk, the drive is also POSIX-mounted
|
|
81
|
+
at `/factory` inside sandboxes — fine for reading and for scratch shared
|
|
82
|
+
between sibling runs. But a direct `/factory` write is **not indexed** (no
|
|
83
|
+
revision/attribution), so to make a file appear in the dashboard Files tab use
|
|
84
|
+
`agentc files put` (or the SDK), which goes through the indexed files API.
|
|
@@ -60,7 +60,7 @@ about "step-form vs run-form" — that's an internal call.
|
|
|
60
60
|
- step-form vs run-form
|
|
61
61
|
- sandbox environments / dependency installation
|
|
62
62
|
- snapshots: `saveLatest`, `retainSteps`, `bootFrom`
|
|
63
|
-
- `
|
|
63
|
+
- `processors`
|
|
64
64
|
|
|
65
65
|
Decide those internally based on what they described (see the
|
|
66
66
|
"Internal decisions" section below).
|
|
@@ -69,28 +69,44 @@ Decide those internally based on what they described (see the
|
|
|
69
69
|
|
|
70
70
|
After answering the questions above, decide the shape WITHOUT asking:
|
|
71
71
|
|
|
72
|
-
### Pick the workflow shape
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
72
|
+
### Pick the workflow shape — DEFAULT TO STEP-FORM
|
|
73
|
+
|
|
74
|
+
**Default to step-form, even for agent-driven workflows.** Each meaningful
|
|
75
|
+
phase — including an agent pass — becomes a `defineStep(...)`, and
|
|
76
|
+
`snapshots: { saveLatest: true }` makes the engine capture a snapshot AFTER
|
|
77
|
+
EACH step (latest-only). That durability is the whole point: if a later step's
|
|
78
|
+
sandbox dies, or a step needs retrying, the engine resumes from the last
|
|
79
|
+
completed step's snapshot instead of re-running the entire (often expensive,
|
|
80
|
+
multi-agent) pipeline from the top.
|
|
81
|
+
|
|
82
|
+
A multi-phase agent pipeline written **run-form is ONE engine step** — a single
|
|
83
|
+
snapshot at the very end — so ANY failure throws away all the work and restarts
|
|
84
|
+
at phase one. That's the brittleness step-form avoids.
|
|
85
|
+
|
|
86
|
+
- **Step-form** (`defineWorkflow({ id, input, output, snapshots: { saveLatest: true } }).step(s1).step(s2).build()`
|
|
87
|
+
with `defineStep(...)` objects) — the default. Each phase (fetch, an agent
|
|
88
|
+
pass, a transform, a deploy) is a step. **Steps DO receive a `sandbox`** and
|
|
89
|
+
run `agent({ sandbox: ctx.sandbox, … })`, `ctx.sandbox.commands.run(…)`, etc.
|
|
90
|
+
— they are NOT limited to pure data transforms. Each step's output threads
|
|
91
|
+
into the next step's input. The dashboard renders each as a typed,
|
|
92
|
+
separately-timed, separately-snapshotted phase. Examples: content → imagery →
|
|
93
|
+
polish → verify → deploy; fetch → score → rank → publish.
|
|
94
|
+
|
|
95
|
+
- **Run-form** (one `async (ctx, sandbox)` body) — LEGACY; being phased out in
|
|
96
|
+
favor of step-form everywhere. Only acceptable when the work is a SINGLE
|
|
97
|
+
indivisible agent loop with no meaningful phase boundaries. You give up
|
|
98
|
+
per-step snapshots (one capture at the very end). Use `ctx.step("phase", () => …)`
|
|
99
|
+
inside for timeline observability — it is NOT a snapshot boundary.
|
|
100
|
+
⚠️ Replay semantics: a paused run-form workflow RESUMES BY RE-EXECUTING the
|
|
101
|
+
whole function from the top — only `ctx.requestDecision` results are
|
|
102
|
+
memoized. Every pre-pause side effect re-fires on resume, so writes must be
|
|
103
|
+
create-only/non-clobbering (an unconditional re-write destroyed a human's
|
|
104
|
+
pause-time edits in production testing). If the workflow pauses at all,
|
|
105
|
+
author it step-form: completed steps structurally never replay.
|
|
106
|
+
|
|
107
|
+
If the work has more than one phase — especially multiple agent passes, or any
|
|
108
|
+
step whose work you'd hate to lose on a failure — it's step-form. Each such
|
|
109
|
+
phase is its own step.
|
|
94
110
|
|
|
95
111
|
### Should it have a sandbox environment?
|
|
96
112
|
|
|
@@ -126,6 +142,21 @@ Most workflows DO NOT need this. Default to no sandbox env.
|
|
|
126
142
|
IO panels; without them, fields show only their type.
|
|
127
143
|
- `.describe("...")` on the root schema too where helpful — surfaces
|
|
128
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.
|
|
129
160
|
|
|
130
161
|
## Templates
|
|
131
162
|
|
|
@@ -196,6 +227,7 @@ export default defineWorkflow({
|
|
|
196
227
|
description: "Pulls open PRs from a GitHub repo, scores each one by review urgency, surfaces the top five.",
|
|
197
228
|
input: InputSchema,
|
|
198
229
|
output: OutputSchema,
|
|
230
|
+
snapshots: { saveLatest: true }, // snapshot after each step → resume from the last one, not the top
|
|
199
231
|
})
|
|
200
232
|
.step(fetchStep)
|
|
201
233
|
.step(scoreStep)
|
|
@@ -206,10 +238,18 @@ export default defineWorkflow({
|
|
|
206
238
|
Notes:
|
|
207
239
|
- Each step's `output` schema must satisfy the next step's `input`
|
|
208
240
|
schema (the SDK enforces this at `defineWorkflow().step(...)` time
|
|
209
|
-
via TS inference)
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
241
|
+
via TS inference), and the FINAL step's `output` must be the same
|
|
242
|
+
zod instance as the workflow's declared `output`. Reshape inside the
|
|
243
|
+
upstream step's `run` body, not at the boundary.
|
|
244
|
+
- **Step bodies DO receive a `sandbox`** — `ctx.sandbox` is present
|
|
245
|
+
whenever the run executes in a sandbox-backed workspace. Run
|
|
246
|
+
`agent({ sandbox: ctx.sandbox, … })`, `ctx.sandbox.commands.run(…)`,
|
|
247
|
+
`ctx.sandbox.files.write(…)` inside any step. (The example above uses
|
|
248
|
+
pure transforms, but an agent pass is a perfectly good step — and the
|
|
249
|
+
preferred shape, because each step is independently snapshotted.)
|
|
250
|
+
- `snapshots: { saveLatest: true }` captures the sandbox after EACH
|
|
251
|
+
step (latest-only); add `retainSteps: true` only if you need every
|
|
252
|
+
step's snapshot kept for fork/replay (linear storage cost).
|
|
213
253
|
|
|
214
254
|
### Template B — run-form (agent-driven body)
|
|
215
255
|
|
|
@@ -281,13 +321,6 @@ export default defineWorkflow({
|
|
|
281
321
|
// saveLatest: true,
|
|
282
322
|
// retainSteps: false,
|
|
283
323
|
// },
|
|
284
|
-
//
|
|
285
|
-
// memory — opt-in. Requires `workflow-memory` to be registered in
|
|
286
|
-
// this factory.
|
|
287
|
-
// memory: true,
|
|
288
|
-
//
|
|
289
|
-
// postRunHooks — workflows that run after this one completes:
|
|
290
|
-
// postRunHooks: ["audit-trail", "notify-slack"],
|
|
291
324
|
});
|
|
292
325
|
```
|
|
293
326
|
|
|
@@ -347,20 +380,3 @@ export default defineWorkflow({
|
|
|
347
380
|
"To run it on the schedule: `agentc schedule create pr-triage-daily
|
|
348
381
|
--workflow pr-triage --cron '<expr>'` — the cron pattern we
|
|
349
382
|
worked out earlier.")
|
|
350
|
-
|
|
351
|
-
## When the user asks for memory extraction
|
|
352
|
-
|
|
353
|
-
The built-in memory extractor (`workflow-memory`) is a separate
|
|
354
|
-
workflow that must be registered in the same factory. Walk them
|
|
355
|
-
through it in user terms:
|
|
356
|
-
|
|
357
|
-
1. "I'll add the `workflow-memory` recipe to your project."
|
|
358
|
-
Copy from `.agentc/templates/workflow-memory.ts`.
|
|
359
|
-
2. "Run `agentc register ./workflow-memory.ts` once to install it."
|
|
360
|
-
3. "Now any workflow with `memory: true` will trigger it after each
|
|
361
|
-
successful run."
|
|
362
|
-
|
|
363
|
-
For custom post-run workflows (analytics, audit, notifications)
|
|
364
|
-
without the built-in extractor, use `postRunHooks: [...]` instead.
|
|
365
|
-
Each post-hook runs in declaration order with the source run's full
|
|
366
|
-
context.
|