@audienti/cli 0.1.50 → 0.1.51

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.51] - 2026-09-03
8
+
9
+ ### Added
10
+
11
+ - Add first-class `audienti inbox-ops queue`, `filters`, and `rule` commands backed by owner-scoped account APIs, authoritative row-derived sender/domain identities, all four allow/filter rule combinations, and unchanged JSON output.
12
+
7
13
  ## [0.1.50] - 2026-09-02
8
14
 
9
15
  ### Added
package/README.md CHANGED
@@ -214,6 +214,21 @@ audienti operator next --plan
214
214
  audienti operator next --done --note "Connection request sent."
215
215
  ```
216
216
 
217
+ Inbox Ops has its own owner-scoped CLI surface. Queue rows expose the stable row
218
+ id used by the rule command; sender and domain values are always derived by the
219
+ server from that authorized row:
220
+
221
+ ```bash
222
+ audienti inbox-ops queue [--page <n>]
223
+ audienti inbox-ops filters
224
+ audienti inbox-ops rule <row_id> --scope sender --disposition filter
225
+ audienti inbox-ops rule <row_id> --scope domain --disposition allow
226
+ ```
227
+
228
+ The four rule combinations mirror the Operator card actions. They update only
229
+ the authenticated owner's personal/global Inbox Ops preferences and do not
230
+ archive mail, call the provider, or add a DNC entry.
231
+
217
232
  To manage simple reminders for yourself:
218
233
 
219
234
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@audienti/cli",
3
- "version": "0.1.50",
3
+ "version": "0.1.51",
4
4
  "description": "Agent-first command-line client for Audienti.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -132,6 +132,9 @@ audienti motions activate <motn_id> --json
132
132
  audienti motions delete <motn_id> --confirm yes --json
133
133
  audienti operator next --json
134
134
  audienti operator next --plan
135
+ audienti inbox-ops queue --json
136
+ audienti inbox-ops filters --json
137
+ audienti inbox-ops rule <row_id> --scope <sender|domain> --disposition <allow|filter> --json
135
138
  audienti analytics motions --json
136
139
  audienti analytics icps --json
137
140
  audienti analytics prospects --window 24h --json
package/src/api-client.js CHANGED
@@ -657,6 +657,17 @@ export class AudientiClient {
657
657
  return this.requestJson(accountPath(accountId, ["operator"], query));
658
658
  }
659
659
 
660
+ inboxOpsFilters(accountId) {
661
+ return this.requestJson(accountPath(accountId, ["inbox_ops", "filters"]));
662
+ }
663
+
664
+ updateInboxOpsRule(accountId, rowId, body) {
665
+ return this.requestJson(accountPath(accountId, ["inbox_ops", rowId, "rule"]), {
666
+ method: "PATCH",
667
+ body
668
+ });
669
+ }
670
+
660
671
  operatorNext(accountId, query = {}) {
661
672
  return this.requestJson(accountPath(accountId, ["operator", "next"], query));
662
673
  }
package/src/cli.js CHANGED
@@ -65,6 +65,10 @@ 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>]";
69
+ const INBOX_OPS_FILTERS_USAGE = "Usage: audienti inbox-ops filters [--json] [--account <acct_id>]";
70
+ const INBOX_OPS_RULE_USAGE = "Usage: audienti inbox-ops rule <row_id> --scope <sender|domain> --disposition <allow|filter> [--json] [--account <acct_id>]";
71
+ const INBOX_OPS_ROW_ID_PATTERN = /^inbox_ops_message_[1-9]\d*$/;
68
72
  const DNC_ADD_USAGE = "Usage: audienti dnc add <email|citation_id|profile_url> [--json] [--account <acct_id>]";
69
73
  const DNC_IMPORT_USAGE = "Usage: audienti dnc import --file <txt|csv> [--json] [--account <acct_id>]";
70
74
  const DNC_REMOVE_USAGE = "Usage: audienti dnc remove <dnc_entry_id> [--json] [--account <acct_id>]";
@@ -330,6 +334,9 @@ async function dispatch(argv, context) {
330
334
  if (normalizedResource === "operator" && action === "queue") return operatorQueue(rest, context, { accountOverride });
331
335
  if (normalizedResource === "operator" && action === "next") return operatorNext(rest, context, { accountOverride });
332
336
  if (normalizedResource === "operator" && action === "outcome") return operatorOutcome(rest, context, { accountOverride });
337
+ if (normalizedResource === "inbox-ops" && action === "queue") return inboxOpsQueue(rest, context, { accountOverride });
338
+ if (normalizedResource === "inbox-ops" && action === "filters") return inboxOpsFilters(rest, context, { accountOverride });
339
+ if (normalizedResource === "inbox-ops" && action === "rule") return inboxOpsRule(rest, context, { accountOverride });
333
340
  if (normalizedResource === "analytics" && ["prospects", "prospect"].includes(action)) return analyticsProspects(rest, context, { accountOverride });
334
341
  if (normalizedResource === "analytics" && ["users", "user"].includes(action)) return analyticsUsers(rest, context, { accountOverride });
335
342
  if (normalizedResource === "analytics" && ["visibility", "visops"].includes(action)) return analyticsVisibility(rest, context, { accountOverride });
@@ -3214,6 +3221,60 @@ async function operatorQueue(args, context, { accountOverride } = {}) {
3214
3221
  renderOperatorQueue(payload, context);
3215
3222
  }
3216
3223
 
3224
+ async function inboxOpsQueue(args, context, { accountOverride } = {}) {
3225
+ const { values, positionals } = parseCommandArgs(args, {
3226
+ ...jsonOptions(),
3227
+ page: { type: "string" }
3228
+ });
3229
+ if (positionals.length > 0) throw new CommandError(INBOX_OPS_QUEUE_USAGE);
3230
+
3231
+ const page = normalizeOptionalPositiveInteger(values.page, "--page");
3232
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
3233
+ const payload = await client.operatorQueue(accountId, compactObject({
3234
+ opportunity_kind: "inbox",
3235
+ operator_page: page
3236
+ }));
3237
+ if (values.json) return writeJson(context.stdout, payload);
3238
+
3239
+ renderInboxOpsQueue(payload, context);
3240
+ }
3241
+
3242
+ async function inboxOpsFilters(args, context, { accountOverride } = {}) {
3243
+ const { values, positionals } = parseCommandArgs(args, jsonOptions());
3244
+ if (positionals.length > 0) throw new CommandError(INBOX_OPS_FILTERS_USAGE);
3245
+
3246
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
3247
+ const payload = await client.inboxOpsFilters(accountId);
3248
+ if (values.json) return writeJson(context.stdout, payload);
3249
+
3250
+ renderInboxOpsFilters(payload, context);
3251
+ }
3252
+
3253
+ async function inboxOpsRule(args, context, { accountOverride } = {}) {
3254
+ const { values, positionals } = parseCommandArgs(args, {
3255
+ ...jsonOptions(),
3256
+ scope: { type: "string" },
3257
+ disposition: { type: "string" }
3258
+ });
3259
+ if (positionals.length !== 1) throw new CommandError(INBOX_OPS_RULE_USAGE);
3260
+ if (!["sender", "domain"].includes(values.scope)) throw new CommandError("--scope must be sender or domain.");
3261
+ if (!["allow", "filter"].includes(values.disposition)) throw new CommandError("--disposition must be allow or filter.");
3262
+
3263
+ const rowId = positionals[0];
3264
+ if (!INBOX_OPS_ROW_ID_PATTERN.test(rowId)) {
3265
+ throw new CommandError("<row_id> must match inbox_ops_message_<positive integer>.");
3266
+ }
3267
+
3268
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
3269
+ const payload = await client.updateInboxOpsRule(accountId, rowId, {
3270
+ scope: values.scope,
3271
+ disposition: values.disposition
3272
+ });
3273
+ if (values.json) return writeJson(context.stdout, payload);
3274
+
3275
+ renderInboxOpsRule(payload, context);
3276
+ }
3277
+
3217
3278
  async function operatorFailedDrafts(args, context, { accountOverride } = {}) {
3218
3279
  if (args[0] === "requeue") {
3219
3280
  return operatorFailedDraftsRequeue(args.slice(1), context, { accountOverride });
@@ -6151,6 +6212,51 @@ function renderOperatorQueue(payload, context) {
6151
6212
  writeOperatorRows(context, queue);
6152
6213
  }
6153
6214
 
6215
+ function renderInboxOpsQueue(payload, context) {
6216
+ const decisionQueue = Array.isArray(payload?.decision_queue) ? payload.decision_queue : [];
6217
+ const rows = decisionQueue.length > 0 ? decisionQueue : [payload?.next_move].filter(Boolean);
6218
+ if (rows.length === 0) return writeLine(context.stdout, "No Inbox Ops rows found.");
6219
+
6220
+ writeAlignedTable(context, ["ROW ID", "SENDER", "DOMAIN", "SUBJECT", "CONNECTED INBOX"], rows.map((row) => [
6221
+ display(row?.id),
6222
+ display(row?.inbox_ops?.sender),
6223
+ display(row?.inbox_ops?.domain),
6224
+ display(row?.inbox_ops?.subject),
6225
+ display(row?.inbox_ops?.connected_account)
6226
+ ]));
6227
+ if (payload?.has_more === true && payload?.next_page) {
6228
+ writeLine(context.stdout, "");
6229
+ writeLine(context.stdout, `More rows: audienti inbox-ops queue --page ${payload.next_page}`);
6230
+ }
6231
+ }
6232
+
6233
+ function renderInboxOpsFilters(payload, context) {
6234
+ const owner = payload?.owner || {};
6235
+ const filters = payload?.email_filters || {};
6236
+ writeLine(context.stdout, `Inbox Ops filters for ${display(owner.email || owner.name || owner.id)}`);
6237
+ writeLine(context.stdout, `Subscriptions: ${filters.subscriptions === false ? "Show" : "Filter"}`);
6238
+ writeLine(context.stdout, `Automated/no-reply: ${filters.automated_no_reply === false ? "Show" : "Filter"}`);
6239
+ writeLine(context.stdout, `Provider promotions: ${filters.provider_promotions === false ? "Show" : "Filter"}`);
6240
+ writeInboxOpsRules(context, "Sender rules", filters.sender_rules);
6241
+ writeInboxOpsRules(context, "Domain rules", filters.domain_rules);
6242
+ }
6243
+
6244
+ function writeInboxOpsRules(context, heading, rules) {
6245
+ const rows = Object.entries(rules || {}).sort(([left], [right]) => left.localeCompare(right));
6246
+ writeLine(context.stdout, "");
6247
+ writeLine(context.stdout, heading);
6248
+ if (rows.length === 0) return writeLine(context.stdout, "None");
6249
+
6250
+ writeAlignedTable(context, ["KEY", "DISPOSITION"], rows.map(([key, disposition]) => [key, humanize(disposition)]));
6251
+ }
6252
+
6253
+ function renderInboxOpsRule(payload, context) {
6254
+ const rule = payload?.rule || {};
6255
+ const action = rule.disposition === "allow" ? "Always showing" : "Always filtering";
6256
+ writeLine(context.stdout, `${action} ${display(rule.scope)} ${display(rule.normalized_key)}.`);
6257
+ writeLine(context.stdout, "Re-run `audienti inbox-ops queue` to inspect the current queue.");
6258
+ }
6259
+
6154
6260
  function renderOperatorNext(row, context) {
6155
6261
  if (!row) return writeLine(context.stdout, "No operator moves found.");
6156
6262
 
@@ -7520,6 +7626,11 @@ const HELP_TOPICS = new Map([
7520
7626
  " audienti operator queue",
7521
7627
  " audienti operator failed-drafts",
7522
7628
  " audienti operator failed-drafts requeue <row_id>",
7629
+ "",
7630
+ " Inbox Ops",
7631
+ " audienti inbox-ops queue",
7632
+ " audienti inbox-ops filters",
7633
+ " audienti inbox-ops rule <row_id> --scope <sender|domain> --disposition <allow|filter>",
7523
7634
  "",
7524
7635
  " Analytics",
7525
7636
  " audienti analytics motions",
@@ -10327,6 +10438,72 @@ const HELP_TOPICS = new Map([
10327
10438
  " GET /api/v1/accounts/:account_id/tools/linkedin-review/reports/:id.json"
10328
10439
  ].join("\n")],
10329
10440
 
10441
+ ["inbox-ops", [
10442
+ "Usage:",
10443
+ ` ${INBOX_OPS_QUEUE_USAGE.slice("Usage: ".length)}`,
10444
+ ` ${INBOX_OPS_FILTERS_USAGE.slice("Usage: ".length)}`,
10445
+ ` ${INBOX_OPS_RULE_USAGE.slice("Usage: ".length)}`,
10446
+ "",
10447
+ "Status: implemented",
10448
+ "",
10449
+ "Purpose:",
10450
+ " Inspect the authenticated owner's private Inbox Ops queue and personal/global email rules, then set one authoritative sender or domain rule from a current queue row.",
10451
+ "",
10452
+ "Safety:",
10453
+ " Rules always apply to the token owner. This command does not accept --principal or a client-supplied sender/domain.",
10454
+ " The server resolves the normalized identity from the authorized current Inbox Ops row.",
10455
+ "",
10456
+ "Rule values:",
10457
+ " --scope sender|domain",
10458
+ " --disposition allow|filter",
10459
+ "",
10460
+ "API:",
10461
+ " queue: GET /api/v1/accounts/:account_id/operator.json?opportunity_kind=inbox",
10462
+ " filters: GET /api/v1/accounts/:account_id/inbox_ops/filters.json",
10463
+ " rule: PATCH /api/v1/accounts/:account_id/inbox_ops/:row_id/rule.json"
10464
+ ].join("\n")],
10465
+
10466
+ ["inbox-ops queue", [
10467
+ INBOX_OPS_QUEUE_USAGE,
10468
+ "",
10469
+ "Status: implemented",
10470
+ "",
10471
+ "Purpose:",
10472
+ " List one page of the authenticated owner's current private Inbox Ops rows with the authoritative sender/domain rule identity, subject, and connected inbox.",
10473
+ " When more rows exist, the plain output prints the next --page command.",
10474
+ "",
10475
+ "API:",
10476
+ " GET /api/v1/accounts/:account_id/operator.json?opportunity_kind=inbox"
10477
+ ].join("\n")],
10478
+
10479
+ ["inbox-ops filters", [
10480
+ INBOX_OPS_FILTERS_USAGE,
10481
+ "",
10482
+ "Status: implemented",
10483
+ "",
10484
+ "Purpose:",
10485
+ " Read the authenticated owner's normalized personal/global Inbox Ops filters and sender/domain rules.",
10486
+ "",
10487
+ "API:",
10488
+ " GET /api/v1/accounts/:account_id/inbox_ops/filters.json"
10489
+ ].join("\n")],
10490
+
10491
+ ["inbox-ops rule", [
10492
+ INBOX_OPS_RULE_USAGE,
10493
+ "",
10494
+ "Status: implemented",
10495
+ "",
10496
+ "Purpose:",
10497
+ " Set one allow or filter rule from an authorized current Inbox Ops row. The server derives the identity; the client cannot supply it.",
10498
+ "",
10499
+ "Rule values:",
10500
+ " --scope sender|domain",
10501
+ " --disposition allow|filter",
10502
+ "",
10503
+ "API:",
10504
+ " PATCH /api/v1/accounts/:account_id/inbox_ops/:row_id/rule.json"
10505
+ ].join("\n")],
10506
+
10330
10507
  ["operator", [
10331
10508
  "Usage:",
10332
10509
  " audienti operator next [--json|--plan|--done|--skip|--fail|--return]",
@@ -10342,7 +10519,7 @@ const HELP_TOPICS = new Map([
10342
10519
  " --motion <motn_id>",
10343
10520
  " --list <list_id>",
10344
10521
  " --stage <stage>",
10345
- " --opportunity-kind prospect|visibility",
10522
+ " --opportunity-kind prospect|visibility|inbox",
10346
10523
  " --writing-status ready|drafting|draft_failed"
10347
10524
  ].join("\n")],
10348
10525
 
@@ -10854,6 +11031,9 @@ const HELP_TOPICS = new Map([
10854
11031
  " audienti operator failed-drafts",
10855
11032
  " audienti operator failed-drafts requeue <row_id>",
10856
11033
  " audienti operator outcome <row_id> --payload <file.json>",
11034
+ " audienti inbox-ops queue",
11035
+ " audienti inbox-ops filters",
11036
+ " audienti inbox-ops rule <row_id> --scope sender --disposition filter",
10857
11037
  "",
10858
11038
  "7. Inspect account analytics",
10859
11039
  " audienti users activity me --window 7d",