@audienti/cli 0.1.6 → 0.1.8
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 +14 -0
- package/src/cli.js +147 -6
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.8] - 2026-07-14
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Add `audienti motions clone <motn_id> --name <text>` to clone a motion/play config through the API without copying people.
|
|
12
|
+
- Add `audienti motions move-prospects <source_motn_id> --target <target_motn_id> <prsp_id> [prsp_id...]` to transfer prospects between motions/plays.
|
|
13
|
+
|
|
14
|
+
## [0.1.7] - 2026-07-12
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- Show writer test-run target-step error status and warnings when a draft fails instead of rendering an empty drafted-copy block.
|
|
19
|
+
|
|
7
20
|
## [0.1.6] - 2026-07-12
|
|
8
21
|
|
|
9
22
|
### Added
|
package/package.json
CHANGED
package/src/api-client.js
CHANGED
|
@@ -130,6 +130,20 @@ export class AudientiClient {
|
|
|
130
130
|
});
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
cloneMotion(accountId, motionId, body) {
|
|
134
|
+
return this.requestJson(accountPath(accountId, ["motions", motionId, "clone"]), {
|
|
135
|
+
method: "POST",
|
|
136
|
+
body
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
moveMotionProspects(accountId, motionId, body) {
|
|
141
|
+
return this.requestJson(accountPath(accountId, ["motions", motionId, "move_prospects"]), {
|
|
142
|
+
method: "POST",
|
|
143
|
+
body
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
133
147
|
motionStatus(accountId, motionId) {
|
|
134
148
|
return this.requestJson(accountPath(accountId, ["motions", motionId, "status"]));
|
|
135
149
|
}
|
package/src/cli.js
CHANGED
|
@@ -31,6 +31,8 @@ const PROSPECTS_ADD_PROFILE_USAGE = "Usage: audienti prospects add-profile <prsp
|
|
|
31
31
|
const PROSPECTS_REPORT_BAD_PROFILE_USAGE = "Usage: audienti prospects report-bad-profile <prsp_id> <prof_id|citation_id> [--json] [--account <acct_id>]";
|
|
32
32
|
const WRITER_TEST_RUN_USAGE = "Usage: audienti writer test-run <prsp_id> [--json] [--mode <report|plan|step>] [--branch <both|no-accept|accepted>] [--step <step_key|row_number>] [--no-cache] [--clear-cache] [--account <acct_id>]";
|
|
33
33
|
const MOTIONS_ANALYTICS_USAGE = "Usage: audienti motions analytics <motn_id> [--window 30d] [--json] [--account <acct_id>]";
|
|
34
|
+
const MOTIONS_CLONE_USAGE = "Usage: audienti motions clone <motn_id> --name <text> [--json] [--account <acct_id>]";
|
|
35
|
+
const MOTIONS_MOVE_PROSPECTS_USAGE = "Usage: audienti motions move-prospects <source_motn_id> --target <target_motn_id> <prsp_id> [prsp_id...] [--json] [--account <acct_id>]";
|
|
34
36
|
const ANALYTICS_PROSPECTS_USAGE = "Usage: audienti analytics prospects [--window 24h] [--cohort-start YYYY-MM-DD --cohort-end YYYY-MM-DD] [--motion <motn_id>] [--provenance <source>] [--user <account_user_id|email|name|me>] [--json] [--account <acct_id>]";
|
|
35
37
|
const ANALYTICS_PROSPECTS_COHORT_ANALYSIS_USAGE = "Usage: audienti analytics prospects cohort-analysis [--weeks <n>] [--window 24h] [--motion <motn_id>] [--provenance <source>] [--user <account_user_id|email|name|me>] [--json] [--account <acct_id>]";
|
|
36
38
|
const ANALYTICS_USERS_USAGE = "Usage: audienti analytics users [--user <account_user_id|email|name|me>] [--window 30d | --start YYYY-MM-DD --end YYYY-MM-DD] [--cohort-start YYYY-MM-DD --cohort-end YYYY-MM-DD] [--motion <motn_id>] [--provenance <source>] [--platform <linkedin|email|gmail>] [--json] [--account <acct_id>]";
|
|
@@ -135,6 +137,8 @@ async function dispatch(argv, context) {
|
|
|
135
137
|
if (normalizedResource === "motions" && action === "prospects") return motionsProspects(rest, context, { accountOverride });
|
|
136
138
|
if (normalizedResource === "motions" && action === "add-prospects") return motionsAddProspects(rest, context, { accountOverride });
|
|
137
139
|
if (normalizedResource === "motions" && action === "create") return motionsCreate(rest, context, { accountOverride });
|
|
140
|
+
if (normalizedResource === "motions" && action === "clone") return motionsClone(rest, context, { accountOverride });
|
|
141
|
+
if (normalizedResource === "motions" && action === "move-prospects") return motionsMoveProspects(rest, context, { accountOverride });
|
|
138
142
|
if (normalizedResource === "prospects" && action === "list") return prospectsList(rest, context, { accountOverride });
|
|
139
143
|
if (normalizedResource === "prospects" && action === "show") return prospectsShow(rest, context, { accountOverride });
|
|
140
144
|
if (normalizedResource === "prospects" && action === "timeline") return prospectsTimeline(rest, context, { accountOverride });
|
|
@@ -782,6 +786,52 @@ async function motionsCreate(args, context, { accountOverride } = {}) {
|
|
|
782
786
|
renderMotion(created, context);
|
|
783
787
|
}
|
|
784
788
|
|
|
789
|
+
async function motionsClone(args, context, { accountOverride } = {}) {
|
|
790
|
+
const { values, positionals } = parseCommandArgs(args, {
|
|
791
|
+
...jsonOptions(),
|
|
792
|
+
name: { type: "string" }
|
|
793
|
+
});
|
|
794
|
+
if (positionals.length !== 1 || !values.name) {
|
|
795
|
+
throw new CommandError(MOTIONS_CLONE_USAGE);
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
const { client, accountId } = await requireAccountContext(context, { accountOverride });
|
|
799
|
+
const cloned = await client.cloneMotion(accountId, positionals[0], {
|
|
800
|
+
motion: {
|
|
801
|
+
name: values.name
|
|
802
|
+
}
|
|
803
|
+
});
|
|
804
|
+
if (values.json) return writeJson(context.stdout, cloned);
|
|
805
|
+
|
|
806
|
+
writeLine(context.stdout, `Cloned motion ${display(positionals[0])} as ${display(cloned?.name)} (${display(cloned?.prefix_id)}).`);
|
|
807
|
+
renderMotion(cloned, context);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
async function motionsMoveProspects(args, context, { accountOverride } = {}) {
|
|
811
|
+
const { values, positionals } = parseCommandArgs(args, {
|
|
812
|
+
...jsonOptions(),
|
|
813
|
+
target: { type: "string" }
|
|
814
|
+
});
|
|
815
|
+
if (positionals.length < 2 || !values.target) {
|
|
816
|
+
throw new CommandError(MOTIONS_MOVE_PROSPECTS_USAGE);
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
const [sourceMotionId, ...prospectIds] = positionals;
|
|
820
|
+
const { client, accountId } = await requireAccountContext(context, { accountOverride });
|
|
821
|
+
const payload = await client.moveMotionProspects(accountId, sourceMotionId, {
|
|
822
|
+
target_motion_id: values.target,
|
|
823
|
+
prospect_ids: prospectIds
|
|
824
|
+
});
|
|
825
|
+
if (values.json) return writeJson(context.stdout, payload);
|
|
826
|
+
|
|
827
|
+
const moved = Number(payload?.moved || 0);
|
|
828
|
+
const failed = Array.isArray(payload?.failed) ? payload.failed.length : 0;
|
|
829
|
+
writeLine(context.stdout, `Moved ${moved} prospects from ${display(sourceMotionId)} to ${display(values.target)}.`);
|
|
830
|
+
if (failed > 0) {
|
|
831
|
+
writeLine(context.stdout, `${failed} prospects failed.`);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
785
835
|
async function prospectsList(args, context, { accountOverride } = {}) {
|
|
786
836
|
const { values, positionals } = parseCommandArgs(args, {
|
|
787
837
|
...jsonOptions(),
|
|
@@ -1113,7 +1163,7 @@ function writerCachedDraftsForRequest(cache, branchFilter) {
|
|
|
1113
1163
|
const branchKeys = writerRequestedBranchKeys(branchFilter);
|
|
1114
1164
|
return Object.values(cache.entries || {})
|
|
1115
1165
|
.filter((entry) => branchKeys.includes(entry.branch))
|
|
1116
|
-
.filter((entry) => entry.key && (entry.body || entry.text || entry.subject))
|
|
1166
|
+
.filter((entry) => entry.key && (entry.body || entry.text || entry.subject || writerCacheQualityFailure(entry)))
|
|
1117
1167
|
.map((entry) => compactObject({
|
|
1118
1168
|
branch: entry.branch,
|
|
1119
1169
|
key: entry.key,
|
|
@@ -1125,6 +1175,9 @@ function writerCachedDraftsForRequest(cache, branchFilter) {
|
|
|
1125
1175
|
body: entry.body,
|
|
1126
1176
|
text: entry.text,
|
|
1127
1177
|
status: entry.status,
|
|
1178
|
+
quality_codes: entry.quality_codes,
|
|
1179
|
+
blank_reason: entry.blank_reason,
|
|
1180
|
+
writer_path: entry.writer_path,
|
|
1128
1181
|
generated_at: entry.generated_at,
|
|
1129
1182
|
writer_engine: entry.writer_engine,
|
|
1130
1183
|
target: entry.target,
|
|
@@ -1187,7 +1240,7 @@ async function persistWriterDraftsFromPayload(context, { cache, payload }) {
|
|
|
1187
1240
|
function writerCacheEntryFromStep(step, { branchKey, generatedAt }) {
|
|
1188
1241
|
if (step?.kind !== "message") return null;
|
|
1189
1242
|
if (!step.key) return null;
|
|
1190
|
-
if (!(step.body || step.text || step.subject)) return null;
|
|
1243
|
+
if (!(step.body || step.text || step.subject || writerCacheQualityFailure(step))) return null;
|
|
1191
1244
|
if (step.status === "planned" || step.status === "unavailable" || step.status === "error") return null;
|
|
1192
1245
|
|
|
1193
1246
|
return compactObject({
|
|
@@ -1201,8 +1254,11 @@ function writerCacheEntryFromStep(step, { branchKey, generatedAt }) {
|
|
|
1201
1254
|
body: step.body,
|
|
1202
1255
|
text: step.text,
|
|
1203
1256
|
status: step.status,
|
|
1257
|
+
quality_codes: Array.isArray(step.quality_codes) ? step.quality_codes.filter(Boolean) : undefined,
|
|
1258
|
+
blank_reason: step.blank_reason,
|
|
1259
|
+
writer_path: step.writer_path,
|
|
1204
1260
|
generated_at: step.generated_at || generatedAt || new Date().toISOString(),
|
|
1205
|
-
writer_engine: step?.metadata?.writer_engine,
|
|
1261
|
+
writer_engine: step.writer_engine || step?.metadata?.writer_engine,
|
|
1206
1262
|
target: step.target,
|
|
1207
1263
|
metadata: step.metadata
|
|
1208
1264
|
});
|
|
@@ -1212,6 +1268,10 @@ function writerCacheEntryKey(entry) {
|
|
|
1212
1268
|
return `${entry.branch}:${entry.key}`;
|
|
1213
1269
|
}
|
|
1214
1270
|
|
|
1271
|
+
function writerCacheQualityFailure(entry) {
|
|
1272
|
+
return String(entry?.status || "") === "quality_failure";
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1215
1275
|
async function prospectsImport(args, context, { accountOverride } = {}) {
|
|
1216
1276
|
const { values, positionals } = parseCommandArgs(args, {
|
|
1217
1277
|
...jsonOptions(),
|
|
@@ -2343,7 +2403,7 @@ function renderWriterStepRow(step, index, context) {
|
|
|
2343
2403
|
fixedWidth(compactKindLabel(step.kind), 4),
|
|
2344
2404
|
fixedWidth(writerStepAction(step), 42),
|
|
2345
2405
|
fixedWidth(compactChannelLabel(step.channel), 7),
|
|
2346
|
-
fixedWidth(
|
|
2406
|
+
fixedWidth(compactWriterStatusLabel(step.status), 9)
|
|
2347
2407
|
].join(" ");
|
|
2348
2408
|
writeLine(context.stdout, row);
|
|
2349
2409
|
}
|
|
@@ -2359,8 +2419,21 @@ function renderWriterTargetDraft(branch, context) {
|
|
|
2359
2419
|
writeLine(context.stdout, `Drafted copy: ${display(draftedStep.stage)}${branch.resolved_target_step ? ` (${branch.resolved_target_step})` : ""}`);
|
|
2360
2420
|
const targetUrl = writerDraftTargetUrl(draftedStep);
|
|
2361
2421
|
if (targetUrl) writeLine(context.stdout, `Replying to: ${targetUrl}`);
|
|
2422
|
+
if (draftedStep.status && draftedStep.status !== "success") writeLine(context.stdout, `Status: ${draftedStep.status}`);
|
|
2423
|
+
if (String(draftedStep.status || "") === "quality_failure") {
|
|
2424
|
+
const qualityCodes = Array.isArray(draftedStep.quality_codes) ? draftedStep.quality_codes.filter(Boolean) : [];
|
|
2425
|
+
if (qualityCodes.length) writeLine(context.stdout, `Quality failure: ${qualityCodes.join(", ")}`);
|
|
2426
|
+
if (draftedStep.blank_reason) writeLine(context.stdout, `Blank reason: ${draftedStep.blank_reason}`);
|
|
2427
|
+
if (draftedStep.writer_path) writeLine(context.stdout, `Writer path: ${draftedStep.writer_path}`);
|
|
2428
|
+
if (draftedStep.writer_engine) writeLine(context.stdout, `Writer engine: ${draftedStep.writer_engine}`);
|
|
2429
|
+
}
|
|
2430
|
+
for (const warning of Array.isArray(draftedStep.warnings) ? draftedStep.warnings : []) {
|
|
2431
|
+
if (warning) writeLine(context.stdout, `Warning: ${warning}`);
|
|
2432
|
+
}
|
|
2433
|
+
if (draftedStep.missing_reason) writeLine(context.stdout, `Missing reason: ${draftedStep.missing_reason}`);
|
|
2362
2434
|
if (draftedStep.subject) writeLine(context.stdout, `Subject: ${draftedStep.subject}`);
|
|
2363
|
-
|
|
2435
|
+
const body = draftedStep.body || draftedStep.text || draftedStep.empty_body_reason;
|
|
2436
|
+
writeLine(context.stdout, display(body || "No draft body returned."));
|
|
2364
2437
|
}
|
|
2365
2438
|
|
|
2366
2439
|
function writerDraftTargetUrl(step) {
|
|
@@ -2414,6 +2487,12 @@ function compactChannelLabel(channel) {
|
|
|
2414
2487
|
return value;
|
|
2415
2488
|
}
|
|
2416
2489
|
|
|
2490
|
+
function compactWriterStatusLabel(status) {
|
|
2491
|
+
const value = String(display(status));
|
|
2492
|
+
if (value === "quality_failure") return "quality";
|
|
2493
|
+
return value;
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2417
2496
|
function fixedWidth(value, width, options = {}) {
|
|
2418
2497
|
const text = truncateCliText(value, width);
|
|
2419
2498
|
return options.align === "right" ? text.padStart(width) : text.padEnd(width);
|
|
@@ -3241,6 +3320,8 @@ const HELP_TOPICS = new Map([
|
|
|
3241
3320
|
" audienti motions analytics <motn_id>",
|
|
3242
3321
|
" audienti motions prospects <motn_id>",
|
|
3243
3322
|
" audienti motions create --payload <file.json>",
|
|
3323
|
+
" audienti motions clone <motn_id> --name <text>",
|
|
3324
|
+
" audienti motions move-prospects <source_motn_id> --target <target_motn_id> <prsp_id> [prsp_id...]",
|
|
3244
3325
|
" Tip: `plays` is accepted anywhere `motions` is accepted.",
|
|
3245
3326
|
"",
|
|
3246
3327
|
" Prospects",
|
|
@@ -3813,8 +3894,10 @@ const HELP_TOPICS = new Map([
|
|
|
3813
3894
|
" audienti motions prospects <motn_id> [--json]",
|
|
3814
3895
|
" audienti motions add-prospects <motn_id> <prsp_id> [prsp_id...] [--json]",
|
|
3815
3896
|
" audienti motions create --payload <file.json> [--json]",
|
|
3897
|
+
" audienti motions clone <motn_id> --name <text> [--json]",
|
|
3898
|
+
" audienti motions move-prospects <source_motn_id> --target <target_motn_id> <prsp_id> [prsp_id...] [--json]",
|
|
3816
3899
|
"",
|
|
3817
|
-
"Status: read, create, status, and prospect attachment commands implemented",
|
|
3900
|
+
"Status: read, create, clone, status, and prospect attachment commands implemented",
|
|
3818
3901
|
"",
|
|
3819
3902
|
"CLI synonym:",
|
|
3820
3903
|
" `plays` is accepted anywhere `motions` is accepted",
|
|
@@ -3988,6 +4071,62 @@ const HELP_TOPICS = new Map([
|
|
|
3988
4071
|
" Use `audienti offers list`, `audienti icps list`, and `audienti users list` to resolve valid ids before calling this command."
|
|
3989
4072
|
].join("\n")],
|
|
3990
4073
|
|
|
4074
|
+
["motions clone", [
|
|
4075
|
+
"Usage:",
|
|
4076
|
+
` ${MOTIONS_CLONE_USAGE.slice("Usage: ".length)}`,
|
|
4077
|
+
"",
|
|
4078
|
+
"Status: implemented",
|
|
4079
|
+
"",
|
|
4080
|
+
"Purpose:",
|
|
4081
|
+
" Clone one motion or play's configuration under a new name without copying its people.",
|
|
4082
|
+
"",
|
|
4083
|
+
"Input shape:",
|
|
4084
|
+
" motn_id: motn_ prefix id",
|
|
4085
|
+
" name: new motion name",
|
|
4086
|
+
"",
|
|
4087
|
+
"Behavior:",
|
|
4088
|
+
" Copies the motion kind, offer, ICP, principal, premise, approach, targeting profile, suppression policy, secondary roles, and active signal rows.",
|
|
4089
|
+
" The clone starts as draft with a new empty backing list.",
|
|
4090
|
+
"",
|
|
4091
|
+
"API:",
|
|
4092
|
+
" POST /api/v1/accounts/:account_id/motions/:id/clone.json",
|
|
4093
|
+
"",
|
|
4094
|
+
"JSON body:",
|
|
4095
|
+
" {",
|
|
4096
|
+
" \"motion\": {",
|
|
4097
|
+
" \"name\": \"Wine Campaign Restaurant Operators\"",
|
|
4098
|
+
" }",
|
|
4099
|
+
" }"
|
|
4100
|
+
].join("\n")],
|
|
4101
|
+
|
|
4102
|
+
["motions move-prospects", [
|
|
4103
|
+
"Usage:",
|
|
4104
|
+
` ${MOTIONS_MOVE_PROSPECTS_USAGE.slice("Usage: ".length)}`,
|
|
4105
|
+
"",
|
|
4106
|
+
"Status: implemented",
|
|
4107
|
+
"",
|
|
4108
|
+
"Purpose:",
|
|
4109
|
+
" Move prospects out of one motion or play and into another motion or play.",
|
|
4110
|
+
"",
|
|
4111
|
+
"Input shape:",
|
|
4112
|
+
" source_motn_id: source motn_ prefix id",
|
|
4113
|
+
" target_motn_id: target motn_ prefix id",
|
|
4114
|
+
" prsp_id: one or more prospect prefix ids",
|
|
4115
|
+
"",
|
|
4116
|
+
"Behavior:",
|
|
4117
|
+
" Move removes each selected prospect from the source motion and source backing list, then adds it to the target motion and target backing list.",
|
|
4118
|
+
" Copy is intentionally not exposed until multi-motion membership exists.",
|
|
4119
|
+
"",
|
|
4120
|
+
"API:",
|
|
4121
|
+
" POST /api/v1/accounts/:account_id/motions/:id/move_prospects.json",
|
|
4122
|
+
"",
|
|
4123
|
+
"JSON body:",
|
|
4124
|
+
" {",
|
|
4125
|
+
" \"target_motion_id\": \"motn_target\",",
|
|
4126
|
+
" \"prospect_ids\": [\"prsp_one\", \"prsp_two\"]",
|
|
4127
|
+
" }"
|
|
4128
|
+
].join("\n")],
|
|
4129
|
+
|
|
3991
4130
|
["prospects", [
|
|
3992
4131
|
"Usage:",
|
|
3993
4132
|
" audienti prospects list [--json] [filters]",
|
|
@@ -4733,6 +4872,8 @@ const HELP_TOPICS = new Map([
|
|
|
4733
4872
|
"",
|
|
4734
4873
|
"2. Create a motion or play",
|
|
4735
4874
|
" audienti motions create --payload <file.json>",
|
|
4875
|
+
" audienti motions clone <motn_id> --name \"New subset motion\"",
|
|
4876
|
+
" audienti motions move-prospects <source_motn_id> --target <target_motn_id> <prsp_id> [prsp_id...]",
|
|
4736
4877
|
" audienti motions status <motn_id>",
|
|
4737
4878
|
"",
|
|
4738
4879
|
"3. Add a new prospect from LinkedIn and poll enrichment",
|