@agent-compose/cli 0.3.2 → 0.3.4
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 +138 -52
- package/package.json +2 -2
- package/skills/ac:files.md +84 -0
- package/skills/ac:generate-workflow.md +52 -53
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
|
|
|
@@ -137,7 +137,8 @@ async function saveLocalSettings(projectDir, settings) {
|
|
|
137
137
|
var [_project, _projectDir] = findProjectRoot();
|
|
138
138
|
var _local = loadLocalSettings(_projectDir);
|
|
139
139
|
var _global = loadGlobalSettings();
|
|
140
|
-
var
|
|
140
|
+
var _candidateEnv = _local.env ?? _project.defaultEnv ?? "prod";
|
|
141
|
+
var _activeEnv = _project.envs?.[_candidateEnv] ? _candidateEnv : undefined;
|
|
141
142
|
var _envUrl = _activeEnv ? _project.envs?.[_activeEnv]?.url : undefined;
|
|
142
143
|
var _envDashboardUrl = _activeEnv ? _project.envs?.[_activeEnv]?.dashboardUrl : undefined;
|
|
143
144
|
var _envKey = _activeEnv ? _local.keys?.[_activeEnv] : undefined;
|
|
@@ -146,6 +147,7 @@ function fallbackDashboardUrl(env) {
|
|
|
146
147
|
return env === "local" ? "http://localhost:3000" : "https://platform.agentcompose.ai";
|
|
147
148
|
}
|
|
148
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;
|
|
149
151
|
var defaultDashboardUrl = process.env.AGENT_COMPOSE_DASHBOARD_URL ?? _envDashboardUrl ?? _global.dashboardUrl ?? fallbackDashboardUrl(_activeEnv);
|
|
150
152
|
var defaultApiKey = process.env.AGENT_COMPOSE_API_KEY ?? _envKey ?? _global.apiKey ?? "";
|
|
151
153
|
var defaultFactory = process.env.AGENT_COMPOSE_FACTORY ?? _envFactory ?? "default";
|
|
@@ -154,6 +156,10 @@ var projectSettings = _project;
|
|
|
154
156
|
var localSettings = _local;
|
|
155
157
|
var activeEnv = _activeEnv;
|
|
156
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
|
+
}
|
|
157
163
|
if (!apiKey) {
|
|
158
164
|
console.error(`API key required. Provide via:
|
|
159
165
|
` + ` • Project: .agentc/settings.local.json
|
|
@@ -183,7 +189,9 @@ var registerCommand = new Command("register").description("Register a workflow w
|
|
|
183
189
|
console.error(`Error: workflow not found at ${workflowPath}`);
|
|
184
190
|
process.exit(1);
|
|
185
191
|
}
|
|
186
|
-
const { source, manifest, description, networkPolicy, placeholders, snapshots,
|
|
192
|
+
const { source, manifest, description, networkPolicy, placeholders, snapshots, 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`);
|
|
187
195
|
if (networkPolicy)
|
|
188
196
|
console.log(`[register] Network policy detected — credentials will be brokered via Vercel firewall`);
|
|
189
197
|
if (snapshots?.bootFrom)
|
|
@@ -204,18 +212,14 @@ var registerCommand = new Command("register").description("Register a workflow w
|
|
|
204
212
|
...networkPolicy ? { networkPolicy } : {},
|
|
205
213
|
...placeholders ? { placeholders } : {},
|
|
206
214
|
...snapshots !== undefined ? { snapshots } : {},
|
|
207
|
-
...memory !== undefined ? { memory } : {},
|
|
208
|
-
...postRunHooks !== undefined ? { postRunHooks } : {},
|
|
209
215
|
...inputSchema !== undefined ? { inputSchema } : {},
|
|
210
|
-
...outputSchema !== undefined ? { outputSchema } : {}
|
|
216
|
+
...outputSchema !== undefined ? { outputSchema } : {},
|
|
217
|
+
...connectors !== undefined ? { connectors } : {},
|
|
218
|
+
...connectorOperation !== undefined ? { connectorOperation } : {},
|
|
219
|
+
...invokePolicy !== undefined ? { invokePolicy } : {}
|
|
211
220
|
});
|
|
212
221
|
const scheduleNote = opts.schedule ? ` — schedule: ${opts.schedule}` : "";
|
|
213
222
|
console.log(`✓ Workflow: ${result.name}@${result.version} (${result.id})${scheduleNote}`);
|
|
214
|
-
if (result.warnings && result.warnings.length > 0) {
|
|
215
|
-
for (const w of result.warnings) {
|
|
216
|
-
console.warn(`! warning: ${w}`);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
223
|
if (!opts.build)
|
|
220
224
|
return;
|
|
221
225
|
console.log(`[build] Invoking "${name}" with snapshots:{ saveLatest: true } to capture a snapshot…`);
|
|
@@ -241,7 +245,7 @@ var registerCommand = new Command("register").description("Register a workflow w
|
|
|
241
245
|
process.exit(1);
|
|
242
246
|
});
|
|
243
247
|
function formatBootFrom(b) {
|
|
244
|
-
return `snapshot ${b.snapshotId}`;
|
|
248
|
+
return b === "reuse" ? "reuse (this workflow's own latest snapshot)" : `snapshot ${b.snapshotId}`;
|
|
245
249
|
}
|
|
246
250
|
|
|
247
251
|
// src/commands/invoke.ts
|
|
@@ -257,8 +261,15 @@ function fmt(ms) {
|
|
|
257
261
|
return `${Math.floor(ms / 60000)}m ${Math.floor(ms % 60000 / 1000)}s`;
|
|
258
262
|
}
|
|
259
263
|
function extractToolSnippet(toolInput) {
|
|
260
|
-
|
|
261
|
-
|
|
264
|
+
if (!toolInput || typeof toolInput !== "object")
|
|
265
|
+
return "";
|
|
266
|
+
const input = toolInput;
|
|
267
|
+
for (const key of ["command", "file_path", "url", "query", "pattern"]) {
|
|
268
|
+
const value = input[key];
|
|
269
|
+
if (typeof value === "string" && value.length > 0)
|
|
270
|
+
return value.slice(0, 80);
|
|
271
|
+
}
|
|
272
|
+
return "";
|
|
262
273
|
}
|
|
263
274
|
function printAgentMessage(data) {
|
|
264
275
|
const message = data.message;
|
|
@@ -280,7 +291,7 @@ function printAgentMessage(data) {
|
|
|
280
291
|
break;
|
|
281
292
|
}
|
|
282
293
|
case "tool_use": {
|
|
283
|
-
const detail = extractToolSnippet(message.
|
|
294
|
+
const detail = extractToolSnippet(message.toolInput);
|
|
284
295
|
process.stdout.write(` ${pc.cyan("→")} ${pc.bold(String(message.toolName ?? "tool"))}${detail ? ` ${pc.dim(detail)}` : ""}
|
|
285
296
|
`);
|
|
286
297
|
break;
|
|
@@ -291,6 +302,15 @@ function printAgentMessage(data) {
|
|
|
291
302
|
break;
|
|
292
303
|
}
|
|
293
304
|
}
|
|
305
|
+
function printConsoleLine(line) {
|
|
306
|
+
const text = line.line.trimEnd();
|
|
307
|
+
if (!text)
|
|
308
|
+
return;
|
|
309
|
+
const label = line.stream === "stderr" ? pc.red("stderr") : pc.dim("stdout");
|
|
310
|
+
const color = line.stream === "stderr" ? pc.red : pc.dim;
|
|
311
|
+
process.stdout.write(`${label} ${color(text)}
|
|
312
|
+
`);
|
|
313
|
+
}
|
|
294
314
|
function printEvent(event, data, state) {
|
|
295
315
|
switch (event) {
|
|
296
316
|
case "step_started":
|
|
@@ -424,45 +444,81 @@ ${pc.bold(pc.yellow("⊘ Run canceled"))}
|
|
|
424
444
|
}
|
|
425
445
|
}
|
|
426
446
|
var TERMINAL_EVENTS = new Set(["run_complete", "run_failed", "run_canceled"]);
|
|
447
|
+
var CONSOLE_POLL_MS = 500;
|
|
427
448
|
async function streamLogs(runId, opts) {
|
|
428
449
|
const client = makeClient(opts);
|
|
450
|
+
const view = opts.view ?? "all";
|
|
429
451
|
const state = {
|
|
430
452
|
agentStart: new Map,
|
|
431
453
|
agentLabel: new Map,
|
|
432
454
|
startedAt: Date.now()
|
|
433
455
|
};
|
|
434
456
|
let lastSeq = 0;
|
|
457
|
+
let lastLogId = 0;
|
|
458
|
+
let stopConsole = false;
|
|
435
459
|
const MAX_RETRIES = 5;
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
460
|
+
const drainConsole = async () => {
|
|
461
|
+
const lines = await client.listRunLogs(runId, { afterId: lastLogId, limit: 200, direction: "asc" });
|
|
462
|
+
for (const line of lines) {
|
|
463
|
+
if (line.id > lastLogId)
|
|
464
|
+
lastLogId = line.id;
|
|
465
|
+
printConsoleLine(line);
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
const consoleLoop = view === "agent" ? null : (async () => {
|
|
469
|
+
while (!stopConsole) {
|
|
470
|
+
await drainConsole().catch((err) => {
|
|
471
|
+
if (err instanceof AgentComposeError2 && err.status === 404)
|
|
472
|
+
return;
|
|
473
|
+
throw err;
|
|
474
|
+
});
|
|
475
|
+
if (!stopConsole)
|
|
476
|
+
await new Promise((r) => setTimeout(r, CONSOLE_POLL_MS));
|
|
477
|
+
}
|
|
478
|
+
await drainConsole().catch(() => {});
|
|
479
|
+
})();
|
|
480
|
+
try {
|
|
481
|
+
for (let attempt = 0;attempt <= MAX_RETRIES; attempt++) {
|
|
482
|
+
let settled = false;
|
|
483
|
+
try {
|
|
484
|
+
for await (const ev of client.streamRunLogs(runId, { lastEventId: lastSeq })) {
|
|
485
|
+
const seq = ev.seq ?? 0;
|
|
486
|
+
if (seq > 0)
|
|
487
|
+
lastSeq = seq;
|
|
488
|
+
if (view !== "console" || TERMINAL_EVENTS.has(ev.event)) {
|
|
489
|
+
printEvent(ev.event, ev, state);
|
|
490
|
+
}
|
|
491
|
+
if (TERMINAL_EVENTS.has(ev.event)) {
|
|
492
|
+
settled = true;
|
|
493
|
+
break;
|
|
494
|
+
}
|
|
447
495
|
}
|
|
496
|
+
} catch (err) {
|
|
497
|
+
if (err instanceof AgentComposeError2) {
|
|
498
|
+
console.error(`Error: failed to connect to log stream (${err.status})`);
|
|
499
|
+
process.exit(1);
|
|
500
|
+
}
|
|
501
|
+
throw err;
|
|
448
502
|
}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
}
|
|
454
|
-
throw err;
|
|
503
|
+
if (settled)
|
|
504
|
+
return;
|
|
505
|
+
if (attempt < MAX_RETRIES)
|
|
506
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
455
507
|
}
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
await new Promise((r) => setTimeout(r, 200));
|
|
508
|
+
} finally {
|
|
509
|
+
stopConsole = true;
|
|
510
|
+
await consoleLoop;
|
|
460
511
|
}
|
|
461
512
|
console.error("Error: stream closed without terminal event after retries");
|
|
462
513
|
process.exit(1);
|
|
463
514
|
}
|
|
464
|
-
var logsCommand = new Command2("logs").description("Stream logs for a run").argument("<run-id>", "Run ID").option("--url <url>", "Server URL", parseUrlFlag, defaultUrl).option("--api-key <key>", "API key", defaultApiKey).action(async (runId, opts) => {
|
|
465
|
-
|
|
515
|
+
var logsCommand = new Command2("logs").description("Stream logs for a run").argument("<run-id>", "Run ID").option("--agent", "Show structured agent/lifecycle events only").option("--console", "Show raw runner stdout/stderr only").option("--url <url>", "Server URL", parseUrlFlag, defaultUrl).option("--api-key <key>", "API key", defaultApiKey).action(async (runId, opts) => {
|
|
516
|
+
if (opts.agent && opts.console) {
|
|
517
|
+
console.error("Error: choose only one of --agent or --console");
|
|
518
|
+
process.exit(1);
|
|
519
|
+
}
|
|
520
|
+
const view = opts.agent ? "agent" : opts.console ? "console" : "all";
|
|
521
|
+
await streamLogs(runId, { ...opts, view });
|
|
466
522
|
});
|
|
467
523
|
|
|
468
524
|
// src/commands/invoke.ts
|
|
@@ -1160,11 +1216,38 @@ var listCommand2 = new Command13("list").description("List events for a run, or
|
|
|
1160
1216
|
});
|
|
1161
1217
|
var eventsCommand = new Command13("events").description("Send or list run events (Events API)").addCommand(sendCommand).addCommand(listCommand2);
|
|
1162
1218
|
|
|
1163
|
-
// src/commands/
|
|
1219
|
+
// src/commands/files.ts
|
|
1220
|
+
import { readFileSync as readFileSync4, writeFileSync } from "node:fs";
|
|
1164
1221
|
import { Command as Command14 } from "commander";
|
|
1165
|
-
var sharedOpts2 = (cmd) => cmd.option("--factory <slug>", `Factory
|
|
1166
|
-
var
|
|
1167
|
-
sharedOpts2(
|
|
1222
|
+
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");
|
|
1224
|
+
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
|
+
const client = makeClient(opts);
|
|
1226
|
+
const result = await client.putFactoryFile(drivePath, readFileSync4(localFile), {
|
|
1227
|
+
factorySlug: opts.factory,
|
|
1228
|
+
contentType: opts.contentType
|
|
1229
|
+
});
|
|
1230
|
+
console.log(`✓ ${result.created ? "created" : "updated"} ${result.path} (${result.sizeBytes} bytes)`);
|
|
1231
|
+
});
|
|
1232
|
+
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) => {
|
|
1233
|
+
const client = makeClient(opts);
|
|
1234
|
+
const content = await client.getFactoryFile(drivePath, {
|
|
1235
|
+
factorySlug: opts.factory,
|
|
1236
|
+
...opts.revision !== undefined ? { revision: Number(opts.revision) } : {}
|
|
1237
|
+
});
|
|
1238
|
+
if (opts.out) {
|
|
1239
|
+
writeFileSync(opts.out, content);
|
|
1240
|
+
console.log(`✓ ${drivePath} → ${opts.out}`);
|
|
1241
|
+
} else {
|
|
1242
|
+
process.stdout.write(content);
|
|
1243
|
+
}
|
|
1244
|
+
});
|
|
1245
|
+
|
|
1246
|
+
// src/commands/schedule.ts
|
|
1247
|
+
import { Command as Command15 } from "commander";
|
|
1248
|
+
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");
|
|
1250
|
+
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) => {
|
|
1168
1251
|
const client = makeClient(opts);
|
|
1169
1252
|
const created = await client.createSchedule({
|
|
1170
1253
|
name,
|
|
@@ -1174,7 +1257,7 @@ sharedOpts2(scheduleCommand.command("create <name>").description("Create a new s
|
|
|
1174
1257
|
});
|
|
1175
1258
|
console.log(`✓ Schedule "${created.name}" → workflow "${created.workflow}" @ "${created.cron}" (${created.id})`);
|
|
1176
1259
|
});
|
|
1177
|
-
|
|
1260
|
+
sharedOpts3(scheduleCommand.command("list").description("List schedules in a factory")).action(async (opts) => {
|
|
1178
1261
|
const client = makeClient(opts);
|
|
1179
1262
|
const rows = await client.listSchedules(opts.factory);
|
|
1180
1263
|
if (rows.length === 0) {
|
|
@@ -1187,21 +1270,21 @@ sharedOpts2(scheduleCommand.command("list").description("List schedules in a fac
|
|
|
1187
1270
|
console.log(` ${s.name.padEnd(24)} ${s.workflowName.padEnd(20)} ${s.cron.padEnd(16)} next: ${next} ${s.id}`);
|
|
1188
1271
|
}
|
|
1189
1272
|
});
|
|
1190
|
-
|
|
1273
|
+
sharedOpts3(scheduleCommand.command("delete <id>").description("Delete a schedule by id")).action(async (id, opts) => {
|
|
1191
1274
|
const client = makeClient(opts);
|
|
1192
1275
|
await client.deleteSchedule(id, opts.factory);
|
|
1193
1276
|
console.log(`✓ Schedule ${id} deleted from factory "${opts.factory}"`);
|
|
1194
1277
|
});
|
|
1195
1278
|
|
|
1196
1279
|
// src/commands/upgrade.ts
|
|
1197
|
-
import { Command as
|
|
1280
|
+
import { Command as Command16 } from "commander";
|
|
1198
1281
|
import { spawnSync } from "node:child_process";
|
|
1199
|
-
import { readFileSync as
|
|
1282
|
+
import { readFileSync as readFileSync5 } from "node:fs";
|
|
1200
1283
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
1201
1284
|
import pc4 from "picocolors";
|
|
1202
1285
|
var PACKAGE_NAME = "@agent-compose/cli";
|
|
1203
|
-
var upgradeCommand = new
|
|
1204
|
-
const pkg = JSON.parse(
|
|
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) => {
|
|
1287
|
+
const pkg = JSON.parse(readFileSync5(fileURLToPath2(new URL("../../package.json", import.meta.url)), "utf8"));
|
|
1205
1288
|
const current = pkg.version;
|
|
1206
1289
|
const controller = new AbortController;
|
|
1207
1290
|
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
@@ -1243,7 +1326,7 @@ var upgradeCommand = new Command15("upgrade").description("Update the agentc CLI
|
|
|
1243
1326
|
|
|
1244
1327
|
// src/update-check.ts
|
|
1245
1328
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
1246
|
-
import { mkdirSync as mkdirSync2, readFileSync as
|
|
1329
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync2 } from "node:fs";
|
|
1247
1330
|
import { homedir as homedir3 } from "node:os";
|
|
1248
1331
|
import { join as join3 } from "node:path";
|
|
1249
1332
|
import { createInterface } from "node:readline/promises";
|
|
@@ -1283,7 +1366,7 @@ function isNewer(latest, current) {
|
|
|
1283
1366
|
}
|
|
1284
1367
|
function readCache() {
|
|
1285
1368
|
try {
|
|
1286
|
-
const raw =
|
|
1369
|
+
const raw = readFileSync6(cachePath(), "utf8");
|
|
1287
1370
|
const parsed = JSON.parse(raw);
|
|
1288
1371
|
if (typeof parsed.checkedAt !== "number")
|
|
1289
1372
|
return null;
|
|
@@ -1299,7 +1382,7 @@ function writeCache(entry) {
|
|
|
1299
1382
|
try {
|
|
1300
1383
|
const path = cachePath();
|
|
1301
1384
|
mkdirSync2(join3(path, ".."), { recursive: true });
|
|
1302
|
-
|
|
1385
|
+
writeFileSync2(path, JSON.stringify(entry, null, 2) + `
|
|
1303
1386
|
`, "utf8");
|
|
1304
1387
|
} catch {}
|
|
1305
1388
|
}
|
|
@@ -1372,7 +1455,7 @@ async function checkForUpdate(currentVersion) {
|
|
|
1372
1455
|
}
|
|
1373
1456
|
|
|
1374
1457
|
// src/index.ts
|
|
1375
|
-
var pkg = JSON.parse(
|
|
1458
|
+
var pkg = JSON.parse(readFileSync7(fileURLToPath3(new URL("../package.json", import.meta.url)), "utf8"));
|
|
1376
1459
|
program.name("agentc").description("Coordinate agent production lines.").version(pkg.version);
|
|
1377
1460
|
program.hook("preAction", async () => {
|
|
1378
1461
|
try {
|
|
@@ -1386,6 +1469,7 @@ program.addCommand(cancelCommand);
|
|
|
1386
1469
|
program.addCommand(scheduleCommand);
|
|
1387
1470
|
program.addCommand(logsCommand);
|
|
1388
1471
|
program.addCommand(eventsCommand);
|
|
1472
|
+
program.addCommand(filesCommand);
|
|
1389
1473
|
program.addCommand(factoryCommand);
|
|
1390
1474
|
program.addCommand(snapshotCommand);
|
|
1391
1475
|
program.addCommand(secretsCommand);
|
|
@@ -1399,6 +1483,7 @@ Topics:
|
|
|
1399
1483
|
Workflows register, invoke, list, cancel
|
|
1400
1484
|
Scheduling schedule
|
|
1401
1485
|
Run inspection logs, events
|
|
1486
|
+
Factory drive files
|
|
1402
1487
|
Resources factory, snapshot, secrets
|
|
1403
1488
|
Account keys, auth, usage
|
|
1404
1489
|
Setup init, upgrade
|
|
@@ -1408,6 +1493,7 @@ Examples:
|
|
|
1408
1493
|
$ agentc invoke pipeline --follow
|
|
1409
1494
|
$ agentc schedule create nightly --workflow pipeline --cron '0 9 * * *'
|
|
1410
1495
|
$ agentc logs <run-id>
|
|
1496
|
+
$ agentc files put report.md runs/abc123/report.md
|
|
1411
1497
|
|
|
1412
1498
|
Docs:
|
|
1413
1499
|
https://github.com/Layr-Labs/agent-compose
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-compose/cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
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.2",
|
|
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
|
|
|
@@ -196,6 +212,7 @@ export default defineWorkflow({
|
|
|
196
212
|
description: "Pulls open PRs from a GitHub repo, scores each one by review urgency, surfaces the top five.",
|
|
197
213
|
input: InputSchema,
|
|
198
214
|
output: OutputSchema,
|
|
215
|
+
snapshots: { saveLatest: true }, // snapshot after each step → resume from the last one, not the top
|
|
199
216
|
})
|
|
200
217
|
.step(fetchStep)
|
|
201
218
|
.step(scoreStep)
|
|
@@ -206,10 +223,18 @@ export default defineWorkflow({
|
|
|
206
223
|
Notes:
|
|
207
224
|
- Each step's `output` schema must satisfy the next step's `input`
|
|
208
225
|
schema (the SDK enforces this at `defineWorkflow().step(...)` time
|
|
209
|
-
via TS inference)
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
226
|
+
via TS inference), and the FINAL step's `output` must be the same
|
|
227
|
+
zod instance as the workflow's declared `output`. Reshape inside the
|
|
228
|
+
upstream step's `run` body, not at the boundary.
|
|
229
|
+
- **Step bodies DO receive a `sandbox`** — `ctx.sandbox` is present
|
|
230
|
+
whenever the run executes in a sandbox-backed workspace. Run
|
|
231
|
+
`agent({ sandbox: ctx.sandbox, … })`, `ctx.sandbox.commands.run(…)`,
|
|
232
|
+
`ctx.sandbox.files.write(…)` inside any step. (The example above uses
|
|
233
|
+
pure transforms, but an agent pass is a perfectly good step — and the
|
|
234
|
+
preferred shape, because each step is independently snapshotted.)
|
|
235
|
+
- `snapshots: { saveLatest: true }` captures the sandbox after EACH
|
|
236
|
+
step (latest-only); add `retainSteps: true` only if you need every
|
|
237
|
+
step's snapshot kept for fork/replay (linear storage cost).
|
|
213
238
|
|
|
214
239
|
### Template B — run-form (agent-driven body)
|
|
215
240
|
|
|
@@ -281,13 +306,6 @@ export default defineWorkflow({
|
|
|
281
306
|
// saveLatest: true,
|
|
282
307
|
// retainSteps: false,
|
|
283
308
|
// },
|
|
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
309
|
});
|
|
292
310
|
```
|
|
293
311
|
|
|
@@ -347,22 +365,3 @@ export default defineWorkflow({
|
|
|
347
365
|
"To run it on the schedule: `agentc schedule create pr-triage-daily
|
|
348
366
|
--workflow pr-triage --cron '<expr>'` — the cron pattern we
|
|
349
367
|
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 `templates/workflow-memory.ts` (or
|
|
359
|
-
`.agentc/smoketest/workflows/workflow-memory.ts` for the smoke
|
|
360
|
-
variant).
|
|
361
|
-
2. "Run `agentc register ./workflow-memory.ts` once to install it."
|
|
362
|
-
3. "Now any workflow with `memory: true` will trigger it after each
|
|
363
|
-
successful run."
|
|
364
|
-
|
|
365
|
-
For custom post-run workflows (analytics, audit, notifications)
|
|
366
|
-
without the built-in extractor, use `postRunHooks: [...]` instead.
|
|
367
|
-
Each post-hook runs in declaration order with the source run's full
|
|
368
|
-
context.
|