@pixelml/agenticflow-cli 1.5.1 → 1.5.3

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.
@@ -1 +1 @@
1
- {"version":3,"file":"changelog.d.ts","sourceRoot":"","sources":["../../src/cli/changelog.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,eAAO,MAAM,SAAS,EAAE,cAAc,EAmHrC,CAAC;AAEF,wBAAgB,kBAAkB,IAAI,cAAc,CAEnD;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,EAAE,CAInE"}
1
+ {"version":3,"file":"changelog.d.ts","sourceRoot":"","sources":["../../src/cli/changelog.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,eAAO,MAAM,SAAS,EAAE,cAAc,EAoJrC,CAAC;AAEF,wBAAgB,kBAAkB,IAAI,cAAc,CAEnD;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,EAAE,CAInE"}
@@ -5,6 +5,39 @@
5
5
  * and displayed after upgrade.
6
6
  */
7
7
  export const CHANGELOG = [
8
+ {
9
+ version: "1.5.3",
10
+ date: "2026-04-14",
11
+ highlights: [
12
+ "Model preflight on `af agent create/update` — invalid model strings (typos, missing slash) fail fast BEFORE the agent gets created with a broken config. Unknown-but-plausible models warn but proceed (so new models work between CLI releases)",
13
+ "Structured error hints on 401/403/404/409/422/429 — every common HTTP failure now carries an actionable `hint` in --json output pointing at the right recovery command (`af whoami`, `af agent list`, fetch-and-reconcile, etc.)",
14
+ "`af agent update` emits a stderr `[info]` line naming which null-valued fields got auto-stripped. Closes the footgun where bots thought they'd cleared a field but the server never saw null. Silenced with --json (keeps stdout clean for piping)",
15
+ ],
16
+ for_ai: [
17
+ "If you're iterating on an agent's system prompt with `--patch`, don't also clear optional fields by sending null — the CLI strips them and the stderr info line tells you which ones were dropped",
18
+ "When a command fails, check the `hint` field in the error envelope before retrying — 404s point you at the matching `list` command, 422s point you at `details.payload` for field-level errors",
19
+ "Pass only models from `af bootstrap --json > models[]` — typos fail at validation time, not at next `agent run`. If you're trying a brand-new model and hit a warning, you can proceed (CLI is conservative — warns but doesn't block)",
20
+ ],
21
+ },
22
+ {
23
+ version: "1.5.2",
24
+ date: "2026-04-14",
25
+ highlights: [
26
+ "`af workforce run --trigger-data '{...}'` now auto-wraps in the server's required `{trigger_data: ...}` envelope. Previously you had to pass `{\"trigger_data\":{\"topic\":\"AI\"}}` which felt like a CLI bug. Explicit wrapping still works (pass-through)",
27
+ "`af workforce delete` returns the consistent `agenticflow.delete.v1` envelope instead of bare `null`. Scripts now get the same shape as `af agent delete`",
28
+ "`af mcp-clients list --name-contains <substr> --fields id,name` — same filter + projection flags as `af agent list`. Essential for workspaces with dozens of MCP clients",
29
+ "`af mcp-clients inspect` surfaces the underlying `fetch_error` + `classification_reason` when tools can't be enumerated. Stops returning a misleading `pattern: unknown` that callers might treat as 'safe to attach'",
30
+ "`af schema <resource> --field <name>` now resolves against top-level schema keys too (not just create.optional). Lets you drill into `schema`, `update`, `stream` subtrees — e.g. `af schema workforce --field schema --json` returns the node_shape + edge_shape + agent_node_input docs",
31
+ "`af schema agent` clarifies that `project_id` is REQUIRED on create (server does NOT auto-inject for agents, unlike workforces). Separates the contract per resource",
32
+ ],
33
+ for_ai: [
34
+ "When you hit a 422 `body.trigger_data missing` on `workforce run`, upgrade to 1.5.2 — the CLI now auto-wraps",
35
+ "If `mcp-clients inspect` returns `classification_reason: 'fetch_failed'` or `'unauthenticated'`, DO NOT attach that client to an agent — re-auth in the web UI first",
36
+ "Filter MCP clients with `af mcp-clients list --name-contains 'google docs' --fields id,name --json` — no more grep-piping",
37
+ "Drill into any schema subtree: `af schema workforce --field schema --json` returns graph shape; `af schema agent --field mcp_clients --json` returns attach shape",
38
+ "Consistent delete envelope: every `af <resource> delete` returns `{schema:'agenticflow.delete.v1', deleted:true, id, resource}` on success",
39
+ ],
40
+ },
8
41
  {
9
42
  version: "1.5.1",
10
43
  date: "2026-04-14",
@@ -1 +1 @@
1
- {"version":3,"file":"changelog.js","sourceRoot":"","sources":["../../src/cli/changelog.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,MAAM,CAAC,MAAM,SAAS,GAAqB;IACzC;QACE,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,YAAY;QAClB,UAAU,EAAE;YACV,gSAAgS;YAChS,0HAA0H;YAC1H,2KAA2K;YAC3K,sSAAsS;YACtS,wOAAwO;YACxO,yLAAyL;SAC1L;QACD,MAAM,EAAE;YACN,0UAA0U;YAC1U,+IAA+I;YAC/I,4IAA4I;YAC5I,mKAAmK;SACpK;KACF;IACD;QACE,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,YAAY;QAClB,UAAU,EAAE;YACV,iKAAiK;YACjK,mIAAmI;YACnI,4JAA4J;YAC5J,qJAAqJ;YACrJ,8GAA8G;YAC9G,yGAAyG;SAC1G;QACD,MAAM,EAAE;YACN,mMAAmM;YACnM,wLAAwL;YACxL,wGAAwG;YACxG,gGAAgG;YAChG,iIAAiI;YACjI,oIAAoI;SACrI;KACF;IACD;QACE,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,YAAY;QAClB,UAAU,EAAE;YACV,2GAA2G;YAC3G,+MAA+M;YAC/M,2LAA2L;YAC3L,kJAAkJ;YAClJ,2GAA2G;YAC3G,2GAA2G;YAC3G,iKAAiK;YACjK,kHAAkH;YAClH,uHAAuH;SACxH;QACD,MAAM,EAAE;YACN,6LAA6L;YAC7L,uLAAuL;YACvL,4PAA4P;YAC5P,+JAA+J;YAC/J,2GAA2G;SAC5G;KACF;IACD;QACE,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,YAAY;QAClB,UAAU,EAAE;YACV,uEAAuE;YACvE,mFAAmF;YACnF,yFAAyF;YACzF,gFAAgF;YAChF,kEAAkE;YAClE,uFAAuF;YACvF,iEAAiE;YACjE,uEAAuE;YACvE,yDAAyD;YACzD,wDAAwD;YACxD,6EAA6E;YAC7E,6GAA6G;SAC9G;QACD,MAAM,EAAE;YACN,6FAA6F;YAC7F,+GAA+G;YAC/G,qFAAqF;YACrF,6EAA6E;YAC7E,qEAAqE;YACrE,oHAAoH;YACpH,wGAAwG;YACxG,mGAAmG;SACpG;KACF;IACD;QACE,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,YAAY;QAClB,UAAU,EAAE;YACV,gEAAgE;YAChE,uEAAuE;YACvE,mDAAmD;YACnD,4CAA4C;SAC7C;QACD,MAAM,EAAE;YACN,6CAA6C;YAC7C,6DAA6D;SAC9D;KACF;IACD;QACE,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,YAAY;QAClB,UAAU,EAAE;YACV,kCAAkC;YAClC,6CAA6C;YAC7C,qCAAqC;SACtC;QACD,MAAM,EAAE;YACN,0CAA0C;SAC3C;KACF;CACF,CAAC;AAEF,MAAM,UAAU,kBAAkB;IAChC,OAAO,SAAS,CAAC,CAAC,CAAC,CAAC;AACtB,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,OAAe;IAC/C,MAAM,GAAG,GAAG,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,OAAO,CAAC,CAAC;IAC9D,IAAI,GAAG,IAAI,CAAC;QAAE,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;IACpC,OAAO,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AACjC,CAAC"}
1
+ {"version":3,"file":"changelog.js","sourceRoot":"","sources":["../../src/cli/changelog.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/cli/main.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAy4BpC,wBAAgB,aAAa,IAAI,OAAO,CAwvKvC;AAED,wBAAsB,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAwB3D"}
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/cli/main.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAi8BpC,wBAAgB,aAAa,IAAI,OAAO,CAs2KvC;AAED,wBAAsB,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAwB3D"}
package/dist/cli/main.js CHANGED
@@ -17,9 +17,10 @@ import { LinearConnector } from "./gateway/connectors/linear.js";
17
17
  import { WebhookConnector } from "./gateway/connectors/webhook.js";
18
18
  import { listBlueprints, getBlueprint } from "./company-blueprints.js";
19
19
  import { CHANGELOG, getLatestChangelog } from "./changelog.js";
20
- import { stripNullFields } from "./utils/patch.js";
20
+ import { stripNullFields, AGENT_UPDATE_STRIP_NULL_FIELDS } from "./utils/patch.js";
21
21
  import { inspectMcpToolsPattern } from "./utils/mcp-inspect.js";
22
22
  import { emitDeprecation } from "./utils/deprecation.js";
23
+ import { validateModel } from "./utils/models.js";
23
24
  import { OperationRegistry, defaultSpecPath, loadOpenapiSpec, isPublic, } from "./spec.js";
24
25
  import { listPlaybooks, getPlaybook } from "./playbooks.js";
25
26
  import { loadPolicy, writeDefaultPolicy, policyFilePath, } from "./policy.js";
@@ -438,6 +439,19 @@ function buildClient(parentOpts) {
438
439
  projectId: resolveProjectId(parentOpts.projectId),
439
440
  });
440
441
  }
442
+ /**
443
+ * Map of HTTP status codes to actionable hints AI operators can follow without
444
+ * additional context. Keeps error responses useful when the server's error
445
+ * message is terse (e.g. "Agent not found" with no follow-up).
446
+ */
447
+ const STATUS_HINT_MAP = {
448
+ 401: "Authentication failed. Run `af whoami` to check current auth, or `af login --api-key <key>` to refresh.",
449
+ 403: "You don't have permission for this operation. Check the resource's workspace/project or your API key's scopes.",
450
+ 404: "Resource not found. Run the matching `list` command (e.g. `af agent list --json`) to see available IDs, or double-check the ID you passed.",
451
+ 409: "Conflict — the resource state disagrees with your request. Fetch the current state with `get` and reconcile before retrying.",
452
+ 422: "Validation failed. Check `details.payload` for the specific field errors — pydantic returns a list with field name + expected type per issue.",
453
+ 429: "Rate limited. Back off and retry with exponential delay.",
454
+ };
441
455
  /** Wrap an async SDK call with error handling. */
442
456
  async function run(fn) {
443
457
  try {
@@ -458,11 +472,47 @@ async function run(fn) {
458
472
  details["request_id"] = err.requestId;
459
473
  if (err.payload !== null && err.payload !== undefined)
460
474
  details["payload"] = err.payload;
461
- fail("request_failed", message, undefined, details);
475
+ const hint = STATUS_HINT_MAP[err.statusCode];
476
+ fail("request_failed", message, hint, details);
462
477
  }
463
478
  fail("request_failed", message);
464
479
  }
465
480
  }
481
+ /**
482
+ * Validate the `model` field on an agent create/update payload, if present.
483
+ * Fail-fast on implausible strings; warn (stderr) on plausible-but-unknown
484
+ * strings so new models work without CLI updates.
485
+ */
486
+ function preflightModel(payload, context) {
487
+ if (!("model" in payload))
488
+ return;
489
+ const res = validateModel(payload["model"]);
490
+ if (!res.valid) {
491
+ fail("invalid_option_value", `Invalid model in ${context}: ${String(payload["model"])}.`, res.suggestion);
492
+ }
493
+ if (!res.known && res.suggestion && !isJsonFlagEnabled()) {
494
+ console.error(`[warn] ${res.suggestion}`);
495
+ }
496
+ }
497
+ /**
498
+ * Report which keys got stripped by stripNullFields() so callers don't think
499
+ * they successfully cleared a field when the CLI silently dropped it.
500
+ * Emitted to stderr only (keeps stdout JSON clean for piping).
501
+ */
502
+ function warnOnStrippedNulls(original, stripped) {
503
+ if (isJsonFlagEnabled())
504
+ return; // don't pollute stderr on bot-driven runs
505
+ const dropped = [];
506
+ for (const key of AGENT_UPDATE_STRIP_NULL_FIELDS) {
507
+ if (key in original && original[key] === null && !(key in stripped)) {
508
+ dropped.push(key);
509
+ }
510
+ }
511
+ if (dropped.length > 0) {
512
+ console.error(`[info] Stripped ${dropped.length} null-valued field(s) the server rejects on update: ${dropped.join(", ")}. ` +
513
+ "This is expected — server-required shape. See `af schema agent --field update --json` for the full list.");
514
+ }
515
+ }
466
516
  // ═══════════════════════════════════════════════════════════════════
467
517
  // Spec-based helpers (for generic commands: call, ops, catalog, doctor)
468
518
  // ═══════════════════════════════════════════════════════════════════
@@ -758,6 +808,7 @@ export function createProgram() {
758
808
  const SCHEMAS = {
759
809
  agent: {
760
810
  resource: "agent",
811
+ note: "`project_id` is REQUIRED in the body on agent create (server does NOT auto-inject from client config, unlike workforces). The CLI's local validator enforces this — grab the value from `af bootstrap --json > auth.project_id`. The `workspace_id` scoping is handled server-side via API key.",
761
812
  create: {
762
813
  required: ["name", "tools", "project_id"],
763
814
  optional: {
@@ -1094,39 +1145,59 @@ export function createProgram() {
1094
1145
  if (!schema) {
1095
1146
  fail("schema_not_found", `Unknown resource: ${resource}`, `Available: ${Object.keys(SCHEMAS).join(", ")}`);
1096
1147
  }
1097
- // Field drilldown: return just the docs for one field
1148
+ // Field drilldown: resolve `--field X` against multiple schema locations.
1149
+ // We look in this order: top-level sibling key (e.g. `schema`, `update`,
1150
+ // `stream`), then create.required, then create.optional. This lets the
1151
+ // drilldown return rich subtree docs (like `schema` node/edge shapes on
1152
+ // the workforce resource) rather than "not found".
1098
1153
  if (opts?.field) {
1099
1154
  const fieldName = opts.field;
1100
1155
  const s = schema;
1101
- const create = s.create ?? {};
1102
- const required = create.required ?? [];
1103
- const optional = create.optional ?? {};
1104
1156
  let doc = null;
1157
+ let location = null;
1105
1158
  let isRequired = false;
1106
- if (required.includes(fieldName)) {
1107
- doc = "required";
1108
- isRequired = true;
1159
+ if (fieldName in s && fieldName !== "resource" && fieldName !== "fields") {
1160
+ doc = s[fieldName];
1161
+ location = "top_level";
1109
1162
  }
1110
- if (fieldName in optional)
1111
- doc = optional[fieldName] ?? null;
1163
+ else {
1164
+ const create = s.create ?? {};
1165
+ const required = create.required ?? [];
1166
+ const optional = create.optional ?? {};
1167
+ if (required.includes(fieldName)) {
1168
+ doc = "required";
1169
+ isRequired = true;
1170
+ location = "create.required";
1171
+ }
1172
+ else if (fieldName in optional) {
1173
+ doc = optional[fieldName] ?? null;
1174
+ location = "create.optional";
1175
+ }
1176
+ }
1177
+ const found = doc !== null && doc !== undefined;
1112
1178
  const result = {
1113
1179
  schema: "agenticflow.schema.field.v1",
1114
1180
  resource: s.resource,
1115
1181
  field: fieldName,
1116
1182
  required: isRequired,
1183
+ location,
1117
1184
  doc,
1118
- found: doc !== null,
1119
- hint: doc === null
1120
- ? `Field '${fieldName}' has no documented shape in the static schema. For live introspection, fetch an existing instance via 'af ${s.resource} get --${s.resource}-id <id> --json' and inspect the returned value for that field.`
1185
+ found,
1186
+ hint: !found
1187
+ ? `Field '${fieldName}' has no documented shape in the static schema. Candidates: top-level keys (${Object.keys(s).filter((k) => !["resource", "fields"].includes(k)).join(", ")}); create.required; create.optional. For live introspection, fetch an existing instance via 'af ${s.resource} get --${s.resource}-id <id> --json' and inspect the returned value for that field.`
1121
1188
  : undefined,
1122
1189
  };
1123
1190
  if (isJsonFlagEnabled()) {
1124
1191
  printResult(result);
1125
1192
  }
1126
1193
  else {
1127
- console.log(`${s.resource}.${fieldName}${isRequired ? " (required)" : " (optional)"}`);
1128
- if (doc)
1194
+ console.log(`${s.resource}.${fieldName}${isRequired ? " (required)" : ""}${location ? ` [${location}]` : ""}`);
1195
+ if (typeof doc === "string") {
1129
1196
  console.log(` ${doc}`);
1197
+ }
1198
+ else if (doc !== null && doc !== undefined) {
1199
+ console.log(JSON.stringify(doc, null, 2).split("\n").map((l) => " " + l).join("\n"));
1200
+ }
1130
1201
  if (result.hint)
1131
1202
  console.log(` ${result.hint}`);
1132
1203
  }
@@ -3466,6 +3537,7 @@ export function createProgram() {
3466
3537
  const body = loadJsonPayload(opts.body);
3467
3538
  hardenInput(JSON.stringify(body), "agent create body");
3468
3539
  ensureLocalValidation("agent.create", validateAgentCreatePayload(body));
3540
+ preflightModel(body, "agent create");
3469
3541
  if (opts.dryRun) {
3470
3542
  printResult({ schema: "agenticflow.dry_run.v1", valid: true, target: "agent.create", payload: body });
3471
3543
  return;
@@ -3483,15 +3555,22 @@ export function createProgram() {
3483
3555
  const client = buildClient(program.opts());
3484
3556
  const body = loadJsonPayload(opts.body);
3485
3557
  ensureLocalValidation("agent.update", validateAgentUpdatePayload(body));
3558
+ preflightModel(body, "agent update");
3486
3559
  if (opts.patch) {
3487
3560
  await run(() => client.agents.patch(opts.agentId, body, {
3488
- prepare: (merged) => stripNullFields(merged),
3561
+ prepare: (merged) => {
3562
+ const stripped = stripNullFields(merged);
3563
+ warnOnStrippedNulls(merged, stripped);
3564
+ return stripped;
3565
+ },
3489
3566
  }));
3490
3567
  }
3491
3568
  else {
3492
3569
  // Full replace, but strip server-rejected nulls so a round-tripped
3493
3570
  // `af agent get | af agent update --body @-` workflow doesn't 422.
3494
- const prepared = stripNullFields(body);
3571
+ const original = body;
3572
+ const prepared = stripNullFields(original);
3573
+ warnOnStrippedNulls(original, prepared);
3495
3574
  await run(() => client.agents.update(opts.agentId, prepared));
3496
3575
  }
3497
3576
  });
@@ -4040,28 +4119,54 @@ export function createProgram() {
4040
4119
  .option("--project-id <id>", "Project ID")
4041
4120
  .option("--limit <n>", "Limit")
4042
4121
  .option("--offset <n>", "Offset")
4122
+ .option("--name-contains <substr>", "Client-side case-insensitive substring filter on client `name`. Essential in busy workspaces with dozens of MCP clients.")
4123
+ .option("--fields <fields>", "Comma-separated fields to return (e.g. id,name,is_authenticated). Applies after --name-contains filter.")
4043
4124
  .option("--verify-auth", "Reconcile is_authenticated by calling `get` for each client — slower " +
4044
4125
  "but catches the case where list() reports auth=true but get() reveals " +
4045
4126
  "the underlying provider session is expired. N+1 call; use sparingly.")
4046
4127
  .action(async (opts) => {
4047
4128
  const client = buildClient(program.opts());
4129
+ // Helper: apply client-side name filter + fields projection to a list response
4130
+ const postFilter = (rows) => {
4131
+ let out = rows;
4132
+ const nameContains = opts.nameContains;
4133
+ if (nameContains && Array.isArray(out)) {
4134
+ const needle = nameContains.toLowerCase();
4135
+ out = out.filter((r) => {
4136
+ const n = r["name"];
4137
+ return typeof n === "string" && n.toLowerCase().includes(needle);
4138
+ });
4139
+ }
4140
+ return applyFieldsFilter(out, opts.fields);
4141
+ };
4048
4142
  if (!opts.verifyAuth) {
4049
- await run(() => client.mcpClients.list({
4050
- workspaceId: opts.workspaceId,
4051
- projectId: opts.projectId,
4052
- limit: parseOptionalInteger(opts.limit, "--limit", 1),
4053
- offset: parseOptionalInteger(opts.offset, "--offset", 0),
4054
- }));
4143
+ await run(async () => {
4144
+ const rows = await client.mcpClients.list({
4145
+ workspaceId: opts.workspaceId,
4146
+ projectId: opts.projectId,
4147
+ limit: parseOptionalInteger(opts.limit, "--limit", 1),
4148
+ offset: parseOptionalInteger(opts.offset, "--offset", 0),
4149
+ });
4150
+ return postFilter(rows);
4151
+ });
4055
4152
  return;
4056
4153
  }
4057
- // --verify-auth: list, then re-check each row's auth via get()
4154
+ // --verify-auth: list, filter, then re-check each remaining row's auth via get()
4058
4155
  await run(async () => {
4059
- const rows = (await client.mcpClients.list({
4156
+ let rows = (await client.mcpClients.list({
4060
4157
  workspaceId: opts.workspaceId,
4061
4158
  projectId: opts.projectId,
4062
4159
  limit: parseOptionalInteger(opts.limit, "--limit", 1),
4063
4160
  offset: parseOptionalInteger(opts.offset, "--offset", 0),
4064
4161
  }));
4162
+ const nameContains = opts.nameContains;
4163
+ if (nameContains) {
4164
+ const needle = nameContains.toLowerCase();
4165
+ rows = rows.filter((r) => {
4166
+ const n = r["name"];
4167
+ return typeof n === "string" && n.toLowerCase().includes(needle);
4168
+ });
4169
+ }
4065
4170
  const verified = await Promise.all(rows.map(async (row) => {
4066
4171
  const id = row["id"];
4067
4172
  if (!id)
@@ -4079,7 +4184,7 @@ export function createProgram() {
4079
4184
  return { ...row, verified_auth_error: err instanceof Error ? err.message : String(err) };
4080
4185
  }
4081
4186
  }));
4082
- return verified;
4187
+ return applyFieldsFilter(verified, opts.fields);
4083
4188
  });
4084
4189
  });
4085
4190
  mcpClientsCmd
@@ -4118,16 +4223,38 @@ export function createProgram() {
4118
4223
  ? (toolsBox.tools)
4119
4224
  : [];
4120
4225
  const report = inspectMcpToolsPattern(tools);
4226
+ // Surface the underlying fetch/auth error when the tools list couldn't
4227
+ // be enumerated — previously we silently returned pattern="unknown",
4228
+ // which callers mistook for "safe to attach." Now the `fetch_error`
4229
+ // + `classification_reason` make the failure explicit.
4230
+ const fetchError = raw["error"];
4231
+ const isAuth = raw["is_authenticated"];
4232
+ const classification_reason = tools.length > 0
4233
+ ? "tools_enumerated"
4234
+ : fetchError
4235
+ ? "fetch_failed"
4236
+ : isAuth === false
4237
+ ? "unauthenticated"
4238
+ : "unknown";
4239
+ const additional_quirks = [...report.quirks];
4240
+ if (tools.length === 0 && (fetchError || isAuth === false)) {
4241
+ additional_quirks.unshift(`Cannot classify this MCP client's tools — ${classification_reason}. ` +
4242
+ (fetchError ? `Server reported: ${fetchError}. ` : "") +
4243
+ `Do NOT attach this client to an agent until the underlying issue is resolved. ` +
4244
+ `Re-auth via the AgenticFlow web UI (workspaces/<ws>/mcp/${clientId}).`);
4245
+ }
4121
4246
  return {
4122
4247
  schema: "agenticflow.mcp_client.inspect.v1",
4123
4248
  client_id: clientId,
4124
4249
  client_name: raw["name"] ?? null,
4125
- is_authenticated: raw["is_authenticated"] ?? null,
4250
+ is_authenticated: isAuth ?? null,
4126
4251
  tool_count: tools.length,
4127
4252
  pattern: report.pattern, // "pipedream" | "composio" | "mixed" | "unknown"
4253
+ classification_reason,
4254
+ fetch_error: fetchError ?? null,
4128
4255
  write_capable_tools: report.writeCapable,
4129
4256
  pipedream_instruction_only_tools: report.pipedreamTools,
4130
- known_quirks: report.quirks,
4257
+ known_quirks: additional_quirks,
4131
4258
  playbook: "af playbook mcp-client-quirks",
4132
4259
  };
4133
4260
  });
@@ -4216,7 +4343,21 @@ export function createProgram() {
4216
4343
  .option("--workspace-id <id>", "Workspace ID (overrides env)")
4217
4344
  .action(async (opts) => {
4218
4345
  const client = buildClient(program.opts());
4219
- await run(() => client.workforces.delete(opts.workforceId, { workspaceId: opts.workspaceId }));
4346
+ try {
4347
+ await client.workforces.delete(opts.workforceId, { workspaceId: opts.workspaceId });
4348
+ // Server returns 204/null on success — wrap in the same delete envelope
4349
+ // that `af agent delete` uses so scripts get a consistent shape.
4350
+ printResult({
4351
+ schema: "agenticflow.delete.v1",
4352
+ deleted: true,
4353
+ id: opts.workforceId,
4354
+ resource: "workforce",
4355
+ });
4356
+ }
4357
+ catch (err) {
4358
+ const message = err instanceof Error ? err.message : String(err);
4359
+ fail("request_failed", message);
4360
+ }
4220
4361
  });
4221
4362
  workforceCmd
4222
4363
  .command("schema")
@@ -4275,13 +4416,24 @@ export function createProgram() {
4275
4416
  .description("Execute a workforce. Streams SSE events — each event prints as one JSON line. " +
4276
4417
  "Exits when the stream closes.")
4277
4418
  .requiredOption("--workforce-id <id>", "Workforce ID")
4278
- .option("--trigger-data <json>", "Trigger data (inline JSON or @file)", "{}")
4419
+ .option("--trigger-data <json>", "Trigger input as inline JSON or @file. Pass the data your trigger node expects " +
4420
+ "(e.g. `{\"topic\":\"AI\"}`) — the CLI automatically wraps it in the server's " +
4421
+ "required `{trigger_data: ...}` envelope. If you pass `{\"trigger_data\":{...}}` " +
4422
+ "explicitly, it's left as-is.", "{}")
4279
4423
  .option("--workspace-id <id>", "Workspace ID (overrides env)")
4280
4424
  .action(async (opts) => {
4281
4425
  const client = buildClient(program.opts());
4282
- const triggerData = loadJsonPayload(opts.triggerData);
4426
+ const raw = loadJsonPayload(opts.triggerData);
4427
+ // Server accepts `{trigger_data: {...}}`. If the caller already wrapped
4428
+ // it (explicitly nested under trigger_data), pass through. Otherwise,
4429
+ // wrap the user's payload. This is the ergonomics fix for friction S6 —
4430
+ // previously passing `{topic:"..."}` returned `422: body.trigger_data
4431
+ // missing` which looked like a CLI bug, not a payload shape bug.
4432
+ const triggerBody = "trigger_data" in raw && typeof raw["trigger_data"] === "object" && raw["trigger_data"] !== null
4433
+ ? raw
4434
+ : { trigger_data: raw };
4283
4435
  try {
4284
- const response = await client.workforces.run(opts.workforceId, triggerData, {
4436
+ const response = await client.workforces.run(opts.workforceId, triggerBody, {
4285
4437
  workspaceId: opts.workspaceId,
4286
4438
  });
4287
4439
  if (!response.ok) {