@audienti/cli 0.1.74 → 0.1.76

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
@@ -113,6 +113,25 @@ const COMPANY_RULES_UPDATE_USAGE = "Usage: audienti company-rules update <rule_i
113
113
  const COMPANY_RULES_REMOVE_USAGE = "Usage: audienti company-rules remove <rule_id> [--json] [--account <acct_id>]";
114
114
  const COMPANY_RULES_APPLY_USAGE = "Usage: audienti company-rules apply (<rule_id>|--all) [--json] [--account <acct_id>]";
115
115
  const COMPANY_RULES_SHOW_USAGE = "Usage: audienti company-rules show <rule_id> [--json] [--account <acct_id>]";
116
+ const HUBSPOT_SHOW_USAGE = "Usage: audienti hubspot show [--errors] [--limit <n>] [--json] [--account <acct_id>]";
117
+ const HUBSPOT_CONNECT_USAGE = "Usage: audienti hubspot connect (--token <private_app_token> | --token-stdin) [--json] [--account <acct_id>]";
118
+ const WEBHOOK_STATUS_VALUES = new Set(["enabled", "disabled"]);
119
+ const HUBSPOT_DISCONNECT_USAGE = "Usage: audienti hubspot disconnect [--json] [--account <acct_id>]";
120
+ const HUBSPOT_SYNC_USAGE = "Usage: audienti hubspot sync [--json] [--account <acct_id>]";
121
+ const HUBSPOT_RETRY_USAGE = "Usage: audienti hubspot retry <event_id> [--json] [--account <acct_id>]";
122
+ const HUBSPOT_LIST_SYNCS_CREATE_USAGE = "Usage: audienti hubspot list-syncs create --hubspot-list <hubspot_list_id> --signal <signal_id> --source-agent <agent_id> [--json] [--account <acct_id>]";
123
+ const HUBSPOT_LIST_SYNCS_UPDATE_USAGE = "Usage: audienti hubspot list-syncs update <list_sync_id> --signal <signal_id> --source-agent <agent_id> [--json] [--account <acct_id>]";
124
+ const HUBSPOT_LIST_SYNCS_REMOVE_USAGE = "Usage: audienti hubspot list-syncs remove <list_sync_id> [--json] [--account <acct_id>]";
125
+ const HUBSPOT_LIST_SYNCS_SYNC_USAGE = "Usage: audienti hubspot list-syncs sync <list_sync_id> [--json] [--account <acct_id>]";
126
+ const WEBHOOKS_LIST_USAGE = "Usage: audienti webhooks list [--json] [--account <acct_id>]";
127
+ const WEBHOOKS_CREATE_USAGE = "Usage: audienti webhooks create [--label <text>] [--signal <signal_id>] [--list <list_id>] [--json] [--account <acct_id>]";
128
+ const WEBHOOKS_UPDATE_USAGE = "Usage: audienti webhooks update <endpoint_id> [--label <text>] [--status <enabled|disabled>] [--signal <signal_id|none>] [--json] [--account <acct_id>]";
129
+ const WEBHOOKS_ROTATE_USAGE = "Usage: audienti webhooks rotate <endpoint_id> [--json] [--account <acct_id>]";
130
+ const WEBHOOKS_REMOVE_USAGE = "Usage: audienti webhooks remove <endpoint_id> [--json] [--account <acct_id>]";
131
+ const REPLY_ALERTS_SHOW_USAGE = "Usage: audienti reply-alerts show [--json]";
132
+ const REPLY_ALERTS_UPDATE_USAGE = "Usage: audienti reply-alerts update [--phone <number|none>] [--sms <true|false>] [--slack <true|false>] [--json]";
133
+ const BRAND_PROFILE_SHOW_USAGE = "Usage: audienti brand-profile show [--json] [--account <acct_id>]";
134
+ const BRAND_PROFILE_UPDATE_USAGE = "Usage: audienti brand-profile update [--voice <text>] [--style <text>] [--do-not-use <text>] [--json] [--account <acct_id>]";
116
135
  const ACCOUNTS_SHOW_USAGE = "Usage: audienti accounts show [<acct_id>] [--json] [--account <acct_id>]";
117
136
  const LINKEDIN_LOOKUPS_USAGE = "Usage: audienti linkedin-lookups <company-sizes|company-types|functions|industries|job-titles|locations|seniorities> [--query <text>] [--icp <icp_id>] [--json] [--account <acct_id>]";
118
137
  const LINKEDIN_LOOKUP_KINDS = new Map([
@@ -188,6 +207,21 @@ const TOOLS_LINKEDIN_REVIEW_USAGE = "Usage: audienti tools linkedin-review --url
188
207
  const TOOLS_LINKEDIN_REVIEW_REPORTS_USAGE = "Usage: audienti tools linkedin-review reports [--limit <n>] [--json] [--account <acct_id>]";
189
208
  const TOOLS_LINKEDIN_REVIEW_SHOW_USAGE = "Usage: audienti tools linkedin-review show <rprt_id> [--json] [--account <acct_id>]";
190
209
  const TOOLS_LINKEDIN_REVIEW_STATUS_USAGE = "Usage: audienti tools linkedin-review status <rprt_id> [--json] [--account <acct_id>]";
210
+ const TOOLS_LINKEDIN_STRATEGY_REVIEW_DELETE_USAGE = "Usage: audienti tools linkedin-strategy-review delete <rprt_id> --confirm <yes|true|Y|y> [--json] [--account <acct_id>]";
211
+ const LISTS_BULK_ADD_TAG_USAGE = "Usage: audienti lists bulk-add-tag --tag <tag> <list_id> [list_id...] [--json] [--account <acct_id>]";
212
+ const LISTS_MERGE_USAGE = "Usage: audienti lists merge <list_id> <list_id> [--json] [--account <acct_id>]";
213
+ const LISTS_EXPORT_USAGE = "Usage: audienti lists export <list_id> [--output <file.csv>] [--inactive-reason <reason>] [--json] [--account <acct_id>]";
214
+ const LIST_ROUTING_RULES_TOGGLE_USAGE = "Usage: audienti lists routing-rules <list_id> toggle <rule_id> [--json] [--account <acct_id>]";
215
+ const ICPS_BULK_ADD_TAG_USAGE = "Usage: audienti icps bulk-add-tag --tag <tag> <icp_id> [icp_id...] [--json] [--account <acct_id>]";
216
+ const ICPS_CLONE_USAGE = "Usage: audienti icps clone <icp_id> [--json] [--account <acct_id>]";
217
+ const ICPS_DELETE_USAGE = "Usage: audienti icps delete <icp_id> --confirm <yes|true|Y|y> [--json] [--account <acct_id>]";
218
+ const ICPS_PROSPECTS_USAGE = "Usage: audienti icps prospects <icp_id> [--query <text>] [--limit <n>] [--offset <n> | --page <n>] [--profiles] [--json] [--account <acct_id>]";
219
+ const OFFERS_REGENERATE_RESEARCH_USAGE = "Usage: audienti offers regenerate-research <offr_id> [--guidance <text>] [--json] [--account <acct_id>]";
220
+ const OFFERS_UPDATE_WRITEUP_USAGE = "Usage: audienti offers update-writeup <offr_id> --description <text> [--json] [--account <acct_id>]";
221
+ const OFFERS_ADD_ARTIFACTS_USAGE = "Usage: audienti offers add-artifacts <offr_id> <file> [file...] [--json] [--account <acct_id>]";
222
+ const OFFERS_REMOVE_ARTIFACT_USAGE = "Usage: audienti offers remove-artifact <offr_id> <artifact_id> [--json] [--account <acct_id>]";
223
+ const TASKS_UPDATE_USAGE = "Usage: audienti tasks update <ptsk_id> [--title <text>] [--due <time>] [--notes <text>] [--prospect <prsp_id|\"\">] [--list <list_id|\"\">] [--assigned-user <id|me>] [--json] [--account <acct_id>]";
224
+ const TASKS_BULK_UPDATE_USAGE = "Usage: audienti tasks bulk-update --action <complete|reassign> [--assigned-user <id|me>] <ptsk_id> [ptsk_id...] [--json] [--account <acct_id>]";
191
225
  const SOCIAL_COOKIES_SYNC_MESSAGES_USAGE = "Usage: audienti social-cookies sync-messages <scok_id> [--folder <folder>] [--retry] [--json] [--account <acct_id>]";
192
226
  const COHORT_STAGE_ORDER = [
193
227
  "identified",
@@ -290,6 +324,10 @@ async function dispatch(argv, context) {
290
324
  if (normalizedResource === "offers" && action === "create") return offersCreate(rest, context, { accountOverride });
291
325
  if (normalizedResource === "offers" && action === "update") return offersUpdate(rest, context, { accountOverride });
292
326
  if (normalizedResource === "offers" && action === "delete") return offersDelete(rest, context, { accountOverride });
327
+ if (normalizedResource === "offers" && action === "regenerate-research") return offersRegenerateResearch(rest, context, { accountOverride });
328
+ if (normalizedResource === "offers" && action === "update-writeup") return offersUpdateWriteup(rest, context, { accountOverride });
329
+ if (normalizedResource === "offers" && action === "add-artifacts") return offersAddArtifacts(rest, context, { accountOverride });
330
+ if (normalizedResource === "offers" && action === "remove-artifact") return offersRemoveArtifact(rest, context, { accountOverride });
293
331
  if (normalizedResource === "icps" && action === "list") return icpsList(rest, context, { accountOverride });
294
332
  if (normalizedResource === "icps" && action === "show") return icpsShow(rest, context, { accountOverride });
295
333
  if (normalizedResource === "icps" && action === "analytics") return analyticsIcps(rest, context, { accountOverride });
@@ -299,6 +337,10 @@ async function dispatch(argv, context) {
299
337
  if (normalizedResource === "icps" && action === "restore") return icpsLifecycleMutation("restore", rest, context, { accountOverride });
300
338
  if (normalizedResource === "icps" && action === "add-tag") return icpsTagMutation("add", rest, context, { accountOverride });
301
339
  if (normalizedResource === "icps" && action === "remove-tag") return icpsTagMutation("remove", rest, context, { accountOverride });
340
+ if (normalizedResource === "icps" && action === "bulk-add-tag") return icpsBulkAddTag(rest, context, { accountOverride });
341
+ if (normalizedResource === "icps" && action === "clone") return icpsClone(rest, context, { accountOverride });
342
+ if (normalizedResource === "icps" && action === "delete") return icpsDelete(rest, context, { accountOverride });
343
+ if (normalizedResource === "icps" && action === "prospects") return icpsProspects(rest, context, { accountOverride });
302
344
  if (normalizedResource === "companies" && action === "search") return companiesSearch(rest, context, { accountOverride });
303
345
  if (normalizedResource === "dnc" && action === "list") return dncList(rest, context, { accountOverride });
304
346
  if (normalizedResource === "dnc" && action === "add") return dncAdd(rest, context, { accountOverride });
@@ -310,11 +352,18 @@ async function dispatch(argv, context) {
310
352
  if (normalizedResource === "company-rules" && action === "update") return companyRulesUpdate(rest, context, { accountOverride });
311
353
  if (normalizedResource === "company-rules" && ["remove", "delete"].includes(action)) return companyRulesRemove(rest, context, { accountOverride });
312
354
  if (normalizedResource === "company-rules" && action === "apply") return companyRulesApply(rest, context, { accountOverride });
355
+ if (normalizedResource === "hubspot" && action === "list-syncs") return hubspotListSyncs(rest[0], rest.slice(1), context, { accountOverride });
356
+ if (normalizedResource === "hubspot") return hubspotCommand(action, rest, context, { accountOverride });
357
+ if (normalizedResource === "webhooks") return webhooksCommand(action, rest, context, { accountOverride });
358
+ if (normalizedResource === "reply-alerts") return replyAlertsCommand(action, rest, context);
359
+ if (normalizedResource === "brand-profile") return brandProfileCommand(action, rest, context, { accountOverride });
313
360
  if (normalizedResource === "tags" && action === "list") return tagsList(rest, context, { accountOverride });
314
361
  if (normalizedResource === "tags" && action === "show") return tagsShow(rest, context, { accountOverride });
315
362
  if (normalizedResource === "tasks" && ["list", "manage"].includes(action)) return tasksList(rest, context, { accountOverride });
316
363
  if (normalizedResource === "tasks" && action === "add") return tasksAdd(rest, context, { accountOverride });
317
364
  if (normalizedResource === "tasks" && action === "complete") return tasksComplete(rest, context, { accountOverride });
365
+ if (normalizedResource === "tasks" && action === "update") return tasksUpdate(rest, context, { accountOverride });
366
+ if (normalizedResource === "tasks" && action === "bulk-update") return tasksBulkUpdate(rest, context, { accountOverride });
318
367
  if (normalizedResource === "lists" && action === "list") return listsList(rest, context, { accountOverride });
319
368
  if (normalizedResource === "lists" && action === "create") return listsCreate(rest, context, { accountOverride });
320
369
  if (normalizedResource === "lists" && action === "show") return listsShow(rest, context, { accountOverride });
@@ -322,6 +371,9 @@ async function dispatch(argv, context) {
322
371
  if (normalizedResource === "lists" && action === "add-tag") return listsTagMutation("add", rest, context, { accountOverride });
323
372
  if (normalizedResource === "lists" && action === "remove-tag") return listsTagMutation("remove", rest, context, { accountOverride });
324
373
  if (normalizedResource === "lists" && action === "delete") return listsDelete(rest, context, { accountOverride });
374
+ if (normalizedResource === "lists" && action === "bulk-add-tag") return listsBulkAddTag(rest, context, { accountOverride });
375
+ if (normalizedResource === "lists" && action === "merge") return listsMerge(rest, context, { accountOverride });
376
+ if (normalizedResource === "lists" && action === "export") return listsExport(rest, context, { accountOverride });
325
377
  if (normalizedResource === "lists" && action === "prospects") return listProspects(rest, context, { accountOverride });
326
378
  if (normalizedResource === "lists" && action === "add-prospects") return listsAddProspects(rest, context, { accountOverride });
327
379
  if (normalizedResource === "lists" && action === "remove-prospects") return listsRemoveProspects(rest, context, { accountOverride });
@@ -385,6 +437,7 @@ async function dispatch(argv, context) {
385
437
  if (normalizedResource === "tools" && action === "get") return toolsGet(rest, context, { accountOverride });
386
438
  if (normalizedResource === "tools" && action === "humanize") return toolsHumanize(rest, context, { accountOverride });
387
439
  if (normalizedResource === "tools" && action === "linkedin-review") return toolsLinkedinReview(rest, context, { accountOverride });
440
+ if (normalizedResource === "tools" && action === "linkedin-strategy-review") return toolsLinkedinStrategyReview(rest, context, { accountOverride });
388
441
  if (normalizedResource === "operator" && action === "failed-drafts") return operatorFailedDrafts(rest, context, { accountOverride });
389
442
  if (normalizedResource === "operator" && action === "queue") return operatorQueue(rest, context, { accountOverride });
390
443
  if (normalizedResource === "operator" && action === "next") return operatorNext(rest, context, { accountOverride });
@@ -497,6 +550,10 @@ function normalizeResource(resource) {
497
550
  if (resource === "principals") return "users";
498
551
  if (resource === "writers") return "writer";
499
552
  if (resource === "company_rules" || resource === "company-rules") return "company-rules";
553
+ if (resource === "hubspot_integration" || resource === "hubspot-integration") return "hubspot";
554
+ if (resource === "prospect_webhook_endpoints" || resource === "prospect-webhook-endpoints") return "webhooks";
555
+ if (resource === "reply_alerts") return "reply-alerts";
556
+ if (resource === "brand_profile") return "brand-profile";
500
557
  if (resource === "linkedin_lookups" || resource === "linkedin-lookups") return "linkedin-lookups";
501
558
  if (resource === "social_cookie" || resource === "social_cookies") return "social-cookies";
502
559
  return resource === "plays" ? "motions" : resource;
@@ -1222,6 +1279,65 @@ async function offersDelete(args, context, { accountOverride } = {}) {
1222
1279
  writeLine(context.stdout, `Deleted offer ${display(payload?.name)} (${display(payload?.prefix_id)}).`);
1223
1280
  }
1224
1281
 
1282
+ async function offersRegenerateResearch(args, context, { accountOverride } = {}) {
1283
+ const { values, positionals } = parseCommandArgs(args, { ...jsonOptions(), guidance: { type: "string" } });
1284
+ if (positionals.length !== 1) throw new CommandError(OFFERS_REGENERATE_RESEARCH_USAGE);
1285
+
1286
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1287
+ const offer = await client.regenerateOfferResearch(accountId, positionals[0], compactObject({ guidance: values.guidance }));
1288
+ if (values.json) return writeJson(context.stdout, offer);
1289
+
1290
+ writeLine(context.stdout, `Queued a new research write-up for offer ${display(offer?.name)} (${display(offer?.prefix_id)}).`);
1291
+ }
1292
+
1293
+ async function offersUpdateWriteup(args, context, { accountOverride } = {}) {
1294
+ const { values, positionals } = parseCommandArgs(args, { ...jsonOptions(), description: { type: "string" } });
1295
+ if (positionals.length !== 1 || values.description === undefined) throw new CommandError(OFFERS_UPDATE_WRITEUP_USAGE);
1296
+
1297
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1298
+ const offer = await client.updateOfferWriteup(accountId, positionals[0], { offer: { description: values.description } });
1299
+ if (values.json) return writeJson(context.stdout, offer);
1300
+
1301
+ writeLine(context.stdout, `Updated the write-up for offer ${display(offer?.name)} (${display(offer?.prefix_id)}).`);
1302
+ }
1303
+
1304
+ async function offersAddArtifacts(args, context, { accountOverride } = {}) {
1305
+ const { values, positionals } = parseCommandArgs(args, jsonOptions());
1306
+ if (positionals.length < 2) throw new CommandError(OFFERS_ADD_ARTIFACTS_USAGE);
1307
+
1308
+ const [offerId, ...paths] = positionals;
1309
+ const artifacts = [];
1310
+ for (const path of paths) {
1311
+ let data;
1312
+ try {
1313
+ data = await readFile(path);
1314
+ } catch (error) {
1315
+ throw new CommandError(`Cannot read ${path}: ${error.message}`);
1316
+ }
1317
+ artifacts.push({ filename: basename(path), data: data.toString("base64") });
1318
+ }
1319
+
1320
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1321
+ const payload = await client.addOfferArtifacts(accountId, offerId, { artifacts });
1322
+ if (values.json) return writeJson(context.stdout, payload);
1323
+
1324
+ const count = payload?.attached_count ?? artifacts.length;
1325
+ writeLine(context.stdout, `Attached ${count} ${count === 1 ? "artifact" : "artifacts"} to offer ${display(payload?.offer_id || offerId)}.`);
1326
+ renderOfferArtifacts(payload?.artifacts, context);
1327
+ }
1328
+
1329
+ async function offersRemoveArtifact(args, context, { accountOverride } = {}) {
1330
+ const { values, positionals } = parseCommandArgs(args, jsonOptions());
1331
+ if (positionals.length !== 2) throw new CommandError(OFFERS_REMOVE_ARTIFACT_USAGE);
1332
+
1333
+ const [offerId, artifactId] = positionals;
1334
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1335
+ const payload = await client.removeOfferArtifact(accountId, offerId, artifactId);
1336
+ if (values.json) return writeJson(context.stdout, payload);
1337
+
1338
+ writeLine(context.stdout, `Removed artifact ${display(artifactId)} from offer ${display(payload?.offer_id || offerId)}.`);
1339
+ }
1340
+
1225
1341
  async function icpsList(args, context, { accountOverride } = {}) {
1226
1342
  const { values, positionals } = parseCommandArgs(args, {
1227
1343
  ...jsonOptions(),
@@ -1331,6 +1447,68 @@ async function icpsTagMutation(action, args, context, { accountOverride } = {})
1331
1447
  if (Array.isArray(icp?.tags)) writeLine(context.stdout, `Tags: ${display(icp.tags.join(", "), "-")}`);
1332
1448
  }
1333
1449
 
1450
+ async function icpsBulkAddTag(args, context, { accountOverride } = {}) {
1451
+ const { values, positionals } = parseCommandArgs(args, { ...jsonOptions(), tag: { type: "string" } });
1452
+ if (positionals.length === 0 || !String(values.tag || "").trim()) throw new CommandError(ICPS_BULK_ADD_TAG_USAGE);
1453
+
1454
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1455
+ const icps = await client.bulkAddIcpTag(accountId, { icp_ids: positionals, tag: values.tag });
1456
+ if (values.json) return writeJson(context.stdout, icps);
1457
+
1458
+ const count = Array.isArray(icps) ? icps.length : 0;
1459
+ writeLine(context.stdout, `Added tag ${display(values.tag)} to ${count} ${count === 1 ? "ICP" : "ICPs"}.`);
1460
+ }
1461
+
1462
+ async function icpsClone(args, context, { accountOverride } = {}) {
1463
+ const { values, positionals } = parseCommandArgs(args, jsonOptions());
1464
+ if (positionals.length !== 1) throw new CommandError(ICPS_CLONE_USAGE);
1465
+
1466
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1467
+ const icp = await client.cloneIcp(accountId, positionals[0]);
1468
+ if (values.json) return writeJson(context.stdout, icp);
1469
+
1470
+ writeLine(context.stdout, `Cloned ICP as ${display(icp?.name)} (${display(icp?.prefix_id)}).`);
1471
+ }
1472
+
1473
+ async function icpsDelete(args, context, { accountOverride } = {}) {
1474
+ const { values, positionals } = parseCommandArgs(args, { ...jsonOptions(), confirm: { type: "string" } });
1475
+ const normalizedConfirm = String(values.confirm || "").trim().toLowerCase();
1476
+ if (positionals.length !== 1 || !DELETE_CONFIRMATION_VALUES.has(normalizedConfirm)) throw new CommandError(ICPS_DELETE_USAGE);
1477
+
1478
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1479
+ const payload = await client.deleteIcp(accountId, positionals[0]);
1480
+ if (values.json) return writeJson(context.stdout, payload);
1481
+
1482
+ writeLine(context.stdout, `Deleted ICP ${display(payload?.name)} (${display(payload?.prefix_id)}).`);
1483
+ }
1484
+
1485
+ async function icpsProspects(args, context, { accountOverride } = {}) {
1486
+ const { values, positionals } = parseCommandArgs(args, {
1487
+ ...jsonOptions(),
1488
+ query: { type: "string" },
1489
+ limit: { type: "string" },
1490
+ offset: { type: "string" },
1491
+ page: { type: "string" },
1492
+ profiles: { type: "boolean" }
1493
+ });
1494
+ if (positionals.length !== 1) throw new CommandError(ICPS_PROSPECTS_USAGE);
1495
+ if (values.page && values.offset) throw new CommandError("Choose one pagination mode: use either --page or --offset.");
1496
+
1497
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1498
+ const payload = await client.icpProspects(accountId, positionals[0], compactObject({
1499
+ query: values.query,
1500
+ limit: values.limit,
1501
+ offset: values.offset,
1502
+ page: values.page,
1503
+ include_profiles: values.profiles
1504
+ }));
1505
+ if (values.json) return writeJson(context.stdout, payload);
1506
+
1507
+ renderProspects(payload, context, { profiles: values.profiles });
1508
+ const meta = payload?.meta || {};
1509
+ writeLine(context.stdout, `Showing ${display(meta.returned_count, 0)} of ${display(meta.total_count, 0)} matched prospects.`);
1510
+ }
1511
+
1334
1512
  async function companiesSearch(args, context, { accountOverride } = {}) {
1335
1513
  const { values, positionals } = parseCommandArgs(args, {
1336
1514
  ...jsonOptions(),
@@ -1491,6 +1669,377 @@ async function companyRulesApply(args, context, { accountOverride } = {}) {
1491
1669
  }
1492
1670
  }
1493
1671
 
1672
+ async function hubspotCommand(action, args, context, { accountOverride } = {}) {
1673
+ if (action === "show") return hubspotShow(args, context, { accountOverride });
1674
+ if (action === "connect") return hubspotConnect(args, context, { accountOverride });
1675
+ if (action === "disconnect") return hubspotDisconnect(args, context, { accountOverride });
1676
+ if (action === "sync") return hubspotSync(args, context, { accountOverride });
1677
+ if (action === "retry") return hubspotRetry(args, context, { accountOverride });
1678
+
1679
+ throw new CommandError(`Unknown hubspot command "${display(action, "")}". Run \`audienti hubspot --help\`.`);
1680
+ }
1681
+
1682
+ async function hubspotShow(args, context, { accountOverride } = {}) {
1683
+ const { values, positionals } = parseCommandArgs(args, {
1684
+ ...jsonOptions(),
1685
+ errors: { type: "boolean" },
1686
+ limit: { type: "string" }
1687
+ });
1688
+ if (positionals.length > 0) throw new CommandError(HUBSPOT_SHOW_USAGE);
1689
+ const limit = normalizeOptionalPositiveInteger(values.limit, "--limit");
1690
+
1691
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1692
+ const payload = await client.hubspotIntegration(accountId, {
1693
+ log_status: values.errors ? "error" : undefined,
1694
+ limit
1695
+ });
1696
+ if (values.json) return writeJson(context.stdout, payload);
1697
+
1698
+ const integration = payload?.hubspot_integration;
1699
+ if (!integration) {
1700
+ writeLine(context.stdout, "HubSpot: not connected.");
1701
+ return;
1702
+ }
1703
+
1704
+ writeLine(context.stdout, `HubSpot: ${display(integration.status)} | Auto sync: ${integration.auto_sync_enabled ? "on" : "off"}`);
1705
+ writeLine(context.stdout, `Portal: ${display(integration.external_account_name, "-")} (${display(integration.external_account_id, "-")})`);
1706
+ writeLine(context.stdout, `Last synced: ${display(integration.last_synced_at, "never")}`);
1707
+ if (integration.last_error_message) writeLine(context.stdout, `Last error: ${integration.last_error_message}`);
1708
+
1709
+ const listSyncs = payload?.list_syncs || [];
1710
+ writeLine(context.stdout, `List syncs: ${listSyncs.length}`);
1711
+ for (const listSync of listSyncs) {
1712
+ writeLine(context.stdout, ` ${display(listSync.id)} ${display(listSync.source_list_name || listSync.source_list_id)} ${display(listSync.display_status || listSync.status)} signal: ${display(listSync.custom_signal?.name, "-")}`);
1713
+ }
1714
+
1715
+ const events = payload?.recent_events || [];
1716
+ writeLine(context.stdout, `Recent events: ${events.length}`);
1717
+ for (const event of events) {
1718
+ const retry = event.retryable ? " (retryable)" : "";
1719
+ writeLine(context.stdout, ` ${display(event.id)} ${display(event.status)} ${display(event.event_type)} ${display(event.message, "")}${retry}`);
1720
+ }
1721
+ }
1722
+
1723
+ async function hubspotConnect(args, context, { accountOverride } = {}) {
1724
+ const { values, positionals } = parseCommandArgs(args, {
1725
+ ...jsonOptions(),
1726
+ token: { type: "string" },
1727
+ "token-stdin": { type: "boolean" }
1728
+ });
1729
+ const useStdin = Boolean(values["token-stdin"]);
1730
+ if (positionals.length > 0 || useStdin === (values.token !== undefined)) throw new CommandError(HUBSPOT_CONNECT_USAGE);
1731
+
1732
+ const token = (useStdin ? await readStdinText(context) : String(values.token)).trim();
1733
+ if (!token) throw new CommandError(HUBSPOT_CONNECT_USAGE);
1734
+
1735
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1736
+ const payload = await client.connectHubspot(accountId, { personal_access_token: token });
1737
+ if (values.json) return writeJson(context.stdout, payload);
1738
+
1739
+ const integration = payload?.hubspot_integration;
1740
+ writeLine(context.stdout, `Connected HubSpot portal ${display(integration?.external_account_name || integration?.external_account_id)}.`);
1741
+ if (payload?.warning) writeLine(context.stdout, `Warning: ${payload.warning}`);
1742
+ }
1743
+
1744
+ async function hubspotDisconnect(args, context, { accountOverride } = {}) {
1745
+ const { values, positionals } = parseCommandArgs(args, jsonOptions());
1746
+ if (positionals.length > 0) throw new CommandError(HUBSPOT_DISCONNECT_USAGE);
1747
+
1748
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1749
+ const payload = await client.disconnectHubspot(accountId);
1750
+ if (values.json) return writeJson(context.stdout, payload);
1751
+
1752
+ writeLine(context.stdout, "Disconnected HubSpot.");
1753
+ }
1754
+
1755
+ async function hubspotSync(args, context, { accountOverride } = {}) {
1756
+ const { values, positionals } = parseCommandArgs(args, jsonOptions());
1757
+ if (positionals.length > 0) throw new CommandError(HUBSPOT_SYNC_USAGE);
1758
+
1759
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1760
+ const payload = await client.syncHubspot(accountId);
1761
+ if (values.json) return writeJson(context.stdout, payload);
1762
+
1763
+ writeLine(context.stdout, display(payload?.message, "HubSpot sync all queued."));
1764
+ }
1765
+
1766
+ async function hubspotRetry(args, context, { accountOverride } = {}) {
1767
+ const { values, positionals } = parseCommandArgs(args, jsonOptions());
1768
+ if (positionals.length !== 1) throw new CommandError(HUBSPOT_RETRY_USAGE);
1769
+
1770
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1771
+ const payload = await client.retryHubspotEvent(accountId, { event_id: positionals[0] });
1772
+ if (values.json) return writeJson(context.stdout, payload);
1773
+
1774
+ writeLine(context.stdout, display(payload?.message, "HubSpot retry queued."));
1775
+ }
1776
+
1777
+ async function hubspotListSyncs(action, args, context, { accountOverride } = {}) {
1778
+ const options = {
1779
+ ...jsonOptions(),
1780
+ "hubspot-list": { type: "string" },
1781
+ signal: { type: "string" },
1782
+ "source-agent": { type: "string" }
1783
+ };
1784
+
1785
+ if (action === "create") {
1786
+ const { values, positionals } = parseCommandArgs(args, options);
1787
+ if (positionals.length > 0 || !values["hubspot-list"] || !values.signal || !values["source-agent"]) {
1788
+ throw new CommandError(HUBSPOT_LIST_SYNCS_CREATE_USAGE);
1789
+ }
1790
+
1791
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1792
+ const payload = await client.createHubspotListSync(accountId, {
1793
+ source_list_id: values["hubspot-list"],
1794
+ custom_signal_id: values.signal,
1795
+ source_agent_id: values["source-agent"]
1796
+ });
1797
+ if (values.json) return writeJson(context.stdout, payload);
1798
+
1799
+ const listSync = payload?.list_sync;
1800
+ writeLine(context.stdout, `Created HubSpot list sync ${display(listSync?.id)} for ${display(listSync?.source_list_name || listSync?.source_list_id)}.`);
1801
+ return;
1802
+ }
1803
+
1804
+ if (action === "update") {
1805
+ const { values, positionals } = parseCommandArgs(args, options);
1806
+ if (positionals.length !== 1 || !values.signal || !values["source-agent"]) throw new CommandError(HUBSPOT_LIST_SYNCS_UPDATE_USAGE);
1807
+
1808
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1809
+ const payload = await client.updateHubspotListSync(accountId, positionals[0], {
1810
+ custom_signal_id: values.signal,
1811
+ source_agent_id: values["source-agent"]
1812
+ });
1813
+ if (values.json) return writeJson(context.stdout, payload);
1814
+
1815
+ writeLine(context.stdout, `Updated HubSpot list sync ${display(payload?.list_sync?.id || positionals[0])}. Updated prospects: ${display(payload?.updated_count, 0)}.`);
1816
+ return;
1817
+ }
1818
+
1819
+ if (action === "remove" || action === "delete") {
1820
+ const { values, positionals } = parseCommandArgs(args, jsonOptions());
1821
+ if (positionals.length !== 1) throw new CommandError(HUBSPOT_LIST_SYNCS_REMOVE_USAGE);
1822
+
1823
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1824
+ const payload = await client.deleteHubspotListSync(accountId, positionals[0]);
1825
+ if (values.json) return writeJson(context.stdout, payload);
1826
+
1827
+ writeLine(context.stdout, `Removed HubSpot list sync ${display(payload?.id || positionals[0])}.`);
1828
+ return;
1829
+ }
1830
+
1831
+ if (action === "sync") {
1832
+ const { values, positionals } = parseCommandArgs(args, jsonOptions());
1833
+ if (positionals.length !== 1) throw new CommandError(HUBSPOT_LIST_SYNCS_SYNC_USAGE);
1834
+
1835
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1836
+ const payload = await client.syncHubspotListSync(accountId, positionals[0]);
1837
+ if (values.json) return writeJson(context.stdout, payload);
1838
+
1839
+ writeLine(context.stdout, display(payload?.message, "HubSpot list sync queued."));
1840
+ return;
1841
+ }
1842
+
1843
+ throw new CommandError(`Unknown hubspot list-syncs command "${display(action, "")}". Run \`audienti hubspot list-syncs --help\`.`);
1844
+ }
1845
+
1846
+ async function webhooksCommand(action, args, context, { accountOverride } = {}) {
1847
+ const options = {
1848
+ ...jsonOptions(),
1849
+ label: { type: "string" },
1850
+ signal: { type: "string" },
1851
+ list: { type: "string" },
1852
+ status: { type: "string" }
1853
+ };
1854
+
1855
+ if (action === "list") {
1856
+ const { values, positionals } = parseCommandArgs(args, jsonOptions());
1857
+ if (positionals.length > 0) throw new CommandError(WEBHOOKS_LIST_USAGE);
1858
+
1859
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1860
+ const payload = await client.prospectWebhookEndpoints(accountId);
1861
+ if (values.json) return writeJson(context.stdout, payload);
1862
+
1863
+ const endpoints = payload?.prospect_webhook_endpoints || [];
1864
+ if (endpoints.length === 0) {
1865
+ writeLine(context.stdout, "No prospect webhook endpoints.");
1866
+ return;
1867
+ }
1868
+ for (const endpoint of endpoints) {
1869
+ writeLine(context.stdout, `${display(endpoint.id)} ${display(endpoint.display_label || endpoint.label)} ${display(endpoint.status)} ${display(endpoint.url)}`);
1870
+ }
1871
+ return;
1872
+ }
1873
+
1874
+ if (action === "create") {
1875
+ const { values, positionals } = parseCommandArgs(args, options);
1876
+ if (positionals.length > 0 || values.status !== undefined) throw new CommandError(WEBHOOKS_CREATE_USAGE);
1877
+
1878
+ const body = {};
1879
+ if (values.label !== undefined) body.label = values.label;
1880
+ if (values.signal !== undefined) body.custom_signal_id = values.signal;
1881
+ if (values.list !== undefined) body.list_id = values.list;
1882
+
1883
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1884
+ const payload = await client.createProspectWebhookEndpoint(accountId, body);
1885
+ if (values.json) return writeJson(context.stdout, payload);
1886
+
1887
+ writeWebhookEndpoint("Created", payload?.prospect_webhook_endpoint, context);
1888
+ return;
1889
+ }
1890
+
1891
+ if (action === "update") {
1892
+ const { values, positionals } = parseCommandArgs(args, options);
1893
+ const body = {};
1894
+ if (values.label !== undefined) body.label = values.label;
1895
+ if (values.status !== undefined) {
1896
+ if (!WEBHOOK_STATUS_VALUES.has(values.status)) throw new CommandError("--status must be enabled or disabled.");
1897
+ body.status = values.status;
1898
+ }
1899
+ if (values.signal !== undefined) body.custom_signal_id = values.signal === "none" ? "" : values.signal;
1900
+ if (positionals.length !== 1 || values.list !== undefined || Object.keys(body).length === 0) {
1901
+ throw new CommandError(WEBHOOKS_UPDATE_USAGE);
1902
+ }
1903
+
1904
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1905
+ const payload = await client.updateProspectWebhookEndpoint(accountId, positionals[0], body);
1906
+ if (values.json) return writeJson(context.stdout, payload);
1907
+
1908
+ writeWebhookEndpoint("Updated", payload?.prospect_webhook_endpoint, context);
1909
+ return;
1910
+ }
1911
+
1912
+ if (action === "rotate") {
1913
+ const { values, positionals } = parseCommandArgs(args, jsonOptions());
1914
+ if (positionals.length !== 1) throw new CommandError(WEBHOOKS_ROTATE_USAGE);
1915
+
1916
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1917
+ const payload = await client.rotateProspectWebhookEndpoint(accountId, positionals[0]);
1918
+ if (values.json) return writeJson(context.stdout, payload);
1919
+
1920
+ writeWebhookEndpoint("Rotated", payload?.prospect_webhook_endpoint, context);
1921
+ return;
1922
+ }
1923
+
1924
+ if (action === "remove" || action === "delete") {
1925
+ const { values, positionals } = parseCommandArgs(args, jsonOptions());
1926
+ if (positionals.length !== 1) throw new CommandError(WEBHOOKS_REMOVE_USAGE);
1927
+
1928
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1929
+ const payload = await client.deleteProspectWebhookEndpoint(accountId, positionals[0]);
1930
+ if (values.json) return writeJson(context.stdout, payload);
1931
+
1932
+ writeLine(context.stdout, `Removed prospect webhook endpoint ${display(payload?.id || positionals[0])}.`);
1933
+ return;
1934
+ }
1935
+
1936
+ throw new CommandError(`Unknown webhooks command "${display(action, "")}". Run \`audienti webhooks --help\`.`);
1937
+ }
1938
+
1939
+ async function readStdinText(context) {
1940
+ const input = context.stdin;
1941
+ if (!input) return "";
1942
+
1943
+ let text = "";
1944
+ for await (const chunk of input) text += String(chunk);
1945
+ return text;
1946
+ }
1947
+
1948
+ function writeWebhookEndpoint(verb, endpoint, context) {
1949
+ writeLine(context.stdout, `${verb} prospect webhook endpoint ${display(endpoint?.display_label || endpoint?.label)} (${display(endpoint?.id)}).`);
1950
+ writeLine(context.stdout, `Status: ${display(endpoint?.status)} | Signal: ${display(endpoint?.custom_signal?.name, "-")} | List: ${display(endpoint?.list?.name, "-")}`);
1951
+ writeLine(context.stdout, `URL: ${display(endpoint?.url)}`);
1952
+ }
1953
+
1954
+ async function replyAlertsCommand(action, args, context) {
1955
+ if (action === "show") {
1956
+ const { values, positionals } = parseCommandArgs(args, jsonOptions());
1957
+ if (positionals.length > 0) throw new CommandError(REPLY_ALERTS_SHOW_USAGE);
1958
+
1959
+ const config = await requireAuthenticatedConfig(context);
1960
+ const client = clientFromConfig(config, context);
1961
+ const payload = await client.replyAlerts();
1962
+ if (values.json) return writeJson(context.stdout, payload);
1963
+
1964
+ writeReplyAlerts(payload?.reply_alerts, context);
1965
+ return;
1966
+ }
1967
+
1968
+ if (action === "update") {
1969
+ const { values, positionals } = parseCommandArgs(args, {
1970
+ ...jsonOptions(),
1971
+ phone: { type: "string" },
1972
+ sms: { type: "string" },
1973
+ slack: { type: "string" }
1974
+ });
1975
+ const body = {};
1976
+ if (values.phone !== undefined) body.phone_number = values.phone === "none" ? "" : values.phone;
1977
+ if (values.sms !== undefined) body.sms_enabled = parseBooleanString(values.sms, "--sms");
1978
+ if (values.slack !== undefined) body.slack_enabled = parseBooleanString(values.slack, "--slack");
1979
+ if (positionals.length > 0 || Object.keys(body).length === 0) throw new CommandError(REPLY_ALERTS_UPDATE_USAGE);
1980
+
1981
+ const config = await requireAuthenticatedConfig(context);
1982
+ const client = clientFromConfig(config, context);
1983
+ const payload = await client.updateReplyAlerts(body);
1984
+ if (values.json) return writeJson(context.stdout, payload);
1985
+
1986
+ writeLine(context.stdout, "Updated reply alerts.");
1987
+ writeReplyAlerts(payload?.reply_alerts, context);
1988
+ return;
1989
+ }
1990
+
1991
+ throw new CommandError(`Unknown reply-alerts command "${display(action, "")}". Run \`audienti reply-alerts --help\`.`);
1992
+ }
1993
+
1994
+ function writeReplyAlerts(alerts, context) {
1995
+ writeLine(context.stdout, `SMS: ${alerts?.sms_enabled ? "on" : "off"} | Phone: ${display(alerts?.phone_number, "-")} | Active: ${alerts?.sms_active ? "yes" : "no"}`);
1996
+ writeLine(context.stdout, `Slack: ${alerts?.slack_enabled ? "on" : "off"} | Connected: ${alerts?.slack_connected ? "yes" : "no"} | Channel: ${display(alerts?.slack_channel, "-")}`);
1997
+ }
1998
+
1999
+ async function brandProfileCommand(action, args, context, { accountOverride } = {}) {
2000
+ if (action === "show") {
2001
+ const { values, positionals } = parseCommandArgs(args, jsonOptions());
2002
+ if (positionals.length > 0) throw new CommandError(BRAND_PROFILE_SHOW_USAGE);
2003
+
2004
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
2005
+ const payload = await client.brandProfile(accountId);
2006
+ if (values.json) return writeJson(context.stdout, payload);
2007
+
2008
+ writeBrandProfile(payload?.brand_profile, context);
2009
+ return;
2010
+ }
2011
+
2012
+ if (action === "update") {
2013
+ const { values, positionals } = parseCommandArgs(args, {
2014
+ ...jsonOptions(),
2015
+ voice: { type: "string" },
2016
+ style: { type: "string" },
2017
+ "do-not-use": { type: "string" }
2018
+ });
2019
+ const body = {};
2020
+ if (values.voice !== undefined) body.voice = values.voice;
2021
+ if (values.style !== undefined) body.style = values.style;
2022
+ if (values["do-not-use"] !== undefined) body.do_not_use = values["do-not-use"];
2023
+ if (positionals.length > 0 || Object.keys(body).length === 0) throw new CommandError(BRAND_PROFILE_UPDATE_USAGE);
2024
+
2025
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
2026
+ const payload = await client.updateBrandProfile(accountId, body);
2027
+ if (values.json) return writeJson(context.stdout, payload);
2028
+
2029
+ writeLine(context.stdout, "Updated brand profile.");
2030
+ writeBrandProfile(payload?.brand_profile, context);
2031
+ return;
2032
+ }
2033
+
2034
+ throw new CommandError(`Unknown brand-profile command "${display(action, "")}". Run \`audienti brand-profile --help\`.`);
2035
+ }
2036
+
2037
+ function writeBrandProfile(profile, context) {
2038
+ writeLine(context.stdout, `Voice: ${display(profile?.voice, "-")}`);
2039
+ writeLine(context.stdout, `Style: ${display(profile?.style, "-")}`);
2040
+ writeLine(context.stdout, `Do not use: ${display(profile?.do_not_use, "-")}`);
2041
+ }
2042
+
1494
2043
  async function tagsList(args, context, { accountOverride } = {}) {
1495
2044
  const { values, positionals } = parseCommandArgs(args, jsonOptions());
1496
2045
  if (positionals.length > 0) throw new CommandError("Usage: audienti tags list [--json] [--account <acct_id>]");
@@ -1567,7 +2116,57 @@ async function tasksComplete(args, context, { accountOverride } = {}) {
1567
2116
  const payload = await client.completeTask(accountId, positionals[0]);
1568
2117
  if (values.json) return writeJson(context.stdout, payload);
1569
2118
 
1570
- writeLine(context.stdout, `Completed task ${display(payload?.prefix_id || payload?.id || positionals[0])}.`);
2119
+ writeLine(context.stdout, `Completed task ${display(payload?.prefix_id || payload?.id || positionals[0])}.`);
2120
+ }
2121
+
2122
+ async function tasksUpdate(args, context, { accountOverride } = {}) {
2123
+ const { values, positionals } = parseCommandArgs(args, taskAddOptions());
2124
+ const fields = {
2125
+ title: values.title,
2126
+ due_at: values.due,
2127
+ notes: values.notes,
2128
+ prospect_id: values.prospect,
2129
+ list_id: values.list,
2130
+ assignee_account_user_id: values["assigned-user"]
2131
+ };
2132
+ const sentFields = Object.entries(fields).filter(([, value]) => value !== undefined);
2133
+ if (positionals.length !== 1 || sentFields.length === 0) throw new CommandError(TASKS_UPDATE_USAGE);
2134
+ if (values.prospect && values.list) throw new CommandError("Use either --prospect or --list, not both.");
2135
+
2136
+ const { client, accountId, config } = await requireAccountContext(context, { accountOverride });
2137
+ const task = Object.fromEntries(sentFields);
2138
+ if (task.assignee_account_user_id !== undefined) {
2139
+ task.assignee_account_user_id = resolveAccountUserId(task.assignee_account_user_id, config, { accountOverride }) ?? "";
2140
+ }
2141
+ const payload = await client.updateTask(accountId, positionals[0], { task });
2142
+ if (values.json) return writeJson(context.stdout, payload);
2143
+
2144
+ writeLine(context.stdout, `Updated task ${display(payload?.prefix_id || payload?.id || positionals[0])}.`);
2145
+ writeLine(context.stdout, `Title: ${display(payload?.title)}`);
2146
+ writeLine(context.stdout, `Due: ${display(payload?.due_at, "-")} | Association: ${taskAssociationLabel(payload)}`);
2147
+ }
2148
+
2149
+ async function tasksBulkUpdate(args, context, { accountOverride } = {}) {
2150
+ const { values, positionals } = parseCommandArgs(args, {
2151
+ ...jsonOptions(),
2152
+ action: { type: "string" },
2153
+ "assigned-user": { type: "string" }
2154
+ });
2155
+ const bulkAction = String(values.action || "").trim();
2156
+ if (positionals.length === 0 || !["complete", "reassign"].includes(bulkAction)) throw new CommandError(TASKS_BULK_UPDATE_USAGE);
2157
+ if (bulkAction === "reassign" && !values["assigned-user"]) throw new CommandError("--assigned-user is required with --action reassign.");
2158
+
2159
+ const { client, accountId, config } = await requireAccountContext(context, { accountOverride });
2160
+ const payload = await client.bulkUpdateTasks(accountId, compactObject({
2161
+ task_ids: positionals,
2162
+ bulk_action: bulkAction,
2163
+ assignee_account_user_id: bulkAction === "reassign" ? resolveAccountUserId(values["assigned-user"], config, { accountOverride }) : undefined
2164
+ }));
2165
+ if (values.json) return writeJson(context.stdout, payload);
2166
+
2167
+ const count = payload?.count ?? 0;
2168
+ const noun = count === 1 ? "task" : "tasks";
2169
+ writeLine(context.stdout, bulkAction === "complete" ? `Completed ${count} ${noun}.` : `Queued reassignment of ${count} ${noun}.`);
1571
2170
  }
1572
2171
 
1573
2172
  async function listsCreate(args, context, { accountOverride } = {}) {
@@ -1687,6 +2286,52 @@ async function listsDelete(args, context, { accountOverride } = {}) {
1687
2286
  }
1688
2287
  }
1689
2288
 
2289
+ async function listsBulkAddTag(args, context, { accountOverride } = {}) {
2290
+ const { values, positionals } = parseCommandArgs(args, { ...jsonOptions(), tag: { type: "string" } });
2291
+ if (positionals.length === 0 || !String(values.tag || "").trim()) throw new CommandError(LISTS_BULK_ADD_TAG_USAGE);
2292
+
2293
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
2294
+ const lists = await client.bulkAddListTag(accountId, { list_ids: positionals, tag: values.tag });
2295
+ if (values.json) return writeJson(context.stdout, lists);
2296
+
2297
+ const count = Array.isArray(lists) ? lists.length : 0;
2298
+ writeLine(context.stdout, `Added tag ${display(values.tag)} to ${count} ${count === 1 ? "list" : "lists"}.`);
2299
+ }
2300
+
2301
+ async function listsMerge(args, context, { accountOverride } = {}) {
2302
+ const { values, positionals } = parseCommandArgs(args, jsonOptions());
2303
+ if (positionals.length !== 2) throw new CommandError(LISTS_MERGE_USAGE);
2304
+
2305
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
2306
+ const list = await client.mergeLists(accountId, { list_ids: positionals });
2307
+ if (values.json) return writeJson(context.stdout, list);
2308
+
2309
+ writeLine(context.stdout, `Merged into list ${display(list?.name)} (${display(list?.prefix_id)}); the newer list was deleted.`);
2310
+ writeLine(context.stdout, `Prospects: ${display(list?.prospect_count, 0)}`);
2311
+ }
2312
+
2313
+ async function listsExport(args, context, { accountOverride } = {}) {
2314
+ const { values, positionals } = parseCommandArgs(args, {
2315
+ ...jsonOptions(),
2316
+ output: { type: "string" },
2317
+ "inactive-reason": { type: "string" }
2318
+ });
2319
+ if (positionals.length !== 1) throw new CommandError(LISTS_EXPORT_USAGE);
2320
+
2321
+ const listId = positionals[0];
2322
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
2323
+ const csv = String(await client.exportList(accountId, listId, compactObject({ inactive_reason: values["inactive-reason"] })) ?? "");
2324
+
2325
+ if (values.output) {
2326
+ await writeFile(values.output, csv);
2327
+ if (values.json) return writeJson(context.stdout, { list_id: listId, output: values.output, bytes: Buffer.byteLength(csv) });
2328
+ return writeLine(context.stdout, `Wrote list ${display(listId)} CSV to ${values.output}.`);
2329
+ }
2330
+ if (values.json) return writeJson(context.stdout, { list_id: listId, csv });
2331
+
2332
+ context.stdout.write(csv);
2333
+ }
2334
+
1690
2335
  async function listProspects(args, context, { accountOverride } = {}) {
1691
2336
  const { values, positionals } = parseCommandArgs(args, {
1692
2337
  ...jsonOptions(),
@@ -1768,7 +2413,7 @@ async function listsRemoveProspects(args, context, { accountOverride } = {}) {
1768
2413
  async function listsRoutingRules(args, context, { accountOverride } = {}) {
1769
2414
  const [listId, subaction, ...rest] = args;
1770
2415
  if (!listId || !subaction) {
1771
- throw new CommandError("Usage: audienti lists routing-rules <list_id> <list|create|update|remove|move|apply> [args] [--json] [--account <acct_id>]");
2416
+ throw new CommandError("Usage: audienti lists routing-rules <list_id> <list|create|update|remove|move|toggle|apply> [args] [--json] [--account <acct_id>]");
1772
2417
  }
1773
2418
 
1774
2419
  if (subaction === "list") return listRoutingRulesList(listId, rest, context, { accountOverride });
@@ -1776,9 +2421,10 @@ async function listsRoutingRules(args, context, { accountOverride } = {}) {
1776
2421
  if (subaction === "update") return listRoutingRulesUpdate(listId, rest, context, { accountOverride });
1777
2422
  if (["remove", "delete"].includes(subaction)) return listRoutingRulesRemove(listId, rest, context, { accountOverride });
1778
2423
  if (subaction === "move") return listRoutingRulesMove(listId, rest, context, { accountOverride });
2424
+ if (subaction === "toggle") return listRoutingRulesToggle(listId, rest, context, { accountOverride });
1779
2425
  if (subaction === "apply") return listRoutingRulesApply(listId, rest, context, { accountOverride });
1780
2426
 
1781
- throw new CommandError("Usage: audienti lists routing-rules <list_id> <list|create|update|remove|move|apply> [args] [--json] [--account <acct_id>]");
2427
+ throw new CommandError("Usage: audienti lists routing-rules <list_id> <list|create|update|remove|move|toggle|apply> [args] [--json] [--account <acct_id>]");
1782
2428
  }
1783
2429
 
1784
2430
  async function listRoutingRulesList(listId, args, context, { accountOverride } = {}) {
@@ -1852,6 +2498,19 @@ async function listRoutingRulesMove(listId, args, context, { accountOverride } =
1852
2498
  renderListRoutingRules(payload, context);
1853
2499
  }
1854
2500
 
2501
+ async function listRoutingRulesToggle(listId, args, context, { accountOverride } = {}) {
2502
+ const { values, positionals } = parseCommandArgs(args, jsonOptions());
2503
+ if (positionals.length !== 1) throw new CommandError(LIST_ROUTING_RULES_TOGGLE_USAGE);
2504
+
2505
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
2506
+ const payload = await client.toggleListRoutingRule(accountId, listId, positionals[0]);
2507
+ if (values.json) return writeJson(context.stdout, payload);
2508
+
2509
+ const rule = payload?.routing_rule;
2510
+ const state = rule?.enabled ? "Enabled" : "Disabled";
2511
+ writeLine(context.stdout, `${state} routing rule ${display(rule?.name)} (${display(rule?.id || positionals[0])}) on list ${display(payload?.list_id || listId)}.`);
2512
+ }
2513
+
1855
2514
  async function listRoutingRulesApply(listId, args, context, { accountOverride } = {}) {
1856
2515
  const { values, positionals } = parseCommandArgs(args, jsonOptions());
1857
2516
  if (positionals.length > 0) throw new CommandError(LIST_ROUTING_RULES_APPLY_USAGE);
@@ -3404,6 +4063,23 @@ async function toolsLinkedinReviewStatus(args, context, { accountOverride } = {}
3404
4063
  renderLinkedinReviewStatus(payload, context);
3405
4064
  }
3406
4065
 
4066
+ async function toolsLinkedinStrategyReview(args, context, { accountOverride } = {}) {
4067
+ const [subaction, ...rest] = args;
4068
+ if (subaction !== "delete") throw new CommandError(TOOLS_LINKEDIN_STRATEGY_REVIEW_DELETE_USAGE);
4069
+
4070
+ const { values, positionals } = parseCommandArgs(rest, { ...jsonOptions(), confirm: { type: "string" } });
4071
+ const normalizedConfirm = String(values.confirm || "").trim().toLowerCase();
4072
+ if (positionals.length !== 1 || !DELETE_CONFIRMATION_VALUES.has(normalizedConfirm)) {
4073
+ throw new CommandError(TOOLS_LINKEDIN_STRATEGY_REVIEW_DELETE_USAGE);
4074
+ }
4075
+
4076
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
4077
+ const payload = await client.deleteLinkedinStrategyReview(accountId, positionals[0]);
4078
+ if (values.json) return writeJson(context.stdout, payload);
4079
+
4080
+ writeLine(context.stdout, `Deleted LinkedIn strategy review report ${display(payload?.id || positionals[0])}.`);
4081
+ }
4082
+
3407
4083
  async function operatorQueue(args, context, { accountOverride } = {}) {
3408
4084
  const { values, positionals } = parseCommandArgs(args, operatorFilterOptions(operatorPaginationOptions()));
3409
4085
  if (positionals.length > 0) throw new CommandError("Usage: audienti operator queue [--json] [filters] [--account <acct_id>]");
@@ -5729,6 +6405,16 @@ function renderOffer(offer, context) {
5729
6405
  writeLine(context.stdout, `Offer: ${display(offer?.name)} (${display(offer?.prefix_id)})`);
5730
6406
  if (offer?.description) writeLine(context.stdout, `Description: ${offer.description}`);
5731
6407
  if (offer?.url) writeLine(context.stdout, `URL: ${offer.url}`);
6408
+ if (Array.isArray(offer?.artifacts) && offer.artifacts.length > 0) renderOfferArtifacts(offer.artifacts, context);
6409
+ }
6410
+
6411
+ function renderOfferArtifacts(artifacts, context) {
6412
+ if (!Array.isArray(artifacts) || artifacts.length === 0) return writeLine(context.stdout, "No artifacts attached.");
6413
+
6414
+ writeLine(context.stdout, "ARTIFACT ID\tFILENAME\tTYPE\tBYTES");
6415
+ for (const artifact of artifacts) {
6416
+ writeLine(context.stdout, [display(artifact.id), display(artifact.filename), display(artifact.content_type, "-"), display(artifact.byte_size, 0)].join("\t"));
6417
+ }
5732
6418
  }
5733
6419
 
5734
6420
  function renderIcps(icps, context) {
@@ -8572,15 +9258,23 @@ const HELP_TOPICS = new Map([
8572
9258
  " audienti lists routing-rules <list_id> list",
8573
9259
  " audienti lists routing-rules <list_id> create --payload <file.json>",
8574
9260
  " audienti lists routing-rules <list_id> apply",
9261
+ " audienti lists bulk-add-tag --tag <tag> <list_id> [list_id...]",
9262
+ " audienti lists merge <list_id> <list_id>",
9263
+ " audienti lists export <list_id> [--output <file.csv>]",
8575
9264
  " audienti tags list",
8576
9265
  " audienti tags show <tag>",
8577
9266
  " audienti tasks list [--status open]",
8578
9267
  " audienti tasks add --title <text> --due <time>",
8579
9268
  " audienti tasks complete <ptsk_id>",
9269
+ " audienti tasks update <ptsk_id> [--title <text>]",
9270
+ " audienti tasks bulk-update --action <complete|reassign> <ptsk_id> [ptsk_id...]",
8580
9271
  " audienti offers list",
8581
9272
  " audienti offers show <offr_id>",
8582
9273
  " audienti offers update <offr_id> [--name <text>]",
8583
9274
  " audienti offers delete <offr_id> --confirm <yes|true|Y|y>",
9275
+ " audienti offers regenerate-research <offr_id> [--guidance <text>]",
9276
+ " audienti offers update-writeup <offr_id> --description <text>",
9277
+ " audienti offers add-artifacts <offr_id> <file> [file...]",
8584
9278
  " audienti icps list [--status <active|archived|all>] [--tag <tag>]",
8585
9279
  " audienti icps show <icp_id>",
8586
9280
  " audienti icps update <icp_id> [--tags <tag[,tag...]> | --payload <file.json>]",
@@ -8588,6 +9282,10 @@ const HELP_TOPICS = new Map([
8588
9282
  " audienti icps restore <icp_id>",
8589
9283
  " audienti icps add-tag <icp_id> <tag>",
8590
9284
  " audienti icps remove-tag <icp_id> <tag>",
9285
+ " audienti icps bulk-add-tag --tag <tag> <icp_id> [icp_id...]",
9286
+ " audienti icps clone <icp_id>",
9287
+ " audienti icps delete <icp_id> --confirm <yes|true|Y|y>",
9288
+ " audienti icps prospects <icp_id> [--query <text>]",
8591
9289
  " audienti companies search --query <text>",
8592
9290
  " audienti linkedin-lookups <kind> [--query <text>]",
8593
9291
  " audienti dnc list",
@@ -8595,6 +9293,10 @@ const HELP_TOPICS = new Map([
8595
9293
  " audienti company-rules list",
8596
9294
  " audienti company-rules show <rule_id>",
8597
9295
  " audienti company-rules create (--linkedin-url <url> | --domain <domain>) --disposition <state>",
9296
+ " audienti hubspot show",
9297
+ " audienti webhooks list",
9298
+ " audienti brand-profile show",
9299
+ " audienti reply-alerts show",
8598
9300
  "",
8599
9301
  " Writer",
8600
9302
  " audienti writer test-run <prsp_id>",
@@ -9125,6 +9827,10 @@ const HELP_TOPICS = new Map([
9125
9827
  " audienti offers create --name <text> [--json]",
9126
9828
  " audienti offers update <offr_id> [--name <text>] [--json]",
9127
9829
  " audienti offers delete <offr_id> --confirm <yes|true|Y|y> [--json]",
9830
+ " audienti offers regenerate-research <offr_id> [--guidance <text>] [--json]",
9831
+ " audienti offers update-writeup <offr_id> --description <text> [--json]",
9832
+ " audienti offers add-artifacts <offr_id> <file> [file...] [--json]",
9833
+ " audienti offers remove-artifact <offr_id> <artifact_id> [--json]",
9128
9834
  "",
9129
9835
  "Status: implemented",
9130
9836
  "",
@@ -9236,6 +9942,10 @@ const HELP_TOPICS = new Map([
9236
9942
  " audienti icps remove-tag <icp_id> <tag> [--json]",
9237
9943
  " audienti icps archive <icp_id> [--json]",
9238
9944
  " audienti icps restore <icp_id> [--json]",
9945
+ " audienti icps bulk-add-tag --tag <tag> <icp_id> [icp_id...] [--json]",
9946
+ " audienti icps clone <icp_id> [--json]",
9947
+ " audienti icps delete <icp_id> --confirm <yes|true|Y|y> [--json]",
9948
+ " audienti icps prospects <icp_id> [--query <text>] [--json]",
9239
9949
  "",
9240
9950
  "Status: implemented",
9241
9951
  "",
@@ -9412,141 +10122,376 @@ const HELP_TOPICS = new Map([
9412
10122
  " }"
9413
10123
  ].join("\n")],
9414
10124
 
9415
- ["icps add-tag", [
10125
+ ["icps add-tag", [
10126
+ "Usage:",
10127
+ " audienti icps add-tag <icp_id> <tag> [--json] [--account <acct_id>]",
10128
+ "",
10129
+ "Status: implemented",
10130
+ "",
10131
+ "Purpose:",
10132
+ " Add one normalized tag to an ICP.",
10133
+ "",
10134
+ "Input shape:",
10135
+ " icp_id: icpp_ prefixed id or integer id",
10136
+ " tag: string",
10137
+ "",
10138
+ "API:",
10139
+ " POST /api/v1/accounts/:account_id/icps/:id/add_tag.json",
10140
+ "",
10141
+ "JSON body:",
10142
+ " {",
10143
+ " \"tag\": \"enterprise\"",
10144
+ " }"
10145
+ ].join("\n")],
10146
+
10147
+ ["icps remove-tag", [
10148
+ "Usage:",
10149
+ " audienti icps remove-tag <icp_id> <tag> [--json] [--account <acct_id>]",
10150
+ "",
10151
+ "Status: implemented",
10152
+ "",
10153
+ "Purpose:",
10154
+ " Remove one normalized tag from an ICP.",
10155
+ "",
10156
+ "Input shape:",
10157
+ " icp_id: icpp_ prefixed id or integer id",
10158
+ " tag: string",
10159
+ "",
10160
+ "API:",
10161
+ " DELETE /api/v1/accounts/:account_id/icps/:id/remove_tag.json",
10162
+ "",
10163
+ "JSON body:",
10164
+ " {",
10165
+ " \"tag\": \"enterprise\"",
10166
+ " }"
10167
+ ].join("\n")],
10168
+
10169
+ ["companies", [
10170
+ "Usage:",
10171
+ " audienti companies search --query <text> [--json]",
10172
+ "",
10173
+ "Status: implemented",
10174
+ "",
10175
+ "Purpose:",
10176
+ " Returns persisted LinkedIn company profiles that match a company search query."
10177
+ ].join("\n")],
10178
+
10179
+ ["companies search", [
10180
+ "Usage:",
10181
+ " audienti companies search --query <text> [--json] [--account <acct_id>]",
10182
+ "",
10183
+ "Status: implemented",
10184
+ "",
10185
+ "API:",
10186
+ " GET /api/v1/accounts/:account_id/companies.json",
10187
+ "",
10188
+ "Input shape:",
10189
+ " query: string",
10190
+ "",
10191
+ "Output shape:",
10192
+ " companies[].prefix_id: prof_ profile id used by --company-profile",
10193
+ " companies[].citation_id: linkedin/company:...",
10194
+ " companies[].display_name: string",
10195
+ " companies[].url: string",
10196
+ " companies[].industry: string | null",
10197
+ " companies[].location: string | null"
10198
+ ].join("\n")],
10199
+
10200
+ ["dnc", [
10201
+ "Usage:",
10202
+ " audienti dnc list [--json]",
10203
+ ` ${DNC_ADD_USAGE.slice("Usage: ".length)}`,
10204
+ ` ${DNC_IMPORT_USAGE.slice("Usage: ".length)}`,
10205
+ ` ${DNC_REMOVE_USAGE.slice("Usage: ".length)}`,
10206
+ "",
10207
+ "Status: implemented",
10208
+ "",
10209
+ "Purpose:",
10210
+ " Manage account-level do-not-contact entries through the same settings/API path as the app."
10211
+ ].join("\n")],
10212
+
10213
+ ["dnc list", [
10214
+ "Usage:",
10215
+ " audienti dnc list [--limit <n>] [--offset <n>] [--json] [--account <acct_id>]",
10216
+ "",
10217
+ "Status: implemented",
10218
+ "",
10219
+ "API:",
10220
+ " GET /api/v1/accounts/:account_id/dnc.json"
10221
+ ].join("\n")],
10222
+
10223
+ ["dnc add", [
10224
+ "Usage:",
10225
+ ` ${DNC_ADD_USAGE.slice("Usage: ".length)}`,
10226
+ "",
10227
+ "Status: implemented",
10228
+ "",
10229
+ "Input shape:",
10230
+ " value: email, citation ID, or supported person profile URL",
10231
+ "",
10232
+ "Behavior:",
10233
+ " Creates or reactivates an account DNC entry and retroactively rejects matching account prospects.",
10234
+ "",
10235
+ "API:",
10236
+ " POST /api/v1/accounts/:account_id/dnc.json"
10237
+ ].join("\n")],
10238
+
10239
+ ["dnc import", [
10240
+ "Usage:",
10241
+ ` ${DNC_IMPORT_USAGE.slice("Usage: ".length)}`,
10242
+ "",
10243
+ "Status: implemented",
10244
+ "",
10245
+ "Input shape:",
10246
+ " file: text or CSV file. The CLI sends one value per line using column one for CSV-like rows.",
10247
+ "",
10248
+ "API:",
10249
+ " POST /api/v1/accounts/:account_id/dnc/import.json"
10250
+ ].join("\n")],
10251
+
10252
+ ["dnc remove", [
10253
+ "Usage:",
10254
+ ` ${DNC_REMOVE_USAGE.slice("Usage: ".length)}`,
10255
+ "",
10256
+ "Status: implemented",
10257
+ "",
10258
+ "API:",
10259
+ " DELETE /api/v1/accounts/:account_id/dnc/:id.json"
10260
+ ].join("\n")],
10261
+
10262
+ ["hubspot", [
10263
+ "Usage:",
10264
+ ` ${HUBSPOT_SHOW_USAGE.slice("Usage: ".length)}`,
10265
+ ` ${HUBSPOT_CONNECT_USAGE.slice("Usage: ".length)}`,
10266
+ ` ${HUBSPOT_DISCONNECT_USAGE.slice("Usage: ".length)}`,
10267
+ ` ${HUBSPOT_SYNC_USAGE.slice("Usage: ".length)}`,
10268
+ ` ${HUBSPOT_RETRY_USAGE.slice("Usage: ".length)}`,
10269
+ ` ${HUBSPOT_LIST_SYNCS_CREATE_USAGE.slice("Usage: ".length)}`,
10270
+ ` ${HUBSPOT_LIST_SYNCS_UPDATE_USAGE.slice("Usage: ".length)}`,
10271
+ ` ${HUBSPOT_LIST_SYNCS_REMOVE_USAGE.slice("Usage: ".length)}`,
10272
+ ` ${HUBSPOT_LIST_SYNCS_SYNC_USAGE.slice("Usage: ".length)}`,
10273
+ "",
10274
+ "Status: implemented",
10275
+ "",
10276
+ "Purpose:",
10277
+ " Connect HubSpot, queue syncs, retry failed sync events, and manage HubSpot list syncs. The token is never returned.",
10278
+ "",
10279
+ "API:",
10280
+ " GET /api/v1/accounts/:account_id/hubspot_integration.json",
10281
+ " POST /api/v1/accounts/:account_id/hubspot_integration.json",
10282
+ " DELETE /api/v1/accounts/:account_id/hubspot_integration.json",
10283
+ " POST /api/v1/accounts/:account_id/hubspot_integration/sync_all.json",
10284
+ " POST /api/v1/accounts/:account_id/hubspot_integration/retry_event.json"
10285
+ ].join("\n")],
10286
+
10287
+ ["hubspot show", [
10288
+ "Usage:",
10289
+ ` ${HUBSPOT_SHOW_USAGE.slice("Usage: ".length)}`,
10290
+ "",
10291
+ "Status: implemented",
10292
+ "",
10293
+ "API:",
10294
+ " GET /api/v1/accounts/:account_id/hubspot_integration.json"
10295
+ ].join("\n")],
10296
+
10297
+ ["hubspot connect", [
10298
+ "Usage:",
10299
+ ` ${HUBSPOT_CONNECT_USAGE.slice("Usage: ".length)}`,
10300
+ "",
10301
+ "Status: implemented",
10302
+ "",
10303
+ "Notes:",
10304
+ " --token-stdin reads the token from stdin, keeping it out of shell history and the process list.",
10305
+ "",
10306
+ "API:",
10307
+ " POST /api/v1/accounts/:account_id/hubspot_integration.json"
10308
+ ].join("\n")],
10309
+
10310
+ ["hubspot disconnect", [
10311
+ "Usage:",
10312
+ ` ${HUBSPOT_DISCONNECT_USAGE.slice("Usage: ".length)}`,
10313
+ "",
10314
+ "Status: implemented",
10315
+ "",
10316
+ "API:",
10317
+ " DELETE /api/v1/accounts/:account_id/hubspot_integration.json"
10318
+ ].join("\n")],
10319
+
10320
+ ["hubspot sync", [
10321
+ "Usage:",
10322
+ ` ${HUBSPOT_SYNC_USAGE.slice("Usage: ".length)}`,
10323
+ "",
10324
+ "Status: implemented",
10325
+ "",
10326
+ "API:",
10327
+ " POST /api/v1/accounts/:account_id/hubspot_integration/sync_all.json"
10328
+ ].join("\n")],
10329
+
10330
+ ["hubspot retry", [
10331
+ "Usage:",
10332
+ ` ${HUBSPOT_RETRY_USAGE.slice("Usage: ".length)}`,
10333
+ "",
10334
+ "Status: implemented",
10335
+ "",
10336
+ "API:",
10337
+ " POST /api/v1/accounts/:account_id/hubspot_integration/retry_event.json"
10338
+ ].join("\n")],
10339
+
10340
+ ["hubspot list-syncs", [
10341
+ "Usage:",
10342
+ ` ${HUBSPOT_LIST_SYNCS_CREATE_USAGE.slice("Usage: ".length)}`,
10343
+ ` ${HUBSPOT_LIST_SYNCS_UPDATE_USAGE.slice("Usage: ".length)}`,
10344
+ ` ${HUBSPOT_LIST_SYNCS_REMOVE_USAGE.slice("Usage: ".length)}`,
10345
+ ` ${HUBSPOT_LIST_SYNCS_SYNC_USAGE.slice("Usage: ".length)}`,
10346
+ "",
10347
+ "Status: implemented",
10348
+ "",
10349
+ "API:",
10350
+ " POST /api/v1/accounts/:account_id/hubspot_integration/list_syncs.json",
10351
+ " PATCH /api/v1/accounts/:account_id/hubspot_integration/list_syncs/:id.json",
10352
+ " DELETE /api/v1/accounts/:account_id/hubspot_integration/list_syncs/:id.json",
10353
+ " POST /api/v1/accounts/:account_id/hubspot_integration/list_syncs/:id/sync_now.json"
10354
+ ].join("\n")],
10355
+
10356
+ ["webhooks", [
10357
+ "Usage:",
10358
+ ` ${WEBHOOKS_LIST_USAGE.slice("Usage: ".length)}`,
10359
+ ` ${WEBHOOKS_CREATE_USAGE.slice("Usage: ".length)}`,
10360
+ ` ${WEBHOOKS_UPDATE_USAGE.slice("Usage: ".length)}`,
10361
+ ` ${WEBHOOKS_ROTATE_USAGE.slice("Usage: ".length)}`,
10362
+ ` ${WEBHOOKS_REMOVE_USAGE.slice("Usage: ".length)}`,
10363
+ "",
10364
+ "Status: implemented",
10365
+ "",
10366
+ "Purpose:",
10367
+ " Manage prospect intake webhook endpoints. Create and rotate return the endpoint URL.",
10368
+ "",
10369
+ "API:",
10370
+ " GET /api/v1/accounts/:account_id/prospect_webhook_endpoints.json",
10371
+ " POST /api/v1/accounts/:account_id/prospect_webhook_endpoints.json",
10372
+ " PATCH /api/v1/accounts/:account_id/prospect_webhook_endpoints/:id.json",
10373
+ " POST /api/v1/accounts/:account_id/prospect_webhook_endpoints/:id/rotate.json",
10374
+ " DELETE /api/v1/accounts/:account_id/prospect_webhook_endpoints/:id.json"
10375
+ ].join("\n")],
10376
+
10377
+ ["webhooks list", [
10378
+ "Usage:",
10379
+ ` ${WEBHOOKS_LIST_USAGE.slice("Usage: ".length)}`,
10380
+ "",
10381
+ "Status: implemented",
10382
+ "",
10383
+ "API:",
10384
+ " GET /api/v1/accounts/:account_id/prospect_webhook_endpoints.json"
10385
+ ].join("\n")],
10386
+
10387
+ ["webhooks create", [
9416
10388
  "Usage:",
9417
- " audienti icps add-tag <icp_id> <tag> [--json] [--account <acct_id>]",
10389
+ ` ${WEBHOOKS_CREATE_USAGE.slice("Usage: ".length)}`,
9418
10390
  "",
9419
10391
  "Status: implemented",
9420
10392
  "",
9421
- "Purpose:",
9422
- " Add one normalized tag to an ICP.",
9423
- "",
9424
- "Input shape:",
9425
- " icp_id: icpp_ prefixed id or integer id",
9426
- " tag: string",
9427
- "",
9428
10393
  "API:",
9429
- " POST /api/v1/accounts/:account_id/icps/:id/add_tag.json",
9430
- "",
9431
- "JSON body:",
9432
- " {",
9433
- " \"tag\": \"enterprise\"",
9434
- " }"
10394
+ " POST /api/v1/accounts/:account_id/prospect_webhook_endpoints.json"
9435
10395
  ].join("\n")],
9436
10396
 
9437
- ["icps remove-tag", [
10397
+ ["webhooks update", [
9438
10398
  "Usage:",
9439
- " audienti icps remove-tag <icp_id> <tag> [--json] [--account <acct_id>]",
10399
+ ` ${WEBHOOKS_UPDATE_USAGE.slice("Usage: ".length)}`,
9440
10400
  "",
9441
10401
  "Status: implemented",
9442
10402
  "",
9443
- "Purpose:",
9444
- " Remove one normalized tag from an ICP.",
9445
- "",
9446
- "Input shape:",
9447
- " icp_id: icpp_ prefixed id or integer id",
9448
- " tag: string",
9449
- "",
9450
10403
  "API:",
9451
- " DELETE /api/v1/accounts/:account_id/icps/:id/remove_tag.json",
9452
- "",
9453
- "JSON body:",
9454
- " {",
9455
- " \"tag\": \"enterprise\"",
9456
- " }"
10404
+ " PATCH /api/v1/accounts/:account_id/prospect_webhook_endpoints/:id.json"
9457
10405
  ].join("\n")],
9458
10406
 
9459
- ["companies", [
10407
+ ["webhooks rotate", [
9460
10408
  "Usage:",
9461
- " audienti companies search --query <text> [--json]",
10409
+ ` ${WEBHOOKS_ROTATE_USAGE.slice("Usage: ".length)}`,
9462
10410
  "",
9463
10411
  "Status: implemented",
9464
10412
  "",
9465
- "Purpose:",
9466
- " Returns persisted LinkedIn company profiles that match a company search query."
10413
+ "API:",
10414
+ " POST /api/v1/accounts/:account_id/prospect_webhook_endpoints/:id/rotate.json"
9467
10415
  ].join("\n")],
9468
10416
 
9469
- ["companies search", [
10417
+ ["webhooks remove", [
9470
10418
  "Usage:",
9471
- " audienti companies search --query <text> [--json] [--account <acct_id>]",
10419
+ ` ${WEBHOOKS_REMOVE_USAGE.slice("Usage: ".length)}`,
9472
10420
  "",
9473
10421
  "Status: implemented",
9474
10422
  "",
9475
10423
  "API:",
9476
- " GET /api/v1/accounts/:account_id/companies.json",
9477
- "",
9478
- "Input shape:",
9479
- " query: string",
9480
- "",
9481
- "Output shape:",
9482
- " companies[].prefix_id: prof_ profile id used by --company-profile",
9483
- " companies[].citation_id: linkedin/company:...",
9484
- " companies[].display_name: string",
9485
- " companies[].url: string",
9486
- " companies[].industry: string | null",
9487
- " companies[].location: string | null"
10424
+ " DELETE /api/v1/accounts/:account_id/prospect_webhook_endpoints/:id.json"
9488
10425
  ].join("\n")],
9489
10426
 
9490
- ["dnc", [
10427
+ ["reply-alerts", [
9491
10428
  "Usage:",
9492
- " audienti dnc list [--json]",
9493
- ` ${DNC_ADD_USAGE.slice("Usage: ".length)}`,
9494
- ` ${DNC_IMPORT_USAGE.slice("Usage: ".length)}`,
9495
- ` ${DNC_REMOVE_USAGE.slice("Usage: ".length)}`,
10429
+ ` ${REPLY_ALERTS_SHOW_USAGE.slice("Usage: ".length)}`,
10430
+ ` ${REPLY_ALERTS_UPDATE_USAGE.slice("Usage: ".length)}`,
9496
10431
  "",
9497
10432
  "Status: implemented",
9498
10433
  "",
9499
10434
  "Purpose:",
9500
- " Manage account-level do-not-contact entries through the same settings/API path as the app."
10435
+ " Show or change your own SMS and Slack reply alerts. Connect Slack in the web app.",
10436
+ "",
10437
+ "API:",
10438
+ " GET /api/v1/me/reply_alerts.json",
10439
+ " PATCH /api/v1/me/reply_alerts.json"
9501
10440
  ].join("\n")],
9502
10441
 
9503
- ["dnc list", [
10442
+ ["reply-alerts show", [
9504
10443
  "Usage:",
9505
- " audienti dnc list [--limit <n>] [--offset <n>] [--json] [--account <acct_id>]",
10444
+ ` ${REPLY_ALERTS_SHOW_USAGE.slice("Usage: ".length)}`,
9506
10445
  "",
9507
10446
  "Status: implemented",
9508
10447
  "",
9509
10448
  "API:",
9510
- " GET /api/v1/accounts/:account_id/dnc.json"
10449
+ " GET /api/v1/me/reply_alerts.json"
9511
10450
  ].join("\n")],
9512
10451
 
9513
- ["dnc add", [
10452
+ ["reply-alerts update", [
9514
10453
  "Usage:",
9515
- ` ${DNC_ADD_USAGE.slice("Usage: ".length)}`,
10454
+ ` ${REPLY_ALERTS_UPDATE_USAGE.slice("Usage: ".length)}`,
9516
10455
  "",
9517
10456
  "Status: implemented",
9518
10457
  "",
9519
- "Input shape:",
9520
- " value: email, citation ID, or supported person profile URL",
10458
+ "API:",
10459
+ " PATCH /api/v1/me/reply_alerts.json"
10460
+ ].join("\n")],
10461
+
10462
+ ["brand-profile", [
10463
+ "Usage:",
10464
+ ` ${BRAND_PROFILE_SHOW_USAGE.slice("Usage: ".length)}`,
10465
+ ` ${BRAND_PROFILE_UPDATE_USAGE.slice("Usage: ".length)}`,
9521
10466
  "",
9522
- "Behavior:",
9523
- " Creates or reactivates an account DNC entry and retroactively rejects matching account prospects.",
10467
+ "Status: implemented",
10468
+ "",
10469
+ "Purpose:",
10470
+ " Show or change the account brand voice, style, and banned phrases. Account admins only.",
9524
10471
  "",
9525
10472
  "API:",
9526
- " POST /api/v1/accounts/:account_id/dnc.json"
10473
+ " GET /api/v1/accounts/:account_id/brand_profile.json",
10474
+ " PATCH /api/v1/accounts/:account_id/brand_profile.json"
9527
10475
  ].join("\n")],
9528
10476
 
9529
- ["dnc import", [
10477
+ ["brand-profile show", [
9530
10478
  "Usage:",
9531
- ` ${DNC_IMPORT_USAGE.slice("Usage: ".length)}`,
10479
+ ` ${BRAND_PROFILE_SHOW_USAGE.slice("Usage: ".length)}`,
9532
10480
  "",
9533
10481
  "Status: implemented",
9534
10482
  "",
9535
- "Input shape:",
9536
- " file: text or CSV file. The CLI sends one value per line using column one for CSV-like rows.",
9537
- "",
9538
10483
  "API:",
9539
- " POST /api/v1/accounts/:account_id/dnc/import.json"
10484
+ " GET /api/v1/accounts/:account_id/brand_profile.json"
9540
10485
  ].join("\n")],
9541
10486
 
9542
- ["dnc remove", [
10487
+ ["brand-profile update", [
9543
10488
  "Usage:",
9544
- ` ${DNC_REMOVE_USAGE.slice("Usage: ".length)}`,
10489
+ ` ${BRAND_PROFILE_UPDATE_USAGE.slice("Usage: ".length)}`,
9545
10490
  "",
9546
10491
  "Status: implemented",
9547
10492
  "",
9548
10493
  "API:",
9549
- " DELETE /api/v1/accounts/:account_id/dnc/:id.json"
10494
+ " PATCH /api/v1/accounts/:account_id/brand_profile.json"
9550
10495
  ].join("\n")],
9551
10496
 
9552
10497
  ["company-rules", [
@@ -9699,6 +10644,8 @@ const HELP_TOPICS = new Map([
9699
10644
  ` ${TASKS_LIST_USAGE.slice("Usage: ".length)}`,
9700
10645
  ` ${TASKS_ADD_USAGE.slice("Usage: ".length)}`,
9701
10646
  ` ${TASKS_COMPLETE_USAGE.slice("Usage: ".length)}`,
10647
+ ` ${TASKS_UPDATE_USAGE.slice("Usage: ".length)}`,
10648
+ ` ${TASKS_BULK_UPDATE_USAGE.slice("Usage: ".length)}`,
9702
10649
  "",
9703
10650
  "Status: implemented",
9704
10651
  "",
@@ -9779,10 +10726,13 @@ const HELP_TOPICS = new Map([
9779
10726
  " audienti lists add-tag <list_id> <tag> [--json]",
9780
10727
  " audienti lists remove-tag <list_id> <tag> [--json]",
9781
10728
  " audienti lists delete <list_id> --confirm <yes|true|Y|y> [--json]",
10729
+ " audienti lists bulk-add-tag --tag <tag> <list_id> [list_id...] [--json]",
10730
+ " audienti lists merge <list_id> <list_id> [--json]",
10731
+ " audienti lists export <list_id> [--output <file.csv>] [--json]",
9782
10732
  " audienti lists prospects <list_id> [--json]",
9783
10733
  " audienti lists add-prospects <list_id> <prsp_id> [prsp_id...] [--json]",
9784
10734
  " audienti lists remove-prospects <list_id> <prsp_id> [prsp_id...] [--json]",
9785
- " audienti lists routing-rules <list_id> <list|create|update|remove|move|apply> [args] [--json]",
10735
+ " audienti lists routing-rules <list_id> <list|create|update|remove|move|toggle|apply> [args] [--json]",
9786
10736
  "",
9787
10737
  "Status: read, create, update, delete, membership, and routing-rule commands implemented",
9788
10738
  "",
@@ -9797,6 +10747,7 @@ const HELP_TOPICS = new Map([
9797
10747
  " audienti lists routing-rules <list_id> update <rule_id> --payload <file.json> [--json] [--account <acct_id>]",
9798
10748
  " audienti lists routing-rules <list_id> remove <rule_id> [--json] [--account <acct_id>]",
9799
10749
  " audienti lists routing-rules <list_id> move <rule_id> <up|down> [--json] [--account <acct_id>]",
10750
+ " audienti lists routing-rules <list_id> toggle <rule_id> [--json] [--account <acct_id>]",
9800
10751
  " audienti lists routing-rules <list_id> apply [--json] [--account <acct_id>]",
9801
10752
  "",
9802
10753
  "Status: implemented",
@@ -9938,6 +10889,259 @@ const HELP_TOPICS = new Map([
9938
10889
  " PATCH /api/v1/accounts/:account_id/lists/:id.json"
9939
10890
  ].join("\n")],
9940
10891
 
10892
+ ["lists bulk-add-tag", [
10893
+ "Usage:",
10894
+ ` ${LISTS_BULK_ADD_TAG_USAGE.slice("Usage: ".length)}`,
10895
+ "",
10896
+ "Status: implemented",
10897
+ "",
10898
+ "Purpose:",
10899
+ " Add one normalized tag to several lists at once. All lists change or none do.",
10900
+ "",
10901
+ "Input shape:",
10902
+ " list_id: list_ prefix id or integer id; every id must belong to the account",
10903
+ " tag: string",
10904
+ "",
10905
+ "API:",
10906
+ " POST /api/v1/accounts/:account_id/lists/bulk_add_tag.json"
10907
+ ].join("\n")],
10908
+
10909
+ ["lists merge", [
10910
+ "Usage:",
10911
+ ` ${LISTS_MERGE_USAGE.slice("Usage: ".length)}`,
10912
+ "",
10913
+ "Status: implemented",
10914
+ "",
10915
+ "Purpose:",
10916
+ " Merge two lists. The older list keeps its name and gains the newer list's tags, prospects, agents, and routing targets; the newer list is deleted.",
10917
+ " System and HubSpot-synced lists cannot be merged.",
10918
+ "",
10919
+ "Input shape:",
10920
+ " list_id: exactly two different list ids in the account",
10921
+ "",
10922
+ "API:",
10923
+ " POST /api/v1/accounts/:account_id/lists/merge_selected.json"
10924
+ ].join("\n")],
10925
+
10926
+ ["lists export", [
10927
+ "Usage:",
10928
+ ` ${LISTS_EXPORT_USAGE.slice("Usage: ".length)}`,
10929
+ "",
10930
+ "Status: implemented",
10931
+ "",
10932
+ "Purpose:",
10933
+ " Download the list's prospects as CSV with the same columns as the web export.",
10934
+ " Prints to stdout unless --output is given.",
10935
+ "",
10936
+ "Input shape:",
10937
+ " list_id: list_ prefix id",
10938
+ " inactive-reason: optional filter for the inactive system list",
10939
+ "",
10940
+ "API:",
10941
+ " GET /api/v1/accounts/:account_id/lists/:id/export.json (returns text/csv)"
10942
+ ].join("\n")],
10943
+
10944
+ ["lists routing-rules toggle", [
10945
+ "Usage:",
10946
+ ` ${LIST_ROUTING_RULES_TOGGLE_USAGE.slice("Usage: ".length)}`,
10947
+ "",
10948
+ "Status: implemented",
10949
+ "",
10950
+ "Purpose:",
10951
+ " Turn one routing rule on or off.",
10952
+ "",
10953
+ "Input shape:",
10954
+ " rule_id: routing rule id on the list",
10955
+ "",
10956
+ "API:",
10957
+ " PATCH /api/v1/accounts/:account_id/lists/:list_id/routing_rules/:id/toggle.json"
10958
+ ].join("\n")],
10959
+
10960
+ ["icps bulk-add-tag", [
10961
+ "Usage:",
10962
+ ` ${ICPS_BULK_ADD_TAG_USAGE.slice("Usage: ".length)}`,
10963
+ "",
10964
+ "Status: implemented",
10965
+ "",
10966
+ "Purpose:",
10967
+ " Add one normalized tag to several ICPs at once. All ICPs change or none do.",
10968
+ "",
10969
+ "Input shape:",
10970
+ " icp_id: icpp_ prefix id or integer id; every id must belong to the account",
10971
+ " tag: string",
10972
+ "",
10973
+ "API:",
10974
+ " POST /api/v1/accounts/:account_id/icps/bulk_add_tag.json"
10975
+ ].join("\n")],
10976
+
10977
+ ["icps clone", [
10978
+ "Usage:",
10979
+ ` ${ICPS_CLONE_USAGE.slice("Usage: ".length)}`,
10980
+ "",
10981
+ "Status: implemented",
10982
+ "",
10983
+ "Purpose:",
10984
+ " Copy an ICP and its audience rules under a new \"(Copy)\" name.",
10985
+ "",
10986
+ "Input shape:",
10987
+ " icp_id: icpp_ prefix id or integer id",
10988
+ "",
10989
+ "API:",
10990
+ " POST /api/v1/accounts/:account_id/icps/:id/clone.json"
10991
+ ].join("\n")],
10992
+
10993
+ ["icps delete", [
10994
+ "Usage:",
10995
+ ` ${ICPS_DELETE_USAGE.slice("Usage: ".length)}`,
10996
+ "",
10997
+ "Status: implemented",
10998
+ "",
10999
+ "Purpose:",
11000
+ " Delete an ICP. ICPs still used by motions or agents are refused; archive them instead.",
11001
+ "",
11002
+ "Input shape:",
11003
+ " icp_id: icpp_ prefix id or integer id",
11004
+ " confirm: one of yes, true, Y, y",
11005
+ "",
11006
+ "API:",
11007
+ " DELETE /api/v1/accounts/:account_id/icps/:id.json"
11008
+ ].join("\n")],
11009
+
11010
+ ["icps prospects", [
11011
+ "Usage:",
11012
+ ` ${ICPS_PROSPECTS_USAGE.slice("Usage: ".length)}`,
11013
+ "",
11014
+ "Status: implemented",
11015
+ "",
11016
+ "Purpose:",
11017
+ " List prospects matched to an ICP, newest first, optionally filtered by a text query.",
11018
+ "",
11019
+ "Input shape:",
11020
+ " icp_id: icpp_ prefix id or integer id",
11021
+ " limit: 1-100 (default 25)",
11022
+ "",
11023
+ "API:",
11024
+ " GET /api/v1/accounts/:account_id/icps/:id/prospects.json"
11025
+ ].join("\n")],
11026
+
11027
+ ["offers regenerate-research", [
11028
+ "Usage:",
11029
+ ` ${OFFERS_REGENERATE_RESEARCH_USAGE.slice("Usage: ".length)}`,
11030
+ "",
11031
+ "Status: implemented",
11032
+ "",
11033
+ "Purpose:",
11034
+ " Queue a new research write-up for an offer, optionally steered by guidance. Returns once queued.",
11035
+ "",
11036
+ "Input shape:",
11037
+ " offr_id: offr_ prefixed id or integer id",
11038
+ " guidance: optional text",
11039
+ "",
11040
+ "API:",
11041
+ " POST /api/v1/accounts/:account_id/offers/:id/regenerate_research.json"
11042
+ ].join("\n")],
11043
+
11044
+ ["offers update-writeup", [
11045
+ "Usage:",
11046
+ ` ${OFFERS_UPDATE_WRITEUP_USAGE.slice("Usage: ".length)}`,
11047
+ "",
11048
+ "Status: implemented",
11049
+ "",
11050
+ "Purpose:",
11051
+ " Replace an offer's write-up without queueing new research.",
11052
+ "",
11053
+ "Input shape:",
11054
+ " offr_id: offr_ prefixed id or integer id",
11055
+ " description: text",
11056
+ "",
11057
+ "API:",
11058
+ " PATCH /api/v1/accounts/:account_id/offers/:id/writeup.json"
11059
+ ].join("\n")],
11060
+
11061
+ ["offers add-artifacts", [
11062
+ "Usage:",
11063
+ ` ${OFFERS_ADD_ARTIFACTS_USAGE.slice("Usage: ".length)}`,
11064
+ "",
11065
+ "Status: implemented",
11066
+ "",
11067
+ "Purpose:",
11068
+ " Attach local files to an offer as artifacts. `audienti offers show` lists attached artifacts.",
11069
+ "",
11070
+ "Input shape:",
11071
+ " offr_id: offr_ prefixed id or integer id",
11072
+ " file: local file path; sent as base64",
11073
+ "",
11074
+ "API:",
11075
+ " POST /api/v1/accounts/:account_id/offers/:offer_id/artifacts.json"
11076
+ ].join("\n")],
11077
+
11078
+ ["offers remove-artifact", [
11079
+ "Usage:",
11080
+ ` ${OFFERS_REMOVE_ARTIFACT_USAGE.slice("Usage: ".length)}`,
11081
+ "",
11082
+ "Status: implemented",
11083
+ "",
11084
+ "Purpose:",
11085
+ " Remove one artifact from an offer.",
11086
+ "",
11087
+ "Input shape:",
11088
+ " artifact_id: id from `audienti offers show <offr_id>`",
11089
+ "",
11090
+ "API:",
11091
+ " DELETE /api/v1/accounts/:account_id/offers/:offer_id/artifacts/:id.json"
11092
+ ].join("\n")],
11093
+
11094
+ ["tasks update", [
11095
+ "Usage:",
11096
+ ` ${TASKS_UPDATE_USAGE.slice("Usage: ".length)}`,
11097
+ "",
11098
+ "Status: implemented",
11099
+ "",
11100
+ "Purpose:",
11101
+ " Edit any task in the account. Only the options you pass change; pass an empty --prospect or --list to clear it.",
11102
+ "",
11103
+ "Input shape:",
11104
+ " task_id: ptsk_ prefix id or numeric id",
11105
+ " assigned-user: account user id or me",
11106
+ "",
11107
+ "API:",
11108
+ " PATCH /api/v1/accounts/:account_id/tasks/:id.json"
11109
+ ].join("\n")],
11110
+
11111
+ ["tasks bulk-update", [
11112
+ "Usage:",
11113
+ ` ${TASKS_BULK_UPDATE_USAGE.slice("Usage: ".length)}`,
11114
+ "",
11115
+ "Status: implemented",
11116
+ "",
11117
+ "Purpose:",
11118
+ " Complete several tasks now, or queue their reassignment to one account user.",
11119
+ "",
11120
+ "Input shape:",
11121
+ " task_id: ptsk_ prefix ids; every id must belong to the account",
11122
+ " action: complete or reassign (reassign needs --assigned-user)",
11123
+ "",
11124
+ "API:",
11125
+ " PATCH /api/v1/accounts/:account_id/tasks/bulk_update.json"
11126
+ ].join("\n")],
11127
+
11128
+ ["tools linkedin-strategy-review delete", [
11129
+ "Usage:",
11130
+ ` ${TOOLS_LINKEDIN_STRATEGY_REVIEW_DELETE_USAGE.slice("Usage: ".length)}`,
11131
+ "",
11132
+ "Status: implemented",
11133
+ "",
11134
+ "Purpose:",
11135
+ " Delete one LinkedIn strategy review report.",
11136
+ "",
11137
+ "Input shape:",
11138
+ " rprt_id: rprt_ report id of a strategy review",
11139
+ " confirm: one of yes, true, Y, y",
11140
+ "",
11141
+ "API:",
11142
+ " DELETE /api/v1/accounts/:account_id/tools/linkedin-strategy-review/reports/:id.json"
11143
+ ].join("\n")],
11144
+
9941
11145
  ["lists add-tag", [
9942
11146
  "Usage:",
9943
11147
  ` ${LISTS_ADD_TAG_USAGE.slice("Usage: ".length)}`,
@@ -11399,6 +12603,7 @@ const HELP_TOPICS = new Map([
11399
12603
  " audienti tools linkedin-review reports [--limit <n>] [--json]",
11400
12604
  " audienti tools linkedin-review show <rprt_id> [--json]",
11401
12605
  " audienti tools linkedin-review status <rprt_id> [--json]",
12606
+ " audienti tools linkedin-strategy-review delete <rprt_id> --confirm <yes|true|Y|y> [--json]",
11402
12607
  "",
11403
12608
  "Status: implemented",
11404
12609
  "",
@@ -11409,7 +12614,8 @@ const HELP_TOPICS = new Map([
11409
12614
  " audienti tools linkedin-review Queue a LinkedIn personal profile authority review and ICP-fit positioning blueprint.",
11410
12615
  " audienti tools linkedin-review reports List recent LinkedIn Review reports for the active account.",
11411
12616
  " audienti tools linkedin-review show View the completed report content in the terminal.",
11412
- " audienti tools linkedin-review status Show the current report stage and run status."
12617
+ " audienti tools linkedin-review status Show the current report stage and run status.",
12618
+ " audienti tools linkedin-strategy-review delete Delete one LinkedIn strategy review report."
11413
12619
  ].join("\n")],
11414
12620
 
11415
12621
  ["tools list", [