@audienti/cli 0.1.57 → 0.1.58

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,12 @@ All notable changes to the Audienti CLI are documented here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.1.58] - 2026-09-06
8
+
9
+ ### Fixed
10
+
11
+ - Continue Operator and Inbox queue reads with the exact returned offset or cursor, preserving account and filters even after an empty page. Report incomplete scans without claiming the queue is empty, and keep pagination separate from outcome and requeue operations.
12
+
7
13
  ## [0.1.57] - 2026-09-05
8
14
 
9
15
  ### Added
package/README.md CHANGED
@@ -218,12 +218,23 @@ audienti operator next --plan
218
218
  audienti operator next --done --note "Connection request sent."
219
219
  ```
220
220
 
221
+ `operator queue`, `operator failed-drafts`, and `inbox-ops queue` print a
222
+ continuation command when more rows remain. Read-only `operator next` (including
223
+ `--plan`) also prints one when its page has no focal move but more rows remain;
224
+ when a move is present, inspect or act on it and rerun `operator next`.
225
+ Follow the continuation command even when the current page has no ready
226
+ rows. It preserves the account and filters and uses the API's exact `--page` and
227
+ `--offset` (which can be zero), or its opaque `--cursor`. Do not combine a cursor
228
+ with an offset. If the scan limit is reached, narrow the filters; an incomplete
229
+ scan does not mean the queue is empty. JSON output preserves the API response.
230
+ Pagination flags are not accepted when recording outcomes or requeueing drafts.
231
+
221
232
  Inbox Ops has its own owner-scoped CLI surface. Queue rows expose the stable row
222
233
  id used by the row-based rule command. Rules can also be set or removed directly
223
234
  by sender or domain; the server normalizes and validates every supplied key:
224
235
 
225
236
  ```bash
226
- audienti inbox-ops queue [--page <n>]
237
+ audienti inbox-ops queue [--page <n>] [--offset <n>|--cursor <token>]
227
238
  audienti inbox-ops filters
228
239
  audienti inbox-ops rule <row_id> --scope sender --disposition filter
229
240
  audienti inbox-ops rule <row_id> --scope domain --disposition allow
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@audienti/cli",
3
- "version": "0.1.57",
3
+ "version": "0.1.58",
4
4
  "description": "Agent-first command-line client for Audienti.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.js CHANGED
@@ -65,7 +65,16 @@ const PROSPECTS_CHECK_USAGE = "Usage: audienti prospects check [--json|--csv] [f
65
65
  const PROSPECTS_IMPORT_BATCH_USAGE = "Usage: audienti prospects import-batch --file <csv|jsonl|json> [--list <list_id>] [--motion <motn_id>] [--assigned-user <id|me>] [--json] [--account <acct_id>]";
66
66
  const OPERATOR_FAILED_DRAFTS_USAGE = "Usage: audienti operator failed-drafts [--json] [filters] [--account <acct_id>]";
67
67
  const OPERATOR_FAILED_DRAFTS_REQUEUE_USAGE = "Usage: audienti operator failed-drafts requeue (--all | <row_id> [row_id...]) [--limit <n>] [--json] [filters] [--account <acct_id>]";
68
- const INBOX_OPS_QUEUE_USAGE = "Usage: audienti inbox-ops queue [--page <n>] [--json] [--account <acct_id>]";
68
+ const INBOX_OPS_QUEUE_USAGE = "Usage: audienti inbox-ops queue [--page <n>] [--offset <n>|--cursor <token>] [--json] [--account <acct_id>]";
69
+ const OPERATOR_PAGINATION_HELP = [
70
+ "Read pagination:",
71
+ " --page <n> Positive page number",
72
+ " --offset <n> Exact returned next_offset, including zero",
73
+ " --cursor <token> Opaque returned next_cursor; cannot be combined with --offset",
74
+ " Follow the printed continuation with the same account and filters, even after an empty page.",
75
+ " Pagination is not accepted with outcome or requeue operations.",
76
+ ""
77
+ ];
69
78
  const INBOX_OPS_FILTERS_USAGE = "Usage: audienti inbox-ops filters [--json] [--account <acct_id>]";
70
79
  const INBOX_OPS_RULE_USAGE = "Usage: audienti inbox-ops rule <row_id> --scope <sender|domain> --disposition <allow|filter> [--json] [--account <acct_id>]";
71
80
  const INBOX_OPS_RULE_SET_USAGE = "Usage: audienti inbox-ops rule set --scope <sender|domain> --key <email|domain> --disposition <allow|filter> [--json] [--account <acct_id>]";
@@ -3240,32 +3249,30 @@ async function toolsLinkedinReviewStatus(args, context, { accountOverride } = {}
3240
3249
  }
3241
3250
 
3242
3251
  async function operatorQueue(args, context, { accountOverride } = {}) {
3243
- const { values, positionals } = parseCommandArgs(args, operatorFilterOptions());
3252
+ const { values, positionals } = parseCommandArgs(args, operatorFilterOptions(operatorPaginationOptions()));
3244
3253
  if (positionals.length > 0) throw new CommandError("Usage: audienti operator queue [--json] [filters] [--account <acct_id>]");
3245
3254
 
3255
+ const query = { ...operatorQuery(values), ...operatorPaginationQuery(values) };
3246
3256
  const { client, accountId } = await requireAccountContext(context, { accountOverride });
3247
- const payload = await client.operatorQueue(accountId, operatorQuery(values));
3257
+ const payload = await client.operatorQueue(accountId, query);
3248
3258
  if (values.json) return writeJson(context.stdout, payload);
3249
3259
 
3250
- renderOperatorQueue(payload, context);
3260
+ renderOperatorRead(payload, context, { command: "operator queue", accountId, query }, () => renderOperatorQueue(payload, context));
3251
3261
  }
3252
3262
 
3253
3263
  async function inboxOpsQueue(args, context, { accountOverride } = {}) {
3254
3264
  const { values, positionals } = parseCommandArgs(args, {
3255
3265
  ...jsonOptions(),
3256
- page: { type: "string" }
3266
+ ...operatorPaginationOptions()
3257
3267
  });
3258
3268
  if (positionals.length > 0) throw new CommandError(INBOX_OPS_QUEUE_USAGE);
3259
3269
 
3260
- const page = normalizeOptionalPositiveInteger(values.page, "--page");
3270
+ const query = { opportunity_kind: "inbox", ...operatorPaginationQuery(values) };
3261
3271
  const { client, accountId } = await requireAccountContext(context, { accountOverride });
3262
- const payload = await client.operatorQueue(accountId, compactObject({
3263
- opportunity_kind: "inbox",
3264
- operator_page: page
3265
- }));
3272
+ const payload = await client.operatorQueue(accountId, query);
3266
3273
  if (values.json) return writeJson(context.stdout, payload);
3267
3274
 
3268
- renderInboxOpsQueue(payload, context);
3275
+ renderOperatorRead(payload, context, { command: "inbox-ops queue", accountId, query }, () => renderInboxOpsQueue(payload, context));
3269
3276
  }
3270
3277
 
3271
3278
  async function inboxOpsFilters(args, context, { accountOverride } = {}) {
@@ -3345,14 +3352,15 @@ async function operatorFailedDrafts(args, context, { accountOverride } = {}) {
3345
3352
  return operatorFailedDraftsRequeue(args.slice(1), context, { accountOverride });
3346
3353
  }
3347
3354
 
3348
- const { values, positionals } = parseCommandArgs(args, operatorFailedDraftOptions());
3355
+ const { values, positionals } = parseCommandArgs(args, operatorFailedDraftOptions(operatorPaginationOptions()));
3349
3356
  if (positionals.length > 0) throw new CommandError(OPERATOR_FAILED_DRAFTS_USAGE);
3350
3357
 
3358
+ const query = { ...operatorFailedDraftQuery(values), ...operatorPaginationQuery(values) };
3351
3359
  const { client, accountId } = await requireAccountContext(context, { accountOverride });
3352
- const payload = await client.operatorQueue(accountId, operatorFailedDraftQuery(values));
3360
+ const payload = await client.operatorQueue(accountId, query);
3353
3361
  if (values.json) return writeJson(context.stdout, payload);
3354
3362
 
3355
- renderOperatorFailedDrafts(payload, context);
3363
+ renderOperatorRead(payload, context, { command: "operator failed-drafts", accountId, query }, () => renderOperatorFailedDrafts(payload, context));
3356
3364
  }
3357
3365
 
3358
3366
  async function operatorFailedDraftsRequeue(args, context, { accountOverride } = {}) {
@@ -3381,12 +3389,16 @@ async function operatorNext(args, context, { accountOverride } = {}) {
3381
3389
  if (values.json && values.plan) throw new CommandError("Choose one output format: use either --json or --plan.");
3382
3390
  const outcomeStatus = operatorNextOutcomeStatus(values);
3383
3391
  if (values.plan && outcomeStatus) throw new CommandError("Choose one mode: use either --plan or an outcome flag.");
3392
+ if (outcomeStatus && [values.page, values.offset, values.cursor].some((value) => value !== undefined)) {
3393
+ throw new CommandError("Pagination is only available for reads; omit --page, --offset, and --cursor when recording an outcome.");
3394
+ }
3384
3395
  if (!outcomeStatus && (values.note !== undefined || values["occurred-at"] !== undefined)) {
3385
3396
  throw new CommandError("--note and --occurred-at require an outcome flag: --done, --skip, --fail, or --return.");
3386
3397
  }
3387
3398
 
3399
+ const query = { ...operatorQuery(values), ...operatorPaginationQuery(values) };
3388
3400
  const { client, accountId } = await requireAccountContext(context, { accountOverride });
3389
- const payload = await client.operatorNext(accountId, operatorQuery(values));
3401
+ const payload = await client.operatorNext(accountId, query);
3390
3402
  if (outcomeStatus) {
3391
3403
  const response = await client.operatorOutcome(accountId, operatorNextOutcomePayload(payload?.next_move, {
3392
3404
  status: outcomeStatus,
@@ -3399,9 +3411,10 @@ async function operatorNext(args, context, { accountOverride } = {}) {
3399
3411
  return renderOperatorOutcome(response, context);
3400
3412
  }
3401
3413
  if (values.json) return writeJson(context.stdout, payload);
3402
- if (values.plan) return renderOperatorPlan(payload?.next_move, context);
3403
-
3404
- renderOperatorNext(payload?.next_move, context);
3414
+ renderOperatorRead(payload, context, { command: "operator next", accountId, query, plan: values.plan }, () => {
3415
+ if (values.plan) return renderOperatorPlan(payload?.next_move, context);
3416
+ renderOperatorNext(payload?.next_move, context);
3417
+ });
3405
3418
  }
3406
3419
 
3407
3420
  async function operatorOutcome(args, context, { accountOverride } = {}) {
@@ -3760,6 +3773,7 @@ function operatorFilterOptions(extra = {}) {
3760
3773
 
3761
3774
  function operatorNextOptions() {
3762
3775
  return operatorFilterOptions({
3776
+ ...operatorPaginationOptions(),
3763
3777
  plan: { type: "boolean" },
3764
3778
  done: { type: "boolean" },
3765
3779
  skip: { type: "boolean" },
@@ -3770,6 +3784,28 @@ function operatorNextOptions() {
3770
3784
  });
3771
3785
  }
3772
3786
 
3787
+ function operatorPaginationOptions() {
3788
+ return { page: { type: "string" }, offset: { type: "string" }, cursor: { type: "string" } };
3789
+ }
3790
+
3791
+ function operatorPaginationQuery(values) {
3792
+ const page = normalizeOptionalPositiveInteger(values.page, "--page");
3793
+ if (values.page !== undefined && page === undefined) throw new CommandError("--page must be a positive integer.");
3794
+ if (page !== undefined && !Number.isSafeInteger(page)) throw new CommandError("--page must be a safe positive integer.");
3795
+
3796
+ let offset;
3797
+ if (values.offset !== undefined) {
3798
+ offset = Number(values.offset);
3799
+ if (!Number.isSafeInteger(offset) || offset < 0 || String(offset) !== values.offset.trim()) {
3800
+ throw new CommandError("--offset must be a nonnegative integer.");
3801
+ }
3802
+ }
3803
+ if (values.cursor !== undefined && !values.cursor.trim()) throw new CommandError("--cursor must not be blank.");
3804
+ if (values.cursor !== undefined && offset !== undefined) throw new CommandError("Choose either --cursor or --offset, not both.");
3805
+
3806
+ return compactObject({ operator_page: page, operator_offset: offset, operator_cursor: values.cursor });
3807
+ }
3808
+
3773
3809
  function operatorFailedDraftOptions(extra = {}) {
3774
3810
  return {
3775
3811
  ...jsonOptions(),
@@ -6332,6 +6368,75 @@ function renderOperatorQueue(payload, context) {
6332
6368
  queue.forEach((row) => renderPlannerQuestion(row, context));
6333
6369
  }
6334
6370
 
6371
+ function renderOperatorRead(payload, context, continuation, renderRows) {
6372
+ if (["reset_to_legacy", "cursor_stale"].includes(payload?.metrics?.cursor_status)) {
6373
+ writeLine(context.stdout, "Queue changed; restarted at the first page.");
6374
+ }
6375
+ const hasRows = payload?.next_move || payload?.decision_queue?.length > 0;
6376
+ if (!hasRows && (payload?.has_more === true || payload?.metrics?.scan_ceiling_reached === true)) {
6377
+ writeLine(context.stdout, "No ready rows in this page.");
6378
+ } else {
6379
+ renderRows();
6380
+ }
6381
+ if (payload?.metrics?.scan_ceiling_reached === true) {
6382
+ writeLine(context.stdout, "Queue scan limit reached; more work may remain. Narrow the filters to inspect it.");
6383
+ }
6384
+ // /next exposes one focal move, while next_offset covers the whole scanned page.
6385
+ const focalMoveShown = continuation.command === "operator next" && payload?.next_move;
6386
+ if (payload?.has_more === true && !focalMoveShown) {
6387
+ const command = operatorContinuationCommand(payload, continuation);
6388
+ writeLine(context.stdout, command ? `More rows: ${command}` : "More work may remain; narrow the filters to continue.");
6389
+ }
6390
+ }
6391
+
6392
+ function operatorContinuationCommand(payload, { command, accountId, query, plan }) {
6393
+ const args = ["audienti", ...command.split(" ")];
6394
+ const cursor = payload?.metrics?.next_cursor;
6395
+ if (payload.next_page) args.push(...operatorCommandOption("page", payload.next_page));
6396
+ if (cursor) {
6397
+ args.push(...operatorCommandOption("cursor", cursor));
6398
+ } else {
6399
+ if (!payload.next_page) return null;
6400
+ if (payload?.metrics?.next_offset !== undefined && payload.metrics.next_offset !== null) {
6401
+ args.push(...operatorCommandOption("offset", payload.metrics.next_offset));
6402
+ }
6403
+ }
6404
+ args.push(...operatorCommandOption("account", accountId));
6405
+ args.push(...operatorContinuationFilters(command, query, payload.filters));
6406
+ if (plan) args.push("--plan");
6407
+ return args.map(operatorCommandArgument).join(" ");
6408
+ }
6409
+
6410
+ function operatorContinuationFilters(command, query, resolvedFilters = {}) {
6411
+ if (command === "inbox-ops queue") return [];
6412
+
6413
+ const filters = { ...resolvedFilters, ...query };
6414
+ const flags = {
6415
+ principal_account_user_id: "principal", motion_id: "motion", list_id: "list",
6416
+ stage: "stage", opportunity_kind: "opportunity-kind", writing_status: "writing-status"
6417
+ };
6418
+ filters.stage ||= filters.pipeline_stage;
6419
+ if (command === "operator failed-drafts") {
6420
+ delete flags.opportunity_kind;
6421
+ delete flags.writing_status;
6422
+ flags.query = "query";
6423
+ }
6424
+ return Object.entries(flags).flatMap(([key, flag]) => {
6425
+ const value = filters[key];
6426
+ return value === undefined || value === null || value === "" ? [] : operatorCommandOption(flag, Array.isArray(value) ? value.join(",") : value);
6427
+ });
6428
+ }
6429
+
6430
+ function operatorCommandOption(flag, value) {
6431
+ const text = String(value);
6432
+ return text.startsWith("-") ? [`--${flag}=${text}`] : [`--${flag}`, text];
6433
+ }
6434
+
6435
+ function operatorCommandArgument(value) {
6436
+ const text = String(value);
6437
+ return /^[a-zA-Z0-9_./:@=-]+$/.test(text) ? text : `'${text.replaceAll("'", "'\\''")}'`;
6438
+ }
6439
+
6335
6440
  function renderInboxOpsQueue(payload, context) {
6336
6441
  const decisionQueue = Array.isArray(payload?.decision_queue) ? payload.decision_queue : [];
6337
6442
  const rows = decisionQueue.length > 0 ? decisionQueue : [payload?.next_move].filter(Boolean);
@@ -6344,10 +6449,6 @@ function renderInboxOpsQueue(payload, context) {
6344
6449
  display(row?.inbox_ops?.subject),
6345
6450
  display(row?.inbox_ops?.connected_account)
6346
6451
  ]));
6347
- if (payload?.has_more === true && payload?.next_page) {
6348
- writeLine(context.stdout, "");
6349
- writeLine(context.stdout, `More rows: audienti inbox-ops queue --page ${payload.next_page}`);
6350
- }
6351
6452
  }
6352
6453
 
6353
6454
  function renderInboxOpsFilters(payload, context) {
@@ -10663,9 +10764,10 @@ const HELP_TOPICS = new Map([
10663
10764
  "",
10664
10765
  "Status: implemented",
10665
10766
  "",
10767
+ ...OPERATOR_PAGINATION_HELP,
10666
10768
  "Purpose:",
10667
10769
  " List one page of the authenticated owner's current private Inbox Ops rows with the authoritative sender/domain rule identity, subject, and connected inbox.",
10668
- " When more rows exist, the plain output prints the next --page command.",
10770
+ " When more rows exist, the plain output prints the exact continuation command with the same account.",
10669
10771
  "",
10670
10772
  "API:",
10671
10773
  " GET /api/v1/accounts/:account_id/operator.json?opportunity_kind=inbox"
@@ -10733,6 +10835,7 @@ const HELP_TOPICS = new Map([
10733
10835
  "",
10734
10836
  "Status: implemented",
10735
10837
  "",
10838
+ ...OPERATOR_PAGINATION_HELP,
10736
10839
  "Options:",
10737
10840
  " --plan Render a deterministic static plan from the existing next-action coach payload, CTA, and operator draft state",
10738
10841
  " --done Mark the current next prospect move completed through the operator outcome API",
@@ -10763,12 +10866,16 @@ const HELP_TOPICS = new Map([
10763
10866
  "",
10764
10867
  "Status: implemented",
10765
10868
  "",
10869
+ ...OPERATOR_PAGINATION_HELP,
10766
10870
  "Output shape:",
10767
10871
  " next_move: focal operator row",
10768
10872
  " decision_queue[]: ordered operator rows",
10769
10873
  " daily_progress: pacing counters",
10770
10874
  " outcome_rollups: queue rollups",
10771
10875
  " options: motions, principals, lists, stages",
10876
+ " has_more/next_page: continuation state",
10877
+ " metrics.next_offset/next_cursor: exact continuation position",
10878
+ " metrics.scan_ceiling_reached: incomplete scan; narrow the filters",
10772
10879
  "",
10773
10880
  "API:",
10774
10881
  " GET /api/v1/accounts/:account_id/operator.json"
@@ -10781,6 +10888,7 @@ const HELP_TOPICS = new Map([
10781
10888
  "",
10782
10889
  "Status: implemented",
10783
10890
  "",
10891
+ ...OPERATOR_PAGINATION_HELP,
10784
10892
  "Purpose:",
10785
10893
  " Lists failed prospect operator drafts and queues selected drafts for rewriting.",
10786
10894
  "",