@audienti/cli 0.1.70 → 0.1.74

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,37 @@ All notable changes to the Audienti CLI are documented here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.1.74] - 2026-09-27
8
+
9
+ ### Added
10
+
11
+ - Add `accounts show`, `company-rules show`, and `linkedin-lookups <kind>` so account details, single company rules, and LinkedIn targeting values are reachable from the CLI.
12
+ - Add Motion-owned discovery signal tracking to `motions show` and the `motions signals` command, including attribution holds, source counts, and retained Agent provenance.
13
+
14
+ ### Fixed
15
+
16
+ - LinkedIn lookups accept API tokens and prefixed ICP ids, so CLI lookups no longer fail with an authentication error.
17
+
18
+ ## [0.1.73] - 2026-09-26
19
+
20
+ ### Added
21
+
22
+ - Add the account-scoped `motions signals` command for inspecting Motion-owned Topic and profile signals.
23
+
24
+ ## [0.1.72] - 2026-09-26
25
+
26
+ ### Added
27
+
28
+ - 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.
29
+
30
+ ## [0.1.71] - 2026-09-24
31
+
32
+ ### Changed
33
+
34
+ - 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.
35
+
36
+ - Prospect replan API responses now use the canonical Planner contract for admitted memberships.
37
+
7
38
  ## [0.1.70] - 2026-09-18
8
39
 
9
40
  ### 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.74",
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
@@ -40,6 +40,14 @@ export class AudientiClient {
40
40
  return this.requestJson("/api/v1/accounts.json");
41
41
  }
42
42
 
43
+ account(accountId) {
44
+ return this.requestJson(accountPath(accountId, []));
45
+ }
46
+
47
+ linkedinLookup(kind, query = {}) {
48
+ return this.requestJson(apiPath(["linkedin_lookups", kind], query));
49
+ }
50
+
43
51
  mcp(message) {
44
52
  return this.requestJson("/mcp", {
45
53
  method: "POST",
@@ -66,6 +74,13 @@ export class AudientiClient {
66
74
  return this.requestJson(accountPath(accountId, ["social_cookies"], query));
67
75
  }
68
76
 
77
+ syncSocialCookieMessages(accountId, socialCookieId, body = {}) {
78
+ return this.requestJson(accountPath(accountId, ["social_cookies", socialCookieId, "sync_messages"]), {
79
+ method: "POST",
80
+ body
81
+ });
82
+ }
83
+
69
84
  userActivity(accountId, userId, query = {}) {
70
85
  return this.requestJson(accountPath(accountId, ["operations", "users", userId, "activity"], query));
71
86
  }
@@ -180,6 +195,10 @@ export class AudientiClient {
180
195
  return this.requestJson(accountPath(accountId, ["company_rules"]));
181
196
  }
182
197
 
198
+ companyRule(accountId, ruleId) {
199
+ return this.requestJson(accountPath(accountId, ["company_rules", ruleId]));
200
+ }
201
+
183
202
  createCompanyRule(accountId, body) {
184
203
  return this.requestJson(accountPath(accountId, ["company_rules"]), {
185
204
  method: "POST",
@@ -339,6 +358,10 @@ export class AudientiClient {
339
358
  return this.requestJson(accountPath(accountId, ["motions", motionId]));
340
359
  }
341
360
 
361
+ motionSignals(accountId, motionId) {
362
+ return this.requestJson(accountPath(accountId, ["motions", motionId, "signals"]));
363
+ }
364
+
342
365
  createMotion(accountId, body) {
343
366
  return this.requestJson(accountPath(accountId, ["motions"]), {
344
367
  method: "POST",
@@ -808,11 +831,13 @@ export class AudientiClient {
808
831
  }
809
832
 
810
833
  function accountPath(accountId, segments, query = {}) {
834
+ return apiPath(["accounts", accountId, ...segments], query);
835
+ }
836
+
837
+ function apiPath(segments, query = {}) {
811
838
  const encodedSegments = [
812
839
  "api",
813
840
  "v1",
814
- "accounts",
815
- accountId,
816
841
  ...segments
817
842
  ].map((segment) => encodeURIComponent(segment));
818
843
  const searchParams = new URLSearchParams();
package/src/cli.js CHANGED
@@ -112,6 +112,18 @@ const COMPANY_RULES_CREATE_USAGE = "Usage: audienti company-rules create (--link
112
112
  const COMPANY_RULES_UPDATE_USAGE = "Usage: audienti company-rules update <rule_id> [--linkedin-url <url>] [--domain <domain>] [--disposition <monitor|nurture|not_fit|reject>] [--name <text>] [--user <account_user_id|email|me|none>] [--note <text>] [--json] [--account <acct_id>]";
113
113
  const COMPANY_RULES_REMOVE_USAGE = "Usage: audienti company-rules remove <rule_id> [--json] [--account <acct_id>]";
114
114
  const COMPANY_RULES_APPLY_USAGE = "Usage: audienti company-rules apply (<rule_id>|--all) [--json] [--account <acct_id>]";
115
+ const COMPANY_RULES_SHOW_USAGE = "Usage: audienti company-rules show <rule_id> [--json] [--account <acct_id>]";
116
+ const ACCOUNTS_SHOW_USAGE = "Usage: audienti accounts show [<acct_id>] [--json] [--account <acct_id>]";
117
+ const LINKEDIN_LOOKUPS_USAGE = "Usage: audienti linkedin-lookups <company-sizes|company-types|functions|industries|job-titles|locations|seniorities> [--query <text>] [--icp <icp_id>] [--json] [--account <acct_id>]";
118
+ const LINKEDIN_LOOKUP_KINDS = new Map([
119
+ ["company-sizes", { path: "company_sizes", query: "none", icp: true }],
120
+ ["company-types", { path: "company_types", query: "none", icp: true }],
121
+ ["functions", { path: "functions", query: "required", icp: true }],
122
+ ["industries", { path: "industries", query: "required", icp: false }],
123
+ ["job-titles", { path: "job_titles", query: "required", icp: true }],
124
+ ["locations", { path: "locations", query: "required", icp: true }],
125
+ ["seniorities", { path: "seniorities", query: "optional", icp: true }]
126
+ ]);
115
127
  const TASKS_LIST_USAGE = "Usage: audienti tasks list [--status <open|completed>] [--limit <n>] [--json] [--account <acct_id>]";
116
128
  const TASKS_ADD_USAGE = "Usage: audienti tasks add --title <text> --due <time> [--prospect <prsp_id>] [--list <list_id>] [--assigned-user <id|me>] [--notes <text>] [--json] [--account <acct_id>]";
117
129
  const TASKS_COMPLETE_USAGE = "Usage: audienti tasks complete <ptsk_id> [--json] [--account <acct_id>]";
@@ -176,6 +188,7 @@ const TOOLS_LINKEDIN_REVIEW_USAGE = "Usage: audienti tools linkedin-review --url
176
188
  const TOOLS_LINKEDIN_REVIEW_REPORTS_USAGE = "Usage: audienti tools linkedin-review reports [--limit <n>] [--json] [--account <acct_id>]";
177
189
  const TOOLS_LINKEDIN_REVIEW_SHOW_USAGE = "Usage: audienti tools linkedin-review show <rprt_id> [--json] [--account <acct_id>]";
178
190
  const TOOLS_LINKEDIN_REVIEW_STATUS_USAGE = "Usage: audienti tools linkedin-review status <rprt_id> [--json] [--account <acct_id>]";
191
+ const SOCIAL_COOKIES_SYNC_MESSAGES_USAGE = "Usage: audienti social-cookies sync-messages <scok_id> [--folder <folder>] [--retry] [--json] [--account <acct_id>]";
179
192
  const COHORT_STAGE_ORDER = [
180
193
  "identified",
181
194
  "pre_connect",
@@ -264,11 +277,14 @@ async function dispatch(argv, context) {
264
277
  if (normalizedResource === "update" && action === "check") return updateCheck(rest, context);
265
278
  if (normalizedResource === "accounts" && action === "list") return accountsList(rest, context, { accountOverride });
266
279
  if (normalizedResource === "accounts" && action === "select") return accountsSelect(rest, context);
280
+ if (normalizedResource === "accounts" && action === "show") return accountsShow(rest, context, { accountOverride });
281
+ if (normalizedResource === "linkedin-lookups") return linkedinLookups(action, rest, context, { accountOverride });
267
282
  if (normalizedResource === "users" && action === "list") return usersList(rest, context, { accountOverride });
268
283
  if (normalizedResource === "users" && action === "select") return usersSelect(rest, context, { accountOverride });
269
284
  if (normalizedResource === "users" && action === "activity") return usersActivity(rest, context, { accountOverride });
270
285
  if (normalizedResource === "users" && action === "automation") return usersAutomation(rest, context, { accountOverride });
271
286
  if (normalizedResource === "setup" && action === "play" && rest[0] === "preflight") return setupPlayPreflight(rest.slice(1), context, { accountOverride });
287
+ if (normalizedResource === "social-cookies" && action === "sync-messages") return socialCookiesSyncMessages(rest, context, { accountOverride });
272
288
  if (normalizedResource === "offers" && action === "list") return offersList(rest, context, { accountOverride });
273
289
  if (normalizedResource === "offers" && action === "show") return offersShow(rest, context, { accountOverride });
274
290
  if (normalizedResource === "offers" && action === "create") return offersCreate(rest, context, { accountOverride });
@@ -289,6 +305,7 @@ async function dispatch(argv, context) {
289
305
  if (normalizedResource === "dnc" && action === "import") return dncImport(rest, context, { accountOverride });
290
306
  if (normalizedResource === "dnc" && ["remove", "delete"].includes(action)) return dncRemove(rest, context, { accountOverride });
291
307
  if (normalizedResource === "company-rules" && action === "list") return companyRulesList(rest, context, { accountOverride });
308
+ if (normalizedResource === "company-rules" && action === "show") return companyRulesShow(rest, context, { accountOverride });
292
309
  if (normalizedResource === "company-rules" && action === "create") return companyRulesCreate(rest, context, { accountOverride });
293
310
  if (normalizedResource === "company-rules" && action === "update") return companyRulesUpdate(rest, context, { accountOverride });
294
311
  if (normalizedResource === "company-rules" && ["remove", "delete"].includes(action)) return companyRulesRemove(rest, context, { accountOverride });
@@ -311,6 +328,7 @@ async function dispatch(argv, context) {
311
328
  if (normalizedResource === "lists" && ["routing-rules", "rules"].includes(action)) return listsRoutingRules(rest, context, { accountOverride });
312
329
  if (normalizedResource === "motions" && action === "list") return motionsList(rest, context, { accountOverride });
313
330
  if (normalizedResource === "motions" && action === "show") return motionsShow(rest, context, { accountOverride });
331
+ if (normalizedResource === "motions" && action === "signals") return motionsSignals(rest, context, { accountOverride });
314
332
  if (normalizedResource === "motions" && action === "status") return motionsStatus(rest, context, { accountOverride });
315
333
  if (normalizedResource === "motions" && ["abm-companies", "company-filters"].includes(action)) return motionsAbmCompanies(rest, context, { accountOverride });
316
334
  if (normalizedResource === "motions" && action === "analytics") return motionsAnalytics(rest, context, { accountOverride });
@@ -479,6 +497,8 @@ function normalizeResource(resource) {
479
497
  if (resource === "principals") return "users";
480
498
  if (resource === "writers") return "writer";
481
499
  if (resource === "company_rules" || resource === "company-rules") return "company-rules";
500
+ if (resource === "linkedin_lookups" || resource === "linkedin-lookups") return "linkedin-lookups";
501
+ if (resource === "social_cookie" || resource === "social_cookies") return "social-cookies";
482
502
  return resource === "plays" ? "motions" : resource;
483
503
  }
484
504
 
@@ -798,6 +818,64 @@ async function accountsList(args, context, { accountOverride } = {}) {
798
818
  }
799
819
  }
800
820
 
821
+ async function accountsShow(args, context, { accountOverride } = {}) {
822
+ const { values, positionals } = parseCommandArgs(args, jsonOptions());
823
+ if (positionals.length > 1) throw new CommandError(ACCOUNTS_SHOW_USAGE);
824
+
825
+ const config = await requireAuthenticatedConfig(context);
826
+ const accountId = positionals[0] || accountOverride || config.accountId;
827
+ if (!accountId) {
828
+ throw new CommandError("No active account. Run `audienti accounts select <acct_id>`, pass `--account <acct_id>`, or give an account id.");
829
+ }
830
+
831
+ const account = await clientFromConfig(config, context).account(accountId);
832
+ if (values.json) return writeJson(context.stdout, account);
833
+
834
+ writeLine(context.stdout, `Account: ${display(account?.name)} (${display(account?.prefix_id || accountId)})`);
835
+ writeLine(context.stdout, `Personal: ${account?.personal ? "yes" : "no"}`);
836
+ writeLine(context.stdout, `Members: ${Array.isArray(account?.account_users) ? account.account_users.length : 0}`);
837
+ writeLine(context.stdout, `Created: ${display(account?.created_at, "-")}`);
838
+ }
839
+
840
+ async function linkedinLookups(kind, args, context, { accountOverride } = {}) {
841
+ const lookup = LINKEDIN_LOOKUP_KINDS.get(String(kind || "").replaceAll("_", "-"));
842
+ if (!lookup) throw new CommandError(LINKEDIN_LOOKUPS_USAGE);
843
+
844
+ const { values, positionals } = parseCommandArgs(args, {
845
+ ...jsonOptions(),
846
+ query: { type: "string" },
847
+ icp: { type: "string" }
848
+ });
849
+ const query = values.query?.trim();
850
+ const invalid = positionals.length > 0 ||
851
+ (lookup.query === "required" && !query) ||
852
+ (lookup.query === "none" && values.query !== undefined) ||
853
+ (!lookup.icp && values.icp !== undefined);
854
+ if (invalid) throw new CommandError(LINKEDIN_LOOKUPS_USAGE);
855
+
856
+ const config = await requireAuthenticatedConfig(context);
857
+ const params = { q: query };
858
+ if (values.icp) {
859
+ const accountId = accountOverride || config.accountId;
860
+ if (!accountId) {
861
+ throw new CommandError("No active account. Run `audienti accounts select <acct_id>` or pass `--account <acct_id>` to look up with --icp.");
862
+ }
863
+ params.icp_id = values.icp;
864
+ params.account_id = accountId;
865
+ }
866
+
867
+ const payload = await clientFromConfig(config, context).linkedinLookup(lookup.path, params);
868
+ if (values.json) return writeJson(context.stdout, payload);
869
+
870
+ const rows = Array.isArray(payload) ? payload : [];
871
+ if (rows.length === 0) return writeLine(context.stdout, "No results found.");
872
+
873
+ writeLine(context.stdout, "ID\tNAME\tSOURCE");
874
+ for (const row of rows) {
875
+ writeLine(context.stdout, [display(row.id, "-"), display(row.name), display(row.source, "-")].join("\t"));
876
+ }
877
+ }
878
+
801
879
  async function accountsSelect(args, context) {
802
880
  const { positionals } = parseCommandArgs(args, {});
803
881
  if (positionals.length !== 1) throw new CommandError("Usage: audienti accounts select <acct_id>");
@@ -1033,6 +1111,26 @@ async function setupPlayPreflight(args, context, { accountOverride } = {}) {
1033
1111
  renderSetupPlayPreflight(payload, context);
1034
1112
  }
1035
1113
 
1114
+ async function socialCookiesSyncMessages(args, context, { accountOverride } = {}) {
1115
+ const { values, positionals } = parseCommandArgs(args, {
1116
+ ...jsonOptions(),
1117
+ folder: { type: "string" },
1118
+ retry: { type: "boolean" }
1119
+ });
1120
+ if (positionals.length !== 1 || (values.folder !== undefined && !String(values.folder).trim())) {
1121
+ throw new CommandError(SOCIAL_COOKIES_SYNC_MESSAGES_USAGE);
1122
+ }
1123
+
1124
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1125
+ const payload = await client.syncSocialCookieMessages(accountId, positionals[0], compactObject({
1126
+ folder: values.folder,
1127
+ retry: values.retry ? true : undefined
1128
+ }));
1129
+ if (values.json) return writeJson(context.stdout, payload);
1130
+
1131
+ renderSocialCookieSync(payload, context);
1132
+ }
1133
+
1036
1134
  async function offersList(args, context, { accountOverride } = {}) {
1037
1135
  const { values, positionals } = parseCommandArgs(args, jsonOptions());
1038
1136
  if (positionals.length > 0) throw new CommandError("Usage: audienti offers list [--json] [--account <acct_id>]");
@@ -1315,6 +1413,22 @@ async function companyRulesList(args, context, { accountOverride } = {}) {
1315
1413
  renderCompanyRules(payload, context);
1316
1414
  }
1317
1415
 
1416
+ async function companyRulesShow(args, context, { accountOverride } = {}) {
1417
+ const { values, positionals } = parseCommandArgs(args, jsonOptions());
1418
+ if (positionals.length !== 1) throw new CommandError(COMPANY_RULES_SHOW_USAGE);
1419
+
1420
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1421
+ const payload = await client.companyRule(accountId, positionals[0]);
1422
+ if (values.json) return writeJson(context.stdout, payload);
1423
+
1424
+ const rule = payload?.company_rule;
1425
+ writeLine(context.stdout, `Company rule ${display(rule?.name || rule?.domain || rule?.linkedin_company_identifier)} (${display(rule?.prefix_id || rule?.id)})`);
1426
+ writeLine(context.stdout, `Disposition: ${display(rule?.disposition)} | Scope: ${companyRuleScopeLabel(rule)} | Active: ${rule?.active ? "yes" : "no"}`);
1427
+ writeLine(context.stdout, `LinkedIn: ${display(rule?.linkedin_company_url || rule?.linkedin_company_identifier, "-")}`);
1428
+ writeLine(context.stdout, `Domain: ${display(rule?.domain, "-")}`);
1429
+ if (rule?.note) writeLine(context.stdout, `Note: ${rule.note}`);
1430
+ }
1431
+
1318
1432
  async function companyRulesCreate(args, context, { accountOverride } = {}) {
1319
1433
  const { values, positionals } = parseCommandArgs(args, companyRuleOptions());
1320
1434
  if (positionals.length > 0 || (!values["linkedin-url"] && !values.domain) || !values.disposition) {
@@ -1774,6 +1888,17 @@ async function motionsShow(args, context, { accountOverride } = {}) {
1774
1888
  renderMotion(motion, context);
1775
1889
  }
1776
1890
 
1891
+ async function motionsSignals(args, context, { accountOverride } = {}) {
1892
+ const { values, positionals } = parseCommandArgs(args, jsonOptions());
1893
+ if (positionals.length !== 1) throw new CommandError("Usage: audienti motions signals <motn_id> [--json] [--account <acct_id>]");
1894
+
1895
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1896
+ const payload = await client.motionSignals(accountId, positionals[0]);
1897
+ if (values.json) return writeJson(context.stdout, payload);
1898
+
1899
+ renderMotionDiscoverySignals(payload, context);
1900
+ }
1901
+
1777
1902
  async function motionsStatus(args, context, { accountOverride } = {}) {
1778
1903
  const { values, positionals } = parseCommandArgs(args, jsonOptions());
1779
1904
  if (positionals.length !== 1) throw new CommandError("Usage: audienti motions status <motn_id> [--json] [--account <acct_id>]");
@@ -5441,6 +5566,30 @@ function renderSetupPlayPreflight(payload, context) {
5441
5566
  }
5442
5567
  }
5443
5568
 
5569
+ function renderSocialCookieSync(payload, context) {
5570
+ const requestStatus = payload?.requested ? "accepted" : "not requested";
5571
+ const coalesced = payload?.coalesced ? " (coalesced)" : "";
5572
+ writeLine(context.stdout, `Email sync: ${requestStatus}${coalesced} for ${display(payload?.social_cookie_id)}`);
5573
+ if (payload?.reason) writeLine(context.stdout, `Reason: ${payload.reason}`);
5574
+
5575
+ const states = Array.isArray(payload?.sync_states) ? payload.sync_states : [];
5576
+ if (states.length === 0) return writeLine(context.stdout, "No durable sync state returned.");
5577
+
5578
+ writeLine(context.stdout, "FOLDER\tSTATUS\tGENERATION\tATTEMPTS\tDUE\tLAST SUCCESS\tLAST ERROR\tSYNCED THROUGH");
5579
+ for (const state of states) {
5580
+ writeLine(context.stdout, [
5581
+ display(state?.canonical_folder),
5582
+ display(state?.status),
5583
+ display(state?.generation, "0"),
5584
+ display(state?.attempts, "0"),
5585
+ display(state?.due_at),
5586
+ display(state?.last_success_at),
5587
+ display(state?.last_error_code),
5588
+ display(state?.last_synced_through_at)
5589
+ ].join("\t"));
5590
+ }
5591
+ }
5592
+
5444
5593
  function renderSetupLinkedInCapabilities(capabilities, context) {
5445
5594
  const storedBoolean = (value) => value === true ? "yes" : value === false ? "no" : "unknown";
5446
5595
  writeLine(
@@ -5833,6 +5982,7 @@ function renderMotion(motion, context) {
5833
5982
  if (Array.isArray(motion?.inbound_channels)) writeLine(context.stdout, `Inbound channels: ${display(motion.inbound_channels.join(", "), "-")}`);
5834
5983
  renderMotionLopaProfiles(motion?.lopa_profiles, context);
5835
5984
  renderMotionSignalRows(motion?.signal_rows, context);
5985
+ renderMotionDiscoverySignals(motion?.discovery_signals, context);
5836
5986
  renderMotionAbmCompanies(motion, context, { showEmpty: false });
5837
5987
  if (Array.isArray(motion?.play_tags)) writeLine(context.stdout, `Tags: ${display(motion.play_tags.join(", "), "-")}`);
5838
5988
  if (motion?.principal_account_user?.id) {
@@ -5867,6 +6017,32 @@ function renderMotionSignalRows(rows, context) {
5867
6017
  }
5868
6018
  }
5869
6019
 
6020
+ function renderMotionDiscoverySignals(payload, context) {
6021
+ if (!payload || typeof payload !== "object") return;
6022
+
6023
+ const rows = Array.isArray(payload?.signals) ? payload.signals : [];
6024
+ const counts = payload?.counts || {};
6025
+ const motionId = payload?.prefix_id ? ` (${display(payload.prefix_id)})` : "";
6026
+
6027
+ 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`);
6028
+ if (rows.length === 0) return;
6029
+
6030
+ for (const row of rows) {
6031
+ const provenance = Array.isArray(row.agent_provenance)
6032
+ ? row.agent_provenance.map((entry) => [entry.agent_name, entry.agent_id].filter(Boolean).join(" · ")).filter(Boolean).join(", ")
6033
+ : "";
6034
+ const status = row.actionable ? "actionable" : display(row.status, "pending");
6035
+ const sourceCounts = row.source_counts || {};
6036
+ const detail = [
6037
+ row.canonical_url || row.topic?.slug || row.source_key,
6038
+ `sources=${display(sourceCounts.total, "0")}/${display(sourceCounts.accepted, "0")}/${display(sourceCounts.rejected, "0")}`,
6039
+ provenance ? `agents=${provenance}` : null,
6040
+ !row.actionable && row.motion_attribution_reason ? `reason=${singleLine(row.motion_attribution_reason)}` : null
6041
+ ].filter(Boolean).join("; ");
6042
+ writeLine(context.stdout, `- [${status}] ${display(row.type, "signal")}: ${display(row.label, "unnamed")} (${detail})`);
6043
+ }
6044
+ }
6045
+
5870
6046
  function renderMotionAbmCompanies(payload, context, { showEmpty = true } = {}) {
5871
6047
  const rows = Array.isArray(payload?.abm_companies) ? payload.abm_companies : [];
5872
6048
  if (rows.length === 0) {
@@ -6054,16 +6230,32 @@ function renderProspectReplan(payload, context) {
6054
6230
  const current = payload?.current || {};
6055
6231
  const replanned = payload?.replanned || {};
6056
6232
  const status = replanStatusLabel(payload);
6233
+ const reason = payload?.reason_code || payload?.refresh?.reason;
6057
6234
 
6058
6235
  writeLine(context.stdout, `Replan ${status} for ${display(prospect.display_name || prospect.name)} (${display(prospect.prefix_id)}).`);
6059
6236
  writeLine(context.stdout, `Changed: ${payload?.changed ? "yes" : "no"}`);
6060
- if (payload?.reason_code) writeLine(context.stdout, `Reason: ${payload.reason_code}`);
6237
+ if (reason) writeLine(context.stdout, `Reason: ${reason}`);
6061
6238
  writeLine(context.stdout, `Current: ${formatCoachAction(current.next_action)}`);
6062
6239
  writeLine(context.stdout, `Replanned: ${formatCoachAction(replanned.next_action)}`);
6063
6240
  if (replanned.rationale) writeLine(context.stdout, `Rationale: ${replanned.rationale}`);
6064
6241
  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.");
6242
+ if (payload?.status === "dry_run") {
6243
+ writeLine(context.stdout, "Run again with --apply to persist this plan.");
6244
+ } else if (payload?.status === "coach_error") {
6245
+ writeLine(context.stdout, "The plan was not persisted. Fix the coach error and rerun with --apply.");
6246
+ } else if (payload?.status === "pending") {
6247
+ writeLine(context.stdout, "The replan request was recorded but is not applied yet. Retry after the refresh reason clears.");
6248
+ } else if (payload?.status === "busy") {
6249
+ writeLine(context.stdout, "Planner execution is busy. No replan was applied; retry later.");
6250
+ } else if (payload?.status === "failed") {
6251
+ writeLine(context.stdout, "The request was recorded, but the Planner could not apply it. Review the reason before retrying.");
6252
+ } else if (payload?.status === "evaluation_error") {
6253
+ writeLine(context.stdout, "Planner evaluation failed. Fix the evaluation error and rerun with --apply.");
6254
+ } else if (payload?.status === "unsupported_plan_version") {
6255
+ writeLine(context.stdout, "This membership uses an unsupported Planner version. Migrate it before retrying.");
6256
+ } else if (payload?.status === "not_applied") {
6257
+ writeLine(context.stdout, "The plan was not persisted. Review the reason before retrying.");
6258
+ }
6067
6259
  }
6068
6260
 
6069
6261
  function renderProspectReenrich(payload, context) {
@@ -6121,12 +6313,24 @@ function formatObjectCounts(value) {
6121
6313
  .join(", ");
6122
6314
  }
6123
6315
 
6316
+ const REPLAN_STATUS_LABELS = Object.freeze({
6317
+ dry_run: "dry run",
6318
+ coach_error: "coach error",
6319
+ not_applied: "not applied",
6320
+ pending: "pending",
6321
+ busy: "busy",
6322
+ failed: "failed",
6323
+ evaluation_error: "evaluation error",
6324
+ unsupported_plan_version: "unsupported plan version"
6325
+ });
6326
+
6124
6327
  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";
6328
+ const rawStatus = payload?.status;
6329
+ if (Object.prototype.hasOwnProperty.call(REPLAN_STATUS_LABELS, rawStatus)) {
6330
+ return REPLAN_STATUS_LABELS[rawStatus];
6331
+ }
6128
6332
 
6129
- return payload?.applied ? "applied" : "dry run";
6333
+ return payload?.applied ? "applied" : "not applied";
6130
6334
  }
6131
6335
 
6132
6336
  function formatCoachAction(nextAction = {}) {
@@ -8297,6 +8501,7 @@ const HELP_TOPICS = new Map([
8297
8501
  " Setup & identity",
8298
8502
  " audienti auth status",
8299
8503
  " audienti config list",
8504
+ " audienti accounts show [acct_id]",
8300
8505
  " audienti update check",
8301
8506
  " audienti setup play preflight",
8302
8507
  " audienti users list",
@@ -8305,6 +8510,9 @@ const HELP_TOPICS = new Map([
8305
8510
  " audienti users automation show <account_user_id|me>",
8306
8511
  " audienti users automation update <account_user_id|me> --payload <file.json> [--apply]",
8307
8512
  "",
8513
+ " Email sync",
8514
+ " audienti social-cookies sync-messages <scok_id> [--folder <folder>] [--retry]",
8515
+ "",
8308
8516
  " Motions / plays",
8309
8517
  " audienti motions list",
8310
8518
  " audienti motions show <motn_id>",
@@ -8381,9 +8589,11 @@ const HELP_TOPICS = new Map([
8381
8589
  " audienti icps add-tag <icp_id> <tag>",
8382
8590
  " audienti icps remove-tag <icp_id> <tag>",
8383
8591
  " audienti companies search --query <text>",
8592
+ " audienti linkedin-lookups <kind> [--query <text>]",
8384
8593
  " audienti dnc list",
8385
8594
  " audienti dnc add <email|citation_id|profile_url>",
8386
8595
  " audienti company-rules list",
8596
+ " audienti company-rules show <rule_id>",
8387
8597
  " audienti company-rules create (--linkedin-url <url> | --domain <domain>) --disposition <state>",
8388
8598
  "",
8389
8599
  " Writer",
@@ -8608,6 +8818,7 @@ const HELP_TOPICS = new Map([
8608
8818
  ["accounts", [
8609
8819
  "Usage:",
8610
8820
  " audienti accounts list [--json]",
8821
+ " audienti accounts show [<acct_id>] [--json]",
8611
8822
  " audienti accounts select <acct_id>",
8612
8823
  "",
8613
8824
  "Status: implemented",
@@ -8616,6 +8827,41 @@ const HELP_TOPICS = new Map([
8616
8827
  " acct_id: string Account prefix id, for example acct_abc123"
8617
8828
  ].join("\n")],
8618
8829
 
8830
+ ["social-cookies", [
8831
+ "Usage:",
8832
+ " audienti social-cookies sync-messages <scok_id> [--folder <folder>] [--retry] [--json] [--account <acct_id>]",
8833
+ "",
8834
+ "Status: implemented",
8835
+ "",
8836
+ "Commands:",
8837
+ " audienti social-cookies sync-messages Request the shared durable email sync slot",
8838
+ "",
8839
+ "Run `audienti social-cookies sync-messages help` for the request and output contract."
8840
+ ].join("\n")],
8841
+
8842
+ ["social-cookies sync-messages", [
8843
+ "Usage:",
8844
+ " audienti social-cookies sync-messages <scok_id> [--folder <folder>] [--retry] [--json] [--account <acct_id>]",
8845
+ "",
8846
+ "Status: implemented",
8847
+ "",
8848
+ "Purpose:",
8849
+ " Request one account-scoped email sync through the server's durable cookie/folder slot.",
8850
+ "",
8851
+ "Input shape:",
8852
+ " scok_id: string Social cookie prefix id or numeric id",
8853
+ " folder: string Provider folder; omitted means the server's INBOX default",
8854
+ " retry: flag Explicitly reopen an exhausted or credential-held slot",
8855
+ "",
8856
+ "Output shape:",
8857
+ " requested, coalesced, reason, sync_states Server response and durable slot status",
8858
+ " sync_states Includes status, generation, attempts, due, last error, and watermark",
8859
+ "",
8860
+ "API:",
8861
+ " POST /api/v1/accounts/:account_id/social_cookies/:id/sync_messages.json",
8862
+ " The CLI only requests work; provider traversal remains owned by the server worker."
8863
+ ].join("\n")],
8864
+
8619
8865
  ["accounts list", [
8620
8866
  "Usage:",
8621
8867
  " audienti accounts list [--json] [--account <acct_id>]",
@@ -8631,6 +8877,51 @@ const HELP_TOPICS = new Map([
8631
8877
  " audienti accounts list --json"
8632
8878
  ].join("\n")],
8633
8879
 
8880
+ ["accounts show", [
8881
+ "Usage:",
8882
+ ` ${ACCOUNTS_SHOW_USAGE.slice("Usage: ".length)}`,
8883
+ "",
8884
+ "Status: implemented",
8885
+ "",
8886
+ "API:",
8887
+ " GET /api/v1/accounts/:id.json",
8888
+ "",
8889
+ "Input shape:",
8890
+ " acct_id: string Account prefix id; defaults to --account or the selected account",
8891
+ "",
8892
+ "Output shape:",
8893
+ " id, prefix_id, name, personal, owner_id, account_users[], created_at, updated_at"
8894
+ ].join("\n")],
8895
+
8896
+ ["linkedin-lookups", [
8897
+ "Usage:",
8898
+ ` ${LINKEDIN_LOOKUPS_USAGE.slice("Usage: ".length)}`,
8899
+ "",
8900
+ "Status: implemented",
8901
+ "",
8902
+ "Purpose:",
8903
+ " Look up LinkedIn targeting values (the ids used in ICP filters).",
8904
+ "",
8905
+ "Kinds:",
8906
+ " job-titles, functions, locations --query required",
8907
+ " industries --query required; --icp not supported",
8908
+ " seniorities --query optional filter",
8909
+ " company-sizes, company-types no --query; returns the full list",
8910
+ "",
8911
+ "ICP option:",
8912
+ " --icp <icp_id> uses values saved on that ICP of the active account (or --account).",
8913
+ " For company-sizes, company-types and seniorities it also saves the list onto that ICP.",
8914
+ "",
8915
+ "API:",
8916
+ " GET /api/v1/linkedin_lookups/<kind>.json?q=<text>&icp_id=<icp_id>",
8917
+ "",
8918
+ "Output shape:",
8919
+ " [{ id, name, source? }]",
8920
+ "",
8921
+ "Example:",
8922
+ " audienti linkedin-lookups job-titles --query \"head of sales\" --json"
8923
+ ].join("\n")],
8924
+
8634
8925
  ["accounts select", [
8635
8926
  "Usage:",
8636
8927
  " audienti accounts select <acct_id>",
@@ -9261,6 +9552,7 @@ const HELP_TOPICS = new Map([
9261
9552
  ["company-rules", [
9262
9553
  "Usage:",
9263
9554
  " audienti company-rules list [--json]",
9555
+ ` ${COMPANY_RULES_SHOW_USAGE.slice("Usage: ".length)}`,
9264
9556
  ` ${COMPANY_RULES_CREATE_USAGE.slice("Usage: ".length)}`,
9265
9557
  ` ${COMPANY_RULES_UPDATE_USAGE.slice("Usage: ".length)}`,
9266
9558
  ` ${COMPANY_RULES_REMOVE_USAGE.slice("Usage: ".length)}`,
@@ -9282,6 +9574,16 @@ const HELP_TOPICS = new Map([
9282
9574
  " GET /api/v1/accounts/:account_id/company_rules.json"
9283
9575
  ].join("\n")],
9284
9576
 
9577
+ ["company-rules show", [
9578
+ "Usage:",
9579
+ ` ${COMPANY_RULES_SHOW_USAGE.slice("Usage: ".length)}`,
9580
+ "",
9581
+ "Status: implemented",
9582
+ "",
9583
+ "API:",
9584
+ " GET /api/v1/accounts/:account_id/company_rules/:id.json"
9585
+ ].join("\n")],
9586
+
9285
9587
  ["company-rules create", [
9286
9588
  "Usage:",
9287
9589
  ` ${COMPANY_RULES_CREATE_USAGE.slice("Usage: ".length)}`,
@@ -9774,6 +10076,7 @@ const HELP_TOPICS = new Map([
9774
10076
  "Usage:",
9775
10077
  " audienti motions list [--tag <tag>] [--json]",
9776
10078
  " audienti motions show <motn_id> [--json]",
10079
+ " audienti motions signals <motn_id> [--json]",
9777
10080
  " audienti motions status <motn_id> [--json]",
9778
10081
  " audienti motions run-discovery <motn_id> [--target-count <n>] [--json]",
9779
10082
  " audienti motions quick-start --url <company_url> [--confirm] [--wait] [--json]",
@@ -9869,12 +10172,30 @@ const HELP_TOPICS = new Map([
9869
10172
  " inbound_channels: enabled inbound collectors for inbound motions",
9870
10173
  " lopa_profiles[]: tracked LinkedIn profile rows for LOPA motions",
9871
10174
  " signal_rows[]: outbound signal configuration rows",
10175
+ " discovery_signals: Motion-owned Topic/profile signals with attribution state and Agent provenance",
9872
10176
  " abm_companies[]: motion-scoped positive company filter rows",
9873
10177
  "",
9874
10178
  "API:",
9875
10179
  " GET /api/v1/accounts/:account_id/motions/:id.json"
9876
10180
  ].join("\n")],
9877
10181
 
10182
+ ["motions signals", [
10183
+ "Usage:",
10184
+ " audienti motions signals <motn_id> [--json] [--account <acct_id>]",
10185
+ "",
10186
+ "Status: implemented",
10187
+ "",
10188
+ "Purpose:",
10189
+ " Read the Topic and tracked-profile signals owned by one Motion.",
10190
+ " Actionable rows have exact Motion attribution; pending and held rows remain inspectable with their reason and evidence.",
10191
+ "",
10192
+ "Output:",
10193
+ " Shows attribution state, canonical source URL or Topic, accepted/rejected source counts, and retained Agent provenance.",
10194
+ "",
10195
+ "API:",
10196
+ " GET /api/v1/accounts/:account_id/motions/:id/signals.json"
10197
+ ].join("\n")],
10198
+
9878
10199
  ["motions abm-companies", [
9879
10200
  "Usage:",
9880
10201
  ` ${MOTIONS_ABM_COMPANIES_LIST_USAGE.slice("Usage: ".length)}`,