@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/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
- init_workforce: "af workforce init --blueprint <id> --json",
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
- id: b.id,
1030
- name: b.name,
1031
- agents: b.agents.length,
1032
- native_target: "workforce",
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 commands (init, validate, simulate, run, install, list, uninstall). " +
2436
- "DEPRECATED in v1.7.0 the \"pack\" concept has been collapsed into blueprints. " +
2437
- "The 3 legacy packs (amazon-seller-pack, tutor-pack, freelancer-pack) are all " +
2438
- "available as workforce blueprints now. Use `af workforce init --blueprint <id>` " +
2439
- "instead. Sunset 2026-10-14.");
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
- if (opts.input)
3292
- body["input"] = loadJsonPayload(opts.input);
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
- .requiredOption("--workflow-run-id <id>", "Workflow run ID")
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(opts.workflowRunId));
4104
+ await run(() => client.workflows.getRun(runId));
3434
4105
  }
3435
4106
  else {
3436
- await run(() => client.workflows.getRunAnonymous(opts.workflowRunId));
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
- .requiredOption("--agent-id <id>", "Agent ID")
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
- if (token) {
3587
- await run(() => client.agents.get(opts.agentId));
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
- fail("request_failed", `Workforce run failed with status ${response.status}`, undefined, { status_code: response.status });
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("Publish and control AgenticFlow agents on a Paperclip instance. " +
5002
- "DEPRECATED prefer `af workforce *` for AgenticFlow-native deploy. See: af playbook migrate-from-paperclip.");
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 configuration export and import.");
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.")