@audienti/cli 0.1.55 → 0.1.57

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/CHANGELOG.md CHANGED
@@ -4,6 +4,19 @@ All notable changes to the Audienti CLI are documented here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.1.57] - 2026-09-05
8
+
9
+ ### Added
10
+
11
+ - Show the selected LinkedIn account's stored Premium and Sales Navigator status and last detection timestamp in setup preflight, preserving unknown values and the unchanged JSON response.
12
+
13
+ ## [0.1.56] - 2026-09-05
14
+
15
+ ### Added
16
+
17
+ - Configure Motion Approach-guided adaptive post-accept planning with Approach alone; explicit clearing restores the existing sequence, without a separate mode selector.
18
+ - Answer current planner or writer questions with `operator answer`, exact-row refetching and stale-context protection; show prompts and candidate options in queue and next output.
19
+
7
20
  ## [0.1.55] - 2026-09-05
8
21
 
9
22
  ### Changed
package/README.md CHANGED
@@ -7,6 +7,10 @@ create and manage plays, import prospects, build lists, manage task reminders,
7
7
  configure list routing rules and account-user automation safety, and work
8
8
  supported operator flows.
9
9
 
10
+ The Motion Approach guides post-accept planning and writing. Set it with `audienti motions update motn_123 --approach "Test the premise before asking for a meeting"` or the `approach` field in a create/update payload. A nonblank Approach automatically chooses adaptive planning; blank, nil, or whitespace uses the existing sequence. `--approach ""` explicitly clears it and restores the existing sequence. There is no separate planning-mode selector. Existing sending controls still apply.
11
+
12
+ `audienti operator queue` and `operator next` show planner and writer questions with candidate options. Answer one with `audienti operator answer <row_id> --choice <id>` or `--answer "Your guidance"`. The command fetches that exact row even when it is not the next move, then submits its current fingerprints. Stale or double answers return 409; an accepted answer returns 202 planning. Answers guide only the current decision. Planning and preview never authorize sending.
13
+
10
14
  ## Install
11
15
 
12
16
  Requires Node.js 20 or newer.
@@ -351,6 +355,13 @@ today's window, and whether provider execution is currently inside the window.
351
355
  It also reports the configured user fallback location, the connected account's
352
356
  configured and last-verified effective proxy geography, and the proxy source
353
357
  without exposing proxy URLs, credentials, egress IPs, or browser session data.
358
+ For authorized LinkedIn account owners and admins, `social_cookie.capabilities.linkedin`
359
+ reports Audienti's stored `premium` and `sales_navigator` booleans plus the
360
+ `checked_at` timestamp. Text output shows yes, no, or unknown and when detection
361
+ last ran. Null or missing values mean unknown, including older server responses
362
+ and unavailable access. These are stored observations that may be stale;
363
+ preflight does not perform a fresh provider check. `--json` preserves the server
364
+ payload unchanged.
354
365
  For LinkedIn, `social_cookie.automation.pacing` includes effective weekly
355
366
  quotas, daily targets, motion active days, the outstanding-invitation cap, ramp
356
367
  configuration, current outstanding inventory, any inventory blocker, and current
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@audienti/cli",
3
- "version": "0.1.55",
3
+ "version": "0.1.57",
4
4
  "description": "Agent-first command-line client for Audienti.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -10,6 +10,10 @@ the production contract. The packaged `audienti-mcp` command is only a local
10
10
  stdio bridge for MCP hosts. Do not build a parallel wrapper or call
11
11
  undocumented API endpoints.
12
12
 
13
+ Set `approach` in a Motion create/update payload, or use `motions update <id> --approach "..."`. A nonblank Approach automatically guides adaptive planning and writing; blank, nil, or whitespace keeps the existing sequence. The simple `--approach ""` flag explicitly clears the guidance and restores that sequence. There is no separate planning-mode selector, and existing sending controls still apply.
14
+
15
+ When Operator exposes `answer_planner_question`, read its prompt and candidate options with `operator queue` or `operator next`. Use `operator answer <row_id> --choice <id>` or `--answer "..."`, exactly one input. This command refetches the exact row and submits the current row and decision fingerprints. Do not substitute a generic outcome, a reusable steer note, or a guessed send. A 409 means refresh the question; 202 means the answer was recorded and planning continues. Answers apply only to that decision.
16
+
13
17
  ## Setup
14
18
 
15
19
  1. Verify the command is installed:
package/src/api-client.js CHANGED
@@ -686,6 +686,14 @@ export class AudientiClient {
686
686
  return this.requestJson(accountPath(accountId, ["operator", "next"], query));
687
687
  }
688
688
 
689
+ operatorRow(accountId, rowId, query = {}) {
690
+ return this.requestJson(accountPath(accountId, ["operator", "row"], { ...query, row_id: rowId }));
691
+ }
692
+
693
+ operatorAnswer(accountId, body) {
694
+ return this.requestJson(accountPath(accountId, ["operator", "answer"]), { method: "POST", body });
695
+ }
696
+
689
697
  requeueOperatorFailedDrafts(accountId, body = {}) {
690
698
  return this.requestJson(accountPath(accountId, ["operator", "failed_drafts", "requeue"]), {
691
699
  method: "POST",
package/src/cli.js CHANGED
@@ -91,7 +91,7 @@ const OFFERS_DELETE_USAGE = "Usage: audienti offers delete <offr_id> --confirm <
91
91
  const WRITER_TEST_RUN_USAGE = "Usage: audienti writer test-run <prsp_id> [--json] [--mode <plan|report|step>] [--branch <both|no-accept|accepted>] [--step <step_key|row_number>] [--report <rprt_id>] [--no-wait] [--timeout-seconds <n>] [--poll-interval-seconds <n>] [--account <acct_id>]";
92
92
  const WRITER_TEST_RUN_SHOW_USAGE = "Usage: audienti writer test-run show <prsp_id> <rprt_id> [--json] [--account <acct_id>]";
93
93
  const MOTIONS_ANALYTICS_USAGE = "Usage: audienti motions analytics <motn_id> [--window 30d] [--json] [--account <acct_id>]";
94
- const MOTIONS_UPDATE_USAGE = "Usage: audienti motions update <motn_id> ([--status <draft|preparing|active|closing|paused|archived>] [--tags <tag[,tag...]>] [--own-post-engagement <true|false>] [--start-date <YYYY-MM-DD|none>] [--end-date <YYYY-MM-DD|none>] [--maximum-company-count <n|none>] | --payload <file.json>) [--json] [--account <acct_id>]";
94
+ const MOTIONS_UPDATE_USAGE = "Usage: audienti motions update <motn_id> ([--status <draft|preparing|active|closing|paused|archived>] [--tags <tag[,tag...]>] [--own-post-engagement <true|false>] [--start-date <YYYY-MM-DD|none>] [--end-date <YYYY-MM-DD|none>] [--maximum-company-count <n|none>] [--approach <text>] | --payload <file.json>) [--json] [--account <acct_id>]";
95
95
  const CONTENT_PROGRAMS_USAGE = "Usage: audienti content programs [--user <account_user_id|email|name|me>] [--json] [--account <acct_id>]";
96
96
  const CONTENT_PLAN_USAGE = "Usage: audienti content plan <cprg_id> [--week <n>] [--due] [--json] [--account <acct_id>]";
97
97
  const CONTENT_SHOW_USAGE = "Usage: audienti content show <cpwi_id> [--json] [--account <acct_id>]";
@@ -336,6 +336,7 @@ async function dispatch(argv, context) {
336
336
  if (normalizedResource === "operator" && action === "queue") return operatorQueue(rest, context, { accountOverride });
337
337
  if (normalizedResource === "operator" && action === "next") return operatorNext(rest, context, { accountOverride });
338
338
  if (normalizedResource === "operator" && action === "outcome") return operatorOutcome(rest, context, { accountOverride });
339
+ if (normalizedResource === "operator" && action === "answer") return operatorAnswer(rest, context, { accountOverride });
339
340
  if (normalizedResource === "inbox-ops" && action === "queue") return inboxOpsQueue(rest, context, { accountOverride });
340
341
  if (normalizedResource === "inbox-ops" && action === "filters") return inboxOpsFilters(rest, context, { accountOverride });
341
342
  if (normalizedResource === "inbox-ops" && action === "rule") return inboxOpsRule(rest, context, { accountOverride });
@@ -1923,6 +1924,7 @@ async function motionsCreate(args, context, { accountOverride } = {}) {
1923
1924
 
1924
1925
  function normalizeMotionCreatePayload(payload) {
1925
1926
  if (!payload || typeof payload !== "object" || Array.isArray(payload)) return payload;
1927
+ rejectMotionPlanningMode(payload);
1926
1928
  validateMotionSignalRows(payload);
1927
1929
  if (String(payload.kind || "").trim().toLowerCase() !== "inbound") return payload;
1928
1930
  if (Object.prototype.hasOwnProperty.call(payload, "inbound_channels")) return payload;
@@ -2007,6 +2009,7 @@ async function motionsUpdate(args, context, { accountOverride } = {}) {
2007
2009
  ...jsonOptions(),
2008
2010
  payload: { type: "string" },
2009
2011
  status: { type: "string" },
2012
+ approach: { type: "string" },
2010
2013
  tags: { type: "string" },
2011
2014
  "own-post-engagement": { type: "string" },
2012
2015
  "start-date": { type: "string" },
@@ -2033,6 +2036,7 @@ async function motionUpdatePayload(values) {
2033
2036
  }
2034
2037
 
2035
2038
  const payload = await readJsonPayload(values.payload);
2039
+ rejectMotionPlanningMode(payload);
2036
2040
  validateMotionSignalRows(payload);
2037
2041
  return payload;
2038
2042
  }
@@ -2043,6 +2047,7 @@ async function motionUpdatePayload(values) {
2043
2047
  own_post_engagement: values["own-post-engagement"] !== undefined ? parseBooleanString(values["own-post-engagement"], "--own-post-engagement") : undefined
2044
2048
  });
2045
2049
  const nullableSettings = {
2050
+ approach: values.approach,
2046
2051
  starts_on: normalizeMotionDate(values["start-date"], "--start-date"),
2047
2052
  ends_on: normalizeMotionDate(values["end-date"], "--end-date"),
2048
2053
  maximum_company_count: normalizeMotionMaximumCompanyCount(values["maximum-company-count"])
@@ -2056,6 +2061,7 @@ async function motionUpdatePayload(values) {
2056
2061
 
2057
2062
  function motionSimpleFieldsPresent(values) {
2058
2063
  return Boolean(values.status) ||
2064
+ values.approach !== undefined ||
2059
2065
  values.tags !== undefined ||
2060
2066
  values["own-post-engagement"] !== undefined ||
2061
2067
  values["start-date"] !== undefined ||
@@ -2063,6 +2069,12 @@ function motionSimpleFieldsPresent(values) {
2063
2069
  values["maximum-company-count"] !== undefined;
2064
2070
  }
2065
2071
 
2072
+ function rejectMotionPlanningMode(payload) {
2073
+ if (payload && Object.prototype.hasOwnProperty.call(payload, "post_accept_planning_mode")) {
2074
+ throw new CommandError("post_accept_planning_mode is read-only. Set a nonblank Approach for adaptive planning, or clear Approach to use the existing sequence.");
2075
+ }
2076
+ }
2077
+
2066
2078
  function normalizeMotionDate(value, flagName) {
2067
2079
  if (value === undefined) return undefined;
2068
2080
 
@@ -3412,6 +3424,39 @@ async function operatorOutcome(args, context, { accountOverride } = {}) {
3412
3424
  renderOperatorOutcome(response, context);
3413
3425
  }
3414
3426
 
3427
+ async function operatorAnswer(args, context, { accountOverride } = {}) {
3428
+ const { values, positionals } = parseCommandArgs(args, {
3429
+ ...jsonOptions(),
3430
+ choice: { type: "string" },
3431
+ principal: { type: "string" },
3432
+ answer: { type: "string" }
3433
+ });
3434
+ if (positionals.length !== 1 || (values.choice !== undefined) === (values.answer !== undefined)) {
3435
+ throw new CommandError("Usage: audienti operator answer <row_id> (--choice <id> | --answer <text>) [--json] [--account <acct_id>]");
3436
+ }
3437
+ if (!String(values.choice ?? values.answer).trim()) throw new CommandError("The choice or answer must not be blank.");
3438
+
3439
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
3440
+ const filters = compactObject({ principal_account_user_id: values.principal });
3441
+ const payload = await client.operatorRow(accountId, positionals[0], filters);
3442
+ const row = payload?.row;
3443
+ const action = row?.next_action;
3444
+ if (String(row?.id) !== positionals[0] || action?.type !== "answer_planner_question" ||
3445
+ !row.fingerprint || !action.decision_id || !action.context_fingerprint) {
3446
+ throw new CommandError("This row has no current planner question. Refresh the Operator queue.");
3447
+ }
3448
+ const response = await client.operatorAnswer(accountId, {
3449
+ ...filters,
3450
+ row_id: row.id,
3451
+ fingerprint: row.fingerprint,
3452
+ decision_id: action.decision_id,
3453
+ context_fingerprint: action.context_fingerprint,
3454
+ ...(values.choice !== undefined ? { choice_id: values.choice } : { answer: values.answer })
3455
+ });
3456
+ if (values.json) return writeJson(context.stdout, response);
3457
+ writeLine(context.stdout, "Answer recorded. Planning the next step.");
3458
+ }
3459
+
3415
3460
  async function analyticsProspects(args, context, { accountOverride } = {}) {
3416
3461
  if (args[0] === "cohort-analysis") {
3417
3462
  return analyticsProspectsCohortAnalysis(args.slice(1), context, { accountOverride });
@@ -4932,6 +4977,9 @@ function renderSetupPlayPreflight(payload, context) {
4932
4977
  writeLine(context.stdout, `Connection status: ${display(socialCookie.status)}`);
4933
4978
  writeLine(context.stdout, `Mapped to account: ${socialCookie.accessible_to_account ? "yes" : "no"}`);
4934
4979
  writeLine(context.stdout, `Actionable: ${socialCookie.actionable ? "yes" : "no"}`);
4980
+ if ((socialCookie.service_identifier || payload?.platform) === "linkedin") {
4981
+ renderSetupLinkedInCapabilities(socialCookie.capabilities?.linkedin, context);
4982
+ }
4935
4983
  renderSetupProxyLocation(socialCookie.proxy_location, context);
4936
4984
  renderSetupAutomationSummary(socialCookie.automation || {}, context);
4937
4985
  } else {
@@ -4949,6 +4997,14 @@ function renderSetupPlayPreflight(payload, context) {
4949
4997
  }
4950
4998
  }
4951
4999
 
5000
+ function renderSetupLinkedInCapabilities(capabilities, context) {
5001
+ const storedBoolean = (value) => value === true ? "yes" : value === false ? "no" : "unknown";
5002
+ writeLine(
5003
+ context.stdout,
5004
+ `LinkedIn capabilities (stored): Premium ${storedBoolean(capabilities?.premium)}, Sales Navigator ${storedBoolean(capabilities?.sales_navigator)}; last checked ${display(capabilities?.checked_at, "not recorded")}`
5005
+ );
5006
+ }
5007
+
4952
5008
  function renderSetupAutomationSummary(automation, context) {
4953
5009
  const summary = [
4954
5010
  `autopilot ${automation.autopilot_enabled ? "on" : "off"}`,
@@ -5321,6 +5377,10 @@ function renderMotion(motion, context) {
5321
5377
  writeLine(context.stdout, `Motion: ${display(motion?.name)} (${display(motion?.prefix_id)})`);
5322
5378
  writeLine(context.stdout, `Status: ${display(motion?.status)}`);
5323
5379
  writeLine(context.stdout, `Kind: ${display(motion?.kind)}`);
5380
+ writeLine(context.stdout, `Approach: ${display(motion?.approach, "not set")}`);
5381
+ if (typeof motion?.post_accept_actions_enabled === "boolean") {
5382
+ writeLine(context.stdout, `Post-accept actions: ${motion.post_accept_actions_enabled ? "enabled" : "disabled"}`);
5383
+ }
5324
5384
  writeLine(context.stdout, `Start date: ${display(motion?.starts_on, "not set")}`);
5325
5385
  writeLine(context.stdout, `End date: ${display(motion?.ends_on, "not set")}`);
5326
5386
  writeLine(context.stdout, `Maximum companies: ${display(motion?.maximum_company_count, "not set")}`);
@@ -5973,6 +6033,10 @@ function renderSequenceStep(step, index, context) {
5973
6033
  if (step.body) writeLine(context.stdout, ` Body: ${step.body}`);
5974
6034
  if (step.empty_body_reason) writeLine(context.stdout, ` Empty body reason: ${step.empty_body_reason}`);
5975
6035
  if (step.missing_reason) writeLine(context.stdout, ` Missing reason: ${step.missing_reason}`);
6036
+ if (step.kind === "decision") {
6037
+ for (const option of step.next_action?.options || []) writeLine(context.stdout, ` ${display(option.id)}: ${display(option.label)}`);
6038
+ if (step.eligible_action_families?.length) writeLine(context.stdout, ` Eligible next steps: ${step.eligible_action_families.map(humanize).join(", ")}`);
6039
+ }
5976
6040
  }
5977
6041
 
5978
6042
  function renderProspectSequenceExport(payload, context) {
@@ -6265,6 +6329,7 @@ function renderOperatorQueue(payload, context) {
6265
6329
  }
6266
6330
 
6267
6331
  writeOperatorRows(context, queue);
6332
+ queue.forEach((row) => renderPlannerQuestion(row, context));
6268
6333
  }
6269
6334
 
6270
6335
  function renderInboxOpsQueue(payload, context) {
@@ -6321,6 +6386,17 @@ function renderOperatorNext(row, context) {
6321
6386
  if (!row) return writeLine(context.stdout, "No operator moves found.");
6322
6387
 
6323
6388
  writeOperatorRows(context, [row]);
6389
+ renderPlannerQuestion(row, context);
6390
+ }
6391
+
6392
+ function renderPlannerQuestion(row, context) {
6393
+ const action = row?.next_action;
6394
+ if (action?.type !== "answer_planner_question") return;
6395
+ writeLine(context.stdout, `Question (${row.id}): ${display(action.prompt)}`);
6396
+ for (const option of action.options || []) {
6397
+ writeLine(context.stdout, ` ${display(option.id)}: ${display(option.label)}`);
6398
+ }
6399
+ writeLine(context.stdout, `Answer: audienti operator answer ${row.id} --choice <id> or --answer <text>`);
6324
6400
  }
6325
6401
 
6326
6402
  function renderOperatorPlan(row, context) {
@@ -6329,6 +6405,7 @@ function renderOperatorPlan(row, context) {
6329
6405
  const nextAction = row.next_action || {};
6330
6406
  const cta = row.cta || {};
6331
6407
  const draft = row.operator_draft || {};
6408
+ renderPlannerQuestion(row, context);
6332
6409
 
6333
6410
  writeLine(context.stdout, "Static operator plan");
6334
6411
  writeLine(context.stdout, `Move: ${display(row.id)}`);
@@ -6594,6 +6671,22 @@ function renderAnalyticsMotions(payload, context) {
6594
6671
 
6595
6672
  writeMotionMixTable(payload?.prospect_mix, context);
6596
6673
  writeMotionContributionTable(payload?.motions, context);
6674
+ renderAdaptiveTreatments(payload?.adaptive_treatments, context);
6675
+ }
6676
+
6677
+ function renderAdaptiveTreatments(rollup, context) {
6678
+ if (!rollup) return;
6679
+ writeLine(context.stdout, "");
6680
+ writeLine(context.stdout, "Adaptive treatments (all time; exact outbound-event outcome links)");
6681
+ const rows = rollup.treatments || [];
6682
+ if (rows.length > 0) {
6683
+ writeAlignedTable(context, ["SOURCE MOTION", "APPROACH", "OUTBOUND", "REPLIES", "MEETING ASKS", "ACCEPTED", "QUESTIONS", "MEAN ANSWER (s)"], rows.map((row) => [
6684
+ display(row.source_motion_id), display(row.approach_digest).slice(0, 12), integerLabel(row.outbound_count),
6685
+ integerLabel(row.replies), integerLabel(row.meeting_asks), integerLabel(row.meetings_accepted),
6686
+ `${integerLabel(row.questions)}/${integerLabel(row.decision_count)}`, display(row.mean_answer_latency_seconds)
6687
+ ]));
6688
+ } else writeLine(context.stdout, "No adaptive treatments yet.");
6689
+ writeLine(context.stdout, `Adaptive prospects without an adaptive message link: ${integerLabel(rollup.unattributed?.replies)} replies, ${integerLabel(rollup.unattributed?.meetings_accepted)} accepted meetings.`);
6597
6690
  }
6598
6691
 
6599
6692
  function renderAnalyticsIcps(payload, context) {
@@ -7624,7 +7717,7 @@ const HELP_TOPICS = new Map([
7624
7717
  " audienti motions abm-companies <motn_id> list",
7625
7718
  " audienti motions abm-companies <motn_id> add <domain_or_linkedin_url>...",
7626
7719
  " audienti motions create --payload <file.json>",
7627
- " audienti motions update <motn_id> [--status <state>] [--tags <tag[,tag...]>] [--own-post-engagement <true|false>] [--start-date <date|none>] [--end-date <date|none>] [--maximum-company-count <n|none>]",
7720
+ " audienti motions update <motn_id> [--status <state>] [--tags <tag[,tag...]>] [--own-post-engagement <true|false>] [--start-date <date|none>] [--end-date <date|none>] [--maximum-company-count <n|none>] [--approach <text>]",
7628
7721
  " audienti motions update <motn_id> --payload <file.json>",
7629
7722
  " audienti motions add-tag <motn_id> <tag>",
7630
7723
  " audienti motions remove-tag <motn_id> <tag>",
@@ -7702,6 +7795,7 @@ const HELP_TOPICS = new Map([
7702
7795
  "",
7703
7796
  " Operator queue",
7704
7797
  " audienti operator next --plan",
7798
+ " audienti operator answer <row_id> (--choice <id> | --answer <text>)",
7705
7799
  " audienti operator next --done --note <text>",
7706
7800
  " audienti operator queue",
7707
7801
  " audienti operator failed-drafts",
@@ -7986,6 +8080,8 @@ const HELP_TOPICS = new Map([
7986
8080
  " social_cookie.automation.in_working_hours: server-side current schedule decision",
7987
8081
  " account_user.location: configured user location used as the proxy fallback; unknown fields are null",
7988
8082
  " social_cookie.proxy_location: configured and last-verified effective proxy geography plus its source",
8083
+ " social_cookie.capabilities.linkedin: stored premium and sales_navigator booleans (null means unknown), plus checked_at; missing or unauthorized data is unknown",
8084
+ " Stored capabilities may be stale. Preflight does not check the provider again.",
7989
8085
  " social_cookie.automation.pacing: effective LinkedIn quotas, ramp settings, invitation inventory authority, and current capacity",
7990
8086
  " social_cookie.urls.mapping_url: owner URL for granting account access",
7991
8087
  " social_cookie.urls.edit_url: operations URL for reconnect/settings review when mapped",
@@ -9358,7 +9454,7 @@ const HELP_TOPICS = new Map([
9358
9454
  "Input shape:",
9359
9455
  " name: string",
9360
9456
  " premise: string",
9361
- " approach: string | optional",
9457
+ " approach: string | optional; a nonblank Approach guides adaptive planning, while blank uses the existing sequence",
9362
9458
  " kind: outbound | inbound | lopa | transition",
9363
9459
  " status: draft | active | paused | archived",
9364
9460
  " offer_id: offr_ prefix id",
@@ -9435,6 +9531,7 @@ const HELP_TOPICS = new Map([
9435
9531
  "Input shape:",
9436
9532
  " motn_id: motn_ prefix id",
9437
9533
  " status: draft | preparing | active | closing | paused | archived | optional",
9534
+ " approach: string | optional; a nonblank Approach guides adaptive planning; --approach \"\" restores the existing sequence",
9438
9535
  " principal_account_user_id: account user id | me | optional in payload mode",
9439
9536
  " list_id: list_ prefix id, numeric id, or null | optional in payload mode",
9440
9537
  " tags: comma-separated tag list | optional",
@@ -10617,6 +10714,7 @@ const HELP_TOPICS = new Map([
10617
10714
  " audienti operator failed-drafts [--json]",
10618
10715
  " audienti operator failed-drafts requeue (--all | <row_id> [row_id...])",
10619
10716
  " audienti operator outcome <row_id> --payload <file.json>",
10717
+ " audienti operator answer <row_id> (--choice <id> | --answer <text>)",
10620
10718
  "",
10621
10719
  "Status: read commands, failed draft requeue, and prospect next-move writeback implemented",
10622
10720
  "",
@@ -10707,6 +10805,23 @@ const HELP_TOPICS = new Map([
10707
10805
  " POST /api/v1/accounts/:account_id/operator/failed_drafts/requeue.json"
10708
10806
  ].join("\n")],
10709
10807
 
10808
+ ["operator answer", [
10809
+ "Usage:",
10810
+ " audienti operator answer <row_id> (--choice <id> | --answer <text>) [--json] [--account <acct_id>]",
10811
+ " --principal <account_user_id> selects the same authorized sender scope used by operator queue.",
10812
+ "",
10813
+ "Status: implemented",
10814
+ "",
10815
+ "Behavior:",
10816
+ " Refetches the exact current row, including questions that are not the next move, then sends its decision and fingerprints.",
10817
+ " Choose exactly one candidate id or a nonblank answer. Answers apply only to this decision; stale or already answered questions return 409.",
10818
+ " A 202 planning response records the answer; it does not authorize a provider send.",
10819
+ "",
10820
+ "API:",
10821
+ " GET /api/v1/accounts/:account_id/operator/row.json?row_id=:row_id",
10822
+ " POST /api/v1/accounts/:account_id/operator/answer.json"
10823
+ ].join("\n")],
10824
+
10710
10825
  ["operator outcome", [
10711
10826
  "Usage:",
10712
10827
  " audienti operator outcome <row_id> --payload <file.json> [--json] [--account <acct_id>]",