@pixelml/agenticflow-cli 1.7.1 → 1.10.0
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/cli/blueprint-to-agent.d.ts +67 -0
- package/dist/cli/blueprint-to-agent.d.ts.map +1 -0
- package/dist/cli/blueprint-to-agent.js +167 -0
- package/dist/cli/blueprint-to-agent.js.map +1 -0
- package/dist/cli/blueprint-to-workflow.d.ts +72 -0
- package/dist/cli/blueprint-to-workflow.d.ts.map +1 -0
- package/dist/cli/blueprint-to-workflow.js +120 -0
- package/dist/cli/blueprint-to-workflow.js.map +1 -0
- package/dist/cli/blueprint-to-workforce.d.ts.map +1 -1
- package/dist/cli/blueprint-to-workforce.js +148 -25
- package/dist/cli/blueprint-to-workforce.js.map +1 -1
- package/dist/cli/changelog.d.ts.map +1 -1
- package/dist/cli/changelog.js +82 -0
- package/dist/cli/changelog.js.map +1 -1
- package/dist/cli/company-blueprints.d.ts +129 -7
- package/dist/cli/company-blueprints.d.ts.map +1 -1
- package/dist/cli/company-blueprints.js +514 -4
- package/dist/cli/company-blueprints.js.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +824 -30
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/playbooks.d.ts.map +1 -1
- package/dist/cli/playbooks.js +230 -0
- package/dist/cli/playbooks.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/main.js
CHANGED
|
@@ -15,7 +15,7 @@ import { startGateway } from "./gateway/server.js";
|
|
|
15
15
|
import { PaperclipConnector } from "./gateway/connectors/paperclip.js";
|
|
16
16
|
import { LinearConnector } from "./gateway/connectors/linear.js";
|
|
17
17
|
import { WebhookConnector } from "./gateway/connectors/webhook.js";
|
|
18
|
-
import { listBlueprints, getBlueprint } from "./company-blueprints.js";
|
|
18
|
+
import { listBlueprints, getBlueprint, blueprintKind, blueprintComplexity } from "./company-blueprints.js";
|
|
19
19
|
import { CHANGELOG, getLatestChangelog } from "./changelog.js";
|
|
20
20
|
import { stripNullFields, AGENT_UPDATE_STRIP_NULL_FIELDS } from "./utils/patch.js";
|
|
21
21
|
import { inspectMcpToolsPattern } from "./utils/mcp-inspect.js";
|
|
@@ -1005,10 +1005,14 @@ export function createProgram() {
|
|
|
1005
1005
|
list_agents_filtered: "af agent list --name-contains <substr> --fields id,name --json",
|
|
1006
1006
|
update_agent_patch: "af agent update --agent-id <id> --patch --body '{\"field\":\"value\"}' --json",
|
|
1007
1007
|
delete_agent: "af agent delete --agent-id <id> --json",
|
|
1008
|
-
|
|
1008
|
+
init_agent_from_blueprint: "af agent init --blueprint <tier1-id> --json # Tier 1 single-agent + plugins, works in any workspace",
|
|
1009
|
+
init_workforce: "af workforce init --blueprint <id> --json # Tier 3 multi-agent DAG (requires MAS feature)",
|
|
1009
1010
|
run_workforce: "af workforce run --workforce-id <id> --trigger-data '{}'",
|
|
1010
1011
|
publish_workforce: "af workforce publish --workforce-id <id> --json",
|
|
1011
1012
|
delete_workforce: "af workforce delete --workforce-id <id> --json",
|
|
1013
|
+
browse_marketplace: "af marketplace list --type agent_template|workflow_template|mas_template --json",
|
|
1014
|
+
try_marketplace_item: "af marketplace try --id <item_id> --dry-run --json # clone an item into your workspace",
|
|
1015
|
+
duplicate_workforce_template: "af templates duplicate workforce --template-id <marketplace_mas_id> --dry-run --json",
|
|
1012
1016
|
inspect_mcp_client: "af mcp-clients inspect --id <id> --json",
|
|
1013
1017
|
deploy_to_paperclip: "af paperclip init --blueprint <id> --json # DEPRECATED, use `af workforce init`",
|
|
1014
1018
|
send_webhook: "curl -X POST http://localhost:4100/webhook/webhook -H 'Content-Type: application/json' -d '{\"agent_id\":\"<id>\",\"message\":\"<msg>\"}'",
|
|
@@ -1025,12 +1029,22 @@ export function createProgram() {
|
|
|
1025
1029
|
"agenticflow/deepseek-v3.2",
|
|
1026
1030
|
"agenticflow/qwen-3.5-flash",
|
|
1027
1031
|
],
|
|
1028
|
-
blueprints: listBlueprints().map((b) =>
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1032
|
+
blueprints: listBlueprints().map((b) => {
|
|
1033
|
+
const k = blueprintKind(b);
|
|
1034
|
+
return {
|
|
1035
|
+
id: b.id,
|
|
1036
|
+
name: b.name,
|
|
1037
|
+
kind: k,
|
|
1038
|
+
complexity: blueprintComplexity(b),
|
|
1039
|
+
tier: b.tier ?? (k === "agent" ? 1 : 3),
|
|
1040
|
+
agents: b.agents.length,
|
|
1041
|
+
node_count: b.workflowNodes?.length ?? 0,
|
|
1042
|
+
use_cases: b.useCases ?? null,
|
|
1043
|
+
// Deploy verb matches kind: workflow/agent/workforce → af <verb> init
|
|
1044
|
+
deploy_command: `af ${k} init --blueprint ${b.id} --json`,
|
|
1045
|
+
native_target: k,
|
|
1046
|
+
};
|
|
1047
|
+
}),
|
|
1034
1048
|
playbooks: listPlaybooks().map((p) => p.topic),
|
|
1035
1049
|
whats_new: getLatestChangelog(),
|
|
1036
1050
|
_links: {
|
|
@@ -2394,6 +2408,161 @@ export function createProgram() {
|
|
|
2394
2408
|
fail("request_failed", message);
|
|
2395
2409
|
}
|
|
2396
2410
|
});
|
|
2411
|
+
// `templates duplicate workforce` — clone a MAS template into a fresh
|
|
2412
|
+
// workforce. Unlike agent/workflow templates, MAS templates already
|
|
2413
|
+
// contain `nodes[]` + `edges[]` ready to feed into workforce.putSchema.
|
|
2414
|
+
// Agent nodes inside the template reference real agent_ids of the SOURCE
|
|
2415
|
+
// workspace — those will 400 on schema PUT if they aren't accessible from
|
|
2416
|
+
// the target workspace. We surface this as a warning on dry-run.
|
|
2417
|
+
templatesDuplicateCmd
|
|
2418
|
+
.command("workforce")
|
|
2419
|
+
.description("Duplicate a MAS/workforce template into a new workforce. Accepts --template-id " +
|
|
2420
|
+
"(mas_template id, e.g. from `af marketplace list --type mas_template`) or " +
|
|
2421
|
+
"--template-file (a local JSON snapshot).")
|
|
2422
|
+
.option("--template-id <id>", "MAS template ID (UUID of the mas_template row, NOT the source workforce ULID)")
|
|
2423
|
+
.option("--template-file <path>", "Local MAS template JSON file (with nodes, edges, name)")
|
|
2424
|
+
.option("--workforce-id <id>", "Source workforce ULID (passed to /v1/mas-templates/?workforce_id=X to enumerate versions)")
|
|
2425
|
+
.option("--workspace-id <id>", "Target workspace ID override")
|
|
2426
|
+
.option("--project-id <id>", "Target project ID override")
|
|
2427
|
+
.option("--name <name>", "Name for the duplicated workforce (defaults to template name + suffix)")
|
|
2428
|
+
.option("--name-suffix <suffix>", "Suffix if --name is not provided", " [Copy]")
|
|
2429
|
+
.option("--dry-run", "Print the create payload + schema without writing")
|
|
2430
|
+
.action(async (opts) => {
|
|
2431
|
+
const parentOpts = program.opts();
|
|
2432
|
+
const client = buildClient(parentOpts);
|
|
2433
|
+
const templateId = opts.templateId;
|
|
2434
|
+
const templateFile = opts.templateFile;
|
|
2435
|
+
const sourceWorkforceId = opts.workforceId;
|
|
2436
|
+
const provided = [templateId, templateFile, sourceWorkforceId].filter(Boolean).length;
|
|
2437
|
+
if (provided === 0) {
|
|
2438
|
+
fail("missing_required_option", "Provide one of: --template-id <mas_template_id>, --template-file <path>, or --workforce-id <src_workforce_ulid>.", "Browse MAS templates via: af marketplace list --type mas_template --json");
|
|
2439
|
+
}
|
|
2440
|
+
if (provided > 1) {
|
|
2441
|
+
fail("invalid_request_options", "Pass only one of --template-id, --template-file, --workforce-id.");
|
|
2442
|
+
}
|
|
2443
|
+
// Resolve the template snapshot (must contain nodes[] + edges[] + name)
|
|
2444
|
+
let template;
|
|
2445
|
+
let templateSource;
|
|
2446
|
+
if (templateFile) {
|
|
2447
|
+
template = loadJsonPayload(`@${templateFile}`);
|
|
2448
|
+
templateSource = "file";
|
|
2449
|
+
}
|
|
2450
|
+
else if (templateId) {
|
|
2451
|
+
// Try marketplace detail first (returns mas_template_detail with nodes/edges)
|
|
2452
|
+
try {
|
|
2453
|
+
const item = (await client.sdk.get(`/v1/marketplace/items/${templateId}`)).data;
|
|
2454
|
+
const detail = item["mas_template_detail"];
|
|
2455
|
+
if (detail && typeof detail === "object") {
|
|
2456
|
+
template = detail;
|
|
2457
|
+
templateSource = "marketplace_item";
|
|
2458
|
+
}
|
|
2459
|
+
else {
|
|
2460
|
+
throw new Error("Marketplace item has no mas_template_detail");
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
catch {
|
|
2464
|
+
// Fallback: direct MAS template id via versions endpoint requires workforce_id,
|
|
2465
|
+
// so we can only reach this path via the marketplace item. Surface a clear error.
|
|
2466
|
+
fail("template_not_found", `No MAS template detail found for id "${templateId}".`, "The id must be a marketplace item id (from `af marketplace list --type mas_template`) or a mas_template row id reachable via marketplace lookup.");
|
|
2467
|
+
return;
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2470
|
+
else {
|
|
2471
|
+
// --workforce-id path: enumerate versions, pick the latest
|
|
2472
|
+
try {
|
|
2473
|
+
const versions = (await client.sdk.get("/v1/mas-templates", {
|
|
2474
|
+
queryParams: { workforce_id: sourceWorkforceId, limit: 1, offset: 0 },
|
|
2475
|
+
})).data;
|
|
2476
|
+
const list = Array.isArray(versions) ? versions : [];
|
|
2477
|
+
if (list.length === 0) {
|
|
2478
|
+
fail("template_not_found", `No MAS template versions found for workforce ${sourceWorkforceId}.`, "Use 'af workforce versions list --workforce-id <id>' to confirm versions exist.");
|
|
2479
|
+
}
|
|
2480
|
+
template = list[0];
|
|
2481
|
+
templateSource = "api_versions";
|
|
2482
|
+
}
|
|
2483
|
+
catch (err) {
|
|
2484
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2485
|
+
fail("request_failed", message);
|
|
2486
|
+
return;
|
|
2487
|
+
}
|
|
2488
|
+
}
|
|
2489
|
+
const projectId = resolveProjectId(opts.projectId);
|
|
2490
|
+
if (!projectId && !opts.dryRun) {
|
|
2491
|
+
fail("missing_project_id", "Project ID is required to create a duplicated workforce.", "Set AGENTICFLOW_PROJECT_ID or pass --project-id.");
|
|
2492
|
+
}
|
|
2493
|
+
const workspaceId = resolveWorkspaceId(opts.workspaceId);
|
|
2494
|
+
if (!workspaceId && !opts.dryRun) {
|
|
2495
|
+
fail("missing_workspace_id", "Workspace ID is required to create a duplicated workforce.", "Set AGENTICFLOW_WORKSPACE_ID or pass --workspace-id.");
|
|
2496
|
+
}
|
|
2497
|
+
const srcName = template["name"] ?? "Workforce";
|
|
2498
|
+
const targetName = opts.name ??
|
|
2499
|
+
`${srcName}${opts.nameSuffix ?? " [Copy]"}`;
|
|
2500
|
+
const nodes = Array.isArray(template["nodes"]) ? template["nodes"] : [];
|
|
2501
|
+
const edges = Array.isArray(template["edges"]) ? template["edges"] : [];
|
|
2502
|
+
// Cross-workspace agent-node warnings — MAS templates reference real
|
|
2503
|
+
// agent_ids of the source workspace, which will typically 400 on PUT
|
|
2504
|
+
// in a different workspace.
|
|
2505
|
+
const agentNodeIds = [];
|
|
2506
|
+
for (const n of nodes) {
|
|
2507
|
+
if (n && typeof n === "object") {
|
|
2508
|
+
const node = n;
|
|
2509
|
+
if (node["type"] === "agent" || node["type"] === "agent_team_member") {
|
|
2510
|
+
const input = node["input"] ?? {};
|
|
2511
|
+
const agentId = input["agent_id"];
|
|
2512
|
+
if (typeof agentId === "string")
|
|
2513
|
+
agentNodeIds.push(agentId);
|
|
2514
|
+
}
|
|
2515
|
+
}
|
|
2516
|
+
}
|
|
2517
|
+
if (opts.dryRun) {
|
|
2518
|
+
printResult({
|
|
2519
|
+
schema: TEMPLATE_DUPLICATE_SCHEMA_VERSION,
|
|
2520
|
+
kind: "workforce",
|
|
2521
|
+
dry_run: true,
|
|
2522
|
+
template_source: templateSource,
|
|
2523
|
+
workforce: { name: targetName, description: template["description"] ?? null },
|
|
2524
|
+
node_count: nodes.length,
|
|
2525
|
+
edge_count: edges.length,
|
|
2526
|
+
referenced_agent_ids: agentNodeIds,
|
|
2527
|
+
warnings: agentNodeIds.length
|
|
2528
|
+
? [
|
|
2529
|
+
`Template references ${agentNodeIds.length} agent_id(s) from the source workspace. ` +
|
|
2530
|
+
`These must also exist (or be duplicated) in the target workspace or schema PUT will 400.`,
|
|
2531
|
+
]
|
|
2532
|
+
: [],
|
|
2533
|
+
});
|
|
2534
|
+
return;
|
|
2535
|
+
}
|
|
2536
|
+
try {
|
|
2537
|
+
const created = (await client.workforces.create({ name: targetName, description: template["description"] ?? null }, { workspaceId: workspaceId, projectId: projectId }));
|
|
2538
|
+
const workforceId = created["id"];
|
|
2539
|
+
if (!workforceId)
|
|
2540
|
+
throw new Error("Workforce create did not return an id.");
|
|
2541
|
+
await client.workforces.putSchema(workforceId, {
|
|
2542
|
+
nodes: nodes,
|
|
2543
|
+
edges: edges,
|
|
2544
|
+
}, { workspaceId: workspaceId });
|
|
2545
|
+
printResult({
|
|
2546
|
+
schema: TEMPLATE_DUPLICATE_SCHEMA_VERSION,
|
|
2547
|
+
kind: "workforce",
|
|
2548
|
+
dry_run: false,
|
|
2549
|
+
template_source: templateSource,
|
|
2550
|
+
workforce_id: workforceId,
|
|
2551
|
+
name: targetName,
|
|
2552
|
+
node_count: nodes.length,
|
|
2553
|
+
edge_count: edges.length,
|
|
2554
|
+
warnings: agentNodeIds.length
|
|
2555
|
+
? [
|
|
2556
|
+
`Duplicated workforce references ${agentNodeIds.length} agent_id(s) from the source workspace — run it to verify the agents resolve in this workspace. If not, duplicate the source agents first.`,
|
|
2557
|
+
]
|
|
2558
|
+
: [],
|
|
2559
|
+
});
|
|
2560
|
+
}
|
|
2561
|
+
catch (err) {
|
|
2562
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2563
|
+
fail("request_failed", message);
|
|
2564
|
+
}
|
|
2565
|
+
});
|
|
2397
2566
|
templatesCmd
|
|
2398
2567
|
.command("index")
|
|
2399
2568
|
.description("Inspect a local template cache manifest.")
|
|
@@ -2428,15 +2597,414 @@ export function createProgram() {
|
|
|
2428
2597
|
}
|
|
2429
2598
|
});
|
|
2430
2599
|
// ═════════════════════════════════════════════════════════════════
|
|
2600
|
+
// blueprints (CLI-shipped starter catalog — the dedicated discovery surface)
|
|
2601
|
+
// ═════════════════════════════════════════════════════════════════
|
|
2602
|
+
// Historically blueprints were only discoverable by reading `agent init --help`
|
|
2603
|
+
// text or via `bootstrap --json > blueprints[]`. PDCA round (2026-04-14)
|
|
2604
|
+
// showed fresh users hit "no single `blueprints list` command" friction —
|
|
2605
|
+
// they'd look for a dedicated catalog command first. This group provides it
|
|
2606
|
+
// as a thin wrapper over the in-process registry (no backend call).
|
|
2607
|
+
const blueprintsCmd = program
|
|
2608
|
+
.command("blueprints")
|
|
2609
|
+
.description("Browse CLI-shipped blueprints (offline, versioned starter patterns). " +
|
|
2610
|
+
"Tier 1 = single agent (af agent init), Tier 3 = workforce (af workforce init). " +
|
|
2611
|
+
"For the live user/admin-curated catalog, use `af marketplace` instead.");
|
|
2612
|
+
// Shared helper — keep blueprints list/get/bootstrap in sync on deploy_command.
|
|
2613
|
+
const deployCommandForBlueprint = (b) => {
|
|
2614
|
+
const k = blueprintKind(b);
|
|
2615
|
+
return `af ${k} init --blueprint ${b.id} --json`;
|
|
2616
|
+
};
|
|
2617
|
+
blueprintsCmd
|
|
2618
|
+
.command("list")
|
|
2619
|
+
.description("List all CLI-shipped blueprints with kind + complexity + deploy command. No backend call.")
|
|
2620
|
+
.option("--kind <kind>", "Filter by kind: workflow | agent | workforce")
|
|
2621
|
+
.option("--complexity <n>", "Filter by ladder rung: 0-6 (0=simplest, 6=workforce DAG)")
|
|
2622
|
+
.option("--tier <n>", "[LEGACY] Filter by tier: 1 or 3 (superseded by --kind)")
|
|
2623
|
+
.option("--fields <fields>", "Comma-separated fields (id,name,kind,complexity,tier,deploy_command,use_cases,description)")
|
|
2624
|
+
.action((opts) => {
|
|
2625
|
+
const kindFilter = opts.kind;
|
|
2626
|
+
if (kindFilter && !["workflow", "agent", "workforce"].includes(kindFilter)) {
|
|
2627
|
+
fail("invalid_option_value", `--kind must be workflow | agent | workforce; got ${kindFilter}`);
|
|
2628
|
+
}
|
|
2629
|
+
const complexityFilter = opts.complexity != null ? parseInt(opts.complexity, 10) : undefined;
|
|
2630
|
+
if (complexityFilter != null && (complexityFilter < 0 || complexityFilter > 6)) {
|
|
2631
|
+
fail("invalid_option_value", `--complexity must be 0-6; got ${opts.complexity}`);
|
|
2632
|
+
}
|
|
2633
|
+
const tierFilter = opts.tier ? parseInt(opts.tier, 10) : undefined;
|
|
2634
|
+
if (tierFilter != null && tierFilter !== 1 && tierFilter !== 3) {
|
|
2635
|
+
fail("invalid_option_value", `--tier must be 1 or 3; got ${opts.tier}`);
|
|
2636
|
+
}
|
|
2637
|
+
const all = listBlueprints().map((b) => ({
|
|
2638
|
+
id: b.id,
|
|
2639
|
+
name: b.name,
|
|
2640
|
+
kind: blueprintKind(b),
|
|
2641
|
+
complexity: blueprintComplexity(b),
|
|
2642
|
+
tier: b.tier ?? (blueprintKind(b) === "agent" ? 1 : 3),
|
|
2643
|
+
description: b.description,
|
|
2644
|
+
use_cases: b.useCases ?? null,
|
|
2645
|
+
agent_count: b.agents.length,
|
|
2646
|
+
node_count: b.workflowNodes?.length ?? 0,
|
|
2647
|
+
deploy_command: deployCommandForBlueprint(b),
|
|
2648
|
+
}));
|
|
2649
|
+
let filtered = all;
|
|
2650
|
+
if (kindFilter)
|
|
2651
|
+
filtered = filtered.filter((b) => b.kind === kindFilter);
|
|
2652
|
+
if (complexityFilter != null)
|
|
2653
|
+
filtered = filtered.filter((b) => b.complexity === complexityFilter);
|
|
2654
|
+
if (tierFilter != null)
|
|
2655
|
+
filtered = filtered.filter((b) => b.tier === tierFilter);
|
|
2656
|
+
printResult(applyFieldsFilter(filtered, opts.fields));
|
|
2657
|
+
});
|
|
2658
|
+
blueprintsCmd
|
|
2659
|
+
.command("get")
|
|
2660
|
+
.aliases(["show"])
|
|
2661
|
+
.description("Get full details of a specific blueprint (agents, plugins, starter tasks, or workflow nodes). Alias: `show`.")
|
|
2662
|
+
.option("--id <id>", "Blueprint id (e.g. research-assistant, dev-shop, summarize-url)")
|
|
2663
|
+
.option("--blueprint <id>", "Alias for --id")
|
|
2664
|
+
.action((opts) => {
|
|
2665
|
+
const id = opts.id ?? opts.blueprint;
|
|
2666
|
+
if (!id) {
|
|
2667
|
+
fail("missing_required_option", "Blueprint id is required.", "Pass --id <slug>. See `af blueprints list` for available ids.");
|
|
2668
|
+
}
|
|
2669
|
+
const b = getBlueprint(id);
|
|
2670
|
+
if (!b) {
|
|
2671
|
+
fail("invalid_option_value", `Unknown blueprint id: ${id}`, "Run `af blueprints list --json` for available ids.");
|
|
2672
|
+
}
|
|
2673
|
+
printResult({
|
|
2674
|
+
id: b.id,
|
|
2675
|
+
name: b.name,
|
|
2676
|
+
kind: blueprintKind(b),
|
|
2677
|
+
complexity: blueprintComplexity(b),
|
|
2678
|
+
tier: b.tier ?? (blueprintKind(b) === "agent" ? 1 : 3),
|
|
2679
|
+
description: b.description,
|
|
2680
|
+
goal: b.goal,
|
|
2681
|
+
use_cases: b.useCases ?? null,
|
|
2682
|
+
agents: b.agents.map((a) => ({
|
|
2683
|
+
role: a.role,
|
|
2684
|
+
title: a.title,
|
|
2685
|
+
description: a.description,
|
|
2686
|
+
optional: Boolean(a.optional),
|
|
2687
|
+
plugins: (a.plugins ?? []).map((p) => p.nodeTypeName),
|
|
2688
|
+
is_synthesizer: a.isSynthesizer ?? false,
|
|
2689
|
+
suggested_template: a.suggestedTemplate ?? null,
|
|
2690
|
+
})),
|
|
2691
|
+
workflow_nodes: b.workflowNodes?.map((n) => ({
|
|
2692
|
+
name: n.name,
|
|
2693
|
+
node_type: n.nodeType,
|
|
2694
|
+
title: n.title,
|
|
2695
|
+
description: n.description,
|
|
2696
|
+
})),
|
|
2697
|
+
workflow_input_schema: b.workflowInputSchema ?? null,
|
|
2698
|
+
starter_tasks: b.starterTasks,
|
|
2699
|
+
deploy_command: deployCommandForBlueprint(b),
|
|
2700
|
+
});
|
|
2701
|
+
});
|
|
2702
|
+
// ═════════════════════════════════════════════════════════════════
|
|
2703
|
+
// marketplace (unified catalog: agent / workflow / MAS templates)
|
|
2704
|
+
// ═════════════════════════════════════════════════════════════════
|
|
2705
|
+
// Complements blueprints: blueprints ship with the CLI (offline, versioned,
|
|
2706
|
+
// tier-aware), marketplace is the live backend catalog of user- and
|
|
2707
|
+
// admin-curated templates. `af marketplace try` reuses the existing
|
|
2708
|
+
// `templates duplicate` helpers so cloning works the same way.
|
|
2709
|
+
const marketplaceCmd = program
|
|
2710
|
+
.command("marketplace")
|
|
2711
|
+
.description("Browse the live AgenticFlow marketplace catalog (unified agent / workflow / MAS templates). " +
|
|
2712
|
+
"Complements blueprints (CLI-shipped, offline). Clone with `marketplace try` or " +
|
|
2713
|
+
"`templates duplicate <kind>`.");
|
|
2714
|
+
marketplaceCmd
|
|
2715
|
+
.command("list")
|
|
2716
|
+
.description("Browse marketplace items. Pageable. Filter by --type, --search, --featured, --free.")
|
|
2717
|
+
.option("--type <type>", "Filter: agent_template | workflow_template | mas_template")
|
|
2718
|
+
.option("--search <query>", "Server-side search query")
|
|
2719
|
+
.option("--featured", "Only featured items")
|
|
2720
|
+
.option("--free", "Only free items")
|
|
2721
|
+
.option("--limit <n>", "Limit", "50")
|
|
2722
|
+
.option("--offset <n>", "Offset", "0")
|
|
2723
|
+
.option("--fields <fields>", "Comma-separated fields to return (id,name,type,description,creator)")
|
|
2724
|
+
.action(async (opts) => {
|
|
2725
|
+
const client = buildClient(program.opts());
|
|
2726
|
+
const type = opts.type;
|
|
2727
|
+
if (type != null && !["agent_template", "workflow_template", "mas_template"].includes(type)) {
|
|
2728
|
+
fail("invalid_option_value", `--type must be agent_template, workflow_template, or mas_template; got "${type}".`);
|
|
2729
|
+
}
|
|
2730
|
+
await run(async () => {
|
|
2731
|
+
const data = (await client.marketplace.list({
|
|
2732
|
+
limit: parseOptionalInteger(opts.limit, "--limit", 1) ?? 50,
|
|
2733
|
+
offset: parseOptionalInteger(opts.offset, "--offset", 0) ?? 0,
|
|
2734
|
+
type: type,
|
|
2735
|
+
search: opts.search,
|
|
2736
|
+
featured: opts.featured ? true : undefined,
|
|
2737
|
+
isFree: opts.free ? true : undefined,
|
|
2738
|
+
}));
|
|
2739
|
+
// Strip embedded *_template_detail blobs on list — they're huge and
|
|
2740
|
+
// rarely needed for browsing. Fetch via `marketplace get` when needed.
|
|
2741
|
+
const items = data["items"] ?? [];
|
|
2742
|
+
const compact = items.map((it) => {
|
|
2743
|
+
const c = { ...it };
|
|
2744
|
+
delete c["agent_template_detail"];
|
|
2745
|
+
delete c["workflow_template_detail"];
|
|
2746
|
+
delete c["mas_template_detail"];
|
|
2747
|
+
return c;
|
|
2748
|
+
});
|
|
2749
|
+
// `--fields` filters per-item, not the top-level pagination envelope
|
|
2750
|
+
const filteredItems = applyFieldsFilter(compact, opts.fields);
|
|
2751
|
+
return { ...data, items: filteredItems };
|
|
2752
|
+
});
|
|
2753
|
+
});
|
|
2754
|
+
marketplaceCmd
|
|
2755
|
+
.command("get")
|
|
2756
|
+
.description("Get a marketplace item with full embedded template detail (ready to clone).")
|
|
2757
|
+
.requiredOption("--id <id>", "Marketplace item id (from `marketplace list`)")
|
|
2758
|
+
.action(async (opts) => {
|
|
2759
|
+
const client = buildClient(program.opts());
|
|
2760
|
+
await run(() => client.marketplace.get(opts.id));
|
|
2761
|
+
});
|
|
2762
|
+
marketplaceCmd
|
|
2763
|
+
.command("try")
|
|
2764
|
+
.description("Clone a marketplace item into your workspace. Auto-detects type " +
|
|
2765
|
+
"(agent_template → uses `templates duplicate agent`, workflow_template → " +
|
|
2766
|
+
"`templates duplicate workflow`, mas_template → `templates duplicate workforce`).")
|
|
2767
|
+
.requiredOption("--id <id>", "Marketplace item id")
|
|
2768
|
+
.option("--workspace-id <id>", "Target workspace ID")
|
|
2769
|
+
.option("--project-id <id>", "Target project ID")
|
|
2770
|
+
.option("--name-suffix <suffix>", "Suffix for duplicated resource name", " [from marketplace]")
|
|
2771
|
+
.option("--dry-run", "Build the create payload without writing")
|
|
2772
|
+
.action(async (opts) => {
|
|
2773
|
+
const client = buildClient(program.opts());
|
|
2774
|
+
let item;
|
|
2775
|
+
try {
|
|
2776
|
+
item = (await client.marketplace.get(opts.id));
|
|
2777
|
+
}
|
|
2778
|
+
catch (err) {
|
|
2779
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2780
|
+
fail("request_failed", `Could not load marketplace item "${opts.id}": ${message}`);
|
|
2781
|
+
return;
|
|
2782
|
+
}
|
|
2783
|
+
const type = item["type"];
|
|
2784
|
+
const projectId = resolveProjectId(opts.projectId);
|
|
2785
|
+
if (!projectId && !opts.dryRun) {
|
|
2786
|
+
fail("missing_project_id", "Project ID is required to clone marketplace items.", "Set AGENTICFLOW_PROJECT_ID or pass --project-id.");
|
|
2787
|
+
}
|
|
2788
|
+
const workspaceId = resolveWorkspaceId(opts.workspaceId);
|
|
2789
|
+
if (!workspaceId && !opts.dryRun) {
|
|
2790
|
+
fail("missing_workspace_id", "Workspace ID is required to clone marketplace items.", "Set AGENTICFLOW_WORKSPACE_ID or pass --workspace-id.");
|
|
2791
|
+
}
|
|
2792
|
+
const nameSuffix = opts.nameSuffix ?? " [from marketplace]";
|
|
2793
|
+
if (type === "agent_template") {
|
|
2794
|
+
const templateId = item["agent_template_id"];
|
|
2795
|
+
if (!templateId)
|
|
2796
|
+
fail("template_not_found", "Marketplace agent item has no agent_template_id.");
|
|
2797
|
+
// Reuse the agent-duplicate flow end-to-end: fetch the real agent
|
|
2798
|
+
// template by id, materialize tool workflows, create the agent.
|
|
2799
|
+
let agentTemplate;
|
|
2800
|
+
try {
|
|
2801
|
+
agentTemplate = (await client.sdk.get(`/v1/agent-templates/${templateId}`)).data;
|
|
2802
|
+
}
|
|
2803
|
+
catch (err) {
|
|
2804
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2805
|
+
fail("request_failed", `Unable to fetch agent template ${templateId}: ${message}`);
|
|
2806
|
+
return;
|
|
2807
|
+
}
|
|
2808
|
+
const toolRefs = extractAgentTemplateWorkflowReferences(agentTemplate);
|
|
2809
|
+
const duplicatedTools = [];
|
|
2810
|
+
if (opts.dryRun) {
|
|
2811
|
+
let preview;
|
|
2812
|
+
try {
|
|
2813
|
+
preview = buildAgentCreatePayloadFromTemplate(agentTemplate, projectId, duplicatedTools, nameSuffix);
|
|
2814
|
+
}
|
|
2815
|
+
catch (err) {
|
|
2816
|
+
fail("template_payload_invalid", err instanceof Error ? err.message : String(err));
|
|
2817
|
+
return;
|
|
2818
|
+
}
|
|
2819
|
+
printResult({
|
|
2820
|
+
schema: "agenticflow.marketplace.try.v1",
|
|
2821
|
+
marketplace_item: opts.id,
|
|
2822
|
+
type,
|
|
2823
|
+
dry_run: true,
|
|
2824
|
+
agent_template_id: templateId,
|
|
2825
|
+
tool_workflow_count: toolRefs.length,
|
|
2826
|
+
agent_create_payload: preview,
|
|
2827
|
+
});
|
|
2828
|
+
return;
|
|
2829
|
+
}
|
|
2830
|
+
// Materialize tool workflows first
|
|
2831
|
+
for (const ref of toolRefs) {
|
|
2832
|
+
try {
|
|
2833
|
+
const wfTemplate = (await client.sdk.get(`/v1/workflow_templates/${ref.workflowTemplateId}`)).data;
|
|
2834
|
+
const wfPayload = buildWorkflowCreatePayloadFromTemplate(wfTemplate, projectId, " [from marketplace — Tool]");
|
|
2835
|
+
ensureLocalValidation("workflow.create", validateWorkflowCreatePayload(wfPayload));
|
|
2836
|
+
const createdWf = await client.workflows.create(wfPayload, workspaceId);
|
|
2837
|
+
const createdWfId = createdWf["id"];
|
|
2838
|
+
duplicatedTools.push({
|
|
2839
|
+
workflowTemplateId: ref.workflowTemplateId,
|
|
2840
|
+
workflowId: createdWfId,
|
|
2841
|
+
runBehavior: ref.runBehavior,
|
|
2842
|
+
description: ref.description,
|
|
2843
|
+
timeout: ref.timeout,
|
|
2844
|
+
inputConfig: ref.inputConfig,
|
|
2845
|
+
});
|
|
2846
|
+
}
|
|
2847
|
+
catch (err) {
|
|
2848
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2849
|
+
fail("request_failed", `Failed to clone tool workflow ${ref.workflowTemplateId}: ${message}`, "Clone fewer tools via 'templates duplicate agent --skip-missing-tools' or contact the template creator.");
|
|
2850
|
+
return;
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
let agentPayload;
|
|
2854
|
+
try {
|
|
2855
|
+
agentPayload = buildAgentCreatePayloadFromTemplate(agentTemplate, projectId, duplicatedTools, nameSuffix);
|
|
2856
|
+
}
|
|
2857
|
+
catch (err) {
|
|
2858
|
+
fail("template_payload_invalid", err instanceof Error ? err.message : String(err));
|
|
2859
|
+
return;
|
|
2860
|
+
}
|
|
2861
|
+
ensureLocalValidation("agent.create", validateAgentCreatePayload(agentPayload));
|
|
2862
|
+
await run(async () => {
|
|
2863
|
+
const created = (await client.agents.create(agentPayload));
|
|
2864
|
+
return {
|
|
2865
|
+
schema: "agenticflow.marketplace.try.v1",
|
|
2866
|
+
marketplace_item: opts.id,
|
|
2867
|
+
type,
|
|
2868
|
+
dry_run: false,
|
|
2869
|
+
agent_id: created["id"],
|
|
2870
|
+
name: created["name"],
|
|
2871
|
+
tool_workflow_count: duplicatedTools.length,
|
|
2872
|
+
_links: {
|
|
2873
|
+
agent: webUrl("agent", {
|
|
2874
|
+
workspaceId: client.sdk.workspaceId,
|
|
2875
|
+
agentId: created["id"],
|
|
2876
|
+
}),
|
|
2877
|
+
},
|
|
2878
|
+
};
|
|
2879
|
+
});
|
|
2880
|
+
return;
|
|
2881
|
+
}
|
|
2882
|
+
if (type === "workflow_template") {
|
|
2883
|
+
const templateId = item["workflow_template_id"];
|
|
2884
|
+
if (!templateId)
|
|
2885
|
+
fail("template_not_found", "Marketplace workflow item has no workflow_template_id.");
|
|
2886
|
+
let wfTemplate;
|
|
2887
|
+
try {
|
|
2888
|
+
wfTemplate = (await client.sdk.get(`/v1/workflow_templates/${templateId}`)).data;
|
|
2889
|
+
}
|
|
2890
|
+
catch (err) {
|
|
2891
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2892
|
+
fail("request_failed", `Unable to fetch workflow template ${templateId}: ${message}`);
|
|
2893
|
+
return;
|
|
2894
|
+
}
|
|
2895
|
+
let wfPayload;
|
|
2896
|
+
try {
|
|
2897
|
+
wfPayload = buildWorkflowCreatePayloadFromTemplate(wfTemplate, projectId, nameSuffix);
|
|
2898
|
+
}
|
|
2899
|
+
catch (err) {
|
|
2900
|
+
fail("template_payload_invalid", err instanceof Error ? err.message : String(err));
|
|
2901
|
+
return;
|
|
2902
|
+
}
|
|
2903
|
+
ensureLocalValidation("workflow.create", validateWorkflowCreatePayload(wfPayload));
|
|
2904
|
+
if (opts.dryRun) {
|
|
2905
|
+
printResult({
|
|
2906
|
+
schema: "agenticflow.marketplace.try.v1",
|
|
2907
|
+
marketplace_item: opts.id,
|
|
2908
|
+
type,
|
|
2909
|
+
dry_run: true,
|
|
2910
|
+
workflow_template_id: templateId,
|
|
2911
|
+
workflow_create_payload: wfPayload,
|
|
2912
|
+
});
|
|
2913
|
+
return;
|
|
2914
|
+
}
|
|
2915
|
+
await run(async () => {
|
|
2916
|
+
const created = (await client.workflows.create(wfPayload, workspaceId));
|
|
2917
|
+
return {
|
|
2918
|
+
schema: "agenticflow.marketplace.try.v1",
|
|
2919
|
+
marketplace_item: opts.id,
|
|
2920
|
+
type,
|
|
2921
|
+
dry_run: false,
|
|
2922
|
+
workflow_id: created["id"],
|
|
2923
|
+
name: created["name"],
|
|
2924
|
+
};
|
|
2925
|
+
});
|
|
2926
|
+
return;
|
|
2927
|
+
}
|
|
2928
|
+
if (type === "mas_template") {
|
|
2929
|
+
const detail = item["mas_template_detail"];
|
|
2930
|
+
if (!detail) {
|
|
2931
|
+
fail("template_not_found", "Marketplace MAS item has no mas_template_detail — the item may have been unlisted.");
|
|
2932
|
+
return;
|
|
2933
|
+
}
|
|
2934
|
+
const srcName = detail["name"] ?? "Workforce";
|
|
2935
|
+
const targetName = `${srcName}${nameSuffix}`;
|
|
2936
|
+
const nodes = Array.isArray(detail["nodes"]) ? detail["nodes"] : [];
|
|
2937
|
+
const edges = Array.isArray(detail["edges"]) ? detail["edges"] : [];
|
|
2938
|
+
// Detect cross-workspace agent_id references
|
|
2939
|
+
const agentNodeIds = [];
|
|
2940
|
+
for (const n of nodes) {
|
|
2941
|
+
if (n && typeof n === "object") {
|
|
2942
|
+
const node = n;
|
|
2943
|
+
if (node["type"] === "agent" || node["type"] === "agent_team_member") {
|
|
2944
|
+
const input = node["input"] ?? {};
|
|
2945
|
+
if (typeof input["agent_id"] === "string")
|
|
2946
|
+
agentNodeIds.push(input["agent_id"]);
|
|
2947
|
+
}
|
|
2948
|
+
}
|
|
2949
|
+
}
|
|
2950
|
+
if (opts.dryRun) {
|
|
2951
|
+
printResult({
|
|
2952
|
+
schema: "agenticflow.marketplace.try.v1",
|
|
2953
|
+
marketplace_item: opts.id,
|
|
2954
|
+
type,
|
|
2955
|
+
dry_run: true,
|
|
2956
|
+
workforce: { name: targetName, description: detail["description"] ?? null },
|
|
2957
|
+
node_count: nodes.length,
|
|
2958
|
+
edge_count: edges.length,
|
|
2959
|
+
referenced_agent_ids: agentNodeIds,
|
|
2960
|
+
warnings: agentNodeIds.length
|
|
2961
|
+
? [
|
|
2962
|
+
`Template references ${agentNodeIds.length} agent_id(s) from the source workspace.`,
|
|
2963
|
+
]
|
|
2964
|
+
: [],
|
|
2965
|
+
});
|
|
2966
|
+
return;
|
|
2967
|
+
}
|
|
2968
|
+
await run(async () => {
|
|
2969
|
+
const created = (await client.workforces.create({ name: targetName, description: detail["description"] ?? null }, { workspaceId: workspaceId, projectId: projectId }));
|
|
2970
|
+
const workforceId = created["id"];
|
|
2971
|
+
if (!workforceId)
|
|
2972
|
+
throw new Error("Workforce create did not return an id.");
|
|
2973
|
+
await client.workforces.putSchema(workforceId, {
|
|
2974
|
+
nodes: nodes,
|
|
2975
|
+
edges: edges,
|
|
2976
|
+
}, { workspaceId: workspaceId });
|
|
2977
|
+
return {
|
|
2978
|
+
schema: "agenticflow.marketplace.try.v1",
|
|
2979
|
+
marketplace_item: opts.id,
|
|
2980
|
+
type,
|
|
2981
|
+
dry_run: false,
|
|
2982
|
+
workforce_id: workforceId,
|
|
2983
|
+
name: targetName,
|
|
2984
|
+
node_count: nodes.length,
|
|
2985
|
+
edge_count: edges.length,
|
|
2986
|
+
warnings: agentNodeIds.length
|
|
2987
|
+
? [
|
|
2988
|
+
`Workforce references ${agentNodeIds.length} agent_id(s) from the source workspace. Run it to verify — if missing, duplicate source agents first.`,
|
|
2989
|
+
]
|
|
2990
|
+
: [],
|
|
2991
|
+
};
|
|
2992
|
+
});
|
|
2993
|
+
return;
|
|
2994
|
+
}
|
|
2995
|
+
fail("unsupported_type", `Unknown marketplace item type: ${type}`, "Supported: agent_template, workflow_template, mas_template.");
|
|
2996
|
+
});
|
|
2997
|
+
// ═════════════════════════════════════════════════════════════════
|
|
2431
2998
|
// pack (git-native pack control plane)
|
|
2432
2999
|
// ═════════════════════════════════════════════════════════════════
|
|
2433
3000
|
const packCmd = program
|
|
2434
3001
|
.command("pack")
|
|
2435
|
-
.description("Pack lifecycle
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
3002
|
+
.description("[DEPRECATED v1.7.0 — sunset 2026-10-14] Pack lifecycle. Use `af workforce init --blueprint <id>` instead.");
|
|
3003
|
+
// Hide from default `--help` unless user passes AF_SHOW_DEPRECATED=1 or --help-all.
|
|
3004
|
+
// PDCA rounds 1+2 (2026-04-14) flagged deprecated-command blurbs as top-level help noise.
|
|
3005
|
+
if (!(process.env["AF_SHOW_DEPRECATED"] === "1")) {
|
|
3006
|
+
packCmd._hidden = true;
|
|
3007
|
+
}
|
|
2440
3008
|
// Single deprecation warning per subcommand per session (dedup in emitDeprecation).
|
|
2441
3009
|
// Mirrors the paperclip hook pattern.
|
|
2442
3010
|
packCmd.hook("preAction", (thisCommand, actionCommand) => {
|
|
@@ -3206,6 +3774,100 @@ export function createProgram() {
|
|
|
3206
3774
|
const workflowCmd = program
|
|
3207
3775
|
.command("workflow")
|
|
3208
3776
|
.description("Workflow management commands.");
|
|
3777
|
+
workflowCmd
|
|
3778
|
+
.command("init")
|
|
3779
|
+
.description("Deploy a workflow blueprint — rung 0-2 on the composition ladder. " +
|
|
3780
|
+
"Deterministic multi-node flow (llm/web_retrieval/api_call/string_to_json). " +
|
|
3781
|
+
"Auto-discovers an LLM-provider connection in your workspace if any of the " +
|
|
3782
|
+
"nodes need one.")
|
|
3783
|
+
.requiredOption("--blueprint <slug>", "Workflow blueprint id (e.g. llm-hello, llm-chain, summarize-url, api-summary). See `af blueprints list --kind workflow --json`.")
|
|
3784
|
+
.option("--name <name>", "Workflow name (defaults to blueprint name)")
|
|
3785
|
+
.option("--project-id <id>", "Project ID (defaults to env / auth config)")
|
|
3786
|
+
.option("--workspace-id <id>", "Workspace ID (defaults to env / auth config)")
|
|
3787
|
+
.option("--llm-connection-id <id>", "Override auto-discovered LLM connection id")
|
|
3788
|
+
.option("--dry-run", "Show the workflow-create payload without writing")
|
|
3789
|
+
.action(async (opts) => {
|
|
3790
|
+
const b = getBlueprint(opts.blueprint);
|
|
3791
|
+
if (!b) {
|
|
3792
|
+
fail("invalid_option_value", `Unknown blueprint id: ${opts.blueprint}`, "Run `af blueprints list --kind workflow --json`.");
|
|
3793
|
+
}
|
|
3794
|
+
if (blueprintKind(b) !== "workflow") {
|
|
3795
|
+
fail("invalid_option_value", `Blueprint "${opts.blueprint}" is kind "${blueprintKind(b)}", not "workflow".`, `Deploy with: af ${blueprintKind(b)} init --blueprint ${opts.blueprint}`);
|
|
3796
|
+
}
|
|
3797
|
+
const { workflowBlueprintToPayload, findWorkspaceLLMConnection } = await import("./blueprint-to-workflow.js");
|
|
3798
|
+
const initClient = buildClient(program.opts());
|
|
3799
|
+
const projectId = opts.projectId ??
|
|
3800
|
+
program.opts().projectId ??
|
|
3801
|
+
process.env["AGENTICFLOW_PROJECT_ID"] ??
|
|
3802
|
+
initClient.sdk.projectId;
|
|
3803
|
+
if (!projectId && !opts.dryRun) {
|
|
3804
|
+
fail("missing_project_id", "Project ID is required to create a workflow.", "Pass --project-id <id> or set AGENTICFLOW_PROJECT_ID.");
|
|
3805
|
+
}
|
|
3806
|
+
// Auto-discover an LLM-provider connection if the blueprint needs one.
|
|
3807
|
+
let llmConnectionId = opts.llmConnectionId ?? null;
|
|
3808
|
+
const needsLLM = (b.workflowNodes ?? []).some((n) => n.nodeType === "llm");
|
|
3809
|
+
if (needsLLM && !llmConnectionId && !opts.dryRun) {
|
|
3810
|
+
try {
|
|
3811
|
+
const conns = (await initClient.connections.list());
|
|
3812
|
+
llmConnectionId = findWorkspaceLLMConnection(conns);
|
|
3813
|
+
}
|
|
3814
|
+
catch {
|
|
3815
|
+
// Tolerate list failure; fall through with llmConnectionId=null so the
|
|
3816
|
+
// warning fires below.
|
|
3817
|
+
}
|
|
3818
|
+
}
|
|
3819
|
+
let translated;
|
|
3820
|
+
try {
|
|
3821
|
+
translated = workflowBlueprintToPayload(b, {
|
|
3822
|
+
projectId: projectId ?? "DRY_RUN_PROJECT_ID",
|
|
3823
|
+
workflowName: opts.name,
|
|
3824
|
+
llmConnectionId,
|
|
3825
|
+
});
|
|
3826
|
+
}
|
|
3827
|
+
catch (err) {
|
|
3828
|
+
fail("invalid_blueprint", err instanceof Error ? err.message : String(err));
|
|
3829
|
+
return;
|
|
3830
|
+
}
|
|
3831
|
+
if (translated.missing_connections.length > 0 && !opts.dryRun) {
|
|
3832
|
+
fail("missing_connection", `Workflow "${b.id}" requires a connection that wasn't found in the workspace: ${translated.missing_connections.join(", ")}.`, `Create one via \`af connections create --body ...\` or in the UI, then re-run. Alternatively pass --llm-connection-id <id> to use a specific existing connection.`);
|
|
3833
|
+
}
|
|
3834
|
+
if (opts.dryRun) {
|
|
3835
|
+
printResult({
|
|
3836
|
+
schema: "agenticflow.dry_run.v1",
|
|
3837
|
+
valid: true,
|
|
3838
|
+
target: "workflow.init",
|
|
3839
|
+
blueprint: b.id,
|
|
3840
|
+
kind: "workflow",
|
|
3841
|
+
complexity: blueprintComplexity(b),
|
|
3842
|
+
workflow_name: translated.payload.name,
|
|
3843
|
+
node_count: translated.payload.nodes.length,
|
|
3844
|
+
warnings: translated.warnings,
|
|
3845
|
+
missing_connections: translated.missing_connections,
|
|
3846
|
+
payload: translated.payload,
|
|
3847
|
+
});
|
|
3848
|
+
return;
|
|
3849
|
+
}
|
|
3850
|
+
await run(async () => {
|
|
3851
|
+
const created = (await initClient.workflows.create(translated.payload, opts.workspaceId));
|
|
3852
|
+
const workflowId = created["id"];
|
|
3853
|
+
if (!workflowId)
|
|
3854
|
+
throw new Error("Workflow create did not return an id.");
|
|
3855
|
+
return {
|
|
3856
|
+
schema: "agenticflow.workflow.init.v1",
|
|
3857
|
+
workflow_id: workflowId,
|
|
3858
|
+
blueprint: b.id,
|
|
3859
|
+
kind: "workflow",
|
|
3860
|
+
complexity: blueprintComplexity(b),
|
|
3861
|
+
name: created["name"],
|
|
3862
|
+
node_count: translated.payload.nodes.length,
|
|
3863
|
+
warnings: translated.warnings,
|
|
3864
|
+
next_steps: [
|
|
3865
|
+
`af workflow get --workflow-id ${workflowId} --json # inspect`,
|
|
3866
|
+
...translated.suggested_next_steps.map((s) => s.replace(/<id>/g, workflowId)),
|
|
3867
|
+
],
|
|
3868
|
+
};
|
|
3869
|
+
});
|
|
3870
|
+
});
|
|
3209
3871
|
workflowCmd
|
|
3210
3872
|
.command("list")
|
|
3211
3873
|
.description("List workflows.")
|
|
@@ -3283,13 +3945,17 @@ export function createProgram() {
|
|
|
3283
3945
|
.description("Run a workflow.")
|
|
3284
3946
|
.requiredOption("--workflow-id <id>", "Workflow ID")
|
|
3285
3947
|
.option("--input <input>", "JSON input (inline or @file)")
|
|
3948
|
+
.option("--body <input>", "Alias for --input (ergonomic consistency with other `<resource> run` commands)")
|
|
3286
3949
|
.option("--auto-fix-connections", "Automatically prompt to fix missing connections")
|
|
3287
3950
|
.action(async (opts) => {
|
|
3288
3951
|
const client = buildClient(program.opts());
|
|
3289
3952
|
const token = resolveToken(program.opts());
|
|
3290
3953
|
const body = { workflow_id: opts.workflowId };
|
|
3291
|
-
|
|
3292
|
-
|
|
3954
|
+
// Accept --input or --body (PDCA showed users reach for --body by analogy
|
|
3955
|
+
// with agent create/update). Prefer --input if both given.
|
|
3956
|
+
const inputArg = opts.input ?? opts.body;
|
|
3957
|
+
if (inputArg)
|
|
3958
|
+
body["input"] = loadJsonPayload(inputArg);
|
|
3293
3959
|
ensureLocalValidation("workflow.run", validateWorkflowRunPayload(body));
|
|
3294
3960
|
const executeRun = () => token
|
|
3295
3961
|
? client.workflows.run(body)
|
|
@@ -3424,16 +4090,21 @@ export function createProgram() {
|
|
|
3424
4090
|
});
|
|
3425
4091
|
workflowCmd
|
|
3426
4092
|
.command("run-status")
|
|
3427
|
-
.description("Get workflow run status.")
|
|
3428
|
-
.
|
|
4093
|
+
.description("Get workflow run status. Accepts --run-id (alias) or --workflow-run-id (canonical).")
|
|
4094
|
+
.option("--workflow-run-id <id>", "Workflow run ID (canonical)")
|
|
4095
|
+
.option("--run-id <id>", "Alias for --workflow-run-id (returned as `id` from `workflow run`)")
|
|
3429
4096
|
.action(async (opts) => {
|
|
3430
4097
|
const client = buildClient(program.opts());
|
|
3431
4098
|
const token = resolveToken(program.opts());
|
|
4099
|
+
const runId = opts.workflowRunId ?? opts.runId;
|
|
4100
|
+
if (!runId) {
|
|
4101
|
+
fail("missing_required_option", "Run ID is required.", "Pass --workflow-run-id <id> or --run-id <id> (alias). The id is returned as `id` from `af workflow run`.");
|
|
4102
|
+
}
|
|
3432
4103
|
if (token) {
|
|
3433
|
-
await run(() => client.workflows.getRun(
|
|
4104
|
+
await run(() => client.workflows.getRun(runId));
|
|
3434
4105
|
}
|
|
3435
4106
|
else {
|
|
3436
|
-
await run(() => client.workflows.getRunAnonymous(
|
|
4107
|
+
await run(() => client.workflows.getRunAnonymous(runId));
|
|
3437
4108
|
}
|
|
3438
4109
|
});
|
|
3439
4110
|
workflowCmd
|
|
@@ -3579,16 +4250,22 @@ export function createProgram() {
|
|
|
3579
4250
|
agentCmd
|
|
3580
4251
|
.command("get")
|
|
3581
4252
|
.description("Get an agent by ID.")
|
|
3582
|
-
.
|
|
4253
|
+
.option("--agent-id <id>", "Agent ID (canonical)")
|
|
4254
|
+
.option("--id <id>", "Agent ID (alias — consistent with marketplace/mcp-clients get)")
|
|
4255
|
+
.option("--fields <fields>", "Comma-separated fields to return (e.g. id,name,model,plugins)")
|
|
3583
4256
|
.action(async (opts) => {
|
|
3584
4257
|
const client = buildClient(program.opts());
|
|
3585
4258
|
const token = resolveToken(program.opts());
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
else {
|
|
3590
|
-
await run(() => client.agents.getAnonymous(opts.agentId));
|
|
4259
|
+
const agentId = opts.agentId ?? opts.id;
|
|
4260
|
+
if (!agentId) {
|
|
4261
|
+
fail("missing_required_option", "Agent ID is required.", "Pass --agent-id <id> (canonical) or --id <id> (alias).");
|
|
3591
4262
|
}
|
|
4263
|
+
await run(async () => {
|
|
4264
|
+
const data = token
|
|
4265
|
+
? await client.agents.get(agentId)
|
|
4266
|
+
: await client.agents.getAnonymous(agentId);
|
|
4267
|
+
return applyFieldsFilter(data, opts.fields);
|
|
4268
|
+
});
|
|
3592
4269
|
});
|
|
3593
4270
|
agentCmd
|
|
3594
4271
|
.command("create")
|
|
@@ -3727,17 +4404,36 @@ export function createProgram() {
|
|
|
3727
4404
|
if (result.status === "timeout") {
|
|
3728
4405
|
fail("agent_run_timeout", `Agent did not respond within ${opts.timeout}s`, `Thread: ${result.threadId}. Check with: af agent-threads messages --thread-id ${result.threadId}`);
|
|
3729
4406
|
}
|
|
4407
|
+
// PDCA round 2 (2026-04-14): the backend sometimes returns
|
|
4408
|
+
// `status: "completed"` with an empty `response` when the agent
|
|
4409
|
+
// exhausts its recursion_limit in a tool loop without producing a
|
|
4410
|
+
// final assistant message. Silent success looks identical to real
|
|
4411
|
+
// success for a non-interactive caller — surface it explicitly.
|
|
4412
|
+
const responseText = typeof result.response === "string" ? result.response : "";
|
|
4413
|
+
const isEmpty = result.status === "completed" && responseText.trim().length === 0;
|
|
3730
4414
|
printResult({
|
|
3731
4415
|
schema: "agenticflow.agent.run.v1",
|
|
3732
|
-
status: result.status,
|
|
4416
|
+
status: isEmpty ? "completed_empty" : result.status,
|
|
3733
4417
|
agent_id: opts.agentId,
|
|
3734
4418
|
thread_id: result.threadId,
|
|
3735
4419
|
response: result.response,
|
|
4420
|
+
...(isEmpty
|
|
4421
|
+
? {
|
|
4422
|
+
warning: "Agent returned no text output despite status=completed. This usually means " +
|
|
4423
|
+
"the agent exhausted its recursion_limit in a tool loop without producing a final message. " +
|
|
4424
|
+
`Inspect: af agent-threads messages --thread-id ${result.threadId} --json. ` +
|
|
4425
|
+
"Fixes: refine the prompt to converge faster, or raise recursion_limit on the agent via " +
|
|
4426
|
+
"`af agent update --patch --body '{\"recursion_limit\": 50}'`.",
|
|
4427
|
+
}
|
|
4428
|
+
: {}),
|
|
3736
4429
|
_links: {
|
|
3737
4430
|
agent: webUrl("agent", { workspaceId: client.sdk.workspaceId, agentId: opts.agentId }),
|
|
3738
4431
|
thread: webUrl("thread", { workspaceId: client.sdk.workspaceId, agentId: opts.agentId, threadId: result.threadId }),
|
|
3739
4432
|
},
|
|
3740
4433
|
});
|
|
4434
|
+
// Non-zero exit for empty completion so `&&`-chained scripts halt.
|
|
4435
|
+
if (isEmpty)
|
|
4436
|
+
process.exit(2);
|
|
3741
4437
|
}
|
|
3742
4438
|
catch (err) {
|
|
3743
4439
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -3759,6 +4455,78 @@ export function createProgram() {
|
|
|
3759
4455
|
recursion_limit: 25,
|
|
3760
4456
|
});
|
|
3761
4457
|
});
|
|
4458
|
+
agentCmd
|
|
4459
|
+
.command("init")
|
|
4460
|
+
.description("Deploy a Tier 1 blueprint as a single agent with built-in AgenticFlow plugins. " +
|
|
4461
|
+
"Works in any workspace (no external connections, no MAS/workforce feature). " +
|
|
4462
|
+
"For Tier 3 (multi-agent) blueprints, use `af workforce init --blueprint <id>` instead.")
|
|
4463
|
+
.requiredOption("--blueprint <slug>", "Tier 1 blueprint id (research-assistant, content-creator, api-helper). See `af bootstrap --json > blueprints[]`.")
|
|
4464
|
+
.option("--name <name>", "Agent name (defaults to blueprint name)")
|
|
4465
|
+
.option("--project-id <id>", "Project ID (defaults to env / auth config)")
|
|
4466
|
+
.option("--model <model>", "Model for the agent (default: agenticflow/gpt-4o-mini — reliably calls tools. gemini-2.0-flash refuses on 'latest X' prompts)", "agenticflow/gpt-4o-mini")
|
|
4467
|
+
.option("--dry-run", "Show the agent-create payload without writing")
|
|
4468
|
+
.action(async (opts) => {
|
|
4469
|
+
const blueprint = getBlueprint(opts.blueprint);
|
|
4470
|
+
if (!blueprint) {
|
|
4471
|
+
fail("invalid_option_value", `Unknown blueprint id: ${opts.blueprint}`, "Run `af blueprints list --json` or `af bootstrap --json` to see available blueprints (look for `tier: 1` entries).");
|
|
4472
|
+
}
|
|
4473
|
+
if (blueprint.tier !== 1) {
|
|
4474
|
+
fail("invalid_option_value", `Blueprint "${opts.blueprint}" is tier ${blueprint.tier ?? 3}, not tier 1.`, `Tier ${blueprint.tier ?? 3} blueprints deploy via: af workforce init --blueprint ${opts.blueprint}`);
|
|
4475
|
+
}
|
|
4476
|
+
const { tier1BlueprintToAgentPayload } = await import("./blueprint-to-agent.js");
|
|
4477
|
+
// Resolve project_id for agent create (fullClient carries auth-derived defaults)
|
|
4478
|
+
const initClient = buildClient(program.opts());
|
|
4479
|
+
const projectId = opts.projectId ??
|
|
4480
|
+
program.opts().projectId ??
|
|
4481
|
+
process.env["AGENTICFLOW_PROJECT_ID"] ??
|
|
4482
|
+
initClient.sdk.projectId;
|
|
4483
|
+
if (!projectId) {
|
|
4484
|
+
fail("missing_required_option", "Tier 1 agent init requires a project_id.", "Pass --project-id <id>, set AGENTICFLOW_PROJECT_ID, or run `af bootstrap --json` and copy `auth.project_id`.");
|
|
4485
|
+
}
|
|
4486
|
+
let payload;
|
|
4487
|
+
try {
|
|
4488
|
+
payload = tier1BlueprintToAgentPayload(blueprint, {
|
|
4489
|
+
projectId: projectId,
|
|
4490
|
+
agentName: opts.name,
|
|
4491
|
+
model: opts.model,
|
|
4492
|
+
});
|
|
4493
|
+
}
|
|
4494
|
+
catch (err) {
|
|
4495
|
+
fail("invalid_blueprint", err instanceof Error ? err.message : String(err));
|
|
4496
|
+
return; // unreachable, satisfies control flow
|
|
4497
|
+
}
|
|
4498
|
+
if (opts.dryRun) {
|
|
4499
|
+
printResult({
|
|
4500
|
+
schema: "agenticflow.dry_run.v1",
|
|
4501
|
+
valid: true,
|
|
4502
|
+
target: "agent.init",
|
|
4503
|
+
blueprint: blueprint.id,
|
|
4504
|
+
tier: 1,
|
|
4505
|
+
agent: payload.body,
|
|
4506
|
+
plugin_count: payload.body["plugins"].length,
|
|
4507
|
+
});
|
|
4508
|
+
return;
|
|
4509
|
+
}
|
|
4510
|
+
await run(async () => {
|
|
4511
|
+
const created = (await initClient.agents.create(payload.body));
|
|
4512
|
+
const agentId = created["id"];
|
|
4513
|
+
if (!agentId)
|
|
4514
|
+
throw new Error("Agent create did not return an id.");
|
|
4515
|
+
return {
|
|
4516
|
+
schema: "agenticflow.agent.init.v1",
|
|
4517
|
+
agent_id: agentId,
|
|
4518
|
+
blueprint: blueprint.id,
|
|
4519
|
+
tier: 1,
|
|
4520
|
+
name: created["name"],
|
|
4521
|
+
plugin_count: payload.body["plugins"].length,
|
|
4522
|
+
plugins: payload.body["plugins"].map((p) => p["plugin_id"]),
|
|
4523
|
+
_links: {
|
|
4524
|
+
agent: webUrl("agent", { workspaceId: initClient.sdk.workspaceId, agentId }),
|
|
4525
|
+
},
|
|
4526
|
+
next_steps: payload.suggested_next_steps.map((s) => s.replace(/<id>/g, agentId)),
|
|
4527
|
+
};
|
|
4528
|
+
});
|
|
4529
|
+
});
|
|
3762
4530
|
agentCmd
|
|
3763
4531
|
.command("upload-file")
|
|
3764
4532
|
.description("Upload a file for an agent.")
|
|
@@ -4513,7 +5281,26 @@ export function createProgram() {
|
|
|
4513
5281
|
workspaceId: opts.workspaceId,
|
|
4514
5282
|
});
|
|
4515
5283
|
if (!response.ok) {
|
|
4516
|
-
|
|
5284
|
+
// PDCA 2026-04-14 confirmed the authenticated workforce-run path
|
|
5285
|
+
// rejects API-key auth with 400 "Failed to retrieve user info for
|
|
5286
|
+
// user_id: api_key:...". Detect and point users at the known-
|
|
5287
|
+
// working fallback (publish + public run URL) instead of failing
|
|
5288
|
+
// opaquely.
|
|
5289
|
+
let bodyText = "";
|
|
5290
|
+
try {
|
|
5291
|
+
bodyText = await response.text();
|
|
5292
|
+
}
|
|
5293
|
+
catch { /* ignore */ }
|
|
5294
|
+
const isApiKeyUserLookup = response.status === 400 &&
|
|
5295
|
+
/api_key:/i.test(bodyText) &&
|
|
5296
|
+
/user info|user_id/i.test(bodyText);
|
|
5297
|
+
if (isApiKeyUserLookup) {
|
|
5298
|
+
fail("workforce_run_api_key_unsupported", `Authenticated workforce run is not currently supported with API-key auth (backend 400 on user lookup). Body: ${bodyText.slice(0, 200)}`, `Workaround: publish the workforce and call the public endpoint.\n` +
|
|
5299
|
+
` 1. af workforce publish --workforce-id ${opts.workforceId} --json # returns public_key\n` +
|
|
5300
|
+
` 2. curl -X POST https://api.agenticflow.ai/v1/workforce/public/<public_key>/run -H 'Content-Type: application/json' -d '${JSON.stringify(triggerBody)}'\n` +
|
|
5301
|
+
` 3. Poll: af workforce runs list --workforce-id ${opts.workforceId} --json`, { status_code: response.status, body: bodyText.slice(0, 500) });
|
|
5302
|
+
}
|
|
5303
|
+
fail("request_failed", `Workforce run failed with status ${response.status}`, undefined, { status_code: response.status, body: bodyText.slice(0, 500) });
|
|
4517
5304
|
}
|
|
4518
5305
|
const reader = response.body?.getReader();
|
|
4519
5306
|
if (!reader) {
|
|
@@ -4998,8 +5785,11 @@ export function createProgram() {
|
|
|
4998
5785
|
};
|
|
4999
5786
|
const paperclipCmd = program
|
|
5000
5787
|
.command("paperclip")
|
|
5001
|
-
.description("
|
|
5002
|
-
|
|
5788
|
+
.description("[DEPRECATED — sunset 2026-10-14] Paperclip publish/run. Use `af workforce *` instead. See: af playbook migrate-from-paperclip.");
|
|
5789
|
+
// Hide from default `--help` — PDCA 2026-04-14 flagged it as noise.
|
|
5790
|
+
if (!(process.env["AF_SHOW_DEPRECATED"] === "1")) {
|
|
5791
|
+
paperclipCmd._hidden = true;
|
|
5792
|
+
}
|
|
5003
5793
|
// Emit a single deprecation warning per subcommand invocation (session-scoped
|
|
5004
5794
|
// dedup in emitDeprecation). This fires BEFORE the action runs, so even --help
|
|
5005
5795
|
// users see the pointer to `af workforce`.
|
|
@@ -5679,7 +6469,11 @@ export function createProgram() {
|
|
|
5679
6469
|
// ============================================================================
|
|
5680
6470
|
const companyCmd = program
|
|
5681
6471
|
.command("company")
|
|
5682
|
-
.description("Workspace agent
|
|
6472
|
+
.description("[LEGACY] Workspace agent config export/import. Consider `af workforce *` for newer work.");
|
|
6473
|
+
// Hide from default `--help` — the workforce surface is the current path. Unhide via AF_SHOW_DEPRECATED=1.
|
|
6474
|
+
if (!(process.env["AF_SHOW_DEPRECATED"] === "1")) {
|
|
6475
|
+
companyCmd._hidden = true;
|
|
6476
|
+
}
|
|
5683
6477
|
companyCmd
|
|
5684
6478
|
.command("export")
|
|
5685
6479
|
.description("Export workspace agent configuration to a portable YAML file.")
|