@audienti/cli 0.1.70 → 0.1.73

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,30 @@ All notable changes to the Audienti CLI are documented here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ### Added
8
+
9
+ - Add Motion-owned discovery signal tracking to `motions show` and the `motions signals` command, including attribution holds, source counts, and retained Agent provenance.
10
+
11
+ ## [0.1.73] - 2026-09-26
12
+
13
+ ### Added
14
+
15
+ - Add the account-scoped `motions signals` command for inspecting Motion-owned Topic and profile signals.
16
+
17
+ ## [0.1.72] - 2026-09-26
18
+
19
+ ### Added
20
+
21
+ - Add `social-cookies sync-messages` to request account-scoped email syncs with folder and explicit-retry controls, while printing the server's durable sync-slot status.
22
+
23
+ ## [0.1.71] - 2026-09-24
24
+
25
+ ### Changed
26
+
27
+ - Motion updates now honor the server's durable first-discovery Approach lock: edits remain available before the first accepted discovery launch and are rejected after the lock is set.
28
+
29
+ - Prospect replan API responses now use the canonical Planner contract for admitted memberships.
30
+
7
31
  ## [0.1.70] - 2026-09-18
8
32
 
9
33
  ### Added
package/README.md CHANGED
@@ -161,6 +161,7 @@ audienti content programs
161
161
  audienti content plan <cprg_id>
162
162
  audienti content approve <cpwi_id>
163
163
  audienti content comments
164
+ audienti social-cookies sync-messages <scok_id> [--folder <folder>] [--retry] --json
164
165
  audienti prospects show <prsp_id> --json
165
166
  audienti prospects list --profiles
166
167
  audienti prospects list --assigned-user unassigned
@@ -418,6 +419,12 @@ configuration, current outstanding inventory, any inventory blocker, and current
418
419
  invitation capacity calculated by the server. A `null` weekly quota means
419
420
  unlimited.
420
421
 
422
+ For an email social cookie, `social-cookies sync-messages` requests the shared
423
+ account-scoped cookie/folder sync slot and prints the server's durable status.
424
+ Pass `--folder` to select a provider folder and `--retry` only when an explicit
425
+ retry is intended. The command does not call a provider directly; `--json`
426
+ returns the accepted request and `sync_states` payload unchanged.
427
+
421
428
  Use `users automation` to inspect or change one account user's LinkedIn safety
422
429
  policy without changing the saved account selection:
423
430
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@audienti/cli",
3
- "version": "0.1.70",
3
+ "version": "0.1.73",
4
4
  "description": "Agent-first command-line client for Audienti.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -122,6 +122,7 @@ audienti users activity me --window 7d --json
122
122
  audienti users automation show <account_user_id|me> --platform linkedin --json --account <acct_id>
123
123
  audienti users automation update <account_user_id|me> --payload automation-controls.json --json --account <acct_id>
124
124
  audienti users automation update <account_user_id|me> --payload automation-controls.json --apply --json --account <acct_id>
125
+ audienti social-cookies sync-messages <scok_id> [--folder <folder>] [--retry] --json
125
126
  audienti prospects import-batch --file prospects.csv --motion <motn_id> --assigned-user me --json
126
127
  audienti lists create --name "Target list" --json
127
128
  audienti lists routing-rules <list_id> list --json
package/src/api-client.js CHANGED
@@ -66,6 +66,13 @@ export class AudientiClient {
66
66
  return this.requestJson(accountPath(accountId, ["social_cookies"], query));
67
67
  }
68
68
 
69
+ syncSocialCookieMessages(accountId, socialCookieId, body = {}) {
70
+ return this.requestJson(accountPath(accountId, ["social_cookies", socialCookieId, "sync_messages"]), {
71
+ method: "POST",
72
+ body
73
+ });
74
+ }
75
+
69
76
  userActivity(accountId, userId, query = {}) {
70
77
  return this.requestJson(accountPath(accountId, ["operations", "users", userId, "activity"], query));
71
78
  }
@@ -339,6 +346,10 @@ export class AudientiClient {
339
346
  return this.requestJson(accountPath(accountId, ["motions", motionId]));
340
347
  }
341
348
 
349
+ motionSignals(accountId, motionId) {
350
+ return this.requestJson(accountPath(accountId, ["motions", motionId, "signals"]));
351
+ }
352
+
342
353
  createMotion(accountId, body) {
343
354
  return this.requestJson(accountPath(accountId, ["motions"]), {
344
355
  method: "POST",
package/src/cli.js CHANGED
@@ -176,6 +176,7 @@ const TOOLS_LINKEDIN_REVIEW_USAGE = "Usage: audienti tools linkedin-review --url
176
176
  const TOOLS_LINKEDIN_REVIEW_REPORTS_USAGE = "Usage: audienti tools linkedin-review reports [--limit <n>] [--json] [--account <acct_id>]";
177
177
  const TOOLS_LINKEDIN_REVIEW_SHOW_USAGE = "Usage: audienti tools linkedin-review show <rprt_id> [--json] [--account <acct_id>]";
178
178
  const TOOLS_LINKEDIN_REVIEW_STATUS_USAGE = "Usage: audienti tools linkedin-review status <rprt_id> [--json] [--account <acct_id>]";
179
+ const SOCIAL_COOKIES_SYNC_MESSAGES_USAGE = "Usage: audienti social-cookies sync-messages <scok_id> [--folder <folder>] [--retry] [--json] [--account <acct_id>]";
179
180
  const COHORT_STAGE_ORDER = [
180
181
  "identified",
181
182
  "pre_connect",
@@ -269,6 +270,7 @@ async function dispatch(argv, context) {
269
270
  if (normalizedResource === "users" && action === "activity") return usersActivity(rest, context, { accountOverride });
270
271
  if (normalizedResource === "users" && action === "automation") return usersAutomation(rest, context, { accountOverride });
271
272
  if (normalizedResource === "setup" && action === "play" && rest[0] === "preflight") return setupPlayPreflight(rest.slice(1), context, { accountOverride });
273
+ if (normalizedResource === "social-cookies" && action === "sync-messages") return socialCookiesSyncMessages(rest, context, { accountOverride });
272
274
  if (normalizedResource === "offers" && action === "list") return offersList(rest, context, { accountOverride });
273
275
  if (normalizedResource === "offers" && action === "show") return offersShow(rest, context, { accountOverride });
274
276
  if (normalizedResource === "offers" && action === "create") return offersCreate(rest, context, { accountOverride });
@@ -311,6 +313,7 @@ async function dispatch(argv, context) {
311
313
  if (normalizedResource === "lists" && ["routing-rules", "rules"].includes(action)) return listsRoutingRules(rest, context, { accountOverride });
312
314
  if (normalizedResource === "motions" && action === "list") return motionsList(rest, context, { accountOverride });
313
315
  if (normalizedResource === "motions" && action === "show") return motionsShow(rest, context, { accountOverride });
316
+ if (normalizedResource === "motions" && action === "signals") return motionsSignals(rest, context, { accountOverride });
314
317
  if (normalizedResource === "motions" && action === "status") return motionsStatus(rest, context, { accountOverride });
315
318
  if (normalizedResource === "motions" && ["abm-companies", "company-filters"].includes(action)) return motionsAbmCompanies(rest, context, { accountOverride });
316
319
  if (normalizedResource === "motions" && action === "analytics") return motionsAnalytics(rest, context, { accountOverride });
@@ -479,6 +482,7 @@ function normalizeResource(resource) {
479
482
  if (resource === "principals") return "users";
480
483
  if (resource === "writers") return "writer";
481
484
  if (resource === "company_rules" || resource === "company-rules") return "company-rules";
485
+ if (resource === "social_cookie" || resource === "social_cookies") return "social-cookies";
482
486
  return resource === "plays" ? "motions" : resource;
483
487
  }
484
488
 
@@ -1033,6 +1037,26 @@ async function setupPlayPreflight(args, context, { accountOverride } = {}) {
1033
1037
  renderSetupPlayPreflight(payload, context);
1034
1038
  }
1035
1039
 
1040
+ async function socialCookiesSyncMessages(args, context, { accountOverride } = {}) {
1041
+ const { values, positionals } = parseCommandArgs(args, {
1042
+ ...jsonOptions(),
1043
+ folder: { type: "string" },
1044
+ retry: { type: "boolean" }
1045
+ });
1046
+ if (positionals.length !== 1 || (values.folder !== undefined && !String(values.folder).trim())) {
1047
+ throw new CommandError(SOCIAL_COOKIES_SYNC_MESSAGES_USAGE);
1048
+ }
1049
+
1050
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1051
+ const payload = await client.syncSocialCookieMessages(accountId, positionals[0], compactObject({
1052
+ folder: values.folder,
1053
+ retry: values.retry ? true : undefined
1054
+ }));
1055
+ if (values.json) return writeJson(context.stdout, payload);
1056
+
1057
+ renderSocialCookieSync(payload, context);
1058
+ }
1059
+
1036
1060
  async function offersList(args, context, { accountOverride } = {}) {
1037
1061
  const { values, positionals } = parseCommandArgs(args, jsonOptions());
1038
1062
  if (positionals.length > 0) throw new CommandError("Usage: audienti offers list [--json] [--account <acct_id>]");
@@ -1774,6 +1798,17 @@ async function motionsShow(args, context, { accountOverride } = {}) {
1774
1798
  renderMotion(motion, context);
1775
1799
  }
1776
1800
 
1801
+ async function motionsSignals(args, context, { accountOverride } = {}) {
1802
+ const { values, positionals } = parseCommandArgs(args, jsonOptions());
1803
+ if (positionals.length !== 1) throw new CommandError("Usage: audienti motions signals <motn_id> [--json] [--account <acct_id>]");
1804
+
1805
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1806
+ const payload = await client.motionSignals(accountId, positionals[0]);
1807
+ if (values.json) return writeJson(context.stdout, payload);
1808
+
1809
+ renderMotionDiscoverySignals(payload, context);
1810
+ }
1811
+
1777
1812
  async function motionsStatus(args, context, { accountOverride } = {}) {
1778
1813
  const { values, positionals } = parseCommandArgs(args, jsonOptions());
1779
1814
  if (positionals.length !== 1) throw new CommandError("Usage: audienti motions status <motn_id> [--json] [--account <acct_id>]");
@@ -5441,6 +5476,30 @@ function renderSetupPlayPreflight(payload, context) {
5441
5476
  }
5442
5477
  }
5443
5478
 
5479
+ function renderSocialCookieSync(payload, context) {
5480
+ const requestStatus = payload?.requested ? "accepted" : "not requested";
5481
+ const coalesced = payload?.coalesced ? " (coalesced)" : "";
5482
+ writeLine(context.stdout, `Email sync: ${requestStatus}${coalesced} for ${display(payload?.social_cookie_id)}`);
5483
+ if (payload?.reason) writeLine(context.stdout, `Reason: ${payload.reason}`);
5484
+
5485
+ const states = Array.isArray(payload?.sync_states) ? payload.sync_states : [];
5486
+ if (states.length === 0) return writeLine(context.stdout, "No durable sync state returned.");
5487
+
5488
+ writeLine(context.stdout, "FOLDER\tSTATUS\tGENERATION\tATTEMPTS\tDUE\tLAST SUCCESS\tLAST ERROR\tSYNCED THROUGH");
5489
+ for (const state of states) {
5490
+ writeLine(context.stdout, [
5491
+ display(state?.canonical_folder),
5492
+ display(state?.status),
5493
+ display(state?.generation, "0"),
5494
+ display(state?.attempts, "0"),
5495
+ display(state?.due_at),
5496
+ display(state?.last_success_at),
5497
+ display(state?.last_error_code),
5498
+ display(state?.last_synced_through_at)
5499
+ ].join("\t"));
5500
+ }
5501
+ }
5502
+
5444
5503
  function renderSetupLinkedInCapabilities(capabilities, context) {
5445
5504
  const storedBoolean = (value) => value === true ? "yes" : value === false ? "no" : "unknown";
5446
5505
  writeLine(
@@ -5833,6 +5892,7 @@ function renderMotion(motion, context) {
5833
5892
  if (Array.isArray(motion?.inbound_channels)) writeLine(context.stdout, `Inbound channels: ${display(motion.inbound_channels.join(", "), "-")}`);
5834
5893
  renderMotionLopaProfiles(motion?.lopa_profiles, context);
5835
5894
  renderMotionSignalRows(motion?.signal_rows, context);
5895
+ renderMotionDiscoverySignals(motion?.discovery_signals, context);
5836
5896
  renderMotionAbmCompanies(motion, context, { showEmpty: false });
5837
5897
  if (Array.isArray(motion?.play_tags)) writeLine(context.stdout, `Tags: ${display(motion.play_tags.join(", "), "-")}`);
5838
5898
  if (motion?.principal_account_user?.id) {
@@ -5867,6 +5927,32 @@ function renderMotionSignalRows(rows, context) {
5867
5927
  }
5868
5928
  }
5869
5929
 
5930
+ function renderMotionDiscoverySignals(payload, context) {
5931
+ if (!payload || typeof payload !== "object") return;
5932
+
5933
+ const rows = Array.isArray(payload?.signals) ? payload.signals : [];
5934
+ const counts = payload?.counts || {};
5935
+ const motionId = payload?.prefix_id ? ` (${display(payload.prefix_id)})` : "";
5936
+
5937
+ writeLine(context.stdout, `Motion-owned discovery signals${motionId}: ${display(counts.total, "0")} total, ${display(counts.actionable, "0")} actionable, ${display(counts.pending, "0")} pending, ${display(counts.held, "0")} held`);
5938
+ if (rows.length === 0) return;
5939
+
5940
+ for (const row of rows) {
5941
+ const provenance = Array.isArray(row.agent_provenance)
5942
+ ? row.agent_provenance.map((entry) => [entry.agent_name, entry.agent_id].filter(Boolean).join(" · ")).filter(Boolean).join(", ")
5943
+ : "";
5944
+ const status = row.actionable ? "actionable" : display(row.status, "pending");
5945
+ const sourceCounts = row.source_counts || {};
5946
+ const detail = [
5947
+ row.canonical_url || row.topic?.slug || row.source_key,
5948
+ `sources=${display(sourceCounts.total, "0")}/${display(sourceCounts.accepted, "0")}/${display(sourceCounts.rejected, "0")}`,
5949
+ provenance ? `agents=${provenance}` : null,
5950
+ !row.actionable && row.motion_attribution_reason ? `reason=${singleLine(row.motion_attribution_reason)}` : null
5951
+ ].filter(Boolean).join("; ");
5952
+ writeLine(context.stdout, `- [${status}] ${display(row.type, "signal")}: ${display(row.label, "unnamed")} (${detail})`);
5953
+ }
5954
+ }
5955
+
5870
5956
  function renderMotionAbmCompanies(payload, context, { showEmpty = true } = {}) {
5871
5957
  const rows = Array.isArray(payload?.abm_companies) ? payload.abm_companies : [];
5872
5958
  if (rows.length === 0) {
@@ -6054,16 +6140,32 @@ function renderProspectReplan(payload, context) {
6054
6140
  const current = payload?.current || {};
6055
6141
  const replanned = payload?.replanned || {};
6056
6142
  const status = replanStatusLabel(payload);
6143
+ const reason = payload?.reason_code || payload?.refresh?.reason;
6057
6144
 
6058
6145
  writeLine(context.stdout, `Replan ${status} for ${display(prospect.display_name || prospect.name)} (${display(prospect.prefix_id)}).`);
6059
6146
  writeLine(context.stdout, `Changed: ${payload?.changed ? "yes" : "no"}`);
6060
- if (payload?.reason_code) writeLine(context.stdout, `Reason: ${payload.reason_code}`);
6147
+ if (reason) writeLine(context.stdout, `Reason: ${reason}`);
6061
6148
  writeLine(context.stdout, `Current: ${formatCoachAction(current.next_action)}`);
6062
6149
  writeLine(context.stdout, `Replanned: ${formatCoachAction(replanned.next_action)}`);
6063
6150
  if (replanned.rationale) writeLine(context.stdout, `Rationale: ${replanned.rationale}`);
6064
6151
  if (replanned.guidance) writeLine(context.stdout, `Guidance: ${replanned.guidance}`);
6065
- if (payload?.status === "dry_run") writeLine(context.stdout, "Run again with --apply to persist this plan.");
6066
- if (payload?.status === "coach_error") writeLine(context.stdout, "The plan was not persisted. Fix the coach error and rerun with --apply.");
6152
+ if (payload?.status === "dry_run") {
6153
+ writeLine(context.stdout, "Run again with --apply to persist this plan.");
6154
+ } else if (payload?.status === "coach_error") {
6155
+ writeLine(context.stdout, "The plan was not persisted. Fix the coach error and rerun with --apply.");
6156
+ } else if (payload?.status === "pending") {
6157
+ writeLine(context.stdout, "The replan request was recorded but is not applied yet. Retry after the refresh reason clears.");
6158
+ } else if (payload?.status === "busy") {
6159
+ writeLine(context.stdout, "Planner execution is busy. No replan was applied; retry later.");
6160
+ } else if (payload?.status === "failed") {
6161
+ writeLine(context.stdout, "The request was recorded, but the Planner could not apply it. Review the reason before retrying.");
6162
+ } else if (payload?.status === "evaluation_error") {
6163
+ writeLine(context.stdout, "Planner evaluation failed. Fix the evaluation error and rerun with --apply.");
6164
+ } else if (payload?.status === "unsupported_plan_version") {
6165
+ writeLine(context.stdout, "This membership uses an unsupported Planner version. Migrate it before retrying.");
6166
+ } else if (payload?.status === "not_applied") {
6167
+ writeLine(context.stdout, "The plan was not persisted. Review the reason before retrying.");
6168
+ }
6067
6169
  }
6068
6170
 
6069
6171
  function renderProspectReenrich(payload, context) {
@@ -6121,12 +6223,24 @@ function formatObjectCounts(value) {
6121
6223
  .join(", ");
6122
6224
  }
6123
6225
 
6226
+ const REPLAN_STATUS_LABELS = Object.freeze({
6227
+ dry_run: "dry run",
6228
+ coach_error: "coach error",
6229
+ not_applied: "not applied",
6230
+ pending: "pending",
6231
+ busy: "busy",
6232
+ failed: "failed",
6233
+ evaluation_error: "evaluation error",
6234
+ unsupported_plan_version: "unsupported plan version"
6235
+ });
6236
+
6124
6237
  function replanStatusLabel(payload) {
6125
- if (payload?.status === "dry_run") return "dry run";
6126
- if (payload?.status === "coach_error") return "coach error";
6127
- if (payload?.status === "not_applied") return "not applied";
6238
+ const rawStatus = payload?.status;
6239
+ if (Object.prototype.hasOwnProperty.call(REPLAN_STATUS_LABELS, rawStatus)) {
6240
+ return REPLAN_STATUS_LABELS[rawStatus];
6241
+ }
6128
6242
 
6129
- return payload?.applied ? "applied" : "dry run";
6243
+ return payload?.applied ? "applied" : "not applied";
6130
6244
  }
6131
6245
 
6132
6246
  function formatCoachAction(nextAction = {}) {
@@ -8305,6 +8419,9 @@ const HELP_TOPICS = new Map([
8305
8419
  " audienti users automation show <account_user_id|me>",
8306
8420
  " audienti users automation update <account_user_id|me> --payload <file.json> [--apply]",
8307
8421
  "",
8422
+ " Email sync",
8423
+ " audienti social-cookies sync-messages <scok_id> [--folder <folder>] [--retry]",
8424
+ "",
8308
8425
  " Motions / plays",
8309
8426
  " audienti motions list",
8310
8427
  " audienti motions show <motn_id>",
@@ -8616,6 +8733,41 @@ const HELP_TOPICS = new Map([
8616
8733
  " acct_id: string Account prefix id, for example acct_abc123"
8617
8734
  ].join("\n")],
8618
8735
 
8736
+ ["social-cookies", [
8737
+ "Usage:",
8738
+ " audienti social-cookies sync-messages <scok_id> [--folder <folder>] [--retry] [--json] [--account <acct_id>]",
8739
+ "",
8740
+ "Status: implemented",
8741
+ "",
8742
+ "Commands:",
8743
+ " audienti social-cookies sync-messages Request the shared durable email sync slot",
8744
+ "",
8745
+ "Run `audienti social-cookies sync-messages help` for the request and output contract."
8746
+ ].join("\n")],
8747
+
8748
+ ["social-cookies sync-messages", [
8749
+ "Usage:",
8750
+ " audienti social-cookies sync-messages <scok_id> [--folder <folder>] [--retry] [--json] [--account <acct_id>]",
8751
+ "",
8752
+ "Status: implemented",
8753
+ "",
8754
+ "Purpose:",
8755
+ " Request one account-scoped email sync through the server's durable cookie/folder slot.",
8756
+ "",
8757
+ "Input shape:",
8758
+ " scok_id: string Social cookie prefix id or numeric id",
8759
+ " folder: string Provider folder; omitted means the server's INBOX default",
8760
+ " retry: flag Explicitly reopen an exhausted or credential-held slot",
8761
+ "",
8762
+ "Output shape:",
8763
+ " requested, coalesced, reason, sync_states Server response and durable slot status",
8764
+ " sync_states Includes status, generation, attempts, due, last error, and watermark",
8765
+ "",
8766
+ "API:",
8767
+ " POST /api/v1/accounts/:account_id/social_cookies/:id/sync_messages.json",
8768
+ " The CLI only requests work; provider traversal remains owned by the server worker."
8769
+ ].join("\n")],
8770
+
8619
8771
  ["accounts list", [
8620
8772
  "Usage:",
8621
8773
  " audienti accounts list [--json] [--account <acct_id>]",
@@ -9774,6 +9926,7 @@ const HELP_TOPICS = new Map([
9774
9926
  "Usage:",
9775
9927
  " audienti motions list [--tag <tag>] [--json]",
9776
9928
  " audienti motions show <motn_id> [--json]",
9929
+ " audienti motions signals <motn_id> [--json]",
9777
9930
  " audienti motions status <motn_id> [--json]",
9778
9931
  " audienti motions run-discovery <motn_id> [--target-count <n>] [--json]",
9779
9932
  " audienti motions quick-start --url <company_url> [--confirm] [--wait] [--json]",
@@ -9869,12 +10022,30 @@ const HELP_TOPICS = new Map([
9869
10022
  " inbound_channels: enabled inbound collectors for inbound motions",
9870
10023
  " lopa_profiles[]: tracked LinkedIn profile rows for LOPA motions",
9871
10024
  " signal_rows[]: outbound signal configuration rows",
10025
+ " discovery_signals: Motion-owned Topic/profile signals with attribution state and Agent provenance",
9872
10026
  " abm_companies[]: motion-scoped positive company filter rows",
9873
10027
  "",
9874
10028
  "API:",
9875
10029
  " GET /api/v1/accounts/:account_id/motions/:id.json"
9876
10030
  ].join("\n")],
9877
10031
 
10032
+ ["motions signals", [
10033
+ "Usage:",
10034
+ " audienti motions signals <motn_id> [--json] [--account <acct_id>]",
10035
+ "",
10036
+ "Status: implemented",
10037
+ "",
10038
+ "Purpose:",
10039
+ " Read the Topic and tracked-profile signals owned by one Motion.",
10040
+ " Actionable rows have exact Motion attribution; pending and held rows remain inspectable with their reason and evidence.",
10041
+ "",
10042
+ "Output:",
10043
+ " Shows attribution state, canonical source URL or Topic, accepted/rejected source counts, and retained Agent provenance.",
10044
+ "",
10045
+ "API:",
10046
+ " GET /api/v1/accounts/:account_id/motions/:id/signals.json"
10047
+ ].join("\n")],
10048
+
9878
10049
  ["motions abm-companies", [
9879
10050
  "Usage:",
9880
10051
  ` ${MOTIONS_ABM_COMPANIES_LIST_USAGE.slice("Usage: ".length)}`,