@audienti/cli 0.1.77 → 0.1.79
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 +15 -0
- package/package.json +1 -1
- package/src/api-client.js +157 -2
- package/src/cli.js +341 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,21 @@ All notable changes to the Audienti CLI are documented here.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.1.79] - 2026-09-28
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Run the remaining prospect page actions from the CLI: `prospects defer`, `delay --for`, `monitor`, `unmonitor`, `rename --name`, `import-post --url`, `sync --social-cookie`, `cancel-event --event`, `queue-draft` (optional `--context` JSON object), `rewrite`, `engage --type` (with `--post`, `--comment`, `--reply-to`, `--request-event`, `--queue-action` and `--principal` targets), `reject-selected`, and `intake --url`.
|
|
12
|
+
- Add `events retry`, `profiles delete`, `companies stop-pursuing`, `content defer --for`, `network-ops adopt --target-account --motion`, `network-ops ignore`, `reconciliations add-to-motion --motion`, and `reconciliations ignore`.
|
|
13
|
+
- Add `inbox-ops update-filters`, `inbox-ops draft-reply`, `inbox-ops reply --message`, and `inbox-ops adopt`. Each reply sends a fresh reply token so a retried command cannot send twice.
|
|
14
|
+
|
|
15
|
+
## [0.1.78] - 2026-09-27
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- Fix `audienti setup` and `audienti motions quick-start --confirm` failing with HTTP 400 when creating the drafted motion.
|
|
20
|
+
- When drafting fails because the drafting service is down, `audienti setup` now says to try again in a few minutes instead of only suggesting a different website.
|
|
21
|
+
|
|
7
22
|
## [0.1.77] - 2026-09-27
|
|
8
23
|
|
|
9
24
|
### Added
|
package/package.json
CHANGED
package/src/api-client.js
CHANGED
|
@@ -794,6 +794,160 @@ export class AudientiClient {
|
|
|
794
794
|
});
|
|
795
795
|
}
|
|
796
796
|
|
|
797
|
+
deferProspect(accountId, prospectId) {
|
|
798
|
+
return this.requestJson(accountPath(accountId, ["prospects", prospectId, "defer"]), {
|
|
799
|
+
method: "POST"
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
delayProspect(accountId, prospectId, body = {}) {
|
|
804
|
+
return this.requestJson(accountPath(accountId, ["prospects", prospectId, "delay"]), {
|
|
805
|
+
method: "POST",
|
|
806
|
+
body
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
monitorProspect(accountId, prospectId, body = {}) {
|
|
811
|
+
return this.requestJson(accountPath(accountId, ["prospects", prospectId, "monitor"]), {
|
|
812
|
+
method: "POST",
|
|
813
|
+
body
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
unmonitorProspect(accountId, prospectId) {
|
|
818
|
+
return this.requestJson(accountPath(accountId, ["prospects", prospectId, "unmonitor"]), {
|
|
819
|
+
method: "POST"
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
renameProspect(accountId, prospectId, body = {}) {
|
|
824
|
+
return this.requestJson(accountPath(accountId, ["prospects", prospectId, "display_name"]), {
|
|
825
|
+
method: "PATCH",
|
|
826
|
+
body
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
importProspectPost(accountId, prospectId, body = {}) {
|
|
831
|
+
return this.requestJson(accountPath(accountId, ["prospects", prospectId, "import_post"]), {
|
|
832
|
+
method: "POST",
|
|
833
|
+
body
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
syncProspect(accountId, prospectId, body = {}) {
|
|
838
|
+
return this.requestJson(accountPath(accountId, ["prospects", prospectId, "sync"]), {
|
|
839
|
+
method: "POST",
|
|
840
|
+
body
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
cancelProspectScheduledEvent(accountId, prospectId, body = {}) {
|
|
845
|
+
return this.requestJson(accountPath(accountId, ["prospects", prospectId, "cancel_scheduled_event"]), {
|
|
846
|
+
method: "POST",
|
|
847
|
+
body
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
writeProspectQueueDraft(accountId, prospectId, body = {}) {
|
|
852
|
+
return this.requestJson(accountPath(accountId, ["prospects", prospectId, "write"]), {
|
|
853
|
+
method: "POST",
|
|
854
|
+
body
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
rewriteProspectQueueDraft(accountId, prospectId, body = {}) {
|
|
859
|
+
return this.requestJson(accountPath(accountId, ["prospects", prospectId, "rewrite"]), {
|
|
860
|
+
method: "POST",
|
|
861
|
+
body
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
engageProspect(accountId, prospectId, body = {}) {
|
|
866
|
+
return this.requestJson(accountPath(accountId, ["prospects", prospectId, "engage"]), {
|
|
867
|
+
method: "POST",
|
|
868
|
+
body
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
rejectSelectedProspects(accountId, body = {}) {
|
|
873
|
+
return this.requestJson(accountPath(accountId, ["prospects", "reject_selected"]), {
|
|
874
|
+
method: "POST",
|
|
875
|
+
body
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
intakeProspect(accountId, body = {}) {
|
|
880
|
+
return this.requestJson(accountPath(accountId, ["prospects", "intake"]), {
|
|
881
|
+
method: "POST",
|
|
882
|
+
body
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
retryEvent(accountId, eventId) {
|
|
887
|
+
return this.requestJson(accountPath(accountId, ["events", eventId, "retry"]), {
|
|
888
|
+
method: "POST"
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
deleteProfile(accountId, profileId) {
|
|
893
|
+
return this.requestJson(accountPath(accountId, ["profiles", profileId]), {
|
|
894
|
+
method: "DELETE"
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
stopPursuingCompany(accountId, companyId) {
|
|
899
|
+
return this.requestJson(accountPath(accountId, ["companies", companyId, "stop_pursuing"]), {
|
|
900
|
+
method: "POST"
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
contentDefer(accountId, workItemId, body = {}) {
|
|
905
|
+
return this.requestJson(accountPath(accountId, ["content_ops", "work_items", workItemId, "defer"]), {
|
|
906
|
+
method: "POST",
|
|
907
|
+
body
|
|
908
|
+
});
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
updateInboxOpsFilters(accountId, body = {}) {
|
|
912
|
+
return this.requestJson(accountPath(accountId, ["inbox_ops", "filters"]), {
|
|
913
|
+
method: "PATCH",
|
|
914
|
+
body
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
inboxOpsReply(accountId, rowId, body = {}) {
|
|
919
|
+
return this.requestJson(accountPath(accountId, ["inbox_ops", rowId, "reply"]), {
|
|
920
|
+
method: "POST",
|
|
921
|
+
body
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
inboxOpsWriteReply(accountId, rowId) {
|
|
926
|
+
return this.requestJson(accountPath(accountId, ["inbox_ops", rowId, "reply", "write"]), {
|
|
927
|
+
method: "POST"
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
inboxOpsAdopt(accountId, rowId, body = {}) {
|
|
932
|
+
return this.requestJson(accountPath(accountId, ["inbox_ops", rowId, "adopt"]), {
|
|
933
|
+
method: "POST",
|
|
934
|
+
body
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
reconciliationAddToMotion(accountId, sourceKey, body = {}) {
|
|
939
|
+
return this.requestJson(accountPath(accountId, ["reconciliations", sourceKey, "add_to_motion"]), {
|
|
940
|
+
method: "POST",
|
|
941
|
+
body
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
reconciliationIgnore(accountId, sourceKey) {
|
|
946
|
+
return this.requestJson(accountPath(accountId, ["reconciliations", sourceKey, "ignore"]), {
|
|
947
|
+
method: "POST"
|
|
948
|
+
});
|
|
949
|
+
}
|
|
950
|
+
|
|
797
951
|
prospectSequencePreview(accountId, prospectId, body = {}) {
|
|
798
952
|
return this.requestJson(accountPath(accountId, ["prospects", prospectId, "sequence_preview"]), {
|
|
799
953
|
method: "POST",
|
|
@@ -883,9 +1037,10 @@ export class AudientiClient {
|
|
|
883
1037
|
return this.requestJson(accountPath(accountId, ["operator"], query));
|
|
884
1038
|
}
|
|
885
1039
|
|
|
886
|
-
networkOpsAction(accountId, rowId, action) {
|
|
1040
|
+
networkOpsAction(accountId, rowId, action, body) {
|
|
887
1041
|
return this.requestJson(accountPath(accountId, ["network_ops", rowId, action]), {
|
|
888
|
-
method: "POST"
|
|
1042
|
+
method: "POST",
|
|
1043
|
+
...(body === undefined ? {} : { body })
|
|
889
1044
|
});
|
|
890
1045
|
}
|
|
891
1046
|
|
package/src/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { parseArgs } from "node:util";
|
|
2
2
|
import { createServer } from "node:http";
|
|
3
3
|
import { spawn } from "node:child_process";
|
|
4
|
-
import { randomBytes } from "node:crypto";
|
|
4
|
+
import { randomBytes, randomUUID } from "node:crypto";
|
|
5
5
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
6
6
|
import { basename, join } from "node:path";
|
|
7
7
|
import { createInterface } from "node:readline/promises";
|
|
@@ -309,6 +309,8 @@ async function dispatch(argv, context) {
|
|
|
309
309
|
|
|
310
310
|
const [resource, action, ...rest] = args;
|
|
311
311
|
const normalizedResource = normalizeResource(resource);
|
|
312
|
+
const parityActionTopic = `${normalizedResource} ${action}`;
|
|
313
|
+
if (PARITY_ACTION_COMMANDS.has(parityActionTopic)) return runParityActionCommand(parityActionTopic, rest, context, { accountOverride });
|
|
312
314
|
|
|
313
315
|
if (normalizedResource === "auth" && action === "token") return authToken(rest, context);
|
|
314
316
|
if (normalizedResource === "auth" && action === "login") return authLogin(rest, context);
|
|
@@ -1115,7 +1117,7 @@ function setupCreateError(error) {
|
|
|
1115
1117
|
|
|
1116
1118
|
function setupDraftFailedError(reason) {
|
|
1117
1119
|
const detail = reason ? ` (${cleanSentence(reason)})` : "";
|
|
1118
|
-
return new CommandError(`We couldn't draft a motion from that website${detail}. Nothing was created.
|
|
1120
|
+
return new CommandError(`We couldn't draft a motion from that website${detail}. Nothing was created. Run \`audienti setup\` again in a few minutes. If it keeps failing, check the address or add a sentence about who you sell to.`);
|
|
1119
1121
|
}
|
|
1120
1122
|
|
|
1121
1123
|
function renderSetupDraft(draft, { ask }, say) {
|
|
@@ -14056,3 +14058,340 @@ const HELP_TOPICS = new Map([
|
|
|
14056
14058
|
" Operator outcome writeback is implemented for prospect rows, not visibility rows."
|
|
14057
14059
|
].join("\n")]
|
|
14058
14060
|
]);
|
|
14061
|
+
|
|
14062
|
+
// Single-purpose actions that mirror one web button each (#2300, #2302).
|
|
14063
|
+
// Every entry calls one API route that shares its service with the web action.
|
|
14064
|
+
const ACCOUNT_OPTION_USAGE = "[--json] [--account <acct_id>]";
|
|
14065
|
+
const PARITY_ACTION_COMMANDS = new Map([
|
|
14066
|
+
["prospects defer", {
|
|
14067
|
+
usage: "audienti prospects defer <prsp_id>",
|
|
14068
|
+
purpose: "Push one prospect out of the queue for 24 hours, like Defer in the queue.",
|
|
14069
|
+
api: "POST /api/v1/accounts/:account_id/prospects/:id/defer.json",
|
|
14070
|
+
run: (client, accountId, [id]) => client.deferProspect(accountId, id),
|
|
14071
|
+
done: ([id]) => `Deferred prospect ${id} for 24 hours.`
|
|
14072
|
+
}],
|
|
14073
|
+
["prospects delay", {
|
|
14074
|
+
usage: "audienti prospects delay <prsp_id> --for <1_day|1_week|1_month|3_months|6_months>",
|
|
14075
|
+
purpose: "Delay one prospect and move it to the Delayed list until the window ends.",
|
|
14076
|
+
api: "POST /api/v1/accounts/:account_id/prospects/:id/delay.json",
|
|
14077
|
+
options: { for: { type: "string" } },
|
|
14078
|
+
required: ["for"],
|
|
14079
|
+
run: (client, accountId, [id], values) => client.delayProspect(accountId, id, { delay_duration: values.for }),
|
|
14080
|
+
done: ([id], payload) => `Delayed prospect ${id} until ${display(payload?.undelay_at)}.`
|
|
14081
|
+
}],
|
|
14082
|
+
["prospects monitor", {
|
|
14083
|
+
usage: "audienti prospects monitor <prsp_id> [--note <text>]",
|
|
14084
|
+
purpose: "Move one prospect to monitoring, like Monitor on the prospect page.",
|
|
14085
|
+
api: "POST /api/v1/accounts/:account_id/prospects/:id/monitor.json",
|
|
14086
|
+
options: { note: { type: "string" } },
|
|
14087
|
+
run: (client, accountId, [id], values) => client.monitorProspect(accountId, id, compactObject({ monitor_note: values.note })),
|
|
14088
|
+
done: ([id]) => `Monitoring prospect ${id}.`
|
|
14089
|
+
}],
|
|
14090
|
+
["prospects unmonitor", {
|
|
14091
|
+
usage: "audienti prospects unmonitor <prsp_id>",
|
|
14092
|
+
purpose: "Stop monitoring one prospect.",
|
|
14093
|
+
api: "POST /api/v1/accounts/:account_id/prospects/:id/unmonitor.json",
|
|
14094
|
+
run: (client, accountId, [id]) => client.unmonitorProspect(accountId, id),
|
|
14095
|
+
done: ([id]) => `Stopped monitoring prospect ${id}.`
|
|
14096
|
+
}],
|
|
14097
|
+
["prospects rename", {
|
|
14098
|
+
usage: "audienti prospects rename <prsp_id> --name <display_name>",
|
|
14099
|
+
purpose: "Change a prospect's display name. Backend admins only, like the web action.",
|
|
14100
|
+
api: "PATCH /api/v1/accounts/:account_id/prospects/:id/display_name.json",
|
|
14101
|
+
options: { name: { type: "string" } },
|
|
14102
|
+
required: ["name"],
|
|
14103
|
+
run: (client, accountId, [id], values) => client.renameProspect(accountId, id, { display_name: values.name }),
|
|
14104
|
+
done: ([id]) => `Renamed prospect ${id}.`
|
|
14105
|
+
}],
|
|
14106
|
+
["prospects import-post", {
|
|
14107
|
+
usage: "audienti prospects import-post <prsp_id> --url <post_url> [--topic <topic_id>]",
|
|
14108
|
+
purpose: "Import one LinkedIn post for a prospect in the background.",
|
|
14109
|
+
api: "POST /api/v1/accounts/:account_id/prospects/:id/import_post.json",
|
|
14110
|
+
options: { url: { type: "string" }, topic: { type: "string" } },
|
|
14111
|
+
required: ["url"],
|
|
14112
|
+
run: (client, accountId, [id], values) => client.importProspectPost(accountId, id, compactObject({ url: values.url, topic_id: values.topic })),
|
|
14113
|
+
done: ([id]) => `Queued post import for prospect ${id}.`
|
|
14114
|
+
}],
|
|
14115
|
+
["prospects sync", {
|
|
14116
|
+
usage: "audienti prospects sync <prsp_id> --social-cookie <id>",
|
|
14117
|
+
purpose: "Queue a message and email sync for one prospect through the selected LinkedIn account and other eligible sync accounts.",
|
|
14118
|
+
api: "POST /api/v1/accounts/:account_id/prospects/:id/sync.json",
|
|
14119
|
+
options: { "social-cookie": { type: "string" } },
|
|
14120
|
+
required: ["social-cookie"],
|
|
14121
|
+
run: (client, accountId, [id], values) => client.syncProspect(accountId, id, compactObject({ social_cookie_id: values["social-cookie"] })),
|
|
14122
|
+
done: ([id]) => `Queued message sync for prospect ${id}.`
|
|
14123
|
+
}],
|
|
14124
|
+
["prospects cancel-event", {
|
|
14125
|
+
usage: "audienti prospects cancel-event <prsp_id> --event <event_id>",
|
|
14126
|
+
purpose: "Cancel one scheduled event for a prospect.",
|
|
14127
|
+
api: "POST /api/v1/accounts/:account_id/prospects/:id/cancel_scheduled_event.json",
|
|
14128
|
+
options: { event: { type: "string" } },
|
|
14129
|
+
required: ["event"],
|
|
14130
|
+
run: (client, accountId, [id], values) => client.cancelProspectScheduledEvent(accountId, id, { event_id: values.event }),
|
|
14131
|
+
done: ([id], payload) => `Canceled scheduled event ${display(payload?.event?.id)} for prospect ${id}.`
|
|
14132
|
+
}],
|
|
14133
|
+
["prospects queue-draft", {
|
|
14134
|
+
usage: "audienti prospects queue-draft <prsp_id> [--context <json_object>] [--angle <n>]",
|
|
14135
|
+
purpose: "Write the queue draft for one prospect, like Write in the queue composer. Without --context the server uses the prospect's current queue action.",
|
|
14136
|
+
api: "POST /api/v1/accounts/:account_id/prospects/:id/write.json",
|
|
14137
|
+
options: { context: { type: "string" }, angle: { type: "string" } },
|
|
14138
|
+
parse: (values) => ({ ...values, context: parseJsonObjectOption(values.context, "--context") }),
|
|
14139
|
+
run: (client, accountId, [id], values) => client.writeProspectQueueDraft(accountId, id, compactObject({ context: values.context, angle_index: values.angle })),
|
|
14140
|
+
done: ([id]) => `Wrote queue draft for prospect ${id}.`
|
|
14141
|
+
}],
|
|
14142
|
+
["prospects rewrite", {
|
|
14143
|
+
usage: "audienti prospects rewrite <prsp_id> [--subject <text>] [--message <text>] [--angle <n>]",
|
|
14144
|
+
purpose: "Start a fresh queue draft for one prospect in the background.",
|
|
14145
|
+
api: "POST /api/v1/accounts/:account_id/prospects/:id/rewrite.json",
|
|
14146
|
+
options: { subject: { type: "string" }, message: { type: "string" }, angle: { type: "string" } },
|
|
14147
|
+
run: (client, accountId, [id], values) => client.rewriteProspectQueueDraft(accountId, id, compactObject({ subject: values.subject, message: values.message, angle_index: values.angle })),
|
|
14148
|
+
done: ([id]) => `Started queue draft rewrite for prospect ${id}.`
|
|
14149
|
+
}],
|
|
14150
|
+
["prospects engage", {
|
|
14151
|
+
usage: "audienti prospects engage <prsp_id> --type <action_type> [--message <text>] [--subject <text>] [--message-mode <mode>] [--social-cookie <id>] [--profile <id>] [--scheduled-at <iso8601>] [--post <post_id>] [--comment <comment_id>] [--reply-to <event_id>] [--request-event <event_id>] [--request-mode blank] [--queue-action] [--principal <account_user_id>]",
|
|
14152
|
+
purpose: "Run one manual engagement for a prospect with the same rules as the prospect composer. Use --post or --comment for post likes and comments, --reply-to for a message reply, and --queue-action to act like the queue composer (a connection request on a disconnected LinkedIn account is prepared, not sent).",
|
|
14153
|
+
api: "POST /api/v1/accounts/:account_id/prospects/:id/engage.json",
|
|
14154
|
+
options: {
|
|
14155
|
+
type: { type: "string" },
|
|
14156
|
+
message: { type: "string" },
|
|
14157
|
+
subject: { type: "string" },
|
|
14158
|
+
"message-mode": { type: "string" },
|
|
14159
|
+
"social-cookie": { type: "string" },
|
|
14160
|
+
profile: { type: "string" },
|
|
14161
|
+
"scheduled-at": { type: "string" },
|
|
14162
|
+
post: { type: "string" },
|
|
14163
|
+
comment: { type: "string" },
|
|
14164
|
+
"reply-to": { type: "string" },
|
|
14165
|
+
"request-event": { type: "string" },
|
|
14166
|
+
"request-mode": { type: "string" },
|
|
14167
|
+
"queue-action": { type: "boolean" },
|
|
14168
|
+
principal: { type: "string" }
|
|
14169
|
+
},
|
|
14170
|
+
required: ["type"],
|
|
14171
|
+
run: (client, accountId, [id], values) => client.engageProspect(accountId, id, compactObject({
|
|
14172
|
+
action_type: values.type,
|
|
14173
|
+
message: values.message,
|
|
14174
|
+
subject: values.subject,
|
|
14175
|
+
message_mode: values["message-mode"],
|
|
14176
|
+
engage_point_id: values["social-cookie"],
|
|
14177
|
+
profile_id: values.profile,
|
|
14178
|
+
scheduled_at: values["scheduled-at"],
|
|
14179
|
+
mention_id: values.post,
|
|
14180
|
+
comment_id: values.comment,
|
|
14181
|
+
message_id: values["reply-to"],
|
|
14182
|
+
request_event_id: values["request-event"],
|
|
14183
|
+
request_mode: values["request-mode"],
|
|
14184
|
+
queue_action: values["queue-action"] ? true : undefined,
|
|
14185
|
+
principal_account_user_id: values.principal
|
|
14186
|
+
})),
|
|
14187
|
+
done: (_positionals, payload) => display(payload?.message)
|
|
14188
|
+
}],
|
|
14189
|
+
["prospects reject-selected", {
|
|
14190
|
+
usage: "audienti prospects reject-selected <prsp_id> [<prsp_id> ...]",
|
|
14191
|
+
minPositionals: 1,
|
|
14192
|
+
maxPositionals: Infinity,
|
|
14193
|
+
purpose: "Reject several prospects at once. Locked or unavailable prospects are reported as failed.",
|
|
14194
|
+
api: "POST /api/v1/accounts/:account_id/prospects/reject_selected.json",
|
|
14195
|
+
run: (client, accountId, ids) => client.rejectSelectedProspects(accountId, { prospect_ids: ids }),
|
|
14196
|
+
done: (_positionals, payload) => `Rejected ${payload?.rejected?.length ?? 0}; failed ${payload?.failed?.length ?? 0}.`
|
|
14197
|
+
}],
|
|
14198
|
+
["prospects intake", {
|
|
14199
|
+
usage: "audienti prospects intake --url <linkedin_url> [--list <list_id>] [--assign <account_user_id>] [--signal <custom_signal_id>]",
|
|
14200
|
+
minPositionals: 0,
|
|
14201
|
+
maxPositionals: 0,
|
|
14202
|
+
purpose: "Add one LinkedIn person by URL, like Add prospect on the web.",
|
|
14203
|
+
api: "POST /api/v1/accounts/:account_id/prospects/intake.json",
|
|
14204
|
+
options: { url: { type: "string" }, list: { type: "string" }, assign: { type: "string" }, signal: { type: "string" } },
|
|
14205
|
+
required: ["url"],
|
|
14206
|
+
run: (client, accountId, _positionals, values) => client.intakeProspect(accountId, compactObject({
|
|
14207
|
+
url: values.url,
|
|
14208
|
+
list_id: values.list,
|
|
14209
|
+
assigned_to_account_user_id: values.assign,
|
|
14210
|
+
custom_signal_id: values.signal
|
|
14211
|
+
})),
|
|
14212
|
+
done: () => "Prospect intake queued."
|
|
14213
|
+
}],
|
|
14214
|
+
["events retry", {
|
|
14215
|
+
usage: "audienti events retry <event_id>",
|
|
14216
|
+
purpose: "Retry one failed event, with the same eligibility rules as the web retry button.",
|
|
14217
|
+
api: "POST /api/v1/accounts/:account_id/events/:id/retry.json",
|
|
14218
|
+
run: (client, accountId, [id]) => client.retryEvent(accountId, id),
|
|
14219
|
+
done: ([id], payload) => `Retried event ${id} as ${display(payload?.event?.id)}.`
|
|
14220
|
+
}],
|
|
14221
|
+
["profiles delete", {
|
|
14222
|
+
usage: "audienti profiles delete <profile_id>",
|
|
14223
|
+
purpose: "Remove one profile from its prospect. Backend admins only, like the web action.",
|
|
14224
|
+
api: "DELETE /api/v1/accounts/:account_id/profiles/:id.json",
|
|
14225
|
+
run: (client, accountId, [id]) => client.deleteProfile(accountId, id),
|
|
14226
|
+
done: ([id]) => `Removed profile ${id}.`
|
|
14227
|
+
}],
|
|
14228
|
+
["companies stop-pursuing", {
|
|
14229
|
+
usage: "audienti companies stop-pursuing <company_profile_id>",
|
|
14230
|
+
purpose: "Stop pursuing one company, like Stop pursuing on the company page.",
|
|
14231
|
+
api: "POST /api/v1/accounts/:account_id/companies/:id/stop_pursuing.json",
|
|
14232
|
+
run: (client, accountId, [id]) => client.stopPursuingCompany(accountId, id),
|
|
14233
|
+
done: ([id]) => `Stopped pursuing company ${id}.`
|
|
14234
|
+
}],
|
|
14235
|
+
["content defer", {
|
|
14236
|
+
usage: "audienti content defer <work_item_id> --for <1_day|1_week|1_month|3_months|6_months>",
|
|
14237
|
+
purpose: "Defer one content work item.",
|
|
14238
|
+
api: "POST /api/v1/accounts/:account_id/content_ops/work_items/:id/defer.json",
|
|
14239
|
+
options: { for: { type: "string" } },
|
|
14240
|
+
required: ["for"],
|
|
14241
|
+
run: (client, accountId, [id], values) => client.contentDefer(accountId, id, { delay_duration: values.for }),
|
|
14242
|
+
done: ([id]) => `Deferred work item ${id}.`
|
|
14243
|
+
}],
|
|
14244
|
+
["inbox-ops update-filters", {
|
|
14245
|
+
usage: "audienti inbox-ops update-filters [--subscriptions <true|false>] [--automated-no-reply <true|false>] [--provider-promotions <true|false>] [--sender-allow <email>]... [--sender-filter <email>]... [--domain-allow <domain>]... [--domain-filter <domain>]...",
|
|
14246
|
+
minPositionals: 0,
|
|
14247
|
+
maxPositionals: 0,
|
|
14248
|
+
purpose: "Save the owner's Inbox Ops email filters. Rule flags replace the saved list for that rule.",
|
|
14249
|
+
api: "PATCH /api/v1/accounts/:account_id/inbox_ops/filters.json",
|
|
14250
|
+
options: {
|
|
14251
|
+
subscriptions: { type: "string" },
|
|
14252
|
+
"automated-no-reply": { type: "string" },
|
|
14253
|
+
"provider-promotions": { type: "string" },
|
|
14254
|
+
"sender-allow": { type: "string", multiple: true },
|
|
14255
|
+
"sender-filter": { type: "string", multiple: true },
|
|
14256
|
+
"domain-allow": { type: "string", multiple: true },
|
|
14257
|
+
"domain-filter": { type: "string", multiple: true }
|
|
14258
|
+
},
|
|
14259
|
+
run: (client, accountId, _positionals, values) => client.updateInboxOpsFilters(accountId, compactObject({
|
|
14260
|
+
subscriptions: values.subscriptions,
|
|
14261
|
+
automated_no_reply: values["automated-no-reply"],
|
|
14262
|
+
provider_promotions: values["provider-promotions"],
|
|
14263
|
+
sender_allow_rules: values["sender-allow"],
|
|
14264
|
+
sender_filter_rules: values["sender-filter"],
|
|
14265
|
+
domain_allow_rules: values["domain-allow"],
|
|
14266
|
+
domain_filter_rules: values["domain-filter"]
|
|
14267
|
+
})),
|
|
14268
|
+
done: () => "Saved Inbox Ops filters."
|
|
14269
|
+
}],
|
|
14270
|
+
["inbox-ops reply", {
|
|
14271
|
+
usage: "audienti inbox-ops reply <row_id> --message <text> [--subject <text>]",
|
|
14272
|
+
purpose: "Queue a reply to one Inbox Ops row. Each run sends a new reply token.",
|
|
14273
|
+
api: "POST /api/v1/accounts/:account_id/inbox_ops/:row_id/reply.json",
|
|
14274
|
+
options: { message: { type: "string" }, subject: { type: "string" } },
|
|
14275
|
+
required: ["message"],
|
|
14276
|
+
run: (client, accountId, [rowId], values) => client.inboxOpsReply(accountId, rowId, compactObject({
|
|
14277
|
+
message: values.message,
|
|
14278
|
+
subject: values.subject,
|
|
14279
|
+
reply_token: randomUUID()
|
|
14280
|
+
})),
|
|
14281
|
+
done: ([rowId], payload) => `Reply ${display(payload?.status)} for ${rowId}.`
|
|
14282
|
+
}],
|
|
14283
|
+
["inbox-ops draft-reply", {
|
|
14284
|
+
usage: "audienti inbox-ops draft-reply <row_id>",
|
|
14285
|
+
purpose: "Draft a reply for one Inbox Ops row without sending it.",
|
|
14286
|
+
api: "POST /api/v1/accounts/:account_id/inbox_ops/:row_id/reply/write.json",
|
|
14287
|
+
run: (client, accountId, [rowId]) => client.inboxOpsWriteReply(accountId, rowId),
|
|
14288
|
+
done: (_positionals, payload) => display(payload?.message ?? payload?.body)
|
|
14289
|
+
}],
|
|
14290
|
+
["inbox-ops adopt", {
|
|
14291
|
+
usage: "audienti inbox-ops adopt <row_id> [--target-account <acct_id>] [--motion <motion_id>]",
|
|
14292
|
+
purpose: "Adopt the prospect behind one Inbox Ops row.",
|
|
14293
|
+
api: "POST /api/v1/accounts/:account_id/inbox_ops/:row_id/adopt.json",
|
|
14294
|
+
options: { "target-account": { type: "string" }, motion: { type: "string" } },
|
|
14295
|
+
run: (client, accountId, [rowId], values) => client.inboxOpsAdopt(accountId, rowId, compactObject({
|
|
14296
|
+
target_account_id: values["target-account"],
|
|
14297
|
+
motion_id: values.motion
|
|
14298
|
+
})),
|
|
14299
|
+
done: ([rowId]) => `Adopted ${rowId}.`
|
|
14300
|
+
}],
|
|
14301
|
+
["network-ops adopt", {
|
|
14302
|
+
usage: "audienti network-ops adopt <row_id> --target-account <acct_id> --motion <motion_id>",
|
|
14303
|
+
purpose: "Accept one inbound connection request and adopt the person into a motion in an account owned by the request's LinkedIn account owner.",
|
|
14304
|
+
api: "POST /api/v1/accounts/:account_id/network_ops/:row_id/adopt.json",
|
|
14305
|
+
options: { "target-account": { type: "string" }, motion: { type: "string" } },
|
|
14306
|
+
required: ["target-account", "motion"],
|
|
14307
|
+
run: (client, accountId, [rowId], values) => client.networkOpsAction(accountId, rowId, "adopt", {
|
|
14308
|
+
target_account_id: values["target-account"],
|
|
14309
|
+
motion_id: values.motion
|
|
14310
|
+
}),
|
|
14311
|
+
done: ([rowId]) => `Adopted ${rowId}.`
|
|
14312
|
+
}],
|
|
14313
|
+
["network-ops ignore", {
|
|
14314
|
+
usage: "audienti network-ops ignore <row_id>",
|
|
14315
|
+
purpose: "Ignore one inbound connection request without accepting or declining it.",
|
|
14316
|
+
api: "POST /api/v1/accounts/:account_id/network_ops/:row_id/ignore.json",
|
|
14317
|
+
run: (client, accountId, [rowId]) => client.networkOpsAction(accountId, rowId, "ignore"),
|
|
14318
|
+
done: ([rowId]) => `Ignored ${rowId}.`
|
|
14319
|
+
}],
|
|
14320
|
+
["reconciliations add-to-motion", {
|
|
14321
|
+
usage: "audienti reconciliations add-to-motion <source_key> --motion <motion_id>",
|
|
14322
|
+
purpose: "Add one reconciliation candidate to a motion.",
|
|
14323
|
+
api: "POST /api/v1/accounts/:account_id/reconciliations/:id/add_to_motion.json",
|
|
14324
|
+
options: { motion: { type: "string" } },
|
|
14325
|
+
required: ["motion"],
|
|
14326
|
+
run: (client, accountId, [key], values) => client.reconciliationAddToMotion(accountId, key, { motion_id: values.motion }),
|
|
14327
|
+
done: ([key]) => `Added ${key} to the motion.`
|
|
14328
|
+
}],
|
|
14329
|
+
["reconciliations ignore", {
|
|
14330
|
+
usage: "audienti reconciliations ignore <source_key>",
|
|
14331
|
+
purpose: "Ignore one reconciliation candidate.",
|
|
14332
|
+
api: "POST /api/v1/accounts/:account_id/reconciliations/:id/ignore.json",
|
|
14333
|
+
run: (client, accountId, [key]) => client.reconciliationIgnore(accountId, key),
|
|
14334
|
+
done: ([key]) => `Ignored ${key}.`
|
|
14335
|
+
}]
|
|
14336
|
+
]);
|
|
14337
|
+
|
|
14338
|
+
for (const [topic, spec] of PARITY_ACTION_COMMANDS) {
|
|
14339
|
+
if (HELP_TOPICS.has(topic)) throw new Error(`duplicate help topic ${topic}`);
|
|
14340
|
+
HELP_TOPICS.set(topic, [
|
|
14341
|
+
"Usage:",
|
|
14342
|
+
` ${spec.usage} ${ACCOUNT_OPTION_USAGE}`,
|
|
14343
|
+
"",
|
|
14344
|
+
"Status: implemented",
|
|
14345
|
+
"",
|
|
14346
|
+
"Purpose:",
|
|
14347
|
+
` ${spec.purpose}`,
|
|
14348
|
+
"",
|
|
14349
|
+
"API:",
|
|
14350
|
+
` ${spec.api}`
|
|
14351
|
+
].join("\n"));
|
|
14352
|
+
}
|
|
14353
|
+
|
|
14354
|
+
// List the single-purpose actions under their group help (`audienti help prospects`).
|
|
14355
|
+
const parityActionUsagesByGroup = new Map();
|
|
14356
|
+
for (const [topic, spec] of PARITY_ACTION_COMMANDS) {
|
|
14357
|
+
const group = topic.split(" ")[0];
|
|
14358
|
+
if (!parityActionUsagesByGroup.has(group)) parityActionUsagesByGroup.set(group, []);
|
|
14359
|
+
parityActionUsagesByGroup.get(group).push(` ${spec.usage} ${ACCOUNT_OPTION_USAGE}`);
|
|
14360
|
+
}
|
|
14361
|
+
for (const [group, usages] of parityActionUsagesByGroup) {
|
|
14362
|
+
const section = ["Actions:", ...usages].join("\n");
|
|
14363
|
+
const existing = HELP_TOPICS.get(group);
|
|
14364
|
+
HELP_TOPICS.set(group, existing ? `${existing}\n\n${section}` : section);
|
|
14365
|
+
}
|
|
14366
|
+
|
|
14367
|
+
function parseJsonObjectOption(raw, flag) {
|
|
14368
|
+
if (raw === undefined) return undefined;
|
|
14369
|
+
let parsed;
|
|
14370
|
+
try {
|
|
14371
|
+
parsed = JSON.parse(raw);
|
|
14372
|
+
} catch {
|
|
14373
|
+
throw new CommandError(`${flag} must be a JSON object, for example '{"selected_action":{"key":"send_email"}}'.`);
|
|
14374
|
+
}
|
|
14375
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
14376
|
+
throw new CommandError(`${flag} must be a JSON object, for example '{"selected_action":{"key":"send_email"}}'.`);
|
|
14377
|
+
}
|
|
14378
|
+
return parsed;
|
|
14379
|
+
}
|
|
14380
|
+
|
|
14381
|
+
async function runParityActionCommand(topic, args, context, { accountOverride } = {}) {
|
|
14382
|
+
const spec = PARITY_ACTION_COMMANDS.get(topic);
|
|
14383
|
+
const usageLine = `Usage: ${spec.usage} ${ACCOUNT_OPTION_USAGE}`;
|
|
14384
|
+
const { values, positionals } = parseCommandArgs(args, { ...jsonOptions(), ...(spec.options || {}) });
|
|
14385
|
+
const minPositionals = spec.minPositionals ?? 1;
|
|
14386
|
+
const maxPositionals = spec.maxPositionals ?? 1;
|
|
14387
|
+
if (positionals.length < minPositionals || positionals.length > maxPositionals) throw new CommandError(usageLine);
|
|
14388
|
+
if ((spec.required || []).some((key) => !values[key])) throw new CommandError(usageLine);
|
|
14389
|
+
|
|
14390
|
+
const parsedValues = spec.parse ? spec.parse(values, usageLine) : values;
|
|
14391
|
+
|
|
14392
|
+
const { client, accountId } = await requireAccountContext(context, { accountOverride });
|
|
14393
|
+
const payload = await spec.run(client, accountId, positionals, parsedValues);
|
|
14394
|
+
if (values.json) return writeJson(context.stdout, payload);
|
|
14395
|
+
|
|
14396
|
+
writeLine(context.stdout, spec.done(positionals, payload));
|
|
14397
|
+
}
|