@audienti/cli 0.1.78 → 0.1.80
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 +14 -0
- package/package.json +1 -1
- package/src/api-client.js +210 -2
- package/src/cli.js +436 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,20 @@ All notable changes to the Audienti CLI are documented here.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.1.80] - 2026-09-28
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Run the motion list and motion health actions from the CLI: `motions bulk-add-tag --tag`, `bulk-remove-tag --tag`, `bulk-update-principal --principal`, `bulk-update-status --status` (prints any motion the lifecycle rules blocked), `retire-strategy --strategy`, `refresh-launch-check`, `refresh-launch-checks`, and `update-premise --premise`.
|
|
12
|
+
|
|
13
|
+
## [0.1.79] - 2026-09-28
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- 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`.
|
|
18
|
+
- 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`.
|
|
19
|
+
- 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.
|
|
20
|
+
|
|
7
21
|
## [0.1.78] - 2026-09-27
|
|
8
22
|
|
|
9
23
|
### Fixed
|
package/package.json
CHANGED
package/src/api-client.js
CHANGED
|
@@ -675,6 +675,59 @@ export class AudientiClient {
|
|
|
675
675
|
});
|
|
676
676
|
}
|
|
677
677
|
|
|
678
|
+
bulkAddMotionTag(accountId, body) {
|
|
679
|
+
return this.requestJson(accountPath(accountId, ["motions", "bulk_add_tag"]), {
|
|
680
|
+
method: "POST",
|
|
681
|
+
body
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
bulkRemoveMotionTag(accountId, body) {
|
|
686
|
+
return this.requestJson(accountPath(accountId, ["motions", "bulk_remove_tag"]), {
|
|
687
|
+
method: "POST",
|
|
688
|
+
body
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
bulkUpdateMotionPrincipal(accountId, body) {
|
|
693
|
+
return this.requestJson(accountPath(accountId, ["motions", "bulk_update_principal"]), {
|
|
694
|
+
method: "POST",
|
|
695
|
+
body
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
bulkUpdateMotionStatus(accountId, body) {
|
|
700
|
+
return this.requestJson(accountPath(accountId, ["motions", "bulk_update_status"]), {
|
|
701
|
+
method: "POST",
|
|
702
|
+
body
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
retireMotionStrategy(accountId, motionId, strategyId) {
|
|
707
|
+
return this.requestJson(accountPath(accountId, ["motions", motionId, "strategies", strategyId, "retire"]), {
|
|
708
|
+
method: "POST"
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
refreshMotionLaunchCheck(accountId, motionId) {
|
|
713
|
+
return this.requestJson(accountPath(accountId, ["motions", motionId, "refresh_launch_check"]), {
|
|
714
|
+
method: "POST"
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
refreshMotionLaunchChecks(accountId) {
|
|
719
|
+
return this.requestJson(accountPath(accountId, ["motions", "refresh_launch_checks"]), {
|
|
720
|
+
method: "POST"
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
updateMotionPremise(accountId, motionId, body) {
|
|
725
|
+
return this.requestJson(accountPath(accountId, ["motions", motionId, "update_premise"]), {
|
|
726
|
+
method: "PATCH",
|
|
727
|
+
body
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
|
|
678
731
|
createQuickStart(accountId, body) {
|
|
679
732
|
return this.requestJson(accountPath(accountId, ["quick_start"]), {
|
|
680
733
|
method: "POST",
|
|
@@ -794,6 +847,160 @@ export class AudientiClient {
|
|
|
794
847
|
});
|
|
795
848
|
}
|
|
796
849
|
|
|
850
|
+
deferProspect(accountId, prospectId) {
|
|
851
|
+
return this.requestJson(accountPath(accountId, ["prospects", prospectId, "defer"]), {
|
|
852
|
+
method: "POST"
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
delayProspect(accountId, prospectId, body = {}) {
|
|
857
|
+
return this.requestJson(accountPath(accountId, ["prospects", prospectId, "delay"]), {
|
|
858
|
+
method: "POST",
|
|
859
|
+
body
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
monitorProspect(accountId, prospectId, body = {}) {
|
|
864
|
+
return this.requestJson(accountPath(accountId, ["prospects", prospectId, "monitor"]), {
|
|
865
|
+
method: "POST",
|
|
866
|
+
body
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
unmonitorProspect(accountId, prospectId) {
|
|
871
|
+
return this.requestJson(accountPath(accountId, ["prospects", prospectId, "unmonitor"]), {
|
|
872
|
+
method: "POST"
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
renameProspect(accountId, prospectId, body = {}) {
|
|
877
|
+
return this.requestJson(accountPath(accountId, ["prospects", prospectId, "display_name"]), {
|
|
878
|
+
method: "PATCH",
|
|
879
|
+
body
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
importProspectPost(accountId, prospectId, body = {}) {
|
|
884
|
+
return this.requestJson(accountPath(accountId, ["prospects", prospectId, "import_post"]), {
|
|
885
|
+
method: "POST",
|
|
886
|
+
body
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
syncProspect(accountId, prospectId, body = {}) {
|
|
891
|
+
return this.requestJson(accountPath(accountId, ["prospects", prospectId, "sync"]), {
|
|
892
|
+
method: "POST",
|
|
893
|
+
body
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
cancelProspectScheduledEvent(accountId, prospectId, body = {}) {
|
|
898
|
+
return this.requestJson(accountPath(accountId, ["prospects", prospectId, "cancel_scheduled_event"]), {
|
|
899
|
+
method: "POST",
|
|
900
|
+
body
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
writeProspectQueueDraft(accountId, prospectId, body = {}) {
|
|
905
|
+
return this.requestJson(accountPath(accountId, ["prospects", prospectId, "write"]), {
|
|
906
|
+
method: "POST",
|
|
907
|
+
body
|
|
908
|
+
});
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
rewriteProspectQueueDraft(accountId, prospectId, body = {}) {
|
|
912
|
+
return this.requestJson(accountPath(accountId, ["prospects", prospectId, "rewrite"]), {
|
|
913
|
+
method: "POST",
|
|
914
|
+
body
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
engageProspect(accountId, prospectId, body = {}) {
|
|
919
|
+
return this.requestJson(accountPath(accountId, ["prospects", prospectId, "engage"]), {
|
|
920
|
+
method: "POST",
|
|
921
|
+
body
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
rejectSelectedProspects(accountId, body = {}) {
|
|
926
|
+
return this.requestJson(accountPath(accountId, ["prospects", "reject_selected"]), {
|
|
927
|
+
method: "POST",
|
|
928
|
+
body
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
intakeProspect(accountId, body = {}) {
|
|
933
|
+
return this.requestJson(accountPath(accountId, ["prospects", "intake"]), {
|
|
934
|
+
method: "POST",
|
|
935
|
+
body
|
|
936
|
+
});
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
retryEvent(accountId, eventId) {
|
|
940
|
+
return this.requestJson(accountPath(accountId, ["events", eventId, "retry"]), {
|
|
941
|
+
method: "POST"
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
deleteProfile(accountId, profileId) {
|
|
946
|
+
return this.requestJson(accountPath(accountId, ["profiles", profileId]), {
|
|
947
|
+
method: "DELETE"
|
|
948
|
+
});
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
stopPursuingCompany(accountId, companyId) {
|
|
952
|
+
return this.requestJson(accountPath(accountId, ["companies", companyId, "stop_pursuing"]), {
|
|
953
|
+
method: "POST"
|
|
954
|
+
});
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
contentDefer(accountId, workItemId, body = {}) {
|
|
958
|
+
return this.requestJson(accountPath(accountId, ["content_ops", "work_items", workItemId, "defer"]), {
|
|
959
|
+
method: "POST",
|
|
960
|
+
body
|
|
961
|
+
});
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
updateInboxOpsFilters(accountId, body = {}) {
|
|
965
|
+
return this.requestJson(accountPath(accountId, ["inbox_ops", "filters"]), {
|
|
966
|
+
method: "PATCH",
|
|
967
|
+
body
|
|
968
|
+
});
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
inboxOpsReply(accountId, rowId, body = {}) {
|
|
972
|
+
return this.requestJson(accountPath(accountId, ["inbox_ops", rowId, "reply"]), {
|
|
973
|
+
method: "POST",
|
|
974
|
+
body
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
inboxOpsWriteReply(accountId, rowId) {
|
|
979
|
+
return this.requestJson(accountPath(accountId, ["inbox_ops", rowId, "reply", "write"]), {
|
|
980
|
+
method: "POST"
|
|
981
|
+
});
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
inboxOpsAdopt(accountId, rowId, body = {}) {
|
|
985
|
+
return this.requestJson(accountPath(accountId, ["inbox_ops", rowId, "adopt"]), {
|
|
986
|
+
method: "POST",
|
|
987
|
+
body
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
reconciliationAddToMotion(accountId, sourceKey, body = {}) {
|
|
992
|
+
return this.requestJson(accountPath(accountId, ["reconciliations", sourceKey, "add_to_motion"]), {
|
|
993
|
+
method: "POST",
|
|
994
|
+
body
|
|
995
|
+
});
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
reconciliationIgnore(accountId, sourceKey) {
|
|
999
|
+
return this.requestJson(accountPath(accountId, ["reconciliations", sourceKey, "ignore"]), {
|
|
1000
|
+
method: "POST"
|
|
1001
|
+
});
|
|
1002
|
+
}
|
|
1003
|
+
|
|
797
1004
|
prospectSequencePreview(accountId, prospectId, body = {}) {
|
|
798
1005
|
return this.requestJson(accountPath(accountId, ["prospects", prospectId, "sequence_preview"]), {
|
|
799
1006
|
method: "POST",
|
|
@@ -883,9 +1090,10 @@ export class AudientiClient {
|
|
|
883
1090
|
return this.requestJson(accountPath(accountId, ["operator"], query));
|
|
884
1091
|
}
|
|
885
1092
|
|
|
886
|
-
networkOpsAction(accountId, rowId, action) {
|
|
1093
|
+
networkOpsAction(accountId, rowId, action, body) {
|
|
887
1094
|
return this.requestJson(accountPath(accountId, ["network_ops", rowId, action]), {
|
|
888
|
-
method: "POST"
|
|
1095
|
+
method: "POST",
|
|
1096
|
+
...(body === undefined ? {} : { body })
|
|
889
1097
|
});
|
|
890
1098
|
}
|
|
891
1099
|
|
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);
|
|
@@ -3335,10 +3337,21 @@ async function motionsUpdate(args, context, { accountOverride } = {}) {
|
|
|
3335
3337
|
|
|
3336
3338
|
const { client, accountId } = await requireAccountContext(context, { accountOverride });
|
|
3337
3339
|
const motion = await client.updateMotion(accountId, positionals[0], { motion: await motionUpdatePayload(values) });
|
|
3338
|
-
|
|
3340
|
+
const statusBlocked = motion?.status_change?.status === "blocked";
|
|
3341
|
+
if (values.json) {
|
|
3342
|
+
writeJson(context.stdout, motion);
|
|
3343
|
+
return statusBlocked ? 1 : undefined;
|
|
3344
|
+
}
|
|
3339
3345
|
|
|
3340
3346
|
writeLine(context.stdout, `Updated motion ${display(motion?.name)} (${display(motion?.prefix_id)}).`);
|
|
3341
3347
|
renderMotion(motion, context);
|
|
3348
|
+
if (!statusBlocked) return undefined;
|
|
3349
|
+
|
|
3350
|
+
writeLine(
|
|
3351
|
+
context.stderr,
|
|
3352
|
+
`Status not changed to ${display(motion.status_change.requested_status)}: ${display(motion.status_change.reason)}. Other changes were saved.`
|
|
3353
|
+
);
|
|
3354
|
+
return 1;
|
|
3342
3355
|
}
|
|
3343
3356
|
|
|
3344
3357
|
async function motionUpdatePayload(values) {
|
|
@@ -14056,3 +14069,424 @@ const HELP_TOPICS = new Map([
|
|
|
14056
14069
|
" Operator outcome writeback is implemented for prospect rows, not visibility rows."
|
|
14057
14070
|
].join("\n")]
|
|
14058
14071
|
]);
|
|
14072
|
+
|
|
14073
|
+
// Single-purpose actions that mirror one web button each (#2300, #2302).
|
|
14074
|
+
// Every entry calls one API route that shares its service with the web action.
|
|
14075
|
+
const ACCOUNT_OPTION_USAGE = "[--json] [--account <acct_id>]";
|
|
14076
|
+
const PARITY_ACTION_COMMANDS = new Map([
|
|
14077
|
+
["prospects defer", {
|
|
14078
|
+
usage: "audienti prospects defer <prsp_id>",
|
|
14079
|
+
purpose: "Push one prospect out of the queue for 24 hours, like Defer in the queue.",
|
|
14080
|
+
api: "POST /api/v1/accounts/:account_id/prospects/:id/defer.json",
|
|
14081
|
+
run: (client, accountId, [id]) => client.deferProspect(accountId, id),
|
|
14082
|
+
done: ([id]) => `Deferred prospect ${id} for 24 hours.`
|
|
14083
|
+
}],
|
|
14084
|
+
["prospects delay", {
|
|
14085
|
+
usage: "audienti prospects delay <prsp_id> --for <1_day|1_week|1_month|3_months|6_months>",
|
|
14086
|
+
purpose: "Delay one prospect and move it to the Delayed list until the window ends.",
|
|
14087
|
+
api: "POST /api/v1/accounts/:account_id/prospects/:id/delay.json",
|
|
14088
|
+
options: { for: { type: "string" } },
|
|
14089
|
+
required: ["for"],
|
|
14090
|
+
run: (client, accountId, [id], values) => client.delayProspect(accountId, id, { delay_duration: values.for }),
|
|
14091
|
+
done: ([id], payload) => `Delayed prospect ${id} until ${display(payload?.undelay_at)}.`
|
|
14092
|
+
}],
|
|
14093
|
+
["prospects monitor", {
|
|
14094
|
+
usage: "audienti prospects monitor <prsp_id> [--note <text>]",
|
|
14095
|
+
purpose: "Move one prospect to monitoring, like Monitor on the prospect page.",
|
|
14096
|
+
api: "POST /api/v1/accounts/:account_id/prospects/:id/monitor.json",
|
|
14097
|
+
options: { note: { type: "string" } },
|
|
14098
|
+
run: (client, accountId, [id], values) => client.monitorProspect(accountId, id, compactObject({ monitor_note: values.note })),
|
|
14099
|
+
done: ([id]) => `Monitoring prospect ${id}.`
|
|
14100
|
+
}],
|
|
14101
|
+
["prospects unmonitor", {
|
|
14102
|
+
usage: "audienti prospects unmonitor <prsp_id>",
|
|
14103
|
+
purpose: "Stop monitoring one prospect.",
|
|
14104
|
+
api: "POST /api/v1/accounts/:account_id/prospects/:id/unmonitor.json",
|
|
14105
|
+
run: (client, accountId, [id]) => client.unmonitorProspect(accountId, id),
|
|
14106
|
+
done: ([id]) => `Stopped monitoring prospect ${id}.`
|
|
14107
|
+
}],
|
|
14108
|
+
["prospects rename", {
|
|
14109
|
+
usage: "audienti prospects rename <prsp_id> --name <display_name>",
|
|
14110
|
+
purpose: "Change a prospect's display name. Backend admins only, like the web action.",
|
|
14111
|
+
api: "PATCH /api/v1/accounts/:account_id/prospects/:id/display_name.json",
|
|
14112
|
+
options: { name: { type: "string" } },
|
|
14113
|
+
required: ["name"],
|
|
14114
|
+
run: (client, accountId, [id], values) => client.renameProspect(accountId, id, { display_name: values.name }),
|
|
14115
|
+
done: ([id]) => `Renamed prospect ${id}.`
|
|
14116
|
+
}],
|
|
14117
|
+
["prospects import-post", {
|
|
14118
|
+
usage: "audienti prospects import-post <prsp_id> --url <post_url> [--topic <topic_id>]",
|
|
14119
|
+
purpose: "Import one LinkedIn post for a prospect in the background.",
|
|
14120
|
+
api: "POST /api/v1/accounts/:account_id/prospects/:id/import_post.json",
|
|
14121
|
+
options: { url: { type: "string" }, topic: { type: "string" } },
|
|
14122
|
+
required: ["url"],
|
|
14123
|
+
run: (client, accountId, [id], values) => client.importProspectPost(accountId, id, compactObject({ url: values.url, topic_id: values.topic })),
|
|
14124
|
+
done: ([id]) => `Queued post import for prospect ${id}.`
|
|
14125
|
+
}],
|
|
14126
|
+
["prospects sync", {
|
|
14127
|
+
usage: "audienti prospects sync <prsp_id> --social-cookie <id>",
|
|
14128
|
+
purpose: "Queue a message and email sync for one prospect through the selected LinkedIn account and other eligible sync accounts.",
|
|
14129
|
+
api: "POST /api/v1/accounts/:account_id/prospects/:id/sync.json",
|
|
14130
|
+
options: { "social-cookie": { type: "string" } },
|
|
14131
|
+
required: ["social-cookie"],
|
|
14132
|
+
run: (client, accountId, [id], values) => client.syncProspect(accountId, id, compactObject({ social_cookie_id: values["social-cookie"] })),
|
|
14133
|
+
done: ([id]) => `Queued message sync for prospect ${id}.`
|
|
14134
|
+
}],
|
|
14135
|
+
["prospects cancel-event", {
|
|
14136
|
+
usage: "audienti prospects cancel-event <prsp_id> --event <event_id>",
|
|
14137
|
+
purpose: "Cancel one scheduled event for a prospect.",
|
|
14138
|
+
api: "POST /api/v1/accounts/:account_id/prospects/:id/cancel_scheduled_event.json",
|
|
14139
|
+
options: { event: { type: "string" } },
|
|
14140
|
+
required: ["event"],
|
|
14141
|
+
run: (client, accountId, [id], values) => client.cancelProspectScheduledEvent(accountId, id, { event_id: values.event }),
|
|
14142
|
+
done: ([id], payload) => `Canceled scheduled event ${display(payload?.event?.id)} for prospect ${id}.`
|
|
14143
|
+
}],
|
|
14144
|
+
["prospects queue-draft", {
|
|
14145
|
+
usage: "audienti prospects queue-draft <prsp_id> [--context <json_object>] [--angle <n>]",
|
|
14146
|
+
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.",
|
|
14147
|
+
api: "POST /api/v1/accounts/:account_id/prospects/:id/write.json",
|
|
14148
|
+
options: { context: { type: "string" }, angle: { type: "string" } },
|
|
14149
|
+
parse: (values) => ({ ...values, context: parseJsonObjectOption(values.context, "--context") }),
|
|
14150
|
+
run: (client, accountId, [id], values) => client.writeProspectQueueDraft(accountId, id, compactObject({ context: values.context, angle_index: values.angle })),
|
|
14151
|
+
done: ([id]) => `Wrote queue draft for prospect ${id}.`
|
|
14152
|
+
}],
|
|
14153
|
+
["prospects rewrite", {
|
|
14154
|
+
usage: "audienti prospects rewrite <prsp_id> [--subject <text>] [--message <text>] [--angle <n>]",
|
|
14155
|
+
purpose: "Start a fresh queue draft for one prospect in the background.",
|
|
14156
|
+
api: "POST /api/v1/accounts/:account_id/prospects/:id/rewrite.json",
|
|
14157
|
+
options: { subject: { type: "string" }, message: { type: "string" }, angle: { type: "string" } },
|
|
14158
|
+
run: (client, accountId, [id], values) => client.rewriteProspectQueueDraft(accountId, id, compactObject({ subject: values.subject, message: values.message, angle_index: values.angle })),
|
|
14159
|
+
done: ([id]) => `Started queue draft rewrite for prospect ${id}.`
|
|
14160
|
+
}],
|
|
14161
|
+
["prospects engage", {
|
|
14162
|
+
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>]",
|
|
14163
|
+
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).",
|
|
14164
|
+
api: "POST /api/v1/accounts/:account_id/prospects/:id/engage.json",
|
|
14165
|
+
options: {
|
|
14166
|
+
type: { type: "string" },
|
|
14167
|
+
message: { type: "string" },
|
|
14168
|
+
subject: { type: "string" },
|
|
14169
|
+
"message-mode": { type: "string" },
|
|
14170
|
+
"social-cookie": { type: "string" },
|
|
14171
|
+
profile: { type: "string" },
|
|
14172
|
+
"scheduled-at": { type: "string" },
|
|
14173
|
+
post: { type: "string" },
|
|
14174
|
+
comment: { type: "string" },
|
|
14175
|
+
"reply-to": { type: "string" },
|
|
14176
|
+
"request-event": { type: "string" },
|
|
14177
|
+
"request-mode": { type: "string" },
|
|
14178
|
+
"queue-action": { type: "boolean" },
|
|
14179
|
+
principal: { type: "string" }
|
|
14180
|
+
},
|
|
14181
|
+
required: ["type"],
|
|
14182
|
+
run: (client, accountId, [id], values) => client.engageProspect(accountId, id, compactObject({
|
|
14183
|
+
action_type: values.type,
|
|
14184
|
+
message: values.message,
|
|
14185
|
+
subject: values.subject,
|
|
14186
|
+
message_mode: values["message-mode"],
|
|
14187
|
+
engage_point_id: values["social-cookie"],
|
|
14188
|
+
profile_id: values.profile,
|
|
14189
|
+
scheduled_at: values["scheduled-at"],
|
|
14190
|
+
mention_id: values.post,
|
|
14191
|
+
comment_id: values.comment,
|
|
14192
|
+
message_id: values["reply-to"],
|
|
14193
|
+
request_event_id: values["request-event"],
|
|
14194
|
+
request_mode: values["request-mode"],
|
|
14195
|
+
queue_action: values["queue-action"] ? true : undefined,
|
|
14196
|
+
principal_account_user_id: values.principal
|
|
14197
|
+
})),
|
|
14198
|
+
done: (_positionals, payload) => display(payload?.message)
|
|
14199
|
+
}],
|
|
14200
|
+
["prospects reject-selected", {
|
|
14201
|
+
usage: "audienti prospects reject-selected <prsp_id> [<prsp_id> ...]",
|
|
14202
|
+
minPositionals: 1,
|
|
14203
|
+
maxPositionals: Infinity,
|
|
14204
|
+
purpose: "Reject several prospects at once. Locked or unavailable prospects are reported as failed.",
|
|
14205
|
+
api: "POST /api/v1/accounts/:account_id/prospects/reject_selected.json",
|
|
14206
|
+
run: (client, accountId, ids) => client.rejectSelectedProspects(accountId, { prospect_ids: ids }),
|
|
14207
|
+
done: (_positionals, payload) => `Rejected ${payload?.rejected?.length ?? 0}; failed ${payload?.failed?.length ?? 0}.`
|
|
14208
|
+
}],
|
|
14209
|
+
["prospects intake", {
|
|
14210
|
+
usage: "audienti prospects intake --url <linkedin_url> [--list <list_id>] [--assign <account_user_id>] [--signal <custom_signal_id>]",
|
|
14211
|
+
minPositionals: 0,
|
|
14212
|
+
maxPositionals: 0,
|
|
14213
|
+
purpose: "Add one LinkedIn person by URL, like Add prospect on the web.",
|
|
14214
|
+
api: "POST /api/v1/accounts/:account_id/prospects/intake.json",
|
|
14215
|
+
options: { url: { type: "string" }, list: { type: "string" }, assign: { type: "string" }, signal: { type: "string" } },
|
|
14216
|
+
required: ["url"],
|
|
14217
|
+
run: (client, accountId, _positionals, values) => client.intakeProspect(accountId, compactObject({
|
|
14218
|
+
url: values.url,
|
|
14219
|
+
list_id: values.list,
|
|
14220
|
+
assigned_to_account_user_id: values.assign,
|
|
14221
|
+
custom_signal_id: values.signal
|
|
14222
|
+
})),
|
|
14223
|
+
done: () => "Prospect intake queued."
|
|
14224
|
+
}],
|
|
14225
|
+
["events retry", {
|
|
14226
|
+
usage: "audienti events retry <event_id>",
|
|
14227
|
+
purpose: "Retry one failed event, with the same eligibility rules as the web retry button.",
|
|
14228
|
+
api: "POST /api/v1/accounts/:account_id/events/:id/retry.json",
|
|
14229
|
+
run: (client, accountId, [id]) => client.retryEvent(accountId, id),
|
|
14230
|
+
done: ([id], payload) => `Retried event ${id} as ${display(payload?.event?.id)}.`
|
|
14231
|
+
}],
|
|
14232
|
+
["profiles delete", {
|
|
14233
|
+
usage: "audienti profiles delete <profile_id>",
|
|
14234
|
+
purpose: "Remove one profile from its prospect. Backend admins only, like the web action.",
|
|
14235
|
+
api: "DELETE /api/v1/accounts/:account_id/profiles/:id.json",
|
|
14236
|
+
run: (client, accountId, [id]) => client.deleteProfile(accountId, id),
|
|
14237
|
+
done: ([id]) => `Removed profile ${id}.`
|
|
14238
|
+
}],
|
|
14239
|
+
["companies stop-pursuing", {
|
|
14240
|
+
usage: "audienti companies stop-pursuing <company_profile_id>",
|
|
14241
|
+
purpose: "Stop pursuing one company, like Stop pursuing on the company page.",
|
|
14242
|
+
api: "POST /api/v1/accounts/:account_id/companies/:id/stop_pursuing.json",
|
|
14243
|
+
run: (client, accountId, [id]) => client.stopPursuingCompany(accountId, id),
|
|
14244
|
+
done: ([id]) => `Stopped pursuing company ${id}.`
|
|
14245
|
+
}],
|
|
14246
|
+
["content defer", {
|
|
14247
|
+
usage: "audienti content defer <work_item_id> --for <1_day|1_week|1_month|3_months|6_months>",
|
|
14248
|
+
purpose: "Defer one content work item.",
|
|
14249
|
+
api: "POST /api/v1/accounts/:account_id/content_ops/work_items/:id/defer.json",
|
|
14250
|
+
options: { for: { type: "string" } },
|
|
14251
|
+
required: ["for"],
|
|
14252
|
+
run: (client, accountId, [id], values) => client.contentDefer(accountId, id, { delay_duration: values.for }),
|
|
14253
|
+
done: ([id]) => `Deferred work item ${id}.`
|
|
14254
|
+
}],
|
|
14255
|
+
["inbox-ops update-filters", {
|
|
14256
|
+
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>]...",
|
|
14257
|
+
minPositionals: 0,
|
|
14258
|
+
maxPositionals: 0,
|
|
14259
|
+
purpose: "Save the owner's Inbox Ops email filters. Rule flags replace the saved list for that rule.",
|
|
14260
|
+
api: "PATCH /api/v1/accounts/:account_id/inbox_ops/filters.json",
|
|
14261
|
+
options: {
|
|
14262
|
+
subscriptions: { type: "string" },
|
|
14263
|
+
"automated-no-reply": { type: "string" },
|
|
14264
|
+
"provider-promotions": { type: "string" },
|
|
14265
|
+
"sender-allow": { type: "string", multiple: true },
|
|
14266
|
+
"sender-filter": { type: "string", multiple: true },
|
|
14267
|
+
"domain-allow": { type: "string", multiple: true },
|
|
14268
|
+
"domain-filter": { type: "string", multiple: true }
|
|
14269
|
+
},
|
|
14270
|
+
run: (client, accountId, _positionals, values) => client.updateInboxOpsFilters(accountId, compactObject({
|
|
14271
|
+
subscriptions: values.subscriptions,
|
|
14272
|
+
automated_no_reply: values["automated-no-reply"],
|
|
14273
|
+
provider_promotions: values["provider-promotions"],
|
|
14274
|
+
sender_allow_rules: values["sender-allow"],
|
|
14275
|
+
sender_filter_rules: values["sender-filter"],
|
|
14276
|
+
domain_allow_rules: values["domain-allow"],
|
|
14277
|
+
domain_filter_rules: values["domain-filter"]
|
|
14278
|
+
})),
|
|
14279
|
+
done: () => "Saved Inbox Ops filters."
|
|
14280
|
+
}],
|
|
14281
|
+
["inbox-ops reply", {
|
|
14282
|
+
usage: "audienti inbox-ops reply <row_id> --message <text> [--subject <text>]",
|
|
14283
|
+
purpose: "Queue a reply to one Inbox Ops row. Each run sends a new reply token.",
|
|
14284
|
+
api: "POST /api/v1/accounts/:account_id/inbox_ops/:row_id/reply.json",
|
|
14285
|
+
options: { message: { type: "string" }, subject: { type: "string" } },
|
|
14286
|
+
required: ["message"],
|
|
14287
|
+
run: (client, accountId, [rowId], values) => client.inboxOpsReply(accountId, rowId, compactObject({
|
|
14288
|
+
message: values.message,
|
|
14289
|
+
subject: values.subject,
|
|
14290
|
+
reply_token: randomUUID()
|
|
14291
|
+
})),
|
|
14292
|
+
done: ([rowId], payload) => `Reply ${display(payload?.status)} for ${rowId}.`
|
|
14293
|
+
}],
|
|
14294
|
+
["inbox-ops draft-reply", {
|
|
14295
|
+
usage: "audienti inbox-ops draft-reply <row_id>",
|
|
14296
|
+
purpose: "Draft a reply for one Inbox Ops row without sending it.",
|
|
14297
|
+
api: "POST /api/v1/accounts/:account_id/inbox_ops/:row_id/reply/write.json",
|
|
14298
|
+
run: (client, accountId, [rowId]) => client.inboxOpsWriteReply(accountId, rowId),
|
|
14299
|
+
done: (_positionals, payload) => display(payload?.message ?? payload?.body)
|
|
14300
|
+
}],
|
|
14301
|
+
["inbox-ops adopt", {
|
|
14302
|
+
usage: "audienti inbox-ops adopt <row_id> [--target-account <acct_id>] [--motion <motion_id>]",
|
|
14303
|
+
purpose: "Adopt the prospect behind one Inbox Ops row.",
|
|
14304
|
+
api: "POST /api/v1/accounts/:account_id/inbox_ops/:row_id/adopt.json",
|
|
14305
|
+
options: { "target-account": { type: "string" }, motion: { type: "string" } },
|
|
14306
|
+
run: (client, accountId, [rowId], values) => client.inboxOpsAdopt(accountId, rowId, compactObject({
|
|
14307
|
+
target_account_id: values["target-account"],
|
|
14308
|
+
motion_id: values.motion
|
|
14309
|
+
})),
|
|
14310
|
+
done: ([rowId]) => `Adopted ${rowId}.`
|
|
14311
|
+
}],
|
|
14312
|
+
["network-ops adopt", {
|
|
14313
|
+
usage: "audienti network-ops adopt <row_id> --target-account <acct_id> --motion <motion_id>",
|
|
14314
|
+
purpose: "Accept one inbound connection request and adopt the person into a motion in an account owned by the request's LinkedIn account owner.",
|
|
14315
|
+
api: "POST /api/v1/accounts/:account_id/network_ops/:row_id/adopt.json",
|
|
14316
|
+
options: { "target-account": { type: "string" }, motion: { type: "string" } },
|
|
14317
|
+
required: ["target-account", "motion"],
|
|
14318
|
+
run: (client, accountId, [rowId], values) => client.networkOpsAction(accountId, rowId, "adopt", {
|
|
14319
|
+
target_account_id: values["target-account"],
|
|
14320
|
+
motion_id: values.motion
|
|
14321
|
+
}),
|
|
14322
|
+
done: ([rowId]) => `Adopted ${rowId}.`
|
|
14323
|
+
}],
|
|
14324
|
+
["network-ops ignore", {
|
|
14325
|
+
usage: "audienti network-ops ignore <row_id>",
|
|
14326
|
+
purpose: "Ignore one inbound connection request without accepting or declining it.",
|
|
14327
|
+
api: "POST /api/v1/accounts/:account_id/network_ops/:row_id/ignore.json",
|
|
14328
|
+
run: (client, accountId, [rowId]) => client.networkOpsAction(accountId, rowId, "ignore"),
|
|
14329
|
+
done: ([rowId]) => `Ignored ${rowId}.`
|
|
14330
|
+
}],
|
|
14331
|
+
["reconciliations add-to-motion", {
|
|
14332
|
+
usage: "audienti reconciliations add-to-motion <source_key> --motion <motion_id>",
|
|
14333
|
+
purpose: "Add one reconciliation candidate to a motion.",
|
|
14334
|
+
api: "POST /api/v1/accounts/:account_id/reconciliations/:id/add_to_motion.json",
|
|
14335
|
+
options: { motion: { type: "string" } },
|
|
14336
|
+
required: ["motion"],
|
|
14337
|
+
run: (client, accountId, [key], values) => client.reconciliationAddToMotion(accountId, key, { motion_id: values.motion }),
|
|
14338
|
+
done: ([key]) => `Added ${key} to the motion.`
|
|
14339
|
+
}],
|
|
14340
|
+
["reconciliations ignore", {
|
|
14341
|
+
usage: "audienti reconciliations ignore <source_key>",
|
|
14342
|
+
purpose: "Ignore one reconciliation candidate.",
|
|
14343
|
+
api: "POST /api/v1/accounts/:account_id/reconciliations/:id/ignore.json",
|
|
14344
|
+
run: (client, accountId, [key]) => client.reconciliationIgnore(accountId, key),
|
|
14345
|
+
done: ([key]) => `Ignored ${key}.`
|
|
14346
|
+
}],
|
|
14347
|
+
// Motion list bulk actions, strategy retirement, launch checks and premise (#2301).
|
|
14348
|
+
["motions bulk-add-tag", {
|
|
14349
|
+
usage: "audienti motions bulk-add-tag <motn_id> [<motn_id> ...] --tag <tag>",
|
|
14350
|
+
minPositionals: 1,
|
|
14351
|
+
maxPositionals: Infinity,
|
|
14352
|
+
purpose: "Add one tag to several motions, like Add tag on the motion list. Any motion outside the account fails the whole request.",
|
|
14353
|
+
api: "POST /api/v1/accounts/:account_id/motions/bulk_add_tag.json",
|
|
14354
|
+
options: { tag: { type: "string" } },
|
|
14355
|
+
required: ["tag"],
|
|
14356
|
+
run: (client, accountId, ids, values) => client.bulkAddMotionTag(accountId, { motion_ids: ids, tag: values.tag }),
|
|
14357
|
+
done: (_positionals, payload) => `Tagged ${payload?.length ?? 0} motions.`
|
|
14358
|
+
}],
|
|
14359
|
+
["motions bulk-remove-tag", {
|
|
14360
|
+
usage: "audienti motions bulk-remove-tag <motn_id> [<motn_id> ...] --tag <tag>",
|
|
14361
|
+
minPositionals: 1,
|
|
14362
|
+
maxPositionals: Infinity,
|
|
14363
|
+
purpose: "Remove one tag from several motions, like Remove tag on the motion list.",
|
|
14364
|
+
api: "POST /api/v1/accounts/:account_id/motions/bulk_remove_tag.json",
|
|
14365
|
+
options: { tag: { type: "string" } },
|
|
14366
|
+
required: ["tag"],
|
|
14367
|
+
run: (client, accountId, ids, values) => client.bulkRemoveMotionTag(accountId, { motion_ids: ids, tag: values.tag }),
|
|
14368
|
+
done: (_positionals, payload) => `Removed the tag from ${payload?.length ?? 0} motions.`
|
|
14369
|
+
}],
|
|
14370
|
+
["motions bulk-update-principal", {
|
|
14371
|
+
usage: "audienti motions bulk-update-principal <motn_id> [<motn_id> ...] --principal <account_user_id>",
|
|
14372
|
+
minPositionals: 1,
|
|
14373
|
+
maxPositionals: Infinity,
|
|
14374
|
+
purpose: "Give several motions the same owner, like Change owner on the motion list. The owner must be a user in this account.",
|
|
14375
|
+
api: "POST /api/v1/accounts/:account_id/motions/bulk_update_principal.json",
|
|
14376
|
+
options: { principal: { type: "string" } },
|
|
14377
|
+
required: ["principal"],
|
|
14378
|
+
run: (client, accountId, ids, values) => client.bulkUpdateMotionPrincipal(accountId, { motion_ids: ids, principal_account_user_id: values.principal }),
|
|
14379
|
+
done: (_positionals, payload) => `Assigned ${payload?.length ?? 0} motions.`
|
|
14380
|
+
}],
|
|
14381
|
+
["motions bulk-update-status", {
|
|
14382
|
+
usage: "audienti motions bulk-update-status <motn_id> [<motn_id> ...] --status <preparing|active|closing|paused|archived>",
|
|
14383
|
+
minPositionals: 1,
|
|
14384
|
+
maxPositionals: Infinity,
|
|
14385
|
+
purpose: "Change the status of several motions with the same lifecycle rules as the motion list. Motions the rules block keep their status and are listed with the reason.",
|
|
14386
|
+
api: "POST /api/v1/accounts/:account_id/motions/bulk_update_status.json",
|
|
14387
|
+
options: { status: { type: "string" } },
|
|
14388
|
+
required: ["status"],
|
|
14389
|
+
run: (client, accountId, ids, values) => client.bulkUpdateMotionStatus(accountId, { motion_ids: ids, status: values.status }),
|
|
14390
|
+
done: (_positionals, payload) => [
|
|
14391
|
+
`Set ${payload?.applied_count ?? 0} motions to ${display(payload?.status)}; ${payload?.blocked_count ?? 0} blocked.`,
|
|
14392
|
+
...(payload?.blocked || []).map((row) => ` ${row.motion_id}: ${display(row.reason)}`)
|
|
14393
|
+
].join("\n")
|
|
14394
|
+
}],
|
|
14395
|
+
["motions retire-strategy", {
|
|
14396
|
+
usage: "audienti motions retire-strategy <motn_id> --strategy <strategy_id>",
|
|
14397
|
+
purpose: "Retire one strategy of an inbound motion. Retirement is permanent; the slot refills from the backlog on the next planning pass.",
|
|
14398
|
+
api: "POST /api/v1/accounts/:account_id/motions/:id/strategies/:motion_search_scope_id/retire.json",
|
|
14399
|
+
options: { strategy: { type: "string" } },
|
|
14400
|
+
required: ["strategy"],
|
|
14401
|
+
run: (client, accountId, [id], values) => client.retireMotionStrategy(accountId, id, values.strategy),
|
|
14402
|
+
done: (_positionals, payload) => (payload?.already_retired
|
|
14403
|
+
? `Strategy ${display(payload?.strategy?.id)} is already retired.`
|
|
14404
|
+
: `Retired strategy ${display(payload?.strategy?.id)}.`)
|
|
14405
|
+
}],
|
|
14406
|
+
["motions refresh-launch-check", {
|
|
14407
|
+
usage: "audienti motions refresh-launch-check <motn_id>",
|
|
14408
|
+
purpose: "Recheck whether one motion can launch discovery, without starting discovery.",
|
|
14409
|
+
api: "POST /api/v1/accounts/:account_id/motions/:id/refresh_launch_check.json",
|
|
14410
|
+
run: (client, accountId, [id]) => client.refreshMotionLaunchCheck(accountId, id),
|
|
14411
|
+
done: (_positionals, payload) => `${display(payload?.name)}: demand ${display(payload?.demand_status)}, producer ${display(payload?.producer_status)}, reason ${display(payload?.reason)}.`
|
|
14412
|
+
}],
|
|
14413
|
+
["motions refresh-launch-checks", {
|
|
14414
|
+
usage: "audienti motions refresh-launch-checks",
|
|
14415
|
+
minPositionals: 0,
|
|
14416
|
+
maxPositionals: 0,
|
|
14417
|
+
purpose: "Recheck launch readiness for every motion in the account, without starting discovery.",
|
|
14418
|
+
api: "POST /api/v1/accounts/:account_id/motions/refresh_launch_checks.json",
|
|
14419
|
+
run: (client, accountId) => client.refreshMotionLaunchChecks(accountId),
|
|
14420
|
+
done: (_positionals, payload) => `Launch checks refreshed: ${display(payload?.summary)}.`
|
|
14421
|
+
}],
|
|
14422
|
+
["motions update-premise", {
|
|
14423
|
+
usage: "audienti motions update-premise <motn_id> --premise <text>",
|
|
14424
|
+
purpose: "Save a motion's premise and rebuild its ICP targeting from it, like Update premise in the Operator.",
|
|
14425
|
+
api: "PATCH /api/v1/accounts/:account_id/motions/:id/update_premise.json",
|
|
14426
|
+
options: { premise: { type: "string" } },
|
|
14427
|
+
required: ["premise"],
|
|
14428
|
+
run: (client, accountId, [id], values) => client.updateMotionPremise(accountId, id, { motion: { premise: values.premise } }),
|
|
14429
|
+
done: ([id]) => `Updated the premise for ${id}.`
|
|
14430
|
+
}]
|
|
14431
|
+
]);
|
|
14432
|
+
|
|
14433
|
+
for (const [topic, spec] of PARITY_ACTION_COMMANDS) {
|
|
14434
|
+
if (HELP_TOPICS.has(topic)) throw new Error(`duplicate help topic ${topic}`);
|
|
14435
|
+
HELP_TOPICS.set(topic, [
|
|
14436
|
+
"Usage:",
|
|
14437
|
+
` ${spec.usage} ${ACCOUNT_OPTION_USAGE}`,
|
|
14438
|
+
"",
|
|
14439
|
+
"Status: implemented",
|
|
14440
|
+
"",
|
|
14441
|
+
"Purpose:",
|
|
14442
|
+
` ${spec.purpose}`,
|
|
14443
|
+
"",
|
|
14444
|
+
"API:",
|
|
14445
|
+
` ${spec.api}`
|
|
14446
|
+
].join("\n"));
|
|
14447
|
+
}
|
|
14448
|
+
|
|
14449
|
+
// List the single-purpose actions under their group help (`audienti help prospects`).
|
|
14450
|
+
const parityActionUsagesByGroup = new Map();
|
|
14451
|
+
for (const [topic, spec] of PARITY_ACTION_COMMANDS) {
|
|
14452
|
+
const group = topic.split(" ")[0];
|
|
14453
|
+
if (!parityActionUsagesByGroup.has(group)) parityActionUsagesByGroup.set(group, []);
|
|
14454
|
+
parityActionUsagesByGroup.get(group).push(` ${spec.usage} ${ACCOUNT_OPTION_USAGE}`);
|
|
14455
|
+
}
|
|
14456
|
+
for (const [group, usages] of parityActionUsagesByGroup) {
|
|
14457
|
+
const section = ["Actions:", ...usages].join("\n");
|
|
14458
|
+
const existing = HELP_TOPICS.get(group);
|
|
14459
|
+
HELP_TOPICS.set(group, existing ? `${existing}\n\n${section}` : section);
|
|
14460
|
+
}
|
|
14461
|
+
|
|
14462
|
+
function parseJsonObjectOption(raw, flag) {
|
|
14463
|
+
if (raw === undefined) return undefined;
|
|
14464
|
+
let parsed;
|
|
14465
|
+
try {
|
|
14466
|
+
parsed = JSON.parse(raw);
|
|
14467
|
+
} catch {
|
|
14468
|
+
throw new CommandError(`${flag} must be a JSON object, for example '{"selected_action":{"key":"send_email"}}'.`);
|
|
14469
|
+
}
|
|
14470
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
14471
|
+
throw new CommandError(`${flag} must be a JSON object, for example '{"selected_action":{"key":"send_email"}}'.`);
|
|
14472
|
+
}
|
|
14473
|
+
return parsed;
|
|
14474
|
+
}
|
|
14475
|
+
|
|
14476
|
+
async function runParityActionCommand(topic, args, context, { accountOverride } = {}) {
|
|
14477
|
+
const spec = PARITY_ACTION_COMMANDS.get(topic);
|
|
14478
|
+
const usageLine = `Usage: ${spec.usage} ${ACCOUNT_OPTION_USAGE}`;
|
|
14479
|
+
const { values, positionals } = parseCommandArgs(args, { ...jsonOptions(), ...(spec.options || {}) });
|
|
14480
|
+
const minPositionals = spec.minPositionals ?? 1;
|
|
14481
|
+
const maxPositionals = spec.maxPositionals ?? 1;
|
|
14482
|
+
if (positionals.length < minPositionals || positionals.length > maxPositionals) throw new CommandError(usageLine);
|
|
14483
|
+
if ((spec.required || []).some((key) => !values[key])) throw new CommandError(usageLine);
|
|
14484
|
+
|
|
14485
|
+
const parsedValues = spec.parse ? spec.parse(values, usageLine) : values;
|
|
14486
|
+
|
|
14487
|
+
const { client, accountId } = await requireAccountContext(context, { accountOverride });
|
|
14488
|
+
const payload = await spec.run(client, accountId, positionals, parsedValues);
|
|
14489
|
+
if (values.json) return writeJson(context.stdout, payload);
|
|
14490
|
+
|
|
14491
|
+
writeLine(context.stdout, spec.done(positionals, payload));
|
|
14492
|
+
}
|