@audienti/cli 0.1.3 → 0.1.5

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,29 @@ All notable changes to the Audienti CLI are documented here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.1.5] - 2026-07-11
8
+
9
+ ### Added
10
+
11
+ - Add the `https://cli.audienti.com/install` curl installer backed by the public CLI mirror and npm package.
12
+ - Add `audienti operator next --done|--skip|--fail|--return` shortcuts for recording the current prospect next-move outcome without hand-building a payload file.
13
+ - Add `audienti prospects add-profile` and `audienti prospects report-bad-profile` for updating prospect profile channels through the same server paths used by the prospect show page.
14
+
15
+ ### Changed
16
+
17
+ - Send fingerprinted `operator next` outcome shortcuts through the server-derived row contract so queue-row semantics stay on the API side.
18
+
19
+ ### Fixed
20
+
21
+ - Reject `audienti operator next --note` and `--occurred-at` unless an outcome flag is present.
22
+
23
+ ## [0.1.4] - 2026-07-11
24
+
25
+ ### Added
26
+
27
+ - Add `audienti operator next --plan` for deterministic next-action plan output.
28
+ - Add `audienti analytics prospects`, `audienti analytics visibility`, and `audienti analytics content` for account-scoped operational analytics.
29
+
7
30
  ## [0.1.3] - 2026-07-11
8
31
 
9
32
  ### Changed
package/README.md CHANGED
@@ -8,6 +8,13 @@ manage plays, import prospects, build lists, and work supported operator flows.
8
8
 
9
9
  Requires Node.js 20 or newer.
10
10
 
11
+ ```bash
12
+ curl -fsSL https://cli.audienti.com/install | bash
13
+ audienti --help
14
+ ```
15
+
16
+ Or install directly through npm:
17
+
11
18
  ```bash
12
19
  npm install --global @audienti/cli
13
20
  audienti --help
@@ -41,6 +48,35 @@ Use `--json` whenever another program or agent will consume the result. Inspect
41
48
  the target state before mutations, and use the command-specific help before
42
49
  creating, changing, or deleting data.
43
50
 
51
+ Common inspection commands:
52
+
53
+ ```bash
54
+ audienti operator next --plan
55
+ audienti prospects show <prsp_id> --json
56
+ audienti prospects list --profiles
57
+ audienti analytics prospects --window 24h
58
+ audienti analytics visibility --window 24h --user me
59
+ audienti analytics content --window week
60
+ ```
61
+
62
+ To work the supported prospect operator queue from the CLI, inspect the next move
63
+ and record the outcome against that same row:
64
+
65
+ ```bash
66
+ audienti operator next --plan
67
+ audienti operator next --done --note "Connection request sent."
68
+ ```
69
+
70
+ To update a prospect's attached profile channels through the same paths used by
71
+ the prospect show page:
72
+
73
+ ```bash
74
+ audienti prospects add-profile <prsp_id> --url prospect@example.com
75
+ audienti prospects add-profile <prsp_id> --url +12025550123
76
+ audienti prospects add-profile <prsp_id> --url https://www.linkedin.com/in/example
77
+ audienti prospects report-bad-profile <prsp_id> <prof_id>
78
+ ```
79
+
44
80
  ## Compatibility
45
81
 
46
82
  The CLI talks to the versioned Audienti `/api/v1` contract at
package/install ADDED
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ package="@audienti/cli"
5
+ binary="audienti"
6
+ minimum_node_major=20
7
+
8
+ say() {
9
+ printf '%s\n' "$*"
10
+ }
11
+
12
+ fail() {
13
+ printf 'Audienti CLI install failed: %s\n' "$*" >&2
14
+ exit 1
15
+ }
16
+
17
+ require_command() {
18
+ if ! command -v "$1" >/dev/null 2>&1; then
19
+ fail "$2"
20
+ fi
21
+ }
22
+
23
+ require_command node "Node.js ${minimum_node_major} or newer is required. Install Node.js, then rerun this script."
24
+ require_command npm "npm is required. Install Node.js ${minimum_node_major} or newer with npm, then rerun this script."
25
+
26
+ node_major="$(node -p 'Number(process.versions.node.split(".")[0])' 2>/dev/null || true)"
27
+ case "$node_major" in
28
+ ''|*[!0-9]*)
29
+ fail "could not determine the installed Node.js version."
30
+ ;;
31
+ esac
32
+
33
+ if [ "$node_major" -lt "$minimum_node_major" ]; then
34
+ fail "Node.js ${minimum_node_major} or newer is required. Current version: $(node --version)."
35
+ fi
36
+
37
+ say "Installing ${package}..."
38
+ npm install --global --no-audit --no-fund "$package"
39
+
40
+ global_prefix="$(npm prefix --global 2>/dev/null || true)"
41
+ if [ -n "$global_prefix" ] && [ -d "$global_prefix/bin" ]; then
42
+ export PATH="$global_prefix/bin:$PATH"
43
+ fi
44
+
45
+ if ! command -v "$binary" >/dev/null 2>&1; then
46
+ if [ -n "$global_prefix" ] && [ -x "$global_prefix/bin/$binary" ]; then
47
+ fail "${binary} installed at ${global_prefix}/bin/${binary}, but that directory is not on PATH."
48
+ fi
49
+
50
+ fail "${binary} was not found on PATH after installation."
51
+ fi
52
+
53
+ "$binary" --help >/dev/null
54
+
55
+ cat <<'NEXT'
56
+ Audienti CLI installed.
57
+
58
+ Next:
59
+ audienti auth token <token>
60
+ audienti accounts list --json
61
+ audienti accounts select <acct_id>
62
+ NEXT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@audienti/cli",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Agent-first command-line client for Audienti.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,6 +9,8 @@
9
9
  "files": [
10
10
  "bin/",
11
11
  "src/",
12
+ "skills/",
13
+ "install",
12
14
  "README.md",
13
15
  "LICENSE",
14
16
  "CHANGELOG.md"
@@ -0,0 +1,60 @@
1
+ ---
2
+ name: audienti
3
+ description: Use when the user wants to operate Audienti through the production CLI, including account selection, plays, prospect imports, lists, message previews, or supported operator outcomes.
4
+ ---
5
+
6
+ # Audienti CLI
7
+
8
+ Use the installed `audienti` command as the production contract. Do not build a
9
+ parallel wrapper or call undocumented API endpoints.
10
+
11
+ ## Setup
12
+
13
+ 1. Verify the command is installed:
14
+
15
+ ```bash
16
+ audienti --help
17
+ ```
18
+
19
+ 2. If it is unavailable, install the public package:
20
+
21
+ ```bash
22
+ curl -fsSL https://cli.audienti.com/install | bash
23
+ ```
24
+
25
+ 3. Authentication is explicit and per machine. Do not ask a user to paste a
26
+ production token into chat, a repository file, an issue, or a CI secret. Use
27
+ the existing `audienti auth token` flow only after the user supplies a token
28
+ through an approved secure channel.
29
+
30
+ 4. Start with discovery, not mutation:
31
+
32
+ ```bash
33
+ audienti auth status
34
+ audienti accounts list --json
35
+ audienti help agent-workflows
36
+ ```
37
+
38
+ ## Operating Rules
39
+
40
+ - Use `--json` whenever another agent or tool will consume the response.
41
+ - Use `audienti <resource> <action> help` before a mutation when the accepted
42
+ payload or behavior is unclear.
43
+ - Inspect the current resource before a create, update, attach, delete, or
44
+ operator outcome writeback.
45
+ - Treat the production API as the source of truth. Persist durable work in
46
+ Audienti rather than leaving it only in agent prose.
47
+ - Keep current gaps explicit. Do not imply that unsupported actions execute.
48
+
49
+ ## Common Entry Points
50
+
51
+ ```bash
52
+ audienti help agent-workflows
53
+ audienti prospects list --query "name or company" --wide --json
54
+ audienti lists create --name "Target list" --json
55
+ audienti operator next --json
56
+ audienti operator next --plan
57
+ audienti analytics prospects --window 24h --json
58
+ audienti analytics visibility --window 24h --user me --json
59
+ audienti analytics content --window week --json
60
+ ```
package/src/api-client.js CHANGED
@@ -189,6 +189,20 @@ export class AudientiClient {
189
189
  });
190
190
  }
191
191
 
192
+ addProspectProfile(accountId, prospectId, body) {
193
+ return this.requestJson(accountPath(accountId, ["prospects", prospectId, "profiles"]), {
194
+ method: "POST",
195
+ body
196
+ });
197
+ }
198
+
199
+ reportBadProspectProfile(accountId, prospectId, body) {
200
+ return this.requestJson(accountPath(accountId, ["prospects", prospectId, "report_bad_profile"]), {
201
+ method: "POST",
202
+ body
203
+ });
204
+ }
205
+
192
206
  prospectImport(accountId, body) {
193
207
  return this.requestJson(accountPath(accountId, ["prospect_imports"]), {
194
208
  method: "POST",
@@ -215,6 +229,18 @@ export class AudientiClient {
215
229
  });
216
230
  }
217
231
 
232
+ analyticsProspects(accountId, query = {}) {
233
+ return this.requestJson(accountPath(accountId, ["analytics", "prospects"], query));
234
+ }
235
+
236
+ analyticsVisibility(accountId, query = {}) {
237
+ return this.requestJson(accountPath(accountId, ["analytics", "visibility"], query));
238
+ }
239
+
240
+ analyticsContent(accountId, query = {}) {
241
+ return this.requestJson(accountPath(accountId, ["analytics", "content"], query));
242
+ }
243
+
218
244
  async requestJson(path, { method = "GET", body } = {}) {
219
245
  const response = await this.fetchImpl(new URL(path, `${this.host}/`), {
220
246
  method,
@@ -296,6 +322,10 @@ function errorMessage(status, body) {
296
322
  return "The requested Audienti resource was not found.";
297
323
  }
298
324
 
325
+ if (status === 409) {
326
+ return body?.error || "Audienti rejected the request because the resource changed. Re-fetch and try again.";
327
+ }
328
+
299
329
  if (status === 422) {
300
330
  const reasons = [body?.errors, body?.details].find(Array.isArray);
301
331
  const details = reasons?.length > 0 ? reasons.join(", ") : body?.error;
package/src/cli.js CHANGED
@@ -26,6 +26,8 @@ const DEFAULT_PROFILE_IDENTIFIERS = [
26
26
  const DELETE_CONFIRMATION_VALUES = new Set(["yes", "true", "y"]);
27
27
  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>]";
28
28
  const PROSPECTS_ADD_STEER_USAGE = "Usage: audienti prospects add-steer <prsp_id> (--message <text> [--engagement-type <key>] | --payload <file.json>) [--json] [--account <acct_id>]";
29
+ const PROSPECTS_ADD_PROFILE_USAGE = "Usage: audienti prospects add-profile <prsp_id> --url <profile_url|email|phone> [--json] [--account <acct_id>]";
30
+ const PROSPECTS_REPORT_BAD_PROFILE_USAGE = "Usage: audienti prospects report-bad-profile <prsp_id> <prof_id|citation_id> [--json] [--account <acct_id>]";
29
31
  const SEQUENCE_EXPORT_CSV_COLUMNS = [
30
32
  "prospect_id",
31
33
  "prospect_name",
@@ -114,6 +116,8 @@ async function dispatch(argv, context) {
114
116
  if (normalizedResource === "prospects" && action === "write") return prospectsWrite(rest, context, { accountOverride });
115
117
  if (normalizedResource === "prospects" && action === "add-note") return prospectsAddNote(rest, context, { accountOverride });
116
118
  if (normalizedResource === "prospects" && action === "add-steer") return prospectsAddSteer(rest, context, { accountOverride });
119
+ if (normalizedResource === "prospects" && action === "add-profile") return prospectsAddProfile(rest, context, { accountOverride });
120
+ if (normalizedResource === "prospects" && action === "report-bad-profile") return prospectsReportBadProfile(rest, context, { accountOverride });
117
121
  if (normalizedResource === "prospects" && action === "sequence-preview") return prospectsSequencePreview(rest, context, { accountOverride });
118
122
  if (normalizedResource === "prospects" && action === "sequence-export") return prospectsSequenceExport(rest, context, { accountOverride });
119
123
  if (normalizedResource === "prospects" && action === "import") return prospectsImport(rest, context, { accountOverride });
@@ -122,6 +126,9 @@ async function dispatch(argv, context) {
122
126
  if (normalizedResource === "operator" && action === "queue") return operatorQueue(rest, context, { accountOverride });
123
127
  if (normalizedResource === "operator" && action === "next") return operatorNext(rest, context, { accountOverride });
124
128
  if (normalizedResource === "operator" && action === "outcome") return operatorOutcome(rest, context, { accountOverride });
129
+ if (normalizedResource === "analytics" && ["prospects", "prospect"].includes(action)) return analyticsProspects(rest, context, { accountOverride });
130
+ if (normalizedResource === "analytics" && ["visibility", "visops"].includes(action)) return analyticsVisibility(rest, context, { accountOverride });
131
+ if (normalizedResource === "analytics" && action === "content") return analyticsContent(rest, context, { accountOverride });
125
132
 
126
133
  throw new CommandError(usage(), { exitCode: resource ? 1 : 0 });
127
134
  }
@@ -858,6 +865,31 @@ async function prospectsAddSteer(args, context, { accountOverride } = {}) {
858
865
  });
859
866
  }
860
867
 
868
+ async function prospectsAddProfile(args, context, { accountOverride } = {}) {
869
+ const { values, positionals } = parseCommandArgs(args, {
870
+ ...jsonOptions(),
871
+ url: { type: "string" }
872
+ });
873
+ if (positionals.length !== 1 || !values.url) throw new CommandError(PROSPECTS_ADD_PROFILE_USAGE);
874
+
875
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
876
+ const response = await client.addProspectProfile(accountId, positionals[0], { url: values.url });
877
+ if (values.json) return writeJson(context.stdout, response);
878
+
879
+ renderProspectProfileMutation(response, context, { action: "Added" });
880
+ }
881
+
882
+ async function prospectsReportBadProfile(args, context, { accountOverride } = {}) {
883
+ const { values, positionals } = parseCommandArgs(args, jsonOptions());
884
+ if (positionals.length !== 2) throw new CommandError(PROSPECTS_REPORT_BAD_PROFILE_USAGE);
885
+
886
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
887
+ const response = await client.reportBadProspectProfile(accountId, positionals[0], { profile_id: positionals[1] });
888
+ if (values.json) return writeJson(context.stdout, response);
889
+
890
+ renderProspectProfileMutation(response, context, { action: "Reported" });
891
+ }
892
+
861
893
  async function prospectNoteCommand(args, context, { accountOverride, forcedType, usageText }) {
862
894
  const { values, positionals } = parseCommandArgs(args, {
863
895
  ...jsonOptions(),
@@ -1010,7 +1042,7 @@ async function toolsGet(args, context, { accountOverride } = {}) {
1010
1042
  }
1011
1043
 
1012
1044
  async function operatorQueue(args, context, { accountOverride } = {}) {
1013
- const { values, positionals } = parseCommandArgs(args, operatorOptions());
1045
+ const { values, positionals } = parseCommandArgs(args, operatorFilterOptions());
1014
1046
  if (positionals.length > 0) throw new CommandError("Usage: audienti operator queue [--json] [filters] [--account <acct_id>]");
1015
1047
 
1016
1048
  const { client, accountId } = await requireAccountContext(context, { accountOverride });
@@ -1021,12 +1053,30 @@ async function operatorQueue(args, context, { accountOverride } = {}) {
1021
1053
  }
1022
1054
 
1023
1055
  async function operatorNext(args, context, { accountOverride } = {}) {
1024
- const { values, positionals } = parseCommandArgs(args, operatorOptions());
1025
- if (positionals.length > 0) throw new CommandError("Usage: audienti operator next [--json] [filters] [--account <acct_id>]");
1056
+ const { values, positionals } = parseCommandArgs(args, operatorNextOptions());
1057
+ if (positionals.length > 0) throw new CommandError("Usage: audienti operator next [--json|--plan|--done|--skip|--fail|--return] [filters] [--note <text>] [--account <acct_id>]");
1058
+ if (values.json && values.plan) throw new CommandError("Choose one output format: use either --json or --plan.");
1059
+ const outcomeStatus = operatorNextOutcomeStatus(values);
1060
+ if (values.plan && outcomeStatus) throw new CommandError("Choose one mode: use either --plan or an outcome flag.");
1061
+ if (!outcomeStatus && (values.note !== undefined || values["occurred-at"] !== undefined)) {
1062
+ throw new CommandError("--note and --occurred-at require an outcome flag: --done, --skip, --fail, or --return.");
1063
+ }
1026
1064
 
1027
1065
  const { client, accountId } = await requireAccountContext(context, { accountOverride });
1028
1066
  const payload = await client.operatorNext(accountId, operatorQuery(values));
1067
+ if (outcomeStatus) {
1068
+ const response = await client.operatorOutcome(accountId, operatorNextOutcomePayload(payload?.next_move, {
1069
+ status: outcomeStatus,
1070
+ note: values.note,
1071
+ occurredAt: values["occurred-at"],
1072
+ filters: payload?.filters
1073
+ }));
1074
+ if (values.json) return writeJson(context.stdout, response);
1075
+
1076
+ return renderOperatorOutcome(response, context);
1077
+ }
1029
1078
  if (values.json) return writeJson(context.stdout, payload);
1079
+ if (values.plan) return renderOperatorPlan(payload?.next_move, context);
1030
1080
 
1031
1081
  renderOperatorNext(payload?.next_move, context);
1032
1082
  }
@@ -1051,6 +1101,39 @@ async function operatorOutcome(args, context, { accountOverride } = {}) {
1051
1101
  renderOperatorOutcome(response, context);
1052
1102
  }
1053
1103
 
1104
+ async function analyticsProspects(args, context, { accountOverride } = {}) {
1105
+ const { values, positionals } = parseCommandArgs(args, analyticsOptions());
1106
+ if (positionals.length > 0) throw new CommandError("Usage: audienti analytics prospects [--window 24h] [--user <account_user_id|email|name|me>] [--json] [--account <acct_id>]");
1107
+
1108
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1109
+ const payload = await client.analyticsProspects(accountId, analyticsQuery(values));
1110
+ if (values.json) return writeJson(context.stdout, payload);
1111
+
1112
+ renderAnalyticsProspects(payload, context);
1113
+ }
1114
+
1115
+ async function analyticsVisibility(args, context, { accountOverride } = {}) {
1116
+ const { values, positionals } = parseCommandArgs(args, analyticsOptions());
1117
+ if (positionals.length > 0) throw new CommandError("Usage: audienti analytics visibility [--window 24h] [--user <account_user_id|email|name|me>] [--json] [--account <acct_id>]");
1118
+
1119
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1120
+ const payload = await client.analyticsVisibility(accountId, analyticsQuery(values));
1121
+ if (values.json) return writeJson(context.stdout, payload);
1122
+
1123
+ renderAnalyticsVisibility(payload, context);
1124
+ }
1125
+
1126
+ async function analyticsContent(args, context, { accountOverride } = {}) {
1127
+ const { values, positionals } = parseCommandArgs(args, analyticsOptions());
1128
+ if (positionals.length > 0) throw new CommandError("Usage: audienti analytics content [--window 24h] [--user <account_user_id|email|name|me>] [--json] [--account <acct_id>]");
1129
+
1130
+ const { client, accountId } = await requireAccountContext(context, { accountOverride });
1131
+ const payload = await client.analyticsContent(accountId, analyticsQuery(values));
1132
+ if (values.json) return writeJson(context.stdout, payload);
1133
+
1134
+ renderAnalyticsContent(payload, context);
1135
+ }
1136
+
1054
1137
  function parseCommandArgs(args, options) {
1055
1138
  try {
1056
1139
  return parseArgs({
@@ -1106,7 +1189,7 @@ function jsonOptions() {
1106
1189
  };
1107
1190
  }
1108
1191
 
1109
- function operatorOptions() {
1192
+ function operatorFilterOptions(extra = {}) {
1110
1193
  return {
1111
1194
  ...jsonOptions(),
1112
1195
  principal: { type: "string" },
@@ -1114,10 +1197,23 @@ function operatorOptions() {
1114
1197
  list: { type: "string" },
1115
1198
  stage: { type: "string" },
1116
1199
  "opportunity-kind": { type: "string" },
1117
- "writing-status": { type: "string" }
1200
+ "writing-status": { type: "string" },
1201
+ ...extra
1118
1202
  };
1119
1203
  }
1120
1204
 
1205
+ function operatorNextOptions() {
1206
+ return operatorFilterOptions({
1207
+ plan: { type: "boolean" },
1208
+ done: { type: "boolean" },
1209
+ skip: { type: "boolean" },
1210
+ fail: { type: "boolean" },
1211
+ return: { type: "boolean" },
1212
+ note: { type: "string" },
1213
+ "occurred-at": { type: "string" }
1214
+ });
1215
+ }
1216
+
1121
1217
  function operatorQuery(values) {
1122
1218
  return compactObject({
1123
1219
  principal_account_user_id: values.principal,
@@ -1129,6 +1225,48 @@ function operatorQuery(values) {
1129
1225
  });
1130
1226
  }
1131
1227
 
1228
+ function operatorNextOutcomeStatus(values) {
1229
+ const selected = [
1230
+ values.done ? "done" : null,
1231
+ values.skip ? "skipped" : null,
1232
+ values.fail ? "failed" : null,
1233
+ values.return ? "returned" : null
1234
+ ].filter(Boolean);
1235
+ if (selected.length > 1) throw new CommandError("Choose one outcome flag: --done, --skip, --fail, or --return.");
1236
+
1237
+ return selected[0];
1238
+ }
1239
+
1240
+ function operatorNextOutcomePayload(row, { status, note, occurredAt, filters }) {
1241
+ if (!row) throw new CommandError("No operator moves found.");
1242
+ if (!row.id) throw new CommandError("The next operator move is missing a row id.");
1243
+ if (!row.fingerprint) throw new CommandError("The next operator move is missing a fingerprint; update the server before using outcome shortcuts.");
1244
+
1245
+ return compactObject({
1246
+ row_id: row.id,
1247
+ status,
1248
+ fingerprint: row.fingerprint,
1249
+ queue_filters: filters,
1250
+ note,
1251
+ occurred_at: occurredAt
1252
+ });
1253
+ }
1254
+
1255
+ function analyticsOptions() {
1256
+ return {
1257
+ ...jsonOptions(),
1258
+ window: { type: "string" },
1259
+ user: { type: "string" }
1260
+ };
1261
+ }
1262
+
1263
+ function analyticsQuery(values) {
1264
+ return compactObject({
1265
+ window: values.window,
1266
+ account_user_id: values.user
1267
+ });
1268
+ }
1269
+
1132
1270
  function compactObject(object) {
1133
1271
  return Object.fromEntries(
1134
1272
  Object.entries(object).filter(([, value]) => value !== undefined && value !== null && String(value).trim() !== "")
@@ -1622,6 +1760,19 @@ function renderProspectNote(payload, context) {
1622
1760
  }
1623
1761
  }
1624
1762
 
1763
+ function renderProspectProfileMutation(payload, context, { action }) {
1764
+ const prospect = payload?.prospect || {};
1765
+ const profile = payload?.profile || {};
1766
+ const status = payload?.status ? ` (${payload.status})` : "";
1767
+
1768
+ writeLine(context.stdout, `${action} profile${status}.`);
1769
+ writeLine(context.stdout, `Prospect: ${display(prospect.display_name || prospect.name)} (${display(prospect.prefix_id)})`);
1770
+ writeLine(context.stdout, `Profile: ${display(profile.citation_id || profile.prefix_id)}`);
1771
+ if (profile.identifier) writeLine(context.stdout, `Type: ${profile.identifier}`);
1772
+ if (profile.username) writeLine(context.stdout, `Username: ${profile.username}`);
1773
+ if (profile.url) writeLine(context.stdout, `URL: ${profile.url}`);
1774
+ }
1775
+
1625
1776
  function renderProspectSequencePreview(payload, context) {
1626
1777
  const prospect = payload?.prospect || {};
1627
1778
  const report = payload?.report || {};
@@ -1761,6 +1912,60 @@ function renderOperatorNext(row, context) {
1761
1912
  writeLine(context.stdout, operatorRowLine(row));
1762
1913
  }
1763
1914
 
1915
+ function renderOperatorPlan(row, context) {
1916
+ if (!row) return writeLine(context.stdout, "No operator moves found.");
1917
+
1918
+ const nextAction = row.next_action || {};
1919
+ const cta = row.cta || {};
1920
+ const draft = row.operator_draft || {};
1921
+
1922
+ writeLine(context.stdout, "Static operator plan");
1923
+ writeLine(context.stdout, `Move: ${display(row.id)}`);
1924
+ writeLine(context.stdout, `Kind: ${display(row.opportunity_kind)}`);
1925
+ writeLine(context.stdout, `Prospect: ${entityLabel(row.prospect)}`);
1926
+ if (row.motion) writeLine(context.stdout, `Motion: ${entityLabel(row.motion)}`);
1927
+ if (row.pipeline_stage || row.plan_state || row.status_label) {
1928
+ writeLine(context.stdout, `State: ${compactText([row.pipeline_stage, row.plan_state, row.status_label]).join(", ")}`);
1929
+ }
1930
+ if (row.due_label) writeLine(context.stdout, `Due: ${row.due_label}`);
1931
+
1932
+ writeLine(context.stdout, "");
1933
+ writeLine(context.stdout, `Next action: ${display(nextActionLabel(row), "Unknown")} (${display(nextAction.type, "unknown")})`);
1934
+ if (nextAction.request_mode) writeLine(context.stdout, `Request mode: ${nextAction.request_mode}`);
1935
+ const timing = timingLabel(nextAction, row);
1936
+ if (timing) writeLine(context.stdout, `Timing: ${timing}`);
1937
+ const target = targetLabel(nextAction);
1938
+ if (target) writeLine(context.stdout, `Target: ${target}`);
1939
+
1940
+ if (Object.keys(cta).length > 0) {
1941
+ writeLine(context.stdout, "");
1942
+ writeLine(context.stdout, `CTA: ${ctaLabel(cta)}`);
1943
+ }
1944
+
1945
+ if (Object.keys(draft).length > 0) {
1946
+ writeLine(context.stdout, "");
1947
+ writeLine(context.stdout, `Draft: ${draftLabel(draft)}`);
1948
+ if (draft.writer_path) writeLine(context.stdout, `Writer: ${draft.writer_path}`);
1949
+ if (draft.subject) writeLine(context.stdout, `Subject: ${draft.subject}`);
1950
+ const body = draft.body || draft.text;
1951
+ if (body) {
1952
+ writeLine(context.stdout, "Body:");
1953
+ writeLine(context.stdout, body);
1954
+ }
1955
+ }
1956
+
1957
+ if (row.rationale) {
1958
+ writeLine(context.stdout, "");
1959
+ writeLine(context.stdout, "Rationale:");
1960
+ writeLine(context.stdout, row.rationale);
1961
+ }
1962
+ if (row.guidance) {
1963
+ writeLine(context.stdout, "");
1964
+ writeLine(context.stdout, "Guidance:");
1965
+ writeLine(context.stdout, row.guidance);
1966
+ }
1967
+ }
1968
+
1764
1969
  function renderOperatorOutcome(payload, context) {
1765
1970
  const outcome = payload?.operator_outcome || {};
1766
1971
  const rowId = payload?.row_id || outcome.row_id;
@@ -1774,6 +1979,84 @@ function renderOperatorOutcome(payload, context) {
1774
1979
  }
1775
1980
  }
1776
1981
 
1982
+ function renderAnalyticsProspects(payload, context) {
1983
+ writeLine(context.stdout, `Prospect analytics (${analyticsWindowLabel(payload)})`);
1984
+ writeAnalyticsScope(payload, context);
1985
+ writeLine(context.stdout, `Prospects added: ${display(payload?.prospects_added_count, 0)}`);
1986
+ writeAnalyticsActionSummary(payload?.actions, context, "Actions");
1987
+ writeCountTable(context, "Action breakdown", payload?.actions?.breakdown, ["ACTION", "COUNT", "AUTOMATED", "AUTO %"], actionBreakdownRow);
1988
+ writeCountTable(context, "Queue stages", payload?.queue_stages, ["STAGE", "COUNT"], countRow);
1989
+ }
1990
+
1991
+ function renderAnalyticsVisibility(payload, context) {
1992
+ writeLine(context.stdout, `Visibility analytics (${analyticsWindowLabel(payload)})`);
1993
+ writeAnalyticsScope(payload, context);
1994
+ writeLine(context.stdout, `Unique people engaged: ${display(payload?.unique_people_engaged_count, 0)}`);
1995
+ writeAnalyticsActionSummary(payload?.engagements, context, "Engagements");
1996
+ writeCountTable(context, "Engagement breakdown", payload?.engagements?.breakdown, ["ACTION", "COUNT", "AUTOMATED", "AUTO %"], actionBreakdownRow);
1997
+ }
1998
+
1999
+ function renderAnalyticsContent(payload, context) {
2000
+ writeLine(context.stdout, `Content analytics (${analyticsWindowLabel(payload)})`);
2001
+ writeAnalyticsScope(payload, context);
2002
+ writeLine(context.stdout, `Published posts: ${display(payload?.published_posts_count, 0)}`);
2003
+ writeCountTable(context, "Stages", payload?.stage_breakdown, ["STAGE", "COUNT"], countRow);
2004
+ writeCountTable(context, "Execution statuses", payload?.execution_status_breakdown, ["STATUS", "COUNT"], countRow);
2005
+ }
2006
+
2007
+ function writeAnalyticsScope(payload, context) {
2008
+ if (payload?.account_user) {
2009
+ writeLine(context.stdout, `User: ${entityLabel(payload.account_user)}`);
2010
+ } else {
2011
+ writeLine(context.stdout, "User: all account users");
2012
+ }
2013
+ }
2014
+
2015
+ function writeAnalyticsActionSummary(actions, context, label) {
2016
+ const total = display(actions?.total_count, 0);
2017
+ const automated = display(actions?.automated_count, 0);
2018
+ const percentage = percentageLabel(actions?.automated_percentage);
2019
+ writeLine(context.stdout, `${label}: ${total} (automated ${automated}, ${percentage})`);
2020
+ }
2021
+
2022
+ function writeCountTable(context, title, rows, headers, mapRow) {
2023
+ const list = Array.isArray(rows) ? rows : [];
2024
+ writeLine(context.stdout, "");
2025
+ writeLine(context.stdout, title);
2026
+ if (list.length === 0) return writeLine(context.stdout, "None");
2027
+
2028
+ writeLine(context.stdout, headers.join("\t"));
2029
+ for (const row of list) writeLine(context.stdout, mapRow(row).join("\t"));
2030
+ }
2031
+
2032
+ function actionBreakdownRow(row) {
2033
+ return [
2034
+ display(row?.label || row?.key),
2035
+ display(row?.count, 0),
2036
+ display(row?.automated_count, 0),
2037
+ percentageLabel(row?.automated_percentage)
2038
+ ];
2039
+ }
2040
+
2041
+ function countRow(row) {
2042
+ return [
2043
+ display(row?.label || row?.key),
2044
+ display(row?.count, 0)
2045
+ ];
2046
+ }
2047
+
2048
+ function analyticsWindowLabel(payload) {
2049
+ const window = payload?.window || {};
2050
+ const key = display(window.key, "24h");
2051
+ if (!window.started_at || !window.ended_at) return key;
2052
+
2053
+ return `${key}: ${window.started_at} to ${window.ended_at}`;
2054
+ }
2055
+
2056
+ function percentageLabel(value) {
2057
+ return value === undefined || value === null || value === "" ? "n/a" : `${value}%`;
2058
+ }
2059
+
1777
2060
  function operatorRowLine(row) {
1778
2061
  return [
1779
2062
  display(row?.id),
@@ -1788,6 +2071,53 @@ function nextActionLabel(source) {
1788
2071
  return source?.recommended_action_label || source?.next_action?.label || source?.cta?.label;
1789
2072
  }
1790
2073
 
2074
+ function entityLabel(entity) {
2075
+ if (!entity) return "";
2076
+
2077
+ const name = entity.display_name || entity.name || entity.prefix_id || entity.id;
2078
+ const id = entity.prefix_id || entity.id;
2079
+ return id && id !== name ? `${display(name)} (${display(id)})` : display(name);
2080
+ }
2081
+
2082
+ function timingLabel(nextAction, row) {
2083
+ const timing = nextAction.timing || {};
2084
+ const mode = timing.mode || row.timing_mode;
2085
+ const scheduledFor = timing.scheduled_for || row.scheduled_for;
2086
+ const parts = compactText([
2087
+ mode,
2088
+ scheduledFor ? `scheduled for ${scheduledFor}` : null
2089
+ ]);
2090
+
2091
+ return parts.join(", ");
2092
+ }
2093
+
2094
+ function targetLabel(nextAction) {
2095
+ const target = nextAction.target || {};
2096
+ return compactText([
2097
+ target.platform,
2098
+ target.profile_url,
2099
+ target.post_url,
2100
+ target.message_event_id ? `message ${target.message_event_id}` : null,
2101
+ target.post_id ? `post ${target.post_id}` : null
2102
+ ]).join(" | ");
2103
+ }
2104
+
2105
+ function ctaLabel(cta) {
2106
+ const action = compactText([cta.action || cta.type, cta.platform ? `on ${cta.platform}` : null]).join(" ");
2107
+ const disabled = cta.disabled ? "disabled" : "enabled";
2108
+ return `${display(cta.label, "Unnamed CTA")}${action ? ` (${action})` : ""}${cta.disabled === undefined ? "" : `, ${disabled}`}`;
2109
+ }
2110
+
2111
+ function draftLabel(draft) {
2112
+ const required = draft.required === true ? "required" : draft.required === false ? "not required" : null;
2113
+ const ready = draft.ready === true ? "ready" : draft.ready === false ? "not ready" : null;
2114
+ return compactText([draft.state, ready, required]).join(", ") || "unknown";
2115
+ }
2116
+
2117
+ function compactText(values) {
2118
+ return values.map((value) => String(value || "").trim()).filter(Boolean);
2119
+ }
2120
+
1791
2121
  function successCount(payload) {
1792
2122
  if (Array.isArray(payload?.added)) return payload.added.length;
1793
2123
  if (Array.isArray(payload?.removed)) return payload.removed.length;
@@ -2036,14 +2366,19 @@ const HELP_TOPICS = new Map([
2036
2366
  " audienti prospects write <prsp_id> --type <surface_key> [--json]",
2037
2367
  " audienti prospects add-note <prsp_id> --message <text> [--json]",
2038
2368
  " audienti prospects add-steer <prsp_id> --message <text> [--json]",
2369
+ " audienti prospects add-profile <prsp_id> --url <profile_url|email|phone> [--json]",
2370
+ " audienti prospects report-bad-profile <prsp_id> <prof_id|citation_id> [--json]",
2039
2371
  " audienti prospects sequence-preview <prsp_id> [--json]",
2040
2372
  " audienti prospects sequence-export <prsp_id> [--csv]",
2041
2373
  " audienti prospects import <linkedin_url> [--list <list_id>] [--motion <motn_id>] [--json]",
2042
2374
  " audienti prospects import-status <primp_id> [--json]",
2043
2375
  " audienti tools get <email|phone> --url <linkedin_url> [--json]",
2044
- " audienti operator next [--json]",
2376
+ " audienti operator next [--json|--plan|--done|--skip|--fail|--return]",
2045
2377
  " audienti operator queue [--json]",
2046
2378
  " audienti operator outcome <row_id> --payload <file.json> [--json]",
2379
+ " audienti analytics prospects [--window 24h] [--user <account_user_id|email|name|me>] [--json]",
2380
+ " audienti analytics visibility [--window 24h] [--user <account_user_id|email|name|me>] [--json]",
2381
+ " audienti analytics content [--window 24h] [--user <account_user_id|email|name|me>] [--json]",
2047
2382
  "",
2048
2383
  "Planned submit-shape help topics:",
2049
2384
  " audienti prospects disposition help",
@@ -2734,6 +3069,8 @@ const HELP_TOPICS = new Map([
2734
3069
  " audienti prospects write <prsp_id> --type <surface_key> [--json]",
2735
3070
  " audienti prospects add-note <prsp_id> --message <text> [--json]",
2736
3071
  " audienti prospects add-steer <prsp_id> --message <text> [--json]",
3072
+ " audienti prospects add-profile <prsp_id> --url <profile_url|email|phone> [--json]",
3073
+ " audienti prospects report-bad-profile <prsp_id> <prof_id|citation_id> [--json]",
2737
3074
  " audienti prospects sequence-preview <prsp_id> [--json]",
2738
3075
  " audienti prospects sequence-export <prsp_id> [--csv]",
2739
3076
  " audienti prospects import <linkedin_url> [--list <list_id>] [--motion <motn_id>] [--json]",
@@ -2945,6 +3282,60 @@ const HELP_TOPICS = new Map([
2945
3282
  " }"
2946
3283
  ].join("\n")],
2947
3284
 
3285
+ ["prospects add-profile", [
3286
+ "Usage:",
3287
+ ` ${PROSPECTS_ADD_PROFILE_USAGE.slice("Usage: ".length)}`,
3288
+ "",
3289
+ "Status: implemented",
3290
+ "",
3291
+ "Purpose:",
3292
+ " Add a profile, email address, or phone number to an existing prospect through the same add-profile path used by the prospect show page.",
3293
+ "",
3294
+ "Input shape:",
3295
+ " prsp_id: prsp_ prospect prefix id",
3296
+ " url: supported profile URL, plain email address, mailto: URL, plain phone number, or tel: URL",
3297
+ "",
3298
+ "Output shape:",
3299
+ " prospect: prospect summary",
3300
+ " profile: attached profile with prefix_id, citation_id, identifier, username, url, and status",
3301
+ " status: attached | already_attached",
3302
+ "",
3303
+ "API:",
3304
+ " POST /api/v1/accounts/:account_id/prospects/:id/profiles.json",
3305
+ "",
3306
+ "JSON body:",
3307
+ " {",
3308
+ " \"url\": \"prospect@example.com\"",
3309
+ " }"
3310
+ ].join("\n")],
3311
+
3312
+ ["prospects report-bad-profile", [
3313
+ "Usage:",
3314
+ ` ${PROSPECTS_REPORT_BAD_PROFILE_USAGE.slice("Usage: ".length)}`,
3315
+ "",
3316
+ "Status: implemented",
3317
+ "",
3318
+ "Purpose:",
3319
+ " Report one of a prospect's attached profiles as bad through the same report action used by the prospect show page.",
3320
+ "",
3321
+ "Input shape:",
3322
+ " prsp_id: prsp_ prospect prefix id",
3323
+ " prof_id: prof_ prefix id or citation id such as email/profile:name@example.com",
3324
+ "",
3325
+ "Output shape:",
3326
+ " prospect: prospect summary",
3327
+ " profile: reported profile",
3328
+ " status: reported",
3329
+ "",
3330
+ "API:",
3331
+ " POST /api/v1/accounts/:account_id/prospects/:id/report_bad_profile.json",
3332
+ "",
3333
+ "JSON body:",
3334
+ " {",
3335
+ " \"profile_id\": \"prof_abc123\"",
3336
+ " }"
3337
+ ].join("\n")],
3338
+
2948
3339
  ["prospects sequence-preview", [
2949
3340
  "Usage:",
2950
3341
  " audienti prospects sequence-preview <prsp_id> [--json] [--connection-state <state>] [--account <acct_id>]",
@@ -3109,11 +3500,11 @@ const HELP_TOPICS = new Map([
3109
3500
 
3110
3501
  ["operator", [
3111
3502
  "Usage:",
3112
- " audienti operator next [--json]",
3503
+ " audienti operator next [--json|--plan|--done|--skip|--fail|--return]",
3113
3504
  " audienti operator queue [--json]",
3114
3505
  " audienti operator outcome <row_id> --payload <file.json>",
3115
3506
  "",
3116
- "Status: read commands and prospect outcome writeback implemented",
3507
+ "Status: read commands and prospect next-move writeback implemented",
3117
3508
  "",
3118
3509
  "Filters:",
3119
3510
  " --principal <account_user_id>",
@@ -3126,10 +3517,19 @@ const HELP_TOPICS = new Map([
3126
3517
 
3127
3518
  ["operator next", [
3128
3519
  "Usage:",
3129
- " audienti operator next [--json] [filters] [--account <acct_id>]",
3520
+ " audienti operator next [--json|--plan|--done|--skip|--fail|--return] [filters] [--note <text>] [--account <acct_id>]",
3130
3521
  "",
3131
3522
  "Status: implemented",
3132
3523
  "",
3524
+ "Options:",
3525
+ " --plan Render a deterministic static plan from the existing next-action coach payload, CTA, and operator draft state",
3526
+ " --done Mark the current next prospect move completed through the operator outcome API",
3527
+ " --skip Mark the current next prospect move skipped through the operator outcome API",
3528
+ " --fail Mark the current next prospect move failed through the operator outcome API",
3529
+ " --return Mark the current next prospect move returned through the operator outcome API",
3530
+ " --note <text> Optional outcome note used with --done, --skip, --fail, or --return",
3531
+ " --occurred-at <ISO8601> Optional completion timestamp used with an outcome flag",
3532
+ "",
3133
3533
  "Output shape:",
3134
3534
  " next_move.id: row id",
3135
3535
  " next_move.prospect.prefix_id: prsp_ | null",
@@ -3141,7 +3541,8 @@ const HELP_TOPICS = new Map([
3141
3541
  " metrics: queue-builder metrics",
3142
3542
  "",
3143
3543
  "API:",
3144
- " GET /api/v1/accounts/:account_id/operator/next.json"
3544
+ " GET /api/v1/accounts/:account_id/operator/next.json",
3545
+ " POST /api/v1/accounts/:account_id/operator/outcome.json when an outcome flag is used"
3145
3546
  ].join("\n")],
3146
3547
 
3147
3548
  ["operator queue", [
@@ -3187,6 +3588,84 @@ const HELP_TOPICS = new Map([
3187
3588
  " POST /api/v1/accounts/:account_id/operator/outcome.json"
3188
3589
  ].join("\n")],
3189
3590
 
3591
+ ["analytics", [
3592
+ "Usage:",
3593
+ " audienti analytics prospects [--window 24h] [--user <account_user_id|email|name|me>] [--json]",
3594
+ " audienti analytics visibility [--window 24h] [--user <account_user_id|email|name|me>] [--json]",
3595
+ " audienti analytics visops [--window 24h] [--user <account_user_id|email|name|me>] [--json]",
3596
+ " audienti analytics content [--window 24h] [--user <account_user_id|email|name|me>] [--json]",
3597
+ "",
3598
+ "Status: implemented",
3599
+ "",
3600
+ "Window:",
3601
+ " --window <24h|7d|1w|day|week>",
3602
+ " --user <account_user_id|email|name|me> Narrow analytics to one account user. Email/name partials are accepted when they match exactly one account user.",
3603
+ "",
3604
+ "Output:",
3605
+ " Account-scoped analytics for prospects, visibility engagement, and ContentOps publishing."
3606
+ ].join("\n")],
3607
+
3608
+ ["analytics prospects", [
3609
+ "Usage:",
3610
+ " audienti analytics prospects [--window 24h] [--user <account_user_id|email|name|me>] [--json] [--account <acct_id>]",
3611
+ "",
3612
+ "Status: implemented",
3613
+ "",
3614
+ "Output shape:",
3615
+ " prospects_added_count: account prospects added in the window",
3616
+ " account_user: selected account user when --user is provided, otherwise null",
3617
+ " actions: outbound action totals, type breakdown, and automated percentage",
3618
+ " queue_stages[]: current account prospect stage counts",
3619
+ "",
3620
+ "API:",
3621
+ " GET /api/v1/accounts/:account_id/analytics/prospects.json"
3622
+ ].join("\n")],
3623
+
3624
+ ["analytics prospect", [
3625
+ "Usage:",
3626
+ " audienti analytics prospect [--window 24h] [--user <account_user_id|email|name|me>] [--json] [--account <acct_id>]",
3627
+ "",
3628
+ "Alias for `audienti analytics prospects`."
3629
+ ].join("\n")],
3630
+
3631
+ ["analytics visibility", [
3632
+ "Usage:",
3633
+ " audienti analytics visibility [--window 24h] [--user <account_user_id|email|name|me>] [--json] [--account <acct_id>]",
3634
+ "",
3635
+ "Status: implemented",
3636
+ "",
3637
+ "Output shape:",
3638
+ " unique_people_engaged_count: unique prospects or profiles touched by visibility actions in the window",
3639
+ " account_user: selected account user when --user is provided, otherwise null",
3640
+ " engagements: visibility action totals, type breakdown, and automated percentage",
3641
+ "",
3642
+ "API:",
3643
+ " GET /api/v1/accounts/:account_id/analytics/visibility.json"
3644
+ ].join("\n")],
3645
+
3646
+ ["analytics visops", [
3647
+ "Usage:",
3648
+ " audienti analytics visops [--window 24h] [--user <account_user_id|email|name|me>] [--json] [--account <acct_id>]",
3649
+ "",
3650
+ "Alias for `audienti analytics visibility`."
3651
+ ].join("\n")],
3652
+
3653
+ ["analytics content", [
3654
+ "Usage:",
3655
+ " audienti analytics content [--window 24h] [--user <account_user_id|email|name|me>] [--json] [--account <acct_id>]",
3656
+ "",
3657
+ "Status: implemented",
3658
+ "",
3659
+ "Output shape:",
3660
+ " account_user: selected account user when --user is provided, otherwise null",
3661
+ " published_posts_count: ContentOps work items published in the window",
3662
+ " stage_breakdown[]: current ContentOps work item stage counts",
3663
+ " execution_status_breakdown[]: current ContentOps execution status counts",
3664
+ "",
3665
+ "API:",
3666
+ " GET /api/v1/accounts/:account_id/analytics/content.json"
3667
+ ].join("\n")],
3668
+
3190
3669
  ["agent-workflows", [
3191
3670
  "Usage:",
3192
3671
  " audienti help agent-workflows",
@@ -3220,6 +3699,8 @@ const HELP_TOPICS = new Map([
3220
3699
  " audienti prospects show <prsp_id>",
3221
3700
  " audienti prospects timeline <prsp_id> --types post,comment,reaction --json",
3222
3701
  " audienti prospects message-types <prsp_id>",
3702
+ " audienti prospects add-profile <prsp_id> --url prospect@example.com",
3703
+ " audienti prospects report-bad-profile <prsp_id> <prof_id>",
3223
3704
  " audienti prospects add-note <prsp_id> --type steer --message \"Meeting will not happen\" --engagement-type action.meeting.canceled",
3224
3705
  " audienti prospects sequence-preview <prsp_id>",
3225
3706
  " audienti prospects sequence-export <prsp_id> --csv",
@@ -3230,9 +3711,15 @@ const HELP_TOPICS = new Map([
3230
3711
  "",
3231
3712
  "6. Work the operator queue",
3232
3713
  " audienti operator next",
3714
+ " audienti operator next --plan",
3233
3715
  " audienti operator queue --json",
3234
3716
  " audienti operator outcome <row_id> --payload <file.json>",
3235
3717
  "",
3718
+ "7. Inspect account analytics",
3719
+ " audienti analytics prospects --window 24h",
3720
+ " audienti analytics visibility --window 24h --user me",
3721
+ " audienti analytics content --window week",
3722
+ "",
3236
3723
  "Good defaults:",
3237
3724
  " Use --json when another tool or agent will parse the result.",
3238
3725
  " Use --account <acct_id> to avoid mutating the saved account during one-off runs.",