@audienti/cli 0.1.15 → 0.1.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,26 @@ All notable changes to the Audienti CLI are documented here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.1.18] - 2026-07-17
8
+
9
+ ### Added
10
+
11
+ - Add ContentOps CLI commands for inspecting programs, reviewing plan rows, approving/scheduling/publishing work items, and handling comment tasks.
12
+ - Add `audienti motions update <motn_id> --own-post-engagement <true|false>` to enable or disable owned-post engagement intake for inbound motions.
13
+
14
+ ## [0.1.17] - 2026-07-17
15
+
16
+ ### Added
17
+
18
+ - Add `audienti dnc list/add/import/remove` for account DNC settings.
19
+ - Add `audienti company-rules list/create/update/remove/apply` for account-wide and user-scoped company disposition policies.
20
+
21
+ ## [0.1.16] - 2026-07-17
22
+
23
+ ### Added
24
+
25
+ - Add direct prospect emergency state commands: `audienti prospects set-status`, `audienti prospects lock`, and `audienti prospects unlock`.
26
+
7
27
  ## [0.1.15] - 2026-07-16
8
28
 
9
29
  ### Added
package/README.md CHANGED
@@ -63,13 +63,22 @@ audienti writer test-run <prsp_id>
63
63
  audienti motions analytics <motn_id>
64
64
  audienti motions run-discovery <motn_id>
65
65
  audienti motions update <motn_id> --status paused
66
+ audienti motions update <motn_id> --own-post-engagement true
66
67
  audienti motions activate <motn_id>
67
68
  audienti motions delete <motn_id> --confirm yes
69
+ audienti content programs
70
+ audienti content plan <cprg_id>
71
+ audienti content approve <cpwi_id>
72
+ audienti content comments
68
73
  audienti prospects show <prsp_id> --json
69
74
  audienti prospects list --profiles
70
75
  audienti prospects list --assigned-user unassigned
71
76
  audienti prospects check --all --csv
72
77
  audienti prospects assign <prsp_id> --assigned-user me
78
+ audienti prospects set-status <prsp_id> --status not_fit
79
+ audienti prospects lock <prsp_id> --note "Emergency hold"
80
+ audienti dnc list
81
+ audienti company-rules list
73
82
  audienti users activity --window 7d
74
83
  audienti analytics prospects --window 24h
75
84
  audienti analytics dashboard --play-tag wine_campaign
@@ -170,6 +179,25 @@ audienti prospects assign <prsp_id> --assigned-user <account_user_id|me>
170
179
  audienti prospects assign <prsp_id> --assigned-user unassign
171
180
  ```
172
181
 
182
+ To make emergency prospect state changes without going through a motion:
183
+
184
+ ```bash
185
+ audienti prospects set-status <prsp_id> --status nurture
186
+ audienti prospects set-status <prsp_id> --status not_fit
187
+ audienti prospects lock <prsp_id> --note "Emergency hold"
188
+ audienti prospects unlock <prsp_id>
189
+ ```
190
+
191
+ To manage account DNC and company disposition rules from the CLI:
192
+
193
+ ```bash
194
+ audienti dnc add prospect@example.com
195
+ audienti dnc import --file dnc.txt
196
+ audienti company-rules create --linkedin-url https://www.linkedin.com/company/example --disposition monitor
197
+ audienti company-rules create --domain example.com --disposition not_fit --user me
198
+ audienti company-rules apply --all
199
+ ```
200
+
173
201
  To import multiple LinkedIn people through the same per-prospect import path:
174
202
 
175
203
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@audienti/cli",
3
- "version": "0.1.15",
3
+ "version": "0.1.18",
4
4
  "description": "Agent-first command-line client for Audienti.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -53,6 +53,9 @@ audienti help agent-workflows
53
53
  audienti prospects list --query "name or company" --wide --json
54
54
  audienti prospects list --assigned-user unassigned --json
55
55
  audienti prospects assign <prsp_id> --assigned-user me --json
56
+ audienti prospects set-status <prsp_id> --status not_fit --json
57
+ audienti prospects lock <prsp_id> --note "Emergency hold" --json
58
+ audienti prospects unlock <prsp_id> --json
56
59
  audienti users activity me --window 7d --json
57
60
  audienti prospects import-batch --file prospects.csv --motion <motn_id> --assigned-user me --json
58
61
  audienti lists create --name "Target list" --json
package/src/api-client.js CHANGED
@@ -115,6 +115,66 @@ export class AudientiClient {
115
115
  return this.requestJson(accountPath(accountId, ["companies"], query));
116
116
  }
117
117
 
118
+ dncEntries(accountId, query = {}) {
119
+ return this.requestJson(accountPath(accountId, ["dnc"], query));
120
+ }
121
+
122
+ createDncEntry(accountId, body) {
123
+ return this.requestJson(accountPath(accountId, ["dnc"]), {
124
+ method: "POST",
125
+ body
126
+ });
127
+ }
128
+
129
+ importDncEntries(accountId, body) {
130
+ return this.requestJson(accountPath(accountId, ["dnc", "import"]), {
131
+ method: "POST",
132
+ body
133
+ });
134
+ }
135
+
136
+ deleteDncEntry(accountId, entryId) {
137
+ return this.requestJson(accountPath(accountId, ["dnc", entryId]), {
138
+ method: "DELETE"
139
+ });
140
+ }
141
+
142
+ companyRules(accountId) {
143
+ return this.requestJson(accountPath(accountId, ["company_rules"]));
144
+ }
145
+
146
+ createCompanyRule(accountId, body) {
147
+ return this.requestJson(accountPath(accountId, ["company_rules"]), {
148
+ method: "POST",
149
+ body
150
+ });
151
+ }
152
+
153
+ updateCompanyRule(accountId, ruleId, body) {
154
+ return this.requestJson(accountPath(accountId, ["company_rules", ruleId]), {
155
+ method: "PATCH",
156
+ body
157
+ });
158
+ }
159
+
160
+ deleteCompanyRule(accountId, ruleId) {
161
+ return this.requestJson(accountPath(accountId, ["company_rules", ruleId]), {
162
+ method: "DELETE"
163
+ });
164
+ }
165
+
166
+ applyCompanyRule(accountId, ruleId) {
167
+ return this.requestJson(accountPath(accountId, ["company_rules", ruleId, "apply"]), {
168
+ method: "POST"
169
+ });
170
+ }
171
+
172
+ applyAllCompanyRules(accountId) {
173
+ return this.requestJson(accountPath(accountId, ["company_rules", "apply_all"]), {
174
+ method: "POST"
175
+ });
176
+ }
177
+
118
178
  tags(accountId) {
119
179
  return this.requestJson(accountPath(accountId, ["tags"]));
120
180
  }
@@ -201,6 +261,46 @@ export class AudientiClient {
201
261
  });
202
262
  }
203
263
 
264
+ contentPrograms(accountId, query = {}) {
265
+ return this.requestJson(accountPath(accountId, ["content_ops", "programs"], query));
266
+ }
267
+
268
+ contentPlan(accountId, programId, query = {}) {
269
+ return this.requestJson(accountPath(accountId, ["content_ops", "programs", programId, "plan"], query));
270
+ }
271
+
272
+ contentWorkItem(accountId, workItemId) {
273
+ return this.requestJson(accountPath(accountId, ["content_ops", "work_items", workItemId]));
274
+ }
275
+
276
+ contentFeedback(accountId, workItemId, body) {
277
+ return this.requestJson(accountPath(accountId, ["content_ops", "work_items", workItemId, "feedback"]), { method: "POST", body });
278
+ }
279
+
280
+ contentApprove(accountId, workItemId) {
281
+ return this.requestJson(accountPath(accountId, ["content_ops", "work_items", workItemId, "approve"]), { method: "POST" });
282
+ }
283
+
284
+ contentSchedule(accountId, workItemId, body) {
285
+ return this.requestJson(accountPath(accountId, ["content_ops", "work_items", workItemId, "schedule"]), { method: "POST", body });
286
+ }
287
+
288
+ contentPublish(accountId, workItemId, body) {
289
+ return this.requestJson(accountPath(accountId, ["content_ops", "work_items", workItemId, "publish"]), { method: "POST", body });
290
+ }
291
+
292
+ contentComments(accountId, query = {}) {
293
+ return this.requestJson(accountPath(accountId, ["content_ops", "comment_tasks"], query));
294
+ }
295
+
296
+ contentReply(accountId, commentTaskId, body) {
297
+ return this.requestJson(accountPath(accountId, ["content_ops", "comment_tasks", commentTaskId, "send_reply"]), { method: "POST", body });
298
+ }
299
+
300
+ contentDismiss(accountId, commentTaskId) {
301
+ return this.requestJson(accountPath(accountId, ["content_ops", "comment_tasks", commentTaskId, "dismiss"]), { method: "POST" });
302
+ }
303
+
204
304
  addMotionTag(accountId, motionId, body) {
205
305
  return this.requestJson(accountPath(accountId, ["motions", motionId, "add_tag"]), {
206
306
  method: "POST",
@@ -306,6 +406,19 @@ export class AudientiClient {
306
406
  });
307
407
  }
308
408
 
409
+ lockProspect(accountId, prospectId, body = {}) {
410
+ return this.requestJson(accountPath(accountId, ["prospects", prospectId, "lock"]), {
411
+ method: "POST",
412
+ body
413
+ });
414
+ }
415
+
416
+ unlockProspect(accountId, prospectId) {
417
+ return this.requestJson(accountPath(accountId, ["prospects", prospectId, "unlock"]), {
418
+ method: "POST"
419
+ });
420
+ }
421
+
309
422
  prospectSequencePreview(accountId, prospectId, body = {}) {
310
423
  return this.requestJson(accountPath(accountId, ["prospects", prospectId, "sequence_preview"]), {
311
424
  method: "POST",
package/src/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { parseArgs } from "node:util";
2
2
  import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
3
- import { dirname, join } from "node:path";
3
+ import { basename, dirname, join } from "node:path";
4
4
  import { ApiError, AudientiClient, DEFAULT_HOST, normalizeHost } from "./api-client.js";
5
5
  import { configPath, deleteConfig, maskToken, readConfig, writeConfig } from "./config.js";
6
6
 
@@ -28,23 +28,44 @@ const DEFAULT_PROFILE_IDENTIFIERS = [
28
28
  ];
29
29
  const DELETE_CONFIRMATION_VALUES = new Set(["yes", "true", "y"]);
30
30
  const MOTION_STATUS_VALUES = new Set(["draft", "preparing", "active", "paused", "archived"]);
31
+ const PROSPECT_STATUS_VALUES = new Set(["active", "nurture", "non_responsive", "not_fit", "rejected"]);
31
32
  const PROSPECTS_ADD_NOTE_USAGE = "Usage: audienti prospects add-note <prsp_id> (--message <text> [--type <note|steer|voicemail_outreach|video_outreach>] [--engagement-type <key>] | --payload <file.json>) [--json] [--account <acct_id>]";
32
33
  const PROSPECTS_ADD_STEER_USAGE = "Usage: audienti prospects add-steer <prsp_id> (--message <text> [--engagement-type <key>] | --payload <file.json>) [--json] [--account <acct_id>]";
33
34
  const PROSPECTS_ADD_PROFILE_USAGE = "Usage: audienti prospects add-profile <prsp_id> --url <profile_url|email|phone> [--json] [--account <acct_id>]";
34
35
  const PROSPECTS_REPORT_BAD_PROFILE_USAGE = "Usage: audienti prospects report-bad-profile <prsp_id> <prof_id|citation_id> [--json] [--account <acct_id>]";
35
36
  const PROSPECTS_ASSIGN_USAGE = "Usage: audienti prospects assign <prsp_id> [prsp_id...] --assigned-user <id|me|unassign> [--json] [--account <acct_id>]";
37
+ const PROSPECTS_SET_STATUS_USAGE = "Usage: audienti prospects set-status <prsp_id> --status <active|nurture|non_responsive|not_fit|rejected> [--json] [--account <acct_id>]";
36
38
  const PROSPECTS_REJECT_USAGE = "Usage: audienti prospects reject <prsp_id> [--json] [--account <acct_id>]";
37
39
  const PROSPECTS_NURTURE_USAGE = "Usage: audienti prospects nurture <prsp_id> [--reason <nurture|non_responsive|not_fit>] [--json] [--account <acct_id>]";
38
40
  const PROSPECTS_RESTORE_USAGE = "Usage: audienti prospects restore <prsp_id> [--json] [--account <acct_id>]";
41
+ const PROSPECTS_LOCK_USAGE = "Usage: audienti prospects lock <prsp_id> [--note <text>] [--kind <protected_relationship|company_policy>] [--json] [--account <acct_id>]";
42
+ const PROSPECTS_UNLOCK_USAGE = "Usage: audienti prospects unlock <prsp_id> [--json] [--account <acct_id>]";
39
43
  const PROSPECTS_CHECK_USAGE = "Usage: audienti prospects check [--json|--csv] [filters] [--account <acct_id>]";
40
44
  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>]";
45
+ const DNC_ADD_USAGE = "Usage: audienti dnc add <email|citation_id|profile_url> [--json] [--account <acct_id>]";
46
+ const DNC_IMPORT_USAGE = "Usage: audienti dnc import --file <txt|csv> [--json] [--account <acct_id>]";
47
+ const DNC_REMOVE_USAGE = "Usage: audienti dnc remove <dnc_entry_id> [--json] [--account <acct_id>]";
48
+ const COMPANY_RULES_CREATE_USAGE = "Usage: audienti company-rules create (--linkedin-url <url> | --domain <domain>) --disposition <monitor|nurture|not_fit|reject> [--name <text>] [--user <account_user_id|email|me>] [--note <text>] [--json] [--account <acct_id>]";
49
+ const COMPANY_RULES_UPDATE_USAGE = "Usage: audienti company-rules update <rule_id> [--linkedin-url <url>] [--domain <domain>] [--disposition <monitor|nurture|not_fit|reject>] [--name <text>] [--user <account_user_id|email|me|none>] [--note <text>] [--json] [--account <acct_id>]";
50
+ const COMPANY_RULES_REMOVE_USAGE = "Usage: audienti company-rules remove <rule_id> [--json] [--account <acct_id>]";
51
+ const COMPANY_RULES_APPLY_USAGE = "Usage: audienti company-rules apply (<rule_id>|--all) [--json] [--account <acct_id>]";
41
52
  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
53
  const OFFERS_SHOW_USAGE = "Usage: audienti offers show <offr_id> [--json] [--account <acct_id>]";
43
54
  const OFFERS_UPDATE_USAGE = "Usage: audienti offers update <offr_id> [--name <text>] [--description <text>] [--url <url>] [--json] [--account <acct_id>]";
44
55
  const OFFERS_DELETE_USAGE = "Usage: audienti offers delete <offr_id> --confirm <yes|true|Y|y> [--json] [--account <acct_id>]";
45
56
  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>]";
46
57
  const MOTIONS_ANALYTICS_USAGE = "Usage: audienti motions analytics <motn_id> [--window 30d] [--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>]";
58
+ const MOTIONS_UPDATE_USAGE = "Usage: audienti motions update <motn_id> [--status <draft|preparing|active|paused|archived>] [--tags <tag[,tag...]>] [--own-post-engagement <true|false>] [--json] [--account <acct_id>]";
59
+ const CONTENT_PROGRAMS_USAGE = "Usage: audienti content programs [--user <account_user_id|email|name|me>] [--json] [--account <acct_id>]";
60
+ const CONTENT_PLAN_USAGE = "Usage: audienti content plan <cprg_id> [--week <n>] [--due] [--json] [--account <acct_id>]";
61
+ const CONTENT_SHOW_USAGE = "Usage: audienti content show <cpwi_id> [--json] [--account <acct_id>]";
62
+ const CONTENT_FEEDBACK_USAGE = "Usage: audienti content feedback <cpwi_id> (--message <text> | --payload <file.json>) [--json] [--account <acct_id>]";
63
+ const CONTENT_APPROVE_USAGE = "Usage: audienti content approve <cpwi_id> [--json] [--account <acct_id>]";
64
+ const CONTENT_SCHEDULE_USAGE = "Usage: audienti content schedule <cpwi_id> --at <time> [--json] [--account <acct_id>]";
65
+ const CONTENT_PUBLISH_USAGE = "Usage: audienti content publish <cpwi_id> --url <permalink> [--json] [--account <acct_id>]";
66
+ const CONTENT_COMMENTS_USAGE = "Usage: audienti content comments [--unresolved] [--user <account_user_id|email|name|me>] [--json] [--account <acct_id>]";
67
+ const CONTENT_REPLY_USAGE = "Usage: audienti content reply <cctk_id> [--body <text>] [--json] [--account <acct_id>]";
68
+ const CONTENT_DISMISS_USAGE = "Usage: audienti content dismiss <cctk_id> [--json] [--account <acct_id>]";
48
69
  const MOTIONS_ADD_TAG_USAGE = "Usage: audienti motions add-tag <motn_id> <tag> [--json] [--account <acct_id>]";
49
70
  const MOTIONS_REMOVE_TAG_USAGE = "Usage: audienti motions remove-tag <motn_id> <tag> [--json] [--account <acct_id>]";
50
71
  const MOTIONS_DELETE_USAGE = "Usage: audienti motions delete <motn_id> --confirm <yes|true|Y|y> [--json] [--account <acct_id>]";
@@ -157,6 +178,15 @@ async function dispatch(argv, context) {
157
178
  if (normalizedResource === "icps" && action === "add-tag") return icpsTagMutation("add", rest, context, { accountOverride });
158
179
  if (normalizedResource === "icps" && action === "remove-tag") return icpsTagMutation("remove", rest, context, { accountOverride });
159
180
  if (normalizedResource === "companies" && action === "search") return companiesSearch(rest, context, { accountOverride });
181
+ if (normalizedResource === "dnc" && action === "list") return dncList(rest, context, { accountOverride });
182
+ if (normalizedResource === "dnc" && action === "add") return dncAdd(rest, context, { accountOverride });
183
+ if (normalizedResource === "dnc" && action === "import") return dncImport(rest, context, { accountOverride });
184
+ if (normalizedResource === "dnc" && ["remove", "delete"].includes(action)) return dncRemove(rest, context, { accountOverride });
185
+ if (normalizedResource === "company-rules" && action === "list") return companyRulesList(rest, context, { accountOverride });
186
+ if (normalizedResource === "company-rules" && action === "create") return companyRulesCreate(rest, context, { accountOverride });
187
+ if (normalizedResource === "company-rules" && action === "update") return companyRulesUpdate(rest, context, { accountOverride });
188
+ if (normalizedResource === "company-rules" && ["remove", "delete"].includes(action)) return companyRulesRemove(rest, context, { accountOverride });
189
+ if (normalizedResource === "company-rules" && action === "apply") return companyRulesApply(rest, context, { accountOverride });
160
190
  if (normalizedResource === "tags" && action === "list") return tagsList(rest, context, { accountOverride });
161
191
  if (normalizedResource === "tags" && action === "show") return tagsShow(rest, context, { accountOverride });
162
192
  if (normalizedResource === "lists" && action === "list") return listsList(rest, context, { accountOverride });
@@ -184,13 +214,26 @@ async function dispatch(argv, context) {
184
214
  if (normalizedResource === "motions" && action === "clone") return motionsClone(rest, context, { accountOverride });
185
215
  if (normalizedResource === "motions" && action === "move-prospects") return motionsMoveProspects(rest, context, { accountOverride });
186
216
  if (normalizedResource === "motions" && action === "run-discovery") return motionsRunDiscovery(rest, context, { accountOverride });
217
+ if (normalizedResource === "content" && action === "programs") return contentPrograms(rest, context, { accountOverride });
218
+ if (normalizedResource === "content" && action === "plan") return contentPlan(rest, context, { accountOverride });
219
+ if (normalizedResource === "content" && action === "show") return contentShow(rest, context, { accountOverride });
220
+ if (normalizedResource === "content" && action === "feedback") return contentFeedback(rest, context, { accountOverride });
221
+ if (normalizedResource === "content" && action === "approve") return contentApprove(rest, context, { accountOverride });
222
+ if (normalizedResource === "content" && action === "schedule") return contentSchedule(rest, context, { accountOverride });
223
+ if (normalizedResource === "content" && action === "publish") return contentPublish(rest, context, { accountOverride });
224
+ if (normalizedResource === "content" && action === "comments") return contentComments(rest, context, { accountOverride });
225
+ if (normalizedResource === "content" && action === "reply") return contentReply(rest, context, { accountOverride });
226
+ if (normalizedResource === "content" && action === "dismiss") return contentDismiss(rest, context, { accountOverride });
187
227
  if (normalizedResource === "prospects" && action === "list") return prospectsList(rest, context, { accountOverride });
188
228
  if (normalizedResource === "prospects" && action === "check") return prospectsCheck(rest, context, { accountOverride });
189
229
  if (normalizedResource === "prospects" && action === "show") return prospectsShow(rest, context, { accountOverride });
190
230
  if (normalizedResource === "prospects" && action === "assign") return prospectsAssign(rest, context, { accountOverride });
231
+ if (normalizedResource === "prospects" && action === "set-status") return prospectsSetStatus(rest, context, { accountOverride });
191
232
  if (normalizedResource === "prospects" && action === "reject") return prospectsDisposition("reject", rest, context, { accountOverride });
192
233
  if (normalizedResource === "prospects" && action === "nurture") return prospectsDisposition("nurture", rest, context, { accountOverride });
193
234
  if (normalizedResource === "prospects" && action === "restore") return prospectsDisposition("restore", rest, context, { accountOverride });
235
+ if (normalizedResource === "prospects" && action === "lock") return prospectsLock(rest, context, { accountOverride });
236
+ if (normalizedResource === "prospects" && action === "unlock") return prospectsUnlock(rest, context, { accountOverride });
194
237
  if (normalizedResource === "prospects" && action === "timeline") return prospectsTimeline(rest, context, { accountOverride });
195
238
  if (normalizedResource === "prospects" && action === "message-types") return prospectsMessageTypes(rest, context, { accountOverride });
196
239
  if (normalizedResource === "prospects" && action === "write") return prospectsWrite(rest, context, { accountOverride });
@@ -263,6 +306,7 @@ function normalizeTopicParts(parts) {
263
306
  function normalizeResource(resource) {
264
307
  if (resource === "principals") return "users";
265
308
  if (resource === "writers") return "writer";
309
+ if (resource === "company_rules" || resource === "company-rules") return "company-rules";
266
310
  return resource === "plays" ? "motions" : resource;
267
311
  }
268
312
 
@@ -813,6 +857,134 @@ async function companiesSearch(args, context, { accountOverride } = {}) {
813
857
  renderCompanies(payload, context);
814
858
  }
815
859
 
860
+ async function dncList(args, context, { accountOverride } = {}) {
861
+ const { values, positionals } = parseCommandArgs(args, {
862
+ ...jsonOptions(),
863
+ limit: { type: "string" },
864
+ offset: { type: "string" }
865
+ });
866
+ if (positionals.length > 0) throw new CommandError("Usage: audienti dnc list [--limit <n>] [--offset <n>] [--json] [--account <acct_id>]");
867
+
868
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
869
+ const payload = await client.dncEntries(accountId, compactObject({ limit: values.limit, offset: values.offset }));
870
+ if (values.json) return writeJson(context.stdout, payload);
871
+
872
+ renderDncEntries(payload, context);
873
+ }
874
+
875
+ async function dncAdd(args, context, { accountOverride } = {}) {
876
+ const { values, positionals } = parseCommandArgs(args, jsonOptions());
877
+ if (positionals.length !== 1) throw new CommandError(DNC_ADD_USAGE);
878
+
879
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
880
+ const payload = await client.createDncEntry(accountId, { value: positionals[0] });
881
+ if (values.json) return writeJson(context.stdout, payload);
882
+
883
+ writeLine(context.stdout, `DNC entry ${display(payload?.status, "created")}: ${display(payload?.dnc_entry?.canonical_value || payload?.dnc_entry?.citation_id)} (${display(payload?.dnc_entry?.prefix_id || payload?.dnc_entry?.id)}).`);
884
+ }
885
+
886
+ async function dncImport(args, context, { accountOverride } = {}) {
887
+ const { values, positionals } = parseCommandArgs(args, {
888
+ ...jsonOptions(),
889
+ file: { type: "string" }
890
+ });
891
+ if (positionals.length > 0 || !values.file) throw new CommandError(DNC_IMPORT_USAGE);
892
+
893
+ const body = await readFile(values.file, "utf8");
894
+ const importValues = parseDncImportValues(body);
895
+ if (importValues.length === 0) throw new CommandError("DNC import file did not contain any values.");
896
+
897
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
898
+ const payload = await client.importDncEntries(accountId, { values: importValues, filename: basename(values.file) });
899
+ if (values.json) return writeJson(context.stdout, payload);
900
+
901
+ writeLine(context.stdout, `Imported DNC entries. Accepted ${display(payload?.accepted_count, 0)}, skipped ${display(payload?.skipped_count, 0)}, invalid ${display(payload?.invalid_count, 0)}, matched ${display(payload?.matched_prospect_count, 0)}.`);
902
+ }
903
+
904
+ async function dncRemove(args, context, { accountOverride } = {}) {
905
+ const { values, positionals } = parseCommandArgs(args, jsonOptions());
906
+ if (positionals.length !== 1) throw new CommandError(DNC_REMOVE_USAGE);
907
+
908
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
909
+ const payload = await client.deleteDncEntry(accountId, positionals[0]);
910
+ if (values.json) return writeJson(context.stdout, payload);
911
+
912
+ writeLine(context.stdout, `Removed DNC entry ${display(payload?.prefix_id || payload?.id || positionals[0])}.`);
913
+ }
914
+
915
+ async function companyRulesList(args, context, { accountOverride } = {}) {
916
+ const { values, positionals } = parseCommandArgs(args, jsonOptions());
917
+ if (positionals.length > 0) throw new CommandError("Usage: audienti company-rules list [--json] [--account <acct_id>]");
918
+
919
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
920
+ const payload = await client.companyRules(accountId);
921
+ if (values.json) return writeJson(context.stdout, payload);
922
+
923
+ renderCompanyRules(payload, context);
924
+ }
925
+
926
+ async function companyRulesCreate(args, context, { accountOverride } = {}) {
927
+ const { values, positionals } = parseCommandArgs(args, companyRuleOptions());
928
+ if (positionals.length > 0 || (!values["linkedin-url"] && !values.domain) || !values.disposition) {
929
+ throw new CommandError(COMPANY_RULES_CREATE_USAGE);
930
+ }
931
+
932
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
933
+ const payload = await client.createCompanyRule(accountId, { company_rule: companyRulePayload(values) });
934
+ if (values.json) return writeJson(context.stdout, payload);
935
+
936
+ const rule = payload?.company_rule;
937
+ writeLine(context.stdout, `Created company rule ${display(rule?.name || rule?.domain || rule?.linkedin_company_identifier)} (${display(rule?.prefix_id || rule?.id)}).`);
938
+ writeLine(context.stdout, `Disposition: ${display(rule?.disposition)} | Scope: ${companyRuleScopeLabel(rule)}`);
939
+ }
940
+
941
+ async function companyRulesUpdate(args, context, { accountOverride } = {}) {
942
+ const { values, positionals } = parseCommandArgs(args, companyRuleOptions());
943
+ const hasUpdate = values.disposition || values.name !== undefined || values["linkedin-url"] !== undefined || values.domain !== undefined || values.user !== undefined || values.note !== undefined;
944
+ if (positionals.length !== 1 || !hasUpdate) throw new CommandError(COMPANY_RULES_UPDATE_USAGE);
945
+
946
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
947
+ const payload = await client.updateCompanyRule(accountId, positionals[0], { company_rule: companyRulePayload(values) });
948
+ if (values.json) return writeJson(context.stdout, payload);
949
+
950
+ const rule = payload?.company_rule;
951
+ writeLine(context.stdout, `Updated company rule ${display(rule?.name || rule?.domain || rule?.linkedin_company_identifier)} (${display(rule?.prefix_id || rule?.id)}).`);
952
+ writeLine(context.stdout, `Disposition: ${display(rule?.disposition)} | Scope: ${companyRuleScopeLabel(rule)}`);
953
+ }
954
+
955
+ async function companyRulesRemove(args, context, { accountOverride } = {}) {
956
+ const { values, positionals } = parseCommandArgs(args, jsonOptions());
957
+ if (positionals.length !== 1) throw new CommandError(COMPANY_RULES_REMOVE_USAGE);
958
+
959
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
960
+ const payload = await client.deleteCompanyRule(accountId, positionals[0]);
961
+ if (values.json) return writeJson(context.stdout, payload);
962
+
963
+ writeLine(context.stdout, `Removed company rule ${display(payload?.company_rule?.prefix_id || payload?.company_rule?.id || positionals[0])}.`);
964
+ }
965
+
966
+ async function companyRulesApply(args, context, { accountOverride } = {}) {
967
+ const { values, positionals } = parseCommandArgs(args, {
968
+ ...jsonOptions(),
969
+ all: { type: "boolean" }
970
+ });
971
+ if ((values.all && positionals.length > 0) || (!values.all && positionals.length !== 1)) {
972
+ throw new CommandError(COMPANY_RULES_APPLY_USAGE);
973
+ }
974
+
975
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
976
+ const payload = values.all ?
977
+ await client.applyAllCompanyRules(accountId) :
978
+ await client.applyCompanyRule(accountId, positionals[0]);
979
+ if (values.json) return writeJson(context.stdout, payload);
980
+
981
+ if (values.all) {
982
+ writeLine(context.stdout, `Applied company rules. Matched ${display(payload?.matched_count, 0)}, changed ${display(payload?.applied_count, 0)}.`);
983
+ } else {
984
+ writeLine(context.stdout, `Applied company rule. Matched ${display(payload?.matched_count, 0)}, changed ${display(payload?.applied_count, 0)}.`);
985
+ }
986
+ }
987
+
816
988
  async function tagsList(args, context, { accountOverride } = {}) {
817
989
  const { values, positionals } = parseCommandArgs(args, jsonOptions());
818
990
  if (positionals.length > 0) throw new CommandError("Usage: audienti tags list [--json] [--account <acct_id>]");
@@ -1208,9 +1380,10 @@ async function motionsUpdate(args, context, { accountOverride } = {}) {
1208
1380
  const { values, positionals } = parseCommandArgs(args, {
1209
1381
  ...jsonOptions(),
1210
1382
  status: { type: "string" },
1211
- tags: { type: "string" }
1383
+ tags: { type: "string" },
1384
+ "own-post-engagement": { type: "string" }
1212
1385
  });
1213
- const hasUpdateField = values.status || values.tags !== undefined;
1386
+ const hasUpdateField = values.status || values.tags !== undefined || values["own-post-engagement"] !== undefined;
1214
1387
  if (positionals.length !== 1 || !hasUpdateField) {
1215
1388
  throw new CommandError(MOTIONS_UPDATE_USAGE);
1216
1389
  }
@@ -1218,7 +1391,8 @@ async function motionsUpdate(args, context, { accountOverride } = {}) {
1218
1391
  const normalizedStatus = normalizeMotionStatus(values.status);
1219
1392
  const motionAttributes = compactObject({
1220
1393
  status: normalizedStatus,
1221
- play_tags: values.tags !== undefined ? tagList(values.tags) : undefined
1394
+ play_tags: values.tags !== undefined ? tagList(values.tags) : undefined,
1395
+ own_post_engagement: values["own-post-engagement"] !== undefined ? parseBooleanString(values["own-post-engagement"], "--own-post-engagement") : undefined
1222
1396
  });
1223
1397
 
1224
1398
  const { client, accountId } = await requireAccountContext(context, { accountOverride });
@@ -1259,6 +1433,149 @@ async function updateMotionStatus(motionId, status, context, { accountOverride,
1259
1433
  renderMotion(motion, context);
1260
1434
  }
1261
1435
 
1436
+ async function contentPrograms(args, context, { accountOverride } = {}) {
1437
+ const { values, positionals } = parseCommandArgs(args, {
1438
+ ...jsonOptions(),
1439
+ user: { type: "string" }
1440
+ });
1441
+ if (positionals.length > 0) throw new CommandError(CONTENT_PROGRAMS_USAGE);
1442
+
1443
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1444
+ const programs = await client.contentPrograms(accountId, compactObject({ user: values.user }));
1445
+ if (values.json) return writeJson(context.stdout, programs);
1446
+
1447
+ renderContentPrograms(programs, context);
1448
+ }
1449
+
1450
+ async function contentPlan(args, context, { accountOverride } = {}) {
1451
+ const { values, positionals } = parseCommandArgs(args, {
1452
+ ...jsonOptions(),
1453
+ week: { type: "string" },
1454
+ due: { type: "boolean" }
1455
+ });
1456
+ if (positionals.length !== 1) throw new CommandError(CONTENT_PLAN_USAGE);
1457
+
1458
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1459
+ const payload = await client.contentPlan(accountId, positionals[0]);
1460
+ if (values.json) return writeJson(context.stdout, payload);
1461
+
1462
+ const week = values.week ? Number.parseInt(values.week, 10) : null;
1463
+ let rows = Array.isArray(payload?.rows) ? payload.rows : [];
1464
+ if (week) rows = rows.filter((row) => Number(row.week_number) === week);
1465
+ if (values.due) rows = rows.filter((row) => ["researching", "drafting", "needs_operator_review", "needs_operator_approval", "scheduled", "ready_to_post"].includes(row.stage));
1466
+ renderContentPlanRows(rows, context);
1467
+ }
1468
+
1469
+ async function contentShow(args, context, { accountOverride } = {}) {
1470
+ const { values, positionals } = parseCommandArgs(args, jsonOptions());
1471
+ if (positionals.length !== 1) throw new CommandError(CONTENT_SHOW_USAGE);
1472
+
1473
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1474
+ const item = await client.contentWorkItem(accountId, positionals[0]);
1475
+ if (values.json) return writeJson(context.stdout, item);
1476
+
1477
+ renderContentWorkItem(item, context);
1478
+ }
1479
+
1480
+ async function contentFeedback(args, context, { accountOverride } = {}) {
1481
+ const { values, positionals } = parseCommandArgs(args, {
1482
+ ...jsonOptions(),
1483
+ message: { type: "string" },
1484
+ payload: { type: "string" }
1485
+ });
1486
+ if (positionals.length !== 1 || (!values.message && !values.payload)) throw new CommandError(CONTENT_FEEDBACK_USAGE);
1487
+
1488
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1489
+ const payload = values.payload ? await readJsonPayload(values.payload) : { draft_feedback: values.message };
1490
+ const item = await client.contentFeedback(accountId, positionals[0], payload);
1491
+ if (values.json) return writeJson(context.stdout, item);
1492
+
1493
+ writeLine(context.stdout, `Queued feedback for ${display(item?.prefix_id)}.`);
1494
+ renderContentWorkItem(item, context);
1495
+ }
1496
+
1497
+ async function contentApprove(args, context, { accountOverride } = {}) {
1498
+ const { values, positionals } = parseCommandArgs(args, jsonOptions());
1499
+ if (positionals.length !== 1) throw new CommandError(CONTENT_APPROVE_USAGE);
1500
+
1501
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1502
+ const item = await client.contentApprove(accountId, positionals[0]);
1503
+ if (values.json) return writeJson(context.stdout, item);
1504
+
1505
+ writeLine(context.stdout, `Approved ${display(item?.prefix_id)}.`);
1506
+ renderContentWorkItem(item, context);
1507
+ }
1508
+
1509
+ async function contentSchedule(args, context, { accountOverride } = {}) {
1510
+ const { values, positionals } = parseCommandArgs(args, {
1511
+ ...jsonOptions(),
1512
+ at: { type: "string" }
1513
+ });
1514
+ if (positionals.length !== 1 || !values.at) throw new CommandError(CONTENT_SCHEDULE_USAGE);
1515
+
1516
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1517
+ const item = await client.contentSchedule(accountId, positionals[0], { scheduled_at: values.at });
1518
+ if (values.json) return writeJson(context.stdout, item);
1519
+
1520
+ writeLine(context.stdout, `Scheduled ${display(item?.prefix_id)}.`);
1521
+ renderContentWorkItem(item, context);
1522
+ }
1523
+
1524
+ async function contentPublish(args, context, { accountOverride } = {}) {
1525
+ const { values, positionals } = parseCommandArgs(args, {
1526
+ ...jsonOptions(),
1527
+ url: { type: "string" }
1528
+ });
1529
+ if (positionals.length !== 1 || !values.url) throw new CommandError(CONTENT_PUBLISH_USAGE);
1530
+
1531
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1532
+ const item = await client.contentPublish(accountId, positionals[0], { permalink: values.url });
1533
+ if (values.json) return writeJson(context.stdout, item);
1534
+
1535
+ writeLine(context.stdout, `Published ${display(item?.prefix_id)}.`);
1536
+ renderContentWorkItem(item, context);
1537
+ }
1538
+
1539
+ async function contentComments(args, context, { accountOverride } = {}) {
1540
+ const { values, positionals } = parseCommandArgs(args, {
1541
+ ...jsonOptions(),
1542
+ unresolved: { type: "boolean" },
1543
+ user: { type: "string" }
1544
+ });
1545
+ if (positionals.length > 0) throw new CommandError(CONTENT_COMMENTS_USAGE);
1546
+
1547
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1548
+ const comments = await client.contentComments(accountId, compactObject({ unresolved: values.unresolved === false ? false : true, user: values.user }));
1549
+ if (values.json) return writeJson(context.stdout, comments);
1550
+
1551
+ renderContentComments(comments, context);
1552
+ }
1553
+
1554
+ async function contentReply(args, context, { accountOverride } = {}) {
1555
+ const { values, positionals } = parseCommandArgs(args, {
1556
+ ...jsonOptions(),
1557
+ body: { type: "string" }
1558
+ });
1559
+ if (positionals.length !== 1) throw new CommandError(CONTENT_REPLY_USAGE);
1560
+
1561
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1562
+ const comment = await client.contentReply(accountId, positionals[0], compactObject({ body: values.body }));
1563
+ if (values.json) return writeJson(context.stdout, comment);
1564
+
1565
+ writeLine(context.stdout, `Sent reply for ${display(comment?.prefix_id)}.`);
1566
+ }
1567
+
1568
+ async function contentDismiss(args, context, { accountOverride } = {}) {
1569
+ const { values, positionals } = parseCommandArgs(args, jsonOptions());
1570
+ if (positionals.length !== 1) throw new CommandError(CONTENT_DISMISS_USAGE);
1571
+
1572
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1573
+ const comment = await client.contentDismiss(accountId, positionals[0]);
1574
+ if (values.json) return writeJson(context.stdout, comment);
1575
+
1576
+ writeLine(context.stdout, `Dismissed ${display(comment?.prefix_id)}.`);
1577
+ }
1578
+
1262
1579
  function normalizeMotionStatus(status) {
1263
1580
  if (status === undefined) return undefined;
1264
1581
 
@@ -1270,6 +1587,14 @@ function normalizeMotionStatus(status) {
1270
1587
  return normalizedStatus;
1271
1588
  }
1272
1589
 
1590
+ function parseBooleanString(value, flagName) {
1591
+ const normalized = String(value || "").trim().toLowerCase();
1592
+ if (["true", "1", "yes", "y"].includes(normalized)) return true;
1593
+ if (["false", "0", "no", "n"].includes(normalized)) return false;
1594
+
1595
+ throw new CommandError(`${flagName} must be true or false.`);
1596
+ }
1597
+
1273
1598
  async function motionsTagMutation(action, args, context, { accountOverride } = {}) {
1274
1599
  const usageText = action === "add" ? MOTIONS_ADD_TAG_USAGE : MOTIONS_REMOVE_TAG_USAGE;
1275
1600
  const { values, positionals } = parseCommandArgs(args, jsonOptions());
@@ -1489,6 +1814,59 @@ async function prospectsDisposition(action, args, context, { accountOverride } =
1489
1814
  renderProspectDisposition(payload, context, { action });
1490
1815
  }
1491
1816
 
1817
+ async function prospectsSetStatus(args, context, { accountOverride } = {}) {
1818
+ const { values, positionals } = parseCommandArgs(args, {
1819
+ ...jsonOptions(),
1820
+ status: { type: "string" }
1821
+ });
1822
+ const status = String(values.status || "").trim();
1823
+ if (positionals.length !== 1 || !PROSPECT_STATUS_VALUES.has(status)) {
1824
+ throw new CommandError(PROSPECTS_SET_STATUS_USAGE);
1825
+ }
1826
+
1827
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1828
+ const payload = await mutateProspectStatus(client, accountId, positionals[0], status);
1829
+ if (values.json) return writeJson(context.stdout, payload);
1830
+
1831
+ renderProspectDisposition(payload, context, { action: "set-status" });
1832
+ }
1833
+
1834
+ async function prospectsLock(args, context, { accountOverride } = {}) {
1835
+ const { values, positionals } = parseCommandArgs(args, {
1836
+ ...jsonOptions(),
1837
+ kind: { type: "string" },
1838
+ note: { type: "string" }
1839
+ });
1840
+ if (positionals.length !== 1) throw new CommandError(PROSPECTS_LOCK_USAGE);
1841
+
1842
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1843
+ const payload = await client.lockProspect(accountId, positionals[0], compactObject({
1844
+ lock_kind: values.kind,
1845
+ lock_note: values.note
1846
+ }));
1847
+ if (values.json) return writeJson(context.stdout, payload);
1848
+
1849
+ renderProspectDisposition(payload, context, { action: "lock" });
1850
+ }
1851
+
1852
+ async function prospectsUnlock(args, context, { accountOverride } = {}) {
1853
+ const { values, positionals } = parseCommandArgs(args, jsonOptions());
1854
+ if (positionals.length !== 1) throw new CommandError(PROSPECTS_UNLOCK_USAGE);
1855
+
1856
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1857
+ const payload = await client.unlockProspect(accountId, positionals[0]);
1858
+ if (values.json) return writeJson(context.stdout, payload);
1859
+
1860
+ renderProspectDisposition(payload, context, { action: "unlock" });
1861
+ }
1862
+
1863
+ async function mutateProspectStatus(client, accountId, prospectId, status) {
1864
+ if (status === "active") return client.restoreProspect(accountId, prospectId);
1865
+ if (status === "rejected") return client.rejectProspect(accountId, prospectId);
1866
+
1867
+ return client.nurtureProspect(accountId, prospectId, { inactive_reason: status });
1868
+ }
1869
+
1492
1870
  async function prospectsTimeline(args, context, { accountOverride } = {}) {
1493
1871
  const { values, positionals } = parseCommandArgs(args, {
1494
1872
  ...jsonOptions(),
@@ -2599,6 +2977,43 @@ function tagList(value) {
2599
2977
  .filter((tag, index, tags) => tags.indexOf(tag) === index);
2600
2978
  }
2601
2979
 
2980
+ function companyRuleOptions() {
2981
+ return {
2982
+ ...jsonOptions(),
2983
+ name: { type: "string" },
2984
+ "linkedin-url": { type: "string" },
2985
+ domain: { type: "string" },
2986
+ disposition: { type: "string" },
2987
+ user: { type: "string" },
2988
+ note: { type: "string" }
2989
+ };
2990
+ }
2991
+
2992
+ function companyRulePayload(values) {
2993
+ const userValue = values.user === undefined ? undefined : String(values.user || "").trim();
2994
+ const userCleared = userValue && ["none", "account", "all"].includes(userValue.toLowerCase());
2995
+
2996
+ return compactObject({
2997
+ name: values.name,
2998
+ linkedin_company_url: values["linkedin-url"],
2999
+ domain: values.domain,
3000
+ disposition: values.disposition,
3001
+ note: values.note,
3002
+ scope_kind: userValue === undefined ? undefined : (userCleared ? "account" : "account_user"),
3003
+ account_user_id: userValue === undefined ? undefined : (userCleared ? "" : userValue)
3004
+ });
3005
+ }
3006
+
3007
+ function parseDncImportValues(body) {
3008
+ return String(body || "")
3009
+ .split(/\r?\n/)
3010
+ .map((line) => line.trim())
3011
+ .filter(Boolean)
3012
+ .map((line) => line.split(",")[0])
3013
+ .map((value) => value.trim().replace(/^"|"$/g, ""))
3014
+ .filter(Boolean);
3015
+ }
3016
+
2602
3017
  function filterRecordsByTag(records, value, field) {
2603
3018
  const tag = tagList(value)?.[0];
2604
3019
  if (!tag) return records;
@@ -3117,6 +3532,54 @@ function renderCompanies(payload, context) {
3117
3532
  }
3118
3533
  }
3119
3534
 
3535
+ function renderDncEntries(payload, context) {
3536
+ const entries = Array.isArray(payload?.dnc_entries) ? payload.dnc_entries : [];
3537
+ if (entries.length === 0) return writeLine(context.stdout, "No DNC entries found.");
3538
+
3539
+ writeLine(context.stdout, "DNC ID\tKIND\tVALUE\tIDENTIFIER\tSOURCE\tPROSPECT");
3540
+ for (const entry of entries) {
3541
+ writeLine(
3542
+ context.stdout,
3543
+ [
3544
+ display(entry.prefix_id || entry.id),
3545
+ display(entry.key_kind),
3546
+ display(entry.canonical_value || entry.citation_id),
3547
+ display(entry.identifier),
3548
+ display(entry.source_kind),
3549
+ display(entry.prospect_id)
3550
+ ].join("\t")
3551
+ );
3552
+ }
3553
+ }
3554
+
3555
+ function renderCompanyRules(payload, context) {
3556
+ const rules = Array.isArray(payload?.company_rules) ? payload.company_rules : [];
3557
+ if (rules.length === 0) return writeLine(context.stdout, "No company rules found.");
3558
+
3559
+ writeLine(context.stdout, "RULE ID\tCOMPANY\tDOMAIN\tDISPOSITION\tSCOPE\tACTIVE");
3560
+ for (const rule of rules) {
3561
+ writeLine(
3562
+ context.stdout,
3563
+ [
3564
+ display(rule.prefix_id || rule.id),
3565
+ display(rule.name || rule.linkedin_company_identifier || rule.linkedin_company_url),
3566
+ display(rule.domain),
3567
+ display(rule.disposition),
3568
+ companyRuleScopeLabel(rule),
3569
+ rule.active ? "yes" : "no"
3570
+ ].join("\t")
3571
+ );
3572
+ }
3573
+ }
3574
+
3575
+ function companyRuleScopeLabel(rule) {
3576
+ if (rule?.scope_kind === "account_user") {
3577
+ return `user:${display(rule.account_user_email || rule.account_user_id)}`;
3578
+ }
3579
+
3580
+ return "account";
3581
+ }
3582
+
3120
3583
  async function performBulkMutation(perform) {
3121
3584
  try {
3122
3585
  return { payload: await perform(), rejected: false };
@@ -3161,6 +3624,7 @@ function renderMotion(motion, context) {
3161
3624
  if (motion?.offer?.name) writeLine(context.stdout, `Offer: ${motion.offer.name} (${display(motion.offer.prefix_id)})`);
3162
3625
  if (motion?.icp?.name) writeLine(context.stdout, `ICP: ${motion.icp.name} (${display(motion.icp.prefix_id)})`);
3163
3626
  if (motion?.list?.name) writeLine(context.stdout, `List: ${motion.list.name} (${display(motion.list.prefix_id)})`);
3627
+ writeLine(context.stdout, `Own-post engagement: ${motion?.own_post_engagement ? "enabled" : "disabled"}`);
3164
3628
  if (Array.isArray(motion?.play_tags)) writeLine(context.stdout, `Tags: ${display(motion.play_tags.join(", "), "-")}`);
3165
3629
  if (motion?.principal_account_user?.id) {
3166
3630
  writeLine(
@@ -3170,6 +3634,59 @@ function renderMotion(motion, context) {
3170
3634
  }
3171
3635
  }
3172
3636
 
3637
+ function renderContentPrograms(programs, context) {
3638
+ if (!Array.isArray(programs) || programs.length === 0) return writeLine(context.stdout, "No ContentOps programs found.");
3639
+
3640
+ writeAlignedTable(context, ["PROGRAM ID", "OWNER", "STAGE", "BLOCKED", "CURRENT", "SCHEDULED", "STALE"], programs.map((program) => [
3641
+ display(program.prefix_id),
3642
+ display(program.owner?.name || program.owner?.email),
3643
+ display(program.stage),
3644
+ display(program.blocking_reason, "-"),
3645
+ display(program.current_piece?.prefix_id || program.current_piece?.piece_key, "-"),
3646
+ display(program.scheduled_piece?.prefix_id || program.scheduled_piece?.piece_key, "-"),
3647
+ program.plan_stale_motion_set ? "yes" : "no"
3648
+ ]));
3649
+ }
3650
+
3651
+ function renderContentPlanRows(rows, context) {
3652
+ if (!Array.isArray(rows) || rows.length === 0) return writeLine(context.stdout, "No ContentOps plan rows found.");
3653
+
3654
+ writeAlignedTable(context, ["DAY", "DATE", "MOTION", "STAGE", "STATUS", "TITLE"], rows.map((row) => [
3655
+ display(row.day_number),
3656
+ display(row.planned_publish_on, "-"),
3657
+ display(row.motion?.name || row.motion?.motion_prefix_id, "-"),
3658
+ display(row.stage, "-"),
3659
+ display(row.publish_status || row.workflow_phase, "-"),
3660
+ display(row.title)
3661
+ ]));
3662
+ }
3663
+
3664
+ function renderContentWorkItem(item, context) {
3665
+ writeLine(context.stdout, `Content item: ${display(item?.title)} (${display(item?.prefix_id)})`);
3666
+ writeLine(context.stdout, `Stage: ${display(item?.stage)}`);
3667
+ writeLine(context.stdout, `Blocking: ${display(item?.blocking_reason, "-")}`);
3668
+ if (item?.motion?.name || item?.motion?.motion_prefix_id) writeLine(context.stdout, `Motion: ${display(item.motion.name || item.motion.motion_prefix_id)}`);
3669
+ if (item?.scheduled_at) writeLine(context.stdout, `Scheduled: ${item.scheduled_at}`);
3670
+ if (item?.permalink) writeLine(context.stdout, `Permalink: ${item.permalink}`);
3671
+ if (item?.finalized_content) {
3672
+ writeLine(context.stdout, "");
3673
+ writeLine(context.stdout, item.finalized_content);
3674
+ }
3675
+ }
3676
+
3677
+ function renderContentComments(comments, context) {
3678
+ if (!Array.isArray(comments) || comments.length === 0) return writeLine(context.stdout, "No ContentOps comment tasks found.");
3679
+
3680
+ writeAlignedTable(context, ["TASK ID", "STATUS", "COMMENTER", "FIT", "OUTCOME", "COMMENT"], comments.map((comment) => [
3681
+ display(comment.prefix_id),
3682
+ display(comment.status),
3683
+ display(comment.commenter?.name),
3684
+ display(comment.fit_badge?.status || comment.fit_badge, "-"),
3685
+ display(comment.promotion_outcome, "-"),
3686
+ display(truncateCliText(comment.comment_body, 80))
3687
+ ]));
3688
+ }
3689
+
3173
3690
  function renderMotionStatus(status, context) {
3174
3691
  writeLine(context.stdout, `Motion: ${display(status?.name)} (${display(status?.prefix_id)})`);
3175
3692
  writeLine(context.stdout, `State: ${display(status?.state)}`);
@@ -3265,12 +3782,18 @@ function renderProspectDisposition(payload, context, { action }) {
3265
3782
  const actionLabel = {
3266
3783
  reject: "Rejected",
3267
3784
  nurture: "Moved to nurture",
3268
- restore: "Restored"
3785
+ restore: "Restored",
3786
+ "set-status": "Set status for",
3787
+ lock: "Locked",
3788
+ unlock: "Unlocked"
3269
3789
  }[action] || display(payload?.status, "Updated");
3270
3790
 
3271
3791
  writeLine(context.stdout, `${actionLabel} prospect ${display(prospect.display_name || prospect.name)} (${display(prospect.prefix_id)}).`);
3272
3792
  if (accountProspect.status) writeLine(context.stdout, `Status: ${accountProspect.status}`);
3273
3793
  if (accountProspect.inactive_reason) writeLine(context.stdout, `Inactive reason: ${accountProspect.inactive_reason}`);
3794
+ if (accountProspect.locked_at) writeLine(context.stdout, `Locked at: ${accountProspect.locked_at}`);
3795
+ if (accountProspect.lock_kind) writeLine(context.stdout, `Lock kind: ${accountProspect.lock_kind}`);
3796
+ if (accountProspect.lock_note) writeLine(context.stdout, `Lock note: ${accountProspect.lock_note}`);
3274
3797
  if (payload?.system_list?.name) writeLine(context.stdout, `List: ${payload.system_list.name} (${display(payload.system_list.prefix_id)})`);
3275
3798
  }
3276
3799
 
@@ -4531,7 +5054,7 @@ const HELP_TOPICS = new Map([
4531
5054
  " audienti motions run-discovery <motn_id>",
4532
5055
  " audienti motions prospects <motn_id>",
4533
5056
  " audienti motions create --payload <file.json>",
4534
- " audienti motions update <motn_id> [--status <state>] [--tags <tag[,tag...]>]",
5057
+ " audienti motions update <motn_id> [--status <state>] [--tags <tag[,tag...]>] [--own-post-engagement <true|false>]",
4535
5058
  " audienti motions add-tag <motn_id> <tag>",
4536
5059
  " audienti motions remove-tag <motn_id> <tag>",
4537
5060
  " audienti motions activate <motn_id>",
@@ -4541,14 +5064,26 @@ const HELP_TOPICS = new Map([
4541
5064
  " audienti motions move-prospects <source_motn_id> --target <target_motn_id> <prsp_id> [prsp_id...]",
4542
5065
  " Tip: `plays` is accepted anywhere `motions` is accepted.",
4543
5066
  "",
5067
+ " ContentOps",
5068
+ " audienti content programs",
5069
+ " audienti content plan <cprg_id>",
5070
+ " audienti content show <cpwi_id>",
5071
+ " audienti content feedback <cpwi_id> --message <text>",
5072
+ " audienti content approve <cpwi_id>",
5073
+ " audienti content publish <cpwi_id> --url <permalink>",
5074
+ " audienti content comments",
5075
+ "",
4544
5076
  " Prospects",
4545
5077
  " audienti prospects list [filters]",
4546
5078
  " audienti prospects check [filters]",
4547
5079
  " audienti prospects show <prsp_id>",
4548
5080
  " audienti prospects assign <prsp_id> --assigned-user <id|me|unassign>",
5081
+ " audienti prospects set-status <prsp_id> --status <active|nurture|non_responsive|not_fit|rejected>",
5082
+ " audienti prospects lock <prsp_id> [--note <text>]",
4549
5083
  " audienti prospects reject <prsp_id>",
4550
5084
  " audienti prospects nurture <prsp_id> [--reason <reason>]",
4551
5085
  " audienti prospects restore <prsp_id>",
5086
+ " audienti prospects unlock <prsp_id>",
4552
5087
  " audienti prospects timeline <prsp_id>",
4553
5088
  " audienti prospects import <linkedin_url> [--motion <motn_id>]",
4554
5089
  " audienti prospects import-batch --file <csv|jsonl|json>",
@@ -4572,6 +5107,10 @@ const HELP_TOPICS = new Map([
4572
5107
  " audienti icps add-tag <icp_id> <tag>",
4573
5108
  " audienti icps remove-tag <icp_id> <tag>",
4574
5109
  " audienti companies search --query <text>",
5110
+ " audienti dnc list",
5111
+ " audienti dnc add <email|citation_id|profile_url>",
5112
+ " audienti company-rules list",
5113
+ " audienti company-rules create (--linkedin-url <url> | --domain <domain>) --disposition <state>",
4575
5114
  "",
4576
5115
  " Writer",
4577
5116
  " audienti writer test-run <prsp_id>",
@@ -5155,6 +5694,148 @@ const HELP_TOPICS = new Map([
5155
5694
  " companies[].location: string | null"
5156
5695
  ].join("\n")],
5157
5696
 
5697
+ ["dnc", [
5698
+ "Usage:",
5699
+ " audienti dnc list [--json]",
5700
+ ` ${DNC_ADD_USAGE.slice("Usage: ".length)}`,
5701
+ ` ${DNC_IMPORT_USAGE.slice("Usage: ".length)}`,
5702
+ ` ${DNC_REMOVE_USAGE.slice("Usage: ".length)}`,
5703
+ "",
5704
+ "Status: implemented",
5705
+ "",
5706
+ "Purpose:",
5707
+ " Manage account-level do-not-contact entries through the same settings/API path as the app."
5708
+ ].join("\n")],
5709
+
5710
+ ["dnc list", [
5711
+ "Usage:",
5712
+ " audienti dnc list [--limit <n>] [--offset <n>] [--json] [--account <acct_id>]",
5713
+ "",
5714
+ "Status: implemented",
5715
+ "",
5716
+ "API:",
5717
+ " GET /api/v1/accounts/:account_id/dnc.json"
5718
+ ].join("\n")],
5719
+
5720
+ ["dnc add", [
5721
+ "Usage:",
5722
+ ` ${DNC_ADD_USAGE.slice("Usage: ".length)}`,
5723
+ "",
5724
+ "Status: implemented",
5725
+ "",
5726
+ "Input shape:",
5727
+ " value: email, citation ID, or supported person profile URL",
5728
+ "",
5729
+ "Behavior:",
5730
+ " Creates or reactivates an account DNC entry and retroactively rejects matching account prospects.",
5731
+ "",
5732
+ "API:",
5733
+ " POST /api/v1/accounts/:account_id/dnc.json"
5734
+ ].join("\n")],
5735
+
5736
+ ["dnc import", [
5737
+ "Usage:",
5738
+ ` ${DNC_IMPORT_USAGE.slice("Usage: ".length)}`,
5739
+ "",
5740
+ "Status: implemented",
5741
+ "",
5742
+ "Input shape:",
5743
+ " file: text or CSV file. The CLI sends one value per line using column one for CSV-like rows.",
5744
+ "",
5745
+ "API:",
5746
+ " POST /api/v1/accounts/:account_id/dnc/import.json"
5747
+ ].join("\n")],
5748
+
5749
+ ["dnc remove", [
5750
+ "Usage:",
5751
+ ` ${DNC_REMOVE_USAGE.slice("Usage: ".length)}`,
5752
+ "",
5753
+ "Status: implemented",
5754
+ "",
5755
+ "API:",
5756
+ " DELETE /api/v1/accounts/:account_id/dnc/:id.json"
5757
+ ].join("\n")],
5758
+
5759
+ ["company-rules", [
5760
+ "Usage:",
5761
+ " audienti company-rules list [--json]",
5762
+ ` ${COMPANY_RULES_CREATE_USAGE.slice("Usage: ".length)}`,
5763
+ ` ${COMPANY_RULES_UPDATE_USAGE.slice("Usage: ".length)}`,
5764
+ ` ${COMPANY_RULES_REMOVE_USAGE.slice("Usage: ".length)}`,
5765
+ ` ${COMPANY_RULES_APPLY_USAGE.slice("Usage: ".length)}`,
5766
+ "",
5767
+ "Status: implemented",
5768
+ "",
5769
+ "Purpose:",
5770
+ " Manage account-wide and user-scoped company disposition rules keyed by LinkedIn company URL or domain."
5771
+ ].join("\n")],
5772
+
5773
+ ["company-rules list", [
5774
+ "Usage:",
5775
+ " audienti company-rules list [--json] [--account <acct_id>]",
5776
+ "",
5777
+ "Status: implemented",
5778
+ "",
5779
+ "API:",
5780
+ " GET /api/v1/accounts/:account_id/company_rules.json"
5781
+ ].join("\n")],
5782
+
5783
+ ["company-rules create", [
5784
+ "Usage:",
5785
+ ` ${COMPANY_RULES_CREATE_USAGE.slice("Usage: ".length)}`,
5786
+ "",
5787
+ "Status: implemented",
5788
+ "",
5789
+ "Input shape:",
5790
+ " linkedin-url: LinkedIn company URL | optional when domain is present",
5791
+ " domain: company domain | optional when linkedin-url is present",
5792
+ " disposition: monitor | nurture | not_fit | reject",
5793
+ " user: account user id, email, or me | optional. Omit for account-wide.",
5794
+ "",
5795
+ "Behavior:",
5796
+ " Matching prospects are always created first. Rule application then applies the disposition; monitor locks activity with lock_kind=company_policy until dedicated monitor mode exists.",
5797
+ "",
5798
+ "API:",
5799
+ " POST /api/v1/accounts/:account_id/company_rules.json"
5800
+ ].join("\n")],
5801
+
5802
+ ["company-rules update", [
5803
+ "Usage:",
5804
+ ` ${COMPANY_RULES_UPDATE_USAGE.slice("Usage: ".length)}`,
5805
+ "",
5806
+ "Status: implemented",
5807
+ "",
5808
+ "Input shape:",
5809
+ " user: account user id, email, me, or none. none makes the rule account-wide.",
5810
+ "",
5811
+ "API:",
5812
+ " PATCH /api/v1/accounts/:account_id/company_rules/:id.json"
5813
+ ].join("\n")],
5814
+
5815
+ ["company-rules remove", [
5816
+ "Usage:",
5817
+ ` ${COMPANY_RULES_REMOVE_USAGE.slice("Usage: ".length)}`,
5818
+ "",
5819
+ "Status: implemented",
5820
+ "",
5821
+ "API:",
5822
+ " DELETE /api/v1/accounts/:account_id/company_rules/:id.json"
5823
+ ].join("\n")],
5824
+
5825
+ ["company-rules apply", [
5826
+ "Usage:",
5827
+ ` ${COMPANY_RULES_APPLY_USAGE.slice("Usage: ".length)}`,
5828
+ "",
5829
+ "Status: implemented",
5830
+ "",
5831
+ "Behavior:",
5832
+ " Backfills active visible account prospects through the same company-rule applicator used after profile writes.",
5833
+ "",
5834
+ "API:",
5835
+ " POST /api/v1/accounts/:account_id/company_rules/:id/apply.json",
5836
+ " POST /api/v1/accounts/:account_id/company_rules/apply_all.json"
5837
+ ].join("\n")],
5838
+
5158
5839
  ["tags", [
5159
5840
  "Usage:",
5160
5841
  " audienti tags list [--json]",
@@ -5462,7 +6143,7 @@ const HELP_TOPICS = new Map([
5462
6143
  " audienti motions prospects <motn_id> [--json]",
5463
6144
  " audienti motions add-prospects <motn_id> <prsp_id> [prsp_id...] [--json]",
5464
6145
  " audienti motions create --payload <file.json> [--json]",
5465
- " audienti motions update <motn_id> [--status <draft|preparing|active|paused|archived>] [--tags <tag[,tag...]>] [--json]",
6146
+ " audienti motions update <motn_id> [--status <draft|preparing|active|paused|archived>] [--tags <tag[,tag...]>] [--own-post-engagement <true|false>] [--json]",
5466
6147
  " audienti motions add-tag <motn_id> <tag> [--json]",
5467
6148
  " audienti motions remove-tag <motn_id> <tag> [--json]",
5468
6149
  " audienti motions activate <motn_id> [--json]",
@@ -5481,6 +6162,34 @@ const HELP_TOPICS = new Map([
5481
6162
  " motn_id: motn_ prefix id"
5482
6163
  ].join("\n")],
5483
6164
 
6165
+ ["content", [
6166
+ "Usage:",
6167
+ " audienti content programs [--user <account_user_id|email|name|me>] [--json]",
6168
+ " audienti content plan <cprg_id> [--week <n>] [--due] [--json]",
6169
+ " audienti content show <cpwi_id> [--json]",
6170
+ " audienti content feedback <cpwi_id> --message <text> [--json]",
6171
+ " audienti content approve <cpwi_id> [--json]",
6172
+ " audienti content schedule <cpwi_id> --at <time> [--json]",
6173
+ " audienti content publish <cpwi_id> --url <permalink> [--json]",
6174
+ " audienti content comments [--unresolved] [--user <account_user_id|email|name|me>] [--json]",
6175
+ " audienti content reply <cctk_id> [--body <text>] [--json]",
6176
+ " audienti content dismiss <cctk_id> [--json]",
6177
+ "",
6178
+ "API:",
6179
+ " GET/POST /api/v1/accounts/:account_id/content_ops/..."
6180
+ ].join("\n")],
6181
+
6182
+ ["content programs", [CONTENT_PROGRAMS_USAGE].join("\n")],
6183
+ ["content plan", [CONTENT_PLAN_USAGE].join("\n")],
6184
+ ["content show", [CONTENT_SHOW_USAGE].join("\n")],
6185
+ ["content feedback", [CONTENT_FEEDBACK_USAGE].join("\n")],
6186
+ ["content approve", [CONTENT_APPROVE_USAGE].join("\n")],
6187
+ ["content schedule", [CONTENT_SCHEDULE_USAGE].join("\n")],
6188
+ ["content publish", [CONTENT_PUBLISH_USAGE].join("\n")],
6189
+ ["content comments", [CONTENT_COMMENTS_USAGE].join("\n")],
6190
+ ["content reply", [CONTENT_REPLY_USAGE].join("\n")],
6191
+ ["content dismiss", [CONTENT_DISMISS_USAGE].join("\n")],
6192
+
5484
6193
  ["motions list", [
5485
6194
  "Usage:",
5486
6195
  " audienti motions list [--tag <tag>] [--json] [--account <acct_id>]",
@@ -5867,9 +6576,12 @@ const HELP_TOPICS = new Map([
5867
6576
  " audienti prospects check [--json|--csv] [filters]",
5868
6577
  " audienti prospects show <prsp_id> [--json]",
5869
6578
  " audienti prospects assign <prsp_id> [prsp_id...] --assigned-user <id|me|unassign> [--json]",
6579
+ " audienti prospects set-status <prsp_id> --status <active|nurture|non_responsive|not_fit|rejected> [--json]",
5870
6580
  " audienti prospects reject <prsp_id> [--json]",
5871
6581
  " audienti prospects nurture <prsp_id> [--reason <reason>] [--json]",
5872
6582
  " audienti prospects restore <prsp_id> [--json]",
6583
+ " audienti prospects lock <prsp_id> [--note <text>] [--json]",
6584
+ " audienti prospects unlock <prsp_id> [--json]",
5873
6585
  " audienti prospects timeline <prsp_id> [--json]",
5874
6586
  " audienti prospects message-types <prsp_id> [--json]",
5875
6587
  " audienti prospects write <prsp_id> --type <surface_key> [--json]",
@@ -5883,7 +6595,7 @@ const HELP_TOPICS = new Map([
5883
6595
  " audienti prospects import-batch --file <csv|jsonl|json> [--list <list_id>] [--motion <motn_id>] [--json]",
5884
6596
  " audienti prospects import-status <primp_id> [--json]",
5885
6597
  "",
5886
- "Status: read commands, assignment, disposition, per-prospect draft preview, sequence preview, and import implemented",
6598
+ "Status: read commands, assignment, disposition, lock/unlock, per-prospect draft preview, sequence preview, and import implemented",
5887
6599
  "",
5888
6600
  "Filters:",
5889
6601
  " --query <text>",
@@ -6012,6 +6724,27 @@ const HELP_TOPICS = new Map([
6012
6724
  " }"
6013
6725
  ].join("\n")],
6014
6726
 
6727
+ ["prospects set-status", [
6728
+ "Usage:",
6729
+ ` ${PROSPECTS_SET_STATUS_USAGE.slice("Usage: ".length)}`,
6730
+ "",
6731
+ "Status: implemented",
6732
+ "",
6733
+ "Purpose:",
6734
+ " Set the account-scoped prospect disposition directly, without routing through a motion.",
6735
+ "",
6736
+ "Input shape:",
6737
+ " status: active | nurture | non_responsive | not_fit | rejected",
6738
+ "",
6739
+ "Behavior:",
6740
+ " active restores the prospect, rejected uses the rejection/DNC path, and nurture/non_responsive/not_fit use the shared inactive disposition path.",
6741
+ "",
6742
+ "API:",
6743
+ " POST /api/v1/accounts/:account_id/prospects/:id/restore.json",
6744
+ " POST /api/v1/accounts/:account_id/prospects/:id/reject.json",
6745
+ " POST /api/v1/accounts/:account_id/prospects/:id/nurture.json"
6746
+ ].join("\n")],
6747
+
6015
6748
  ["prospects show", [
6016
6749
  "Usage:",
6017
6750
  " audienti prospects show <prsp_id> [--json] [--account <acct_id>]",
@@ -6067,6 +6800,36 @@ const HELP_TOPICS = new Map([
6067
6800
  " POST /api/v1/accounts/:account_id/prospects/:id/restore.json"
6068
6801
  ].join("\n")],
6069
6802
 
6803
+ ["prospects lock", [
6804
+ "Usage:",
6805
+ ` ${PROSPECTS_LOCK_USAGE.slice("Usage: ".length)}`,
6806
+ "",
6807
+ "Status: implemented",
6808
+ "",
6809
+ "Purpose:",
6810
+ " Lock one prospect immediately through the same protected-relationship path used by the prospect page.",
6811
+ "",
6812
+ "Input shape:",
6813
+ " kind: protected_relationship | company_policy. Defaults to protected_relationship.",
6814
+ " note: optional operator note explaining the lock.",
6815
+ "",
6816
+ "API:",
6817
+ " POST /api/v1/accounts/:account_id/prospects/:id/lock.json"
6818
+ ].join("\n")],
6819
+
6820
+ ["prospects unlock", [
6821
+ "Usage:",
6822
+ ` ${PROSPECTS_UNLOCK_USAGE.slice("Usage: ".length)}`,
6823
+ "",
6824
+ "Status: implemented",
6825
+ "",
6826
+ "Purpose:",
6827
+ " Clear one prospect lock through the same unlock path used by the prospect page.",
6828
+ "",
6829
+ "API:",
6830
+ " POST /api/v1/accounts/:account_id/prospects/:id/unlock.json"
6831
+ ].join("\n")],
6832
+
6070
6833
  ["prospects timeline", [
6071
6834
  "Usage:",
6072
6835
  " audienti prospects timeline <prsp_id> [--json] [--types <post,comment,reaction>] [--limit <n>] [--account <acct_id>]",
@@ -6776,9 +7539,12 @@ const HELP_TOPICS = new Map([
6776
7539
  " audienti prospects add-profile <prsp_id> --url prospect@example.com",
6777
7540
  " audienti prospects report-bad-profile <prsp_id> <prof_id>",
6778
7541
  " audienti prospects add-note <prsp_id> --type steer --message \"Meeting will not happen\" --engagement-type action.meeting.canceled",
7542
+ " audienti prospects set-status <prsp_id> --status not_fit",
7543
+ " audienti prospects lock <prsp_id> --note \"Emergency hold\"",
6779
7544
  " audienti prospects reject <prsp_id>",
6780
7545
  " audienti prospects nurture <prsp_id>",
6781
7546
  " audienti prospects restore <prsp_id>",
7547
+ " audienti prospects unlock <prsp_id>",
6782
7548
  " audienti prospects sequence-preview <prsp_id>",
6783
7549
  " audienti writer test-run <prsp_id>",
6784
7550
  " audienti prospects sequence-export <prsp_id> --csv",