@audienti/cli 0.1.0
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 +11 -0
- package/LICENSE +19 -0
- package/README.md +62 -0
- package/bin/audienti.js +6 -0
- package/package.json +42 -0
- package/src/api-client.js +295 -0
- package/src/cli.js +3055 -0
- package/src/config.js +69 -0
package/src/cli.js
ADDED
|
@@ -0,0 +1,3055 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { ApiError, AudientiClient, DEFAULT_HOST, normalizeHost } from "./api-client.js";
|
|
4
|
+
import { configPath, deleteConfig, maskToken, readConfig, writeConfig } from "./config.js";
|
|
5
|
+
|
|
6
|
+
class CommandError extends Error {
|
|
7
|
+
constructor(message, { exitCode = 1 } = {}) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = "CommandError";
|
|
10
|
+
this.exitCode = exitCode;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const MAX_ALL_PROSPECTS = 1000;
|
|
15
|
+
const DEFAULT_LIST_LIMIT = 20;
|
|
16
|
+
const API_MAX_LIST_LIMIT = 100;
|
|
17
|
+
const DEFAULT_LOOKUP_TIMEOUT_SECONDS = 60;
|
|
18
|
+
const DEFAULT_LOOKUP_POLL_INTERVAL_SECONDS = 2;
|
|
19
|
+
const DEFAULT_PROFILE_IDENTIFIERS = [
|
|
20
|
+
"linkedin/profile",
|
|
21
|
+
"linkedin/company",
|
|
22
|
+
"twitter/profile",
|
|
23
|
+
"phone/profile",
|
|
24
|
+
"email/profile"
|
|
25
|
+
];
|
|
26
|
+
const DELETE_CONFIRMATION_VALUES = new Set(["yes", "true", "y"]);
|
|
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
|
+
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
|
+
|
|
30
|
+
export async function run(argv = process.argv.slice(2), deps = {}) {
|
|
31
|
+
const context = {
|
|
32
|
+
env: deps.env || process.env,
|
|
33
|
+
fetchImpl: deps.fetch || globalThis.fetch,
|
|
34
|
+
sleep: deps.sleep || sleep,
|
|
35
|
+
stdout: deps.stdout || process.stdout,
|
|
36
|
+
stderr: deps.stderr || process.stderr
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const exitCode = await dispatch(argv, context);
|
|
41
|
+
return exitCode ?? 0;
|
|
42
|
+
} catch (error) {
|
|
43
|
+
writeLine(context.stderr, `Error: ${error.message}`);
|
|
44
|
+
return error.exitCode ?? 1;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function dispatch(argv, context) {
|
|
49
|
+
const { args, accountOverride } = extractGlobalOptions(argv);
|
|
50
|
+
const helpTopic = helpTopicFromArgs(args);
|
|
51
|
+
|
|
52
|
+
if (helpTopic) {
|
|
53
|
+
writeLine(context.stdout, helpFor(helpTopic));
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const [resource, action, ...rest] = args;
|
|
58
|
+
const normalizedResource = normalizeResource(resource);
|
|
59
|
+
|
|
60
|
+
if (normalizedResource === "auth" && action === "token") return authToken(rest, context);
|
|
61
|
+
if (normalizedResource === "auth" && action === "status") return authStatus(rest, context, { accountOverride });
|
|
62
|
+
if (normalizedResource === "auth" && action === "logout") return authLogout(rest, context);
|
|
63
|
+
if (normalizedResource === "config" && action === "list") return configList(rest, context);
|
|
64
|
+
if (normalizedResource === "accounts" && action === "list") return accountsList(rest, context, { accountOverride });
|
|
65
|
+
if (normalizedResource === "accounts" && action === "select") return accountsSelect(rest, context);
|
|
66
|
+
if (normalizedResource === "users" && action === "list") return usersList(rest, context, { accountOverride });
|
|
67
|
+
if (normalizedResource === "offers" && action === "list") return offersList(rest, context, { accountOverride });
|
|
68
|
+
if (normalizedResource === "offers" && action === "create") return offersCreate(rest, context, { accountOverride });
|
|
69
|
+
if (normalizedResource === "icps" && action === "list") return icpsList(rest, context, { accountOverride });
|
|
70
|
+
if (normalizedResource === "icps" && action === "create") return icpsCreate(rest, context, { accountOverride });
|
|
71
|
+
if (normalizedResource === "companies" && action === "search") return companiesSearch(rest, context, { accountOverride });
|
|
72
|
+
if (normalizedResource === "lists" && action === "list") return listsList(rest, context, { accountOverride });
|
|
73
|
+
if (normalizedResource === "lists" && action === "create") return listsCreate(rest, context, { accountOverride });
|
|
74
|
+
if (normalizedResource === "lists" && action === "show") return listsShow(rest, context, { accountOverride });
|
|
75
|
+
if (normalizedResource === "lists" && action === "update") return listsUpdate(rest, context, { accountOverride });
|
|
76
|
+
if (normalizedResource === "lists" && action === "delete") return listsDelete(rest, context, { accountOverride });
|
|
77
|
+
if (normalizedResource === "lists" && action === "prospects") return listProspects(rest, context, { accountOverride });
|
|
78
|
+
if (normalizedResource === "lists" && action === "add-prospects") return listsAddProspects(rest, context, { accountOverride });
|
|
79
|
+
if (normalizedResource === "lists" && action === "remove-prospects") return listsRemoveProspects(rest, context, { accountOverride });
|
|
80
|
+
if (normalizedResource === "motions" && action === "list") return motionsList(rest, context, { accountOverride });
|
|
81
|
+
if (normalizedResource === "motions" && action === "show") return motionsShow(rest, context, { accountOverride });
|
|
82
|
+
if (normalizedResource === "motions" && action === "status") return motionsStatus(rest, context, { accountOverride });
|
|
83
|
+
if (normalizedResource === "motions" && action === "prospects") return motionsProspects(rest, context, { accountOverride });
|
|
84
|
+
if (normalizedResource === "motions" && action === "add-prospects") return motionsAddProspects(rest, context, { accountOverride });
|
|
85
|
+
if (normalizedResource === "motions" && action === "create") return motionsCreate(rest, context, { accountOverride });
|
|
86
|
+
if (normalizedResource === "prospects" && action === "list") return prospectsList(rest, context, { accountOverride });
|
|
87
|
+
if (normalizedResource === "prospects" && action === "show") return prospectsShow(rest, context, { accountOverride });
|
|
88
|
+
if (normalizedResource === "prospects" && action === "message-types") return prospectsMessageTypes(rest, context, { accountOverride });
|
|
89
|
+
if (normalizedResource === "prospects" && action === "write") return prospectsWrite(rest, context, { accountOverride });
|
|
90
|
+
if (normalizedResource === "prospects" && action === "add-note") return prospectsAddNote(rest, context, { accountOverride });
|
|
91
|
+
if (normalizedResource === "prospects" && action === "add-steer") return prospectsAddSteer(rest, context, { accountOverride });
|
|
92
|
+
if (normalizedResource === "prospects" && action === "sequence-preview") return prospectsSequencePreview(rest, context, { accountOverride });
|
|
93
|
+
if (normalizedResource === "prospects" && action === "import") return prospectsImport(rest, context, { accountOverride });
|
|
94
|
+
if (normalizedResource === "prospects" && action === "import-status") return prospectsImportStatus(rest, context, { accountOverride });
|
|
95
|
+
if (normalizedResource === "tools" && action === "get") return toolsGet(rest, context, { accountOverride });
|
|
96
|
+
if (normalizedResource === "operator" && action === "queue") return operatorQueue(rest, context, { accountOverride });
|
|
97
|
+
if (normalizedResource === "operator" && action === "next") return operatorNext(rest, context, { accountOverride });
|
|
98
|
+
if (normalizedResource === "operator" && action === "outcome") return operatorOutcome(rest, context, { accountOverride });
|
|
99
|
+
|
|
100
|
+
throw new CommandError(usage(), { exitCode: resource ? 1 : 0 });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function extractGlobalOptions(argv) {
|
|
104
|
+
const args = [];
|
|
105
|
+
let accountOverride;
|
|
106
|
+
|
|
107
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
108
|
+
const arg = argv[index];
|
|
109
|
+
|
|
110
|
+
if (arg === "--account") {
|
|
111
|
+
accountOverride = argv[index + 1];
|
|
112
|
+
if (!accountOverride || accountOverride.startsWith("--")) {
|
|
113
|
+
throw new CommandError("--account requires an account id.");
|
|
114
|
+
}
|
|
115
|
+
index += 1;
|
|
116
|
+
} else if (arg.startsWith("--account=")) {
|
|
117
|
+
accountOverride = arg.slice("--account=".length);
|
|
118
|
+
if (!accountOverride) throw new CommandError("--account requires an account id.");
|
|
119
|
+
} else {
|
|
120
|
+
args.push(arg);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return { args, accountOverride };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function helpTopicFromArgs(args) {
|
|
128
|
+
if (args.length === 0) return [];
|
|
129
|
+
if (args[0] === "--help" || args[0] === "-h") return [];
|
|
130
|
+
if (args.at(-1) === "help") return normalizeTopicParts(args.slice(0, -1));
|
|
131
|
+
if (args[0] === "help") return normalizeTopicParts(args.slice(1));
|
|
132
|
+
|
|
133
|
+
const helpIndex = args.findIndex((arg) => arg === "--help" || arg === "-h");
|
|
134
|
+
if (helpIndex === -1) return null;
|
|
135
|
+
|
|
136
|
+
return normalizeTopicParts(args.slice(0, helpIndex));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function normalizeTopicParts(parts) {
|
|
140
|
+
if (parts[0] === "plays") return ["motions", ...parts.slice(1)];
|
|
141
|
+
if (parts[0] === "principals") return ["users", ...parts.slice(1)];
|
|
142
|
+
return parts;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function normalizeResource(resource) {
|
|
146
|
+
if (resource === "principals") return "users";
|
|
147
|
+
return resource === "plays" ? "motions" : resource;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function authToken(args, context) {
|
|
151
|
+
const { values, positionals } = parseCommandArgs(args, {
|
|
152
|
+
host: { type: "string" }
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
if (positionals.length !== 1) {
|
|
156
|
+
throw new CommandError("Usage: audienti auth token <token> [--host https://app.audienti.com]");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const token = positionals[0].trim();
|
|
160
|
+
if (!token) throw new CommandError("API token cannot be blank.");
|
|
161
|
+
|
|
162
|
+
const host = normalizeHost(values.host || DEFAULT_HOST);
|
|
163
|
+
const client = new AudientiClient({ host, token, fetchImpl: context.fetchImpl });
|
|
164
|
+
const user = await client.me();
|
|
165
|
+
|
|
166
|
+
await writeConfig({ host, token }, { env: context.env });
|
|
167
|
+
|
|
168
|
+
const userLabel = user?.name || user?.email || user?.id;
|
|
169
|
+
writeLine(context.stdout, userLabel ? `Authenticated to ${host} as ${userLabel}.` : `Authenticated to ${host}.`);
|
|
170
|
+
writeLine(context.stdout, "Run `audienti accounts list` to choose an account.");
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async function authStatus(args, context, { accountOverride } = {}) {
|
|
174
|
+
assertNoPositionals(args, "Usage: audienti auth status [--account <acct_id>]");
|
|
175
|
+
|
|
176
|
+
const config = await readConfig({ env: context.env });
|
|
177
|
+
if (!config.token) {
|
|
178
|
+
writeLine(context.stdout, "Not authenticated. Run `audienti auth token <token>`.");
|
|
179
|
+
return 1;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const client = clientFromConfig(config, context);
|
|
183
|
+
const user = await client.me();
|
|
184
|
+
const userLabel = user?.name || user?.email || user?.id || "unknown user";
|
|
185
|
+
const accountId = accountOverride || config.accountId;
|
|
186
|
+
const accountSuffix = accountOverride && accountOverride !== config.accountId ? " (override)" : "";
|
|
187
|
+
|
|
188
|
+
writeLine(context.stdout, `Host: ${client.host}`);
|
|
189
|
+
writeLine(context.stdout, `Token: ${maskToken(config.token)}`);
|
|
190
|
+
writeLine(context.stdout, `User: ${userLabel}`);
|
|
191
|
+
|
|
192
|
+
if (accountId) {
|
|
193
|
+
const name = !accountOverride && config.accountName ? `${config.accountName} ` : "";
|
|
194
|
+
writeLine(context.stdout, `Active account: ${name}(${accountId})${accountSuffix}`);
|
|
195
|
+
} else {
|
|
196
|
+
writeLine(context.stdout, "Active account: none selected");
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function authLogout(args, context) {
|
|
201
|
+
assertNoPositionals(args, "Usage: audienti auth logout");
|
|
202
|
+
|
|
203
|
+
await deleteConfig({ env: context.env });
|
|
204
|
+
writeLine(context.stdout, "Logged out.");
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async function configList(args, context) {
|
|
208
|
+
const { values, positionals } = parseCommandArgs(args, jsonOptions());
|
|
209
|
+
if (positionals.length > 0) throw new CommandError("Usage: audienti config list [--json]");
|
|
210
|
+
|
|
211
|
+
const filePath = configPath(context.env);
|
|
212
|
+
const config = await readConfig({ env: context.env });
|
|
213
|
+
const payload = {
|
|
214
|
+
path: filePath,
|
|
215
|
+
exists: Object.keys(config).length > 0,
|
|
216
|
+
host: config.host || null,
|
|
217
|
+
token: config.token ? maskToken(config.token) : null,
|
|
218
|
+
accountId: config.accountId || null,
|
|
219
|
+
accountName: config.accountName || null
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
if (values.json) return writeJson(context.stdout, payload);
|
|
223
|
+
|
|
224
|
+
writeLine(context.stdout, `Path: ${payload.path}`);
|
|
225
|
+
writeLine(context.stdout, `Exists: ${payload.exists ? "yes" : "no"}`);
|
|
226
|
+
writeLine(context.stdout, `Host: ${payload.host || "none"}`);
|
|
227
|
+
writeLine(context.stdout, `Token: ${payload.token || "none"}`);
|
|
228
|
+
|
|
229
|
+
if (payload.accountId) {
|
|
230
|
+
const name = payload.accountName ? `${payload.accountName} ` : "";
|
|
231
|
+
writeLine(context.stdout, `Active account: ${name}(${payload.accountId})`);
|
|
232
|
+
} else {
|
|
233
|
+
writeLine(context.stdout, "Active account: none selected");
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async function accountsList(args, context, { accountOverride } = {}) {
|
|
238
|
+
const { values, positionals } = parseCommandArgs(args, {
|
|
239
|
+
json: { type: "boolean" }
|
|
240
|
+
});
|
|
241
|
+
if (positionals.length > 0) throw new CommandError("Usage: audienti accounts list [--json] [--account <acct_id>]");
|
|
242
|
+
|
|
243
|
+
const config = await requireAuthenticatedConfig(context);
|
|
244
|
+
const accounts = await clientFromConfig(config, context).accounts();
|
|
245
|
+
const activeAccountId = accountOverride || config.accountId;
|
|
246
|
+
|
|
247
|
+
if (values.json) {
|
|
248
|
+
writeLine(context.stdout, JSON.stringify(accounts, null, 2));
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (!accounts.length) {
|
|
253
|
+
writeLine(context.stdout, "No accounts found.");
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
writeLine(context.stdout, " ACCOUNT ID\tNAME");
|
|
258
|
+
for (const account of accounts) {
|
|
259
|
+
const accountId = account.prefix_id;
|
|
260
|
+
if (!accountId) throw new CommandError("Account payload is missing prefix_id.");
|
|
261
|
+
|
|
262
|
+
const marker = accountId === activeAccountId ? "*" : " ";
|
|
263
|
+
writeLine(context.stdout, `${marker} ${accountId}\t${account.name}`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async function accountsSelect(args, context) {
|
|
268
|
+
const { positionals } = parseCommandArgs(args, {});
|
|
269
|
+
if (positionals.length !== 1) throw new CommandError("Usage: audienti accounts select <acct_id>");
|
|
270
|
+
|
|
271
|
+
const requestedAccountId = positionals[0];
|
|
272
|
+
const config = await requireAuthenticatedConfig(context);
|
|
273
|
+
const accounts = await clientFromConfig(config, context).accounts();
|
|
274
|
+
const account = resolveAccountSelection(accounts, requestedAccountId);
|
|
275
|
+
|
|
276
|
+
if (!account) {
|
|
277
|
+
throw new CommandError(`Account ${requestedAccountId} does not exist or is not visible to this token.`);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
await writeConfig({
|
|
281
|
+
...config,
|
|
282
|
+
accountId: account.prefix_id,
|
|
283
|
+
accountName: account.name
|
|
284
|
+
}, { env: context.env });
|
|
285
|
+
|
|
286
|
+
writeLine(context.stdout, `Selected account ${account.name} (${account.prefix_id}).`);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function resolveAccountSelection(accounts, term) {
|
|
290
|
+
const requested = String(term || "").trim();
|
|
291
|
+
if (!requested) return null;
|
|
292
|
+
|
|
293
|
+
const exactPrefix = accounts.find((candidate) => candidate.prefix_id === requested);
|
|
294
|
+
if (exactPrefix) return exactPrefix;
|
|
295
|
+
|
|
296
|
+
const normalizedRequested = requested.toLowerCase();
|
|
297
|
+
const exactName = accounts.find((candidate) => String(candidate.name || "").toLowerCase() === normalizedRequested);
|
|
298
|
+
if (exactName) return exactName;
|
|
299
|
+
|
|
300
|
+
const matches = accounts.filter((candidate) => {
|
|
301
|
+
const prefixId = String(candidate.prefix_id || "").toLowerCase();
|
|
302
|
+
const name = String(candidate.name || "").toLowerCase();
|
|
303
|
+
return prefixId.includes(normalizedRequested) || name.includes(normalizedRequested);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
if (matches.length === 1) return matches[0];
|
|
307
|
+
if (matches.length === 0) return null;
|
|
308
|
+
|
|
309
|
+
const options = matches.map((candidate) => `${candidate.name} (${candidate.prefix_id})`).join(", ");
|
|
310
|
+
throw new CommandError(`Account term "${requested}" matched multiple accounts: ${options}.`);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async function listsList(args, context, { accountOverride } = {}) {
|
|
314
|
+
const { values, positionals } = parseCommandArgs(args, jsonOptions());
|
|
315
|
+
if (positionals.length > 0) throw new CommandError("Usage: audienti lists list [--json] [--account <acct_id>]");
|
|
316
|
+
|
|
317
|
+
const { client, accountId } = await requireAccountContext(context, { accountOverride });
|
|
318
|
+
const lists = await client.lists(accountId);
|
|
319
|
+
if (values.json) return writeJson(context.stdout, lists);
|
|
320
|
+
|
|
321
|
+
renderLists(lists, context);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
async function usersList(args, context, { accountOverride } = {}) {
|
|
325
|
+
const { values, positionals } = parseCommandArgs(args, jsonOptions());
|
|
326
|
+
if (positionals.length > 0) throw new CommandError("Usage: audienti users list [--json] [--account <acct_id>]");
|
|
327
|
+
|
|
328
|
+
const { client, accountId } = await requireAccountContext(context, { accountOverride });
|
|
329
|
+
const users = await client.users(accountId);
|
|
330
|
+
if (values.json) return writeJson(context.stdout, users);
|
|
331
|
+
|
|
332
|
+
renderUsers(users, context);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async function offersList(args, context, { accountOverride } = {}) {
|
|
336
|
+
const { values, positionals } = parseCommandArgs(args, jsonOptions());
|
|
337
|
+
if (positionals.length > 0) throw new CommandError("Usage: audienti offers list [--json] [--account <acct_id>]");
|
|
338
|
+
|
|
339
|
+
const { client, accountId } = await requireAccountContext(context, { accountOverride });
|
|
340
|
+
const offers = await client.offers(accountId);
|
|
341
|
+
if (values.json) return writeJson(context.stdout, offers);
|
|
342
|
+
|
|
343
|
+
renderOffers(offers, context);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
async function offersCreate(args, context, { accountOverride } = {}) {
|
|
347
|
+
const { values, positionals } = parseCommandArgs(args, {
|
|
348
|
+
...jsonOptions(),
|
|
349
|
+
name: { type: "string" },
|
|
350
|
+
description: { type: "string" },
|
|
351
|
+
url: { type: "string" }
|
|
352
|
+
});
|
|
353
|
+
if (positionals.length > 0 || !values.name || (!values.description && !values.url)) {
|
|
354
|
+
throw new CommandError("Usage: audienti offers create --name <text> [--description <text>] [--url <url>] [--json] [--account <acct_id>]");
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const { client, accountId } = await requireAccountContext(context, { accountOverride });
|
|
358
|
+
const offer = await client.createOffer(accountId, {
|
|
359
|
+
offer: compactObject({
|
|
360
|
+
name: values.name,
|
|
361
|
+
description: values.description,
|
|
362
|
+
url: values.url
|
|
363
|
+
})
|
|
364
|
+
});
|
|
365
|
+
if (values.json) return writeJson(context.stdout, offer);
|
|
366
|
+
|
|
367
|
+
writeLine(context.stdout, `Created offer ${display(offer?.name)} (${display(offer?.prefix_id)}).`);
|
|
368
|
+
if (offer?.description) writeLine(context.stdout, `Description: ${offer.description}`);
|
|
369
|
+
if (offer?.url) writeLine(context.stdout, `URL: ${offer.url}`);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
async function icpsList(args, context, { accountOverride } = {}) {
|
|
373
|
+
const { values, positionals } = parseCommandArgs(args, jsonOptions());
|
|
374
|
+
if (positionals.length > 0) throw new CommandError("Usage: audienti icps list [--json] [--account <acct_id>]");
|
|
375
|
+
|
|
376
|
+
const { client, accountId } = await requireAccountContext(context, { accountOverride });
|
|
377
|
+
const icps = await client.icps(accountId);
|
|
378
|
+
if (values.json) return writeJson(context.stdout, icps);
|
|
379
|
+
|
|
380
|
+
renderIcps(icps, context);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
async function icpsCreate(args, context, { accountOverride } = {}) {
|
|
384
|
+
const { values, positionals } = parseCommandArgs(args, {
|
|
385
|
+
...jsonOptions(),
|
|
386
|
+
payload: { type: "string" },
|
|
387
|
+
name: { type: "string" },
|
|
388
|
+
notes: { type: "string" },
|
|
389
|
+
"discovery-keyword": { type: "string" }
|
|
390
|
+
});
|
|
391
|
+
if (positionals.length > 0) {
|
|
392
|
+
throw new CommandError("Usage: audienti icps create (--name <text> [--notes <text>] [--discovery-keyword <text>] | --payload <file.json>) [--json] [--account <acct_id>]");
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const { client, accountId } = await requireAccountContext(context, { accountOverride });
|
|
396
|
+
const icpPayload = await icpCreatePayload(values);
|
|
397
|
+
const icp = await client.createIcp(accountId, { icp: icpPayload });
|
|
398
|
+
if (values.json) return writeJson(context.stdout, icp);
|
|
399
|
+
|
|
400
|
+
writeLine(context.stdout, `Created ICP ${display(icp?.name)} (${display(icp?.prefix_id)}).`);
|
|
401
|
+
if (icp?.notes) writeLine(context.stdout, `Notes: ${icp.notes}`);
|
|
402
|
+
if (icp?.discovery_keyword) writeLine(context.stdout, `Discovery keyword: ${icp.discovery_keyword}`);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
async function companiesSearch(args, context, { accountOverride } = {}) {
|
|
406
|
+
const { values, positionals } = parseCommandArgs(args, {
|
|
407
|
+
...jsonOptions(),
|
|
408
|
+
query: { type: "string" }
|
|
409
|
+
});
|
|
410
|
+
if (positionals.length > 0 || !values.query) {
|
|
411
|
+
throw new CommandError("Usage: audienti companies search --query <text> [--json] [--account <acct_id>]");
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const { client, accountId } = await requireAccountContext(context, { accountOverride });
|
|
415
|
+
const payload = await client.companies(accountId, { query: values.query });
|
|
416
|
+
if (values.json) return writeJson(context.stdout, payload);
|
|
417
|
+
|
|
418
|
+
renderCompanies(payload, context);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
async function listsCreate(args, context, { accountOverride } = {}) {
|
|
422
|
+
const { values, positionals } = parseCommandArgs(args, {
|
|
423
|
+
...jsonOptions(),
|
|
424
|
+
name: { type: "string" },
|
|
425
|
+
description: { type: "string" },
|
|
426
|
+
"campaign-hook": { type: "string" },
|
|
427
|
+
"audience-note": { type: "string" }
|
|
428
|
+
});
|
|
429
|
+
if (positionals.length > 0 || !values.name) {
|
|
430
|
+
throw new CommandError("Usage: audienti lists create --name <text> [--description <text>] [--campaign-hook <text>] [--audience-note <text>] [--json] [--account <acct_id>]");
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const { client, accountId } = await requireAccountContext(context, { accountOverride });
|
|
434
|
+
const campaignBrief = compactObject({
|
|
435
|
+
hook: values["campaign-hook"],
|
|
436
|
+
audience_note: values["audience-note"]
|
|
437
|
+
});
|
|
438
|
+
const payload = await client.createList(accountId, {
|
|
439
|
+
list: compactObject({
|
|
440
|
+
name: values.name,
|
|
441
|
+
description: values.description,
|
|
442
|
+
campaign_brief: Object.keys(campaignBrief).length > 0 ? campaignBrief : undefined
|
|
443
|
+
})
|
|
444
|
+
});
|
|
445
|
+
if (values.json) return writeJson(context.stdout, payload);
|
|
446
|
+
|
|
447
|
+
writeLine(context.stdout, `Created list ${display(payload?.name)} (${display(payload?.prefix_id)}).`);
|
|
448
|
+
if (payload?.description) writeLine(context.stdout, `Description: ${payload.description}`);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
async function listsShow(args, context, { accountOverride } = {}) {
|
|
452
|
+
const { values, positionals } = parseCommandArgs(args, jsonOptions());
|
|
453
|
+
if (positionals.length !== 1) throw new CommandError("Usage: audienti lists show <list_id> [--json] [--account <acct_id>]");
|
|
454
|
+
|
|
455
|
+
const { client, accountId } = await requireAccountContext(context, { accountOverride });
|
|
456
|
+
const list = await client.list(accountId, positionals[0]);
|
|
457
|
+
if (values.json) return writeJson(context.stdout, list);
|
|
458
|
+
|
|
459
|
+
renderList(list, context);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
async function listsUpdate(args, context, { accountOverride } = {}) {
|
|
463
|
+
const { values, positionals } = parseCommandArgs(args, {
|
|
464
|
+
...jsonOptions(),
|
|
465
|
+
name: { type: "string" },
|
|
466
|
+
description: { type: "string" },
|
|
467
|
+
"campaign-hook": { type: "string" },
|
|
468
|
+
"audience-note": { type: "string" }
|
|
469
|
+
});
|
|
470
|
+
const hasCampaignUpdate = values["campaign-hook"] || values["audience-note"];
|
|
471
|
+
const hasUpdateField = values.name || values.description || hasCampaignUpdate;
|
|
472
|
+
if (positionals.length !== 1 || !hasUpdateField) {
|
|
473
|
+
throw new CommandError("Usage: audienti lists update <list_id> [--name <text>] [--description <text>] [--campaign-hook <text>] [--audience-note <text>] [--json] [--account <acct_id>]");
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
const { client, accountId } = await requireAccountContext(context, { accountOverride });
|
|
477
|
+
const campaignBrief = compactObject({
|
|
478
|
+
hook: values["campaign-hook"],
|
|
479
|
+
audience_note: values["audience-note"]
|
|
480
|
+
});
|
|
481
|
+
const payload = await client.updateList(accountId, positionals[0], {
|
|
482
|
+
list: compactObject({
|
|
483
|
+
name: values.name,
|
|
484
|
+
description: values.description,
|
|
485
|
+
campaign_brief: Object.keys(campaignBrief).length > 0 ? campaignBrief : undefined
|
|
486
|
+
})
|
|
487
|
+
});
|
|
488
|
+
if (values.json) return writeJson(context.stdout, payload);
|
|
489
|
+
|
|
490
|
+
writeLine(context.stdout, `Updated list ${display(payload?.name)} (${display(payload?.prefix_id)}).`);
|
|
491
|
+
if (payload?.description) writeLine(context.stdout, `Description: ${payload.description}`);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
async function listsDelete(args, context, { accountOverride } = {}) {
|
|
495
|
+
const { values, positionals } = parseCommandArgs(args, {
|
|
496
|
+
...jsonOptions(),
|
|
497
|
+
confirm: { type: "string" }
|
|
498
|
+
});
|
|
499
|
+
const normalizedConfirm = String(values.confirm || "").trim().toLowerCase();
|
|
500
|
+
if (positionals.length !== 1 || !DELETE_CONFIRMATION_VALUES.has(normalizedConfirm)) {
|
|
501
|
+
throw new CommandError("Usage: audienti lists delete <list_id> --confirm <yes|true|Y|y> [--json] [--account <acct_id>]");
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const { client, accountId } = await requireAccountContext(context, { accountOverride });
|
|
505
|
+
const payload = await client.deleteList(accountId, positionals[0]);
|
|
506
|
+
if (values.json) return writeJson(context.stdout, payload);
|
|
507
|
+
|
|
508
|
+
writeLine(context.stdout, `Deleted list ${display(payload?.name)} (${display(payload?.prefix_id)}).`);
|
|
509
|
+
if (payload?.reassigned_agent_count !== undefined) {
|
|
510
|
+
writeLine(context.stdout, `Reassigned agents: ${display(payload.reassigned_agent_count, 0)}`);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
async function listProspects(args, context, { accountOverride } = {}) {
|
|
515
|
+
const { values, positionals } = parseCommandArgs(args, {
|
|
516
|
+
...jsonOptions(),
|
|
517
|
+
all: { type: "boolean" },
|
|
518
|
+
csv: { type: "boolean" },
|
|
519
|
+
limit: { type: "string" },
|
|
520
|
+
offset: { type: "string" },
|
|
521
|
+
page: { type: "string" },
|
|
522
|
+
profiles: { type: "boolean" },
|
|
523
|
+
wide: { type: "boolean" }
|
|
524
|
+
});
|
|
525
|
+
if (positionals.length !== 1) throw new CommandError("Usage: audienti lists prospects <list_id> [--json] [options] [--account <acct_id>]");
|
|
526
|
+
if (values.csv && values.json) throw new CommandError("Choose one output format: use either --csv or --json.");
|
|
527
|
+
if (values.page && values.offset) throw new CommandError("Choose one pagination mode: use either --page or --offset.");
|
|
528
|
+
if (values.all && (values.page || values.offset)) throw new CommandError("--all cannot be combined with --page or --offset.");
|
|
529
|
+
|
|
530
|
+
const { client, accountId } = await requireAccountContext(context, { accountOverride });
|
|
531
|
+
const listId = positionals[0];
|
|
532
|
+
const query = compactObject({
|
|
533
|
+
limit: values.limit,
|
|
534
|
+
offset: values.offset,
|
|
535
|
+
page: values.page,
|
|
536
|
+
include_profiles: values.profiles
|
|
537
|
+
});
|
|
538
|
+
const payload = values.all ?
|
|
539
|
+
await fetchAllPages((pageQuery) => client.listProspects(accountId, listId, pageQuery), query, { totalLimit: parseProspectTotalLimit(values.limit) }) :
|
|
540
|
+
await client.listProspects(accountId, listId, query);
|
|
541
|
+
|
|
542
|
+
if (values.json) return writeJson(context.stdout, payload);
|
|
543
|
+
if (values.csv) return writeLine(context.stdout, prospectsToCsv(payload?.prospects || []));
|
|
544
|
+
|
|
545
|
+
renderProspects(payload, context, { wide: values.wide || values.all, profiles: values.profiles });
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
async function listsAddProspects(args, context, { accountOverride } = {}) {
|
|
549
|
+
const { values, positionals } = parseCommandArgs(args, jsonOptions());
|
|
550
|
+
if (positionals.length < 2) {
|
|
551
|
+
throw new CommandError("Usage: audienti lists add-prospects <list_id> <prsp_id> [prsp_id...] [--json] [--account <acct_id>]");
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const [listId, ...prospectIds] = positionals;
|
|
555
|
+
const { client, accountId } = await requireAccountContext(context, { accountOverride });
|
|
556
|
+
const { payload, rejected } = await performBulkMutation(() =>
|
|
557
|
+
client.addListProspects(accountId, listId, { prospect_ids: prospectIds }));
|
|
558
|
+
if (values.json) {
|
|
559
|
+
writeJson(context.stdout, payload);
|
|
560
|
+
return rejected ? 1 : 0;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
renderBulkMutationResult(payload, context, {
|
|
564
|
+
successLabel: `Added ${successCount(payload)} prospects to list ${listId}.`,
|
|
565
|
+
zeroSuccessLabel: `No prospects were added to list ${listId}.`
|
|
566
|
+
});
|
|
567
|
+
return rejected ? 1 : 0;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
async function listsRemoveProspects(args, context, { accountOverride } = {}) {
|
|
571
|
+
const { values, positionals } = parseCommandArgs(args, jsonOptions());
|
|
572
|
+
if (positionals.length < 2) {
|
|
573
|
+
throw new CommandError("Usage: audienti lists remove-prospects <list_id> <prsp_id> [prsp_id...] [--json] [--account <acct_id>]");
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
const [listId, ...prospectIds] = positionals;
|
|
577
|
+
const { client, accountId } = await requireAccountContext(context, { accountOverride });
|
|
578
|
+
const { payload, rejected } = await performBulkMutation(() =>
|
|
579
|
+
client.removeListProspects(accountId, listId, { prospect_ids: prospectIds }));
|
|
580
|
+
if (values.json) {
|
|
581
|
+
writeJson(context.stdout, payload);
|
|
582
|
+
return rejected ? 1 : 0;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
renderBulkMutationResult(payload, context, {
|
|
586
|
+
successLabel: `Removed ${successCount(payload)} prospects from list ${listId}.`,
|
|
587
|
+
zeroSuccessLabel: `No prospects were removed from list ${listId}.`
|
|
588
|
+
});
|
|
589
|
+
return rejected ? 1 : 0;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
async function motionsList(args, context, { accountOverride } = {}) {
|
|
593
|
+
const { values, positionals } = parseCommandArgs(args, jsonOptions());
|
|
594
|
+
if (positionals.length > 0) throw new CommandError("Usage: audienti motions list [--json] [--account <acct_id>]");
|
|
595
|
+
|
|
596
|
+
const { client, accountId } = await requireAccountContext(context, { accountOverride });
|
|
597
|
+
const motions = await client.motions(accountId);
|
|
598
|
+
if (values.json) return writeJson(context.stdout, motions);
|
|
599
|
+
|
|
600
|
+
renderMotions(motions, context);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
async function motionsShow(args, context, { accountOverride } = {}) {
|
|
604
|
+
const { values, positionals } = parseCommandArgs(args, jsonOptions());
|
|
605
|
+
if (positionals.length !== 1) throw new CommandError("Usage: audienti motions show <motn_id> [--json] [--account <acct_id>]");
|
|
606
|
+
|
|
607
|
+
const { client, accountId } = await requireAccountContext(context, { accountOverride });
|
|
608
|
+
const motion = await client.motion(accountId, positionals[0]);
|
|
609
|
+
if (values.json) return writeJson(context.stdout, motion);
|
|
610
|
+
|
|
611
|
+
renderMotion(motion, context);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
async function motionsStatus(args, context, { accountOverride } = {}) {
|
|
615
|
+
const { values, positionals } = parseCommandArgs(args, jsonOptions());
|
|
616
|
+
if (positionals.length !== 1) throw new CommandError("Usage: audienti motions status <motn_id> [--json] [--account <acct_id>]");
|
|
617
|
+
|
|
618
|
+
const { client, accountId } = await requireAccountContext(context, { accountOverride });
|
|
619
|
+
const status = await client.motionStatus(accountId, positionals[0]);
|
|
620
|
+
if (values.json) return writeJson(context.stdout, status);
|
|
621
|
+
|
|
622
|
+
renderMotionStatus(status, context);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
async function motionsProspects(args, context, { accountOverride } = {}) {
|
|
626
|
+
const { values, positionals } = parseCommandArgs(args, {
|
|
627
|
+
...jsonOptions(),
|
|
628
|
+
all: { type: "boolean" },
|
|
629
|
+
csv: { type: "boolean" },
|
|
630
|
+
limit: { type: "string" },
|
|
631
|
+
offset: { type: "string" },
|
|
632
|
+
page: { type: "string" },
|
|
633
|
+
profiles: { type: "boolean" },
|
|
634
|
+
wide: { type: "boolean" }
|
|
635
|
+
});
|
|
636
|
+
if (positionals.length !== 1) throw new CommandError("Usage: audienti motions prospects <motn_id> [--json] [options] [--account <acct_id>]");
|
|
637
|
+
if (values.csv && values.json) throw new CommandError("Choose one output format: use either --csv or --json.");
|
|
638
|
+
if (values.page && values.offset) throw new CommandError("Choose one pagination mode: use either --page or --offset.");
|
|
639
|
+
if (values.all && (values.page || values.offset)) throw new CommandError("--all cannot be combined with --page or --offset.");
|
|
640
|
+
|
|
641
|
+
const { client, accountId } = await requireAccountContext(context, { accountOverride });
|
|
642
|
+
const motionId = positionals[0];
|
|
643
|
+
const query = compactObject({
|
|
644
|
+
limit: values.limit,
|
|
645
|
+
offset: values.offset,
|
|
646
|
+
page: values.page,
|
|
647
|
+
include_profiles: values.profiles
|
|
648
|
+
});
|
|
649
|
+
const payload = values.all ?
|
|
650
|
+
await fetchAllPages((pageQuery) => client.motionProspects(accountId, motionId, pageQuery), query, { totalLimit: parseProspectTotalLimit(values.limit) }) :
|
|
651
|
+
await client.motionProspects(accountId, motionId, query);
|
|
652
|
+
|
|
653
|
+
if (values.json) return writeJson(context.stdout, payload);
|
|
654
|
+
if (values.csv) return writeLine(context.stdout, prospectsToCsv(payload?.prospects || []));
|
|
655
|
+
|
|
656
|
+
renderProspects(payload, context, { wide: values.wide || values.all, profiles: values.profiles });
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
async function motionsAddProspects(args, context, { accountOverride } = {}) {
|
|
660
|
+
const { values, positionals } = parseCommandArgs(args, {
|
|
661
|
+
...jsonOptions(),
|
|
662
|
+
"assigned-user": { type: "string" }
|
|
663
|
+
});
|
|
664
|
+
if (positionals.length < 2) {
|
|
665
|
+
throw new CommandError("Usage: audienti motions add-prospects <motn_id> <prsp_id> [prsp_id...] [--assigned-user <id|me>] [--json] [--account <acct_id>]");
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
const [motionId, ...prospectIds] = positionals;
|
|
669
|
+
const { client, accountId } = await requireAccountContext(context, { accountOverride });
|
|
670
|
+
const { payload, rejected } = await performBulkMutation(() =>
|
|
671
|
+
client.addMotionProspects(accountId, motionId, compactObject({
|
|
672
|
+
prospect_ids: prospectIds,
|
|
673
|
+
assigned_user_id: values["assigned-user"]
|
|
674
|
+
})));
|
|
675
|
+
if (values.json) {
|
|
676
|
+
writeJson(context.stdout, payload);
|
|
677
|
+
return rejected ? 1 : 0;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
renderBulkMutationResult(payload, context, {
|
|
681
|
+
successLabel: `Assigned ${successCount(payload)} prospects to motion ${motionId}.`,
|
|
682
|
+
zeroSuccessLabel: `No prospects were assigned to motion ${motionId}.`
|
|
683
|
+
});
|
|
684
|
+
return rejected ? 1 : 0;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
async function motionsCreate(args, context, { accountOverride } = {}) {
|
|
688
|
+
const { values, positionals } = parseCommandArgs(args, {
|
|
689
|
+
...jsonOptions(),
|
|
690
|
+
payload: { type: "string" }
|
|
691
|
+
});
|
|
692
|
+
if (positionals.length > 0 || !values.payload) {
|
|
693
|
+
throw new CommandError("Usage: audienti motions create --payload <file.json> [--json] [--account <acct_id>]");
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
const { client, accountId } = await requireAccountContext(context, { accountOverride });
|
|
697
|
+
const payload = await readJsonPayload(values.payload);
|
|
698
|
+
const created = await client.createMotion(accountId, { motion: payload });
|
|
699
|
+
if (values.json) return writeJson(context.stdout, created);
|
|
700
|
+
|
|
701
|
+
writeLine(context.stdout, `Created motion ${display(created?.name)} (${display(created?.prefix_id)}).`);
|
|
702
|
+
renderMotion(created, context);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
async function prospectsList(args, context, { accountOverride } = {}) {
|
|
706
|
+
const { values, positionals } = parseCommandArgs(args, {
|
|
707
|
+
...jsonOptions(),
|
|
708
|
+
all: { type: "boolean" },
|
|
709
|
+
csv: { type: "boolean" },
|
|
710
|
+
query: { type: "string" },
|
|
711
|
+
company: { type: "string" },
|
|
712
|
+
"company-profile": { type: "string" },
|
|
713
|
+
motion: { type: "string" },
|
|
714
|
+
play: { type: "string" },
|
|
715
|
+
list: { type: "string" },
|
|
716
|
+
stage: { type: "string" },
|
|
717
|
+
"assigned-user": { type: "string" },
|
|
718
|
+
limit: { type: "string" },
|
|
719
|
+
offset: { type: "string" },
|
|
720
|
+
page: { type: "string" },
|
|
721
|
+
profiles: { type: "boolean" },
|
|
722
|
+
wide: { type: "boolean" }
|
|
723
|
+
});
|
|
724
|
+
if (positionals.length > 0) throw new CommandError("Usage: audienti prospects list [--json] [filters] [--account <acct_id>]");
|
|
725
|
+
if (values.csv && values.json) throw new CommandError("Choose one output format: use either --csv or --json.");
|
|
726
|
+
if (values.page && values.offset) throw new CommandError("Choose one pagination mode: use either --page or --offset.");
|
|
727
|
+
if (values.all && (values.page || values.offset)) throw new CommandError("--all cannot be combined with --page or --offset.");
|
|
728
|
+
if (values.motion && values.play) throw new CommandError("Choose one motion filter: use either --motion or --play.");
|
|
729
|
+
if (values.company && values["company-profile"]) throw new CommandError("Choose one company filter: use either --company or --company-profile.");
|
|
730
|
+
|
|
731
|
+
const { client, accountId } = await requireAccountContext(context, { accountOverride });
|
|
732
|
+
const query = compactObject({
|
|
733
|
+
query: values.query,
|
|
734
|
+
company: values.company,
|
|
735
|
+
company_profile_id: values["company-profile"],
|
|
736
|
+
motion_id: values.motion,
|
|
737
|
+
play_id: values.play,
|
|
738
|
+
list_id: values.list,
|
|
739
|
+
stage: values.stage,
|
|
740
|
+
assigned_user_id: values["assigned-user"],
|
|
741
|
+
limit: values.limit,
|
|
742
|
+
offset: values.offset,
|
|
743
|
+
page: values.page,
|
|
744
|
+
include_profiles: values.profiles
|
|
745
|
+
});
|
|
746
|
+
const payload = values.all ?
|
|
747
|
+
await fetchAllProspects(client, accountId, query, { totalLimit: parseProspectTotalLimit(values.limit) }) :
|
|
748
|
+
await client.prospects(accountId, query);
|
|
749
|
+
if (values.json) return writeJson(context.stdout, payload);
|
|
750
|
+
if (values.csv) return writeLine(context.stdout, prospectsToCsv(payload?.prospects || []));
|
|
751
|
+
|
|
752
|
+
renderProspects(payload, context, { wide: values.wide || values.all, profiles: values.profiles });
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
async function prospectsShow(args, context, { accountOverride } = {}) {
|
|
756
|
+
const { values, positionals } = parseCommandArgs(args, jsonOptions());
|
|
757
|
+
if (positionals.length !== 1) throw new CommandError("Usage: audienti prospects show <prsp_id> [--json] [--account <acct_id>]");
|
|
758
|
+
|
|
759
|
+
const { client, accountId } = await requireAccountContext(context, { accountOverride });
|
|
760
|
+
const prospect = await client.prospect(accountId, positionals[0]);
|
|
761
|
+
if (values.json) return writeJson(context.stdout, prospect);
|
|
762
|
+
|
|
763
|
+
renderProspect(prospect, context);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
async function prospectsMessageTypes(args, context, { accountOverride } = {}) {
|
|
767
|
+
const { values, positionals } = parseCommandArgs(args, jsonOptions());
|
|
768
|
+
if (positionals.length !== 1) {
|
|
769
|
+
throw new CommandError("Usage: audienti prospects message-types <prsp_id> [--json] [--account <acct_id>]");
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
const { client, accountId } = await requireAccountContext(context, { accountOverride });
|
|
773
|
+
const payload = await client.prospectMessageTypes(accountId, positionals[0]);
|
|
774
|
+
if (values.json) return writeJson(context.stdout, payload);
|
|
775
|
+
|
|
776
|
+
renderProspectMessageTypes(payload, context);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
async function prospectsWrite(args, context, { accountOverride } = {}) {
|
|
780
|
+
const { values, positionals } = parseCommandArgs(args, {
|
|
781
|
+
...jsonOptions(),
|
|
782
|
+
type: { type: "string" },
|
|
783
|
+
surface: { type: "string" }
|
|
784
|
+
});
|
|
785
|
+
const surfaceKey = values.type || values.surface;
|
|
786
|
+
|
|
787
|
+
if (positionals.length !== 1 || !surfaceKey) {
|
|
788
|
+
throw new CommandError("Usage: audienti prospects write <prsp_id> --type <surface_key> [--json] [--account <acct_id>]");
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
const { client, accountId } = await requireAccountContext(context, { accountOverride });
|
|
792
|
+
const payload = await client.writeProspectMessage(accountId, positionals[0], { surface_key: surfaceKey });
|
|
793
|
+
if (values.json) return writeJson(context.stdout, payload);
|
|
794
|
+
|
|
795
|
+
renderProspectMessage(payload, context);
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
async function prospectsAddNote(args, context, { accountOverride } = {}) {
|
|
799
|
+
return prospectNoteCommand(args, context, {
|
|
800
|
+
accountOverride,
|
|
801
|
+
usageText: PROSPECTS_ADD_NOTE_USAGE
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
async function prospectsAddSteer(args, context, { accountOverride } = {}) {
|
|
806
|
+
return prospectNoteCommand(args, context, {
|
|
807
|
+
accountOverride,
|
|
808
|
+
forcedType: "steer",
|
|
809
|
+
usageText: PROSPECTS_ADD_STEER_USAGE
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
async function prospectNoteCommand(args, context, { accountOverride, forcedType, usageText }) {
|
|
814
|
+
const { values, positionals } = parseCommandArgs(args, {
|
|
815
|
+
...jsonOptions(),
|
|
816
|
+
payload: { type: "string" },
|
|
817
|
+
message: { type: "string" },
|
|
818
|
+
type: { type: "string" },
|
|
819
|
+
"track-as-engagement": { type: "boolean" },
|
|
820
|
+
"engagement-type": { type: "string" },
|
|
821
|
+
"engagement-key": { type: "string" }
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
if (positionals.length !== 1) {
|
|
825
|
+
throw new CommandError(usageText);
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
const { client, accountId } = await requireAccountContext(context, { accountOverride });
|
|
829
|
+
const payload = await prospectNotePayload(values, { forcedType, usageText });
|
|
830
|
+
const response = await client.addProspectNote(accountId, positionals[0], payload);
|
|
831
|
+
if (values.json) return writeJson(context.stdout, response);
|
|
832
|
+
|
|
833
|
+
renderProspectNote(response, context);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
async function prospectsSequencePreview(args, context, { accountOverride } = {}) {
|
|
837
|
+
const { values, positionals } = parseCommandArgs(args, {
|
|
838
|
+
...jsonOptions(),
|
|
839
|
+
"connection-state": { type: "string" }
|
|
840
|
+
});
|
|
841
|
+
|
|
842
|
+
if (positionals.length !== 1) {
|
|
843
|
+
throw new CommandError("Usage: audienti prospects sequence-preview <prsp_id> [--json] [--connection-state <state>] [--account <acct_id>]");
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
const { client, accountId } = await requireAccountContext(context, { accountOverride });
|
|
847
|
+
const payload = await client.prospectSequencePreview(accountId, positionals[0], compactObject({
|
|
848
|
+
connection_state: values["connection-state"]
|
|
849
|
+
}));
|
|
850
|
+
if (values.json) return writeJson(context.stdout, payload);
|
|
851
|
+
|
|
852
|
+
renderProspectSequencePreview(payload, context);
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
async function prospectsImport(args, context, { accountOverride } = {}) {
|
|
856
|
+
const { values, positionals } = parseCommandArgs(args, {
|
|
857
|
+
...jsonOptions(),
|
|
858
|
+
list: { type: "string" },
|
|
859
|
+
motion: { type: "string" },
|
|
860
|
+
"assigned-user": { type: "string" }
|
|
861
|
+
});
|
|
862
|
+
if (positionals.length !== 1) {
|
|
863
|
+
throw new CommandError("Usage: audienti prospects import <linkedin_url> [--list <list_id>] [--motion <motn_id>] [--assigned-user <id|me>] [--json] [--account <acct_id>]");
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
const { client, accountId } = await requireAccountContext(context, { accountOverride });
|
|
867
|
+
const payload = await client.prospectImport(accountId, compactObject({
|
|
868
|
+
linkedin_url: positionals[0],
|
|
869
|
+
list_id: values.list,
|
|
870
|
+
motion_id: values.motion,
|
|
871
|
+
assigned_user_id: values["assigned-user"]
|
|
872
|
+
}));
|
|
873
|
+
if (values.json) return writeJson(context.stdout, payload);
|
|
874
|
+
|
|
875
|
+
renderProspectImportStarted(payload, context);
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
async function prospectsImportStatus(args, context, { accountOverride } = {}) {
|
|
879
|
+
const { values, positionals } = parseCommandArgs(args, jsonOptions());
|
|
880
|
+
if (positionals.length !== 1) {
|
|
881
|
+
throw new CommandError("Usage: audienti prospects import-status <primp_id> [--json] [--account <acct_id>]");
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
const { client, accountId } = await requireAccountContext(context, { accountOverride });
|
|
885
|
+
const payload = await client.prospectImportStatus(accountId, positionals[0]);
|
|
886
|
+
if (values.json) return writeJson(context.stdout, payload);
|
|
887
|
+
|
|
888
|
+
renderProspectImportStatus(payload, context);
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
async function toolsGet(args, context, { accountOverride } = {}) {
|
|
892
|
+
const { values, positionals } = parseCommandArgs(args, {
|
|
893
|
+
...jsonOptions(),
|
|
894
|
+
url: { type: "string" },
|
|
895
|
+
"timeout-seconds": { type: "string" },
|
|
896
|
+
"poll-interval-seconds": { type: "string" }
|
|
897
|
+
});
|
|
898
|
+
const lookupType = normalizeLookupType(positionals[0]);
|
|
899
|
+
|
|
900
|
+
if (positionals.length !== 1 || !lookupType || !values.url) {
|
|
901
|
+
throw new CommandError("Usage: audienti tools get <email|phone> --url <linkedin_url> [--json] [--timeout-seconds <n>] [--poll-interval-seconds <n>] [--account <acct_id>]");
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
const timeoutSeconds = normalizePositiveInteger(values["timeout-seconds"]) || DEFAULT_LOOKUP_TIMEOUT_SECONDS;
|
|
905
|
+
const pollIntervalSeconds = normalizePositiveInteger(values["poll-interval-seconds"]) || DEFAULT_LOOKUP_POLL_INTERVAL_SECONDS;
|
|
906
|
+
const linkedinUrl = values.url.trim();
|
|
907
|
+
|
|
908
|
+
const { client, accountId } = await requireAccountContext(context, { accountOverride });
|
|
909
|
+
const started = await client.prospectImport(accountId, { linkedin_url: linkedinUrl });
|
|
910
|
+
const completed = await waitForProspectImport(client, accountId, started, {
|
|
911
|
+
timeoutSeconds,
|
|
912
|
+
pollIntervalSeconds,
|
|
913
|
+
sleepImpl: context.sleep
|
|
914
|
+
});
|
|
915
|
+
const value = contactLookupValue(completed, lookupType);
|
|
916
|
+
const response = {
|
|
917
|
+
kind: lookupType,
|
|
918
|
+
url: linkedinUrl,
|
|
919
|
+
found: Boolean(value),
|
|
920
|
+
value: value || null,
|
|
921
|
+
import_id: completed?.prefix_id || started?.prefix_id || null,
|
|
922
|
+
status: completed?.status || started?.status || null,
|
|
923
|
+
ready: completed?.ready === true,
|
|
924
|
+
prospect: completed?.prospect || started?.prospect || null,
|
|
925
|
+
pipeline: completed?.pipeline || started?.pipeline || null
|
|
926
|
+
};
|
|
927
|
+
|
|
928
|
+
if (values.json) return writeJson(context.stdout, response);
|
|
929
|
+
|
|
930
|
+
if (response.found) {
|
|
931
|
+
writeLine(context.stdout, response.value);
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
writeLine(context.stdout, `No ${lookupType} found for ${linkedinUrl}.`);
|
|
936
|
+
if (response.import_id) writeLine(context.stdout, `Import: ${response.import_id}`);
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
async function operatorQueue(args, context, { accountOverride } = {}) {
|
|
940
|
+
const { values, positionals } = parseCommandArgs(args, operatorOptions());
|
|
941
|
+
if (positionals.length > 0) throw new CommandError("Usage: audienti operator queue [--json] [filters] [--account <acct_id>]");
|
|
942
|
+
|
|
943
|
+
const { client, accountId } = await requireAccountContext(context, { accountOverride });
|
|
944
|
+
const payload = await client.operatorQueue(accountId, operatorQuery(values));
|
|
945
|
+
if (values.json) return writeJson(context.stdout, payload);
|
|
946
|
+
|
|
947
|
+
renderOperatorQueue(payload, context);
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
async function operatorNext(args, context, { accountOverride } = {}) {
|
|
951
|
+
const { values, positionals } = parseCommandArgs(args, operatorOptions());
|
|
952
|
+
if (positionals.length > 0) throw new CommandError("Usage: audienti operator next [--json] [filters] [--account <acct_id>]");
|
|
953
|
+
|
|
954
|
+
const { client, accountId } = await requireAccountContext(context, { accountOverride });
|
|
955
|
+
const payload = await client.operatorNext(accountId, operatorQuery(values));
|
|
956
|
+
if (values.json) return writeJson(context.stdout, payload);
|
|
957
|
+
|
|
958
|
+
renderOperatorNext(payload?.next_move, context);
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
async function operatorOutcome(args, context, { accountOverride } = {}) {
|
|
962
|
+
const { values, positionals } = parseCommandArgs(args, {
|
|
963
|
+
...jsonOptions(),
|
|
964
|
+
payload: { type: "string" }
|
|
965
|
+
});
|
|
966
|
+
if (positionals.length !== 1 || !values.payload) {
|
|
967
|
+
throw new CommandError("Usage: audienti operator outcome <row_id> --payload <file.json> [--json] [--account <acct_id>]");
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
const { client, accountId } = await requireAccountContext(context, { accountOverride });
|
|
971
|
+
const payload = await readJsonPayload(values.payload);
|
|
972
|
+
const response = await client.operatorOutcome(accountId, {
|
|
973
|
+
...payload,
|
|
974
|
+
row_id: positionals[0]
|
|
975
|
+
});
|
|
976
|
+
if (values.json) return writeJson(context.stdout, response);
|
|
977
|
+
|
|
978
|
+
renderOperatorOutcome(response, context);
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
function parseCommandArgs(args, options) {
|
|
982
|
+
try {
|
|
983
|
+
return parseArgs({
|
|
984
|
+
args,
|
|
985
|
+
options,
|
|
986
|
+
allowPositionals: true,
|
|
987
|
+
strict: true
|
|
988
|
+
});
|
|
989
|
+
} catch (error) {
|
|
990
|
+
throw new CommandError(error.message);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
function assertNoPositionals(args, usageText) {
|
|
995
|
+
const { positionals } = parseCommandArgs(args, {});
|
|
996
|
+
if (positionals.length > 0) throw new CommandError(usageText);
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
async function requireAuthenticatedConfig(context) {
|
|
1000
|
+
const config = await readConfig({ env: context.env });
|
|
1001
|
+
if (!config.token) {
|
|
1002
|
+
throw new CommandError("Not authenticated. Run `audienti auth token <token>`.");
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
return config;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
async function requireAccountContext(context, { accountOverride } = {}) {
|
|
1009
|
+
const config = await requireAuthenticatedConfig(context);
|
|
1010
|
+
const accountId = accountOverride || config.accountId;
|
|
1011
|
+
if (!accountId) {
|
|
1012
|
+
throw new CommandError("No active account. Run `audienti accounts select <acct_id>` or pass `--account <acct_id>`.");
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
return {
|
|
1016
|
+
accountId,
|
|
1017
|
+
config,
|
|
1018
|
+
client: clientFromConfig(config, context)
|
|
1019
|
+
};
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
function clientFromConfig(config, context) {
|
|
1023
|
+
return new AudientiClient({
|
|
1024
|
+
host: config.host || DEFAULT_HOST,
|
|
1025
|
+
token: config.token,
|
|
1026
|
+
fetchImpl: context.fetchImpl
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
function jsonOptions() {
|
|
1031
|
+
return {
|
|
1032
|
+
json: { type: "boolean" }
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
function operatorOptions() {
|
|
1037
|
+
return {
|
|
1038
|
+
...jsonOptions(),
|
|
1039
|
+
principal: { type: "string" },
|
|
1040
|
+
motion: { type: "string" },
|
|
1041
|
+
list: { type: "string" },
|
|
1042
|
+
stage: { type: "string" },
|
|
1043
|
+
"opportunity-kind": { type: "string" },
|
|
1044
|
+
"writing-status": { type: "string" }
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
function operatorQuery(values) {
|
|
1049
|
+
return compactObject({
|
|
1050
|
+
principal_account_user_id: values.principal,
|
|
1051
|
+
motion_id: values.motion,
|
|
1052
|
+
list_id: values.list,
|
|
1053
|
+
stage: values.stage,
|
|
1054
|
+
opportunity_kind: values["opportunity-kind"],
|
|
1055
|
+
writing_status: values["writing-status"]
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
function compactObject(object) {
|
|
1060
|
+
return Object.fromEntries(
|
|
1061
|
+
Object.entries(object).filter(([, value]) => value !== undefined && value !== null && String(value).trim() !== "")
|
|
1062
|
+
);
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
async function icpCreatePayload(values) {
|
|
1066
|
+
if (values.payload) {
|
|
1067
|
+
if (values.name || values.notes || values["discovery-keyword"]) {
|
|
1068
|
+
throw new CommandError("Choose one ICP input mode: either --payload <file.json> or the simple --name/--notes/--discovery-keyword flags.");
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
return readJsonPayload(values.payload);
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
if (!values.name) {
|
|
1075
|
+
throw new CommandError("Usage: audienti icps create (--name <text> [--notes <text>] [--discovery-keyword <text>] | --payload <file.json>) [--json] [--account <acct_id>]");
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
return compactObject({
|
|
1079
|
+
name: values.name,
|
|
1080
|
+
notes: values.notes,
|
|
1081
|
+
discovery_keyword: values["discovery-keyword"]
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
async function prospectNotePayload(values, { forcedType, usageText } = {}) {
|
|
1086
|
+
const engagementKey = values["engagement-type"] || values["engagement-key"];
|
|
1087
|
+
|
|
1088
|
+
if (values.payload) {
|
|
1089
|
+
if (values.message || values.type || values["track-as-engagement"] || engagementKey) {
|
|
1090
|
+
throw new CommandError("Choose one prospect note input mode: either --payload <file.json> or the simple --message/--type/--engagement-type flags.");
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
const payload = await readJsonPayload(values.payload);
|
|
1094
|
+
return normalizeProspectNoteType(payload, forcedType);
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
if (!values.message) {
|
|
1098
|
+
throw new CommandError(usageText || PROSPECTS_ADD_NOTE_USAGE);
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
if (values["track-as-engagement"] && !engagementKey) {
|
|
1102
|
+
throw new CommandError("--track-as-engagement requires --engagement-type <key>.");
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
if (forcedType && values.type && values.type !== forcedType) {
|
|
1106
|
+
throw new CommandError(`This command only supports --type ${forcedType}. Use \`audienti prospects add-note\` for other note types.`);
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
return compactObject({
|
|
1110
|
+
note_type: forcedType || values.type || "note",
|
|
1111
|
+
message: values.message,
|
|
1112
|
+
track_as_engagement: values["track-as-engagement"] || Boolean(engagementKey),
|
|
1113
|
+
engagement_key: engagementKey
|
|
1114
|
+
});
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
function normalizeProspectNoteType(payload, forcedType) {
|
|
1118
|
+
if (!forcedType) return payload;
|
|
1119
|
+
|
|
1120
|
+
const noteType = String(payload?.note_type || "").trim();
|
|
1121
|
+
if (noteType && noteType !== forcedType) {
|
|
1122
|
+
throw new CommandError(`Payload note_type must be ${forcedType} for this command. Use \`audienti prospects add-note\` for other note types.`);
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
return {
|
|
1126
|
+
...payload,
|
|
1127
|
+
note_type: forcedType
|
|
1128
|
+
};
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
async function fetchAllPages(fetchPage, baseQuery, { totalLimit = MAX_ALL_PROSPECTS } = {}) {
|
|
1132
|
+
const prospects = [];
|
|
1133
|
+
let offset = 0;
|
|
1134
|
+
let totalCount = null;
|
|
1135
|
+
|
|
1136
|
+
while (prospects.length < totalLimit) {
|
|
1137
|
+
const remaining = totalLimit - prospects.length;
|
|
1138
|
+
const batchLimit = Math.min(remaining, API_MAX_LIST_LIMIT);
|
|
1139
|
+
const payload = await fetchPage({
|
|
1140
|
+
...baseQuery,
|
|
1141
|
+
limit: batchLimit,
|
|
1142
|
+
offset
|
|
1143
|
+
});
|
|
1144
|
+
const rows = Array.isArray(payload?.prospects) ? payload.prospects : [];
|
|
1145
|
+
const meta = payload?.meta || {};
|
|
1146
|
+
totalCount = normalizePositiveInteger(meta.total_count) ?? totalCount;
|
|
1147
|
+
const hasMore = meta.has_more === true || (totalCount !== null && (offset + rows.length) < totalCount);
|
|
1148
|
+
|
|
1149
|
+
prospects.push(...rows.slice(0, remaining));
|
|
1150
|
+
|
|
1151
|
+
if (rows.length === 0) break;
|
|
1152
|
+
offset += rows.length;
|
|
1153
|
+
if (!hasMore) break;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
const inferredTotal = totalCount ?? prospects.length;
|
|
1157
|
+
return {
|
|
1158
|
+
prospects,
|
|
1159
|
+
meta: {
|
|
1160
|
+
total_count: inferredTotal,
|
|
1161
|
+
limit: Math.min(totalLimit, API_MAX_LIST_LIMIT),
|
|
1162
|
+
offset: 0,
|
|
1163
|
+
page: 1,
|
|
1164
|
+
returned_count: prospects.length,
|
|
1165
|
+
has_more: prospects.length < inferredTotal,
|
|
1166
|
+
all: true,
|
|
1167
|
+
max_total: totalLimit,
|
|
1168
|
+
truncated: prospects.length < inferredTotal
|
|
1169
|
+
}
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
async function fetchAllProspects(client, accountId, baseQuery, { totalLimit = MAX_ALL_PROSPECTS } = {}) {
|
|
1174
|
+
return fetchAllPages((pageQuery) => client.prospects(accountId, pageQuery), baseQuery, { totalLimit });
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
async function waitForProspectImport(client, accountId, startedPayload, {
|
|
1178
|
+
timeoutSeconds = DEFAULT_LOOKUP_TIMEOUT_SECONDS,
|
|
1179
|
+
pollIntervalSeconds = DEFAULT_LOOKUP_POLL_INTERVAL_SECONDS,
|
|
1180
|
+
sleepImpl = sleep
|
|
1181
|
+
} = {}) {
|
|
1182
|
+
if (importFinished(startedPayload)) return startedPayload;
|
|
1183
|
+
|
|
1184
|
+
const importId = startedPayload?.prefix_id;
|
|
1185
|
+
if (!importId) {
|
|
1186
|
+
throw new CommandError("Prospect import did not return an import id.");
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
const timeoutAt = Date.now() + (timeoutSeconds * 1000);
|
|
1190
|
+
let latest = startedPayload;
|
|
1191
|
+
|
|
1192
|
+
while (Date.now() < timeoutAt) {
|
|
1193
|
+
await sleepImpl(pollIntervalSeconds * 1000);
|
|
1194
|
+
latest = await client.prospectImportStatus(accountId, importId);
|
|
1195
|
+
if (importFinished(latest)) return latest;
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
throw new CommandError(`Timed out after ${timeoutSeconds} seconds waiting for import ${importId}.`);
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
function importFinished(payload) {
|
|
1202
|
+
return payload?.ready === true || payload?.status === "completed" || payload?.status === "failed";
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
function parseProspectTotalLimit(value) {
|
|
1206
|
+
const parsed = normalizePositiveInteger(value);
|
|
1207
|
+
if (parsed === null) return MAX_ALL_PROSPECTS;
|
|
1208
|
+
|
|
1209
|
+
return Math.min(parsed, MAX_ALL_PROSPECTS);
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
function normalizePositiveInteger(value) {
|
|
1213
|
+
if (value === undefined || value === null || value === "") return null;
|
|
1214
|
+
|
|
1215
|
+
const parsed = Number.parseInt(String(value), 10);
|
|
1216
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return null;
|
|
1217
|
+
|
|
1218
|
+
return parsed;
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
function normalizeLookupType(value) {
|
|
1222
|
+
const normalized = String(value || "").trim().toLowerCase();
|
|
1223
|
+
if (normalized === "email" || normalized === "phone") return normalized;
|
|
1224
|
+
|
|
1225
|
+
return null;
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
function contactLookupValue(payload, lookupType) {
|
|
1229
|
+
if (lookupType === "email") return firstValue(payload?.data?.emails);
|
|
1230
|
+
if (lookupType === "phone") return firstValue(payload?.data?.phones);
|
|
1231
|
+
|
|
1232
|
+
return null;
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
async function readJsonPayload(filePath) {
|
|
1236
|
+
let contents;
|
|
1237
|
+
try {
|
|
1238
|
+
contents = await readFile(filePath, "utf8");
|
|
1239
|
+
} catch (error) {
|
|
1240
|
+
throw new CommandError(`Could not read payload file ${filePath}: ${error.message}`);
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
try {
|
|
1244
|
+
const payload = JSON.parse(contents);
|
|
1245
|
+
if (!payload || Array.isArray(payload) || typeof payload !== "object") {
|
|
1246
|
+
throw new Error("payload must be a JSON object");
|
|
1247
|
+
}
|
|
1248
|
+
return payload;
|
|
1249
|
+
} catch (error) {
|
|
1250
|
+
throw new CommandError(`Invalid JSON payload in ${filePath}: ${error.message}`);
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
function writeLine(stream, text = "") {
|
|
1255
|
+
stream.write(`${text}\n`);
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
function writeJson(stream, value) {
|
|
1259
|
+
writeLine(stream, JSON.stringify(value, null, 2));
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
async function sleep(milliseconds) {
|
|
1263
|
+
await new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
function renderLists(lists, context) {
|
|
1267
|
+
if (!Array.isArray(lists) || lists.length === 0) return writeLine(context.stdout, "No lists found.");
|
|
1268
|
+
|
|
1269
|
+
writeLine(context.stdout, "LIST ID\tPROSPECTS\tNAME");
|
|
1270
|
+
for (const list of lists) {
|
|
1271
|
+
writeLine(context.stdout, `${display(list.prefix_id)}\t${display(list.prospect_count, 0)}\t${display(list.name)}`);
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
function renderList(list, context) {
|
|
1276
|
+
writeLine(context.stdout, `List: ${display(list?.name)} (${display(list?.prefix_id)})`);
|
|
1277
|
+
writeLine(context.stdout, `Prospects: ${display(list?.prospect_count, 0)}`);
|
|
1278
|
+
if (list?.description) writeLine(context.stdout, `Description: ${list.description}`);
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
function renderUsers(users, context) {
|
|
1282
|
+
if (!Array.isArray(users) || users.length === 0) return writeLine(context.stdout, "No account users found.");
|
|
1283
|
+
|
|
1284
|
+
writeLine(context.stdout, "ACCOUNT USER ID\tCURRENT\tROLES\tNAME\tEMAIL");
|
|
1285
|
+
for (const user of users) {
|
|
1286
|
+
writeLine(
|
|
1287
|
+
context.stdout,
|
|
1288
|
+
[
|
|
1289
|
+
display(user.id),
|
|
1290
|
+
user.current ? "yes" : "no",
|
|
1291
|
+
display(Array.isArray(user.roles) && user.roles.length > 0 ? user.roles.join(",") : "member"),
|
|
1292
|
+
display(user.name),
|
|
1293
|
+
display(user.email)
|
|
1294
|
+
].join("\t")
|
|
1295
|
+
);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
function renderOffers(offers, context) {
|
|
1300
|
+
if (!Array.isArray(offers) || offers.length === 0) return writeLine(context.stdout, "No offers found.");
|
|
1301
|
+
|
|
1302
|
+
writeLine(context.stdout, "OFFER ID\tNAME\tURL");
|
|
1303
|
+
for (const offer of offers) {
|
|
1304
|
+
writeLine(
|
|
1305
|
+
context.stdout,
|
|
1306
|
+
[
|
|
1307
|
+
display(offer.prefix_id),
|
|
1308
|
+
display(offer.name),
|
|
1309
|
+
display(offer.url)
|
|
1310
|
+
].join("\t")
|
|
1311
|
+
);
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
function renderIcps(icps, context) {
|
|
1316
|
+
if (!Array.isArray(icps) || icps.length === 0) return writeLine(context.stdout, "No ICPs found.");
|
|
1317
|
+
|
|
1318
|
+
writeLine(context.stdout, "ICP ID\tNAME\tDISCOVERY KEYWORD\tAGENT");
|
|
1319
|
+
for (const icp of icps) {
|
|
1320
|
+
writeLine(
|
|
1321
|
+
context.stdout,
|
|
1322
|
+
[
|
|
1323
|
+
display(icp.prefix_id),
|
|
1324
|
+
display(icp.name),
|
|
1325
|
+
display(icp.discovery_keyword),
|
|
1326
|
+
display(icp.agent?.name)
|
|
1327
|
+
].join("\t")
|
|
1328
|
+
);
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
function renderCompanies(payload, context) {
|
|
1333
|
+
const companies = Array.isArray(payload?.companies) ? payload.companies : [];
|
|
1334
|
+
if (companies.length === 0) return writeLine(context.stdout, "No companies found.");
|
|
1335
|
+
|
|
1336
|
+
writeLine(context.stdout, "PROFILE ID\tCITATION ID\tNAME\tLINKEDIN\tINDUSTRY\tLOCATION");
|
|
1337
|
+
for (const company of companies) {
|
|
1338
|
+
writeLine(
|
|
1339
|
+
context.stdout,
|
|
1340
|
+
[
|
|
1341
|
+
display(company.prefix_id),
|
|
1342
|
+
display(company.citation_id),
|
|
1343
|
+
display(company.display_name || company.name),
|
|
1344
|
+
display(company.url),
|
|
1345
|
+
display(company.industry),
|
|
1346
|
+
display(company.location)
|
|
1347
|
+
].join("\t")
|
|
1348
|
+
);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
async function performBulkMutation(perform) {
|
|
1353
|
+
try {
|
|
1354
|
+
return { payload: await perform(), rejected: false };
|
|
1355
|
+
} catch (error) {
|
|
1356
|
+
if (error instanceof ApiError && error.status === 422 && Array.isArray(error.body?.failed)) {
|
|
1357
|
+
return { payload: error.body, rejected: true };
|
|
1358
|
+
}
|
|
1359
|
+
throw error;
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
function renderBulkMutationResult(payload, context, { successLabel, zeroSuccessLabel }) {
|
|
1364
|
+
const failed = Array.isArray(payload?.failed) ? payload.failed : [];
|
|
1365
|
+
|
|
1366
|
+
writeLine(context.stdout, successCount(payload) > 0 ? successLabel : zeroSuccessLabel);
|
|
1367
|
+
writeLine(context.stdout, `Failures: ${failed.length}`);
|
|
1368
|
+
|
|
1369
|
+
failed.forEach((row) => {
|
|
1370
|
+
writeLine(context.stdout, `- ${display(row?.id, "unknown")}: ${display(row?.reason, "failed")}`);
|
|
1371
|
+
});
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
function renderMotions(motions, context) {
|
|
1375
|
+
if (!Array.isArray(motions) || motions.length === 0) return writeLine(context.stdout, "No motions found.");
|
|
1376
|
+
|
|
1377
|
+
writeLine(context.stdout, "MOTION ID\tSTATUS\tKIND\tNAME");
|
|
1378
|
+
for (const motion of motions) {
|
|
1379
|
+
writeLine(context.stdout, `${display(motion.prefix_id)}\t${display(motion.status)}\t${display(motion.kind)}\t${display(motion.name)}`);
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
function renderMotion(motion, context) {
|
|
1384
|
+
writeLine(context.stdout, `Motion: ${display(motion?.name)} (${display(motion?.prefix_id)})`);
|
|
1385
|
+
writeLine(context.stdout, `Status: ${display(motion?.status)}`);
|
|
1386
|
+
writeLine(context.stdout, `Kind: ${display(motion?.kind)}`);
|
|
1387
|
+
if (motion?.offer?.name) writeLine(context.stdout, `Offer: ${motion.offer.name} (${display(motion.offer.prefix_id)})`);
|
|
1388
|
+
if (motion?.icp?.name) writeLine(context.stdout, `ICP: ${motion.icp.name} (${display(motion.icp.prefix_id)})`);
|
|
1389
|
+
if (motion?.list?.name) writeLine(context.stdout, `List: ${motion.list.name} (${display(motion.list.prefix_id)})`);
|
|
1390
|
+
if (motion?.principal_account_user?.id) {
|
|
1391
|
+
writeLine(
|
|
1392
|
+
context.stdout,
|
|
1393
|
+
`Principal: ${display(motion.principal_account_user.name || motion.principal_account_user.email)} (${display(motion.principal_account_user.id)})`
|
|
1394
|
+
);
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
function renderMotionStatus(status, context) {
|
|
1399
|
+
writeLine(context.stdout, `Motion: ${display(status?.name)} (${display(status?.prefix_id)})`);
|
|
1400
|
+
writeLine(context.stdout, `State: ${display(status?.state)}`);
|
|
1401
|
+
writeLine(context.stdout, `Reason: ${display(status?.reason_label || status?.reason_key)}`);
|
|
1402
|
+
if (status?.description) writeLine(context.stdout, status.description);
|
|
1403
|
+
if (status?.action?.label) writeLine(context.stdout, `Action: ${status.action.label}`);
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
function renderProspects(payload, context, { wide = false, profiles = false } = {}) {
|
|
1407
|
+
const prospects = Array.isArray(payload?.prospects) ? payload.prospects : [];
|
|
1408
|
+
if (prospects.length === 0) return writeLine(context.stdout, "No prospects found.");
|
|
1409
|
+
const profileIdentifiers = profiles ? profileIdentifiersForPayload(payload, prospects) : [];
|
|
1410
|
+
|
|
1411
|
+
if (wide) {
|
|
1412
|
+
const headers = ["PROSPECT ID", "STAGE", "STATUS", "FIT SCORE", "NAME", "TITLE", "COMPANY", "EMAIL", "LINKEDIN", "MOTION", "LISTS"];
|
|
1413
|
+
if (profiles) headers.push(...profileIdentifiers, ...profileIdentifiers.map((identifier) => `${identifier}_url`));
|
|
1414
|
+
headers.push("NEXT ACTION", "UPDATED AT");
|
|
1415
|
+
writeLine(context.stdout, headers.join("\t"));
|
|
1416
|
+
for (const prospect of prospects) {
|
|
1417
|
+
const row = [
|
|
1418
|
+
display(prospect.prefix_id),
|
|
1419
|
+
display(prospect.account_prospect?.pipeline_stage),
|
|
1420
|
+
display(prospect.account_prospect?.status),
|
|
1421
|
+
display(prospect.account_prospect?.fit_score),
|
|
1422
|
+
display(prospect.display_name || prospect.name),
|
|
1423
|
+
display(prospect.title),
|
|
1424
|
+
display(prospect.company),
|
|
1425
|
+
display(prospect.email),
|
|
1426
|
+
display(prospect.linkedin_url),
|
|
1427
|
+
display(prospect.account_prospect?.motion?.name),
|
|
1428
|
+
display((prospect.lists || []).map((list) => list.name).join(" | "))
|
|
1429
|
+
];
|
|
1430
|
+
if (profiles) {
|
|
1431
|
+
row.push(...profileIdentifiers.map((identifier) => display(profileCitationIdsForIdentifier(prospect, identifier))));
|
|
1432
|
+
row.push(...profileIdentifiers.map((identifier) => display(profileUrlsForIdentifier(prospect, identifier))));
|
|
1433
|
+
}
|
|
1434
|
+
row.push(display(nextActionLabel(prospect.queue)), display(prospect.updated_at));
|
|
1435
|
+
writeLine(context.stdout, row.join("\t"));
|
|
1436
|
+
}
|
|
1437
|
+
return;
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
const headers = ["PROSPECT ID", "STAGE", "NAME", "COMPANY"];
|
|
1441
|
+
if (profiles) headers.push(...profileIdentifiers);
|
|
1442
|
+
headers.push("NEXT ACTION");
|
|
1443
|
+
writeLine(context.stdout, headers.join("\t"));
|
|
1444
|
+
for (const prospect of prospects) {
|
|
1445
|
+
const row = [
|
|
1446
|
+
display(prospect.prefix_id),
|
|
1447
|
+
display(prospect.account_prospect?.pipeline_stage),
|
|
1448
|
+
display(prospect.display_name || prospect.name),
|
|
1449
|
+
display(prospect.company)
|
|
1450
|
+
];
|
|
1451
|
+
if (profiles) row.push(...profileIdentifiers.map((identifier) => display(profileCitationIdsForIdentifier(prospect, identifier))));
|
|
1452
|
+
row.push(display(nextActionLabel(prospect.queue)));
|
|
1453
|
+
writeLine(context.stdout, row.join("\t"));
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
function renderProspect(prospect, context) {
|
|
1458
|
+
writeLine(context.stdout, `Prospect: ${display(prospect?.display_name || prospect?.name)} (${display(prospect?.prefix_id)})`);
|
|
1459
|
+
if (prospect?.company) writeLine(context.stdout, `Company: ${prospect.company}`);
|
|
1460
|
+
if (prospect?.title) writeLine(context.stdout, `Title: ${prospect.title}`);
|
|
1461
|
+
if (prospect?.linkedin_url) writeLine(context.stdout, `LinkedIn: ${prospect.linkedin_url}`);
|
|
1462
|
+
if (prospect?.account_prospect?.pipeline_stage) writeLine(context.stdout, `Stage: ${prospect.account_prospect.pipeline_stage}`);
|
|
1463
|
+
if (nextActionLabel(prospect?.queue)) writeLine(context.stdout, `Next action: ${nextActionLabel(prospect.queue)}`);
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
function renderProspectMessageTypes(payload, context) {
|
|
1467
|
+
const surfaces = Array.isArray(payload?.message_surfaces) ? payload.message_surfaces : [];
|
|
1468
|
+
const prospect = payload?.prospect || {};
|
|
1469
|
+
|
|
1470
|
+
writeLine(context.stdout, `Prospect: ${display(prospect.display_name || prospect.name)} (${display(prospect.prefix_id)})`);
|
|
1471
|
+
if (surfaces.length === 0) {
|
|
1472
|
+
writeLine(context.stdout, "No message types found.");
|
|
1473
|
+
return;
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
writeLine(context.stdout, "TYPE\tAVAILABLE\tMESSAGE TYPE\tSTAGE\tCHANNEL");
|
|
1477
|
+
for (const surface of surfaces) {
|
|
1478
|
+
writeLine(context.stdout, [
|
|
1479
|
+
display(surface.key),
|
|
1480
|
+
surface.available ? "yes" : "no",
|
|
1481
|
+
display(surface.canonical_message_type),
|
|
1482
|
+
display(surface.stage),
|
|
1483
|
+
display(surface.channel)
|
|
1484
|
+
].join("\t"));
|
|
1485
|
+
if (!surface.available && surface.missing_reason) {
|
|
1486
|
+
writeLine(context.stdout, ` reason: ${surface.missing_reason}`);
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
function renderProspectMessage(payload, context) {
|
|
1492
|
+
const prospect = payload?.prospect || {};
|
|
1493
|
+
const surface = payload?.message_surface || {};
|
|
1494
|
+
|
|
1495
|
+
writeLine(context.stdout, `Prospect: ${display(prospect.display_name || prospect.name)} (${display(prospect.prefix_id)})`);
|
|
1496
|
+
writeLine(context.stdout, `Type: ${display(surface.key)}`);
|
|
1497
|
+
writeLine(context.stdout, `Message type: ${display(surface.canonical_message_type)}`);
|
|
1498
|
+
writeLine(context.stdout, `Stage: ${display(surface.stage)}`);
|
|
1499
|
+
writeLine(context.stdout, `Channel: ${display(surface.channel)}`);
|
|
1500
|
+
writeLine(context.stdout, `Available: ${surface.available ? "yes" : "no"}`);
|
|
1501
|
+
writeLine(context.stdout, `Status: ${display(surface.status)}`);
|
|
1502
|
+
|
|
1503
|
+
if (surface.subject) writeLine(context.stdout, `Subject: ${surface.subject}`);
|
|
1504
|
+
if (surface.body) {
|
|
1505
|
+
writeLine(context.stdout, "Body:");
|
|
1506
|
+
writeLine(context.stdout, surface.body);
|
|
1507
|
+
}
|
|
1508
|
+
if (!surface.body && surface.empty_body_reason) writeLine(context.stdout, `Empty body reason: ${surface.empty_body_reason}`);
|
|
1509
|
+
if (!surface.available && surface.missing_reason) writeLine(context.stdout, `Missing reason: ${surface.missing_reason}`);
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
function renderProspectNote(payload, context) {
|
|
1513
|
+
const prospect = payload?.prospect || {};
|
|
1514
|
+
const note = payload?.note || {};
|
|
1515
|
+
const event = payload?.event || {};
|
|
1516
|
+
|
|
1517
|
+
writeLine(context.stdout, `Prospect: ${display(prospect.display_name || prospect.name)} (${display(prospect.prefix_id)})`);
|
|
1518
|
+
writeLine(context.stdout, `Type: ${display(note.note_type)}`);
|
|
1519
|
+
writeLine(context.stdout, `Tracked as engagement: ${note.tracked_as_engagement ? "yes" : "no"}`);
|
|
1520
|
+
if (note.engagement_key) {
|
|
1521
|
+
const engagementLabel = note.engagement_label ? `${note.engagement_label} (${note.engagement_key})` : note.engagement_key;
|
|
1522
|
+
writeLine(context.stdout, `Engagement: ${engagementLabel}`);
|
|
1523
|
+
}
|
|
1524
|
+
if (event.prefix_id) writeLine(context.stdout, `Event: ${event.prefix_id} (${display(event.key)})`);
|
|
1525
|
+
if (note.message) {
|
|
1526
|
+
writeLine(context.stdout, "Message:");
|
|
1527
|
+
writeLine(context.stdout, note.message);
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
function renderProspectSequencePreview(payload, context) {
|
|
1532
|
+
const prospect = payload?.prospect || {};
|
|
1533
|
+
const report = payload?.report || {};
|
|
1534
|
+
const preview = report?.last_preview || {};
|
|
1535
|
+
const selected = report?.selected || {};
|
|
1536
|
+
const summary = report?.summary || {};
|
|
1537
|
+
const steps = Array.isArray(report?.steps) ? report.steps : [];
|
|
1538
|
+
const contextInfo = payload?.context || {};
|
|
1539
|
+
|
|
1540
|
+
writeLine(context.stdout, `Prospect: ${display(prospect.display_name || selected.prospect_name)} (${display(prospect.prefix_id || selected.prospect_id)})`);
|
|
1541
|
+
if (contextInfo.source) writeLine(context.stdout, `Context: ${contextInfo.source}`);
|
|
1542
|
+
if (contextInfo.message) writeLine(context.stdout, contextInfo.message);
|
|
1543
|
+
if (selected.motion_name) writeLine(context.stdout, `Motion: ${selected.motion_name}`);
|
|
1544
|
+
if (selected.agent_name) writeLine(context.stdout, `Agent: ${selected.agent_name}`);
|
|
1545
|
+
if (selected.offer_name) writeLine(context.stdout, `Offer: ${selected.offer_name}`);
|
|
1546
|
+
if (summary.channel_sequence?.length) writeLine(context.stdout, `Channels: ${summary.channel_sequence.join(" -> ")}`);
|
|
1547
|
+
if (summary.total_duration_days !== undefined) writeLine(context.stdout, `Duration days: ${summary.total_duration_days}`);
|
|
1548
|
+
if (report.preview_history_count !== undefined) writeLine(context.stdout, `Preview runs: ${report.preview_history_count}`);
|
|
1549
|
+
if (preview.generated_at) writeLine(context.stdout, `Generated at: ${preview.generated_at}`);
|
|
1550
|
+
if (report.status) writeLine(context.stdout, `Status: ${report.status}`);
|
|
1551
|
+
|
|
1552
|
+
if (steps.length === 0) {
|
|
1553
|
+
writeLine(context.stdout, "No sequence steps were generated.");
|
|
1554
|
+
return;
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
writeLine(context.stdout, "");
|
|
1558
|
+
writeLine(context.stdout, "Sequence:");
|
|
1559
|
+
|
|
1560
|
+
steps.forEach((step, index) => {
|
|
1561
|
+
const kind = display(step.kind).toUpperCase();
|
|
1562
|
+
const stage = display(step.stage);
|
|
1563
|
+
const channel = display(step.channel);
|
|
1564
|
+
const timing = step?.timing?.mode === "scheduled" ? ` [scheduled ${display(step?.timing?.scheduled_for)}]` : "";
|
|
1565
|
+
writeLine(context.stdout, `${index + 1}. ${kind} | ${stage} | ${channel}${timing}`);
|
|
1566
|
+
|
|
1567
|
+
if (step.disposition) writeLine(context.stdout, ` Disposition: ${step.disposition}`);
|
|
1568
|
+
if (step.transition_label) writeLine(context.stdout, ` Transition: ${step.transition_label}`);
|
|
1569
|
+
if (step.rationale) writeLine(context.stdout, ` Why: ${step.rationale}`);
|
|
1570
|
+
if (step.guidance) writeLine(context.stdout, ` Guidance: ${step.guidance}`);
|
|
1571
|
+
if (step.body) writeLine(context.stdout, ` Body: ${step.body}`);
|
|
1572
|
+
if (step.empty_body_reason) writeLine(context.stdout, ` Empty body reason: ${step.empty_body_reason}`);
|
|
1573
|
+
if (step.missing_reason) writeLine(context.stdout, ` Missing reason: ${step.missing_reason}`);
|
|
1574
|
+
});
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
function renderProspectImportStarted(payload, context) {
|
|
1578
|
+
writeLine(context.stdout, `Started prospect import ${display(payload?.prefix_id)}.`);
|
|
1579
|
+
writeLine(context.stdout, `Status: ${display(payload?.status)}`);
|
|
1580
|
+
if (payload?.profile?.status) writeLine(context.stdout, `Profile: ${payload.profile.status}`);
|
|
1581
|
+
writeProspectImportProspectLine(payload, context);
|
|
1582
|
+
if (payload?.prefix_id) writeLine(context.stdout, `Run \`audienti prospects import-status ${payload.prefix_id}\` to check completion.`);
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
function renderProspectImportStatus(payload, context) {
|
|
1586
|
+
writeLine(context.stdout, `Import: ${display(payload?.prefix_id)}`);
|
|
1587
|
+
writeLine(context.stdout, `Status: ${display(payload?.status)}`);
|
|
1588
|
+
writeLine(context.stdout, `Ready: ${payload?.ready ? "yes" : "no"}`);
|
|
1589
|
+
if (payload?.pipeline?.enrichment_status) writeLine(context.stdout, `Enrichment: ${payload.pipeline.enrichment_status}`);
|
|
1590
|
+
if (payload?.pipeline?.expansion_status) writeLine(context.stdout, `Expansion: ${payload.pipeline.expansion_status}`);
|
|
1591
|
+
writeProspectImportProspectLine(payload, context);
|
|
1592
|
+
|
|
1593
|
+
const email = firstValue(payload?.data?.emails);
|
|
1594
|
+
const phone = firstValue(payload?.data?.phones);
|
|
1595
|
+
const socialCount = Array.isArray(payload?.data?.social_profiles) ? payload.data.social_profiles.length : 0;
|
|
1596
|
+
if (email) writeLine(context.stdout, `Email: ${email}`);
|
|
1597
|
+
if (phone) writeLine(context.stdout, `Phone: ${phone}`);
|
|
1598
|
+
writeLine(context.stdout, `Social profiles: ${socialCount}`);
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
function writeProspectImportProspectLine(payload, context) {
|
|
1602
|
+
const prospect = payload?.prospect;
|
|
1603
|
+
if (!prospect) return;
|
|
1604
|
+
|
|
1605
|
+
writeLine(context.stdout, `Prospect: ${display(prospect.display_name || prospect.name)} (${display(prospect.prefix_id)})`);
|
|
1606
|
+
if (prospect.company) writeLine(context.stdout, `Company: ${prospect.company}`);
|
|
1607
|
+
if (prospect.title) writeLine(context.stdout, `Title: ${prospect.title}`);
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
function firstValue(rows) {
|
|
1611
|
+
if (!Array.isArray(rows) || rows.length === 0) return null;
|
|
1612
|
+
|
|
1613
|
+
return rows[0]?.value || rows[0]?.username || rows[0]?.url || null;
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
function renderOperatorQueue(payload, context) {
|
|
1617
|
+
const queue = Array.isArray(payload?.decision_queue) ? payload.decision_queue : [];
|
|
1618
|
+
if (queue.length === 0) {
|
|
1619
|
+
renderOperatorNext(payload?.next_move, context);
|
|
1620
|
+
return;
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
writeLine(context.stdout, "MOVE ID\tKIND\tPROSPECT\tMOTION\tNEXT ACTION");
|
|
1624
|
+
for (const row of queue) {
|
|
1625
|
+
writeLine(context.stdout, operatorRowLine(row));
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
function renderOperatorNext(row, context) {
|
|
1630
|
+
if (!row) return writeLine(context.stdout, "No operator moves found.");
|
|
1631
|
+
|
|
1632
|
+
writeLine(context.stdout, "MOVE ID\tKIND\tPROSPECT\tMOTION\tNEXT ACTION");
|
|
1633
|
+
writeLine(context.stdout, operatorRowLine(row));
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
function renderOperatorOutcome(payload, context) {
|
|
1637
|
+
const outcome = payload?.operator_outcome || {};
|
|
1638
|
+
const rowId = payload?.row_id || outcome.row_id;
|
|
1639
|
+
const status = outcome.status || payload?.status || "ok";
|
|
1640
|
+
writeLine(context.stdout, `Recorded ${display(status)} outcome for row ${display(rowId)}.`);
|
|
1641
|
+
if (payload?.prospect?.prefix_id) {
|
|
1642
|
+
writeLine(context.stdout, `Prospect: ${display(payload.prospect.display_name || payload.prospect.name)} (${payload.prospect.prefix_id})`);
|
|
1643
|
+
}
|
|
1644
|
+
if (payload?.event?.prefix_id) {
|
|
1645
|
+
writeLine(context.stdout, `Event: ${payload.event.prefix_id} (${display(payload.event.key)})`);
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
function operatorRowLine(row) {
|
|
1650
|
+
return [
|
|
1651
|
+
display(row?.id),
|
|
1652
|
+
display(row?.opportunity_kind),
|
|
1653
|
+
display(row?.prospect?.display_name || row?.prospect?.name || row?.profile?.display_name),
|
|
1654
|
+
display(row?.motion?.name),
|
|
1655
|
+
display(nextActionLabel(row))
|
|
1656
|
+
].join("\t");
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
function nextActionLabel(source) {
|
|
1660
|
+
return source?.recommended_action_label || source?.next_action?.label || source?.cta?.label;
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
function successCount(payload) {
|
|
1664
|
+
if (Array.isArray(payload?.added)) return payload.added.length;
|
|
1665
|
+
if (Array.isArray(payload?.removed)) return payload.removed.length;
|
|
1666
|
+
if (Array.isArray(payload?.assigned)) return payload.assigned.length;
|
|
1667
|
+
return 0;
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
function display(value, fallback = "") {
|
|
1671
|
+
return value === undefined || value === null || value === "" ? fallback : value;
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
function prospectsToCsv(prospects) {
|
|
1675
|
+
const profileIdentifiers = profileIdentifiersForPayload({ meta: {} }, prospects);
|
|
1676
|
+
const headers = [
|
|
1677
|
+
"prefix_id",
|
|
1678
|
+
"display_name",
|
|
1679
|
+
"name",
|
|
1680
|
+
"kind",
|
|
1681
|
+
"title",
|
|
1682
|
+
"company",
|
|
1683
|
+
"email",
|
|
1684
|
+
"linkedin_url",
|
|
1685
|
+
"website",
|
|
1686
|
+
"created_at",
|
|
1687
|
+
"updated_at",
|
|
1688
|
+
"primary_profile_prefix_id",
|
|
1689
|
+
"primary_profile_identifier",
|
|
1690
|
+
"primary_profile_username",
|
|
1691
|
+
"primary_profile_display_name",
|
|
1692
|
+
"primary_profile_job_title",
|
|
1693
|
+
"primary_profile_url",
|
|
1694
|
+
"primary_profile_status",
|
|
1695
|
+
"account_prospect_id",
|
|
1696
|
+
"account_prospect_status",
|
|
1697
|
+
"account_prospect_score",
|
|
1698
|
+
"account_prospect_fit_score",
|
|
1699
|
+
"account_prospect_fit_rationale",
|
|
1700
|
+
"pipeline_stage",
|
|
1701
|
+
"assigned_to_account_user_id",
|
|
1702
|
+
"motion_prefix_id",
|
|
1703
|
+
"motion_name",
|
|
1704
|
+
"motion_kind",
|
|
1705
|
+
"motion_status",
|
|
1706
|
+
"last_contacted_at",
|
|
1707
|
+
"queue_deferred_until",
|
|
1708
|
+
"locked_at",
|
|
1709
|
+
"lock_kind",
|
|
1710
|
+
"list_ids",
|
|
1711
|
+
"list_names",
|
|
1712
|
+
"recommended_action_label",
|
|
1713
|
+
"queue_status_label",
|
|
1714
|
+
"queue_status_detail",
|
|
1715
|
+
"queue_due_label",
|
|
1716
|
+
"queue_rationale",
|
|
1717
|
+
"queue_guidance",
|
|
1718
|
+
"queue_timing_mode",
|
|
1719
|
+
"queue_scheduled_for",
|
|
1720
|
+
"queue_latest_touch_at"
|
|
1721
|
+
];
|
|
1722
|
+
headers.splice(headers.indexOf("recommended_action_label"), 0, ...profileIdentifiers.flatMap((identifier) => [identifier, `${identifier}_url`]));
|
|
1723
|
+
|
|
1724
|
+
const rows = prospects.map((prospect) => ({
|
|
1725
|
+
prefix_id: prospect.prefix_id,
|
|
1726
|
+
display_name: prospect.display_name,
|
|
1727
|
+
name: prospect.name,
|
|
1728
|
+
kind: prospect.kind,
|
|
1729
|
+
title: prospect.title,
|
|
1730
|
+
company: prospect.company,
|
|
1731
|
+
email: prospect.email,
|
|
1732
|
+
linkedin_url: prospect.linkedin_url,
|
|
1733
|
+
website: prospect.website,
|
|
1734
|
+
created_at: prospect.created_at,
|
|
1735
|
+
updated_at: prospect.updated_at,
|
|
1736
|
+
primary_profile_prefix_id: prospect.primary_profile?.prefix_id,
|
|
1737
|
+
primary_profile_identifier: prospect.primary_profile?.identifier,
|
|
1738
|
+
primary_profile_username: prospect.primary_profile?.username,
|
|
1739
|
+
primary_profile_display_name: prospect.primary_profile?.display_name,
|
|
1740
|
+
primary_profile_job_title: prospect.primary_profile?.job_title,
|
|
1741
|
+
primary_profile_url: prospect.primary_profile?.url,
|
|
1742
|
+
primary_profile_status: prospect.primary_profile?.status,
|
|
1743
|
+
account_prospect_id: prospect.account_prospect?.id,
|
|
1744
|
+
account_prospect_status: prospect.account_prospect?.status,
|
|
1745
|
+
account_prospect_score: prospect.account_prospect?.score,
|
|
1746
|
+
account_prospect_fit_score: prospect.account_prospect?.fit_score,
|
|
1747
|
+
account_prospect_fit_rationale: prospect.account_prospect?.fit_rationale,
|
|
1748
|
+
pipeline_stage: prospect.account_prospect?.pipeline_stage,
|
|
1749
|
+
assigned_to_account_user_id: prospect.account_prospect?.assigned_to_account_user_id,
|
|
1750
|
+
motion_prefix_id: prospect.account_prospect?.motion?.prefix_id,
|
|
1751
|
+
motion_name: prospect.account_prospect?.motion?.name,
|
|
1752
|
+
motion_kind: prospect.account_prospect?.motion?.kind,
|
|
1753
|
+
motion_status: prospect.account_prospect?.motion?.status,
|
|
1754
|
+
last_contacted_at: prospect.account_prospect?.last_contacted_at,
|
|
1755
|
+
queue_deferred_until: prospect.account_prospect?.queue_deferred_until,
|
|
1756
|
+
locked_at: prospect.account_prospect?.locked_at,
|
|
1757
|
+
lock_kind: prospect.account_prospect?.lock_kind,
|
|
1758
|
+
list_ids: (prospect.lists || []).map((list) => list.prefix_id).join(" | "),
|
|
1759
|
+
list_names: (prospect.lists || []).map((list) => list.name).join(" | "),
|
|
1760
|
+
recommended_action_label: prospect.queue?.recommended_action_label,
|
|
1761
|
+
queue_status_label: prospect.queue?.status_label,
|
|
1762
|
+
queue_status_detail: prospect.queue?.status_detail,
|
|
1763
|
+
queue_due_label: prospect.queue?.due_label,
|
|
1764
|
+
queue_rationale: prospect.queue?.rationale,
|
|
1765
|
+
queue_guidance: prospect.queue?.guidance,
|
|
1766
|
+
queue_timing_mode: prospect.queue?.timing_mode,
|
|
1767
|
+
queue_scheduled_for: prospect.queue?.scheduled_for,
|
|
1768
|
+
queue_latest_touch_at: prospect.queue?.latest_touch_at
|
|
1769
|
+
}));
|
|
1770
|
+
|
|
1771
|
+
rows.forEach((row, index) => {
|
|
1772
|
+
const prospect = prospects[index];
|
|
1773
|
+
for (const identifier of profileIdentifiers) {
|
|
1774
|
+
row[identifier] = profileCitationIdsForIdentifier(prospect, identifier);
|
|
1775
|
+
row[`${identifier}_url`] = profileUrlsForIdentifier(prospect, identifier);
|
|
1776
|
+
}
|
|
1777
|
+
});
|
|
1778
|
+
|
|
1779
|
+
return [
|
|
1780
|
+
headers.join(","),
|
|
1781
|
+
...rows.map((row) => headers.map((header) => csvField(row[header])).join(","))
|
|
1782
|
+
].join("\n");
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
function csvField(value) {
|
|
1786
|
+
const text = value === undefined || value === null ? "" : String(value);
|
|
1787
|
+
if (!/[",\n]/.test(text)) return text;
|
|
1788
|
+
|
|
1789
|
+
return `"${text.replaceAll("\"", "\"\"")}"`;
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
function profileIdentifiersForPayload(payload, prospects) {
|
|
1793
|
+
const configured = Array.isArray(payload?.meta?.profile_identifier_columns) ? payload.meta.profile_identifier_columns : [];
|
|
1794
|
+
if (configured.length > 0) return configured;
|
|
1795
|
+
|
|
1796
|
+
const identifiers = new Set(DEFAULT_PROFILE_IDENTIFIERS);
|
|
1797
|
+
|
|
1798
|
+
for (const prospect of prospects) {
|
|
1799
|
+
for (const profile of Array.isArray(prospect?.profiles) ? prospect.profiles : []) {
|
|
1800
|
+
const identifier = String(profile?.identifier || "").trim();
|
|
1801
|
+
if (identifier) identifiers.add(identifier);
|
|
1802
|
+
}
|
|
1803
|
+
for (const identifier of Object.keys(prospect?.profile_identifiers?.values || {})) {
|
|
1804
|
+
if (identifier) identifiers.add(identifier);
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
return [
|
|
1809
|
+
...DEFAULT_PROFILE_IDENTIFIERS.filter((identifier) => identifiers.has(identifier)),
|
|
1810
|
+
...Array.from(identifiers).filter((identifier) => !DEFAULT_PROFILE_IDENTIFIERS.includes(identifier)).sort()
|
|
1811
|
+
];
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
function profileCitationIdsForIdentifier(prospect, identifier) {
|
|
1815
|
+
return profileEntriesForIdentifier(prospect, identifier)
|
|
1816
|
+
.map((profile) => profileCitationId(profile))
|
|
1817
|
+
.filter(Boolean)
|
|
1818
|
+
.join(", ");
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
function profileUrlsForIdentifier(prospect, identifier) {
|
|
1822
|
+
return profileEntriesForIdentifier(prospect, identifier)
|
|
1823
|
+
.map((profile) => String(profile?.url || "").trim())
|
|
1824
|
+
.filter(Boolean)
|
|
1825
|
+
.join(", ");
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
function profileEntriesForIdentifier(prospect, identifier) {
|
|
1829
|
+
const values = prospect?.profile_identifiers?.values;
|
|
1830
|
+
const structuredEntries = Array.isArray(values?.[identifier]) ? values[identifier] : null;
|
|
1831
|
+
if (structuredEntries) return structuredEntries;
|
|
1832
|
+
|
|
1833
|
+
const profiles = Array.isArray(prospect?.profiles) ? prospect.profiles : [];
|
|
1834
|
+
return profiles.filter((profile) => String(profile?.identifier || "").trim() === identifier);
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
function profileCitationId(profile) {
|
|
1838
|
+
const citationId = String(profile?.citation_id || "").trim();
|
|
1839
|
+
if (citationId) return citationId;
|
|
1840
|
+
|
|
1841
|
+
const identifier = String(profile?.identifier || "").trim();
|
|
1842
|
+
const username = String(profile?.username || "").trim();
|
|
1843
|
+
if (identifier && username) return `${identifier}:${username}`;
|
|
1844
|
+
|
|
1845
|
+
return identifier || username;
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
function usage() {
|
|
1849
|
+
return helpFor([]);
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
function helpFor(topicParts) {
|
|
1853
|
+
const topic = topicParts.join(" ").trim();
|
|
1854
|
+
const helpText = HELP_TOPICS.get(topic);
|
|
1855
|
+
if (!helpText) {
|
|
1856
|
+
throw new CommandError(`No help topic found for "${topic || "audienti"}". Run \`audienti --help\`.`);
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
return helpText;
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
const HELP_TOPICS = new Map([
|
|
1863
|
+
["", [
|
|
1864
|
+
"Usage:",
|
|
1865
|
+
" audienti <command> [options]",
|
|
1866
|
+
"",
|
|
1867
|
+
"Start here for local agents:",
|
|
1868
|
+
" audienti help agent-workflows",
|
|
1869
|
+
"",
|
|
1870
|
+
"Implemented commands:",
|
|
1871
|
+
" audienti auth token <token> [--host <url>]",
|
|
1872
|
+
" audienti auth status",
|
|
1873
|
+
" audienti auth logout",
|
|
1874
|
+
" audienti config list [--json]",
|
|
1875
|
+
" audienti accounts list [--json]",
|
|
1876
|
+
" audienti accounts select <acct_id>",
|
|
1877
|
+
" audienti users list [--json]",
|
|
1878
|
+
" audienti offers list [--json]",
|
|
1879
|
+
" audienti offers create --name <text> [--json]",
|
|
1880
|
+
" audienti icps list [--json]",
|
|
1881
|
+
" audienti icps create (--name <text> | --payload <file.json>) [--json]",
|
|
1882
|
+
" audienti companies search --query <text> [--json]",
|
|
1883
|
+
" audienti lists list [--json]",
|
|
1884
|
+
" audienti lists create --name <text> [--json]",
|
|
1885
|
+
" audienti lists show <list_id> [--json]",
|
|
1886
|
+
" audienti lists update <list_id> [--json]",
|
|
1887
|
+
" audienti lists delete <list_id> --confirm <yes|true|Y|y> [--json]",
|
|
1888
|
+
" audienti lists prospects <list_id> [--json]",
|
|
1889
|
+
" audienti lists add-prospects <list_id> <prsp_id> [prsp_id...] [--json]",
|
|
1890
|
+
" audienti lists remove-prospects <list_id> <prsp_id> [prsp_id...] [--json]",
|
|
1891
|
+
" audienti motions list [--json]",
|
|
1892
|
+
" audienti motions show <motn_id> [--json]",
|
|
1893
|
+
" audienti motions status <motn_id> [--json]",
|
|
1894
|
+
" audienti motions prospects <motn_id> [--json]",
|
|
1895
|
+
" audienti motions add-prospects <motn_id> <prsp_id> [prsp_id...] [--json]",
|
|
1896
|
+
" audienti motions create --payload <file.json> [--json]",
|
|
1897
|
+
" audienti prospects list [--json]",
|
|
1898
|
+
" audienti prospects show <prsp_id> [--json]",
|
|
1899
|
+
" audienti prospects message-types <prsp_id> [--json]",
|
|
1900
|
+
" audienti prospects write <prsp_id> --type <surface_key> [--json]",
|
|
1901
|
+
" audienti prospects add-note <prsp_id> --message <text> [--json]",
|
|
1902
|
+
" audienti prospects add-steer <prsp_id> --message <text> [--json]",
|
|
1903
|
+
" audienti prospects sequence-preview <prsp_id> [--json]",
|
|
1904
|
+
" audienti prospects import <linkedin_url> [--list <list_id>] [--motion <motn_id>] [--json]",
|
|
1905
|
+
" audienti prospects import-status <primp_id> [--json]",
|
|
1906
|
+
" audienti tools get <email|phone> --url <linkedin_url> [--json]",
|
|
1907
|
+
" audienti operator next [--json]",
|
|
1908
|
+
" audienti operator queue [--json]",
|
|
1909
|
+
" audienti operator outcome <row_id> --payload <file.json> [--json]",
|
|
1910
|
+
"",
|
|
1911
|
+
"Planned submit-shape help topics:",
|
|
1912
|
+
" audienti prospects disposition help",
|
|
1913
|
+
"",
|
|
1914
|
+
"Global options:",
|
|
1915
|
+
" --account <acct_id> Use an account for one command without saving it",
|
|
1916
|
+
" --help, -h Show help",
|
|
1917
|
+
"",
|
|
1918
|
+
"Run `audienti <command> help` for accepted options, examples, and payload shapes."
|
|
1919
|
+
].join("\n")],
|
|
1920
|
+
|
|
1921
|
+
["auth", [
|
|
1922
|
+
"Usage:",
|
|
1923
|
+
" audienti auth token <token> [--host <url>]",
|
|
1924
|
+
" audienti auth status",
|
|
1925
|
+
" audienti auth logout",
|
|
1926
|
+
"",
|
|
1927
|
+
"Status: implemented",
|
|
1928
|
+
"",
|
|
1929
|
+
"Commands:",
|
|
1930
|
+
" audienti auth token <token> Validate and save a bearer API token",
|
|
1931
|
+
" audienti auth status Check live auth and show selected account",
|
|
1932
|
+
" audienti auth logout Delete local CLI auth config",
|
|
1933
|
+
"",
|
|
1934
|
+
"Run `audienti auth token help` for token input shape."
|
|
1935
|
+
].join("\n")],
|
|
1936
|
+
|
|
1937
|
+
["auth token", [
|
|
1938
|
+
"Usage:",
|
|
1939
|
+
" audienti auth token <token> [--host <url>]",
|
|
1940
|
+
"",
|
|
1941
|
+
"Status: implemented",
|
|
1942
|
+
"",
|
|
1943
|
+
"Options:",
|
|
1944
|
+
" --host <url> Audienti host. Default: https://app.audienti.com",
|
|
1945
|
+
"",
|
|
1946
|
+
"Input shape:",
|
|
1947
|
+
" token: string Existing V10 API token copied from /api_tokens",
|
|
1948
|
+
" host: url Optional absolute http(s) URL",
|
|
1949
|
+
"",
|
|
1950
|
+
"Validation:",
|
|
1951
|
+
" Calls GET /api/v1/me.json with Authorization: Bearer <token> before saving.",
|
|
1952
|
+
"",
|
|
1953
|
+
"Local config:",
|
|
1954
|
+
" Writes host and token to ~/.config/audienti/config.json with mode 0600.",
|
|
1955
|
+
"",
|
|
1956
|
+
"Example:",
|
|
1957
|
+
" audienti auth token aud_123 --host http://localhost:3000"
|
|
1958
|
+
].join("\n")],
|
|
1959
|
+
|
|
1960
|
+
["auth status", [
|
|
1961
|
+
"Usage:",
|
|
1962
|
+
" audienti auth status [--account <acct_id>]",
|
|
1963
|
+
"",
|
|
1964
|
+
"Status: implemented",
|
|
1965
|
+
"",
|
|
1966
|
+
"Output shape:",
|
|
1967
|
+
" Host: string",
|
|
1968
|
+
" Token: masked string",
|
|
1969
|
+
" User: string",
|
|
1970
|
+
" Active account: account name and acct_ id, or none selected"
|
|
1971
|
+
].join("\n")],
|
|
1972
|
+
|
|
1973
|
+
["auth logout", [
|
|
1974
|
+
"Usage:",
|
|
1975
|
+
" audienti auth logout",
|
|
1976
|
+
"",
|
|
1977
|
+
"Status: implemented",
|
|
1978
|
+
"",
|
|
1979
|
+
"Effect:",
|
|
1980
|
+
" Deletes ~/.config/audienti/config.json if it exists."
|
|
1981
|
+
].join("\n")],
|
|
1982
|
+
|
|
1983
|
+
["config", [
|
|
1984
|
+
"Usage:",
|
|
1985
|
+
" audienti config list [--json]",
|
|
1986
|
+
"",
|
|
1987
|
+
"Status: implemented",
|
|
1988
|
+
"",
|
|
1989
|
+
"Commands:",
|
|
1990
|
+
" audienti config list Show the local CLI config path and saved values"
|
|
1991
|
+
].join("\n")],
|
|
1992
|
+
|
|
1993
|
+
["config list", [
|
|
1994
|
+
"Usage:",
|
|
1995
|
+
" audienti config list [--json]",
|
|
1996
|
+
"",
|
|
1997
|
+
"Status: implemented",
|
|
1998
|
+
"",
|
|
1999
|
+
"Output shape:",
|
|
2000
|
+
" Path: absolute config.json path",
|
|
2001
|
+
" Exists: yes|no",
|
|
2002
|
+
" Host: string or none",
|
|
2003
|
+
" Token: masked string or none",
|
|
2004
|
+
" Active account: account name and acct_ id, or none selected"
|
|
2005
|
+
].join("\n")],
|
|
2006
|
+
|
|
2007
|
+
["accounts", [
|
|
2008
|
+
"Usage:",
|
|
2009
|
+
" audienti accounts list [--json]",
|
|
2010
|
+
" audienti accounts select <acct_id>",
|
|
2011
|
+
"",
|
|
2012
|
+
"Status: implemented",
|
|
2013
|
+
"",
|
|
2014
|
+
"Input shape:",
|
|
2015
|
+
" acct_id: string Account prefix id, for example acct_abc123"
|
|
2016
|
+
].join("\n")],
|
|
2017
|
+
|
|
2018
|
+
["accounts list", [
|
|
2019
|
+
"Usage:",
|
|
2020
|
+
" audienti accounts list [--json] [--account <acct_id>]",
|
|
2021
|
+
"",
|
|
2022
|
+
"Status: implemented",
|
|
2023
|
+
"",
|
|
2024
|
+
"Output shape:",
|
|
2025
|
+
" id: integer Raw database id",
|
|
2026
|
+
" prefix_id: acct_ Stable account id for CLI/API routes",
|
|
2027
|
+
" name: string",
|
|
2028
|
+
"",
|
|
2029
|
+
"Example:",
|
|
2030
|
+
" audienti accounts list --json"
|
|
2031
|
+
].join("\n")],
|
|
2032
|
+
|
|
2033
|
+
["accounts select", [
|
|
2034
|
+
"Usage:",
|
|
2035
|
+
" audienti accounts select <acct_id>",
|
|
2036
|
+
"",
|
|
2037
|
+
"Status: implemented",
|
|
2038
|
+
"",
|
|
2039
|
+
"Input shape:",
|
|
2040
|
+
" acct_id: string Exact acct_ id, exact account name, or a unique name/id fragment",
|
|
2041
|
+
"",
|
|
2042
|
+
"Effect:",
|
|
2043
|
+
" Saves accountId and accountName in local CLI config."
|
|
2044
|
+
].join("\n")],
|
|
2045
|
+
|
|
2046
|
+
["users", [
|
|
2047
|
+
"Usage:",
|
|
2048
|
+
" audienti users list [--json]",
|
|
2049
|
+
"",
|
|
2050
|
+
"Status: implemented",
|
|
2051
|
+
"",
|
|
2052
|
+
"Purpose:",
|
|
2053
|
+
" List the account users that can be used as motion principals or assignees.",
|
|
2054
|
+
"",
|
|
2055
|
+
"CLI synonym:",
|
|
2056
|
+
" `principals` is accepted anywhere `users` is accepted"
|
|
2057
|
+
].join("\n")],
|
|
2058
|
+
|
|
2059
|
+
["users list", [
|
|
2060
|
+
"Usage:",
|
|
2061
|
+
" audienti users list [--json] [--account <acct_id>]",
|
|
2062
|
+
"",
|
|
2063
|
+
"Status: implemented",
|
|
2064
|
+
"",
|
|
2065
|
+
"API:",
|
|
2066
|
+
" GET /api/v1/accounts/:account_id/users.json",
|
|
2067
|
+
"",
|
|
2068
|
+
"Output shape:",
|
|
2069
|
+
" id: integer Account user id used by principal_account_user_id and assigned_user_id",
|
|
2070
|
+
" user_id: integer",
|
|
2071
|
+
" name: string",
|
|
2072
|
+
" email: string",
|
|
2073
|
+
" roles: [admin | member]",
|
|
2074
|
+
" current: boolean"
|
|
2075
|
+
].join("\n")],
|
|
2076
|
+
|
|
2077
|
+
["offers", [
|
|
2078
|
+
"Usage:",
|
|
2079
|
+
" audienti offers list [--json]",
|
|
2080
|
+
" audienti offers create --name <text> [--json]",
|
|
2081
|
+
"",
|
|
2082
|
+
"Status: implemented",
|
|
2083
|
+
"",
|
|
2084
|
+
"Purpose:",
|
|
2085
|
+
" List the offers available to the current account so an agent can choose offer_id for motion creation."
|
|
2086
|
+
].join("\n")],
|
|
2087
|
+
|
|
2088
|
+
["offers list", [
|
|
2089
|
+
"Usage:",
|
|
2090
|
+
" audienti offers list [--json] [--account <acct_id>]",
|
|
2091
|
+
"",
|
|
2092
|
+
"Status: implemented",
|
|
2093
|
+
"",
|
|
2094
|
+
"API:",
|
|
2095
|
+
" GET /api/v1/accounts/:account_id/offers.json",
|
|
2096
|
+
"",
|
|
2097
|
+
"Output shape:",
|
|
2098
|
+
" id: integer",
|
|
2099
|
+
" prefix_id: offr_",
|
|
2100
|
+
" name: string",
|
|
2101
|
+
" description: string | null",
|
|
2102
|
+
" url: string | null"
|
|
2103
|
+
].join("\n")],
|
|
2104
|
+
|
|
2105
|
+
["offers create", [
|
|
2106
|
+
"Usage:",
|
|
2107
|
+
" audienti offers create --name <text> [--description <text>] [--url <url>] [--json] [--account <acct_id>]",
|
|
2108
|
+
"",
|
|
2109
|
+
"Status: implemented",
|
|
2110
|
+
"",
|
|
2111
|
+
"Purpose:",
|
|
2112
|
+
" Create a new offer that can be used immediately for motion creation.",
|
|
2113
|
+
"",
|
|
2114
|
+
"Input shape:",
|
|
2115
|
+
" name: string Required offer name",
|
|
2116
|
+
" description: string | optional when url is provided",
|
|
2117
|
+
" url: string | optional when description is provided",
|
|
2118
|
+
"",
|
|
2119
|
+
"Validation:",
|
|
2120
|
+
" The offer model requires name plus either description or url.",
|
|
2121
|
+
"",
|
|
2122
|
+
"API:",
|
|
2123
|
+
" POST /api/v1/accounts/:account_id/offers.json",
|
|
2124
|
+
"",
|
|
2125
|
+
"JSON body:",
|
|
2126
|
+
" {",
|
|
2127
|
+
" \"offer\": {",
|
|
2128
|
+
" \"name\": \"Renewal acceleration audit\",",
|
|
2129
|
+
" \"description\": \"Help revenue teams find renewals at risk before QBRs.\",",
|
|
2130
|
+
" \"url\": \"https://example.com/renewal-audit\"",
|
|
2131
|
+
" }",
|
|
2132
|
+
" }"
|
|
2133
|
+
].join("\n")],
|
|
2134
|
+
|
|
2135
|
+
["icps", [
|
|
2136
|
+
"Usage:",
|
|
2137
|
+
" audienti icps list [--json]",
|
|
2138
|
+
" audienti icps create (--name <text> | --payload <file.json>) [--json]",
|
|
2139
|
+
"",
|
|
2140
|
+
"Status: implemented",
|
|
2141
|
+
"",
|
|
2142
|
+
"Purpose:",
|
|
2143
|
+
" List the ICPs available to the current account so an agent can choose icp_id for motion creation or targeting work."
|
|
2144
|
+
].join("\n")],
|
|
2145
|
+
|
|
2146
|
+
["icps list", [
|
|
2147
|
+
"Usage:",
|
|
2148
|
+
" audienti icps list [--json] [--account <acct_id>]",
|
|
2149
|
+
"",
|
|
2150
|
+
"Status: implemented",
|
|
2151
|
+
"",
|
|
2152
|
+
"API:",
|
|
2153
|
+
" GET /api/v1/accounts/:account_id/icps.json",
|
|
2154
|
+
"",
|
|
2155
|
+
"Output shape:",
|
|
2156
|
+
" id: integer",
|
|
2157
|
+
" prefix_id: icpp_",
|
|
2158
|
+
" name: string",
|
|
2159
|
+
" notes: string | null",
|
|
2160
|
+
" discovery_keyword: string | null",
|
|
2161
|
+
" agent: { id, name } | null"
|
|
2162
|
+
].join("\n")],
|
|
2163
|
+
|
|
2164
|
+
["icps create", [
|
|
2165
|
+
"Usage:",
|
|
2166
|
+
" audienti icps create (--name <text> [--notes <text>] [--discovery-keyword <text>] | --payload <file.json>) [--json] [--account <acct_id>]",
|
|
2167
|
+
"",
|
|
2168
|
+
"Status: implemented",
|
|
2169
|
+
"",
|
|
2170
|
+
"Purpose:",
|
|
2171
|
+
" Create a new account ICP that can be attached to a motion or reused for targeting work.",
|
|
2172
|
+
"",
|
|
2173
|
+
"Input shape:",
|
|
2174
|
+
" name: string Required ICP name",
|
|
2175
|
+
" notes: string | optional",
|
|
2176
|
+
" discovery_keyword: string | optional",
|
|
2177
|
+
" payload: file.json | optional full ICP object using the account API create shape",
|
|
2178
|
+
"",
|
|
2179
|
+
"API:",
|
|
2180
|
+
" POST /api/v1/accounts/:account_id/icps.json",
|
|
2181
|
+
"",
|
|
2182
|
+
"Simple JSON body:",
|
|
2183
|
+
" {",
|
|
2184
|
+
" \"icp\": {",
|
|
2185
|
+
" \"name\": \"Renewal-stage IT leaders\",",
|
|
2186
|
+
" \"notes\": \"IT leaders reviewing vendors before renewal or QBR.\",",
|
|
2187
|
+
" \"discovery_keyword\": \"renewal\"",
|
|
2188
|
+
" }",
|
|
2189
|
+
" }",
|
|
2190
|
+
"",
|
|
2191
|
+
"Payload file example:",
|
|
2192
|
+
" {",
|
|
2193
|
+
" \"name\": \"Vendor Management Office\",",
|
|
2194
|
+
" \"text_criteria\": \"Owns vendor governance, renewals, and escalations.\",",
|
|
2195
|
+
" \"discovery_keyword\": \"vendor governance\",",
|
|
2196
|
+
" \"negative_title_exceptions\": [\"sales\", \"recruiting\"],",
|
|
2197
|
+
" \"company_keywords\": {",
|
|
2198
|
+
" \"include\": [\"vendor governance\", \"supplier performance\"],",
|
|
2199
|
+
" \"exclude\": [\"staffing\"]",
|
|
2200
|
+
" },",
|
|
2201
|
+
" \"job_titles_attributes\": [",
|
|
2202
|
+
" {\"name\": \"Vendor Management Office\"},",
|
|
2203
|
+
" {\"name\": \"Strategic Vendor Management\"}",
|
|
2204
|
+
" ]",
|
|
2205
|
+
" }"
|
|
2206
|
+
].join("\n")],
|
|
2207
|
+
|
|
2208
|
+
["companies", [
|
|
2209
|
+
"Usage:",
|
|
2210
|
+
" audienti companies search --query <text> [--json]",
|
|
2211
|
+
"",
|
|
2212
|
+
"Status: implemented",
|
|
2213
|
+
"",
|
|
2214
|
+
"Purpose:",
|
|
2215
|
+
" Returns persisted LinkedIn company profiles that match a company search query."
|
|
2216
|
+
].join("\n")],
|
|
2217
|
+
|
|
2218
|
+
["companies search", [
|
|
2219
|
+
"Usage:",
|
|
2220
|
+
" audienti companies search --query <text> [--json] [--account <acct_id>]",
|
|
2221
|
+
"",
|
|
2222
|
+
"Status: implemented",
|
|
2223
|
+
"",
|
|
2224
|
+
"API:",
|
|
2225
|
+
" GET /api/v1/accounts/:account_id/companies.json",
|
|
2226
|
+
"",
|
|
2227
|
+
"Input shape:",
|
|
2228
|
+
" query: string",
|
|
2229
|
+
"",
|
|
2230
|
+
"Output shape:",
|
|
2231
|
+
" companies[].prefix_id: prof_ profile id used by --company-profile",
|
|
2232
|
+
" companies[].citation_id: linkedin/company:...",
|
|
2233
|
+
" companies[].display_name: string",
|
|
2234
|
+
" companies[].url: string",
|
|
2235
|
+
" companies[].industry: string | null",
|
|
2236
|
+
" companies[].location: string | null"
|
|
2237
|
+
].join("\n")],
|
|
2238
|
+
|
|
2239
|
+
["lists", [
|
|
2240
|
+
"Usage:",
|
|
2241
|
+
" audienti lists list [--json]",
|
|
2242
|
+
" audienti lists create --name <text> [--json]",
|
|
2243
|
+
" audienti lists show <list_id> [--json]",
|
|
2244
|
+
" audienti lists update <list_id> [--json]",
|
|
2245
|
+
" audienti lists delete <list_id> --confirm <yes|true|Y|y> [--json]",
|
|
2246
|
+
" audienti lists prospects <list_id> [--json]",
|
|
2247
|
+
" audienti lists add-prospects <list_id> <prsp_id> [prsp_id...] [--json]",
|
|
2248
|
+
" audienti lists remove-prospects <list_id> <prsp_id> [prsp_id...] [--json]",
|
|
2249
|
+
"",
|
|
2250
|
+
"Status: read, create, update, delete, and membership commands implemented",
|
|
2251
|
+
"",
|
|
2252
|
+
"ID shape:",
|
|
2253
|
+
" list_id: list_ prefix id"
|
|
2254
|
+
].join("\n")],
|
|
2255
|
+
|
|
2256
|
+
["lists list", [
|
|
2257
|
+
"Usage:",
|
|
2258
|
+
" audienti lists list [--json] [--account <acct_id>]",
|
|
2259
|
+
"",
|
|
2260
|
+
"Status: implemented",
|
|
2261
|
+
"",
|
|
2262
|
+
"API:",
|
|
2263
|
+
" GET /api/v1/accounts/:account_id/lists.json",
|
|
2264
|
+
"",
|
|
2265
|
+
"Output shape:",
|
|
2266
|
+
" id: integer",
|
|
2267
|
+
" prefix_id: list_",
|
|
2268
|
+
" name: string",
|
|
2269
|
+
" description: string | null",
|
|
2270
|
+
" prospect_count: integer",
|
|
2271
|
+
" protected_system_list: boolean",
|
|
2272
|
+
" hubspot_synced: boolean"
|
|
2273
|
+
].join("\n")],
|
|
2274
|
+
|
|
2275
|
+
["lists create", [
|
|
2276
|
+
"Usage:",
|
|
2277
|
+
" audienti lists create --name <text> [--description <text>] [--campaign-hook <text>] [--audience-note <text>] [--json] [--account <acct_id>]",
|
|
2278
|
+
"",
|
|
2279
|
+
"Status: implemented",
|
|
2280
|
+
"",
|
|
2281
|
+
"Purpose:",
|
|
2282
|
+
" Create a new list so an agent can build prospect membership from zero.",
|
|
2283
|
+
"",
|
|
2284
|
+
"Input shape:",
|
|
2285
|
+
" name: string Required list name",
|
|
2286
|
+
" description: string | optional",
|
|
2287
|
+
" campaign_hook: string | optional",
|
|
2288
|
+
" audience_note: string | optional",
|
|
2289
|
+
"",
|
|
2290
|
+
"API:",
|
|
2291
|
+
" POST /api/v1/accounts/:account_id/lists.json",
|
|
2292
|
+
"",
|
|
2293
|
+
"JSON body:",
|
|
2294
|
+
" {",
|
|
2295
|
+
" \"list\": {",
|
|
2296
|
+
" \"name\": \"CIO renewal targets\",",
|
|
2297
|
+
" \"description\": \"Accounts to review before QBR outreach.\",",
|
|
2298
|
+
" \"campaign_brief\": {",
|
|
2299
|
+
" \"hook\": \"Vendor accountability before renewal\",",
|
|
2300
|
+
" \"audience_note\": \"IT leaders running QBRs and renewals\"",
|
|
2301
|
+
" }",
|
|
2302
|
+
" }",
|
|
2303
|
+
" }"
|
|
2304
|
+
].join("\n")],
|
|
2305
|
+
|
|
2306
|
+
["lists show", [
|
|
2307
|
+
"Usage:",
|
|
2308
|
+
" audienti lists show <list_id> [--json] [--account <acct_id>]",
|
|
2309
|
+
"",
|
|
2310
|
+
"Status: implemented",
|
|
2311
|
+
"",
|
|
2312
|
+
"Input shape:",
|
|
2313
|
+
" list_id: list_ prefix id",
|
|
2314
|
+
"",
|
|
2315
|
+
"API:",
|
|
2316
|
+
" GET /api/v1/accounts/:account_id/lists/:id.json"
|
|
2317
|
+
].join("\n")],
|
|
2318
|
+
|
|
2319
|
+
["lists update", [
|
|
2320
|
+
"Usage:",
|
|
2321
|
+
" audienti lists update <list_id> [--name <text>] [--description <text>] [--campaign-hook <text>] [--audience-note <text>] [--json] [--account <acct_id>]",
|
|
2322
|
+
"",
|
|
2323
|
+
"Status: implemented",
|
|
2324
|
+
"",
|
|
2325
|
+
"Purpose:",
|
|
2326
|
+
" Update a normal user-created list without changing prospect membership.",
|
|
2327
|
+
"",
|
|
2328
|
+
"Input shape:",
|
|
2329
|
+
" list_id: list_ prefix id",
|
|
2330
|
+
" list.name: string | optional",
|
|
2331
|
+
" list.description: string | optional",
|
|
2332
|
+
" list.campaign_brief.hook: string | optional",
|
|
2333
|
+
" list.campaign_brief.audience_note: string | optional",
|
|
2334
|
+
"",
|
|
2335
|
+
"API:",
|
|
2336
|
+
" PATCH /api/v1/accounts/:account_id/lists/:id.json"
|
|
2337
|
+
].join("\n")],
|
|
2338
|
+
|
|
2339
|
+
["lists delete", [
|
|
2340
|
+
"Usage:",
|
|
2341
|
+
" audienti lists delete <list_id> --confirm <yes|true|Y|y> [--json] [--account <acct_id>]",
|
|
2342
|
+
"",
|
|
2343
|
+
"Status: implemented",
|
|
2344
|
+
"",
|
|
2345
|
+
"Purpose:",
|
|
2346
|
+
" Delete a normal user-created list. Existing standard disposition/system lists keep their current behavior.",
|
|
2347
|
+
"",
|
|
2348
|
+
"Input shape:",
|
|
2349
|
+
" list_id: list_ prefix id",
|
|
2350
|
+
" confirm: one of yes, true, Y, y",
|
|
2351
|
+
"",
|
|
2352
|
+
"Response shape:",
|
|
2353
|
+
" deleted: boolean",
|
|
2354
|
+
" prefix_id: list_",
|
|
2355
|
+
" reassigned_agent_count: integer",
|
|
2356
|
+
"",
|
|
2357
|
+
"API:",
|
|
2358
|
+
" DELETE /api/v1/accounts/:account_id/lists/:id.json"
|
|
2359
|
+
].join("\n")],
|
|
2360
|
+
|
|
2361
|
+
["lists prospects", [
|
|
2362
|
+
"Usage:",
|
|
2363
|
+
" audienti lists prospects <list_id> [--json] [--account <acct_id>]",
|
|
2364
|
+
"",
|
|
2365
|
+
"Status: implemented",
|
|
2366
|
+
"",
|
|
2367
|
+
"Options:",
|
|
2368
|
+
" --limit <n> Max rows for one page; with --all it caps total rows up to 1000",
|
|
2369
|
+
" --page <n> 1-based page number",
|
|
2370
|
+
" --offset <n> Row offset for manual pagination",
|
|
2371
|
+
" --all Fetch every matching prospect in the list up to 1000 rows",
|
|
2372
|
+
" --profiles Include structured profile identifiers and render per-identifier columns",
|
|
2373
|
+
" --wide Render a richer wide table with more columns",
|
|
2374
|
+
" --csv Export a rich CSV instead of table output",
|
|
2375
|
+
"",
|
|
2376
|
+
"Output shape:",
|
|
2377
|
+
" prospects[]: same row shape as `audienti prospects list`",
|
|
2378
|
+
" meta.total_count: total matching prospects in the list",
|
|
2379
|
+
" meta.offset/page/has_more: pagination metadata",
|
|
2380
|
+
"",
|
|
2381
|
+
"API:",
|
|
2382
|
+
" GET /api/v1/accounts/:account_id/lists/:list_id/prospects.json"
|
|
2383
|
+
].join("\n")],
|
|
2384
|
+
|
|
2385
|
+
["lists add-prospects", [
|
|
2386
|
+
"Usage:",
|
|
2387
|
+
" audienti lists add-prospects <list_id> <prsp_id> [prsp_id...] [--json] [--account <acct_id>]",
|
|
2388
|
+
"",
|
|
2389
|
+
"Status: implemented",
|
|
2390
|
+
"",
|
|
2391
|
+
"Purpose:",
|
|
2392
|
+
" Attach one or more existing account prospects to a list without re-importing them.",
|
|
2393
|
+
"",
|
|
2394
|
+
"Input shape:",
|
|
2395
|
+
" list_id: list_ prefix id",
|
|
2396
|
+
" prsp_id: one or more prsp_ prefix ids",
|
|
2397
|
+
"",
|
|
2398
|
+
"API:",
|
|
2399
|
+
" POST /api/v1/accounts/:account_id/lists/:list_id/prospects.json",
|
|
2400
|
+
"",
|
|
2401
|
+
"JSON body:",
|
|
2402
|
+
" {",
|
|
2403
|
+
" \"prospect_ids\": [\"prsp_abc123\", \"prsp_def456\"]",
|
|
2404
|
+
" }"
|
|
2405
|
+
].join("\n")],
|
|
2406
|
+
|
|
2407
|
+
["lists remove-prospects", [
|
|
2408
|
+
"Usage:",
|
|
2409
|
+
" audienti lists remove-prospects <list_id> <prsp_id> [prsp_id...] [--json] [--account <acct_id>]",
|
|
2410
|
+
"",
|
|
2411
|
+
"Status: implemented",
|
|
2412
|
+
"",
|
|
2413
|
+
"Purpose:",
|
|
2414
|
+
" Remove one or more existing account prospects from a list.",
|
|
2415
|
+
"",
|
|
2416
|
+
"Input shape:",
|
|
2417
|
+
" list_id: list_ prefix id",
|
|
2418
|
+
" prsp_id: one or more prsp_ prefix ids",
|
|
2419
|
+
"",
|
|
2420
|
+
"API:",
|
|
2421
|
+
" DELETE /api/v1/accounts/:account_id/lists/:list_id/prospects.json",
|
|
2422
|
+
"",
|
|
2423
|
+
"JSON body:",
|
|
2424
|
+
" {",
|
|
2425
|
+
" \"prospect_ids\": [\"prsp_abc123\", \"prsp_def456\"]",
|
|
2426
|
+
" }"
|
|
2427
|
+
].join("\n")],
|
|
2428
|
+
|
|
2429
|
+
["motions", [
|
|
2430
|
+
"Usage:",
|
|
2431
|
+
" audienti motions list [--json]",
|
|
2432
|
+
" audienti motions show <motn_id> [--json]",
|
|
2433
|
+
" audienti motions status <motn_id> [--json]",
|
|
2434
|
+
" audienti motions prospects <motn_id> [--json]",
|
|
2435
|
+
" audienti motions add-prospects <motn_id> <prsp_id> [prsp_id...] [--json]",
|
|
2436
|
+
" audienti motions create --payload <file.json> [--json]",
|
|
2437
|
+
"",
|
|
2438
|
+
"Status: read, create, status, and prospect attachment commands implemented",
|
|
2439
|
+
"",
|
|
2440
|
+
"CLI synonym:",
|
|
2441
|
+
" `plays` is accepted anywhere `motions` is accepted",
|
|
2442
|
+
"",
|
|
2443
|
+
"ID shape:",
|
|
2444
|
+
" motn_id: motn_ prefix id"
|
|
2445
|
+
].join("\n")],
|
|
2446
|
+
|
|
2447
|
+
["motions list", [
|
|
2448
|
+
"Usage:",
|
|
2449
|
+
" audienti motions list [--json] [--account <acct_id>]",
|
|
2450
|
+
"",
|
|
2451
|
+
"Status: implemented",
|
|
2452
|
+
"",
|
|
2453
|
+
"API:",
|
|
2454
|
+
" GET /api/v1/accounts/:account_id/motions.json",
|
|
2455
|
+
"",
|
|
2456
|
+
"Output shape:",
|
|
2457
|
+
" id: integer",
|
|
2458
|
+
" prefix_id: motn_",
|
|
2459
|
+
" name: string",
|
|
2460
|
+
" kind: outbound | inbound | lopa | transition",
|
|
2461
|
+
" status: draft | active | paused | archived",
|
|
2462
|
+
" offer.prefix_id: offr_",
|
|
2463
|
+
" icp.prefix_id: icpp_",
|
|
2464
|
+
" list.prefix_id: list_ | null",
|
|
2465
|
+
" principal_account_user.id: integer"
|
|
2466
|
+
].join("\n")],
|
|
2467
|
+
|
|
2468
|
+
["motions show", [
|
|
2469
|
+
"Usage:",
|
|
2470
|
+
" audienti motions show <motn_id> [--json] [--account <acct_id>]",
|
|
2471
|
+
"",
|
|
2472
|
+
"Status: implemented",
|
|
2473
|
+
"",
|
|
2474
|
+
"Input shape:",
|
|
2475
|
+
" motn_id: motn_ prefix id",
|
|
2476
|
+
"",
|
|
2477
|
+
"API:",
|
|
2478
|
+
" GET /api/v1/accounts/:account_id/motions/:id.json"
|
|
2479
|
+
].join("\n")],
|
|
2480
|
+
|
|
2481
|
+
["motions status", [
|
|
2482
|
+
"Usage:",
|
|
2483
|
+
" audienti motions status <motn_id> [--json] [--account <acct_id>]",
|
|
2484
|
+
"",
|
|
2485
|
+
"Status: implemented",
|
|
2486
|
+
"",
|
|
2487
|
+
"Input shape:",
|
|
2488
|
+
" motn_id: motn_ prefix id",
|
|
2489
|
+
"",
|
|
2490
|
+
"Output shape:",
|
|
2491
|
+
" state: healthy_idle | broken",
|
|
2492
|
+
" reason_key: string",
|
|
2493
|
+
" reason_label: string",
|
|
2494
|
+
" description: string",
|
|
2495
|
+
" action: { key: string, label: string } | null",
|
|
2496
|
+
" stats: { target_count, deficit, projected_connectable, capacity, daily_target }",
|
|
2497
|
+
"",
|
|
2498
|
+
"API:",
|
|
2499
|
+
" GET /api/v1/accounts/:account_id/motions/:id/status.json"
|
|
2500
|
+
].join("\n")],
|
|
2501
|
+
|
|
2502
|
+
["motions prospects", [
|
|
2503
|
+
"Usage:",
|
|
2504
|
+
" audienti motions prospects <motn_id> [--json] [--account <acct_id>]",
|
|
2505
|
+
"",
|
|
2506
|
+
"Status: implemented",
|
|
2507
|
+
"",
|
|
2508
|
+
"Options:",
|
|
2509
|
+
" --limit <n> Max rows for one page; with --all it caps total rows up to 1000",
|
|
2510
|
+
" --page <n> 1-based page number",
|
|
2511
|
+
" --offset <n> Row offset for manual pagination",
|
|
2512
|
+
" --all Fetch every prospect in the motion up to 1000 rows",
|
|
2513
|
+
" --profiles Include structured profile identifiers and render per-identifier columns",
|
|
2514
|
+
" --wide Render a richer wide table with more columns",
|
|
2515
|
+
" --csv Export a rich CSV instead of table output",
|
|
2516
|
+
"",
|
|
2517
|
+
"Output shape:",
|
|
2518
|
+
" prospects[]: same row shape as `audienti prospects list`",
|
|
2519
|
+
" meta.total_count: total matching prospects in the motion",
|
|
2520
|
+
" meta.offset/page/has_more: pagination metadata",
|
|
2521
|
+
"",
|
|
2522
|
+
"API:",
|
|
2523
|
+
" GET /api/v1/accounts/:account_id/motions/:motion_id/prospects.json"
|
|
2524
|
+
].join("\n")],
|
|
2525
|
+
|
|
2526
|
+
["motions add-prospects", [
|
|
2527
|
+
"Usage:",
|
|
2528
|
+
" audienti motions add-prospects <motn_id> <prsp_id> [prsp_id...] [--assigned-user <id|me>] [--json] [--account <acct_id>]",
|
|
2529
|
+
"",
|
|
2530
|
+
"Status: implemented",
|
|
2531
|
+
"",
|
|
2532
|
+
"Purpose:",
|
|
2533
|
+
" Attach one or more existing prospects to a motion through the same motion assignment seam the app uses.",
|
|
2534
|
+
"",
|
|
2535
|
+
"Input shape:",
|
|
2536
|
+
" motn_id: motn_ prefix id",
|
|
2537
|
+
" prsp_id: one or more prsp_ prefix ids",
|
|
2538
|
+
" assigned_user_id: account user id or me | optional",
|
|
2539
|
+
"",
|
|
2540
|
+
"Behavior:",
|
|
2541
|
+
" Runs motion fit gating, preserves motion-owned relationship truth, assigns the motion principal by default, and adds the prospect to the motion-owned list when the motion has one.",
|
|
2542
|
+
"",
|
|
2543
|
+
"API:",
|
|
2544
|
+
" POST /api/v1/accounts/:account_id/motions/:motion_id/prospects.json",
|
|
2545
|
+
"",
|
|
2546
|
+
"JSON body:",
|
|
2547
|
+
" {",
|
|
2548
|
+
" \"prospect_ids\": [\"prsp_abc123\", \"prsp_def456\"],",
|
|
2549
|
+
" \"assigned_user_id\": \"me\"",
|
|
2550
|
+
" }"
|
|
2551
|
+
].join("\n")],
|
|
2552
|
+
|
|
2553
|
+
["motions create", [
|
|
2554
|
+
"Usage:",
|
|
2555
|
+
" audienti motions create --payload <file.json> [--json] [--account <acct_id>]",
|
|
2556
|
+
"",
|
|
2557
|
+
"Status: implemented",
|
|
2558
|
+
"",
|
|
2559
|
+
"Purpose:",
|
|
2560
|
+
" Create a motion or play through the same managed setup path the app uses.",
|
|
2561
|
+
"",
|
|
2562
|
+
"Input shape:",
|
|
2563
|
+
" name: string",
|
|
2564
|
+
" premise: string",
|
|
2565
|
+
" approach: string | optional",
|
|
2566
|
+
" kind: outbound | inbound | lopa | transition",
|
|
2567
|
+
" status: draft | active | paused | archived",
|
|
2568
|
+
" offer_id: offr_ prefix id",
|
|
2569
|
+
" principal_account_user_id: integer | me | optional",
|
|
2570
|
+
" icp_id: icpp_ prefix id | optional",
|
|
2571
|
+
" list_id: list_ prefix id | optional",
|
|
2572
|
+
" inbound_channels: [linkedin | reddit | x | tiktok | instagram | facebook] | optional",
|
|
2573
|
+
" lopa_profiles: [{ url: string, source_type: creator | competitor | partner | customer | other }] | optional",
|
|
2574
|
+
"",
|
|
2575
|
+
"JSON example:",
|
|
2576
|
+
" {",
|
|
2577
|
+
" \"name\": \"Enterprise migration leaders\",",
|
|
2578
|
+
" \"premise\": \"Find operators discussing stalled CRM migrations.\",",
|
|
2579
|
+
" \"kind\": \"outbound\",",
|
|
2580
|
+
" \"status\": \"draft\",",
|
|
2581
|
+
" \"offer_id\": \"offr_abc123\",",
|
|
2582
|
+
" \"principal_account_user_id\": 42,",
|
|
2583
|
+
" \"list_id\": \"list_abc123\"",
|
|
2584
|
+
" }",
|
|
2585
|
+
"",
|
|
2586
|
+
"Behavior:",
|
|
2587
|
+
" The API calls Motions::Setup and the managed graph provisioner. If principal_account_user_id is omitted, the authenticated account user is used.",
|
|
2588
|
+
" Use `audienti offers list`, `audienti icps list`, and `audienti users list` to resolve valid ids before calling this command."
|
|
2589
|
+
].join("\n")],
|
|
2590
|
+
|
|
2591
|
+
["prospects", [
|
|
2592
|
+
"Usage:",
|
|
2593
|
+
" audienti prospects list [--json] [filters]",
|
|
2594
|
+
" audienti prospects show <prsp_id> [--json]",
|
|
2595
|
+
" audienti prospects message-types <prsp_id> [--json]",
|
|
2596
|
+
" audienti prospects write <prsp_id> --type <surface_key> [--json]",
|
|
2597
|
+
" audienti prospects add-note <prsp_id> --message <text> [--json]",
|
|
2598
|
+
" audienti prospects add-steer <prsp_id> --message <text> [--json]",
|
|
2599
|
+
" audienti prospects sequence-preview <prsp_id> [--json]",
|
|
2600
|
+
" audienti prospects import <linkedin_url> [--list <list_id>] [--motion <motn_id>] [--json]",
|
|
2601
|
+
" audienti prospects import-status <primp_id> [--json]",
|
|
2602
|
+
"",
|
|
2603
|
+
"Status: read commands, per-prospect draft preview, sequence preview, and import implemented; disposition planned",
|
|
2604
|
+
"",
|
|
2605
|
+
"Filters:",
|
|
2606
|
+
" --query <text>",
|
|
2607
|
+
" --company <text>",
|
|
2608
|
+
" --company-profile <prof_id|citation_id>",
|
|
2609
|
+
" --motion <motn_id>",
|
|
2610
|
+
" --play <motn_id>",
|
|
2611
|
+
" --list <list_id>",
|
|
2612
|
+
" --stage <stage>",
|
|
2613
|
+
" --assigned-user <account_user_id|me>",
|
|
2614
|
+
" --limit <n>",
|
|
2615
|
+
" --page <n>",
|
|
2616
|
+
" --offset <n>",
|
|
2617
|
+
" --all",
|
|
2618
|
+
" --profiles",
|
|
2619
|
+
" --wide",
|
|
2620
|
+
" --csv",
|
|
2621
|
+
"",
|
|
2622
|
+
"ID shape:",
|
|
2623
|
+
" prsp_id: prsp_ prefix id",
|
|
2624
|
+
" primp_id: primp_ prospect import prefix id"
|
|
2625
|
+
].join("\n")],
|
|
2626
|
+
|
|
2627
|
+
["prospects list", [
|
|
2628
|
+
"Usage:",
|
|
2629
|
+
" audienti prospects list [--json] [filters] [--account <acct_id>]",
|
|
2630
|
+
"",
|
|
2631
|
+
"Status: implemented",
|
|
2632
|
+
"",
|
|
2633
|
+
"Filters:",
|
|
2634
|
+
" --query <text> Search name, title, company, email, profile URL",
|
|
2635
|
+
" --company <text> Filter prospects by company name only",
|
|
2636
|
+
" --company-profile <id> Filter prospects by a resolved company profile id or citation id",
|
|
2637
|
+
" --motion <motn_id> Filter to a motion",
|
|
2638
|
+
" --play <motn_id> Filter to a play using the same motion relationship",
|
|
2639
|
+
" --list <list_id> Filter to a prospect list",
|
|
2640
|
+
" --stage <stage> Filter to a pipeline stage",
|
|
2641
|
+
" --assigned-user <id|me> Filter by assigned account user",
|
|
2642
|
+
" --limit <n> Max rows for one page; with --all it caps total rows up to 1000",
|
|
2643
|
+
" --page <n> 1-based page number",
|
|
2644
|
+
" --offset <n> Row offset for manual pagination",
|
|
2645
|
+
" --all Fetch every matching prospect up to 1000 rows",
|
|
2646
|
+
" --profiles Include structured profile identifiers and render per-identifier columns",
|
|
2647
|
+
" --wide Render a richer wide table with more columns",
|
|
2648
|
+
" --csv Export a rich CSV instead of table output",
|
|
2649
|
+
"",
|
|
2650
|
+
"Output shape:",
|
|
2651
|
+
" prospects[].prefix_id: prsp_",
|
|
2652
|
+
" prospects[].primary_profile: profile identity",
|
|
2653
|
+
" prospects[].account_prospect: account-scoped state",
|
|
2654
|
+
" prospects[].queue.next_action: recommended next action",
|
|
2655
|
+
" prospects[].queue.cta: executable call-to-action metadata",
|
|
2656
|
+
" prospects[].profiles[]: full profile rows when --profiles is set",
|
|
2657
|
+
" prospects[].profile_identifiers.columns[]: identifier column contract",
|
|
2658
|
+
" prospects[].profile_identifiers.values[identifier][]: citation_id, username, url",
|
|
2659
|
+
" meta.total_count: total matching prospects",
|
|
2660
|
+
" meta.profile_identifier_columns[]: shared identifier columns for list/export rendering",
|
|
2661
|
+
" meta.offset/page/has_more: pagination metadata",
|
|
2662
|
+
"",
|
|
2663
|
+
"API:",
|
|
2664
|
+
" GET /api/v1/accounts/:account_id/prospects.json",
|
|
2665
|
+
"",
|
|
2666
|
+
"Examples:",
|
|
2667
|
+
" audienti prospects list --stage identified --page 2 --limit 50",
|
|
2668
|
+
" audienti prospects list --all --csv"
|
|
2669
|
+
].join("\n")],
|
|
2670
|
+
|
|
2671
|
+
["prospects show", [
|
|
2672
|
+
"Usage:",
|
|
2673
|
+
" audienti prospects show <prsp_id> [--json] [--account <acct_id>]",
|
|
2674
|
+
"",
|
|
2675
|
+
"Status: implemented",
|
|
2676
|
+
"",
|
|
2677
|
+
"Input shape:",
|
|
2678
|
+
" prsp_id: prsp_ prefix id",
|
|
2679
|
+
"",
|
|
2680
|
+
"API:",
|
|
2681
|
+
" GET /api/v1/accounts/:account_id/prospects/:id.json"
|
|
2682
|
+
].join("\n")],
|
|
2683
|
+
|
|
2684
|
+
["prospects message-types", [
|
|
2685
|
+
"Usage:",
|
|
2686
|
+
" audienti prospects message-types <prsp_id> [--json] [--account <acct_id>]",
|
|
2687
|
+
"",
|
|
2688
|
+
"Status: implemented",
|
|
2689
|
+
"",
|
|
2690
|
+
"Purpose:",
|
|
2691
|
+
" List the sequence surface keys the shared writer can preview for one prospect.",
|
|
2692
|
+
"",
|
|
2693
|
+
"Output shape:",
|
|
2694
|
+
" message_surfaces[].key: sequence surface key such as connection_request or post_accept_message",
|
|
2695
|
+
" message_surfaces[].canonical_message_type: connection_request | direct_message | inmail | email | post_comment | comment_reply",
|
|
2696
|
+
" message_surfaces[].available: boolean",
|
|
2697
|
+
" message_surfaces[].missing_reason: string | null",
|
|
2698
|
+
"",
|
|
2699
|
+
"API:",
|
|
2700
|
+
" GET /api/v1/accounts/:account_id/prospects/:id/message_types.json"
|
|
2701
|
+
].join("\n")],
|
|
2702
|
+
|
|
2703
|
+
["prospects write", [
|
|
2704
|
+
"Usage:",
|
|
2705
|
+
" audienti prospects write <prsp_id> --type <surface_key> [--json] [--account <acct_id>]",
|
|
2706
|
+
"",
|
|
2707
|
+
"Status: implemented",
|
|
2708
|
+
"",
|
|
2709
|
+
"Input shape:",
|
|
2710
|
+
" prsp_id: prsp_ prefix id",
|
|
2711
|
+
" surface_key: one of connection_request, post_accept_message, follow_up_direct_message, email, inbound_reply, public_comment, comment_reply",
|
|
2712
|
+
"",
|
|
2713
|
+
"Behavior:",
|
|
2714
|
+
" Generates a prospect-specific draft through the shared writer preview path for the selected sequence surface.",
|
|
2715
|
+
"",
|
|
2716
|
+
"API:",
|
|
2717
|
+
" POST /api/v1/accounts/:account_id/prospects/:id/write_message.json",
|
|
2718
|
+
"",
|
|
2719
|
+
"JSON body:",
|
|
2720
|
+
" {",
|
|
2721
|
+
" \"surface_key\": \"post_accept_message\"",
|
|
2722
|
+
" }"
|
|
2723
|
+
].join("\n")],
|
|
2724
|
+
|
|
2725
|
+
["prospects add-note", [
|
|
2726
|
+
"Usage:",
|
|
2727
|
+
` ${PROSPECTS_ADD_NOTE_USAGE.slice("Usage: ".length)}`,
|
|
2728
|
+
"",
|
|
2729
|
+
"Status: implemented",
|
|
2730
|
+
"",
|
|
2731
|
+
"Purpose:",
|
|
2732
|
+
" Record an internal note, steer guidance, or manual outreach note through the same prospect note event seam the app uses.",
|
|
2733
|
+
"",
|
|
2734
|
+
"Input shape:",
|
|
2735
|
+
" prsp_id: prsp_ prefix id",
|
|
2736
|
+
" note_type: note | steer | voicemail_outreach | video_outreach",
|
|
2737
|
+
" message: string",
|
|
2738
|
+
" engagement_key: optional shared engagement key such as action.meeting.canceled",
|
|
2739
|
+
"",
|
|
2740
|
+
"Behavior:",
|
|
2741
|
+
" Passing --engagement-type tracks the note as an external engagement that already happened, which is how the UI records states like a meeting that will not happen.",
|
|
2742
|
+
"",
|
|
2743
|
+
"API:",
|
|
2744
|
+
" POST /api/v1/accounts/:account_id/prospects/:id/add_note.json",
|
|
2745
|
+
"",
|
|
2746
|
+
"JSON body:",
|
|
2747
|
+
" {",
|
|
2748
|
+
" \"note_type\": \"steer\",",
|
|
2749
|
+
" \"message\": \"Meeting will not happen after procurement pushed it out.\",",
|
|
2750
|
+
" \"track_as_engagement\": true,",
|
|
2751
|
+
" \"engagement_key\": \"action.meeting.canceled\"",
|
|
2752
|
+
" }"
|
|
2753
|
+
].join("\n")],
|
|
2754
|
+
|
|
2755
|
+
["prospects add-steer", [
|
|
2756
|
+
"Usage:",
|
|
2757
|
+
` ${PROSPECTS_ADD_STEER_USAGE.slice("Usage: ".length)}`,
|
|
2758
|
+
"",
|
|
2759
|
+
"Status: implemented",
|
|
2760
|
+
"",
|
|
2761
|
+
"Purpose:",
|
|
2762
|
+
" Record a steer note through the same prospect note event seam the app uses without requiring --type steer.",
|
|
2763
|
+
"",
|
|
2764
|
+
"Input shape:",
|
|
2765
|
+
" prsp_id: prsp_ prefix id",
|
|
2766
|
+
" message: string",
|
|
2767
|
+
" engagement_key: optional shared engagement key such as action.meeting.canceled",
|
|
2768
|
+
"",
|
|
2769
|
+
"Behavior:",
|
|
2770
|
+
" Always submits note_type=steer. Passing --engagement-type tracks the steer as an external engagement that already happened.",
|
|
2771
|
+
"",
|
|
2772
|
+
"API:",
|
|
2773
|
+
" POST /api/v1/accounts/:account_id/prospects/:id/add_note.json",
|
|
2774
|
+
"",
|
|
2775
|
+
"JSON body:",
|
|
2776
|
+
" {",
|
|
2777
|
+
" \"note_type\": \"steer\",",
|
|
2778
|
+
" \"message\": \"Meeting will not happen after procurement pushed it out.\",",
|
|
2779
|
+
" \"track_as_engagement\": true,",
|
|
2780
|
+
" \"engagement_key\": \"action.meeting.canceled\"",
|
|
2781
|
+
" }"
|
|
2782
|
+
].join("\n")],
|
|
2783
|
+
|
|
2784
|
+
["prospects sequence-preview", [
|
|
2785
|
+
"Usage:",
|
|
2786
|
+
" audienti prospects sequence-preview <prsp_id> [--json] [--connection-state <state>] [--account <acct_id>]",
|
|
2787
|
+
"",
|
|
2788
|
+
"Status: implemented",
|
|
2789
|
+
"",
|
|
2790
|
+
"Purpose:",
|
|
2791
|
+
" Run the existing sequence-preview report workflow for one prospect and return the generated report payload.",
|
|
2792
|
+
"",
|
|
2793
|
+
"Options:",
|
|
2794
|
+
" --connection-state <state> Optional branch override: not_connected | request_sent | accepted",
|
|
2795
|
+
"",
|
|
2796
|
+
"Output shape:",
|
|
2797
|
+
" report.selected: resolved prospect, motion, agent, and offer context",
|
|
2798
|
+
" report.steps[]: ordered wait/action/message/terminal steps from the sequence preview tool",
|
|
2799
|
+
" report.summary: channel sequence, touch counts, duration, terminal disposition",
|
|
2800
|
+
" report.last_preview: latest persisted preview history entry",
|
|
2801
|
+
"",
|
|
2802
|
+
"API:",
|
|
2803
|
+
" POST /api/v1/accounts/:account_id/prospects/:id/sequence_preview.json"
|
|
2804
|
+
].join("\n")],
|
|
2805
|
+
|
|
2806
|
+
["prospects import", [
|
|
2807
|
+
"Usage:",
|
|
2808
|
+
" audienti prospects import <linkedin_url> [--list <list_id>] [--motion <motn_id>] [--assigned-user <id|me>] [--json] [--account <acct_id>]",
|
|
2809
|
+
"",
|
|
2810
|
+
"Status: implemented",
|
|
2811
|
+
"",
|
|
2812
|
+
"Input shape:",
|
|
2813
|
+
" linkedin_url: url LinkedIn person profile URL, not a company URL",
|
|
2814
|
+
" list_id: list_ prefix id | optional",
|
|
2815
|
+
" motn_id: motn_ prefix id | optional",
|
|
2816
|
+
" assigned_user_id: account user id or me | optional",
|
|
2817
|
+
"",
|
|
2818
|
+
"Behavior:",
|
|
2819
|
+
" Creates or reuses a person prospect, stores the LinkedIn profile as the prospect primary profile, optionally attaches it to a motion, adds it to the selected list plus the motion list when both are different, and enqueues enrichment plus expansion.",
|
|
2820
|
+
"",
|
|
2821
|
+
"Output shape:",
|
|
2822
|
+
" prefix_id: primp_ prospect import id",
|
|
2823
|
+
" status: running | completed | failed",
|
|
2824
|
+
" ready: boolean",
|
|
2825
|
+
" prospect.prefix_id: prsp_",
|
|
2826
|
+
" profile.prefix_id: prof_",
|
|
2827
|
+
" pipeline.enrichment_status: profile status",
|
|
2828
|
+
" pipeline.expansion_status: waiting_for_enrichment | pending | completed | blocked",
|
|
2829
|
+
"",
|
|
2830
|
+
"API:",
|
|
2831
|
+
" POST /api/v1/accounts/:account_id/prospect_imports.json",
|
|
2832
|
+
"",
|
|
2833
|
+
"JSON body:",
|
|
2834
|
+
" {",
|
|
2835
|
+
" \"linkedin_url\": \"https://www.linkedin.com/in/example-person\",",
|
|
2836
|
+
" \"list_id\": \"list_abc123\",",
|
|
2837
|
+
" \"motion_id\": \"motn_abc123\",",
|
|
2838
|
+
" \"assigned_user_id\": \"me\"",
|
|
2839
|
+
" }"
|
|
2840
|
+
].join("\n")],
|
|
2841
|
+
|
|
2842
|
+
["prospects import-status", [
|
|
2843
|
+
"Usage:",
|
|
2844
|
+
" audienti prospects import-status <primp_id> [--json] [--account <acct_id>]",
|
|
2845
|
+
"",
|
|
2846
|
+
"Status: implemented",
|
|
2847
|
+
"",
|
|
2848
|
+
"Input shape:",
|
|
2849
|
+
" primp_id: primp_ prospect import prefix id",
|
|
2850
|
+
"",
|
|
2851
|
+
"Output shape:",
|
|
2852
|
+
" status: running | completed | failed",
|
|
2853
|
+
" ready: boolean",
|
|
2854
|
+
" prospect: id, prefix_id, display_name, title, company, email, linkedin_url",
|
|
2855
|
+
" profile: imported primary profile with status, bio, job_title, image_url",
|
|
2856
|
+
" data.emails[]: value, source_finder, source_category",
|
|
2857
|
+
" data.phones[]: value",
|
|
2858
|
+
" data.social_profiles[]: identifier, username, url, status",
|
|
2859
|
+
" pipeline.missing_fields[]: enriched profile fields still absent",
|
|
2860
|
+
"",
|
|
2861
|
+
"API:",
|
|
2862
|
+
" GET /api/v1/accounts/:account_id/prospect_imports/:id.json"
|
|
2863
|
+
].join("\n")],
|
|
2864
|
+
|
|
2865
|
+
["prospects disposition", [
|
|
2866
|
+
"Usage:",
|
|
2867
|
+
" audienti prospects disposition <prsp_id> --payload <file.json> [--account <acct_id>]",
|
|
2868
|
+
"",
|
|
2869
|
+
"Status: planned",
|
|
2870
|
+
"",
|
|
2871
|
+
"Input shape:",
|
|
2872
|
+
" action: defer | nurture | reject | restore",
|
|
2873
|
+
" reason: string | optional",
|
|
2874
|
+
" delay_until: ISO8601 datetime | optional",
|
|
2875
|
+
" note: string | optional",
|
|
2876
|
+
"",
|
|
2877
|
+
"JSON example:",
|
|
2878
|
+
" {",
|
|
2879
|
+
" \"action\": \"nurture\",",
|
|
2880
|
+
" \"reason\": \"not_a_fit\",",
|
|
2881
|
+
" \"note\": \"Not a fit for this motion right now.\"",
|
|
2882
|
+
" }"
|
|
2883
|
+
].join("\n")],
|
|
2884
|
+
|
|
2885
|
+
["tools", [
|
|
2886
|
+
"Usage:",
|
|
2887
|
+
" audienti tools get <email|phone> --url <linkedin_url> [--json]",
|
|
2888
|
+
"",
|
|
2889
|
+
"Status: implemented",
|
|
2890
|
+
"",
|
|
2891
|
+
"Commands:",
|
|
2892
|
+
" audienti tools get Run a LinkedIn URL through the existing import and contact-enrichment pipeline, then return the first selected email or phone."
|
|
2893
|
+
].join("\n")],
|
|
2894
|
+
|
|
2895
|
+
["tools get", [
|
|
2896
|
+
"Usage:",
|
|
2897
|
+
" audienti tools get <email|phone> --url <linkedin_url> [--json] [--timeout-seconds <n>] [--poll-interval-seconds <n>] [--account <acct_id>]",
|
|
2898
|
+
"",
|
|
2899
|
+
"Status: implemented",
|
|
2900
|
+
"",
|
|
2901
|
+
"Purpose:",
|
|
2902
|
+
" Uses the existing prospect import enrichment pipeline, waits for completion, and returns the first selected email or phone for the LinkedIn person URL.",
|
|
2903
|
+
"",
|
|
2904
|
+
"Input shape:",
|
|
2905
|
+
" kind: email | phone",
|
|
2906
|
+
" linkedin_url: url LinkedIn person profile URL, not a company URL",
|
|
2907
|
+
"",
|
|
2908
|
+
"Behavior:",
|
|
2909
|
+
" Starts the same account-scoped import flow as `audienti prospects import`, polls `prospects import-status`, and reads the first value from `data.emails[]` or `data.phones[]`.",
|
|
2910
|
+
" phone lookup still depends on the email waterfall selecting an email first, because the current phone waterfall is gated on email discovery.",
|
|
2911
|
+
"",
|
|
2912
|
+
"Options:",
|
|
2913
|
+
" --timeout-seconds <n> Total wait budget before the command fails. Default: 60",
|
|
2914
|
+
" --poll-interval-seconds <n> Delay between import-status polls. Default: 2",
|
|
2915
|
+
"",
|
|
2916
|
+
"Output:",
|
|
2917
|
+
" Plain text: the selected value on success, or a readable not-found message",
|
|
2918
|
+
" JSON: { kind, url, found, value, import_id, status, ready, prospect, pipeline }"
|
|
2919
|
+
].join("\n")],
|
|
2920
|
+
|
|
2921
|
+
["operator", [
|
|
2922
|
+
"Usage:",
|
|
2923
|
+
" audienti operator next [--json]",
|
|
2924
|
+
" audienti operator queue [--json]",
|
|
2925
|
+
" audienti operator outcome <row_id> --payload <file.json>",
|
|
2926
|
+
"",
|
|
2927
|
+
"Status: read commands and prospect outcome writeback implemented",
|
|
2928
|
+
"",
|
|
2929
|
+
"Filters:",
|
|
2930
|
+
" --principal <account_user_id>",
|
|
2931
|
+
" --motion <motn_id>",
|
|
2932
|
+
" --list <list_id>",
|
|
2933
|
+
" --stage <stage>",
|
|
2934
|
+
" --opportunity-kind prospect|visibility",
|
|
2935
|
+
" --writing-status ready|drafting|draft_failed"
|
|
2936
|
+
].join("\n")],
|
|
2937
|
+
|
|
2938
|
+
["operator next", [
|
|
2939
|
+
"Usage:",
|
|
2940
|
+
" audienti operator next [--json] [filters] [--account <acct_id>]",
|
|
2941
|
+
"",
|
|
2942
|
+
"Status: implemented",
|
|
2943
|
+
"",
|
|
2944
|
+
"Output shape:",
|
|
2945
|
+
" next_move.id: row id",
|
|
2946
|
+
" next_move.prospect.prefix_id: prsp_ | null",
|
|
2947
|
+
" next_move.opportunity_kind: prospect | visibility",
|
|
2948
|
+
" next_move.next_action: recommended action payload",
|
|
2949
|
+
" next_move.cta: executable CTA metadata",
|
|
2950
|
+
" next_move.operator_draft: draft state | null",
|
|
2951
|
+
" filters: resolved operator filters",
|
|
2952
|
+
" metrics: queue-builder metrics",
|
|
2953
|
+
"",
|
|
2954
|
+
"API:",
|
|
2955
|
+
" GET /api/v1/accounts/:account_id/operator/next.json"
|
|
2956
|
+
].join("\n")],
|
|
2957
|
+
|
|
2958
|
+
["operator queue", [
|
|
2959
|
+
"Usage:",
|
|
2960
|
+
" audienti operator queue [--json] [filters] [--account <acct_id>]",
|
|
2961
|
+
"",
|
|
2962
|
+
"Status: implemented",
|
|
2963
|
+
"",
|
|
2964
|
+
"Output shape:",
|
|
2965
|
+
" next_move: focal operator row",
|
|
2966
|
+
" decision_queue[]: ordered operator rows",
|
|
2967
|
+
" daily_progress: pacing counters",
|
|
2968
|
+
" outcome_rollups: queue rollups",
|
|
2969
|
+
" options: motions, principals, lists, stages",
|
|
2970
|
+
"",
|
|
2971
|
+
"API:",
|
|
2972
|
+
" GET /api/v1/accounts/:account_id/operator.json"
|
|
2973
|
+
].join("\n")],
|
|
2974
|
+
|
|
2975
|
+
["operator outcome", [
|
|
2976
|
+
"Usage:",
|
|
2977
|
+
" audienti operator outcome <row_id> --payload <file.json> [--json] [--account <acct_id>]",
|
|
2978
|
+
"",
|
|
2979
|
+
"Status: implemented for prospect rows; visibility rows return a validation error",
|
|
2980
|
+
"",
|
|
2981
|
+
"Input shape:",
|
|
2982
|
+
" status: done | skipped | failed | returned",
|
|
2983
|
+
" action_type: connection_request | profile_view | follow | send_direct_message | send_email | move_to_nurture | string",
|
|
2984
|
+
" prospect_id: prsp_ prefix id | optional",
|
|
2985
|
+
" event_id: evnt_ prefix id | optional",
|
|
2986
|
+
" note: string | optional",
|
|
2987
|
+
" occurred_at: ISO8601 datetime | optional",
|
|
2988
|
+
"",
|
|
2989
|
+
"JSON example:",
|
|
2990
|
+
" {",
|
|
2991
|
+
" \"status\": \"done\",",
|
|
2992
|
+
" \"action_type\": \"connection_request\",",
|
|
2993
|
+
" \"prospect_id\": \"prsp_abc123\",",
|
|
2994
|
+
" \"note\": \"Connection request sent.\"",
|
|
2995
|
+
" }",
|
|
2996
|
+
"",
|
|
2997
|
+
"API:",
|
|
2998
|
+
" POST /api/v1/accounts/:account_id/operator/outcome.json"
|
|
2999
|
+
].join("\n")],
|
|
3000
|
+
|
|
3001
|
+
["agent-workflows", [
|
|
3002
|
+
"Usage:",
|
|
3003
|
+
" audienti help agent-workflows",
|
|
3004
|
+
"",
|
|
3005
|
+
"Purpose:",
|
|
3006
|
+
" Give a local coding agent the shortest safe path through the common Audienti production workflows.",
|
|
3007
|
+
"",
|
|
3008
|
+
"1. Authenticate and select an account",
|
|
3009
|
+
" audienti auth token <token>",
|
|
3010
|
+
" audienti accounts list",
|
|
3011
|
+
" audienti accounts select <acct_id>",
|
|
3012
|
+
" audienti users list",
|
|
3013
|
+
" audienti offers list",
|
|
3014
|
+
" audienti icps list",
|
|
3015
|
+
"",
|
|
3016
|
+
"2. Create a motion or play",
|
|
3017
|
+
" audienti motions create --payload <file.json>",
|
|
3018
|
+
" audienti motions status <motn_id>",
|
|
3019
|
+
"",
|
|
3020
|
+
"3. Add a new prospect from LinkedIn and poll enrichment",
|
|
3021
|
+
" audienti lists create --name \"Target list\"",
|
|
3022
|
+
" audienti prospects import https://www.linkedin.com/in/example --list <list_id> --assigned-user me",
|
|
3023
|
+
" audienti prospects import-status <primp_id>",
|
|
3024
|
+
" audienti prospects show <prsp_id>",
|
|
3025
|
+
" audienti tools get email --url https://www.linkedin.com/in/example",
|
|
3026
|
+
"",
|
|
3027
|
+
"4. Find an existing prospect and inspect next step",
|
|
3028
|
+
" audienti prospects list --query \"name or company\" --wide",
|
|
3029
|
+
" audienti companies search --query \"Honeywell\"",
|
|
3030
|
+
" audienti prospects list --company-profile <prof_id>",
|
|
3031
|
+
" audienti prospects show <prsp_id>",
|
|
3032
|
+
" audienti prospects message-types <prsp_id>",
|
|
3033
|
+
" audienti prospects add-note <prsp_id> --type steer --message \"Meeting will not happen\" --engagement-type action.meeting.canceled",
|
|
3034
|
+
" audienti prospects sequence-preview <prsp_id>",
|
|
3035
|
+
"",
|
|
3036
|
+
"5. Attach existing prospects without re-importing",
|
|
3037
|
+
" audienti lists add-prospects <list_id> <prsp_id> [prsp_id...]",
|
|
3038
|
+
" audienti motions add-prospects <motn_id> <prsp_id> [prsp_id...]",
|
|
3039
|
+
"",
|
|
3040
|
+
"6. Work the operator queue",
|
|
3041
|
+
" audienti operator next",
|
|
3042
|
+
" audienti operator queue --json",
|
|
3043
|
+
" audienti operator outcome <row_id> --payload <file.json>",
|
|
3044
|
+
"",
|
|
3045
|
+
"Good defaults:",
|
|
3046
|
+
" Use --json when another tool or agent will parse the result.",
|
|
3047
|
+
" Use --account <acct_id> to avoid mutating the saved account during one-off runs.",
|
|
3048
|
+
" Use `audienti users list` before motion create or prospect assignment when you need a principal or assignee id.",
|
|
3049
|
+
" Prefer prospects import for new LinkedIn people and add-prospects commands for records that already exist.",
|
|
3050
|
+
"",
|
|
3051
|
+
"Current gaps to plan around:",
|
|
3052
|
+
" Prospect disposition still lacks a dedicated CLI mutation.",
|
|
3053
|
+
" Operator outcome writeback is implemented for prospect rows, not visibility rows."
|
|
3054
|
+
].join("\n")]
|
|
3055
|
+
]);
|