@audienti/cli 0.1.79 → 0.1.81
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 +13 -0
- package/package.json +1 -1
- package/src/api-client.js +101 -0
- package/src/cli.js +412 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,19 @@ All notable changes to the Audienti CLI are documented here.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.1.81] - 2026-09-28
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Manage social accounts from the CLI: `social-cookies show`, `pause`, `resume`, `resume-autopilot`, `recheck-account-type`, `reconnect`, `delete`, `submit-otp`, `create`, `update`, `rights` (`--workspace --grant|--revoke` or `--workspaces`), and `settings --scope`.
|
|
12
|
+
- Secrets never go on the command line. Passwords, one-time codes, cookies, TOTP secrets, and messaging PINs come from stdin (`--password-stdin`, `--secrets-stdin`, `--otp-stdin`), an environment variable (`--password-env` reads `AUDIENTI_SOCIAL_PASSWORD`, `--otp-env` reads `AUDIENTI_OTP_CODE`), or a hidden terminal prompt. Flags such as `--password` or `--otp` are refused before any request is sent, and the CLI never prints a secret.
|
|
13
|
+
|
|
14
|
+
## [0.1.80] - 2026-09-28
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- 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`.
|
|
19
|
+
|
|
7
20
|
## [0.1.79] - 2026-09-28
|
|
8
21
|
|
|
9
22
|
### Added
|
package/package.json
CHANGED
package/src/api-client.js
CHANGED
|
@@ -74,6 +74,54 @@ export class AudientiClient {
|
|
|
74
74
|
return this.requestJson(accountPath(accountId, ["social_cookies"], query));
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
socialCookie(accountId, socialCookieId) {
|
|
78
|
+
return this.requestJson(accountPath(accountId, ["social_cookies", socialCookieId]));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
createSocialCookie(accountId, body) {
|
|
82
|
+
return this.requestJson(accountPath(accountId, ["social_cookies"]), { method: "POST", body });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
updateSocialCookie(accountId, socialCookieId, body) {
|
|
86
|
+
return this.requestJson(accountPath(accountId, ["social_cookies", socialCookieId]), { method: "PATCH", body });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
deleteSocialCookie(accountId, socialCookieId) {
|
|
90
|
+
return this.requestJson(accountPath(accountId, ["social_cookies", socialCookieId]), { method: "DELETE" });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
pauseSocialCookie(accountId, socialCookieId) {
|
|
94
|
+
return this.requestJson(accountPath(accountId, ["social_cookies", socialCookieId, "pause"]), { method: "POST" });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
resumeSocialCookie(accountId, socialCookieId) {
|
|
98
|
+
return this.requestJson(accountPath(accountId, ["social_cookies", socialCookieId, "resume"]), { method: "POST" });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
resumeSocialCookieAutopilot(accountId, socialCookieId) {
|
|
102
|
+
return this.requestJson(accountPath(accountId, ["social_cookies", socialCookieId, "resume_autopilot"]), { method: "POST" });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
recheckSocialCookieAccountType(accountId, socialCookieId) {
|
|
106
|
+
return this.requestJson(accountPath(accountId, ["social_cookies", socialCookieId, "recheck_account_type"]), { method: "POST" });
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
reconnectSocialCookie(accountId, socialCookieId) {
|
|
110
|
+
return this.requestJson(accountPath(accountId, ["social_cookies", socialCookieId, "reconnect"]), { method: "POST" });
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
submitSocialCookieOtp(accountId, socialCookieId, body) {
|
|
114
|
+
return this.requestJson(accountPath(accountId, ["social_cookies", socialCookieId, "submit_otp"]), { method: "POST", body });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
updateSocialCookieRights(accountId, socialCookieId, body) {
|
|
118
|
+
return this.requestJson(accountPath(accountId, ["social_cookies", socialCookieId, "rights"]), { method: "PATCH", body });
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
updateSocialCookieSettings(accountId, socialCookieId, body) {
|
|
122
|
+
return this.requestJson(accountPath(accountId, ["social_cookies", socialCookieId, "settings"]), { method: "PATCH", body });
|
|
123
|
+
}
|
|
124
|
+
|
|
77
125
|
syncSocialCookieMessages(accountId, socialCookieId, body = {}) {
|
|
78
126
|
return this.requestJson(accountPath(accountId, ["social_cookies", socialCookieId, "sync_messages"]), {
|
|
79
127
|
method: "POST",
|
|
@@ -675,6 +723,59 @@ export class AudientiClient {
|
|
|
675
723
|
});
|
|
676
724
|
}
|
|
677
725
|
|
|
726
|
+
bulkAddMotionTag(accountId, body) {
|
|
727
|
+
return this.requestJson(accountPath(accountId, ["motions", "bulk_add_tag"]), {
|
|
728
|
+
method: "POST",
|
|
729
|
+
body
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
bulkRemoveMotionTag(accountId, body) {
|
|
734
|
+
return this.requestJson(accountPath(accountId, ["motions", "bulk_remove_tag"]), {
|
|
735
|
+
method: "POST",
|
|
736
|
+
body
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
bulkUpdateMotionPrincipal(accountId, body) {
|
|
741
|
+
return this.requestJson(accountPath(accountId, ["motions", "bulk_update_principal"]), {
|
|
742
|
+
method: "POST",
|
|
743
|
+
body
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
bulkUpdateMotionStatus(accountId, body) {
|
|
748
|
+
return this.requestJson(accountPath(accountId, ["motions", "bulk_update_status"]), {
|
|
749
|
+
method: "POST",
|
|
750
|
+
body
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
retireMotionStrategy(accountId, motionId, strategyId) {
|
|
755
|
+
return this.requestJson(accountPath(accountId, ["motions", motionId, "strategies", strategyId, "retire"]), {
|
|
756
|
+
method: "POST"
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
refreshMotionLaunchCheck(accountId, motionId) {
|
|
761
|
+
return this.requestJson(accountPath(accountId, ["motions", motionId, "refresh_launch_check"]), {
|
|
762
|
+
method: "POST"
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
refreshMotionLaunchChecks(accountId) {
|
|
767
|
+
return this.requestJson(accountPath(accountId, ["motions", "refresh_launch_checks"]), {
|
|
768
|
+
method: "POST"
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
updateMotionPremise(accountId, motionId, body) {
|
|
773
|
+
return this.requestJson(accountPath(accountId, ["motions", motionId, "update_premise"]), {
|
|
774
|
+
method: "PATCH",
|
|
775
|
+
body
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
|
|
678
779
|
createQuickStart(accountId, body) {
|
|
679
780
|
return this.requestJson(accountPath(accountId, ["quick_start"]), {
|
|
680
781
|
method: "POST",
|
package/src/cli.js
CHANGED
|
@@ -3337,10 +3337,21 @@ async function motionsUpdate(args, context, { accountOverride } = {}) {
|
|
|
3337
3337
|
|
|
3338
3338
|
const { client, accountId } = await requireAccountContext(context, { accountOverride });
|
|
3339
3339
|
const motion = await client.updateMotion(accountId, positionals[0], { motion: await motionUpdatePayload(values) });
|
|
3340
|
-
|
|
3340
|
+
const statusBlocked = motion?.status_change?.status === "blocked";
|
|
3341
|
+
if (values.json) {
|
|
3342
|
+
writeJson(context.stdout, motion);
|
|
3343
|
+
return statusBlocked ? 1 : undefined;
|
|
3344
|
+
}
|
|
3341
3345
|
|
|
3342
3346
|
writeLine(context.stdout, `Updated motion ${display(motion?.name)} (${display(motion?.prefix_id)}).`);
|
|
3343
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;
|
|
3344
3355
|
}
|
|
3345
3356
|
|
|
3346
3357
|
async function motionUpdatePayload(values) {
|
|
@@ -14059,6 +14070,195 @@ const HELP_TOPICS = new Map([
|
|
|
14059
14070
|
].join("\n")]
|
|
14060
14071
|
]);
|
|
14061
14072
|
|
|
14073
|
+
// Social account credentials (#2306). Secrets never travel as argv flags,
|
|
14074
|
+
// because argv lands in shell history and the process list. They come from
|
|
14075
|
+
// stdin, one named environment variable, or a no-echo terminal prompt, and the
|
|
14076
|
+
// CLI never prints them.
|
|
14077
|
+
const SOCIAL_SECRET_KEYS = ["password", "otp_secret", "cookie_bundle", "messaging_pin", "otp_code"];
|
|
14078
|
+
const SOCIAL_SECRET_FLAGS = new Set(["password", "pass", "otp", "otp-code", "code", "otp-secret", "totp", "totp-secret", "cookie", "cookies", "cookie-bundle", "messaging-pin", "pin", "secret"]);
|
|
14079
|
+
const SOCIAL_PASSWORD_ENV = "AUDIENTI_SOCIAL_PASSWORD";
|
|
14080
|
+
const SOCIAL_OTP_ENV = "AUDIENTI_OTP_CODE";
|
|
14081
|
+
const SOCIAL_COOKIE_ATTRIBUTE_KEYS = new Set([
|
|
14082
|
+
"service_identifier", "username", "name", "email", "phone_number",
|
|
14083
|
+
"geo_location", "country_code", "postal_code", "state_code", "city",
|
|
14084
|
+
"imap_server", "imap_port", "smtp_server", "smtp_port", "use_ssl",
|
|
14085
|
+
"automation_controls", "rate_limit_overrides", "operator_controls"
|
|
14086
|
+
]);
|
|
14087
|
+
const SOCIAL_COOKIE_FIELD_OPTIONS = {
|
|
14088
|
+
name: { type: "string" },
|
|
14089
|
+
email: { type: "string" },
|
|
14090
|
+
attributes: { type: "string" },
|
|
14091
|
+
"password-stdin": { type: "boolean" },
|
|
14092
|
+
"secrets-stdin": { type: "boolean" },
|
|
14093
|
+
"password-env": { type: "boolean" },
|
|
14094
|
+
"prompt-password": { type: "boolean" }
|
|
14095
|
+
};
|
|
14096
|
+
|
|
14097
|
+
function rejectSecretArgs(args) {
|
|
14098
|
+
for (const arg of args) {
|
|
14099
|
+
if (!String(arg).startsWith("--")) continue;
|
|
14100
|
+
const flag = String(arg).slice(2).split("=")[0];
|
|
14101
|
+
if (SOCIAL_SECRET_FLAGS.has(flag)) {
|
|
14102
|
+
throw new CommandError(`--${flag} is not accepted because command-line secrets leak into shell history. Use --password-stdin, --secrets-stdin, --otp-stdin, the ${SOCIAL_PASSWORD_ENV} or ${SOCIAL_OTP_ENV} variable, or the hidden prompt.`);
|
|
14103
|
+
}
|
|
14104
|
+
}
|
|
14105
|
+
}
|
|
14106
|
+
|
|
14107
|
+
function rejectSecretKeys(object, flag) {
|
|
14108
|
+
const walk = (value) => {
|
|
14109
|
+
if (!value || typeof value !== "object") return;
|
|
14110
|
+
for (const [key, nested] of Object.entries(value)) {
|
|
14111
|
+
if (SOCIAL_SECRET_KEYS.includes(key)) throw new CommandError(`${flag} must not contain ${key}. Send secrets through stdin, an environment variable, or the hidden prompt.`);
|
|
14112
|
+
walk(nested);
|
|
14113
|
+
}
|
|
14114
|
+
};
|
|
14115
|
+
walk(object);
|
|
14116
|
+
}
|
|
14117
|
+
|
|
14118
|
+
function trimSecret(text) {
|
|
14119
|
+
return String(text ?? "").replace(/\r?\n$/, "");
|
|
14120
|
+
}
|
|
14121
|
+
|
|
14122
|
+
// Reads one line from the terminal without echoing it.
|
|
14123
|
+
async function promptHidden(context, question) {
|
|
14124
|
+
const input = context.stdin;
|
|
14125
|
+
if (!input?.isTTY || typeof input.setRawMode !== "function") {
|
|
14126
|
+
throw new CommandError("No terminal is available for a hidden prompt. Use stdin or an environment variable instead.");
|
|
14127
|
+
}
|
|
14128
|
+
const output = context.stderr || context.stdout;
|
|
14129
|
+
output.write(question);
|
|
14130
|
+
input.setRawMode(true);
|
|
14131
|
+
input.resume();
|
|
14132
|
+
|
|
14133
|
+
return new Promise((resolve, reject) => {
|
|
14134
|
+
let value = "";
|
|
14135
|
+
const finish = (error) => {
|
|
14136
|
+
input.removeListener("data", onData);
|
|
14137
|
+
input.setRawMode(false);
|
|
14138
|
+
input.pause();
|
|
14139
|
+
output.write("\n");
|
|
14140
|
+
if (error) reject(error);
|
|
14141
|
+
else resolve(value);
|
|
14142
|
+
};
|
|
14143
|
+
const onData = (chunk) => {
|
|
14144
|
+
for (const char of String(chunk)) {
|
|
14145
|
+
if (char === "\r" || char === "\n" || char === "\u0004") return finish();
|
|
14146
|
+
if (char === "\u0003") return finish(new CommandError("Cancelled."));
|
|
14147
|
+
if (char === "\u007f" || char === "\b") value = value.slice(0, -1);
|
|
14148
|
+
else value += char;
|
|
14149
|
+
}
|
|
14150
|
+
};
|
|
14151
|
+
input.on("data", onData);
|
|
14152
|
+
});
|
|
14153
|
+
}
|
|
14154
|
+
|
|
14155
|
+
async function readSocialOtp(values, usageLine, context) {
|
|
14156
|
+
if (values["otp-stdin"] && values["otp-env"]) throw new CommandError(usageLine);
|
|
14157
|
+
let code;
|
|
14158
|
+
if (values["otp-stdin"]) code = await readStdinText(context);
|
|
14159
|
+
else if (values["otp-env"]) code = context.env?.[SOCIAL_OTP_ENV];
|
|
14160
|
+
else if (stdinIsInteractive(context)) code = await promptHidden(context, "One-time code (hidden): ");
|
|
14161
|
+
else throw new CommandError(`Provide the code with --otp-stdin, --otp-env (${SOCIAL_OTP_ENV}), or run in a terminal for a hidden prompt.`);
|
|
14162
|
+
|
|
14163
|
+
const trimmed = String(code ?? "").trim();
|
|
14164
|
+
if (!trimmed) throw new CommandError("The one-time code is empty.");
|
|
14165
|
+
return trimmed;
|
|
14166
|
+
}
|
|
14167
|
+
|
|
14168
|
+
// Returns the secret fields to send, or {} when none were requested.
|
|
14169
|
+
async function readSocialSecrets(values, usageLine, context, { allowBundle = true } = {}) {
|
|
14170
|
+
const sources = ["password-stdin", "secrets-stdin", "password-env", "prompt-password"].filter((key) => values[key]);
|
|
14171
|
+
if (sources.length > 1) throw new CommandError(`Use only one of --${sources.join(", --")}.`);
|
|
14172
|
+
const [source] = sources;
|
|
14173
|
+
if (!source) return {};
|
|
14174
|
+
|
|
14175
|
+
if (source === "password-stdin") return { password: trimSecret(await readStdinText(context)) };
|
|
14176
|
+
if (source === "password-env") {
|
|
14177
|
+
const password = context.env?.[SOCIAL_PASSWORD_ENV];
|
|
14178
|
+
if (!password) throw new CommandError(`${SOCIAL_PASSWORD_ENV} is not set.`);
|
|
14179
|
+
return { password };
|
|
14180
|
+
}
|
|
14181
|
+
if (source === "prompt-password") return { password: await promptHidden(context, "Password (hidden): ") };
|
|
14182
|
+
|
|
14183
|
+
if (!allowBundle) throw new CommandError(usageLine);
|
|
14184
|
+
let parsed;
|
|
14185
|
+
try {
|
|
14186
|
+
parsed = JSON.parse(await readStdinText(context));
|
|
14187
|
+
} catch {
|
|
14188
|
+
throw new CommandError("--secrets-stdin expects a JSON object with password, otp_secret, cookie_bundle, or messaging_pin.");
|
|
14189
|
+
}
|
|
14190
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
14191
|
+
throw new CommandError("--secrets-stdin expects a JSON object with password, otp_secret, cookie_bundle, or messaging_pin.");
|
|
14192
|
+
}
|
|
14193
|
+
const allowed = ["password", "otp_secret", "cookie_bundle", "messaging_pin"];
|
|
14194
|
+
const unknown = Object.keys(parsed).filter((key) => !allowed.includes(key));
|
|
14195
|
+
if (unknown.length > 0) throw new CommandError(`--secrets-stdin accepts only ${allowed.join(", ")}.`);
|
|
14196
|
+
return parsed;
|
|
14197
|
+
}
|
|
14198
|
+
|
|
14199
|
+
async function socialCookieBody(values, usageLine, context, fields) {
|
|
14200
|
+
const attributes = parseJsonObjectOption(values.attributes, "--attributes") || {};
|
|
14201
|
+
const unknown = Object.keys(attributes).filter((key) => !SOCIAL_COOKIE_ATTRIBUTE_KEYS.has(key));
|
|
14202
|
+
if (unknown.length > 0) {
|
|
14203
|
+
throw new CommandError(`--attributes accepts only ${[...SOCIAL_COOKIE_ATTRIBUTE_KEYS].join(", ")}. Secrets go through stdin, an environment variable, or the hidden prompt.`);
|
|
14204
|
+
}
|
|
14205
|
+
rejectSecretKeys(attributes, "--attributes");
|
|
14206
|
+
|
|
14207
|
+
return {
|
|
14208
|
+
...attributes,
|
|
14209
|
+
...compactObject({ ...fields, name: values.name, email: values.email }),
|
|
14210
|
+
...(await readSocialSecrets(values, usageLine, context))
|
|
14211
|
+
};
|
|
14212
|
+
}
|
|
14213
|
+
|
|
14214
|
+
function socialCookieRightsBody(values, usageLine) {
|
|
14215
|
+
if (values.workspaces !== undefined) {
|
|
14216
|
+
if (values.workspace || values.grant || values.revoke) throw new CommandError(usageLine);
|
|
14217
|
+
return { access_account_ids: values.workspaces.split(",").map((id) => id.trim()).filter(Boolean) };
|
|
14218
|
+
}
|
|
14219
|
+
if (!values.workspace || values.grant === values.revoke) throw new CommandError(usageLine);
|
|
14220
|
+
return { access_account_id: values.workspace, granted: Boolean(values.grant) };
|
|
14221
|
+
}
|
|
14222
|
+
|
|
14223
|
+
async function socialCookieSettingsBody(values, usageLine, context) {
|
|
14224
|
+
const body = parseJsonObjectOption(values.body, "--body") || {};
|
|
14225
|
+
rejectSecretKeys(body, "--body");
|
|
14226
|
+
const merged = {
|
|
14227
|
+
...body,
|
|
14228
|
+
...compactObject({
|
|
14229
|
+
scope: values.scope,
|
|
14230
|
+
enabled: values.enabled,
|
|
14231
|
+
key: values.key,
|
|
14232
|
+
target: values.target,
|
|
14233
|
+
daily: values.daily,
|
|
14234
|
+
hourly: values.hourly,
|
|
14235
|
+
action_type: values["action-type"],
|
|
14236
|
+
mode: values.mode,
|
|
14237
|
+
level: values.level
|
|
14238
|
+
})
|
|
14239
|
+
};
|
|
14240
|
+
|
|
14241
|
+
const secrets = await readSocialSecrets(values, usageLine, context, { allowBundle: false });
|
|
14242
|
+
if (Object.keys(secrets).length > 0) {
|
|
14243
|
+
if (!["details", "servers"].includes(values.scope)) throw new CommandError("A password is accepted only with --scope details or --scope servers.");
|
|
14244
|
+
merged.social_cookie = { ...(merged.social_cookie || {}), ...secrets };
|
|
14245
|
+
}
|
|
14246
|
+
return merged;
|
|
14247
|
+
}
|
|
14248
|
+
|
|
14249
|
+
function describeSocialCookie(cookie) {
|
|
14250
|
+
if (!cookie) return "Social account not found.";
|
|
14251
|
+
const lines = [
|
|
14252
|
+
`${display(cookie.name || cookie.username)} (${display(cookie.prefix_id)})`,
|
|
14253
|
+
`Platform: ${display(cookie.platform)} | Status: ${display(cookie.status)}${cookie.autopilot_paused ? " | Autopilot paused" : ""}`,
|
|
14254
|
+
`Connection: ${display(cookie.connection?.title)}`
|
|
14255
|
+
];
|
|
14256
|
+
if (cookie.connection?.body) lines.push(` ${cookie.connection.body}`);
|
|
14257
|
+
if (cookie.otp_pending) lines.push("Waiting for a one-time code: run `audienti social-cookies submit-otp`.");
|
|
14258
|
+
for (const fact of cookie.account_facts || []) lines.push(`${display(fact?.label)}: ${display(fact?.value)}`);
|
|
14259
|
+
return lines.join("\n");
|
|
14260
|
+
}
|
|
14261
|
+
|
|
14062
14262
|
// Single-purpose actions that mirror one web button each (#2300, #2302).
|
|
14063
14263
|
// Every entry calls one API route that shares its service with the web action.
|
|
14064
14264
|
const ACCOUNT_OPTION_USAGE = "[--json] [--account <acct_id>]";
|
|
@@ -14332,6 +14532,215 @@ const PARITY_ACTION_COMMANDS = new Map([
|
|
|
14332
14532
|
api: "POST /api/v1/accounts/:account_id/reconciliations/:id/ignore.json",
|
|
14333
14533
|
run: (client, accountId, [key]) => client.reconciliationIgnore(accountId, key),
|
|
14334
14534
|
done: ([key]) => `Ignored ${key}.`
|
|
14535
|
+
}],
|
|
14536
|
+
// Motion list bulk actions, strategy retirement, launch checks and premise (#2301).
|
|
14537
|
+
["motions bulk-add-tag", {
|
|
14538
|
+
usage: "audienti motions bulk-add-tag <motn_id> [<motn_id> ...] --tag <tag>",
|
|
14539
|
+
minPositionals: 1,
|
|
14540
|
+
maxPositionals: Infinity,
|
|
14541
|
+
purpose: "Add one tag to several motions, like Add tag on the motion list. Any motion outside the account fails the whole request.",
|
|
14542
|
+
api: "POST /api/v1/accounts/:account_id/motions/bulk_add_tag.json",
|
|
14543
|
+
options: { tag: { type: "string" } },
|
|
14544
|
+
required: ["tag"],
|
|
14545
|
+
run: (client, accountId, ids, values) => client.bulkAddMotionTag(accountId, { motion_ids: ids, tag: values.tag }),
|
|
14546
|
+
done: (_positionals, payload) => `Tagged ${payload?.length ?? 0} motions.`
|
|
14547
|
+
}],
|
|
14548
|
+
["motions bulk-remove-tag", {
|
|
14549
|
+
usage: "audienti motions bulk-remove-tag <motn_id> [<motn_id> ...] --tag <tag>",
|
|
14550
|
+
minPositionals: 1,
|
|
14551
|
+
maxPositionals: Infinity,
|
|
14552
|
+
purpose: "Remove one tag from several motions, like Remove tag on the motion list.",
|
|
14553
|
+
api: "POST /api/v1/accounts/:account_id/motions/bulk_remove_tag.json",
|
|
14554
|
+
options: { tag: { type: "string" } },
|
|
14555
|
+
required: ["tag"],
|
|
14556
|
+
run: (client, accountId, ids, values) => client.bulkRemoveMotionTag(accountId, { motion_ids: ids, tag: values.tag }),
|
|
14557
|
+
done: (_positionals, payload) => `Removed the tag from ${payload?.length ?? 0} motions.`
|
|
14558
|
+
}],
|
|
14559
|
+
["motions bulk-update-principal", {
|
|
14560
|
+
usage: "audienti motions bulk-update-principal <motn_id> [<motn_id> ...] --principal <account_user_id>",
|
|
14561
|
+
minPositionals: 1,
|
|
14562
|
+
maxPositionals: Infinity,
|
|
14563
|
+
purpose: "Give several motions the same owner, like Change owner on the motion list. The owner must be a user in this account.",
|
|
14564
|
+
api: "POST /api/v1/accounts/:account_id/motions/bulk_update_principal.json",
|
|
14565
|
+
options: { principal: { type: "string" } },
|
|
14566
|
+
required: ["principal"],
|
|
14567
|
+
run: (client, accountId, ids, values) => client.bulkUpdateMotionPrincipal(accountId, { motion_ids: ids, principal_account_user_id: values.principal }),
|
|
14568
|
+
done: (_positionals, payload) => `Assigned ${payload?.length ?? 0} motions.`
|
|
14569
|
+
}],
|
|
14570
|
+
["motions bulk-update-status", {
|
|
14571
|
+
usage: "audienti motions bulk-update-status <motn_id> [<motn_id> ...] --status <preparing|active|closing|paused|archived>",
|
|
14572
|
+
minPositionals: 1,
|
|
14573
|
+
maxPositionals: Infinity,
|
|
14574
|
+
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.",
|
|
14575
|
+
api: "POST /api/v1/accounts/:account_id/motions/bulk_update_status.json",
|
|
14576
|
+
options: { status: { type: "string" } },
|
|
14577
|
+
required: ["status"],
|
|
14578
|
+
run: (client, accountId, ids, values) => client.bulkUpdateMotionStatus(accountId, { motion_ids: ids, status: values.status }),
|
|
14579
|
+
done: (_positionals, payload) => [
|
|
14580
|
+
`Set ${payload?.applied_count ?? 0} motions to ${display(payload?.status)}; ${payload?.blocked_count ?? 0} blocked.`,
|
|
14581
|
+
...(payload?.blocked || []).map((row) => ` ${row.motion_id}: ${display(row.reason)}`)
|
|
14582
|
+
].join("\n")
|
|
14583
|
+
}],
|
|
14584
|
+
["motions retire-strategy", {
|
|
14585
|
+
usage: "audienti motions retire-strategy <motn_id> --strategy <strategy_id>",
|
|
14586
|
+
purpose: "Retire one strategy of an inbound motion. Retirement is permanent; the slot refills from the backlog on the next planning pass.",
|
|
14587
|
+
api: "POST /api/v1/accounts/:account_id/motions/:id/strategies/:motion_search_scope_id/retire.json",
|
|
14588
|
+
options: { strategy: { type: "string" } },
|
|
14589
|
+
required: ["strategy"],
|
|
14590
|
+
run: (client, accountId, [id], values) => client.retireMotionStrategy(accountId, id, values.strategy),
|
|
14591
|
+
done: (_positionals, payload) => (payload?.already_retired
|
|
14592
|
+
? `Strategy ${display(payload?.strategy?.id)} is already retired.`
|
|
14593
|
+
: `Retired strategy ${display(payload?.strategy?.id)}.`)
|
|
14594
|
+
}],
|
|
14595
|
+
["motions refresh-launch-check", {
|
|
14596
|
+
usage: "audienti motions refresh-launch-check <motn_id>",
|
|
14597
|
+
purpose: "Recheck whether one motion can launch discovery, without starting discovery.",
|
|
14598
|
+
api: "POST /api/v1/accounts/:account_id/motions/:id/refresh_launch_check.json",
|
|
14599
|
+
run: (client, accountId, [id]) => client.refreshMotionLaunchCheck(accountId, id),
|
|
14600
|
+
done: (_positionals, payload) => `${display(payload?.name)}: demand ${display(payload?.demand_status)}, producer ${display(payload?.producer_status)}, reason ${display(payload?.reason)}.`
|
|
14601
|
+
}],
|
|
14602
|
+
["motions refresh-launch-checks", {
|
|
14603
|
+
usage: "audienti motions refresh-launch-checks",
|
|
14604
|
+
minPositionals: 0,
|
|
14605
|
+
maxPositionals: 0,
|
|
14606
|
+
purpose: "Recheck launch readiness for every motion in the account, without starting discovery.",
|
|
14607
|
+
api: "POST /api/v1/accounts/:account_id/motions/refresh_launch_checks.json",
|
|
14608
|
+
run: (client, accountId) => client.refreshMotionLaunchChecks(accountId),
|
|
14609
|
+
done: (_positionals, payload) => `Launch checks refreshed: ${display(payload?.summary)}.`
|
|
14610
|
+
}],
|
|
14611
|
+
["motions update-premise", {
|
|
14612
|
+
usage: "audienti motions update-premise <motn_id> --premise <text>",
|
|
14613
|
+
purpose: "Save a motion's premise and rebuild its ICP targeting from it, like Update premise in the Operator.",
|
|
14614
|
+
api: "PATCH /api/v1/accounts/:account_id/motions/:id/update_premise.json",
|
|
14615
|
+
options: { premise: { type: "string" } },
|
|
14616
|
+
required: ["premise"],
|
|
14617
|
+
run: (client, accountId, [id], values) => client.updateMotionPremise(accountId, id, { motion: { premise: values.premise } }),
|
|
14618
|
+
done: ([id]) => `Updated the premise for ${id}.`
|
|
14619
|
+
}],
|
|
14620
|
+
// Social account page (#2306). Passwords, OTP codes, cookies, TOTP secrets
|
|
14621
|
+
// and the messaging PIN are never argv flags: they come from stdin, an
|
|
14622
|
+
// environment variable, or a no-echo terminal prompt, and are never printed.
|
|
14623
|
+
["social-cookies show", {
|
|
14624
|
+
usage: "audienti social-cookies show <scok_id>",
|
|
14625
|
+
purpose: "Show one social account's connection state and facts, like its Overview. Never shows credentials.",
|
|
14626
|
+
api: "GET /api/v1/accounts/:account_id/social_cookies/:id.json",
|
|
14627
|
+
run: (client, accountId, [id]) => client.socialCookie(accountId, id),
|
|
14628
|
+
done: (_positionals, payload) => describeSocialCookie(payload?.social_cookie)
|
|
14629
|
+
}],
|
|
14630
|
+
["social-cookies pause", {
|
|
14631
|
+
usage: "audienti social-cookies pause <scok_id>",
|
|
14632
|
+
purpose: "Pause one social account so no activity runs until it is resumed.",
|
|
14633
|
+
api: "POST /api/v1/accounts/:account_id/social_cookies/:id/pause.json",
|
|
14634
|
+
run: (client, accountId, [id]) => client.pauseSocialCookie(accountId, id),
|
|
14635
|
+
done: (_positionals, payload) => display(payload?.message)
|
|
14636
|
+
}],
|
|
14637
|
+
["social-cookies resume", {
|
|
14638
|
+
usage: "audienti social-cookies resume <scok_id>",
|
|
14639
|
+
purpose: "Resume a paused social account; it reconnects before activity restarts.",
|
|
14640
|
+
api: "POST /api/v1/accounts/:account_id/social_cookies/:id/resume.json",
|
|
14641
|
+
run: (client, accountId, [id]) => client.resumeSocialCookie(accountId, id),
|
|
14642
|
+
done: (_positionals, payload) => display(payload?.message)
|
|
14643
|
+
}],
|
|
14644
|
+
["social-cookies resume-autopilot", {
|
|
14645
|
+
usage: "audienti social-cookies resume-autopilot <scok_id>",
|
|
14646
|
+
purpose: "Clear an automatic autopilot pause on one social account.",
|
|
14647
|
+
api: "POST /api/v1/accounts/:account_id/social_cookies/:id/resume_autopilot.json",
|
|
14648
|
+
run: (client, accountId, [id]) => client.resumeSocialCookieAutopilot(accountId, id),
|
|
14649
|
+
done: (_positionals, payload) => display(payload?.message)
|
|
14650
|
+
}],
|
|
14651
|
+
["social-cookies recheck-account-type", {
|
|
14652
|
+
usage: "audienti social-cookies recheck-account-type <scok_id>",
|
|
14653
|
+
purpose: "Queue a LinkedIn account-type check (Premium, Sales Navigator) for one connected account.",
|
|
14654
|
+
api: "POST /api/v1/accounts/:account_id/social_cookies/:id/recheck_account_type.json",
|
|
14655
|
+
run: (client, accountId, [id]) => client.recheckSocialCookieAccountType(accountId, id),
|
|
14656
|
+
done: (_positionals, payload) => display(payload?.message)
|
|
14657
|
+
}],
|
|
14658
|
+
["social-cookies reconnect", {
|
|
14659
|
+
usage: "audienti social-cookies reconnect <scok_id>",
|
|
14660
|
+
purpose: "Ask one social account to reconnect; a mailbox rechecks its connection.",
|
|
14661
|
+
api: "POST /api/v1/accounts/:account_id/social_cookies/:id/reconnect.json",
|
|
14662
|
+
run: (client, accountId, [id]) => client.reconnectSocialCookie(accountId, id),
|
|
14663
|
+
done: (_positionals, payload) => display(payload?.message)
|
|
14664
|
+
}],
|
|
14665
|
+
["social-cookies delete", {
|
|
14666
|
+
usage: "audienti social-cookies delete <scok_id>",
|
|
14667
|
+
purpose: "Delete one social account you own.",
|
|
14668
|
+
api: "DELETE /api/v1/accounts/:account_id/social_cookies/:id.json",
|
|
14669
|
+
run: (client, accountId, [id]) => client.deleteSocialCookie(accountId, id),
|
|
14670
|
+
done: (_positionals, payload) => display(payload?.message)
|
|
14671
|
+
}],
|
|
14672
|
+
["social-cookies submit-otp", {
|
|
14673
|
+
usage: "audienti social-cookies submit-otp <scok_id> [--otp-stdin | --otp-env]",
|
|
14674
|
+
purpose: "Send the one-time login code a social account is waiting for. Reads the code from stdin (--otp-stdin), AUDIENTI_OTP_CODE (--otp-env), or a hidden prompt.",
|
|
14675
|
+
api: "POST /api/v1/accounts/:account_id/social_cookies/:id/submit_otp.json",
|
|
14676
|
+
secretInput: true,
|
|
14677
|
+
options: { "otp-stdin": { type: "boolean" }, "otp-env": { type: "boolean" } },
|
|
14678
|
+
parse: async (values, usageLine, context) => ({ ...values, otpCode: await readSocialOtp(values, usageLine, context) }),
|
|
14679
|
+
run: (client, accountId, [id], values) => client.submitSocialCookieOtp(accountId, id, { otp_code: values.otpCode }),
|
|
14680
|
+
done: (_positionals, payload) => display(payload?.message)
|
|
14681
|
+
}],
|
|
14682
|
+
["social-cookies create", {
|
|
14683
|
+
usage: "audienti social-cookies create --service <linkedin|gmail|...> --username <name> [--name <label>] [--email <email>] [--attributes <json>] [--password-stdin | --secrets-stdin | --password-env | --prompt-password]",
|
|
14684
|
+
minPositionals: 0,
|
|
14685
|
+
maxPositionals: 0,
|
|
14686
|
+
purpose: "Add a social account and queue its login, like Add account on the web. Secrets come from stdin, AUDIENTI_SOCIAL_PASSWORD, or a hidden prompt; never from flags.",
|
|
14687
|
+
api: "POST /api/v1/accounts/:account_id/social_cookies.json",
|
|
14688
|
+
secretInput: true,
|
|
14689
|
+
options: { service: { type: "string" }, username: { type: "string" }, ...SOCIAL_COOKIE_FIELD_OPTIONS },
|
|
14690
|
+
required: ["service"],
|
|
14691
|
+
parse: async (values, usageLine, context) => ({
|
|
14692
|
+
...values,
|
|
14693
|
+
body: { social_cookie: await socialCookieBody(values, usageLine, context, { service_identifier: values.service, username: values.username }) }
|
|
14694
|
+
}),
|
|
14695
|
+
run: (client, accountId, _positionals, values) => client.createSocialCookie(accountId, values.body),
|
|
14696
|
+
done: (_positionals, payload) => `Added social account ${display(payload?.social_cookie?.prefix_id)}; login is queued.`
|
|
14697
|
+
}],
|
|
14698
|
+
["social-cookies update", {
|
|
14699
|
+
usage: "audienti social-cookies update <scok_id> [--username <name>] [--name <label>] [--email <email>] [--attributes <json>] [--password-stdin | --secrets-stdin | --password-env | --prompt-password]",
|
|
14700
|
+
purpose: "Edit one social account's details, controls and limits, like its edit page. A stored password is kept unless a new one is sent.",
|
|
14701
|
+
api: "PATCH /api/v1/accounts/:account_id/social_cookies/:id.json",
|
|
14702
|
+
secretInput: true,
|
|
14703
|
+
options: { username: { type: "string" }, ...SOCIAL_COOKIE_FIELD_OPTIONS },
|
|
14704
|
+
parse: async (values, usageLine, context) => ({
|
|
14705
|
+
...values,
|
|
14706
|
+
body: { social_cookie: await socialCookieBody(values, usageLine, context, { username: values.username }) }
|
|
14707
|
+
}),
|
|
14708
|
+
run: (client, accountId, [id], values) => client.updateSocialCookie(accountId, id, values.body),
|
|
14709
|
+
done: ([id]) => `Updated social account ${id}.`
|
|
14710
|
+
}],
|
|
14711
|
+
["social-cookies rights", {
|
|
14712
|
+
usage: "audienti social-cookies rights <scok_id> (--workspace <acct_id> (--grant | --revoke) | --workspaces <acct_id,acct_id>)",
|
|
14713
|
+
purpose: "Choose which of your workspaces may use one social account, like its Access tab.",
|
|
14714
|
+
api: "PATCH /api/v1/accounts/:account_id/social_cookies/:id/rights.json",
|
|
14715
|
+
options: { workspace: { type: "string" }, workspaces: { type: "string" }, grant: { type: "boolean" }, revoke: { type: "boolean" } },
|
|
14716
|
+
parse: (values, usageLine) => ({ ...values, body: socialCookieRightsBody(values, usageLine) }),
|
|
14717
|
+
run: (client, accountId, [id], values) => client.updateSocialCookieRights(accountId, id, values.body),
|
|
14718
|
+
done: (_positionals, payload) => `Workspaces: ${(payload?.accounts || []).map((account) => `${display(account?.name)} (${display(account?.prefix_id)})`).join(", ") || "none"}.`
|
|
14719
|
+
}],
|
|
14720
|
+
["social-cookies settings", {
|
|
14721
|
+
usage: "audienti social-cookies settings <scok_id> --scope <scope> [--enabled <true|false>] [--key <key>] [--target <n>] [--daily <n>] [--hourly <n>] [--action-type <type>] [--mode <mode>] [--level <level>] [--body <json>] [--password-stdin | --password-env | --prompt-password]",
|
|
14722
|
+
purpose: "Change one Overview setting (scopes: sending, gate, action, visibility_mode, operator, limits, limits_preset, hours, advanced, automation, details, location, servers). Only details and servers accept a password, from stdin, AUDIENTI_SOCIAL_PASSWORD, or a hidden prompt.",
|
|
14723
|
+
api: "PATCH /api/v1/accounts/:account_id/social_cookies/:id/settings.json",
|
|
14724
|
+
secretInput: true,
|
|
14725
|
+
options: {
|
|
14726
|
+
scope: { type: "string" },
|
|
14727
|
+
enabled: { type: "string" },
|
|
14728
|
+
key: { type: "string" },
|
|
14729
|
+
target: { type: "string" },
|
|
14730
|
+
daily: { type: "string" },
|
|
14731
|
+
hourly: { type: "string" },
|
|
14732
|
+
"action-type": { type: "string" },
|
|
14733
|
+
mode: { type: "string" },
|
|
14734
|
+
level: { type: "string" },
|
|
14735
|
+
body: { type: "string" },
|
|
14736
|
+
"password-stdin": { type: "boolean" },
|
|
14737
|
+
"password-env": { type: "boolean" },
|
|
14738
|
+
"prompt-password": { type: "boolean" }
|
|
14739
|
+
},
|
|
14740
|
+
required: ["scope"],
|
|
14741
|
+
parse: async (values, usageLine, context) => ({ ...values, body: await socialCookieSettingsBody(values, usageLine, context) }),
|
|
14742
|
+
run: (client, accountId, [id], values) => client.updateSocialCookieSettings(accountId, id, values.body),
|
|
14743
|
+
done: ([id], payload) => `Saved ${display(payload?.scope)} for social account ${id}.`
|
|
14335
14744
|
}]
|
|
14336
14745
|
]);
|
|
14337
14746
|
|
|
@@ -14381,13 +14790,14 @@ function parseJsonObjectOption(raw, flag) {
|
|
|
14381
14790
|
async function runParityActionCommand(topic, args, context, { accountOverride } = {}) {
|
|
14382
14791
|
const spec = PARITY_ACTION_COMMANDS.get(topic);
|
|
14383
14792
|
const usageLine = `Usage: ${spec.usage} ${ACCOUNT_OPTION_USAGE}`;
|
|
14793
|
+
if (spec.secretInput) rejectSecretArgs(args);
|
|
14384
14794
|
const { values, positionals } = parseCommandArgs(args, { ...jsonOptions(), ...(spec.options || {}) });
|
|
14385
14795
|
const minPositionals = spec.minPositionals ?? 1;
|
|
14386
14796
|
const maxPositionals = spec.maxPositionals ?? 1;
|
|
14387
14797
|
if (positionals.length < minPositionals || positionals.length > maxPositionals) throw new CommandError(usageLine);
|
|
14388
14798
|
if ((spec.required || []).some((key) => !values[key])) throw new CommandError(usageLine);
|
|
14389
14799
|
|
|
14390
|
-
const parsedValues = spec.parse ? spec.parse(values, usageLine) : values;
|
|
14800
|
+
const parsedValues = spec.parse ? await spec.parse(values, usageLine, context) : values;
|
|
14391
14801
|
|
|
14392
14802
|
const { client, accountId } = await requireAccountContext(context, { accountOverride });
|
|
14393
14803
|
const payload = await spec.run(client, accountId, positionals, parsedValues);
|