@audienti/cli 0.1.10 → 0.1.15

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
@@ -17,6 +17,8 @@ const DEFAULT_LIST_LIMIT = 20;
17
17
  const API_MAX_LIST_LIMIT = 100;
18
18
  const DEFAULT_LOOKUP_TIMEOUT_SECONDS = 60;
19
19
  const DEFAULT_LOOKUP_POLL_INTERVAL_SECONDS = 2;
20
+ const PACKAGE_NAME = "@audienti/cli";
21
+ const DEFAULT_NPM_REGISTRY = "https://registry.npmjs.org";
20
22
  const DEFAULT_PROFILE_IDENTIFIERS = [
21
23
  "linkedin/profile",
22
24
  "linkedin/company",
@@ -31,17 +33,34 @@ const PROSPECTS_ADD_STEER_USAGE = "Usage: audienti prospects add-steer <prsp_id>
31
33
  const PROSPECTS_ADD_PROFILE_USAGE = "Usage: audienti prospects add-profile <prsp_id> --url <profile_url|email|phone> [--json] [--account <acct_id>]";
32
34
  const PROSPECTS_REPORT_BAD_PROFILE_USAGE = "Usage: audienti prospects report-bad-profile <prsp_id> <prof_id|citation_id> [--json] [--account <acct_id>]";
33
35
  const PROSPECTS_ASSIGN_USAGE = "Usage: audienti prospects assign <prsp_id> [prsp_id...] --assigned-user <id|me|unassign> [--json] [--account <acct_id>]";
36
+ const PROSPECTS_REJECT_USAGE = "Usage: audienti prospects reject <prsp_id> [--json] [--account <acct_id>]";
37
+ const PROSPECTS_NURTURE_USAGE = "Usage: audienti prospects nurture <prsp_id> [--reason <nurture|non_responsive|not_fit>] [--json] [--account <acct_id>]";
38
+ const PROSPECTS_RESTORE_USAGE = "Usage: audienti prospects restore <prsp_id> [--json] [--account <acct_id>]";
39
+ const PROSPECTS_CHECK_USAGE = "Usage: audienti prospects check [--json|--csv] [filters] [--account <acct_id>]";
34
40
  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>]";
35
- const USERS_ACTIVITY_USAGE = "Usage: audienti users activity <account_user_id|me> [--mode <actor|account_usage>] [--window <24h|7d|30d>] [--platform <linkedin|email|gmail>] [--query <text>] [--limit <n>] [--page <n>] [--json] [--account <acct_id>]";
41
+ const USERS_ACTIVITY_USAGE = "Usage: audienti users activity [account_user_id|me] [--mode <actor|account_usage>] [--window <24h|7d|30d>] [--platform <linkedin|email|gmail>] [--query <text>] [--limit <n>] [--page <n>] [--json] [--account <acct_id>]";
42
+ const OFFERS_SHOW_USAGE = "Usage: audienti offers show <offr_id> [--json] [--account <acct_id>]";
43
+ const OFFERS_UPDATE_USAGE = "Usage: audienti offers update <offr_id> [--name <text>] [--description <text>] [--url <url>] [--json] [--account <acct_id>]";
44
+ const OFFERS_DELETE_USAGE = "Usage: audienti offers delete <offr_id> --confirm <yes|true|Y|y> [--json] [--account <acct_id>]";
36
45
  const WRITER_TEST_RUN_USAGE = "Usage: audienti writer test-run <prsp_id> [--json] [--mode <report|plan|step>] [--branch <both|no-accept|accepted>] [--step <step_key|row_number>] [--no-cache] [--clear-cache] [--account <acct_id>]";
37
46
  const MOTIONS_ANALYTICS_USAGE = "Usage: audienti motions analytics <motn_id> [--window 30d] [--json] [--account <acct_id>]";
38
- const MOTIONS_UPDATE_USAGE = "Usage: audienti motions update <motn_id> --status <draft|preparing|active|paused|archived> [--json] [--account <acct_id>]";
47
+ const MOTIONS_UPDATE_USAGE = "Usage: audienti motions update <motn_id> [--status <draft|preparing|active|paused|archived>] [--tags <tag[,tag...]>] [--json] [--account <acct_id>]";
48
+ const MOTIONS_ADD_TAG_USAGE = "Usage: audienti motions add-tag <motn_id> <tag> [--json] [--account <acct_id>]";
49
+ const MOTIONS_REMOVE_TAG_USAGE = "Usage: audienti motions remove-tag <motn_id> <tag> [--json] [--account <acct_id>]";
39
50
  const MOTIONS_DELETE_USAGE = "Usage: audienti motions delete <motn_id> --confirm <yes|true|Y|y> [--json] [--account <acct_id>]";
40
51
  const MOTIONS_CLONE_USAGE = "Usage: audienti motions clone <motn_id> --name <text> [--json] [--account <acct_id>]";
41
52
  const MOTIONS_MOVE_PROSPECTS_USAGE = "Usage: audienti motions move-prospects <source_motn_id> --target <target_motn_id> <prsp_id> [prsp_id...] [--json] [--account <acct_id>]";
53
+ const MOTIONS_RUN_DISCOVERY_USAGE = "Usage: audienti motions run-discovery <motn_id> [--target-count <n>] [--json] [--account <acct_id>]";
54
+ const ICPS_SHOW_USAGE = "Usage: audienti icps show <icp_id> [--json] [--account <acct_id>]";
55
+ const ICPS_UPDATE_USAGE = "Usage: audienti icps update <icp_id> [--name <text>] [--notes <text>] [--discovery-keyword <text>] [--tags <tag[,tag...]>] [--json] [--account <acct_id>]";
56
+ const ICPS_ADD_TAG_USAGE = "Usage: audienti icps add-tag <icp_id> <tag> [--json] [--account <acct_id>]";
57
+ const ICPS_REMOVE_TAG_USAGE = "Usage: audienti icps remove-tag <icp_id> <tag> [--json] [--account <acct_id>]";
58
+ const LISTS_ADD_TAG_USAGE = "Usage: audienti lists add-tag <list_id> <tag> [--json] [--account <acct_id>]";
59
+ const LISTS_REMOVE_TAG_USAGE = "Usage: audienti lists remove-tag <list_id> <tag> [--json] [--account <acct_id>]";
42
60
  const ANALYTICS_PROSPECTS_USAGE = "Usage: audienti analytics prospects [--window 24h] [--cohort-start YYYY-MM-DD --cohort-end YYYY-MM-DD] [--motion <motn_id>] [--provenance <source>] [--user <account_user_id|email|name|me>] [--json] [--account <acct_id>]";
43
61
  const ANALYTICS_PROSPECTS_COHORT_ANALYSIS_USAGE = "Usage: audienti analytics prospects cohort-analysis [--weeks <n>] [--window 24h] [--motion <motn_id>] [--provenance <source>] [--user <account_user_id|email|name|me>] [--json] [--account <acct_id>]";
44
62
  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>] [--provenance <source>] [--platform <linkedin|email|gmail>] [--json] [--account <acct_id>]";
63
+ const ANALYTICS_DASHBOARD_USAGE = "Usage: audienti analytics dashboard [--cohort-start YYYY-MM-DD --cohort-end YYYY-MM-DD] [--play-tag <tag>] [--motion <motn_id>] [--offer <offr_id>] [--icp <icp_id>] [--user <account_user_id|email|name|me>] [--json] [--account <acct_id>]";
45
64
  const COHORT_STAGE_ORDER = [
46
65
  "identified",
47
66
  "pre_connect",
@@ -120,19 +139,32 @@ async function dispatch(argv, context) {
120
139
  if (normalizedResource === "auth" && action === "status") return authStatus(rest, context, { accountOverride });
121
140
  if (normalizedResource === "auth" && action === "logout") return authLogout(rest, context);
122
141
  if (normalizedResource === "config" && action === "list") return configList(rest, context);
142
+ if (normalizedResource === "update" && action === "check") return updateCheck(rest, context);
123
143
  if (normalizedResource === "accounts" && action === "list") return accountsList(rest, context, { accountOverride });
124
144
  if (normalizedResource === "accounts" && action === "select") return accountsSelect(rest, context);
125
145
  if (normalizedResource === "users" && action === "list") return usersList(rest, context, { accountOverride });
146
+ if (normalizedResource === "users" && action === "select") return usersSelect(rest, context, { accountOverride });
126
147
  if (normalizedResource === "users" && action === "activity") return usersActivity(rest, context, { accountOverride });
127
148
  if (normalizedResource === "offers" && action === "list") return offersList(rest, context, { accountOverride });
149
+ if (normalizedResource === "offers" && action === "show") return offersShow(rest, context, { accountOverride });
128
150
  if (normalizedResource === "offers" && action === "create") return offersCreate(rest, context, { accountOverride });
151
+ if (normalizedResource === "offers" && action === "update") return offersUpdate(rest, context, { accountOverride });
152
+ if (normalizedResource === "offers" && action === "delete") return offersDelete(rest, context, { accountOverride });
129
153
  if (normalizedResource === "icps" && action === "list") return icpsList(rest, context, { accountOverride });
154
+ if (normalizedResource === "icps" && action === "show") return icpsShow(rest, context, { accountOverride });
130
155
  if (normalizedResource === "icps" && action === "create") return icpsCreate(rest, context, { accountOverride });
156
+ if (normalizedResource === "icps" && action === "update") return icpsUpdate(rest, context, { accountOverride });
157
+ if (normalizedResource === "icps" && action === "add-tag") return icpsTagMutation("add", rest, context, { accountOverride });
158
+ if (normalizedResource === "icps" && action === "remove-tag") return icpsTagMutation("remove", rest, context, { accountOverride });
131
159
  if (normalizedResource === "companies" && action === "search") return companiesSearch(rest, context, { accountOverride });
160
+ if (normalizedResource === "tags" && action === "list") return tagsList(rest, context, { accountOverride });
161
+ if (normalizedResource === "tags" && action === "show") return tagsShow(rest, context, { accountOverride });
132
162
  if (normalizedResource === "lists" && action === "list") return listsList(rest, context, { accountOverride });
133
163
  if (normalizedResource === "lists" && action === "create") return listsCreate(rest, context, { accountOverride });
134
164
  if (normalizedResource === "lists" && action === "show") return listsShow(rest, context, { accountOverride });
135
165
  if (normalizedResource === "lists" && action === "update") return listsUpdate(rest, context, { accountOverride });
166
+ if (normalizedResource === "lists" && action === "add-tag") return listsTagMutation("add", rest, context, { accountOverride });
167
+ if (normalizedResource === "lists" && action === "remove-tag") return listsTagMutation("remove", rest, context, { accountOverride });
136
168
  if (normalizedResource === "lists" && action === "delete") return listsDelete(rest, context, { accountOverride });
137
169
  if (normalizedResource === "lists" && action === "prospects") return listProspects(rest, context, { accountOverride });
138
170
  if (normalizedResource === "lists" && action === "add-prospects") return listsAddProspects(rest, context, { accountOverride });
@@ -145,13 +177,20 @@ async function dispatch(argv, context) {
145
177
  if (normalizedResource === "motions" && action === "add-prospects") return motionsAddProspects(rest, context, { accountOverride });
146
178
  if (normalizedResource === "motions" && action === "create") return motionsCreate(rest, context, { accountOverride });
147
179
  if (normalizedResource === "motions" && action === "update") return motionsUpdate(rest, context, { accountOverride });
180
+ if (normalizedResource === "motions" && action === "add-tag") return motionsTagMutation("add", rest, context, { accountOverride });
181
+ if (normalizedResource === "motions" && action === "remove-tag") return motionsTagMutation("remove", rest, context, { accountOverride });
148
182
  if (normalizedResource === "motions" && ["activate", "pause", "archive"].includes(action)) return motionsStatusShortcut(action, rest, context, { accountOverride });
149
183
  if (normalizedResource === "motions" && action === "delete") return motionsDelete(rest, context, { accountOverride });
150
184
  if (normalizedResource === "motions" && action === "clone") return motionsClone(rest, context, { accountOverride });
151
185
  if (normalizedResource === "motions" && action === "move-prospects") return motionsMoveProspects(rest, context, { accountOverride });
186
+ if (normalizedResource === "motions" && action === "run-discovery") return motionsRunDiscovery(rest, context, { accountOverride });
152
187
  if (normalizedResource === "prospects" && action === "list") return prospectsList(rest, context, { accountOverride });
188
+ if (normalizedResource === "prospects" && action === "check") return prospectsCheck(rest, context, { accountOverride });
153
189
  if (normalizedResource === "prospects" && action === "show") return prospectsShow(rest, context, { accountOverride });
154
190
  if (normalizedResource === "prospects" && action === "assign") return prospectsAssign(rest, context, { accountOverride });
191
+ if (normalizedResource === "prospects" && action === "reject") return prospectsDisposition("reject", rest, context, { accountOverride });
192
+ if (normalizedResource === "prospects" && action === "nurture") return prospectsDisposition("nurture", rest, context, { accountOverride });
193
+ if (normalizedResource === "prospects" && action === "restore") return prospectsDisposition("restore", rest, context, { accountOverride });
155
194
  if (normalizedResource === "prospects" && action === "timeline") return prospectsTimeline(rest, context, { accountOverride });
156
195
  if (normalizedResource === "prospects" && action === "message-types") return prospectsMessageTypes(rest, context, { accountOverride });
157
196
  if (normalizedResource === "prospects" && action === "write") return prospectsWrite(rest, context, { accountOverride });
@@ -173,6 +212,7 @@ async function dispatch(argv, context) {
173
212
  if (normalizedResource === "analytics" && ["users", "user"].includes(action)) return analyticsUsers(rest, context, { accountOverride });
174
213
  if (normalizedResource === "analytics" && ["visibility", "visops"].includes(action)) return analyticsVisibility(rest, context, { accountOverride });
175
214
  if (normalizedResource === "analytics" && action === "content") return analyticsContent(rest, context, { accountOverride });
215
+ if (normalizedResource === "analytics" && ["dashboard", "campaign", "campaigns"].includes(action)) return analyticsDashboard(rest, context, { accountOverride });
176
216
 
177
217
  throw new CommandError(usage(), { exitCode: resource ? 1 : 0 });
178
218
  }
@@ -274,6 +314,14 @@ async function authStatus(args, context, { accountOverride } = {}) {
274
314
  } else {
275
315
  writeLine(context.stdout, "Active account: none selected");
276
316
  }
317
+
318
+ const accountUser = defaultAccountUserConfig(config, { accountOverride });
319
+ if (accountUser.id) {
320
+ const name = accountUser.name ? `${accountUser.name} ` : "";
321
+ writeLine(context.stdout, `Default account user: ${name}(${accountUser.id})`);
322
+ } else {
323
+ writeLine(context.stdout, "Default account user: none selected");
324
+ }
277
325
  }
278
326
 
279
327
  async function authLogout(args, context) {
@@ -295,7 +343,10 @@ async function configList(args, context) {
295
343
  host: config.host || null,
296
344
  token: config.token ? maskToken(config.token) : null,
297
345
  accountId: config.accountId || null,
298
- accountName: config.accountName || null
346
+ accountName: config.accountName || null,
347
+ accountUserId: config.accountUserId || null,
348
+ accountUserName: config.accountUserName || null,
349
+ accountUserEmail: config.accountUserEmail || null
299
350
  };
300
351
 
301
352
  if (values.json) return writeJson(context.stdout, payload);
@@ -311,6 +362,58 @@ async function configList(args, context) {
311
362
  } else {
312
363
  writeLine(context.stdout, "Active account: none selected");
313
364
  }
365
+
366
+ if (payload.accountUserId) {
367
+ const name = payload.accountUserName ? `${payload.accountUserName} ` : "";
368
+ writeLine(context.stdout, `Default account user: ${name}(${payload.accountUserId})`);
369
+ } else {
370
+ writeLine(context.stdout, "Default account user: none selected");
371
+ }
372
+ }
373
+
374
+ async function updateCheck(args, context) {
375
+ const { values, positionals } = parseCommandArgs(args, {
376
+ ...jsonOptions(),
377
+ registry: { type: "string" }
378
+ });
379
+ if (positionals.length > 0) throw new CommandError("Usage: audienti update check [--json] [--registry <url>]");
380
+
381
+ const localPackage = await readLocalPackageMetadata();
382
+ let payload;
383
+
384
+ try {
385
+ const latestVersion = await fetchLatestPackageVersion({
386
+ fetchImpl: context.fetchImpl,
387
+ registry: values.registry
388
+ });
389
+ const updateAvailable = compareVersions(localPackage.version, latestVersion) < 0;
390
+ payload = updateCheckPayload({
391
+ currentVersion: localPackage.version,
392
+ latestVersion,
393
+ status: updateAvailable ? "update_available" : "current",
394
+ updateAvailable,
395
+ registry: values.registry || DEFAULT_NPM_REGISTRY,
396
+ now: context.now()
397
+ });
398
+ } catch (error) {
399
+ payload = updateCheckPayload({
400
+ currentVersion: localPackage.version,
401
+ latestVersion: null,
402
+ status: "unknown",
403
+ updateAvailable: null,
404
+ registry: values.registry || DEFAULT_NPM_REGISTRY,
405
+ error: error.message,
406
+ now: context.now()
407
+ });
408
+ }
409
+
410
+ if (values.json) {
411
+ writeJson(context.stdout, payload);
412
+ return payload.status === "unknown" ? 1 : 0;
413
+ }
414
+
415
+ renderUpdateCheck(payload, context);
416
+ return payload.status === "unknown" ? 1 : 0;
314
417
  }
315
418
 
316
419
  async function accountsList(args, context, { accountOverride } = {}) {
@@ -359,7 +462,10 @@ async function accountsSelect(args, context) {
359
462
  await writeConfig({
360
463
  ...config,
361
464
  accountId: account.prefix_id,
362
- accountName: account.name
465
+ accountName: account.name,
466
+ accountUserId: account.prefix_id === config.accountId ? config.accountUserId : undefined,
467
+ accountUserName: account.prefix_id === config.accountId ? config.accountUserName : undefined,
468
+ accountUserEmail: account.prefix_id === config.accountId ? config.accountUserEmail : undefined
363
469
  }, { env: context.env });
364
470
 
365
471
  writeLine(context.stdout, `Selected account ${account.name} (${account.prefix_id}).`);
@@ -389,12 +495,55 @@ function resolveAccountSelection(accounts, term) {
389
495
  throw new CommandError(`Account term "${requested}" matched multiple accounts: ${options}.`);
390
496
  }
391
497
 
498
+ function resolveAccountUserSelection(users, term) {
499
+ const requested = String(term || "").trim();
500
+ if (!requested) return null;
501
+
502
+ if (requested.toLowerCase() === "me") {
503
+ const currentUsers = users.filter((candidate) => candidate.current);
504
+ if (currentUsers.length === 1) return currentUsers[0];
505
+ if (currentUsers.length > 1) throw new CommandError("The token matched multiple current account users.");
506
+ return null;
507
+ }
508
+
509
+ const exactId = users.find((candidate) => String(candidate.id) === requested);
510
+ if (exactId) return exactId;
511
+
512
+ const normalizedRequested = requested.toLowerCase();
513
+ const exactEmail = users.find((candidate) => String(candidate.email || "").toLowerCase() === normalizedRequested);
514
+ if (exactEmail) return exactEmail;
515
+
516
+ const exactName = users.find((candidate) => String(candidate.name || "").toLowerCase() === normalizedRequested);
517
+ if (exactName) return exactName;
518
+
519
+ const matches = users.filter((candidate) => {
520
+ const id = String(candidate.id || "").toLowerCase();
521
+ const name = String(candidate.name || "").toLowerCase();
522
+ const email = String(candidate.email || "").toLowerCase();
523
+ return id.includes(normalizedRequested) || name.includes(normalizedRequested) || email.includes(normalizedRequested);
524
+ });
525
+
526
+ if (matches.length === 1) return matches[0];
527
+ if (matches.length === 0) return null;
528
+
529
+ const options = matches.map(accountUserLabel).join(", ");
530
+ throw new CommandError(`Account user term "${requested}" matched multiple account users: ${options}.`);
531
+ }
532
+
533
+ function accountUserLabel(accountUser) {
534
+ const name = accountUser?.name || accountUser?.email || `Account user ${accountUser?.id}`;
535
+ return `${name} (${accountUser?.id})`;
536
+ }
537
+
392
538
  async function listsList(args, context, { accountOverride } = {}) {
393
- const { values, positionals } = parseCommandArgs(args, jsonOptions());
394
- if (positionals.length > 0) throw new CommandError("Usage: audienti lists list [--json] [--account <acct_id>]");
539
+ const { values, positionals } = parseCommandArgs(args, {
540
+ ...jsonOptions(),
541
+ tag: { type: "string" }
542
+ });
543
+ if (positionals.length > 0) throw new CommandError("Usage: audienti lists list [--tag <tag>] [--json] [--account <acct_id>]");
395
544
 
396
545
  const { client, accountId } = await requireAccountContext(context, { accountOverride });
397
- const lists = await client.lists(accountId);
546
+ const lists = filterRecordsByTag(await client.lists(accountId), values.tag, "tags");
398
547
  if (values.json) return writeJson(context.stdout, lists);
399
548
 
400
549
  renderLists(lists, context);
@@ -411,6 +560,29 @@ async function usersList(args, context, { accountOverride } = {}) {
411
560
  renderUsers(users, context);
412
561
  }
413
562
 
563
+ async function usersSelect(args, context, { accountOverride } = {}) {
564
+ const { positionals } = parseCommandArgs(args, {});
565
+ if (positionals.length !== 1) throw new CommandError("Usage: audienti users select <account_user_id|email|name|me> [--account <acct_id>]");
566
+
567
+ const { client, accountId, config } = await requireAccountContext(context, { accountOverride });
568
+ const users = await client.users(accountId);
569
+ const accountUser = resolveAccountUserSelection(users, positionals[0]);
570
+ if (!accountUser) {
571
+ throw new CommandError(`Account user ${positionals[0]} does not exist or is not visible in account ${accountId}.`);
572
+ }
573
+
574
+ await writeConfig({
575
+ ...config,
576
+ accountId,
577
+ accountName: accountOverride && accountOverride !== config.accountId ? undefined : config.accountName,
578
+ accountUserId: String(accountUser.id),
579
+ accountUserName: accountUser.name,
580
+ accountUserEmail: accountUser.email
581
+ }, { env: context.env });
582
+
583
+ writeLine(context.stdout, `Selected account user ${accountUserLabel(accountUser)}.`);
584
+ }
585
+
414
586
  async function usersActivity(args, context, { accountOverride } = {}) {
415
587
  const { values, positionals } = parseCommandArgs(args, {
416
588
  ...jsonOptions(),
@@ -421,10 +593,10 @@ async function usersActivity(args, context, { accountOverride } = {}) {
421
593
  limit: { type: "string" },
422
594
  page: { type: "string" }
423
595
  });
424
- if (positionals.length !== 1) throw new CommandError(USERS_ACTIVITY_USAGE);
596
+ if (positionals.length > 1) throw new CommandError(USERS_ACTIVITY_USAGE);
425
597
 
426
- const { client, accountId } = await requireAccountContext(context, { accountOverride });
427
- const payload = await client.userActivity(accountId, positionals[0], compactObject({
598
+ const { client, accountId, config } = await requireAccountContext(context, { accountOverride });
599
+ const payload = await client.userActivity(accountId, resolveAccountUserId(positionals[0] || "me", config, { accountOverride }), compactObject({
428
600
  mode: values.mode,
429
601
  window: values.window,
430
602
  platform: values.platform,
@@ -448,6 +620,17 @@ async function offersList(args, context, { accountOverride } = {}) {
448
620
  renderOffers(offers, context);
449
621
  }
450
622
 
623
+ async function offersShow(args, context, { accountOverride } = {}) {
624
+ const { values, positionals } = parseCommandArgs(args, jsonOptions());
625
+ if (positionals.length !== 1) throw new CommandError(OFFERS_SHOW_USAGE);
626
+
627
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
628
+ const offer = await client.offer(accountId, positionals[0]);
629
+ if (values.json) return writeJson(context.stdout, offer);
630
+
631
+ renderOffer(offer, context);
632
+ }
633
+
451
634
  async function offersCreate(args, context, { accountOverride } = {}) {
452
635
  const { values, positionals } = parseCommandArgs(args, {
453
636
  ...jsonOptions(),
@@ -474,27 +657,85 @@ async function offersCreate(args, context, { accountOverride } = {}) {
474
657
  if (offer?.url) writeLine(context.stdout, `URL: ${offer.url}`);
475
658
  }
476
659
 
660
+ async function offersUpdate(args, context, { accountOverride } = {}) {
661
+ const { values, positionals } = parseCommandArgs(args, {
662
+ ...jsonOptions(),
663
+ name: { type: "string" },
664
+ description: { type: "string" },
665
+ url: { type: "string" }
666
+ });
667
+ const hasUpdateField = values.name || values.description !== undefined || values.url !== undefined;
668
+ if (positionals.length !== 1 || !hasUpdateField) {
669
+ throw new CommandError(OFFERS_UPDATE_USAGE);
670
+ }
671
+
672
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
673
+ const offer = await client.updateOffer(accountId, positionals[0], {
674
+ offer: compactObject({
675
+ name: values.name,
676
+ description: values.description,
677
+ url: values.url
678
+ })
679
+ });
680
+ if (values.json) return writeJson(context.stdout, offer);
681
+
682
+ writeLine(context.stdout, `Updated offer ${display(offer?.name)} (${display(offer?.prefix_id)}).`);
683
+ renderOffer(offer, context);
684
+ }
685
+
686
+ async function offersDelete(args, context, { accountOverride } = {}) {
687
+ const { values, positionals } = parseCommandArgs(args, {
688
+ ...jsonOptions(),
689
+ confirm: { type: "string" }
690
+ });
691
+ const normalizedConfirm = String(values.confirm || "").trim().toLowerCase();
692
+ if (positionals.length !== 1 || !DELETE_CONFIRMATION_VALUES.has(normalizedConfirm)) {
693
+ throw new CommandError(OFFERS_DELETE_USAGE);
694
+ }
695
+
696
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
697
+ const payload = await client.deleteOffer(accountId, positionals[0]);
698
+ if (values.json) return writeJson(context.stdout, payload);
699
+
700
+ writeLine(context.stdout, `Deleted offer ${display(payload?.name)} (${display(payload?.prefix_id)}).`);
701
+ }
702
+
477
703
  async function icpsList(args, context, { accountOverride } = {}) {
478
- const { values, positionals } = parseCommandArgs(args, jsonOptions());
479
- if (positionals.length > 0) throw new CommandError("Usage: audienti icps list [--json] [--account <acct_id>]");
704
+ const { values, positionals } = parseCommandArgs(args, {
705
+ ...jsonOptions(),
706
+ tag: { type: "string" }
707
+ });
708
+ if (positionals.length > 0) throw new CommandError("Usage: audienti icps list [--tag <tag>] [--json] [--account <acct_id>]");
480
709
 
481
710
  const { client, accountId } = await requireAccountContext(context, { accountOverride });
482
- const icps = await client.icps(accountId);
711
+ const icps = filterRecordsByTag(await client.icps(accountId), values.tag, "tags");
483
712
  if (values.json) return writeJson(context.stdout, icps);
484
713
 
485
714
  renderIcps(icps, context);
486
715
  }
487
716
 
717
+ async function icpsShow(args, context, { accountOverride } = {}) {
718
+ const { values, positionals } = parseCommandArgs(args, jsonOptions());
719
+ if (positionals.length !== 1) throw new CommandError(ICPS_SHOW_USAGE);
720
+
721
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
722
+ const icp = await client.icp(accountId, positionals[0]);
723
+ if (values.json) return writeJson(context.stdout, icp);
724
+
725
+ renderIcp(icp, context);
726
+ }
727
+
488
728
  async function icpsCreate(args, context, { accountOverride } = {}) {
489
729
  const { values, positionals } = parseCommandArgs(args, {
490
730
  ...jsonOptions(),
491
731
  payload: { type: "string" },
492
732
  name: { type: "string" },
493
733
  notes: { type: "string" },
734
+ tags: { type: "string" },
494
735
  "discovery-keyword": { type: "string" }
495
736
  });
496
737
  if (positionals.length > 0) {
497
- throw new CommandError("Usage: audienti icps create (--name <text> [--notes <text>] [--discovery-keyword <text>] | --payload <file.json>) [--json] [--account <acct_id>]");
738
+ throw new CommandError("Usage: audienti icps create (--name <text> [--notes <text>] [--discovery-keyword <text>] [--tags <tag[,tag...]>] | --payload <file.json>) [--json] [--account <acct_id>]");
498
739
  }
499
740
 
500
741
  const { client, accountId } = await requireAccountContext(context, { accountOverride });
@@ -504,9 +745,58 @@ async function icpsCreate(args, context, { accountOverride } = {}) {
504
745
 
505
746
  writeLine(context.stdout, `Created ICP ${display(icp?.name)} (${display(icp?.prefix_id)}).`);
506
747
  if (icp?.notes) writeLine(context.stdout, `Notes: ${icp.notes}`);
748
+ if (Array.isArray(icp?.tags)) writeLine(context.stdout, `Tags: ${display(icp.tags.join(", "), "-")}`);
507
749
  if (icp?.discovery_keyword) writeLine(context.stdout, `Discovery keyword: ${icp.discovery_keyword}`);
508
750
  }
509
751
 
752
+ async function icpsUpdate(args, context, { accountOverride } = {}) {
753
+ const { values, positionals } = parseCommandArgs(args, {
754
+ ...jsonOptions(),
755
+ name: { type: "string" },
756
+ notes: { type: "string" },
757
+ tags: { type: "string" },
758
+ "discovery-keyword": { type: "string" }
759
+ });
760
+ const hasUpdateField = values.name || values.notes !== undefined || values.tags !== undefined || values["discovery-keyword"] !== undefined;
761
+ if (positionals.length !== 1 || !hasUpdateField) {
762
+ throw new CommandError(ICPS_UPDATE_USAGE);
763
+ }
764
+
765
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
766
+ const icp = await client.updateIcp(accountId, positionals[0], {
767
+ icp: compactObject({
768
+ name: values.name,
769
+ notes: values.notes,
770
+ discovery_keyword: values["discovery-keyword"],
771
+ tags: values.tags !== undefined ? tagList(values.tags) : undefined
772
+ })
773
+ });
774
+ if (values.json) return writeJson(context.stdout, icp);
775
+
776
+ writeLine(context.stdout, `Updated ICP ${display(icp?.name)} (${display(icp?.prefix_id)}).`);
777
+ renderIcp(icp, context);
778
+ }
779
+
780
+ async function icpsTagMutation(action, args, context, { accountOverride } = {}) {
781
+ const usageText = action === "add" ? ICPS_ADD_TAG_USAGE : ICPS_REMOVE_TAG_USAGE;
782
+ const { values, positionals } = parseCommandArgs(args, jsonOptions());
783
+ if (positionals.length !== 2) {
784
+ throw new CommandError(usageText);
785
+ }
786
+
787
+ const [icpId, tag] = positionals;
788
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
789
+ const icp = action === "add" ?
790
+ await client.addIcpTag(accountId, icpId, { tag }) :
791
+ await client.removeIcpTag(accountId, icpId, { tag });
792
+ if (values.json) return writeJson(context.stdout, icp);
793
+
794
+ const verb = action === "add" ? "Added" : "Removed";
795
+ const preposition = action === "add" ? "to" : "from";
796
+ writeLine(context.stdout, `${verb} tag ${display(tag)} ${preposition} ICP ${display(icp?.name)} (${display(icp?.prefix_id)}).`);
797
+ if (Array.isArray(icp?.tags)) writeLine(context.stdout, `Tags: ${display(icp.tags.join(", "), "-")}`);
798
+ }
799
+
510
800
  async function companiesSearch(args, context, { accountOverride } = {}) {
511
801
  const { values, positionals } = parseCommandArgs(args, {
512
802
  ...jsonOptions(),
@@ -523,16 +813,47 @@ async function companiesSearch(args, context, { accountOverride } = {}) {
523
813
  renderCompanies(payload, context);
524
814
  }
525
815
 
816
+ async function tagsList(args, context, { accountOverride } = {}) {
817
+ const { values, positionals } = parseCommandArgs(args, jsonOptions());
818
+ if (positionals.length > 0) throw new CommandError("Usage: audienti tags list [--json] [--account <acct_id>]");
819
+
820
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
821
+ const payload = await client.tags(accountId);
822
+ if (values.json) return writeJson(context.stdout, payload);
823
+
824
+ renderTags(payload, context);
825
+ }
826
+
827
+ async function tagsShow(args, context, { accountOverride } = {}) {
828
+ const { values, positionals } = parseCommandArgs(args, jsonOptions());
829
+ if (positionals.length !== 1) throw new CommandError("Usage: audienti tags show <tag> [--json] [--account <acct_id>]");
830
+
831
+ const tag = tagList(positionals[0])[0];
832
+ if (!tag) throw new CommandError("Usage: audienti tags show <tag> [--json] [--account <acct_id>]");
833
+
834
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
835
+ const payload = {
836
+ tag,
837
+ icps: filterRecordsByTag(await client.icps(accountId), tag, "tags"),
838
+ lists: filterRecordsByTag(await client.lists(accountId), tag, "tags"),
839
+ motions: filterRecordsByTag(await client.motions(accountId), tag, "play_tags")
840
+ };
841
+ if (values.json) return writeJson(context.stdout, payload);
842
+
843
+ renderTagDetails(payload, context);
844
+ }
845
+
526
846
  async function listsCreate(args, context, { accountOverride } = {}) {
527
847
  const { values, positionals } = parseCommandArgs(args, {
528
848
  ...jsonOptions(),
529
849
  name: { type: "string" },
530
850
  description: { type: "string" },
851
+ tags: { type: "string" },
531
852
  "campaign-hook": { type: "string" },
532
853
  "audience-note": { type: "string" }
533
854
  });
534
855
  if (positionals.length > 0 || !values.name) {
535
- throw new CommandError("Usage: audienti lists create --name <text> [--description <text>] [--campaign-hook <text>] [--audience-note <text>] [--json] [--account <acct_id>]");
856
+ throw new CommandError("Usage: audienti lists create --name <text> [--description <text>] [--tags <tag[,tag...]>] [--campaign-hook <text>] [--audience-note <text>] [--json] [--account <acct_id>]");
536
857
  }
537
858
 
538
859
  const { client, accountId } = await requireAccountContext(context, { accountOverride });
@@ -544,6 +865,7 @@ async function listsCreate(args, context, { accountOverride } = {}) {
544
865
  list: compactObject({
545
866
  name: values.name,
546
867
  description: values.description,
868
+ tags: tagList(values.tags),
547
869
  campaign_brief: Object.keys(campaignBrief).length > 0 ? campaignBrief : undefined
548
870
  })
549
871
  });
@@ -569,13 +891,14 @@ async function listsUpdate(args, context, { accountOverride } = {}) {
569
891
  ...jsonOptions(),
570
892
  name: { type: "string" },
571
893
  description: { type: "string" },
894
+ tags: { type: "string" },
572
895
  "campaign-hook": { type: "string" },
573
896
  "audience-note": { type: "string" }
574
897
  });
575
898
  const hasCampaignUpdate = values["campaign-hook"] || values["audience-note"];
576
- const hasUpdateField = values.name || values.description || hasCampaignUpdate;
899
+ const hasUpdateField = values.name || values.description || values.tags !== undefined || hasCampaignUpdate;
577
900
  if (positionals.length !== 1 || !hasUpdateField) {
578
- throw new CommandError("Usage: audienti lists update <list_id> [--name <text>] [--description <text>] [--campaign-hook <text>] [--audience-note <text>] [--json] [--account <acct_id>]");
901
+ throw new CommandError("Usage: audienti lists update <list_id> [--name <text>] [--description <text>] [--tags <tag[,tag...]>] [--campaign-hook <text>] [--audience-note <text>] [--json] [--account <acct_id>]");
579
902
  }
580
903
 
581
904
  const { client, accountId } = await requireAccountContext(context, { accountOverride });
@@ -587,6 +910,7 @@ async function listsUpdate(args, context, { accountOverride } = {}) {
587
910
  list: compactObject({
588
911
  name: values.name,
589
912
  description: values.description,
913
+ tags: values.tags !== undefined ? tagList(values.tags) : undefined,
590
914
  campaign_brief: Object.keys(campaignBrief).length > 0 ? campaignBrief : undefined
591
915
  })
592
916
  });
@@ -596,6 +920,26 @@ async function listsUpdate(args, context, { accountOverride } = {}) {
596
920
  if (payload?.description) writeLine(context.stdout, `Description: ${payload.description}`);
597
921
  }
598
922
 
923
+ async function listsTagMutation(action, args, context, { accountOverride } = {}) {
924
+ const usageText = action === "add" ? LISTS_ADD_TAG_USAGE : LISTS_REMOVE_TAG_USAGE;
925
+ const { values, positionals } = parseCommandArgs(args, jsonOptions());
926
+ if (positionals.length !== 2) {
927
+ throw new CommandError(usageText);
928
+ }
929
+
930
+ const [listId, tag] = positionals;
931
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
932
+ const payload = action === "add" ?
933
+ await client.addListTag(accountId, listId, { tag }) :
934
+ await client.removeListTag(accountId, listId, { tag });
935
+ if (values.json) return writeJson(context.stdout, payload);
936
+
937
+ const verb = action === "add" ? "Added" : "Removed";
938
+ const preposition = action === "add" ? "to" : "from";
939
+ writeLine(context.stdout, `${verb} tag ${display(tag)} ${preposition} list ${display(payload?.name)} (${display(payload?.prefix_id)}).`);
940
+ if (Array.isArray(payload?.tags)) writeLine(context.stdout, `Tags: ${display(payload.tags.join(", "), "-")}`);
941
+ }
942
+
599
943
  async function listsDelete(args, context, { accountOverride } = {}) {
600
944
  const { values, positionals } = parseCommandArgs(args, {
601
945
  ...jsonOptions(),
@@ -695,11 +1039,14 @@ async function listsRemoveProspects(args, context, { accountOverride } = {}) {
695
1039
  }
696
1040
 
697
1041
  async function motionsList(args, context, { accountOverride } = {}) {
698
- const { values, positionals } = parseCommandArgs(args, jsonOptions());
699
- if (positionals.length > 0) throw new CommandError("Usage: audienti motions list [--json] [--account <acct_id>]");
1042
+ const { values, positionals } = parseCommandArgs(args, {
1043
+ ...jsonOptions(),
1044
+ tag: { type: "string" }
1045
+ });
1046
+ if (positionals.length > 0) throw new CommandError("Usage: audienti motions list [--tag <tag>] [--json] [--account <acct_id>]");
700
1047
 
701
1048
  const { client, accountId } = await requireAccountContext(context, { accountOverride });
702
- const motions = await client.motions(accountId);
1049
+ const motions = filterRecordsByTag(await client.motions(accountId), values.tag, "play_tags");
703
1050
  if (values.json) return writeJson(context.stdout, motions);
704
1051
 
705
1052
  renderMotions(motions, context);
@@ -727,6 +1074,22 @@ async function motionsStatus(args, context, { accountOverride } = {}) {
727
1074
  renderMotionStatus(status, context);
728
1075
  }
729
1076
 
1077
+ async function motionsRunDiscovery(args, context, { accountOverride } = {}) {
1078
+ const { values, positionals } = parseCommandArgs(args, {
1079
+ ...jsonOptions(),
1080
+ "target-count": { type: "string" }
1081
+ });
1082
+ if (positionals.length !== 1) throw new CommandError(MOTIONS_RUN_DISCOVERY_USAGE);
1083
+
1084
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1085
+ const payload = await client.runMotionDiscovery(accountId, positionals[0], compactObject({
1086
+ target_count: normalizeOptionalPositiveInteger(values["target-count"], "--target-count")
1087
+ }));
1088
+ if (values.json) return writeJson(context.stdout, payload);
1089
+
1090
+ renderMotionDiscoveryRun(payload, context);
1091
+ }
1092
+
730
1093
  async function motionsAnalytics(args, context, { accountOverride } = {}) {
731
1094
  const { values, positionals } = parseCommandArgs(args, {
732
1095
  ...jsonOptions(),
@@ -788,11 +1151,11 @@ async function motionsAddProspects(args, context, { accountOverride } = {}) {
788
1151
  }
789
1152
 
790
1153
  const [motionId, ...prospectIds] = positionals;
791
- const { client, accountId } = await requireAccountContext(context, { accountOverride });
1154
+ const { client, accountId, config } = await requireAccountContext(context, { accountOverride });
792
1155
  const { payload, rejected } = await performBulkMutation(() =>
793
1156
  client.addMotionProspects(accountId, motionId, compactObject({
794
1157
  prospect_ids: prospectIds,
795
- assigned_user_id: values["assigned-user"]
1158
+ assigned_user_id: resolveAccountUserId(values["assigned-user"], config, { accountOverride })
796
1159
  })));
797
1160
  if (values.json) {
798
1161
  writeJson(context.stdout, payload);
@@ -844,13 +1207,26 @@ async function motionsDelete(args, context, { accountOverride } = {}) {
844
1207
  async function motionsUpdate(args, context, { accountOverride } = {}) {
845
1208
  const { values, positionals } = parseCommandArgs(args, {
846
1209
  ...jsonOptions(),
847
- status: { type: "string" }
1210
+ status: { type: "string" },
1211
+ tags: { type: "string" }
848
1212
  });
849
- if (positionals.length !== 1 || !values.status) {
1213
+ const hasUpdateField = values.status || values.tags !== undefined;
1214
+ if (positionals.length !== 1 || !hasUpdateField) {
850
1215
  throw new CommandError(MOTIONS_UPDATE_USAGE);
851
1216
  }
852
1217
 
853
- return updateMotionStatus(positionals[0], values.status, context, { accountOverride, json: values.json });
1218
+ const normalizedStatus = normalizeMotionStatus(values.status);
1219
+ const motionAttributes = compactObject({
1220
+ status: normalizedStatus,
1221
+ play_tags: values.tags !== undefined ? tagList(values.tags) : undefined
1222
+ });
1223
+
1224
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1225
+ const motion = await client.updateMotion(accountId, positionals[0], { motion: motionAttributes });
1226
+ if (values.json) return writeJson(context.stdout, motion);
1227
+
1228
+ writeLine(context.stdout, `Updated motion ${display(motion?.name)} (${display(motion?.prefix_id)}).`);
1229
+ renderMotion(motion, context);
854
1230
  }
855
1231
 
856
1232
  async function motionsStatusShortcut(action, args, context, { accountOverride } = {}) {
@@ -869,10 +1245,7 @@ async function motionsStatusShortcut(action, args, context, { accountOverride }
869
1245
  }
870
1246
 
871
1247
  async function updateMotionStatus(motionId, status, context, { accountOverride, json } = {}) {
872
- const normalizedStatus = String(status || "").trim().toLowerCase();
873
- if (!MOTION_STATUS_VALUES.has(normalizedStatus)) {
874
- throw new CommandError(MOTIONS_UPDATE_USAGE);
875
- }
1248
+ const normalizedStatus = normalizeMotionStatus(status);
876
1249
 
877
1250
  const { client, accountId } = await requireAccountContext(context, { accountOverride });
878
1251
  const motion = await client.updateMotion(accountId, motionId, {
@@ -886,6 +1259,37 @@ async function updateMotionStatus(motionId, status, context, { accountOverride,
886
1259
  renderMotion(motion, context);
887
1260
  }
888
1261
 
1262
+ function normalizeMotionStatus(status) {
1263
+ if (status === undefined) return undefined;
1264
+
1265
+ const normalizedStatus = String(status || "").trim().toLowerCase();
1266
+ if (!MOTION_STATUS_VALUES.has(normalizedStatus)) {
1267
+ throw new CommandError(MOTIONS_UPDATE_USAGE);
1268
+ }
1269
+
1270
+ return normalizedStatus;
1271
+ }
1272
+
1273
+ async function motionsTagMutation(action, args, context, { accountOverride } = {}) {
1274
+ const usageText = action === "add" ? MOTIONS_ADD_TAG_USAGE : MOTIONS_REMOVE_TAG_USAGE;
1275
+ const { values, positionals } = parseCommandArgs(args, jsonOptions());
1276
+ if (positionals.length !== 2) {
1277
+ throw new CommandError(usageText);
1278
+ }
1279
+
1280
+ const [motionId, tag] = positionals;
1281
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1282
+ const motion = action === "add" ?
1283
+ await client.addMotionTag(accountId, motionId, { tag }) :
1284
+ await client.removeMotionTag(accountId, motionId, { tag });
1285
+ if (values.json) return writeJson(context.stdout, motion);
1286
+
1287
+ const verb = action === "add" ? "Added" : "Removed";
1288
+ const preposition = action === "add" ? "to" : "from";
1289
+ writeLine(context.stdout, `${verb} tag ${display(tag)} ${preposition} motion ${display(motion?.name)} (${display(motion?.prefix_id)}).`);
1290
+ if (Array.isArray(motion?.play_tags)) writeLine(context.stdout, `Tags: ${display(motion.play_tags.join(", "), "-")}`);
1291
+ }
1292
+
889
1293
  async function motionsClone(args, context, { accountOverride } = {}) {
890
1294
  const { values, positionals } = parseCommandArgs(args, {
891
1295
  ...jsonOptions(),
@@ -933,7 +1337,47 @@ async function motionsMoveProspects(args, context, { accountOverride } = {}) {
933
1337
  }
934
1338
 
935
1339
  async function prospectsList(args, context, { accountOverride } = {}) {
936
- const { values, positionals } = parseCommandArgs(args, {
1340
+ const { values, positionals } = parseCommandArgs(args, prospectFilterOptions());
1341
+ if (positionals.length > 0) throw new CommandError("Usage: audienti prospects list [--json] [filters] [--account <acct_id>]");
1342
+ if (values.csv && values.json) throw new CommandError("Choose one output format: use either --csv or --json.");
1343
+ validateProspectFilterValues(values);
1344
+
1345
+ const { client, accountId, config } = await requireAccountContext(context, { accountOverride });
1346
+ const query = prospectQueryFromValues(values, config, { accountOverride });
1347
+ const payload = values.all ?
1348
+ await fetchAllProspects(client, accountId, query, { totalLimit: parseProspectTotalLimit(values.limit) }) :
1349
+ await client.prospects(accountId, query);
1350
+ if (values.json) return writeJson(context.stdout, payload);
1351
+ if (values.csv) return writeLine(context.stdout, prospectsToCsv(payload?.prospects || []));
1352
+
1353
+ renderProspects(payload, context, { wide: values.wide || values.all, profiles: values.profiles });
1354
+ }
1355
+
1356
+ async function prospectsCheck(args, context, { accountOverride } = {}) {
1357
+ const { values, positionals } = parseCommandArgs(args, prospectFilterOptions());
1358
+ if (positionals.length > 0) throw new CommandError(PROSPECTS_CHECK_USAGE);
1359
+ if (values.csv && values.json) throw new CommandError("Choose one output format: use either --csv or --json.");
1360
+ if (values.company || values["company-profile"]) throw new CommandError("Company filters are not supported for `prospects check`; it already finds prospects missing a certified company.");
1361
+ validateProspectFilterValues(values);
1362
+
1363
+ const { client, accountId, config } = await requireAccountContext(context, { accountOverride });
1364
+ const query = {
1365
+ ...prospectQueryFromValues(values, config, { accountOverride }),
1366
+ data_quality: "missing_certified_company"
1367
+ };
1368
+ const rawPayload = values.all ?
1369
+ await fetchAllProspects(client, accountId, query, { totalLimit: parseProspectTotalLimit(values.limit) }) :
1370
+ await client.prospects(accountId, query);
1371
+ const payload = withProspectAppUrls(rawPayload, client.host);
1372
+
1373
+ if (values.json) return writeJson(context.stdout, payload);
1374
+ if (values.csv) return writeLine(context.stdout, prospectCheckToCsv(payload?.prospects || []));
1375
+
1376
+ renderProspectCheck(payload, context);
1377
+ }
1378
+
1379
+ function prospectFilterOptions() {
1380
+ return {
937
1381
  ...jsonOptions(),
938
1382
  all: { type: "boolean" },
939
1383
  csv: { type: "boolean" },
@@ -950,16 +1394,18 @@ async function prospectsList(args, context, { accountOverride } = {}) {
950
1394
  page: { type: "string" },
951
1395
  profiles: { type: "boolean" },
952
1396
  wide: { type: "boolean" }
953
- });
954
- if (positionals.length > 0) throw new CommandError("Usage: audienti prospects list [--json] [filters] [--account <acct_id>]");
955
- if (values.csv && values.json) throw new CommandError("Choose one output format: use either --csv or --json.");
1397
+ };
1398
+ }
1399
+
1400
+ function validateProspectFilterValues(values) {
956
1401
  if (values.page && values.offset) throw new CommandError("Choose one pagination mode: use either --page or --offset.");
957
1402
  if (values.all && (values.page || values.offset)) throw new CommandError("--all cannot be combined with --page or --offset.");
958
1403
  if (values.motion && values.play) throw new CommandError("Choose one motion filter: use either --motion or --play.");
959
1404
  if (values.company && values["company-profile"]) throw new CommandError("Choose one company filter: use either --company or --company-profile.");
1405
+ }
960
1406
 
961
- const { client, accountId } = await requireAccountContext(context, { accountOverride });
962
- const query = compactObject({
1407
+ function prospectQueryFromValues(values, config, { accountOverride } = {}) {
1408
+ return compactObject({
963
1409
  query: values.query,
964
1410
  company: values.company,
965
1411
  company_profile_id: values["company-profile"],
@@ -967,19 +1413,12 @@ async function prospectsList(args, context, { accountOverride } = {}) {
967
1413
  play_id: values.play,
968
1414
  list_id: values.list,
969
1415
  stage: values.stage,
970
- assigned_user_id: values["assigned-user"],
1416
+ assigned_user_id: resolveAccountUserId(values["assigned-user"], config, { accountOverride }),
971
1417
  limit: values.limit,
972
1418
  offset: values.offset,
973
1419
  page: values.page,
974
1420
  include_profiles: values.profiles
975
1421
  });
976
- const payload = values.all ?
977
- await fetchAllProspects(client, accountId, query, { totalLimit: parseProspectTotalLimit(values.limit) }) :
978
- await client.prospects(accountId, query);
979
- if (values.json) return writeJson(context.stdout, payload);
980
- if (values.csv) return writeLine(context.stdout, prospectsToCsv(payload?.prospects || []));
981
-
982
- renderProspects(payload, context, { wide: values.wide || values.all, profiles: values.profiles });
983
1422
  }
984
1423
 
985
1424
  async function prospectsAssign(args, context, { accountOverride } = {}) {
@@ -991,11 +1430,12 @@ async function prospectsAssign(args, context, { accountOverride } = {}) {
991
1430
  throw new CommandError(PROSPECTS_ASSIGN_USAGE);
992
1431
  }
993
1432
 
994
- const { client, accountId } = await requireAccountContext(context, { accountOverride });
1433
+ const { client, accountId, config } = await requireAccountContext(context, { accountOverride });
1434
+ const assignedUserId = resolveAccountUserId(values["assigned-user"], config, { accountOverride });
995
1435
  const { payload, rejected } = await performBulkMutation(() =>
996
1436
  client.assignProspects(accountId, {
997
1437
  prospect_ids: positionals,
998
- assigned_user_id: values["assigned-user"]
1438
+ assigned_user_id: assignedUserId
999
1439
  }));
1000
1440
  if (values.json) {
1001
1441
  writeJson(context.stdout, payload);
@@ -1004,7 +1444,7 @@ async function prospectsAssign(args, context, { accountOverride } = {}) {
1004
1444
 
1005
1445
  const successLabel = values["assigned-user"] === "unassign" ?
1006
1446
  `Unassigned ${successCount(payload)} prospects.` :
1007
- `Assigned ${successCount(payload)} prospects to ${display(values["assigned-user"])}.`;
1447
+ `Assigned ${successCount(payload)} prospects to ${display(assignedUserId)}.`;
1008
1448
  renderBulkMutationResult(payload, context, {
1009
1449
  successLabel,
1010
1450
  zeroSuccessLabel: "No prospects were assigned."
@@ -1023,6 +1463,32 @@ async function prospectsShow(args, context, { accountOverride } = {}) {
1023
1463
  renderProspect(prospect, context);
1024
1464
  }
1025
1465
 
1466
+ async function prospectsDisposition(action, args, context, { accountOverride } = {}) {
1467
+ const usageText = {
1468
+ reject: PROSPECTS_REJECT_USAGE,
1469
+ nurture: PROSPECTS_NURTURE_USAGE,
1470
+ restore: PROSPECTS_RESTORE_USAGE
1471
+ }[action];
1472
+ const { values, positionals } = parseCommandArgs(args, {
1473
+ ...jsonOptions(),
1474
+ reason: { type: "string" }
1475
+ });
1476
+ if (positionals.length !== 1 || (action !== "nurture" && values.reason)) {
1477
+ throw new CommandError(usageText);
1478
+ }
1479
+
1480
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1481
+ const prospectId = positionals[0];
1482
+ const payload = action === "reject" ?
1483
+ await client.rejectProspect(accountId, prospectId) :
1484
+ action === "restore" ?
1485
+ await client.restoreProspect(accountId, prospectId) :
1486
+ await client.nurtureProspect(accountId, prospectId, compactObject({ inactive_reason: values.reason }));
1487
+ if (values.json) return writeJson(context.stdout, payload);
1488
+
1489
+ renderProspectDisposition(payload, context, { action });
1490
+ }
1491
+
1026
1492
  async function prospectsTimeline(args, context, { accountOverride } = {}) {
1027
1493
  const { values, positionals } = parseCommandArgs(args, {
1028
1494
  ...jsonOptions(),
@@ -1413,12 +1879,12 @@ async function prospectsImport(args, context, { accountOverride } = {}) {
1413
1879
  throw new CommandError("Usage: audienti prospects import <linkedin_url> [--list <list_id>] [--motion <motn_id>] [--assigned-user <id|me>] [--json] [--account <acct_id>]");
1414
1880
  }
1415
1881
 
1416
- const { client, accountId } = await requireAccountContext(context, { accountOverride });
1882
+ const { client, accountId, config } = await requireAccountContext(context, { accountOverride });
1417
1883
  const payload = await client.prospectImport(accountId, compactObject({
1418
1884
  linkedin_url: positionals[0],
1419
1885
  list_id: values.list,
1420
1886
  motion_id: values.motion,
1421
- assigned_user_id: values["assigned-user"]
1887
+ assigned_user_id: resolveAccountUserId(values["assigned-user"], config, { accountOverride })
1422
1888
  }));
1423
1889
  if (values.json) return writeJson(context.stdout, payload);
1424
1890
 
@@ -1440,7 +1906,7 @@ async function prospectsImportBatch(args, context, { accountOverride } = {}) {
1440
1906
  const rows = await readProspectImportBatchFile(values.file);
1441
1907
  if (rows.length === 0) throw new CommandError("Import batch file did not contain any prospects.");
1442
1908
 
1443
- const { client, accountId } = await requireAccountContext(context, { accountOverride });
1909
+ const { client, accountId, config } = await requireAccountContext(context, { accountOverride });
1444
1910
  const result = {
1445
1911
  summary: {
1446
1912
  total: rows.length,
@@ -1456,7 +1922,7 @@ async function prospectsImportBatch(args, context, { accountOverride } = {}) {
1456
1922
  linkedin_url: row.linkedin_url,
1457
1923
  list_id: row.list_id || values.list,
1458
1924
  motion_id: row.motion_id || values.motion,
1459
- assigned_user_id: row.assigned_user_id || values["assigned-user"]
1925
+ assigned_user_id: resolveAccountUserId(row.assigned_user_id || values["assigned-user"], config, { accountOverride })
1460
1926
  });
1461
1927
 
1462
1928
  try {
@@ -1614,8 +2080,8 @@ async function analyticsProspects(args, context, { accountOverride } = {}) {
1614
2080
  const { values, positionals } = parseCommandArgs(args, analyticsProspectsOptions());
1615
2081
  if (positionals.length > 0) throw new CommandError(ANALYTICS_PROSPECTS_USAGE);
1616
2082
 
1617
- const { client, accountId } = await requireAccountContext(context, { accountOverride });
1618
- const payload = await client.analyticsProspects(accountId, analyticsQuery(values));
2083
+ const { client, accountId, config } = await requireAccountContext(context, { accountOverride });
2084
+ const payload = await client.analyticsProspects(accountId, analyticsQuery(values, config, { accountOverride }));
1619
2085
  if (values.json) return writeJson(context.stdout, payload);
1620
2086
 
1621
2087
  renderAnalyticsProspects(payload, context);
@@ -1634,13 +2100,13 @@ async function analyticsProspectsCohortAnalysis(args, context, { accountOverride
1634
2100
 
1635
2101
  const weeks = normalizedCohortAnalysisWeeks(values.weeks);
1636
2102
  const cohorts = weeklyCohorts({ weeks, now: currentDate(context) });
1637
- const { client, accountId } = await requireAccountContext(context, { accountOverride });
2103
+ const { client, accountId, config } = await requireAccountContext(context, { accountOverride });
1638
2104
  const rows = [];
1639
2105
 
1640
2106
  for (const cohort of cohorts) {
1641
2107
  const payload = await client.analyticsProspects(accountId, compactObject({
1642
2108
  window: values.window,
1643
- account_user_id: values.user,
2109
+ account_user_id: resolveAccountUserId(values.user, config, { accountOverride }),
1644
2110
  motion_id: values.motion,
1645
2111
  provenance: values.provenance,
1646
2112
  cohort_start: cohort.start_date,
@@ -1669,8 +2135,8 @@ async function analyticsUsers(args, context, { accountOverride } = {}) {
1669
2135
  validateDatePair(values.start, values.end, "--start", "--end");
1670
2136
  validateDatePair(values["cohort-start"], values["cohort-end"], "--cohort-start", "--cohort-end");
1671
2137
 
1672
- const { client, accountId } = await requireAccountContext(context, { accountOverride });
1673
- const payload = await client.analyticsUsers(accountId, analyticsUsersQuery(values));
2138
+ const { client, accountId, config } = await requireAccountContext(context, { accountOverride });
2139
+ const payload = await client.analyticsUsers(accountId, analyticsUsersQuery(values, config, { accountOverride }));
1674
2140
  if (values.json) return writeJson(context.stdout, payload);
1675
2141
 
1676
2142
  renderAnalyticsUsers(payload, context);
@@ -1680,8 +2146,8 @@ async function analyticsVisibility(args, context, { accountOverride } = {}) {
1680
2146
  const { values, positionals } = parseCommandArgs(args, analyticsOptions());
1681
2147
  if (positionals.length > 0) throw new CommandError("Usage: audienti analytics visibility [--window 24h] [--user <account_user_id|email|name|me>] [--json] [--account <acct_id>]");
1682
2148
 
1683
- const { client, accountId } = await requireAccountContext(context, { accountOverride });
1684
- const payload = await client.analyticsVisibility(accountId, analyticsQuery(values));
2149
+ const { client, accountId, config } = await requireAccountContext(context, { accountOverride });
2150
+ const payload = await client.analyticsVisibility(accountId, analyticsQuery(values, config, { accountOverride }));
1685
2151
  if (values.json) return writeJson(context.stdout, payload);
1686
2152
 
1687
2153
  renderAnalyticsVisibility(payload, context);
@@ -1691,13 +2157,25 @@ async function analyticsContent(args, context, { accountOverride } = {}) {
1691
2157
  const { values, positionals } = parseCommandArgs(args, analyticsOptions());
1692
2158
  if (positionals.length > 0) throw new CommandError("Usage: audienti analytics content [--window 24h] [--user <account_user_id|email|name|me>] [--json] [--account <acct_id>]");
1693
2159
 
1694
- const { client, accountId } = await requireAccountContext(context, { accountOverride });
1695
- const payload = await client.analyticsContent(accountId, analyticsQuery(values));
2160
+ const { client, accountId, config } = await requireAccountContext(context, { accountOverride });
2161
+ const payload = await client.analyticsContent(accountId, analyticsQuery(values, config, { accountOverride }));
1696
2162
  if (values.json) return writeJson(context.stdout, payload);
1697
2163
 
1698
2164
  renderAnalyticsContent(payload, context);
1699
2165
  }
1700
2166
 
2167
+ async function analyticsDashboard(args, context, { accountOverride } = {}) {
2168
+ const { values, positionals } = parseCommandArgs(args, analyticsDashboardOptions());
2169
+ if (positionals.length > 0) throw new CommandError(ANALYTICS_DASHBOARD_USAGE);
2170
+ validateDatePair(values["cohort-start"], values["cohort-end"], "--cohort-start", "--cohort-end");
2171
+
2172
+ const { client, accountId, config } = await requireAccountContext(context, { accountOverride });
2173
+ const payload = await client.analyticsDashboard(accountId, analyticsDashboardQuery(values, config, { accountOverride }));
2174
+ if (values.json) return writeJson(context.stdout, payload);
2175
+
2176
+ renderAnalyticsDashboard(payload, context);
2177
+ }
2178
+
1701
2179
  function parseCommandArgs(args, options) {
1702
2180
  try {
1703
2181
  return parseArgs({
@@ -1739,6 +2217,25 @@ async function requireAccountContext(context, { accountOverride } = {}) {
1739
2217
  };
1740
2218
  }
1741
2219
 
2220
+ function resolveAccountUserId(value, config = {}, { accountOverride } = {}) {
2221
+ const rawValue = String(value || "").trim();
2222
+ if (!rawValue) return undefined;
2223
+ if (rawValue.toLowerCase() !== "me") return rawValue;
2224
+
2225
+ return defaultAccountUserConfig(config, { accountOverride }).id || "me";
2226
+ }
2227
+
2228
+ function defaultAccountUserConfig(config = {}, { accountOverride } = {}) {
2229
+ if (!config.accountUserId) return {};
2230
+ if (accountOverride && accountOverride !== config.accountId) return {};
2231
+
2232
+ return {
2233
+ id: String(config.accountUserId),
2234
+ name: config.accountUserName,
2235
+ email: config.accountUserEmail
2236
+ };
2237
+ }
2238
+
1742
2239
  function clientFromConfig(config, context) {
1743
2240
  return new AudientiClient({
1744
2241
  host: config.host || DEFAULT_HOST,
@@ -1850,21 +2347,35 @@ function analyticsUsersOptions() {
1850
2347
  };
1851
2348
  }
1852
2349
 
1853
- function analyticsQuery(values) {
2350
+ function analyticsDashboardOptions() {
2351
+ return {
2352
+ ...jsonOptions(),
2353
+ "cohort-start": { type: "string" },
2354
+ "cohort-end": { type: "string" },
2355
+ "play-tag": { type: "string" },
2356
+ tag: { type: "string" },
2357
+ motion: { type: "string" },
2358
+ offer: { type: "string" },
2359
+ icp: { type: "string" },
2360
+ user: { type: "string" }
2361
+ };
2362
+ }
2363
+
2364
+ function analyticsQuery(values, config = {}, { accountOverride } = {}) {
1854
2365
  return compactObject({
1855
2366
  window: values.window,
1856
2367
  cohort_start: values["cohort-start"],
1857
2368
  cohort_end: values["cohort-end"],
1858
2369
  motion_id: values.motion,
1859
2370
  provenance: values.provenance,
1860
- account_user_id: values.user
2371
+ account_user_id: resolveAccountUserId(values.user, config, { accountOverride })
1861
2372
  });
1862
2373
  }
1863
2374
 
1864
- function analyticsUsersQuery(values) {
2375
+ function analyticsUsersQuery(values, config = {}, { accountOverride } = {}) {
1865
2376
  const hasDateRange = Boolean(values.start || values.end);
1866
2377
  return compactObject({
1867
- account_user_id: values.user || "me",
2378
+ account_user_id: resolveAccountUserId(values.user || "me", config, { accountOverride }),
1868
2379
  window: hasDateRange ? undefined : (values.window || "30d"),
1869
2380
  start_date: values.start,
1870
2381
  end_date: values.end,
@@ -1876,6 +2387,18 @@ function analyticsUsersQuery(values) {
1876
2387
  });
1877
2388
  }
1878
2389
 
2390
+ function analyticsDashboardQuery(values, config = {}, { accountOverride } = {}) {
2391
+ return compactObject({
2392
+ cohort_start_date: values["cohort-start"],
2393
+ cohort_end_date: values["cohort-end"],
2394
+ play_tag: values["play-tag"] || values.tag,
2395
+ motion_id: values.motion,
2396
+ offer_id: values.offer,
2397
+ icp_id: values.icp,
2398
+ account_user_id: resolveAccountUserId(values.user, config, { accountOverride })
2399
+ });
2400
+ }
2401
+
1879
2402
  function validateDatePair(start, end, startFlag, endFlag) {
1880
2403
  if ((start && !end) || (!start && end)) {
1881
2404
  throw new CommandError(`${startFlag} and ${endFlag} must be provided together.`);
@@ -1981,26 +2504,125 @@ function provenancePayload(provenance) {
1981
2504
 
1982
2505
  function compactObject(object) {
1983
2506
  return Object.fromEntries(
1984
- Object.entries(object).filter(([, value]) => value !== undefined && value !== null && String(value).trim() !== "")
2507
+ Object.entries(object).filter(([, value]) => {
2508
+ if (value === undefined || value === null) return false;
2509
+ if (Array.isArray(value)) return true;
2510
+
2511
+ return String(value).trim() !== "";
2512
+ })
1985
2513
  );
1986
2514
  }
1987
2515
 
2516
+ async function readLocalPackageMetadata() {
2517
+ const packageJson = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8"));
2518
+ return {
2519
+ name: packageJson.name || PACKAGE_NAME,
2520
+ version: packageJson.version
2521
+ };
2522
+ }
2523
+
2524
+ async function fetchLatestPackageVersion({ fetchImpl, registry }) {
2525
+ if (!fetchImpl) throw new Error("This Node runtime does not provide fetch.");
2526
+
2527
+ const response = await fetchImpl(registryLatestPackageUrl(registry), {
2528
+ headers: {
2529
+ accept: "application/vnd.npm.install-v1+json, application/json"
2530
+ }
2531
+ });
2532
+ const bodyText = await response.text();
2533
+ const body = bodyText ? JSON.parse(bodyText) : {};
2534
+
2535
+ if (!response.ok) {
2536
+ throw new Error(body?.error || body?.message || `Registry returned HTTP ${response.status}.`);
2537
+ }
2538
+
2539
+ const version = body?.version?.toString().trim();
2540
+ if (!version) throw new Error("Registry response did not include a version.");
2541
+
2542
+ return version;
2543
+ }
2544
+
2545
+ function registryLatestPackageUrl(registry) {
2546
+ const base = new URL(registry || DEFAULT_NPM_REGISTRY);
2547
+ if (!base.pathname.endsWith("/")) base.pathname = `${base.pathname}/`;
2548
+
2549
+ return new URL(`${encodeURIComponent(PACKAGE_NAME)}/latest`, base).toString();
2550
+ }
2551
+
2552
+ function updateCheckPayload({ currentVersion, latestVersion, status, updateAvailable, registry, error, now }) {
2553
+ return {
2554
+ kind: "update_check",
2555
+ package_name: PACKAGE_NAME,
2556
+ current_version: currentVersion,
2557
+ latest_version: latestVersion,
2558
+ update_available: updateAvailable,
2559
+ status,
2560
+ install_command: `npm install --global ${PACKAGE_NAME}`,
2561
+ registry,
2562
+ checked_at: now.toISOString(),
2563
+ error: error || null
2564
+ };
2565
+ }
2566
+
2567
+ function compareVersions(currentVersion, latestVersion) {
2568
+ const current = parseVersion(currentVersion);
2569
+ const latest = parseVersion(latestVersion);
2570
+
2571
+ for (let index = 0; index < 3; index += 1) {
2572
+ if (current.parts[index] !== latest.parts[index]) return current.parts[index] - latest.parts[index];
2573
+ }
2574
+
2575
+ if (current.prerelease === latest.prerelease) return 0;
2576
+ if (!current.prerelease) return 1;
2577
+ if (!latest.prerelease) return -1;
2578
+
2579
+ return current.prerelease.localeCompare(latest.prerelease);
2580
+ }
2581
+
2582
+ function parseVersion(version) {
2583
+ const [core, prerelease = ""] = String(version || "").split("-", 2);
2584
+ const parts = core.split(".").map((part) => Number.parseInt(part, 10));
2585
+
2586
+ return {
2587
+ parts: [parts[0] || 0, parts[1] || 0, parts[2] || 0],
2588
+ prerelease
2589
+ };
2590
+ }
2591
+
2592
+ function tagList(value) {
2593
+ if (value === undefined) return undefined;
2594
+
2595
+ return String(value)
2596
+ .split(/[\r\n,;]+/)
2597
+ .map((tag) => tag.trim().toLowerCase())
2598
+ .filter(Boolean)
2599
+ .filter((tag, index, tags) => tags.indexOf(tag) === index);
2600
+ }
2601
+
2602
+ function filterRecordsByTag(records, value, field) {
2603
+ const tag = tagList(value)?.[0];
2604
+ if (!tag) return records;
2605
+
2606
+ return Array.isArray(records) ? records.filter((record) => (tagList(record?.[field]) || []).includes(tag)) : [];
2607
+ }
2608
+
1988
2609
  async function icpCreatePayload(values) {
1989
2610
  if (values.payload) {
1990
- if (values.name || values.notes || values["discovery-keyword"]) {
1991
- throw new CommandError("Choose one ICP input mode: either --payload <file.json> or the simple --name/--notes/--discovery-keyword flags.");
2611
+ if (values.name || values.notes || values.tags !== undefined || values["discovery-keyword"]) {
2612
+ throw new CommandError("Choose one ICP input mode: either --payload <file.json> or the simple --name/--notes/--discovery-keyword/tags flags.");
1992
2613
  }
1993
2614
 
1994
2615
  return readJsonPayload(values.payload);
1995
2616
  }
1996
2617
 
1997
2618
  if (!values.name) {
1998
- throw new CommandError("Usage: audienti icps create (--name <text> [--notes <text>] [--discovery-keyword <text>] | --payload <file.json>) [--json] [--account <acct_id>]");
2619
+ throw new CommandError("Usage: audienti icps create (--name <text> [--notes <text>] [--discovery-keyword <text>] [--tags <tag[,tag...]>] | --payload <file.json>) [--json] [--account <acct_id>]");
1999
2620
  }
2000
2621
 
2001
2622
  return compactObject({
2002
2623
  name: values.name,
2003
2624
  notes: values.notes,
2625
+ tags: values.tags !== undefined ? tagList(values.tags) : undefined,
2004
2626
  discovery_keyword: values["discovery-keyword"]
2005
2627
  });
2006
2628
  }
@@ -2141,6 +2763,17 @@ function normalizePositiveInteger(value) {
2141
2763
  return parsed;
2142
2764
  }
2143
2765
 
2766
+ function normalizeOptionalPositiveInteger(value, flagName) {
2767
+ if (value === undefined || value === null || value === "") return undefined;
2768
+
2769
+ const parsed = normalizePositiveInteger(value);
2770
+ if (parsed === null || String(parsed) !== String(value).trim()) {
2771
+ throw new CommandError(`${flagName} must be a positive integer.`);
2772
+ }
2773
+
2774
+ return parsed;
2775
+ }
2776
+
2144
2777
  function normalizeLookupType(value) {
2145
2778
  const normalized = String(value || "").trim().toLowerCase();
2146
2779
  if (normalized === "email" || normalized === "phone") return normalized;
@@ -2289,6 +2922,22 @@ function writeJson(stream, value) {
2289
2922
  writeLine(stream, JSON.stringify(value, null, 2));
2290
2923
  }
2291
2924
 
2925
+ function renderUpdateCheck(payload, context) {
2926
+ writeLine(context.stdout, `Package: ${payload.package_name}`);
2927
+ writeLine(context.stdout, `Current version: ${display(payload.current_version, "-")}`);
2928
+ writeLine(context.stdout, `Latest version: ${display(payload.latest_version, "unknown")}`);
2929
+
2930
+ if (payload.status === "update_available") {
2931
+ writeLine(context.stdout, "Status: update available");
2932
+ writeLine(context.stdout, `Update: ${payload.install_command}`);
2933
+ } else if (payload.status === "current") {
2934
+ writeLine(context.stdout, "Status: current");
2935
+ } else {
2936
+ writeLine(context.stdout, "Status: unknown");
2937
+ if (payload.error) writeLine(context.stdout, `Error: ${payload.error}`);
2938
+ }
2939
+ }
2940
+
2292
2941
  async function sleep(milliseconds) {
2293
2942
  await new Promise((resolve) => setTimeout(resolve, milliseconds));
2294
2943
  }
@@ -2305,9 +2954,48 @@ function renderLists(lists, context) {
2305
2954
  function renderList(list, context) {
2306
2955
  writeLine(context.stdout, `List: ${display(list?.name)} (${display(list?.prefix_id)})`);
2307
2956
  writeLine(context.stdout, `Prospects: ${display(list?.prospect_count, 0)}`);
2957
+ if (Array.isArray(list?.tags)) writeLine(context.stdout, `Tags: ${display(list.tags.join(", "), "-")}`);
2308
2958
  if (list?.description) writeLine(context.stdout, `Description: ${list.description}`);
2309
2959
  }
2310
2960
 
2961
+ function renderTags(tags, context) {
2962
+ if (!Array.isArray(tags) || tags.length === 0) return writeLine(context.stdout, "No tags found.");
2963
+
2964
+ writeLine(context.stdout, "TAG\tICPS\tLISTS\tMOTIONS\tTOTAL");
2965
+ for (const tag of tags) {
2966
+ writeLine(
2967
+ context.stdout,
2968
+ [
2969
+ display(tag.name),
2970
+ display(tag.icp_count, 0),
2971
+ display(tag.list_count, 0),
2972
+ display(tag.motion_count, 0),
2973
+ display(tag.total_count, 0)
2974
+ ].join("\t")
2975
+ );
2976
+ }
2977
+ }
2978
+
2979
+ function renderTagDetails(payload, context) {
2980
+ const icps = Array.isArray(payload?.icps) ? payload.icps : [];
2981
+ const lists = Array.isArray(payload?.lists) ? payload.lists : [];
2982
+ const motions = Array.isArray(payload?.motions) ? payload.motions : [];
2983
+
2984
+ writeLine(context.stdout, `Tag: ${display(payload?.tag)}`);
2985
+ writeLine(context.stdout, `ICPs: ${icps.length}`);
2986
+ writeLine(context.stdout, `Lists: ${lists.length}`);
2987
+ writeLine(context.stdout, `Motions: ${motions.length}`);
2988
+ writeLine(context.stdout);
2989
+ writeLine(context.stdout, "ICPs");
2990
+ renderIcps(icps, context);
2991
+ writeLine(context.stdout);
2992
+ writeLine(context.stdout, "Lists");
2993
+ renderLists(lists, context);
2994
+ writeLine(context.stdout);
2995
+ writeLine(context.stdout, "Motions");
2996
+ renderMotions(motions, context);
2997
+ }
2998
+
2311
2999
  function renderUsers(users, context) {
2312
3000
  if (!Array.isArray(users) || users.length === 0) return writeLine(context.stdout, "No account users found.");
2313
3001
 
@@ -2377,16 +3065,23 @@ function renderOffers(offers, context) {
2377
3065
  }
2378
3066
  }
2379
3067
 
3068
+ function renderOffer(offer, context) {
3069
+ writeLine(context.stdout, `Offer: ${display(offer?.name)} (${display(offer?.prefix_id)})`);
3070
+ if (offer?.description) writeLine(context.stdout, `Description: ${offer.description}`);
3071
+ if (offer?.url) writeLine(context.stdout, `URL: ${offer.url}`);
3072
+ }
3073
+
2380
3074
  function renderIcps(icps, context) {
2381
3075
  if (!Array.isArray(icps) || icps.length === 0) return writeLine(context.stdout, "No ICPs found.");
2382
3076
 
2383
- writeLine(context.stdout, "ICP ID\tNAME\tDISCOVERY KEYWORD\tAGENT");
3077
+ writeLine(context.stdout, "ICP ID\tNAME\tTAGS\tDISCOVERY KEYWORD\tAGENT");
2384
3078
  for (const icp of icps) {
2385
3079
  writeLine(
2386
3080
  context.stdout,
2387
3081
  [
2388
3082
  display(icp.prefix_id),
2389
3083
  display(icp.name),
3084
+ display(Array.isArray(icp.tags) && icp.tags.length > 0 ? icp.tags.join(",") : "-"),
2390
3085
  display(icp.discovery_keyword),
2391
3086
  display(icp.agent?.name)
2392
3087
  ].join("\t")
@@ -2394,6 +3089,14 @@ function renderIcps(icps, context) {
2394
3089
  }
2395
3090
  }
2396
3091
 
3092
+ function renderIcp(icp, context) {
3093
+ writeLine(context.stdout, `ICP: ${display(icp?.name)} (${display(icp?.prefix_id)})`);
3094
+ if (Array.isArray(icp?.tags)) writeLine(context.stdout, `Tags: ${display(icp.tags.join(", "), "-")}`);
3095
+ if (icp?.notes) writeLine(context.stdout, `Notes: ${icp.notes}`);
3096
+ if (icp?.discovery_keyword) writeLine(context.stdout, `Discovery keyword: ${icp.discovery_keyword}`);
3097
+ if (icp?.agent?.name) writeLine(context.stdout, `Agent: ${icp.agent.name}`);
3098
+ }
3099
+
2397
3100
  function renderCompanies(payload, context) {
2398
3101
  const companies = Array.isArray(payload?.companies) ? payload.companies : [];
2399
3102
  if (companies.length === 0) return writeLine(context.stdout, "No companies found.");
@@ -2458,6 +3161,7 @@ function renderMotion(motion, context) {
2458
3161
  if (motion?.offer?.name) writeLine(context.stdout, `Offer: ${motion.offer.name} (${display(motion.offer.prefix_id)})`);
2459
3162
  if (motion?.icp?.name) writeLine(context.stdout, `ICP: ${motion.icp.name} (${display(motion.icp.prefix_id)})`);
2460
3163
  if (motion?.list?.name) writeLine(context.stdout, `List: ${motion.list.name} (${display(motion.list.prefix_id)})`);
3164
+ if (Array.isArray(motion?.play_tags)) writeLine(context.stdout, `Tags: ${display(motion.play_tags.join(", "), "-")}`);
2461
3165
  if (motion?.principal_account_user?.id) {
2462
3166
  writeLine(
2463
3167
  context.stdout,
@@ -2525,6 +3229,27 @@ function renderProspects(payload, context, { wide = false, profiles = false } =
2525
3229
  }
2526
3230
  }
2527
3231
 
3232
+ function renderProspectCheck(payload, context) {
3233
+ const prospects = Array.isArray(payload?.prospects) ? payload.prospects : [];
3234
+ const totalCount = display(payload?.meta?.total_count, prospects.length);
3235
+ if (prospects.length === 0) return writeLine(context.stdout, "No suspect prospects found.");
3236
+
3237
+ writeLine(context.stdout, `Suspect prospects: ${totalCount}`);
3238
+ writeLine(context.stdout, "PROSPECT ID\tSTAGE\tNAME\tREPORTED COMPANY\tCERTIFIED\tREASON\tURL");
3239
+ for (const prospect of prospects) {
3240
+ const certification = prospect.company_certification || {};
3241
+ writeLine(context.stdout, [
3242
+ display(prospect.prefix_id),
3243
+ display(prospect.account_prospect?.pipeline_stage),
3244
+ display(prospect.display_name || prospect.name),
3245
+ display(certification.reported_company || prospect.company),
3246
+ certification.status === "certified" ? "yes" : "no",
3247
+ display(certification.reason, "missing_employment_citation"),
3248
+ display(prospect.app_url)
3249
+ ].join("\t"));
3250
+ }
3251
+ }
3252
+
2528
3253
  function renderProspect(prospect, context) {
2529
3254
  writeLine(context.stdout, `Prospect: ${display(prospect?.display_name || prospect?.name)} (${display(prospect?.prefix_id)})`);
2530
3255
  if (prospect?.company) writeLine(context.stdout, `Company: ${prospect.company}`);
@@ -2534,6 +3259,21 @@ function renderProspect(prospect, context) {
2534
3259
  if (nextActionLabel(prospect?.queue)) writeLine(context.stdout, `Next action: ${nextActionLabel(prospect.queue)}`);
2535
3260
  }
2536
3261
 
3262
+ function renderProspectDisposition(payload, context, { action }) {
3263
+ const prospect = payload?.prospect || {};
3264
+ const accountProspect = payload?.account_prospect || {};
3265
+ const actionLabel = {
3266
+ reject: "Rejected",
3267
+ nurture: "Moved to nurture",
3268
+ restore: "Restored"
3269
+ }[action] || display(payload?.status, "Updated");
3270
+
3271
+ writeLine(context.stdout, `${actionLabel} prospect ${display(prospect.display_name || prospect.name)} (${display(prospect.prefix_id)}).`);
3272
+ if (accountProspect.status) writeLine(context.stdout, `Status: ${accountProspect.status}`);
3273
+ if (accountProspect.inactive_reason) writeLine(context.stdout, `Inactive reason: ${accountProspect.inactive_reason}`);
3274
+ if (payload?.system_list?.name) writeLine(context.stdout, `List: ${payload.system_list.name} (${display(payload.system_list.prefix_id)})`);
3275
+ }
3276
+
2537
3277
  function renderProspectTimeline(payload, context) {
2538
3278
  const prospect = payload?.prospect || {};
2539
3279
  const timeline = Array.isArray(payload?.timeline) ? payload.timeline : [];
@@ -3056,6 +3796,32 @@ function renderMotionAnalytics(payload, context) {
3056
3796
  writeCountTable(context, "Prospect cohorts by produced day", payload?.prospects_by_day, ["DATE", "PRODUCED", "ACTIVE", "ACTIVE %", "INACTIVE", "STAGES"], dailyProspectRow);
3057
3797
  }
3058
3798
 
3799
+ function renderMotionDiscoveryRun(payload, context) {
3800
+ const motion = payload?.motion || {};
3801
+ const label = entityLabel(motion) || payload?.motion_id;
3802
+ const prefix = payload?.enqueued ? "Discovery queued" : "Discovery not queued";
3803
+ writeLine(context.stdout, `${prefix} for ${display(label)}.`);
3804
+ writeLine(context.stdout, `Reason: ${display(payload?.reason)}`);
3805
+ writeLine(context.stdout, `Target count: ${display(payload?.target_count)}`);
3806
+ }
3807
+
3808
+ function renderAnalyticsDashboard(payload, context) {
3809
+ writeLine(context.stdout, `Dashboard analytics (${display(payload?.cohort?.label, "selected cohort")})`);
3810
+ if (payload?.cohort) {
3811
+ writeLine(context.stdout, `Cohort: ${payload.cohort.start_date} to ${payload.cohort.end_date} (${display(payload.cohort.field, "account_prospects.created_at")})`);
3812
+ }
3813
+ if (payload?.activity) {
3814
+ writeLine(context.stdout, `Activity: ${payload.activity.start_date} to ${payload.activity.end_date} (${display(payload.activity.field, "events.created_at")})`);
3815
+ }
3816
+ writeDashboardFilters(payload, context);
3817
+ writeLine(context.stdout, `Prospects: ${display(payload?.cohort_size, 0)}`);
3818
+ writeLine(context.stdout, `Companies: ${display(payload?.cohort_company_target_count, 0)}`);
3819
+ writeLine(context.stdout, `People/company: ${display(payload?.cohort_people_per_company_average, "0.0")}`);
3820
+ writeLine(context.stdout, `Active: ${display(payload?.active_cohort_count, 0)} (${display(payload?.active_cohort_company_target_count, 0)} companies, ${percentageLabel(payload?.active_cohort_percentage)})`);
3821
+ writeLine(context.stdout, `Inactive: ${display(payload?.inactive_cohort_count, 0)}`);
3822
+ writeCountTable(context, "Current pipeline stages", payload?.pipeline_stage_counts, ["STAGE", "COUNT"], countRow);
3823
+ }
3824
+
3059
3825
  function renderAnalyticsProspectCohortAnalysis(payload, context) {
3060
3826
  const cohorts = Array.isArray(payload?.cohorts) ? payload.cohorts : [];
3061
3827
  writeLine(context.stdout, `Prospect cohort analysis (${display(payload?.weeks, cohorts.length)} weeks)`);
@@ -3130,6 +3896,19 @@ function writeAnalyticsScope(payload, context) {
3130
3896
  }
3131
3897
  }
3132
3898
 
3899
+ function writeDashboardFilters(payload, context) {
3900
+ const filters = payload?.filters || {};
3901
+ if (filters.motion) writeLine(context.stdout, `Motion: ${entityLabel(filters.motion)}`);
3902
+ if (filters.play_tag) writeLine(context.stdout, `Tag: ${filters.play_tag}`);
3903
+ if (filters.offer) writeLine(context.stdout, `Offer: ${entityLabel(filters.offer)}`);
3904
+ if (filters.icp) writeLine(context.stdout, `ICP: ${entityLabel(filters.icp)}`);
3905
+ if (filters.account_user) {
3906
+ writeLine(context.stdout, `User: ${entityLabel(filters.account_user)}`);
3907
+ } else {
3908
+ writeLine(context.stdout, "User: all account users");
3909
+ }
3910
+ }
3911
+
3133
3912
  function writeAnalyticsPlatform(payload, context) {
3134
3913
  if (!payload?.platform) return;
3135
3914
 
@@ -3569,6 +4348,77 @@ function prospectsToCsv(prospects) {
3569
4348
  ].join("\n");
3570
4349
  }
3571
4350
 
4351
+ function prospectCheckToCsv(prospects) {
4352
+ const headers = [
4353
+ "prefix_id",
4354
+ "display_name",
4355
+ "name",
4356
+ "title",
4357
+ "reported_company",
4358
+ "company_certification_status",
4359
+ "company_certification_reason",
4360
+ "email",
4361
+ "linkedin_url",
4362
+ "app_url",
4363
+ "account_prospect_status",
4364
+ "pipeline_stage",
4365
+ "assigned_to_account_user_id",
4366
+ "motion_prefix_id",
4367
+ "motion_name",
4368
+ "updated_at"
4369
+ ];
4370
+
4371
+ const rows = prospects.map((prospect) => {
4372
+ const certification = prospect.company_certification || {};
4373
+ return {
4374
+ prefix_id: prospect.prefix_id,
4375
+ display_name: prospect.display_name,
4376
+ name: prospect.name,
4377
+ title: prospect.title,
4378
+ reported_company: certification.reported_company || prospect.company,
4379
+ company_certification_status: certification.status,
4380
+ company_certification_reason: certification.reason,
4381
+ email: prospect.email,
4382
+ linkedin_url: prospect.linkedin_url,
4383
+ app_url: prospect.app_url,
4384
+ account_prospect_status: prospect.account_prospect?.status,
4385
+ pipeline_stage: prospect.account_prospect?.pipeline_stage,
4386
+ assigned_to_account_user_id: prospect.account_prospect?.assigned_to_account_user_id,
4387
+ motion_prefix_id: prospect.account_prospect?.motion?.prefix_id,
4388
+ motion_name: prospect.account_prospect?.motion?.name,
4389
+ updated_at: prospect.updated_at
4390
+ };
4391
+ });
4392
+
4393
+ return [
4394
+ headers.join(","),
4395
+ ...rows.map((row) => headers.map((header) => csvField(row[header])).join(","))
4396
+ ].join("\n");
4397
+ }
4398
+
4399
+ function withProspectAppUrls(payload, host) {
4400
+ if (!payload || !Array.isArray(payload.prospects)) return payload;
4401
+
4402
+ return {
4403
+ ...payload,
4404
+ prospects: payload.prospects.map((prospect) => ({
4405
+ ...prospect,
4406
+ app_url: prospectAppUrl(prospect, host)
4407
+ }))
4408
+ };
4409
+ }
4410
+
4411
+ function prospectAppUrl(prospect, host) {
4412
+ const prospectId = String(prospect?.prefix_id || "").trim();
4413
+ if (!prospectId) return undefined;
4414
+
4415
+ try {
4416
+ return new URL(`/prospects/${encodeURIComponent(prospectId)}`, normalizeHost(host)).toString();
4417
+ } catch {
4418
+ return undefined;
4419
+ }
4420
+ }
4421
+
3572
4422
  function sequenceExportRowsToCsv(rows) {
3573
4423
  return [
3574
4424
  SEQUENCE_EXPORT_CSV_COLUMNS.join(","),
@@ -3662,22 +4512,28 @@ const HELP_TOPICS = new Map([
3662
4512
  " audienti auth token <token> Save an API token",
3663
4513
  " audienti accounts list See accounts available to this token",
3664
4514
  " audienti accounts select <acct_id> Use one account by default",
4515
+ " audienti users select <user> Use one account user by default",
3665
4516
  " audienti help agent-workflows Common agent/operator paths",
3666
4517
  "",
3667
4518
  "Work areas:",
3668
4519
  " Setup & identity",
3669
4520
  " audienti auth status",
3670
4521
  " audienti config list",
4522
+ " audienti update check",
3671
4523
  " audienti users list",
3672
- " audienti users activity <account_user_id|me>",
4524
+ " audienti users select <account_user_id|email|name|me>",
4525
+ " audienti users activity [account_user_id|me]",
3673
4526
  "",
3674
4527
  " Motions / plays",
3675
4528
  " audienti motions list",
3676
4529
  " audienti motions show <motn_id>",
3677
4530
  " audienti motions analytics <motn_id>",
4531
+ " audienti motions run-discovery <motn_id>",
3678
4532
  " audienti motions prospects <motn_id>",
3679
4533
  " audienti motions create --payload <file.json>",
3680
- " audienti motions update <motn_id> --status <state>",
4534
+ " audienti motions update <motn_id> [--status <state>] [--tags <tag[,tag...]>]",
4535
+ " audienti motions add-tag <motn_id> <tag>",
4536
+ " audienti motions remove-tag <motn_id> <tag>",
3681
4537
  " audienti motions activate <motn_id>",
3682
4538
  " audienti motions pause <motn_id>",
3683
4539
  " audienti motions delete <motn_id> --confirm <yes|true|Y|y>",
@@ -3687,8 +4543,12 @@ const HELP_TOPICS = new Map([
3687
4543
  "",
3688
4544
  " Prospects",
3689
4545
  " audienti prospects list [filters]",
4546
+ " audienti prospects check [filters]",
3690
4547
  " audienti prospects show <prsp_id>",
3691
4548
  " audienti prospects assign <prsp_id> --assigned-user <id|me|unassign>",
4549
+ " audienti prospects reject <prsp_id>",
4550
+ " audienti prospects nurture <prsp_id> [--reason <reason>]",
4551
+ " audienti prospects restore <prsp_id>",
3692
4552
  " audienti prospects timeline <prsp_id>",
3693
4553
  " audienti prospects import <linkedin_url> [--motion <motn_id>]",
3694
4554
  " audienti prospects import-batch --file <csv|jsonl|json>",
@@ -3696,10 +4556,21 @@ const HELP_TOPICS = new Map([
3696
4556
  " audienti prospects add-profile <prsp_id> --url <profile_url|email|phone>",
3697
4557
  "",
3698
4558
  " Lists & targeting inputs",
3699
- " audienti lists list",
4559
+ " audienti lists list [--tag <tag>]",
3700
4560
  " audienti lists prospects <list_id>",
4561
+ " audienti lists add-tag <list_id> <tag>",
4562
+ " audienti lists remove-tag <list_id> <tag>",
4563
+ " audienti tags list",
4564
+ " audienti tags show <tag>",
3701
4565
  " audienti offers list",
3702
- " audienti icps list",
4566
+ " audienti offers show <offr_id>",
4567
+ " audienti offers update <offr_id> [--name <text>]",
4568
+ " audienti offers delete <offr_id> --confirm <yes|true|Y|y>",
4569
+ " audienti icps list [--tag <tag>]",
4570
+ " audienti icps show <icp_id>",
4571
+ " audienti icps update <icp_id> [--tags <tag[,tag...]>]",
4572
+ " audienti icps add-tag <icp_id> <tag>",
4573
+ " audienti icps remove-tag <icp_id> <tag>",
3703
4574
  " audienti companies search --query <text>",
3704
4575
  "",
3705
4576
  " Writer",
@@ -3714,6 +4585,7 @@ const HELP_TOPICS = new Map([
3714
4585
  "",
3715
4586
  " Analytics",
3716
4587
  " audienti analytics prospects --window 24h",
4588
+ " audienti analytics dashboard --play-tag <tag>",
3717
4589
  " audienti analytics prospects cohort-analysis --weeks 4 --motion <motn_id>",
3718
4590
  " audienti analytics users --user me --window 30d",
3719
4591
  " audienti analytics visibility --window 24h --user me",
@@ -3728,6 +4600,7 @@ const HELP_TOPICS = new Map([
3728
4600
  " Inspect a prospect: audienti prospects show <prsp_id> --json",
3729
4601
  " Preview a campaign: audienti writer test-run <prsp_id>",
3730
4602
  " Analyze one motion: audienti motions analytics <motn_id>",
4603
+ " Count one campaign: audienti analytics dashboard --play-tag <tag>",
3731
4604
  " Audit your work: audienti analytics users --user me --window 30d",
3732
4605
  "",
3733
4606
  "Global options:",
@@ -3789,7 +4662,8 @@ const HELP_TOPICS = new Map([
3789
4662
  " Host: string",
3790
4663
  " Token: masked string",
3791
4664
  " User: string",
3792
- " Active account: account name and acct_ id, or none selected"
4665
+ " Active account: account name and acct_ id, or none selected",
4666
+ " Default account user: account user name and id, or none selected"
3793
4667
  ].join("\n")],
3794
4668
 
3795
4669
  ["auth logout", [
@@ -3823,7 +4697,48 @@ const HELP_TOPICS = new Map([
3823
4697
  " Exists: yes|no",
3824
4698
  " Host: string or none",
3825
4699
  " Token: masked string or none",
3826
- " Active account: account name and acct_ id, or none selected"
4700
+ " Active account: account name and acct_ id, or none selected",
4701
+ " Default account user: account user name and id, or none selected"
4702
+ ].join("\n")],
4703
+
4704
+ ["update", [
4705
+ "Usage:",
4706
+ " audienti update check [--json] [--registry <url>]",
4707
+ "",
4708
+ "Status: implemented",
4709
+ "",
4710
+ "Purpose:",
4711
+ " Check whether this local Audienti CLI install is behind the latest published package.",
4712
+ "",
4713
+ "Commands:",
4714
+ " audienti update check Compare the local package version to the npm registry"
4715
+ ].join("\n")],
4716
+
4717
+ ["update check", [
4718
+ "Usage:",
4719
+ " audienti update check [--json] [--registry <url>]",
4720
+ "",
4721
+ "Status: implemented",
4722
+ "",
4723
+ "Purpose:",
4724
+ " Report whether this local CLI should be updated.",
4725
+ "",
4726
+ "Output shape:",
4727
+ " package_name: @audienti/cli",
4728
+ " current_version: local package version",
4729
+ " latest_version: latest registry version, or null when unknown",
4730
+ " update_available: true | false | null",
4731
+ " status: current | update_available | unknown",
4732
+ " install_command: npm install --global @audienti/cli",
4733
+ " registry: registry URL used for the check",
4734
+ " checked_at: ISO timestamp",
4735
+ " error: string | null",
4736
+ "",
4737
+ "Options:",
4738
+ " --registry <url> Alternate npm-compatible registry. Default: https://registry.npmjs.org",
4739
+ "",
4740
+ "Example:",
4741
+ " audienti update check --json"
3827
4742
  ].join("\n")],
3828
4743
 
3829
4744
  ["accounts", [
@@ -3868,12 +4783,13 @@ const HELP_TOPICS = new Map([
3868
4783
  ["users", [
3869
4784
  "Usage:",
3870
4785
  " audienti users list [--json]",
3871
- " audienti users activity <account_user_id|me> [--json]",
4786
+ " audienti users select <account_user_id|email|name|me>",
4787
+ " audienti users activity [account_user_id|me] [--json]",
3872
4788
  "",
3873
4789
  "Status: implemented",
3874
4790
  "",
3875
4791
  "Purpose:",
3876
- " List the account users that can be used as motion principals or assignees.",
4792
+ " List and select the account users that can be used as motion principals or assignees.",
3877
4793
  "",
3878
4794
  "CLI synonym:",
3879
4795
  " `principals` is accepted anywhere `users` is accepted"
@@ -3897,6 +4813,24 @@ const HELP_TOPICS = new Map([
3897
4813
  " current: boolean"
3898
4814
  ].join("\n")],
3899
4815
 
4816
+ ["users select", [
4817
+ "Usage:",
4818
+ " audienti users select <account_user_id|email|name|me> [--account <acct_id>]",
4819
+ "",
4820
+ "Status: implemented",
4821
+ "",
4822
+ "Purpose:",
4823
+ " Save a default account user for commands that accept `me` or default to the current operator.",
4824
+ "",
4825
+ "Behavior:",
4826
+ " Validates the account user against `audienti users list` for the active account before saving.",
4827
+ " Selecting a different account with `audienti accounts select` clears the saved account user.",
4828
+ " Passing --account selects the account user for that account and makes it the active account.",
4829
+ "",
4830
+ "API:",
4831
+ " GET /api/v1/accounts/:account_id/users.json"
4832
+ ].join("\n")],
4833
+
3900
4834
  ["users activity", [
3901
4835
  "Usage:",
3902
4836
  ` ${USERS_ACTIVITY_USAGE.slice("Usage: ".length)}`,
@@ -3907,7 +4841,7 @@ const HELP_TOPICS = new Map([
3907
4841
  " Inspect one workspace user's outbound activity feed and action summary.",
3908
4842
  "",
3909
4843
  "Input shape:",
3910
- " account_user_id: integer account user id, or me for the authenticated token user",
4844
+ " account_user_id: integer account user id, or me for the saved default account user when configured",
3911
4845
  " mode: actor | account_usage",
3912
4846
  " window: 24h | 7d | 30d",
3913
4847
  " platform: linkedin | email | gmail",
@@ -3919,12 +4853,15 @@ const HELP_TOPICS = new Map([
3919
4853
  ["offers", [
3920
4854
  "Usage:",
3921
4855
  " audienti offers list [--json]",
4856
+ " audienti offers show <offr_id> [--json]",
3922
4857
  " audienti offers create --name <text> [--json]",
4858
+ " audienti offers update <offr_id> [--name <text>] [--json]",
4859
+ " audienti offers delete <offr_id> --confirm <yes|true|Y|y> [--json]",
3923
4860
  "",
3924
4861
  "Status: implemented",
3925
4862
  "",
3926
4863
  "Purpose:",
3927
- " List the offers available to the current account so an agent can choose offer_id for motion creation."
4864
+ " Manage the offers available to the current account so an agent can choose offer_id for motion creation."
3928
4865
  ].join("\n")],
3929
4866
 
3930
4867
  ["offers list", [
@@ -3974,10 +4911,60 @@ const HELP_TOPICS = new Map([
3974
4911
  " }"
3975
4912
  ].join("\n")],
3976
4913
 
4914
+ ["offers show", [
4915
+ "Usage:",
4916
+ ` ${OFFERS_SHOW_USAGE.slice("Usage: ".length)}`,
4917
+ "",
4918
+ "Status: implemented",
4919
+ "",
4920
+ "Input shape:",
4921
+ " offr_id: offr_ prefixed id or integer id",
4922
+ "",
4923
+ "API:",
4924
+ " GET /api/v1/accounts/:account_id/offers/:id.json"
4925
+ ].join("\n")],
4926
+
4927
+ ["offers update", [
4928
+ "Usage:",
4929
+ ` ${OFFERS_UPDATE_USAGE.slice("Usage: ".length)}`,
4930
+ "",
4931
+ "Status: implemented",
4932
+ "",
4933
+ "Purpose:",
4934
+ " Update simple offer fields without changing linked motions.",
4935
+ "",
4936
+ "Input shape:",
4937
+ " offr_id: offr_ prefixed id or integer id",
4938
+ " name: string | optional",
4939
+ " description: string | optional",
4940
+ " url: string | optional",
4941
+ "",
4942
+ "API:",
4943
+ " PATCH /api/v1/accounts/:account_id/offers/:id.json"
4944
+ ].join("\n")],
4945
+
4946
+ ["offers delete", [
4947
+ "Usage:",
4948
+ ` ${OFFERS_DELETE_USAGE.slice("Usage: ".length)}`,
4949
+ "",
4950
+ "Status: implemented",
4951
+ "",
4952
+ "Input shape:",
4953
+ " offr_id: offr_ prefixed id or integer id",
4954
+ " confirm: one of yes, true, Y, y",
4955
+ "",
4956
+ "API:",
4957
+ " DELETE /api/v1/accounts/:account_id/offers/:id.json"
4958
+ ].join("\n")],
4959
+
3977
4960
  ["icps", [
3978
4961
  "Usage:",
3979
- " audienti icps list [--json]",
4962
+ " audienti icps list [--tag <tag>] [--json]",
4963
+ " audienti icps show <icp_id> [--json]",
3980
4964
  " audienti icps create (--name <text> | --payload <file.json>) [--json]",
4965
+ " audienti icps update <icp_id> [--tags <tag[,tag...]>] [--json]",
4966
+ " audienti icps add-tag <icp_id> <tag> [--json]",
4967
+ " audienti icps remove-tag <icp_id> <tag> [--json]",
3981
4968
  "",
3982
4969
  "Status: implemented",
3983
4970
  "",
@@ -3987,10 +4974,13 @@ const HELP_TOPICS = new Map([
3987
4974
 
3988
4975
  ["icps list", [
3989
4976
  "Usage:",
3990
- " audienti icps list [--json] [--account <acct_id>]",
4977
+ " audienti icps list [--tag <tag>] [--json] [--account <acct_id>]",
3991
4978
  "",
3992
4979
  "Status: implemented",
3993
4980
  "",
4981
+ "Options:",
4982
+ " --tag <tag> Filter locally to ICPs whose tags include the normalized tag",
4983
+ "",
3994
4984
  "API:",
3995
4985
  " GET /api/v1/accounts/:account_id/icps.json",
3996
4986
  "",
@@ -3999,13 +4989,14 @@ const HELP_TOPICS = new Map([
3999
4989
  " prefix_id: icpp_",
4000
4990
  " name: string",
4001
4991
  " notes: string | null",
4992
+ " tags: [string]",
4002
4993
  " discovery_keyword: string | null",
4003
4994
  " agent: { id, name } | null"
4004
4995
  ].join("\n")],
4005
4996
 
4006
4997
  ["icps create", [
4007
4998
  "Usage:",
4008
- " audienti icps create (--name <text> [--notes <text>] [--discovery-keyword <text>] | --payload <file.json>) [--json] [--account <acct_id>]",
4999
+ " audienti icps create (--name <text> [--notes <text>] [--discovery-keyword <text>] [--tags <tag[,tag...]>] | --payload <file.json>) [--json] [--account <acct_id>]",
4009
5000
  "",
4010
5001
  "Status: implemented",
4011
5002
  "",
@@ -4015,6 +5006,7 @@ const HELP_TOPICS = new Map([
4015
5006
  "Input shape:",
4016
5007
  " name: string Required ICP name",
4017
5008
  " notes: string | optional",
5009
+ " tags: comma-separated tag list | optional",
4018
5010
  " discovery_keyword: string | optional",
4019
5011
  " payload: file.json | optional full ICP object using the account API create shape",
4020
5012
  "",
@@ -4026,7 +5018,8 @@ const HELP_TOPICS = new Map([
4026
5018
  " \"icp\": {",
4027
5019
  " \"name\": \"Renewal-stage IT leaders\",",
4028
5020
  " \"notes\": \"IT leaders reviewing vendors before renewal or QBR.\",",
4029
- " \"discovery_keyword\": \"renewal\"",
5021
+ " \"discovery_keyword\": \"renewal\",",
5022
+ " \"tags\": [\"enterprise\", \"renewal\"]",
4030
5023
  " }",
4031
5024
  " }",
4032
5025
  "",
@@ -4047,6 +5040,90 @@ const HELP_TOPICS = new Map([
4047
5040
  " }"
4048
5041
  ].join("\n")],
4049
5042
 
5043
+ ["icps show", [
5044
+ "Usage:",
5045
+ ` ${ICPS_SHOW_USAGE.slice("Usage: ".length)}`,
5046
+ "",
5047
+ "Status: implemented",
5048
+ "",
5049
+ "Input shape:",
5050
+ " icp_id: icpp_ prefixed id or integer id",
5051
+ "",
5052
+ "API:",
5053
+ " GET /api/v1/accounts/:account_id/icps/:id.json"
5054
+ ].join("\n")],
5055
+
5056
+ ["icps update", [
5057
+ "Usage:",
5058
+ " audienti icps update <icp_id> [--name <text>] [--notes <text>] [--discovery-keyword <text>] [--tags <tag[,tag...]>] [--json] [--account <acct_id>]",
5059
+ "",
5060
+ "Status: implemented",
5061
+ "",
5062
+ "Purpose:",
5063
+ " Update simple ICP fields or replace the ICP's full tag set.",
5064
+ "",
5065
+ "Input shape:",
5066
+ " icp_id: icpp_ prefixed id or integer id",
5067
+ " name: string | optional",
5068
+ " notes: string | optional",
5069
+ " discovery_keyword: string | optional",
5070
+ " tags: comma-separated tag list | optional",
5071
+ "",
5072
+ "API:",
5073
+ " PATCH /api/v1/accounts/:account_id/icps/:id.json",
5074
+ "",
5075
+ "JSON body:",
5076
+ " {",
5077
+ " \"icp\": {",
5078
+ " \"tags\": [\"enterprise\", \"renewal\"]",
5079
+ " }",
5080
+ " }"
5081
+ ].join("\n")],
5082
+
5083
+ ["icps add-tag", [
5084
+ "Usage:",
5085
+ " audienti icps add-tag <icp_id> <tag> [--json] [--account <acct_id>]",
5086
+ "",
5087
+ "Status: implemented",
5088
+ "",
5089
+ "Purpose:",
5090
+ " Add one normalized tag to an ICP.",
5091
+ "",
5092
+ "Input shape:",
5093
+ " icp_id: icpp_ prefixed id or integer id",
5094
+ " tag: string",
5095
+ "",
5096
+ "API:",
5097
+ " POST /api/v1/accounts/:account_id/icps/:id/add_tag.json",
5098
+ "",
5099
+ "JSON body:",
5100
+ " {",
5101
+ " \"tag\": \"enterprise\"",
5102
+ " }"
5103
+ ].join("\n")],
5104
+
5105
+ ["icps remove-tag", [
5106
+ "Usage:",
5107
+ " audienti icps remove-tag <icp_id> <tag> [--json] [--account <acct_id>]",
5108
+ "",
5109
+ "Status: implemented",
5110
+ "",
5111
+ "Purpose:",
5112
+ " Remove one normalized tag from an ICP.",
5113
+ "",
5114
+ "Input shape:",
5115
+ " icp_id: icpp_ prefixed id or integer id",
5116
+ " tag: string",
5117
+ "",
5118
+ "API:",
5119
+ " DELETE /api/v1/accounts/:account_id/icps/:id/remove_tag.json",
5120
+ "",
5121
+ "JSON body:",
5122
+ " {",
5123
+ " \"tag\": \"enterprise\"",
5124
+ " }"
5125
+ ].join("\n")],
5126
+
4050
5127
  ["companies", [
4051
5128
  "Usage:",
4052
5129
  " audienti companies search --query <text> [--json]",
@@ -4078,12 +5155,68 @@ const HELP_TOPICS = new Map([
4078
5155
  " companies[].location: string | null"
4079
5156
  ].join("\n")],
4080
5157
 
5158
+ ["tags", [
5159
+ "Usage:",
5160
+ " audienti tags list [--json]",
5161
+ " audienti tags show <tag> [--json]",
5162
+ "",
5163
+ "Status: implemented",
5164
+ "",
5165
+ "Purpose:",
5166
+ " Show the shared vocabulary from ICP tags, list tags, and motion play_tags currently in use.",
5167
+ "",
5168
+ "Run `audienti tags list help` or `audienti tags show help` for output shape."
5169
+ ].join("\n")],
5170
+
5171
+ ["tags list", [
5172
+ "Usage:",
5173
+ " audienti tags list [--json] [--account <acct_id>]",
5174
+ "",
5175
+ "Status: implemented",
5176
+ "",
5177
+ "Purpose:",
5178
+ " List normalized tags currently used by account ICPs, lists, and motions so new records can match existing labels.",
5179
+ "",
5180
+ "API:",
5181
+ " GET /api/v1/accounts/:account_id/tags.json",
5182
+ "",
5183
+ "Output shape:",
5184
+ " tags[].name: normalized tag string",
5185
+ " tags[].icp_count: number of ICPs using the tag",
5186
+ " tags[].list_count: number of lists using the tag",
5187
+ " tags[].motion_count: number of motions using the tag",
5188
+ " tags[].total_count: icp_count + list_count + motion_count"
5189
+ ].join("\n")],
5190
+
5191
+ ["tags show", [
5192
+ "Usage:",
5193
+ " audienti tags show <tag> [--json] [--account <acct_id>]",
5194
+ "",
5195
+ "Status: implemented",
5196
+ "",
5197
+ "Purpose:",
5198
+ " Show the ICPs, lists, and motions currently using one normalized tag.",
5199
+ "",
5200
+ "API:",
5201
+ " GET /api/v1/accounts/:account_id/icps.json",
5202
+ " GET /api/v1/accounts/:account_id/lists.json",
5203
+ " GET /api/v1/accounts/:account_id/motions.json",
5204
+ "",
5205
+ "Output shape:",
5206
+ " tag: normalized tag string",
5207
+ " icps[]: ICP rows whose tags include tag",
5208
+ " lists[]: list rows whose tags include tag",
5209
+ " motions[]: motion rows whose play_tags include tag"
5210
+ ].join("\n")],
5211
+
4081
5212
  ["lists", [
4082
5213
  "Usage:",
4083
- " audienti lists list [--json]",
4084
- " audienti lists create --name <text> [--json]",
5214
+ " audienti lists list [--tag <tag>] [--json]",
5215
+ " audienti lists create --name <text> [--tags <tag[,tag...]>] [--json]",
4085
5216
  " audienti lists show <list_id> [--json]",
4086
- " audienti lists update <list_id> [--json]",
5217
+ " audienti lists update <list_id> [--tags <tag[,tag...]>] [--json]",
5218
+ " audienti lists add-tag <list_id> <tag> [--json]",
5219
+ " audienti lists remove-tag <list_id> <tag> [--json]",
4087
5220
  " audienti lists delete <list_id> --confirm <yes|true|Y|y> [--json]",
4088
5221
  " audienti lists prospects <list_id> [--json]",
4089
5222
  " audienti lists add-prospects <list_id> <prsp_id> [prsp_id...] [--json]",
@@ -4097,10 +5230,13 @@ const HELP_TOPICS = new Map([
4097
5230
 
4098
5231
  ["lists list", [
4099
5232
  "Usage:",
4100
- " audienti lists list [--json] [--account <acct_id>]",
5233
+ " audienti lists list [--tag <tag>] [--json] [--account <acct_id>]",
4101
5234
  "",
4102
5235
  "Status: implemented",
4103
5236
  "",
5237
+ "Options:",
5238
+ " --tag <tag> Filter locally to lists whose tags include the normalized tag",
5239
+ "",
4104
5240
  "API:",
4105
5241
  " GET /api/v1/accounts/:account_id/lists.json",
4106
5242
  "",
@@ -4111,12 +5247,13 @@ const HELP_TOPICS = new Map([
4111
5247
  " description: string | null",
4112
5248
  " prospect_count: integer",
4113
5249
  " protected_system_list: boolean",
4114
- " hubspot_synced: boolean"
5250
+ " hubspot_synced: boolean",
5251
+ " tags: [string]"
4115
5252
  ].join("\n")],
4116
5253
 
4117
5254
  ["lists create", [
4118
5255
  "Usage:",
4119
- " audienti lists create --name <text> [--description <text>] [--campaign-hook <text>] [--audience-note <text>] [--json] [--account <acct_id>]",
5256
+ " audienti lists create --name <text> [--description <text>] [--tags <tag[,tag...]>] [--campaign-hook <text>] [--audience-note <text>] [--json] [--account <acct_id>]",
4120
5257
  "",
4121
5258
  "Status: implemented",
4122
5259
  "",
@@ -4126,6 +5263,7 @@ const HELP_TOPICS = new Map([
4126
5263
  "Input shape:",
4127
5264
  " name: string Required list name",
4128
5265
  " description: string | optional",
5266
+ " tags: comma-separated tag list | optional",
4129
5267
  " campaign_hook: string | optional",
4130
5268
  " audience_note: string | optional",
4131
5269
  "",
@@ -4134,10 +5272,11 @@ const HELP_TOPICS = new Map([
4134
5272
  "",
4135
5273
  "JSON body:",
4136
5274
  " {",
4137
- " \"list\": {",
4138
- " \"name\": \"CIO renewal targets\",",
4139
- " \"description\": \"Accounts to review before QBR outreach.\",",
4140
- " \"campaign_brief\": {",
5275
+ " \"list\": {",
5276
+ " \"name\": \"CIO renewal targets\",",
5277
+ " \"description\": \"Accounts to review before QBR outreach.\",",
5278
+ " \"tags\": [\"sarit\", \"pj\"],",
5279
+ " \"campaign_brief\": {",
4141
5280
  " \"hook\": \"Vendor accountability before renewal\",",
4142
5281
  " \"audience_note\": \"IT leaders running QBRs and renewals\"",
4143
5282
  " }",
@@ -4160,7 +5299,7 @@ const HELP_TOPICS = new Map([
4160
5299
 
4161
5300
  ["lists update", [
4162
5301
  "Usage:",
4163
- " audienti lists update <list_id> [--name <text>] [--description <text>] [--campaign-hook <text>] [--audience-note <text>] [--json] [--account <acct_id>]",
5302
+ " audienti lists update <list_id> [--name <text>] [--description <text>] [--tags <tag[,tag...]>] [--campaign-hook <text>] [--audience-note <text>] [--json] [--account <acct_id>]",
4164
5303
  "",
4165
5304
  "Status: implemented",
4166
5305
  "",
@@ -4171,6 +5310,7 @@ const HELP_TOPICS = new Map([
4171
5310
  " list_id: list_ prefix id",
4172
5311
  " list.name: string | optional",
4173
5312
  " list.description: string | optional",
5313
+ " list.tags: comma-separated tag list | optional",
4174
5314
  " list.campaign_brief.hook: string | optional",
4175
5315
  " list.campaign_brief.audience_note: string | optional",
4176
5316
  "",
@@ -4178,6 +5318,50 @@ const HELP_TOPICS = new Map([
4178
5318
  " PATCH /api/v1/accounts/:account_id/lists/:id.json"
4179
5319
  ].join("\n")],
4180
5320
 
5321
+ ["lists add-tag", [
5322
+ "Usage:",
5323
+ ` ${LISTS_ADD_TAG_USAGE.slice("Usage: ".length)}`,
5324
+ "",
5325
+ "Status: implemented",
5326
+ "",
5327
+ "Purpose:",
5328
+ " Add one normalized tag to a list without changing prospect membership.",
5329
+ "",
5330
+ "Input shape:",
5331
+ " list_id: list_ prefix id",
5332
+ " tag: string",
5333
+ "",
5334
+ "API:",
5335
+ " POST /api/v1/accounts/:account_id/lists/:id/add_tag.json",
5336
+ "",
5337
+ "JSON body:",
5338
+ " {",
5339
+ " \"tag\": \"sarit\"",
5340
+ " }"
5341
+ ].join("\n")],
5342
+
5343
+ ["lists remove-tag", [
5344
+ "Usage:",
5345
+ ` ${LISTS_REMOVE_TAG_USAGE.slice("Usage: ".length)}`,
5346
+ "",
5347
+ "Status: implemented",
5348
+ "",
5349
+ "Purpose:",
5350
+ " Remove one normalized tag from a list.",
5351
+ "",
5352
+ "Input shape:",
5353
+ " list_id: list_ prefix id",
5354
+ " tag: string",
5355
+ "",
5356
+ "API:",
5357
+ " DELETE /api/v1/accounts/:account_id/lists/:id/remove_tag.json",
5358
+ "",
5359
+ "JSON body:",
5360
+ " {",
5361
+ " \"tag\": \"sarit\"",
5362
+ " }"
5363
+ ].join("\n")],
5364
+
4181
5365
  ["lists delete", [
4182
5366
  "Usage:",
4183
5367
  " audienti lists delete <list_id> --confirm <yes|true|Y|y> [--json] [--account <acct_id>]",
@@ -4270,14 +5454,17 @@ const HELP_TOPICS = new Map([
4270
5454
 
4271
5455
  ["motions", [
4272
5456
  "Usage:",
4273
- " audienti motions list [--json]",
5457
+ " audienti motions list [--tag <tag>] [--json]",
4274
5458
  " audienti motions show <motn_id> [--json]",
4275
5459
  " audienti motions status <motn_id> [--json]",
5460
+ " audienti motions run-discovery <motn_id> [--target-count <n>] [--json]",
4276
5461
  " audienti motions analytics <motn_id> [--window 30d] [--json]",
4277
5462
  " audienti motions prospects <motn_id> [--json]",
4278
5463
  " audienti motions add-prospects <motn_id> <prsp_id> [prsp_id...] [--json]",
4279
5464
  " audienti motions create --payload <file.json> [--json]",
4280
- " audienti motions update <motn_id> --status <draft|preparing|active|paused|archived> [--json]",
5465
+ " audienti motions update <motn_id> [--status <draft|preparing|active|paused|archived>] [--tags <tag[,tag...]>] [--json]",
5466
+ " audienti motions add-tag <motn_id> <tag> [--json]",
5467
+ " audienti motions remove-tag <motn_id> <tag> [--json]",
4281
5468
  " audienti motions activate <motn_id> [--json]",
4282
5469
  " audienti motions pause <motn_id> [--json]",
4283
5470
  " audienti motions archive <motn_id> [--json]",
@@ -4285,7 +5472,7 @@ const HELP_TOPICS = new Map([
4285
5472
  " audienti motions clone <motn_id> --name <text> [--json]",
4286
5473
  " audienti motions move-prospects <source_motn_id> --target <target_motn_id> <prsp_id> [prsp_id...] [--json]",
4287
5474
  "",
4288
- "Status: read, create, status update, delete, clone, status, and prospect attachment commands implemented",
5475
+ "Status: read, create, status update, delete, clone, status, discovery launch, and prospect attachment commands implemented",
4289
5476
  "",
4290
5477
  "CLI synonym:",
4291
5478
  " `plays` is accepted anywhere `motions` is accepted",
@@ -4296,10 +5483,13 @@ const HELP_TOPICS = new Map([
4296
5483
 
4297
5484
  ["motions list", [
4298
5485
  "Usage:",
4299
- " audienti motions list [--json] [--account <acct_id>]",
5486
+ " audienti motions list [--tag <tag>] [--json] [--account <acct_id>]",
4300
5487
  "",
4301
5488
  "Status: implemented",
4302
5489
  "",
5490
+ "Options:",
5491
+ " --tag <tag> Filter locally to motions whose play_tags include the normalized tag",
5492
+ "",
4303
5493
  "API:",
4304
5494
  " GET /api/v1/accounts/:account_id/motions.json",
4305
5495
  "",
@@ -4312,7 +5502,8 @@ const HELP_TOPICS = new Map([
4312
5502
  " offer.prefix_id: offr_",
4313
5503
  " icp.prefix_id: icpp_",
4314
5504
  " list.prefix_id: list_ | null",
4315
- " principal_account_user.id: integer"
5505
+ " principal_account_user.id: integer",
5506
+ " play_tags: [string]"
4316
5507
  ].join("\n")],
4317
5508
 
4318
5509
  ["motions show", [
@@ -4370,6 +5561,27 @@ const HELP_TOPICS = new Map([
4370
5561
  " GET /api/v1/accounts/:account_id/analytics/prospects.json?motion_id=:motion_id"
4371
5562
  ].join("\n")],
4372
5563
 
5564
+ ["motions run-discovery", [
5565
+ "Usage:",
5566
+ ` ${MOTIONS_RUN_DISCOVERY_USAGE.slice("Usage: ".length)}`,
5567
+ "",
5568
+ "Status: implemented",
5569
+ "",
5570
+ "Purpose:",
5571
+ " Queue an immediate discovery run for one discovery-capable motion or play.",
5572
+ "",
5573
+ "Options:",
5574
+ " --target-count <n> Override the manual replenishment target count for this launch.",
5575
+ "",
5576
+ "Output shape:",
5577
+ " enqueued: true when Motions::DiscoverJob was queued",
5578
+ " reason: launched | run_in_progress | lock_contention | target_met | enqueue_failed",
5579
+ " target_count: requested target count",
5580
+ "",
5581
+ "API:",
5582
+ " POST /api/v1/accounts/:account_id/motions/:id/run_discovery.json"
5583
+ ].join("\n")],
5584
+
4373
5585
  ["motions prospects", [
4374
5586
  "Usage:",
4375
5587
  " audienti motions prospects <motn_id> [--json] [--account <acct_id>]",
@@ -4440,7 +5652,8 @@ const HELP_TOPICS = new Map([
4440
5652
  " principal_account_user_id: integer | me | optional",
4441
5653
  " icp_id: icpp_ prefix id | optional",
4442
5654
  " list_id: list_ prefix id | optional",
4443
- " inbound_channels: [linkedin | reddit | x | tiktok | instagram | facebook] | optional",
5655
+ " play_tags: [string] | optional",
5656
+ " inbound_channels: [linkedin | reddit] | optional",
4444
5657
  " lopa_profiles: [{ url: string, source_type: creator | competitor | partner | customer | other }] | optional",
4445
5658
  "",
4446
5659
  "JSON example:",
@@ -4451,7 +5664,8 @@ const HELP_TOPICS = new Map([
4451
5664
  " \"status\": \"draft\",",
4452
5665
  " \"offer_id\": \"offr_abc123\",",
4453
5666
  " \"principal_account_user_id\": 42,",
4454
- " \"list_id\": \"list_abc123\"",
5667
+ " \"list_id\": \"list_abc123\",",
5668
+ " \"play_tags\": [\"sarit\", \"pj\"]",
4455
5669
  " }",
4456
5670
  "",
4457
5671
  "Behavior:",
@@ -4494,23 +5708,69 @@ const HELP_TOPICS = new Map([
4494
5708
  "Status: implemented",
4495
5709
  "",
4496
5710
  "Purpose:",
4497
- " Change one motion or play's lifecycle status.",
5711
+ " Change one motion or play's lifecycle status or replace its tag set.",
4498
5712
  "",
4499
5713
  "Input shape:",
4500
5714
  " motn_id: motn_ prefix id",
4501
- " status: draft | preparing | active | paused | archived",
5715
+ " status: draft | preparing | active | paused | archived | optional",
5716
+ " tags: comma-separated tag list | optional",
4502
5717
  "",
4503
5718
  "Behavior:",
4504
- " Updates only the motion status. Other motion configuration stays unchanged.",
5719
+ " Updates only the provided fields. Sending --tags replaces the motion's full tag set.",
4505
5720
  "",
4506
5721
  "API:",
4507
5722
  " PATCH /api/v1/accounts/:account_id/motions/:id.json",
4508
5723
  "",
4509
5724
  "JSON body:",
4510
5725
  " {",
4511
- " \"motion\": {",
4512
- " \"status\": \"paused\"",
4513
- " }",
5726
+ " \"motion\": {",
5727
+ " \"status\": \"paused\",",
5728
+ " \"play_tags\": [\"sarit\", \"pj\"]",
5729
+ " }",
5730
+ " }"
5731
+ ].join("\n")],
5732
+
5733
+ ["motions add-tag", [
5734
+ "Usage:",
5735
+ ` ${MOTIONS_ADD_TAG_USAGE.slice("Usage: ".length)}`,
5736
+ "",
5737
+ "Status: implemented",
5738
+ "",
5739
+ "Purpose:",
5740
+ " Add one normalized tag to a motion or play.",
5741
+ "",
5742
+ "Input shape:",
5743
+ " motn_id: motn_ prefix id",
5744
+ " tag: string",
5745
+ "",
5746
+ "API:",
5747
+ " POST /api/v1/accounts/:account_id/motions/:id/add_tag.json",
5748
+ "",
5749
+ "JSON body:",
5750
+ " {",
5751
+ " \"tag\": \"sarit\"",
5752
+ " }"
5753
+ ].join("\n")],
5754
+
5755
+ ["motions remove-tag", [
5756
+ "Usage:",
5757
+ ` ${MOTIONS_REMOVE_TAG_USAGE.slice("Usage: ".length)}`,
5758
+ "",
5759
+ "Status: implemented",
5760
+ "",
5761
+ "Purpose:",
5762
+ " Remove one normalized tag from a motion or play.",
5763
+ "",
5764
+ "Input shape:",
5765
+ " motn_id: motn_ prefix id",
5766
+ " tag: string",
5767
+ "",
5768
+ "API:",
5769
+ " DELETE /api/v1/accounts/:account_id/motions/:id/remove_tag.json",
5770
+ "",
5771
+ "JSON body:",
5772
+ " {",
5773
+ " \"tag\": \"sarit\"",
4514
5774
  " }"
4515
5775
  ].join("\n")],
4516
5776
 
@@ -4604,8 +5864,12 @@ const HELP_TOPICS = new Map([
4604
5864
  ["prospects", [
4605
5865
  "Usage:",
4606
5866
  " audienti prospects list [--json] [filters]",
5867
+ " audienti prospects check [--json|--csv] [filters]",
4607
5868
  " audienti prospects show <prsp_id> [--json]",
4608
5869
  " audienti prospects assign <prsp_id> [prsp_id...] --assigned-user <id|me|unassign> [--json]",
5870
+ " audienti prospects reject <prsp_id> [--json]",
5871
+ " audienti prospects nurture <prsp_id> [--reason <reason>] [--json]",
5872
+ " audienti prospects restore <prsp_id> [--json]",
4609
5873
  " audienti prospects timeline <prsp_id> [--json]",
4610
5874
  " audienti prospects message-types <prsp_id> [--json]",
4611
5875
  " audienti prospects write <prsp_id> --type <surface_key> [--json]",
@@ -4619,7 +5883,7 @@ const HELP_TOPICS = new Map([
4619
5883
  " audienti prospects import-batch --file <csv|jsonl|json> [--list <list_id>] [--motion <motn_id>] [--json]",
4620
5884
  " audienti prospects import-status <primp_id> [--json]",
4621
5885
  "",
4622
- "Status: read commands, assignment, per-prospect draft preview, sequence preview, and import implemented; disposition planned",
5886
+ "Status: read commands, assignment, disposition, per-prospect draft preview, sequence preview, and import implemented",
4623
5887
  "",
4624
5888
  "Filters:",
4625
5889
  " --query <text>",
@@ -4688,6 +5952,43 @@ const HELP_TOPICS = new Map([
4688
5952
  " audienti prospects list --all --csv"
4689
5953
  ].join("\n")],
4690
5954
 
5955
+ ["prospects check", [
5956
+ "Usage:",
5957
+ " audienti prospects check [--json|--csv] [filters] [--account <acct_id>]",
5958
+ "",
5959
+ "Status: implemented",
5960
+ "",
5961
+ "Description:",
5962
+ " Lists suspect people prospects that do not have a certified company employment citation.",
5963
+ " Imported or reported company text is shown for investigation but does not count as certification.",
5964
+ "",
5965
+ "Filters:",
5966
+ " --query <text> Search name, title, company, email, profile URL",
5967
+ " --motion <motn_id> Filter to a motion",
5968
+ " --play <motn_id> Filter to a play using the same motion relationship",
5969
+ " --list <list_id> Filter to a prospect list",
5970
+ " --stage <stage> Filter to a pipeline stage",
5971
+ " --assigned-user <id|me|unassigned> Filter by assigned account user",
5972
+ " --limit <n> Max rows for one page; with --all it caps total rows up to 1000",
5973
+ " --page <n> 1-based page number",
5974
+ " --offset <n> Row offset for manual pagination",
5975
+ " --all Fetch every matching suspect prospect up to 1000 rows",
5976
+ " --csv Export CSV with app_url for operator review",
5977
+ "",
5978
+ "Output shape:",
5979
+ " prospects[].company_certification.status: missing",
5980
+ " prospects[].company_certification.reason: missing_employment_citation",
5981
+ " prospects[].app_url: product URL for operator inspection",
5982
+ " meta.total_count: total matching suspect prospects",
5983
+ "",
5984
+ "API:",
5985
+ " GET /api/v1/accounts/:account_id/prospects.json?data_quality=missing_certified_company",
5986
+ "",
5987
+ "Examples:",
5988
+ " audienti prospects check --motion <motn_id> --all --csv",
5989
+ " audienti prospects check --assigned-user me --json"
5990
+ ].join("\n")],
5991
+
4691
5992
  ["prospects assign", [
4692
5993
  "Usage:",
4693
5994
  ` ${PROSPECTS_ASSIGN_USAGE.slice("Usage: ".length)}`,
@@ -4724,6 +6025,48 @@ const HELP_TOPICS = new Map([
4724
6025
  " GET /api/v1/accounts/:account_id/prospects/:id.json"
4725
6026
  ].join("\n")],
4726
6027
 
6028
+ ["prospects reject", [
6029
+ "Usage:",
6030
+ ` ${PROSPECTS_REJECT_USAGE.slice("Usage: ".length)}`,
6031
+ "",
6032
+ "Status: implemented",
6033
+ "",
6034
+ "Purpose:",
6035
+ " Reject one prospect through the shared disposition and account DNC path.",
6036
+ "",
6037
+ "API:",
6038
+ " POST /api/v1/accounts/:account_id/prospects/:id/reject.json"
6039
+ ].join("\n")],
6040
+
6041
+ ["prospects nurture", [
6042
+ "Usage:",
6043
+ ` ${PROSPECTS_NURTURE_USAGE.slice("Usage: ".length)}`,
6044
+ "",
6045
+ "Status: implemented",
6046
+ "",
6047
+ "Purpose:",
6048
+ " Move one prospect to an inactive nurture disposition through the shared disposition path.",
6049
+ "",
6050
+ "Input shape:",
6051
+ " reason: nurture | non_responsive | not_fit. Defaults to nurture.",
6052
+ "",
6053
+ "API:",
6054
+ " POST /api/v1/accounts/:account_id/prospects/:id/nurture.json"
6055
+ ].join("\n")],
6056
+
6057
+ ["prospects restore", [
6058
+ "Usage:",
6059
+ ` ${PROSPECTS_RESTORE_USAGE.slice("Usage: ".length)}`,
6060
+ "",
6061
+ "Status: implemented",
6062
+ "",
6063
+ "Purpose:",
6064
+ " Restore one rejected or inactive prospect through the shared disposition path.",
6065
+ "",
6066
+ "API:",
6067
+ " POST /api/v1/accounts/:account_id/prospects/:id/restore.json"
6068
+ ].join("\n")],
6069
+
4727
6070
  ["prospects timeline", [
4728
6071
  "Usage:",
4729
6072
  " audienti prospects timeline <prsp_id> [--json] [--types <post,comment,reaction>] [--limit <n>] [--account <acct_id>]",
@@ -5081,26 +6424,6 @@ const HELP_TOPICS = new Map([
5081
6424
  " GET /api/v1/accounts/:account_id/prospect_imports/:id.json"
5082
6425
  ].join("\n")],
5083
6426
 
5084
- ["prospects disposition", [
5085
- "Usage:",
5086
- " audienti prospects disposition <prsp_id> --payload <file.json> [--account <acct_id>]",
5087
- "",
5088
- "Status: planned",
5089
- "",
5090
- "Input shape:",
5091
- " action: defer | nurture | reject | restore",
5092
- " reason: string | optional",
5093
- " delay_until: ISO8601 datetime | optional",
5094
- " note: string | optional",
5095
- "",
5096
- "JSON example:",
5097
- " {",
5098
- " \"action\": \"nurture\",",
5099
- " \"reason\": \"not_a_fit\",",
5100
- " \"note\": \"Not a fit for this motion right now.\"",
5101
- " }"
5102
- ].join("\n")],
5103
-
5104
6427
  ["tools", [
5105
6428
  "Usage:",
5106
6429
  " audienti tools get <email|phone> --url <linkedin_url> [--json]",
@@ -5230,6 +6553,7 @@ const HELP_TOPICS = new Map([
5230
6553
  ["analytics", [
5231
6554
  "Usage:",
5232
6555
  " audienti analytics prospects [--window 24h] [--cohort-start YYYY-MM-DD --cohort-end YYYY-MM-DD] [--motion <motn_id>] [--user <account_user_id|email|name|me>] [--json]",
6556
+ " audienti analytics dashboard [--cohort-start YYYY-MM-DD --cohort-end YYYY-MM-DD] [--play-tag <tag>] [--motion <motn_id>] [--json]",
5233
6557
  " audienti analytics prospects cohort-analysis [--weeks <n>] [--window 24h] [--motion <motn_id>] [--user <account_user_id|email|name|me>] [--json]",
5234
6558
  " 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>] [--platform <linkedin|email|gmail>] [--json]",
5235
6559
  " audienti analytics visibility [--window 24h] [--user <account_user_id|email|name|me>] [--json]",
@@ -5243,13 +6567,42 @@ const HELP_TOPICS = new Map([
5243
6567
  " --start <YYYY-MM-DD> --end <YYYY-MM-DD> For user analytics, select the events.created_at activity range instead of --window.",
5244
6568
  " --cohort-start <YYYY-MM-DD> --cohort-end <YYYY-MM-DD> Select the AccountProspect.created_at cohort while --window or --start/--end selects the activity period.",
5245
6569
  " --motion <motn_id> For prospect and user analytics, filter AccountProspect.motion_id to one motion/play.",
6570
+ " --play-tag <tag> For dashboard analytics, filter to motions/lists tagged with a campaign tag.",
5246
6571
  " --provenance <source> Optional lower-level AccountProspect.intake_source filter.",
5247
6572
  " --platform <linkedin|email|gmail> For user analytics, filter events.platform. --channel is accepted as an alias.",
5248
6573
  " cohort-analysis loops over recent weekly AccountProspect.created_at cohorts and compares their current stages.",
5249
6574
  " --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.",
5250
6575
  "",
5251
6576
  "Output:",
5252
- " Account-scoped analytics for prospects, users, visibility engagement, and ContentOps publishing."
6577
+ " Account-scoped analytics for prospects, campaign dashboard counts, users, visibility engagement, and ContentOps publishing."
6578
+ ].join("\n")],
6579
+
6580
+ ["analytics dashboard", [
6581
+ "Usage:",
6582
+ ` ${ANALYTICS_DASHBOARD_USAGE.slice("Usage: ".length)}`,
6583
+ "",
6584
+ "Status: implemented",
6585
+ "",
6586
+ "Purpose:",
6587
+ " Return the dashboard outreach read model through the CLI, including distinct company target counts for a motion, tag, offer, ICP, or user-filtered cohort.",
6588
+ "",
6589
+ "Options:",
6590
+ " --cohort-start <YYYY-MM-DD> --cohort-end <YYYY-MM-DD> Select the AccountProspect.created_at cohort.",
6591
+ " --play-tag <tag> Filter to motions/lists tagged with a campaign tag. --tag is accepted as an alias.",
6592
+ " --motion <motn_id> Filter to one motion/play.",
6593
+ " --offer <offr_id> Filter to one offer.",
6594
+ " --icp <icp_id> Filter to one ICP.",
6595
+ " --user <account_user_id|email|name|me> Filter to prospects assigned to one account user.",
6596
+ "",
6597
+ "Output shape:",
6598
+ " cohort_size: people in the selected cohort",
6599
+ " cohort_company_target_count: distinct company targets in that cohort",
6600
+ " cohort_people_per_company_average: people per company target",
6601
+ " active_cohort_count and active_cohort_company_target_count: still-active campaign cohort counts",
6602
+ " pipeline_stage_counts[]: current stage distribution",
6603
+ "",
6604
+ "API:",
6605
+ " GET /api/v1/accounts/:account_id/analytics/dashboard.json"
5253
6606
  ].join("\n")],
5254
6607
 
5255
6608
  ["analytics prospects", [
@@ -5389,6 +6742,7 @@ const HELP_TOPICS = new Map([
5389
6742
  " audienti accounts list",
5390
6743
  " audienti accounts select <acct_id>",
5391
6744
  " audienti users list",
6745
+ " audienti users select me",
5392
6746
  " audienti offers list",
5393
6747
  " audienti icps list",
5394
6748
  "",
@@ -5400,6 +6754,7 @@ const HELP_TOPICS = new Map([
5400
6754
  " audienti motions pause <motn_id>",
5401
6755
  " audienti motions delete <motn_id> --confirm yes",
5402
6756
  " audienti motions status <motn_id>",
6757
+ " audienti motions run-discovery <motn_id>",
5403
6758
  "",
5404
6759
  "3. Add a new prospect from LinkedIn and poll enrichment",
5405
6760
  " audienti lists create --name \"Target list\"",
@@ -5421,6 +6776,9 @@ const HELP_TOPICS = new Map([
5421
6776
  " audienti prospects add-profile <prsp_id> --url prospect@example.com",
5422
6777
  " audienti prospects report-bad-profile <prsp_id> <prof_id>",
5423
6778
  " audienti prospects add-note <prsp_id> --type steer --message \"Meeting will not happen\" --engagement-type action.meeting.canceled",
6779
+ " audienti prospects reject <prsp_id>",
6780
+ " audienti prospects nurture <prsp_id>",
6781
+ " audienti prospects restore <prsp_id>",
5424
6782
  " audienti prospects sequence-preview <prsp_id>",
5425
6783
  " audienti writer test-run <prsp_id>",
5426
6784
  " audienti prospects sequence-export <prsp_id> --csv",
@@ -5438,6 +6796,7 @@ const HELP_TOPICS = new Map([
5438
6796
  "7. Inspect account analytics",
5439
6797
  " audienti users activity me --window 7d",
5440
6798
  " audienti analytics prospects --window 24h",
6799
+ " audienti analytics dashboard --play-tag wine_campaign",
5441
6800
  " audienti analytics users --user me --window 30d",
5442
6801
  " audienti analytics visibility --window 24h --user me",
5443
6802
  " audienti analytics content --window week",
@@ -5449,7 +6808,6 @@ const HELP_TOPICS = new Map([
5449
6808
  " Prefer prospects import for new LinkedIn people and add-prospects commands for records that already exist.",
5450
6809
  "",
5451
6810
  "Current gaps to plan around:",
5452
- " Prospect disposition still lacks a dedicated CLI mutation.",
5453
6811
  " Operator outcome writeback is implemented for prospect rows, not visibility rows."
5454
6812
  ].join("\n")]
5455
6813
  ]);