@audienti/cli 0.1.56 → 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,18 @@ 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
+
13
+ ## [0.1.57] - 2026-09-05
14
+
15
+ ### Added
16
+
17
+ - 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.
18
+
7
19
  ## [0.1.56] - 2026-09-05
8
20
 
9
21
  ### 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
@@ -355,6 +366,13 @@ today's window, and whether provider execution is currently inside the window.
355
366
  It also reports the configured user fallback location, the connected account's
356
367
  configured and last-verified effective proxy geography, and the proxy source
357
368
  without exposing proxy URLs, credentials, egress IPs, or browser session data.
369
+ For authorized LinkedIn account owners and admins, `social_cookie.capabilities.linkedin`
370
+ reports Audienti's stored `premium` and `sales_navigator` booleans plus the
371
+ `checked_at` timestamp. Text output shows yes, no, or unknown and when detection
372
+ last ran. Null or missing values mean unknown, including older server responses
373
+ and unavailable access. These are stored observations that may be stale;
374
+ preflight does not perform a fresh provider check. `--json` preserves the server
375
+ payload unchanged.
358
376
  For LinkedIn, `social_cookie.automation.pacing` includes effective weekly
359
377
  quotas, daily targets, motion active days, the outstanding-invitation cap, ramp
360
378
  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.56",
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(),
@@ -4977,6 +5013,9 @@ function renderSetupPlayPreflight(payload, context) {
4977
5013
  writeLine(context.stdout, `Connection status: ${display(socialCookie.status)}`);
4978
5014
  writeLine(context.stdout, `Mapped to account: ${socialCookie.accessible_to_account ? "yes" : "no"}`);
4979
5015
  writeLine(context.stdout, `Actionable: ${socialCookie.actionable ? "yes" : "no"}`);
5016
+ if ((socialCookie.service_identifier || payload?.platform) === "linkedin") {
5017
+ renderSetupLinkedInCapabilities(socialCookie.capabilities?.linkedin, context);
5018
+ }
4980
5019
  renderSetupProxyLocation(socialCookie.proxy_location, context);
4981
5020
  renderSetupAutomationSummary(socialCookie.automation || {}, context);
4982
5021
  } else {
@@ -4994,6 +5033,14 @@ function renderSetupPlayPreflight(payload, context) {
4994
5033
  }
4995
5034
  }
4996
5035
 
5036
+ function renderSetupLinkedInCapabilities(capabilities, context) {
5037
+ const storedBoolean = (value) => value === true ? "yes" : value === false ? "no" : "unknown";
5038
+ writeLine(
5039
+ context.stdout,
5040
+ `LinkedIn capabilities (stored): Premium ${storedBoolean(capabilities?.premium)}, Sales Navigator ${storedBoolean(capabilities?.sales_navigator)}; last checked ${display(capabilities?.checked_at, "not recorded")}`
5041
+ );
5042
+ }
5043
+
4997
5044
  function renderSetupAutomationSummary(automation, context) {
4998
5045
  const summary = [
4999
5046
  `autopilot ${automation.autopilot_enabled ? "on" : "off"}`,
@@ -6321,6 +6368,75 @@ function renderOperatorQueue(payload, context) {
6321
6368
  queue.forEach((row) => renderPlannerQuestion(row, context));
6322
6369
  }
6323
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
+
6324
6440
  function renderInboxOpsQueue(payload, context) {
6325
6441
  const decisionQueue = Array.isArray(payload?.decision_queue) ? payload.decision_queue : [];
6326
6442
  const rows = decisionQueue.length > 0 ? decisionQueue : [payload?.next_move].filter(Boolean);
@@ -6333,10 +6449,6 @@ function renderInboxOpsQueue(payload, context) {
6333
6449
  display(row?.inbox_ops?.subject),
6334
6450
  display(row?.inbox_ops?.connected_account)
6335
6451
  ]));
6336
- if (payload?.has_more === true && payload?.next_page) {
6337
- writeLine(context.stdout, "");
6338
- writeLine(context.stdout, `More rows: audienti inbox-ops queue --page ${payload.next_page}`);
6339
- }
6340
6452
  }
6341
6453
 
6342
6454
  function renderInboxOpsFilters(payload, context) {
@@ -8069,6 +8181,8 @@ const HELP_TOPICS = new Map([
8069
8181
  " social_cookie.automation.in_working_hours: server-side current schedule decision",
8070
8182
  " account_user.location: configured user location used as the proxy fallback; unknown fields are null",
8071
8183
  " social_cookie.proxy_location: configured and last-verified effective proxy geography plus its source",
8184
+ " social_cookie.capabilities.linkedin: stored premium and sales_navigator booleans (null means unknown), plus checked_at; missing or unauthorized data is unknown",
8185
+ " Stored capabilities may be stale. Preflight does not check the provider again.",
8072
8186
  " social_cookie.automation.pacing: effective LinkedIn quotas, ramp settings, invitation inventory authority, and current capacity",
8073
8187
  " social_cookie.urls.mapping_url: owner URL for granting account access",
8074
8188
  " social_cookie.urls.edit_url: operations URL for reconnect/settings review when mapped",
@@ -10650,9 +10764,10 @@ const HELP_TOPICS = new Map([
10650
10764
  "",
10651
10765
  "Status: implemented",
10652
10766
  "",
10767
+ ...OPERATOR_PAGINATION_HELP,
10653
10768
  "Purpose:",
10654
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.",
10655
- " 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.",
10656
10771
  "",
10657
10772
  "API:",
10658
10773
  " GET /api/v1/accounts/:account_id/operator.json?opportunity_kind=inbox"
@@ -10720,6 +10835,7 @@ const HELP_TOPICS = new Map([
10720
10835
  "",
10721
10836
  "Status: implemented",
10722
10837
  "",
10838
+ ...OPERATOR_PAGINATION_HELP,
10723
10839
  "Options:",
10724
10840
  " --plan Render a deterministic static plan from the existing next-action coach payload, CTA, and operator draft state",
10725
10841
  " --done Mark the current next prospect move completed through the operator outcome API",
@@ -10750,12 +10866,16 @@ const HELP_TOPICS = new Map([
10750
10866
  "",
10751
10867
  "Status: implemented",
10752
10868
  "",
10869
+ ...OPERATOR_PAGINATION_HELP,
10753
10870
  "Output shape:",
10754
10871
  " next_move: focal operator row",
10755
10872
  " decision_queue[]: ordered operator rows",
10756
10873
  " daily_progress: pacing counters",
10757
10874
  " outcome_rollups: queue rollups",
10758
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",
10759
10879
  "",
10760
10880
  "API:",
10761
10881
  " GET /api/v1/accounts/:account_id/operator.json"
@@ -10768,6 +10888,7 @@ const HELP_TOPICS = new Map([
10768
10888
  "",
10769
10889
  "Status: implemented",
10770
10890
  "",
10891
+ ...OPERATOR_PAGINATION_HELP,
10771
10892
  "Purpose:",
10772
10893
  " Lists failed prospect operator drafts and queues selected drafts for rewriting.",
10773
10894
  "",