@audienti/cli 0.1.45 → 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/src/cli.js CHANGED
@@ -33,7 +33,7 @@ const DEFAULT_PROFILE_IDENTIFIERS = [
33
33
  "email/profile"
34
34
  ];
35
35
  const DELETE_CONFIRMATION_VALUES = new Set(["yes", "true", "y"]);
36
- const MOTION_STATUS_VALUES = new Set(["draft", "preparing", "active", "paused", "archived"]);
36
+ const MOTION_STATUS_VALUES = new Set(["draft", "preparing", "active", "closing", "paused", "archived"]);
37
37
  const PROSPECT_INACTIVE_REASON_VALUES = new Set(["nurture", "non_responsive", "not_fit", "bad_data_404"]);
38
38
  const PROSPECT_STATUS_VALUES = new Set(["active", ...PROSPECT_INACTIVE_REASON_VALUES, "rejected"]);
39
39
  const OUTBOUND_METRICS_OUTCOMES = new Set([
@@ -51,6 +51,7 @@ const PROSPECTS_ADD_STEER_USAGE = "Usage: audienti prospects add-steer <prsp_id>
51
51
  const PROSPECTS_ADD_PROFILE_USAGE = "Usage: audienti prospects add-profile <prsp_id> --url <profile_url|email|phone> [--json] [--account <acct_id>]";
52
52
  const PROSPECTS_REPORT_BAD_PROFILE_USAGE = "Usage: audienti prospects report-bad-profile <prsp_id> <prof_id|citation_id> [--json] [--account <acct_id>]";
53
53
  const PROSPECTS_ASSIGN_USAGE = "Usage: audienti prospects assign <prsp_id> [prsp_id...] --assigned-user <id|me|unassign> [--json] [--account <acct_id>]";
54
+ const PROSPECTS_MOVE_ACCOUNT_USAGE = "Usage: audienti prospects move-account <prsp_id> --target-account <acct_id> [--assigned-user <id|me>] [--target-motion <motn_id>] [--target-list <list_id>] [--apply] [--json] [--account <source_acct_id>]";
54
55
  const PROSPECTS_SET_STATUS_USAGE = "Usage: audienti prospects set-status <prsp_id> --status <active|nurture|non_responsive|not_fit|bad_data_404|rejected> [--json] [--account <acct_id>]";
55
56
  const PROSPECTS_REPLAN_USAGE = "Usage: audienti prospects replan <prsp_id> [--apply] [--json] [--account <acct_id>]";
56
57
  const PROSPECTS_REENRICH_USAGE = "Usage: audienti prospects reenrich <prsp_id> [--profile <prof_id|citation_id>] [--apply] [--json] [--account <acct_id>]";
@@ -64,6 +65,10 @@ const PROSPECTS_CHECK_USAGE = "Usage: audienti prospects check [--json|--csv] [f
64
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>]";
65
66
  const OPERATOR_FAILED_DRAFTS_USAGE = "Usage: audienti operator failed-drafts [--json] [filters] [--account <acct_id>]";
66
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*$/;
67
72
  const DNC_ADD_USAGE = "Usage: audienti dnc add <email|citation_id|profile_url> [--json] [--account <acct_id>]";
68
73
  const DNC_IMPORT_USAGE = "Usage: audienti dnc import --file <txt|csv> [--json] [--account <acct_id>]";
69
74
  const DNC_REMOVE_USAGE = "Usage: audienti dnc remove <dnc_entry_id> [--json] [--account <acct_id>]";
@@ -75,6 +80,8 @@ const TASKS_LIST_USAGE = "Usage: audienti tasks list [--status <open|completed>]
75
80
  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>]";
76
81
  const TASKS_COMPLETE_USAGE = "Usage: audienti tasks complete <ptsk_id> [--json] [--account <acct_id>]";
77
82
  const USERS_ACTIVITY_USAGE = "Usage: audienti users activity [account_user_id|me] [--mode <actor|account_usage|related>] [--window <24h|7d|30d>] [--platform <linkedin|email|gmail>] [--query <text>] [--limit <n>] [--page <n>] [--json] [--account <acct_id>]";
83
+ const USERS_AUTOMATION_SHOW_USAGE = "Usage: audienti users automation show <account_user_id|me> [--platform linkedin] [--json] [--account <acct_id>]";
84
+ const USERS_AUTOMATION_UPDATE_USAGE = "Usage: audienti users automation update <account_user_id|me> --payload <file.json> [--apply] [--json] [--account <acct_id>]";
78
85
  const SETUP_PLAY_PREFLIGHT_USAGE = "Usage: audienti setup play preflight [--principal <account_user_id|email|name|me>] [--platform linkedin] [--json] [--account <acct_id>]";
79
86
  const OFFERS_SHOW_USAGE = "Usage: audienti offers show <offr_id> [--json] [--account <acct_id>]";
80
87
  const OFFERS_UPDATE_USAGE = "Usage: audienti offers update <offr_id> [--name <text>] [--description <text>] [--url <url>] [--json] [--account <acct_id>]";
@@ -82,7 +89,7 @@ const OFFERS_DELETE_USAGE = "Usage: audienti offers delete <offr_id> --confirm <
82
89
  const WRITER_TEST_RUN_USAGE = "Usage: audienti writer test-run <prsp_id> [--json] [--mode <plan|report|step>] [--branch <both|no-accept|accepted>] [--step <step_key|row_number>] [--report <rprt_id>] [--no-wait] [--timeout-seconds <n>] [--poll-interval-seconds <n>] [--account <acct_id>]";
83
90
  const WRITER_TEST_RUN_SHOW_USAGE = "Usage: audienti writer test-run show <prsp_id> <rprt_id> [--json] [--account <acct_id>]";
84
91
  const MOTIONS_ANALYTICS_USAGE = "Usage: audienti motions analytics <motn_id> [--window 30d] [--json] [--account <acct_id>]";
85
- const MOTIONS_UPDATE_USAGE = "Usage: audienti motions update <motn_id> ([--status <draft|preparing|active|paused|archived>] [--tags <tag[,tag...]>] [--own-post-engagement <true|false>] [--start-date <YYYY-MM-DD|none>] [--end-date <YYYY-MM-DD|none>] [--maximum-company-count <n|none>] | --payload <file.json>) [--json] [--account <acct_id>]";
92
+ const MOTIONS_UPDATE_USAGE = "Usage: audienti motions update <motn_id> ([--status <draft|preparing|active|closing|paused|archived>] [--tags <tag[,tag...]>] [--own-post-engagement <true|false>] [--start-date <YYYY-MM-DD|none>] [--end-date <YYYY-MM-DD|none>] [--maximum-company-count <n|none>] | --payload <file.json>) [--json] [--account <acct_id>]";
86
93
  const CONTENT_PROGRAMS_USAGE = "Usage: audienti content programs [--user <account_user_id|email|name|me>] [--json] [--account <acct_id>]";
87
94
  const CONTENT_PLAN_USAGE = "Usage: audienti content plan <cprg_id> [--week <n>] [--due] [--json] [--account <acct_id>]";
88
95
  const CONTENT_SHOW_USAGE = "Usage: audienti content show <cpwi_id> [--json] [--account <acct_id>]";
@@ -104,7 +111,10 @@ const MOTIONS_ABM_COMPANIES_LIST_USAGE = "Usage: audienti motions abm-companies
104
111
  const MOTIONS_ABM_COMPANIES_ADD_USAGE = "Usage: audienti motions abm-companies <motn_id> add (<domain_or_linkedin_url>... | --file <txt|json>) [--json] [--account <acct_id>]";
105
112
  const MOTIONS_ABM_COMPANIES_REMOVE_USAGE = "Usage: audienti motions abm-companies <motn_id> remove <row_id> [--json] [--account <acct_id>]";
106
113
  const ICPS_SHOW_USAGE = "Usage: audienti icps show <icp_id> [--json] [--account <acct_id>]";
114
+ const ICPS_LIST_USAGE = "Usage: audienti icps list [--status <active|archived|all>] [--tag <tag>] [--json] [--account <acct_id>]";
107
115
  const ICPS_UPDATE_USAGE = "Usage: audienti icps update <icp_id> ([--name <text>] [--notes <text>] [--discovery-keyword <text>] [--tags <tag[,tag...]>] | --payload <file.json>) [--json] [--account <acct_id>]";
116
+ const ICPS_ARCHIVE_USAGE = "Usage: audienti icps archive <icp_id> [--json] [--account <acct_id>]";
117
+ const ICPS_RESTORE_USAGE = "Usage: audienti icps restore <icp_id> [--json] [--account <acct_id>]";
108
118
  const ICPS_ADD_TAG_USAGE = "Usage: audienti icps add-tag <icp_id> <tag> [--json] [--account <acct_id>]";
109
119
  const ICPS_REMOVE_TAG_USAGE = "Usage: audienti icps remove-tag <icp_id> <tag> [--json] [--account <acct_id>]";
110
120
  const LISTS_ADD_TAG_USAGE = "Usage: audienti lists add-tag <list_id> <tag> [--json] [--account <acct_id>]";
@@ -119,6 +129,8 @@ const ANALYTICS_PROSPECTS_USAGE = "Usage: audienti analytics prospects [--window
119
129
  const ANALYTICS_PROSPECTS_COHORT_ANALYSIS_USAGE = "Usage: audienti analytics prospects cohort-analysis [--weeks <n>] [--window 24h] [--motion <motn_id>] [--list <list_id>] [--provenance <source>] [--user <account_user_id|email|name|me>] [--json] [--account <acct_id>]";
120
130
  const ANALYTICS_USERS_USAGE = "Usage: audienti analytics users [--user <account_user_id|email|name|me>] [--window 30d | --start YYYY-MM-DD --end YYYY-MM-DD] [--cohort-start YYYY-MM-DD --cohort-end YYYY-MM-DD] [--motion <motn_id>] [--list <list_id>] [--provenance <source>] [--platform <linkedin|email|gmail>] [--json] [--account <acct_id>]";
121
131
  const ANALYTICS_DASHBOARD_USAGE = "Usage: audienti analytics dashboard [--cohort-start YYYY-MM-DD --cohort-end YYYY-MM-DD] [--play-tag <tag>] [--motion <motn_id>] [--list <list_id>] [--offer <offr_id>] [--icp <icp_id>] [--user <account_user_id|email|name|me>] [--json] [--account <acct_id>]";
132
+ const ANALYTICS_MOTIONS_USAGE = "Usage: audienti analytics motions [--json] [--account <acct_id>]";
133
+ const ANALYTICS_ICPS_USAGE = "Usage: audienti analytics icps [--json] [--account <acct_id>]";
122
134
  const ANALYTICS_METRICS_USAGE = "Usage: audienti analytics metrics [--cohort-start YYYY-MM-DD --cohort-end YYYY-MM-DD | --cohort-preset week-to-date] [--interval <daily|weekly>] [--user <account_user_id|email|name|me>] [--motion <motn_id>] [--play-tag <tag>] [--list <list_id>] [--offer <offr_id>] [--icp <icp_id>] [--social-cookie <scok_id>] [--platform <platform>] [--action <action_key>] [--outcome <success|failure|first_attempt_success|succeeded_after_retry|failed_without_retry|failed_after_retry|in_progress|unresolved>] [--json] [--account <acct_id>]";
123
135
  const ANALYTICS_STAGES_USAGE = "Usage: audienti analytics stages [--interval weekly|monthly] [--cohort-start YYYY-MM-DD --cohort-end YYYY-MM-DD] [--play-tag <tag>] [--motion <motn_id>] [--list <list_id>] [--offer <offr_id>] [--icp <icp_id>] [--user <account_user_id|email|name|me>] [--json] [--account <acct_id>]";
124
136
  const ANALYTICS_COHORT_LIST_USAGE = "Usage: audienti analytics cohorts create-list --name <text> --start YYYY-MM-DD --end YYYY-MM-DD [--event connection_request_sent] [--user <account_user_id|email|name|me>] [--note-mode <any|with_note|blank>] [--motion <motn_id>] [--offer <offr_id>] [--icp <icp_id>] [--play-tag <tag>] [--json] [--account <acct_id>]";
@@ -218,6 +230,7 @@ async function dispatch(argv, context) {
218
230
  if (normalizedResource === "users" && action === "list") return usersList(rest, context, { accountOverride });
219
231
  if (normalizedResource === "users" && action === "select") return usersSelect(rest, context, { accountOverride });
220
232
  if (normalizedResource === "users" && action === "activity") return usersActivity(rest, context, { accountOverride });
233
+ if (normalizedResource === "users" && action === "automation") return usersAutomation(rest, context, { accountOverride });
221
234
  if (normalizedResource === "setup" && action === "play" && rest[0] === "preflight") return setupPlayPreflight(rest.slice(1), context, { accountOverride });
222
235
  if (normalizedResource === "offers" && action === "list") return offersList(rest, context, { accountOverride });
223
236
  if (normalizedResource === "offers" && action === "show") return offersShow(rest, context, { accountOverride });
@@ -226,8 +239,11 @@ async function dispatch(argv, context) {
226
239
  if (normalizedResource === "offers" && action === "delete") return offersDelete(rest, context, { accountOverride });
227
240
  if (normalizedResource === "icps" && action === "list") return icpsList(rest, context, { accountOverride });
228
241
  if (normalizedResource === "icps" && action === "show") return icpsShow(rest, context, { accountOverride });
242
+ if (normalizedResource === "icps" && action === "analytics") return analyticsIcps(rest, context, { accountOverride });
229
243
  if (normalizedResource === "icps" && action === "create") return icpsCreate(rest, context, { accountOverride });
230
244
  if (normalizedResource === "icps" && action === "update") return icpsUpdate(rest, context, { accountOverride });
245
+ if (normalizedResource === "icps" && action === "archive") return icpsLifecycleMutation("archive", rest, context, { accountOverride });
246
+ if (normalizedResource === "icps" && action === "restore") return icpsLifecycleMutation("restore", rest, context, { accountOverride });
231
247
  if (normalizedResource === "icps" && action === "add-tag") return icpsTagMutation("add", rest, context, { accountOverride });
232
248
  if (normalizedResource === "icps" && action === "remove-tag") return icpsTagMutation("remove", rest, context, { accountOverride });
233
249
  if (normalizedResource === "companies" && action === "search") return companiesSearch(rest, context, { accountOverride });
@@ -287,6 +303,7 @@ async function dispatch(argv, context) {
287
303
  if (normalizedResource === "prospects" && action === "check") return prospectsCheck(rest, context, { accountOverride });
288
304
  if (normalizedResource === "prospects" && action === "show") return prospectsShow(rest, context, { accountOverride });
289
305
  if (normalizedResource === "prospects" && action === "assign") return prospectsAssign(rest, context, { accountOverride });
306
+ if (normalizedResource === "prospects" && action === "move-account") return prospectsMoveAccount(rest, context, { accountOverride });
290
307
  if (normalizedResource === "prospects" && action === "set-status") return prospectsSetStatus(rest, context, { accountOverride });
291
308
  if (normalizedResource === "prospects" && action === "replan") return prospectsReplan(rest, context, { accountOverride });
292
309
  if (normalizedResource === "prospects" && action === "reenrich") return prospectsReenrich(rest, context, { accountOverride });
@@ -317,11 +334,16 @@ async function dispatch(argv, context) {
317
334
  if (normalizedResource === "operator" && action === "queue") return operatorQueue(rest, context, { accountOverride });
318
335
  if (normalizedResource === "operator" && action === "next") return operatorNext(rest, context, { accountOverride });
319
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 });
320
340
  if (normalizedResource === "analytics" && ["prospects", "prospect"].includes(action)) return analyticsProspects(rest, context, { accountOverride });
321
341
  if (normalizedResource === "analytics" && ["users", "user"].includes(action)) return analyticsUsers(rest, context, { accountOverride });
322
342
  if (normalizedResource === "analytics" && ["visibility", "visops"].includes(action)) return analyticsVisibility(rest, context, { accountOverride });
323
343
  if (normalizedResource === "analytics" && action === "content") return analyticsContent(rest, context, { accountOverride });
324
344
  if (normalizedResource === "analytics" && ["dashboard", "campaign", "campaigns"].includes(action)) return analyticsDashboard(rest, context, { accountOverride });
345
+ if (normalizedResource === "analytics" && ["motions", "plays"].includes(action)) return analyticsMotions(rest, context, { accountOverride });
346
+ if (normalizedResource === "analytics" && ["icps", "icp"].includes(action)) return analyticsIcps(rest, context, { accountOverride });
325
347
  if (normalizedResource === "analytics" && action === "metrics") return analyticsMetrics(rest, context, { accountOverride });
326
348
  if (normalizedResource === "analytics" && action === "stages") return analyticsStages(rest, context, { accountOverride });
327
349
  if (normalizedResource === "analytics" && ["cohorts", "cohort"].includes(action)) return analyticsCohorts(rest, context, { accountOverride });
@@ -896,6 +918,60 @@ async function usersActivity(args, context, { accountOverride } = {}) {
896
918
  renderUserActivity(payload, context);
897
919
  }
898
920
 
921
+ async function usersAutomation(args, context, { accountOverride } = {}) {
922
+ const [action, ...rest] = args;
923
+ if (action === "show") return usersAutomationShow(rest, context, { accountOverride });
924
+ if (action === "update") return usersAutomationUpdate(rest, context, { accountOverride });
925
+
926
+ throw new CommandError(`${USERS_AUTOMATION_SHOW_USAGE}\n${USERS_AUTOMATION_UPDATE_USAGE}`);
927
+ }
928
+
929
+ async function usersAutomationShow(args, context, { accountOverride } = {}) {
930
+ const { values, positionals } = parseCommandArgs(args, {
931
+ ...jsonOptions(),
932
+ platform: { type: "string" }
933
+ });
934
+ if (positionals.length !== 1) throw new CommandError(USERS_AUTOMATION_SHOW_USAGE);
935
+
936
+ const platform = automationPlatform(values.platform);
937
+ const { client, accountId, config } = await requireAccountContext(context, { accountOverride });
938
+ const accountUserId = resolveAccountUserId(positionals[0], config, { accountOverride });
939
+ const payload = await client.userAutomation(accountId, accountUserId, { platform });
940
+ if (values.json) return writeJson(context.stdout, payload);
941
+
942
+ renderUserAutomation(payload, context);
943
+ }
944
+
945
+ async function usersAutomationUpdate(args, context, { accountOverride } = {}) {
946
+ const { values, positionals } = parseCommandArgs(args, {
947
+ ...jsonOptions(),
948
+ payload: { type: "string" },
949
+ apply: { type: "boolean" }
950
+ });
951
+ if (positionals.length !== 1 || !values.payload) throw new CommandError(USERS_AUTOMATION_UPDATE_USAGE);
952
+
953
+ const platform = automationPlatform();
954
+ const requestedAutomation = await readJsonPayload(values.payload);
955
+ const { client, accountId, config } = await requireAccountContext(context, { accountOverride });
956
+ const accountUserId = resolveAccountUserId(positionals[0], config, { accountOverride });
957
+ const payload = await client.updateUserAutomation(
958
+ accountId,
959
+ accountUserId,
960
+ { automation: { ...requestedAutomation, platform, apply: Boolean(values.apply) } },
961
+ { platform }
962
+ );
963
+ if (values.json) return writeJson(context.stdout, payload);
964
+
965
+ renderUserAutomation(payload, context);
966
+ }
967
+
968
+ function automationPlatform(value = "linkedin") {
969
+ const platform = String(value || "").trim().toLowerCase();
970
+ if (platform !== "linkedin") throw new CommandError("--platform must be linkedin.");
971
+
972
+ return platform;
973
+ }
974
+
899
975
  async function setupPlayPreflight(args, context, { accountOverride } = {}) {
900
976
  const { values, positionals } = parseCommandArgs(args, {
901
977
  ...jsonOptions(),
@@ -1008,17 +1084,35 @@ async function offersDelete(args, context, { accountOverride } = {}) {
1008
1084
  async function icpsList(args, context, { accountOverride } = {}) {
1009
1085
  const { values, positionals } = parseCommandArgs(args, {
1010
1086
  ...jsonOptions(),
1011
- tag: { type: "string" }
1087
+ tag: { type: "string" },
1088
+ status: { type: "string" }
1012
1089
  });
1013
- if (positionals.length > 0) throw new CommandError("Usage: audienti icps list [--tag <tag>] [--json] [--account <acct_id>]");
1090
+ const status = values.status;
1091
+ if (positionals.length > 0 || (status && !["active", "archived", "all"].includes(status))) throw new CommandError(ICPS_LIST_USAGE);
1014
1092
 
1015
1093
  const { client, accountId } = await requireAccountContext(context, { accountOverride });
1016
- const icps = filterRecordsByTag(await client.icps(accountId), values.tag, "tags");
1094
+ const icps = filterRecordsByTag(await client.icps(accountId, { status }), values.tag, "tags");
1017
1095
  if (values.json) return writeJson(context.stdout, icps);
1018
1096
 
1019
1097
  renderIcps(icps, context);
1020
1098
  }
1021
1099
 
1100
+ async function icpsLifecycleMutation(action, args, context, { accountOverride } = {}) {
1101
+ const usageText = action === "archive" ? ICPS_ARCHIVE_USAGE : ICPS_RESTORE_USAGE;
1102
+ const { values, positionals } = parseCommandArgs(args, jsonOptions());
1103
+ if (positionals.length !== 1) throw new CommandError(usageText);
1104
+
1105
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1106
+ const result = action === "archive" ?
1107
+ await client.archiveIcp(accountId, positionals[0]) :
1108
+ await client.restoreIcp(accountId, positionals[0]);
1109
+ if (values.json) return writeJson(context.stdout, result);
1110
+
1111
+ const verb = action === "archive" ? "Archived" : "Restored";
1112
+ writeLine(context.stdout, `${verb} ICP ${display(result?.icp?.name)} (${display(result?.icp?.prefix_id)}).`);
1113
+ writeIcpLifecycleEffects(result, context);
1114
+ }
1115
+
1022
1116
  async function icpsShow(args, context, { accountOverride } = {}) {
1023
1117
  const { values, positionals } = parseCommandArgs(args, jsonOptions());
1024
1118
  if (positionals.length !== 1) throw new CommandError(ICPS_SHOW_USAGE);
@@ -2396,6 +2490,53 @@ async function prospectsShow(args, context, { accountOverride } = {}) {
2396
2490
  renderProspect(prospect, context);
2397
2491
  }
2398
2492
 
2493
+ async function prospectsMoveAccount(args, context, { accountOverride } = {}) {
2494
+ const { values, positionals } = parseCommandArgs(args, {
2495
+ ...jsonOptions(),
2496
+ "target-account": { type: "string" },
2497
+ "assigned-user": { type: "string" },
2498
+ "target-motion": { type: "string" },
2499
+ "target-list": { type: "string" },
2500
+ apply: { type: "boolean" }
2501
+ });
2502
+ if (positionals.length !== 1 || !values["target-account"]) {
2503
+ throw new CommandError(PROSPECTS_MOVE_ACCOUNT_USAGE);
2504
+ }
2505
+
2506
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
2507
+ const prospectId = positionals[0];
2508
+ const request = compactObject({
2509
+ target_account_id: values["target-account"],
2510
+ assigned_user_id: values["assigned-user"],
2511
+ target_motion_id: values["target-motion"],
2512
+ target_list_id: values["target-list"]
2513
+ });
2514
+ const preview = await performProspectAccountMove(() =>
2515
+ client.moveProspectAccount(accountId, prospectId, { ...request, apply: false }));
2516
+
2517
+ if (!values.apply) {
2518
+ if (values.json) writeJson(context.stdout, preview.payload);
2519
+ else renderProspectAccountMove(preview.payload, context, { applying: false });
2520
+ return preview.rejected ? 1 : 0;
2521
+ }
2522
+
2523
+ if (preview.rejected || preview.payload?.success !== true || preview.payload?.eligible !== true || !preview.payload?.manifest_digest) {
2524
+ if (values.json) writeJson(context.stdout, preview.payload);
2525
+ else renderProspectAccountMove(preview.payload, context, { applying: true });
2526
+ return 1;
2527
+ }
2528
+
2529
+ const final = await performProspectAccountMove(() => client.moveProspectAccount(accountId, prospectId, {
2530
+ ...request,
2531
+ apply: true,
2532
+ manifest_digest: preview.payload.manifest_digest
2533
+ }));
2534
+ if (values.json) writeJson(context.stdout, final.payload);
2535
+ else renderProspectAccountMove(final.payload, context, { applying: true });
2536
+
2537
+ return final.rejected || final.payload?.applied !== true ? 1 : 0;
2538
+ }
2539
+
2399
2540
  async function prospectsReplan(args, context, { accountOverride } = {}) {
2400
2541
  const { values, positionals } = parseCommandArgs(args, {
2401
2542
  ...jsonOptions(),
@@ -3080,6 +3221,60 @@ async function operatorQueue(args, context, { accountOverride } = {}) {
3080
3221
  renderOperatorQueue(payload, context);
3081
3222
  }
3082
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
+
3083
3278
  async function operatorFailedDrafts(args, context, { accountOverride } = {}) {
3084
3279
  if (args[0] === "requeue") {
3085
3280
  return operatorFailedDraftsRequeue(args.slice(1), context, { accountOverride });
@@ -3271,6 +3466,28 @@ async function analyticsDashboard(args, context, { accountOverride } = {}) {
3271
3466
  renderAnalyticsDashboard(payload, context);
3272
3467
  }
3273
3468
 
3469
+ async function analyticsMotions(args, context, { accountOverride } = {}) {
3470
+ const { values, positionals } = parseCommandArgs(args, jsonOptions());
3471
+ if (positionals.length > 0) throw new CommandError(ANALYTICS_MOTIONS_USAGE);
3472
+
3473
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
3474
+ const payload = await client.analyticsMotions(accountId);
3475
+ if (values.json) return writeJson(context.stdout, payload);
3476
+
3477
+ renderAnalyticsMotions(payload, context);
3478
+ }
3479
+
3480
+ async function analyticsIcps(args, context, { accountOverride } = {}) {
3481
+ const { values, positionals } = parseCommandArgs(args, jsonOptions());
3482
+ if (positionals.length > 0) throw new CommandError(ANALYTICS_ICPS_USAGE);
3483
+
3484
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
3485
+ const payload = await client.analyticsIcps(accountId);
3486
+ if (values.json) return writeJson(context.stdout, payload);
3487
+
3488
+ renderAnalyticsIcps(payload, context);
3489
+ }
3490
+
3274
3491
  async function analyticsMetrics(args, context, { accountOverride } = {}) {
3275
3492
  const { values, positionals } = parseCommandArgs(args, analyticsMetricsOptions());
3276
3493
  if (positionals.length > 0) throw new CommandError(ANALYTICS_METRICS_USAGE);
@@ -4571,6 +4788,77 @@ function renderUserActivity(payload, context) {
4571
4788
  }
4572
4789
  }
4573
4790
 
4791
+ function renderUserAutomation(payload, context) {
4792
+ const accountUser = payload?.account_user || {};
4793
+ const socialCookie = payload?.social_cookie || {};
4794
+ const snapshot = payload?.current || payload?.after || {};
4795
+ const mode = payload?.applied === true ? "applied" : (payload?.preview === true ? "preview" : "current");
4796
+ const platform = socialCookie.service_identifier || "linkedin";
4797
+
4798
+ writeLine(
4799
+ context.stdout,
4800
+ `Automation ${mode}: ${accountUserLabel(accountUser)} | ${platform} | policy ${display(snapshot.policy_source, "unknown")}`
4801
+ );
4802
+ renderUserAutomationSchedule(snapshot, context);
4803
+ renderUserAutomationControls(snapshot, context);
4804
+ renderUserAutomationLimits(snapshot?.limits, context);
4805
+
4806
+ if (payload?.preview === true) {
4807
+ writeLine(context.stdout, "No changes applied. Review the preview, then rerun with --apply.");
4808
+ } else if (payload?.applied === true && payload?.audit_id) {
4809
+ writeLine(context.stdout, `Audit: ${payload.audit_id}`);
4810
+ }
4811
+ }
4812
+
4813
+ function renderUserAutomationSchedule(snapshot, context) {
4814
+ if (!snapshot?.time_zone && snapshot?.in_working_hours === undefined) return;
4815
+
4816
+ const parts = [display(snapshot.time_zone, "unknown timezone")];
4817
+ if (snapshot.in_working_hours !== undefined) {
4818
+ parts.push(snapshot.in_working_hours ? "in working hours" : "outside working hours");
4819
+ }
4820
+ if (snapshot.today_window?.summary) parts.push(`today ${snapshot.today_window.summary}`);
4821
+ writeLine(context.stdout, `Schedule: ${parts.join(" | ")}`);
4822
+ }
4823
+
4824
+ function renderUserAutomationControls(snapshot, context) {
4825
+ const controls = snapshot?.controls || {};
4826
+ const entries = [
4827
+ ["automatic sending", controls.automatic_sending_enabled],
4828
+ ["visibility operations", controls.visibility_operations_autopilot_enabled],
4829
+ ["risk cooldown", snapshot?.risk_cooldown_enabled]
4830
+ ].filter(([, enabled]) => enabled !== undefined);
4831
+ if (entries.length === 0) return;
4832
+
4833
+ writeLine(context.stdout, `Controls: ${entries.map(([label, enabled]) => `${label} ${enabled ? "on" : "off"}`).join(" | ")}`);
4834
+ }
4835
+
4836
+ function renderUserAutomationLimits(limits, context) {
4837
+ const categories = Array.isArray(limits?.categories) ? limits.categories : [];
4838
+ const categorySummaries = categories.map(automationDailyLimitSummary).filter(Boolean);
4839
+ if (categorySummaries.length > 0) {
4840
+ writeLine(context.stdout, `Daily category limits: ${categorySummaries.join(" | ")}`);
4841
+ }
4842
+
4843
+ const aggregateSummary = automationDailyLimitSummary(limits?.aggregate_visibility);
4844
+ if (aggregateSummary) writeLine(context.stdout, `Aggregate visibility: ${aggregateSummary.replace(/^Aggregate visibility\s+/i, "")}`);
4845
+
4846
+ const bindingLimits = Array.isArray(limits?.binding_limits) ? limits.binding_limits.filter(Boolean) : [];
4847
+ if (bindingLimits.length > 0) writeLine(context.stdout, `Binding limits: ${bindingLimits.join(", ")}`);
4848
+ }
4849
+
4850
+ function automationDailyLimitSummary(limit) {
4851
+ if (!limit || typeof limit !== "object") return null;
4852
+
4853
+ const label = display(limit.label || limit.category, "Limit");
4854
+ const used = limit.used?.daily ?? limit.usage?.daily;
4855
+ const effective = limit.effective?.daily;
4856
+ const remaining = limit.remaining?.daily;
4857
+ if (used === undefined && effective === undefined && remaining === undefined) return null;
4858
+
4859
+ return `${label} ${display(used, "?")}/${display(effective, "?")} daily (${display(remaining, "?")} remaining)`;
4860
+ }
4861
+
4574
4862
  function renderSetupPlayPreflight(payload, context) {
4575
4863
  const accountUser = payload?.account_user || {};
4576
4864
  const socialCookie = payload?.social_cookie || null;
@@ -4581,6 +4869,7 @@ function renderSetupPlayPreflight(payload, context) {
4581
4869
  `Setup preflight: ${display(payload?.platform, "linkedin")} for ${display(accountUser.name || accountUser.email)} (${display(accountUser.id)})`
4582
4870
  );
4583
4871
  writeLine(context.stdout, `Status: ${payload?.ready ? "ready" : "blocked"} (${display(payload?.reason, "unknown")})`);
4872
+ renderSetupUserLocation(accountUser.location, context);
4584
4873
 
4585
4874
  if (socialCookie) {
4586
4875
  writeLine(
@@ -4590,6 +4879,7 @@ function renderSetupPlayPreflight(payload, context) {
4590
4879
  writeLine(context.stdout, `Connection status: ${display(socialCookie.status)}`);
4591
4880
  writeLine(context.stdout, `Mapped to account: ${socialCookie.accessible_to_account ? "yes" : "no"}`);
4592
4881
  writeLine(context.stdout, `Actionable: ${socialCookie.actionable ? "yes" : "no"}`);
4882
+ renderSetupProxyLocation(socialCookie.proxy_location, context);
4593
4883
  renderSetupAutomationSummary(socialCookie.automation || {}, context);
4594
4884
  } else {
4595
4885
  writeLine(context.stdout, "Connected account: none");
@@ -4614,6 +4904,101 @@ function renderSetupAutomationSummary(automation, context) {
4614
4904
  ];
4615
4905
 
4616
4906
  writeLine(context.stdout, `Automation: ${summary.join(", ")}`);
4907
+ if (automation.time_zone || automation.today_window || automation.in_working_hours !== undefined) {
4908
+ writeLine(
4909
+ context.stdout,
4910
+ [
4911
+ `Working hours: ${display(automation.time_zone, "unknown timezone")}`,
4912
+ `today ${display(automation.today_window?.summary, "unknown")}`,
4913
+ `currently ${automation.in_working_hours ? "inside" : "outside"}`
4914
+ ].join(", ")
4915
+ );
4916
+ }
4917
+ renderSetupPacingSummary(automation.pacing, context);
4918
+ }
4919
+
4920
+ function renderSetupUserLocation(location, context) {
4921
+ const country = location?.country_code || "not configured";
4922
+ const place = [location?.city, location?.state_code].filter(Boolean).join(", ");
4923
+ const details = [place, location?.time_zone].filter(Boolean).join("; ");
4924
+ writeLine(context.stdout, `User location: ${country}${details ? ` (${details})` : ""}`);
4925
+ }
4926
+
4927
+ function renderSetupProxyLocation(proxyLocation, context) {
4928
+ const configured = proxyLocation?.configured || {};
4929
+ const configuredCountry = configured.country_code || "not configured";
4930
+ const configuredPlace = [configured.city, configured.state_code].filter(Boolean).join(", ");
4931
+ const configuredDetails = [configuredPlace, configured.postal_code].filter(Boolean).join(" ");
4932
+ writeLine(
4933
+ context.stdout,
4934
+ `Cookie proxy location: ${configuredCountry}${configuredDetails ? ` (${configuredDetails})` : ""}`
4935
+ );
4936
+
4937
+ const effective = proxyLocation?.effective || {};
4938
+ const effectivePlace = [effective.country_code, effective.region, effective.city].filter(Boolean).join(", ");
4939
+ if (!effectivePlace && proxyLocation?.source && proxyLocation.source !== "unavailable") {
4940
+ writeLine(context.stdout, `Effective proxy: not verified (${proxyLocation.source})`);
4941
+ return;
4942
+ }
4943
+ if (!effectivePlace || proxyLocation?.source === "unavailable") {
4944
+ writeLine(context.stdout, "Effective proxy: unavailable");
4945
+ return;
4946
+ }
4947
+
4948
+ const evidence = [proxyLocation?.source, effective.checked_at ? `checked ${effective.checked_at}` : null]
4949
+ .filter(Boolean)
4950
+ .join("; ");
4951
+ writeLine(context.stdout, `Effective proxy: ${effectivePlace}${evidence ? ` (${evidence})` : ""}`);
4952
+ }
4953
+
4954
+ function renderSetupPacingSummary(pacing, context) {
4955
+ if (!pacing) return;
4956
+
4957
+ const quotas = pacing.weekly_quotas || {};
4958
+ writeLine(
4959
+ context.stdout,
4960
+ [
4961
+ `Weekly quotas: profile visits ${formatSetupQuota(quotas.profile_visits)}`,
4962
+ `invitations ${formatSetupQuota(quotas.invitations)}`,
4963
+ `messages ${formatSetupQuota(quotas.messages)}`
4964
+ ].join(", ")
4965
+ );
4966
+ const motionActiveDays = Array.isArray(pacing.motion_active_days) ? pacing.motion_active_days : [];
4967
+ writeLine(context.stdout, `Motion active days: ${motionActiveDays.join(", ") || "none"}`);
4968
+ writeLine(context.stdout, `Outstanding invitation cap: ${display(pacing.outstanding_invitation_cap)}`);
4969
+
4970
+ const ramp = pacing.ramp_config || {};
4971
+ writeLine(
4972
+ context.stdout,
4973
+ `Invitation ramp: ${display(ramp.starting_daily_invitations)}/day start, +${display(ramp.weekly_increment)}/week`
4974
+ );
4975
+
4976
+ const current = pacing.current || {};
4977
+ const inventoryBlocked = Boolean(current.inventory_blocking_reason);
4978
+ writeLine(
4979
+ context.stdout,
4980
+ [
4981
+ `${inventoryBlocked ? "Invitation pacing headroom" : "Invitation capacity"}: ${display(current.daily_usage)} used today`,
4982
+ `${display(current.daily_target)} daily target`,
4983
+ `${display(current.ramp_limit_today)} ramp limit today`,
4984
+ `${display(current.effective_available)} ${inventoryBlocked ? "headroom" : "available"} (binding: ${display(current.binding_constraint)})`
4985
+ ].join(", ")
4986
+ );
4987
+ if (current.current_outstanding !== undefined || current.remaining_outstanding_slots !== undefined) {
4988
+ writeLine(
4989
+ context.stdout,
4990
+ `Outstanding invitations: ${display(current.current_outstanding)} current, ${display(current.remaining_outstanding_slots)} slots remaining`
4991
+ );
4992
+ }
4993
+ if (current.inventory_blocking_reason) {
4994
+ writeLine(context.stdout, `Invitation inventory: blocked (${current.inventory_blocking_reason})`);
4995
+ }
4996
+ }
4997
+
4998
+ function formatSetupQuota(value) {
4999
+ if (value === null) return "unlimited";
5000
+ if (value === undefined) return "unknown";
5001
+ return display(value);
4617
5002
  }
4618
5003
 
4619
5004
  function renderCountRows(context, label, rows) {
@@ -4647,13 +5032,14 @@ function renderOffer(offer, context) {
4647
5032
  function renderIcps(icps, context) {
4648
5033
  if (!Array.isArray(icps) || icps.length === 0) return writeLine(context.stdout, "No ICPs found.");
4649
5034
 
4650
- writeLine(context.stdout, "ICP ID\tNAME\tTAGS\tDISCOVERY KEYWORD\tSENIORITY MODE\tAGENT");
5035
+ writeLine(context.stdout, "ICP ID\tNAME\tSTATUS\tTAGS\tDISCOVERY KEYWORD\tSENIORITY MODE\tAGENT");
4651
5036
  for (const icp of icps) {
4652
5037
  writeLine(
4653
5038
  context.stdout,
4654
5039
  [
4655
5040
  display(icp.prefix_id),
4656
5041
  display(icp.name),
5042
+ icp.archived === true ? "archived" : "active",
4657
5043
  display(Array.isArray(icp.tags) && icp.tags.length > 0 ? icp.tags.join(",") : "-"),
4658
5044
  display(icp.discovery_keyword),
4659
5045
  display(icp.seniority_match_mode),
@@ -4665,6 +5051,7 @@ function renderIcps(icps, context) {
4665
5051
 
4666
5052
  function renderIcp(icp, context) {
4667
5053
  writeLine(context.stdout, `ICP: ${display(icp?.name)} (${display(icp?.prefix_id)})`);
5054
+ writeLine(context.stdout, `Status: ${icp?.archived === true ? "archived" : "active"}`);
4668
5055
  if (Array.isArray(icp?.tags)) writeLine(context.stdout, `Tags: ${display(icp.tags.join(", "), "-")}`);
4669
5056
  if (icp?.notes) writeLine(context.stdout, `Notes: ${icp.notes}`);
4670
5057
  if (icp?.discovery_keyword) writeLine(context.stdout, `Discovery keyword: ${icp.discovery_keyword}`);
@@ -4672,6 +5059,19 @@ function renderIcp(icp, context) {
4672
5059
  if (icp?.agent?.name) writeLine(context.stdout, `Agent: ${icp.agent.name}`);
4673
5060
  }
4674
5061
 
5062
+ function writeIcpLifecycleEffects(result, context) {
5063
+ const motions = Array.isArray(result?.primary_motions) ? result.primary_motions : [];
5064
+ if (motions.length > 0) {
5065
+ const counts = new Map();
5066
+ for (const motion of motions) counts.set(motion.status, (counts.get(motion.status) || 0) + 1);
5067
+ const summary = [...counts.entries()].map(([status, count]) => `${count} ${status}`).join(", ");
5068
+ writeLine(context.stdout, `Primary motions: ${summary}.`);
5069
+ } else {
5070
+ writeLine(context.stdout, "Primary motions: none.");
5071
+ }
5072
+ writeLine(context.stdout, `Secondary motion links preserved: ${display(result?.secondary_motion_count, 0)}.`);
5073
+ }
5074
+
4675
5075
  function renderCompanies(payload, context) {
4676
5076
  const companies = Array.isArray(payload?.companies) ? payload.companies : [];
4677
5077
  if (companies.length === 0) return writeLine(context.stdout, "No companies found.");
@@ -4751,6 +5151,93 @@ async function performBulkMutation(perform) {
4751
5151
  }
4752
5152
  }
4753
5153
 
5154
+ async function performProspectAccountMove(perform) {
5155
+ try {
5156
+ return { payload: await perform(), rejected: false };
5157
+ } catch (error) {
5158
+ const body = error instanceof ApiError && error.body && typeof error.body === "object" ? error.body : null;
5159
+ if (body && (body.schema_version !== undefined || body.error_kind !== undefined)) {
5160
+ return { payload: body, rejected: true };
5161
+ }
5162
+ throw error;
5163
+ }
5164
+ }
5165
+
5166
+ function renderProspectAccountMove(payload, context, { applying }) {
5167
+ const phase = payload?.applied ? "applied" : "preview";
5168
+ writeLine(context.stdout, `Move account ${phase} for ${entityLabel(payload?.prospect)}.`);
5169
+ writeLine(context.stdout, `Source: ${entityLabel(payload?.source)}`);
5170
+ writeLine(context.stdout, `Target: ${entityLabel(payload?.target)}`);
5171
+ writeLine(context.stdout, `Eligible: ${payload?.eligible ? "yes" : "no"}`);
5172
+ writeLine(context.stdout, `Applied: ${payload?.applied ? "yes" : "no"}`);
5173
+
5174
+ const source = payload?.source || {};
5175
+ const sourceLists = Array.isArray(source.lists) ? source.lists.map(entityLabel).filter(Boolean) : [];
5176
+ writeLine(context.stdout, `Current assignee: ${entityLabel(source.assigned_user) || "unassigned"}`);
5177
+ writeLine(context.stdout, `Current motion: ${entityLabel(source.motion) || "none"}`);
5178
+ writeLine(context.stdout, `Current lists: ${sourceLists.join(", ") || "none"}`);
5179
+
5180
+ const mappings = payload?.mappings || {};
5181
+ writeLine(context.stdout, `Assigned user: ${entityLabel(mappings.assigned_user) || "not mapped"}`);
5182
+ writeLine(context.stdout, `Target motion: ${entityLabel(mappings.motion) || "not mapped"}`);
5183
+ writeLine(context.stdout, `Target list: ${entityLabel(mappings.list) || "not mapped"}`);
5184
+
5185
+ renderProspectAccountMoveCounts("Last 30 days", payload?.summary_30_days, context);
5186
+ renderProspectAccountMoveCounts("All time", payload?.all_time_counts, context);
5187
+
5188
+ for (const [disposition, counts] of Object.entries(payload?.dispositions || {})) {
5189
+ renderProspectAccountMoveCounts(humanize(disposition), counts, context);
5190
+ }
5191
+ renderProspectAccountMoveExpectedState(payload?.expected_state, context);
5192
+ renderProspectAccountMoveIssues("Blockers", payload?.blockers, context);
5193
+ renderProspectAccountMoveIssues("Warnings", payload?.warnings, context);
5194
+ if (payload?.refresh?.status) {
5195
+ writeLine(context.stdout, `Refresh: ${display(payload.refresh.status)}`);
5196
+ }
5197
+ if (payload?.error) writeLine(context.stdout, `Error: ${singleLine(payload.error)}`);
5198
+
5199
+ if (!applying && payload?.success && !payload?.applied) {
5200
+ writeLine(context.stdout, "Run again with --apply to move this prospect.");
5201
+ } else if (applying && payload?.eligible !== true) {
5202
+ writeLine(context.stdout, "The preview is not eligible to apply.");
5203
+ } else if (applying && !payload?.applied) {
5204
+ writeLine(context.stdout, "The account move was not applied.");
5205
+ }
5206
+ }
5207
+
5208
+ function renderProspectAccountMoveCounts(label, counts, context) {
5209
+ if (!counts || typeof counts !== "object") return;
5210
+
5211
+ const entries = Object.entries(counts).filter(([, count]) => Number(count) !== 0);
5212
+ if (entries.length === 0) return;
5213
+ writeLine(context.stdout, `${label}: ${entries.map(([key, count]) => `${humanize(key)} ${display(count)}`).join(", ")}`);
5214
+ }
5215
+
5216
+ function renderProspectAccountMoveIssues(label, issues, context) {
5217
+ if (!Array.isArray(issues) || issues.length === 0) return;
5218
+
5219
+ writeLine(context.stdout, `${label}: ${issues.length}`);
5220
+ for (const issue of issues) {
5221
+ const message = typeof issue === "string" ? issue : issue?.message || issue?.error || issue?.reason || JSON.stringify(issue);
5222
+ writeLine(context.stdout, `- ${singleLine(message)}`);
5223
+ }
5224
+ }
5225
+
5226
+ function renderProspectAccountMoveExpectedState(expectedState, context) {
5227
+ if (!expectedState || typeof expectedState !== "object") return;
5228
+
5229
+ const source = expectedState.source || {};
5230
+ const target = expectedState.target || {};
5231
+ writeLine(
5232
+ context.stdout,
5233
+ `Expected source: account prospect ${source.account_prospect ? "present" : "absent"}, executable work ${source.executable_work ? "present" : "absent"}`
5234
+ );
5235
+ writeLine(
5236
+ context.stdout,
5237
+ `Expected target: account prospect ${target.account_prospect ? "present" : "absent"}, events ${display(target.event_count, 0)}`
5238
+ );
5239
+ }
5240
+
4754
5241
  function renderBulkMutationResult(payload, context, { successLabel, zeroSuccessLabel }) {
4755
5242
  const failed = Array.isArray(payload?.failed) ? payload.failed : [];
4756
5243
 
@@ -5725,6 +6212,51 @@ function renderOperatorQueue(payload, context) {
5725
6212
  writeOperatorRows(context, queue);
5726
6213
  }
5727
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
+
5728
6260
  function renderOperatorNext(row, context) {
5729
6261
  if (!row) return writeLine(context.stdout, "No operator moves found.");
5730
6262
 
@@ -5966,6 +6498,132 @@ function renderAnalyticsDashboard(payload, context) {
5966
6498
  writeCountTable(context, "Current pipeline stages", payload?.pipeline_stage_counts, ["STAGE", "COUNT"], countRow);
5967
6499
  }
5968
6500
 
6501
+ function renderAnalyticsMotions(payload, context) {
6502
+ const current = payload?.current || {};
6503
+ const recent = payload?.recent || {};
6504
+ const totals = payload?.totals || {};
6505
+
6506
+ writeLine(context.stdout, "Motion portfolio analytics");
6507
+ writeLine(context.stdout, `Current prospects: ${integerLabel(current.total_count)}`);
6508
+ writeLine(context.stdout, `Last 7 days: ${integerLabel(recent.total_count)} (${motionAnalyticsWindowLabel(recent.window)})`);
6509
+ writeLine(context.stdout, `Motions: ${integerLabel(totals.motion_count)}`);
6510
+ writeLine(context.stdout, `Contributing motions: ${integerLabel(totals.contributing_motion_count)}`);
6511
+ writeLine(context.stdout, `Active without contribution: ${integerLabel(totals.active_without_contribution_count)}`);
6512
+ writeLine(context.stdout, `Current attribution uses the current motion association. Unattributed currently means ${display(current.unattributed_label, "No current motion")}.`);
6513
+ writeLine(context.stdout, `Last 7 days uses the recorded source motion. Unattributed means ${display(recent.unattributed_label, "No attributable source motion")}.`);
6514
+
6515
+ writeMotionMixTable(payload?.prospect_mix, context);
6516
+ writeMotionContributionTable(payload?.motions, context);
6517
+ }
6518
+
6519
+ function renderAnalyticsIcps(payload, context) {
6520
+ const current = payload?.current || {};
6521
+ const recent = payload?.recent || {};
6522
+ const totals = payload?.totals || {};
6523
+
6524
+ writeLine(context.stdout, "ICP portfolio analytics");
6525
+ writeLine(context.stdout, `Current prospects: ${integerLabel(current.total_count)}`);
6526
+ writeLine(context.stdout, `Last 7 days: ${integerLabel(recent.total_count)} (${motionAnalyticsWindowLabel(recent.window)})`);
6527
+ writeLine(context.stdout, `ICPs: ${integerLabel(totals.icp_count)}`);
6528
+ writeLine(context.stdout, `Contributing ICPs: ${integerLabel(totals.contributing_icp_count)}`);
6529
+ writeLine(context.stdout, `No recent contribution: ${integerLabel(totals.without_recent_contribution_count)}`);
6530
+ writeLine(context.stdout, `Current and last-seven-day counts use the recorded source ICP. Unattributed means ${display(current.unattributed_label, "No attributable source ICP")}.`);
6531
+
6532
+ writeIcpMixTable(payload?.prospect_mix, context);
6533
+ writeIcpContributionTable(payload?.icps, context);
6534
+ }
6535
+
6536
+ function writeIcpMixTable(rows, context) {
6537
+ const mix = Array.isArray(rows) ? rows : [];
6538
+ writeLine(context.stdout, "");
6539
+ writeLine(context.stdout, "Prospect mix");
6540
+ if (mix.length === 0) return writeLine(context.stdout, "None");
6541
+
6542
+ writeAlignedTable(context, ["ICP", "STATUS", "AGE", "CURRENT", "CURRENT %", "LAST 7 DAYS", "LAST 7 DAYS %"], mix.map((row) => [
6543
+ display(row?.name, "Unattributed"),
6544
+ row?.archived === true ? "archived" : (row?.id ? "active" : "-"),
6545
+ compactAgeLabel(row?.created_at, context.now()),
6546
+ display(row?.current?.count, 0),
6547
+ percentageLabel(row?.current?.percentage),
6548
+ display(row?.recent?.count, 0),
6549
+ percentageLabel(row?.recent?.percentage)
6550
+ ]), { numericColumns: [false, false, false, true, true, true, true] });
6551
+ }
6552
+
6553
+ function writeIcpContributionTable(rows, context) {
6554
+ const icps = Array.isArray(rows) ? rows : [];
6555
+ writeLine(context.stdout, "");
6556
+ writeLine(context.stdout, "ICP contribution");
6557
+ if (icps.length === 0) return writeLine(context.stdout, "None");
6558
+
6559
+ writeAlignedTable(context, ["ICP", "ID", "STATUS", "MOTIONS", "CURRENT", "LAST 7 DAYS", "CONTRIBUTING"], icps.map((row) => [
6560
+ display(row?.name, "Unattributed"),
6561
+ display(row?.prefix_id, "-"),
6562
+ row?.archived === true ? "archived" : (row?.id ? "active" : "-"),
6563
+ display(row?.linked_motion_count, 0),
6564
+ display(row?.current_prospect_count, 0),
6565
+ display(row?.recent_prospect_count, 0),
6566
+ row?.contributing === true ? "yes" : "no"
6567
+ ]), { numericColumns: [false, false, false, true, true, true, false] });
6568
+ }
6569
+
6570
+ function writeMotionMixTable(rows, context) {
6571
+ const mix = Array.isArray(rows) ? rows : [];
6572
+ writeLine(context.stdout, "");
6573
+ writeLine(context.stdout, "Prospect mix");
6574
+ if (mix.length === 0) return writeLine(context.stdout, "None");
6575
+
6576
+ writeAlignedTable(context, ["TYPE", "CURRENT", "CURRENT %", "LAST 7 DAYS", "LAST 7 DAYS %"], mix.map((row) => [
6577
+ display(row?.label || row?.key),
6578
+ display(row?.current?.count, 0),
6579
+ percentageLabel(row?.current?.percentage),
6580
+ display(row?.recent?.count, 0),
6581
+ percentageLabel(row?.recent?.percentage)
6582
+ ]), { numericColumns: [false, true, true, true, true] });
6583
+ }
6584
+
6585
+ function writeMotionContributionTable(rows, context) {
6586
+ const motions = Array.isArray(rows) ? rows : [];
6587
+ writeLine(context.stdout, "");
6588
+ writeLine(context.stdout, "Motion contribution");
6589
+ if (motions.length === 0) return writeLine(context.stdout, "None");
6590
+
6591
+ writeAlignedTable(context, ["MOTION", "ID", "TYPE", "STATUS", "CURRENT", "LAST 7 DAYS", "CONTRIBUTING"], motions.map((row) => [
6592
+ display(row?.name, "Unattributed"),
6593
+ display(row?.prefix_id, "-"),
6594
+ display(row?.kind, "unattributed"),
6595
+ display(row?.status, "-"),
6596
+ display(row?.current_prospect_count, 0),
6597
+ display(row?.recent_prospect_count, 0),
6598
+ row?.contributing === true ? "yes" : "no"
6599
+ ]), { numericColumns: [false, false, false, false, true, true, false] });
6600
+ }
6601
+
6602
+ function motionAnalyticsWindowLabel(window) {
6603
+ if (!window?.started_at || !window?.ended_at) return "rolling window";
6604
+
6605
+ return `${window.started_at} to ${window.ended_at}`;
6606
+ }
6607
+
6608
+ function integerLabel(value) {
6609
+ const number = Number(value);
6610
+ return Number.isFinite(number) ? number.toLocaleString("en-US") : display(value, 0);
6611
+ }
6612
+
6613
+ function compactAgeLabel(createdAt, now) {
6614
+ if (!createdAt) return "-";
6615
+
6616
+ const created = new Date(createdAt);
6617
+ const reference = now instanceof Date ? now : new Date(now);
6618
+ if (Number.isNaN(created.getTime()) || Number.isNaN(reference.getTime())) return "-";
6619
+
6620
+ const days = Math.max(0, Math.floor((reference.getTime() - created.getTime()) / 86_400_000));
6621
+ if (days < 30) return `${days}d`;
6622
+ if (days < 365) return `${Math.floor(days / 30)}mo`;
6623
+
6624
+ return `${Math.floor(days / 365)}y`;
6625
+ }
6626
+
5969
6627
  function renderAnalyticsMetrics(payload, context) {
5970
6628
  const cohort = payload?.cohort || {};
5971
6629
  const summary = payload?.summary || {};
@@ -6873,6 +7531,8 @@ const HELP_TOPICS = new Map([
6873
7531
  " audienti users list",
6874
7532
  " audienti users select <account_user_id|email|name|me>",
6875
7533
  " audienti users activity [account_user_id|me]",
7534
+ " audienti users automation show <account_user_id|me>",
7535
+ " audienti users automation update <account_user_id|me> --payload <file.json> [--apply]",
6876
7536
  "",
6877
7537
  " Motions / plays",
6878
7538
  " audienti motions list",
@@ -6909,6 +7569,7 @@ const HELP_TOPICS = new Map([
6909
7569
  " audienti prospects check [filters]",
6910
7570
  " audienti prospects show <prsp_id>",
6911
7571
  " audienti prospects assign <prsp_id> --assigned-user <id|me|unassign>",
7572
+ " audienti prospects move-account <prsp_id> --target-account <acct_id> [--apply]",
6912
7573
  " audienti prospects set-status <prsp_id> --status <active|nurture|non_responsive|not_fit|bad_data_404|rejected>",
6913
7574
  " audienti prospects replan <prsp_id> [--apply]",
6914
7575
  " audienti prospects reenrich <prsp_id> [--apply]",
@@ -6941,9 +7602,11 @@ const HELP_TOPICS = new Map([
6941
7602
  " audienti offers show <offr_id>",
6942
7603
  " audienti offers update <offr_id> [--name <text>]",
6943
7604
  " audienti offers delete <offr_id> --confirm <yes|true|Y|y>",
6944
- " audienti icps list [--tag <tag>]",
7605
+ " audienti icps list [--status <active|archived|all>] [--tag <tag>]",
6945
7606
  " audienti icps show <icp_id>",
6946
7607
  " audienti icps update <icp_id> [--tags <tag[,tag...]> | --payload <file.json>]",
7608
+ " audienti icps archive <icp_id>",
7609
+ " audienti icps restore <icp_id>",
6947
7610
  " audienti icps add-tag <icp_id> <tag>",
6948
7611
  " audienti icps remove-tag <icp_id> <tag>",
6949
7612
  " audienti companies search --query <text>",
@@ -6963,8 +7626,14 @@ const HELP_TOPICS = new Map([
6963
7626
  " audienti operator queue",
6964
7627
  " audienti operator failed-drafts",
6965
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>",
6966
7634
  "",
6967
7635
  " Analytics",
7636
+ " audienti analytics motions",
6968
7637
  " audienti analytics prospects --window 24h",
6969
7638
  " audienti analytics dashboard --play-tag <tag>",
6970
7639
  " audienti analytics metrics --cohort-preset week-to-date",
@@ -6989,6 +7658,7 @@ const HELP_TOPICS = new Map([
6989
7658
  " Inspect a prospect: audienti prospects show <prsp_id> --json",
6990
7659
  " Preview a campaign: audienti writer test-run <prsp_id>",
6991
7660
  " Analyze one motion: audienti motions analytics <motn_id>",
7661
+ " Audit motion mix: audienti analytics motions",
6992
7662
  " Count one campaign: audienti analytics dashboard --play-tag <tag>",
6993
7663
  " Inspect outcomes: audienti analytics metrics --cohort-preset week-to-date",
6994
7664
  " Compare stage rates: audienti analytics stages --interval weekly",
@@ -7230,6 +7900,13 @@ const HELP_TOPICS = new Map([
7230
7900
  " ready: true when the selected principal has a mapped, actionable platform account",
7231
7901
  " reason: ready | needs_setup | needs_account_access | needs_reconnect",
7232
7902
  " setup_url: direct operations URL for creating a connected account",
7903
+ " social_cookie.automation.active_days: normalized active day tokens used by server automation guards",
7904
+ " social_cookie.automation.working_hours: normalized timed windows by day token; omitted days are all-day or inactive based on active_days",
7905
+ " social_cookie.automation.time_zone: effective timezone used by server automation guards",
7906
+ " social_cookie.automation.in_working_hours: server-side current schedule decision",
7907
+ " account_user.location: configured user location used as the proxy fallback; unknown fields are null",
7908
+ " social_cookie.proxy_location: configured and last-verified effective proxy geography plus its source",
7909
+ " social_cookie.automation.pacing: effective LinkedIn quotas, ramp settings, invitation inventory authority, and current capacity",
7233
7910
  " social_cookie.urls.mapping_url: owner URL for granting account access",
7234
7911
  " social_cookie.urls.edit_url: operations URL for reconnect/settings review when mapped",
7235
7912
  "",
@@ -7246,6 +7923,8 @@ const HELP_TOPICS = new Map([
7246
7923
  " audienti users list [--json]",
7247
7924
  " audienti users select <account_user_id|email|name|me>",
7248
7925
  " audienti users activity [account_user_id|me] [--json]",
7926
+ " audienti users automation show <account_user_id|me> [--platform linkedin] [--json]",
7927
+ " audienti users automation update <account_user_id|me> --payload <file.json> [--apply] [--json]",
7249
7928
  "",
7250
7929
  "Status: implemented",
7251
7930
  "",
@@ -7256,6 +7935,61 @@ const HELP_TOPICS = new Map([
7256
7935
  " `principals` is accepted anywhere `users` is accepted"
7257
7936
  ].join("\n")],
7258
7937
 
7938
+ ["users automation", [
7939
+ "Usage:",
7940
+ ` ${USERS_AUTOMATION_SHOW_USAGE.slice("Usage: ".length)}`,
7941
+ ` ${USERS_AUTOMATION_UPDATE_USAGE.slice("Usage: ".length)}`,
7942
+ "",
7943
+ "Status: implemented",
7944
+ "",
7945
+ "Purpose:",
7946
+ " Inspect or propose account-user-specific LinkedIn automation controls and safety limits.",
7947
+ " Updates preview by default. Only --apply requests persistence."
7948
+ ].join("\n")],
7949
+
7950
+ ["users automation show", [
7951
+ "Usage:",
7952
+ ` ${USERS_AUTOMATION_SHOW_USAGE.slice("Usage: ".length)}`,
7953
+ "",
7954
+ "Status: implemented",
7955
+ "",
7956
+ "Purpose:",
7957
+ " Read the selected account user's configured and effective automation policy, current usage, remaining capacity, and binding limits.",
7958
+ "",
7959
+ "Behavior:",
7960
+ " platform defaults to linkedin and currently accepts linkedin only.",
7961
+ " JSON output is the unchanged server response.",
7962
+ "",
7963
+ "API:",
7964
+ " GET /api/v1/accounts/:account_id/users/:id/automation.json?platform=linkedin"
7965
+ ].join("\n")],
7966
+
7967
+ ["users automation update", [
7968
+ "Usage:",
7969
+ ` ${USERS_AUTOMATION_UPDATE_USAGE.slice("Usage: ".length)}`,
7970
+ "",
7971
+ "Status: implemented",
7972
+ "",
7973
+ "Purpose:",
7974
+ " Preview or apply principal-specific LinkedIn controls and hourly, daily, or weekly safety limits.",
7975
+ "",
7976
+ "Behavior:",
7977
+ " The command does not persist without --apply.",
7978
+ " The CLI merges platform=linkedin and apply=false|true into the payload; those values cannot be overridden by the file.",
7979
+ " Omitted controls are preserved by the server. JSON output is the unchanged server response.",
7980
+ "",
7981
+ "Payload fields:",
7982
+ " automation_controls: automation booleans, including writing, contact, visibility, and risk-cooldown gates",
7983
+ " action_limits: hourly/daily/weekly caps keyed by profile_view, follow, like, invite, message, comment, or aggregate visibility",
7984
+ " visibility_ramp: enabled, starting_daily_limit, weekly_increment, and optional started_at",
7985
+ "",
7986
+ "Request body:",
7987
+ " { \"automation\": { ...payload, \"platform\": \"linkedin\", \"apply\": false } }",
7988
+ "",
7989
+ "API:",
7990
+ " PATCH /api/v1/accounts/:account_id/users/:id/automation.json?platform=linkedin"
7991
+ ].join("\n")],
7992
+
7259
7993
  ["users list", [
7260
7994
  "Usage:",
7261
7995
  " audienti users list [--json] [--account <acct_id>]",
@@ -7420,12 +8154,15 @@ const HELP_TOPICS = new Map([
7420
8154
 
7421
8155
  ["icps", [
7422
8156
  "Usage:",
7423
- " audienti icps list [--tag <tag>] [--json]",
8157
+ " audienti icps list [--status <active|archived|all>] [--tag <tag>] [--json]",
7424
8158
  " audienti icps show <icp_id> [--json]",
7425
8159
  " audienti icps create (--name <text> | --payload <file.json>) [--json]",
7426
8160
  " audienti icps update <icp_id> ([--tags <tag[,tag...]>] | --payload <file.json>) [--json]",
8161
+ " audienti icps analytics [--json]",
7427
8162
  " audienti icps add-tag <icp_id> <tag> [--json]",
7428
8163
  " audienti icps remove-tag <icp_id> <tag> [--json]",
8164
+ " audienti icps archive <icp_id> [--json]",
8165
+ " audienti icps restore <icp_id> [--json]",
7429
8166
  "",
7430
8167
  "Status: implemented",
7431
8168
  "",
@@ -7433,13 +8170,31 @@ const HELP_TOPICS = new Map([
7433
8170
  " List the ICPs available to the current account so an agent can choose icp_id for motion creation or targeting work."
7434
8171
  ].join("\n")],
7435
8172
 
8173
+ ["icps analytics", [
8174
+ "Usage:",
8175
+ ` ${ANALYTICS_ICPS_USAGE.slice("Usage: ".length)}`,
8176
+ "",
8177
+ "Status: implemented",
8178
+ "",
8179
+ "Purpose:",
8180
+ " Show the account ICP portfolio's current prospect source mix and rolling seven-day contribution.",
8181
+ "",
8182
+ "Attribution:",
8183
+ " Current and last-seven-day counts use the recorded source ICP and fall back to the current motion ICP only when source is blank.",
8184
+ " Unattributed means no attributable account source ICP.",
8185
+ "",
8186
+ "API:",
8187
+ " GET /api/v1/accounts/:account_id/analytics/icps.json"
8188
+ ].join("\n")],
8189
+
7436
8190
  ["icps list", [
7437
8191
  "Usage:",
7438
- " audienti icps list [--tag <tag>] [--json] [--account <acct_id>]",
8192
+ ` ${ICPS_LIST_USAGE.slice("Usage: ".length)}`,
7439
8193
  "",
7440
8194
  "Status: implemented",
7441
8195
  "",
7442
8196
  "Options:",
8197
+ " --status <active|archived|all> Default: active",
7443
8198
  " --tag <tag> Filter locally to ICPs whose tags include the normalized tag",
7444
8199
  "",
7445
8200
  "API:",
@@ -7449,12 +8204,40 @@ const HELP_TOPICS = new Map([
7449
8204
  " id: integer",
7450
8205
  " prefix_id: icpp_",
7451
8206
  " name: string",
8207
+ " archived: boolean",
8208
+ " archived_at: ISO-8601 string | null",
7452
8209
  " notes: string | null",
7453
8210
  " tags: [string]",
7454
8211
  " discovery_keyword: string | null",
7455
8212
  " agent: { id, name } | null"
7456
8213
  ].join("\n")],
7457
8214
 
8215
+ ["icps archive", [
8216
+ "Usage:",
8217
+ ` ${ICPS_ARCHIVE_USAGE.slice("Usage: ".length)}`,
8218
+ "",
8219
+ "Status: implemented",
8220
+ "",
8221
+ "Behavior:",
8222
+ " Hides the ICP from new selection. Active primary motions enter closing; inactive and transition primary motions archive; secondary links remain.",
8223
+ "",
8224
+ "API:",
8225
+ " POST /api/v1/accounts/:account_id/icps/:id/archive.json"
8226
+ ].join("\n")],
8227
+
8228
+ ["icps restore", [
8229
+ "Usage:",
8230
+ ` ${ICPS_RESTORE_USAGE.slice("Usage: ".length)}`,
8231
+ "",
8232
+ "Status: implemented",
8233
+ "",
8234
+ "Behavior:",
8235
+ " Makes the ICP selectable again without reactivating any motion.",
8236
+ "",
8237
+ "API:",
8238
+ " POST /api/v1/accounts/:account_id/icps/:id/restore.json"
8239
+ ].join("\n")],
8240
+
7458
8241
  ["icps create", [
7459
8242
  "Usage:",
7460
8243
  " audienti icps create (--name <text> [--notes <text>] [--discovery-keyword <text>] [--tags <tag[,tag...]>] | --payload <file.json>) [--json] [--account <acct_id>]",
@@ -8556,7 +9339,9 @@ const HELP_TOPICS = new Map([
8556
9339
  "",
8557
9340
  "Input shape:",
8558
9341
  " motn_id: motn_ prefix id",
8559
- " status: draft | preparing | active | paused | archived | optional",
9342
+ " status: draft | preparing | active | closing | paused | archived | optional",
9343
+ " principal_account_user_id: account user id | me | optional in payload mode",
9344
+ " list_id: list_ prefix id, numeric id, or null | optional in payload mode",
8560
9345
  " tags: comma-separated tag list | optional",
8561
9346
  " start-date: YYYY-MM-DD | none | optional",
8562
9347
  " end-date: YYYY-MM-DD | none | optional",
@@ -8565,6 +9350,7 @@ const HELP_TOPICS = new Map([
8565
9350
  "",
8566
9351
  "Behavior:",
8567
9352
  " Choose either --payload or simple flags. Updates only the provided fields. Use none to clear a schedule date or company cap. Sending --tags replaces the motion's full tag set.",
9353
+ " Payload mode can replace the motion principal and backing list. The principal must belong to the selected account; list_id null clears the backing list.",
8568
9354
  " The company cap stops only new company discovery; existing company research, people discovery, prospect intake, and automation continue.",
8569
9355
  " Payload mode supports outbound signal_rows or legacy signal_questions; supplied rows replace the motion's active ready signals.",
8570
9356
  " posting_language is only supported on company-scope hiring signal rows; use topics for person discussion evidence.",
@@ -8735,6 +9521,7 @@ const HELP_TOPICS = new Map([
8735
9521
  " audienti prospects check [--json|--csv] [filters]",
8736
9522
  " audienti prospects show <prsp_id> [--json]",
8737
9523
  " audienti prospects assign <prsp_id> [prsp_id...] --assigned-user <id|me|unassign> [--json]",
9524
+ " audienti prospects move-account <prsp_id> --target-account <acct_id> [--apply] [--json]",
8738
9525
  " audienti prospects set-status <prsp_id> --status <active|nurture|non_responsive|not_fit|bad_data_404|rejected> [--json]",
8739
9526
  " audienti prospects replan <prsp_id> [--apply] [--json]",
8740
9527
  " audienti prospects reenrich <prsp_id> [--profile <prof_id|citation_id>] [--apply] [--json]",
@@ -8886,6 +9673,30 @@ const HELP_TOPICS = new Map([
8886
9673
  " }"
8887
9674
  ].join("\n")],
8888
9675
 
9676
+ ["prospects move-account", [
9677
+ "Usage:",
9678
+ ` ${PROSPECTS_MOVE_ACCOUNT_USAGE.slice("Usage: ".length)}`,
9679
+ "",
9680
+ "Status: implemented",
9681
+ "",
9682
+ "Purpose:",
9683
+ " Move one prospect and its account-scoped history between accounts where you are an administrator.",
9684
+ "",
9685
+ "Behavior:",
9686
+ " The command previews before applying and never applies an ineligible preview.",
9687
+ " Pass --apply to run a fresh preview, then apply that exact manifest digest.",
9688
+ " --account selects the source account; --target-account selects the destination.",
9689
+ " --json prints the final server response unchanged.",
9690
+ "",
9691
+ "Optional target mappings:",
9692
+ " --assigned-user <account_user_id|me>",
9693
+ " --target-motion <motn_id>",
9694
+ " --target-list <list_id>",
9695
+ "",
9696
+ "API:",
9697
+ " POST /api/v1/accounts/:account_id/prospects/:id/move_account.json"
9698
+ ].join("\n")],
9699
+
8889
9700
  ["prospects set-status", [
8890
9701
  "Usage:",
8891
9702
  ` ${PROSPECTS_SET_STATUS_USAGE.slice("Usage: ".length)}`,
@@ -9627,6 +10438,72 @@ const HELP_TOPICS = new Map([
9627
10438
  " GET /api/v1/accounts/:account_id/tools/linkedin-review/reports/:id.json"
9628
10439
  ].join("\n")],
9629
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
+
9630
10507
  ["operator", [
9631
10508
  "Usage:",
9632
10509
  " audienti operator next [--json|--plan|--done|--skip|--fail|--return]",
@@ -9642,7 +10519,7 @@ const HELP_TOPICS = new Map([
9642
10519
  " --motion <motn_id>",
9643
10520
  " --list <list_id>",
9644
10521
  " --stage <stage>",
9645
- " --opportunity-kind prospect|visibility",
10522
+ " --opportunity-kind prospect|visibility|inbox",
9646
10523
  " --writing-status ready|drafting|draft_failed"
9647
10524
  ].join("\n")],
9648
10525
 
@@ -9752,6 +10629,8 @@ const HELP_TOPICS = new Map([
9752
10629
 
9753
10630
  ["analytics", [
9754
10631
  "Usage:",
10632
+ " audienti analytics motions [--json]",
10633
+ " audienti analytics icps [--json]",
9755
10634
  " audienti analytics prospects [--window 24h] [--cohort-start YYYY-MM-DD --cohort-end YYYY-MM-DD] [--motion <motn_id>] [--list <list_id>] [--user <account_user_id|email|name|me>] [--json]",
9756
10635
  " audienti analytics dashboard [--cohort-start YYYY-MM-DD --cohort-end YYYY-MM-DD] [--play-tag <tag>] [--motion <motn_id>] [--list <list_id>] [--json]",
9757
10636
  " audienti analytics metrics [--cohort-start YYYY-MM-DD --cohort-end YYYY-MM-DD | --cohort-preset week-to-date] [--interval <daily|weekly>] [--user <account_user_id|email|name|me>] [--motion <motn_id>] [--play-tag <tag>] [--list <list_id>] [--offer <offr_id>] [--icp <icp_id>] [--social-cookie <scok_id>] [--platform <platform>] [--action <action_key>] [--outcome <success|failure|first_attempt_success|succeeded_after_retry|failed_without_retry|failed_after_retry|in_progress|unresolved>] [--json]",
@@ -9781,7 +10660,54 @@ const HELP_TOPICS = new Map([
9781
10660
  " --user <account_user_id|email|name|me> Narrow analytics to one account user. For prospect analytics, this means prospects assigned to that account user. Email/name partials are accepted when they match exactly one account user.",
9782
10661
  "",
9783
10662
  "Output:",
9784
- " Account-scoped analytics for prospects, campaign dashboard counts, users, visibility engagement, and ContentOps publishing."
10663
+ " Account-scoped analytics for the motion portfolio, ICP portfolio, prospects, campaign dashboard counts, users, visibility engagement, and ContentOps publishing."
10664
+ ].join("\n")],
10665
+
10666
+ ["analytics motions", [
10667
+ "Usage:",
10668
+ ` ${ANALYTICS_MOTIONS_USAGE.slice("Usage: ".length)}`,
10669
+ "",
10670
+ "Status: implemented",
10671
+ "",
10672
+ "Purpose:",
10673
+ " Show the account motion portfolio's current prospect mix and rolling seven-day contribution.",
10674
+ "",
10675
+ "Attribution:",
10676
+ " Current counts use each prospect's current motion association; Unattributed means no current motion.",
10677
+ " Last-seven-day counts use the recorded source motion and fall back to the current motion only when source is blank.",
10678
+ " Recent Unattributed means no attributable account source motion.",
10679
+ "",
10680
+ "Output shape:",
10681
+ " current and recent: totals plus explicit attribution semantics",
10682
+ " totals: motion and contribution counts",
10683
+ " prospect_mix[]: overall and rolling-seven-day count and percentage by motion type",
10684
+ " motions[]: current and rolling-seven-day prospect counts for every account motion",
10685
+ "",
10686
+ "API:",
10687
+ " GET /api/v1/accounts/:account_id/analytics/motions.json"
10688
+ ].join("\n")],
10689
+
10690
+ ["analytics icps", [
10691
+ "Usage:",
10692
+ ` ${ANALYTICS_ICPS_USAGE.slice("Usage: ".length)}`,
10693
+ "",
10694
+ "Status: implemented",
10695
+ "",
10696
+ "Purpose:",
10697
+ " Show the account ICP portfolio's current prospect source mix and rolling seven-day contribution.",
10698
+ "",
10699
+ "Attribution:",
10700
+ " Current and last-seven-day counts use the recorded source ICP and fall back to the current motion ICP only when source is blank.",
10701
+ " Unattributed means no attributable account source ICP.",
10702
+ "",
10703
+ "Output shape:",
10704
+ " current and recent: totals plus explicit attribution semantics",
10705
+ " totals: ICP and contribution counts",
10706
+ " prospect_mix[]: ICP created_at plus overall and rolling-seven-day count and percentage",
10707
+ " icps[]: ICP created_at plus current and rolling-seven-day prospect counts",
10708
+ "",
10709
+ "API:",
10710
+ " GET /api/v1/accounts/:account_id/analytics/icps.json"
9785
10711
  ].join("\n")],
9786
10712
 
9787
10713
  ["analytics dashboard", [
@@ -10105,6 +11031,9 @@ const HELP_TOPICS = new Map([
10105
11031
  " audienti operator failed-drafts",
10106
11032
  " audienti operator failed-drafts requeue <row_id>",
10107
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",
10108
11037
  "",
10109
11038
  "7. Inspect account analytics",
10110
11039
  " audienti users activity me --window 7d",