@ascendkit/cli 0.3.2 → 0.3.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/dist/cli.js +197 -32
- package/dist/commands/content.d.ts +2 -0
- package/dist/commands/email.d.ts +4 -0
- package/dist/commands/email.js +3 -0
- package/dist/tools/email.js +7 -0
- package/dist/utils/journey-format.d.ts +10 -3
- package/dist/utils/journey-format.js +13 -8
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -20,6 +20,7 @@ import { redactArgs } from "./utils/redaction.js";
|
|
|
20
20
|
import { captureTelemetry } from "./utils/telemetry.js";
|
|
21
21
|
import { hostname as osHostname, platform as osPlatform } from "node:os";
|
|
22
22
|
import { formatJourneyAnalytics, formatJourneyWithGuidance, formatNodeList, formatSingleNode, formatSingleTransition, formatTransitionList, } from "./utils/journey-format.js";
|
|
23
|
+
import { formatDistributionResult, formatQuestionList, formatSingleQuestion, } from "./utils/survey-format.js";
|
|
23
24
|
const require = createRequire(import.meta.url);
|
|
24
25
|
const { version: CLI_VERSION } = require("../package.json");
|
|
25
26
|
const HELP = `ascendkit v${CLI_VERSION} - AscendKit CLI
|
|
@@ -68,10 +69,10 @@ Commands:
|
|
|
68
69
|
template: `Usage: ascendkit template <command>
|
|
69
70
|
|
|
70
71
|
Commands:
|
|
71
|
-
template create --name <name> --subject <subject> --body-html <html> --body-text <text> [--slug <slug>] [--description <description>]
|
|
72
|
+
template create --name <name> --subject <subject> --body-html <html> --body-text <text> [--slug <slug>] [--description <description>] [--category <marketing|transactional>]
|
|
72
73
|
template list [--query <search>] [--system true|--custom true]
|
|
73
74
|
template show <template-id>
|
|
74
|
-
template update <template-id> [--subject <subject>] [--body-html <html>] [--body-text <text>] [--change-note <note>]
|
|
75
|
+
template update <template-id> [--subject <subject>] [--body-html <html>] [--body-text <text>] [--change-note <note>] [--category <marketing|transactional>]
|
|
75
76
|
template remove <template-id>
|
|
76
77
|
template version list <template-id>
|
|
77
78
|
template version show <template-id> <n>`,
|
|
@@ -102,16 +103,16 @@ Commands:
|
|
|
102
103
|
journey update <journey-id> [--name <name>] [--nodes <json>] [--transitions <json>] [--description <description>] [--entry-event <event>] [--entry-node <node>] [--entry-conditions <json>] [--re-entry-policy <skip|restart>]
|
|
103
104
|
journey remove <journey-id>
|
|
104
105
|
journey activate <journey-id>
|
|
105
|
-
journey pause <journey-id>
|
|
106
|
+
journey pause <journey-id> [--yes]
|
|
106
107
|
journey resume <journey-id>
|
|
107
108
|
journey archive <journey-id>
|
|
108
109
|
journey analytics <journey-id>
|
|
109
110
|
journey node list <journey-id>
|
|
110
|
-
journey node add <journey-id> --name <node-name> [--action <json>] [--email-id <email>] [--terminal <true|false>]
|
|
111
|
+
journey node add <journey-id> --name <node-name> [--action <json>] [--email-id <email>] [--terminal <true|false>] [--quiet]
|
|
111
112
|
journey node update <journey-id> <node-name> [--action <json>] [--email-id <email>] [--terminal <true|false>]
|
|
112
113
|
journey node remove <journey-id> <node-name>
|
|
113
114
|
journey transition list <journey-id> [--from <node-name>] [--to <node-name>]
|
|
114
|
-
journey transition add <journey-id> --from <node-name> --to <node-name> --trigger <json> [--priority <n>] [--name <transition-name>]
|
|
115
|
+
journey transition add <journey-id> --from <node-name> --to <node-name> --trigger <json> [--priority <n>] [--name <transition-name>] [--quiet]
|
|
115
116
|
journey transition update <journey-id> <transition-name> [--trigger <json>] [--priority <n>]
|
|
116
117
|
journey transition remove <journey-id> <transition-name>`,
|
|
117
118
|
"email-identity": `Usage: ascendkit email-identity <command>
|
|
@@ -123,6 +124,7 @@ Commands:
|
|
|
123
124
|
email-identity remove-domain
|
|
124
125
|
email-identity list
|
|
125
126
|
email-identity add <email> [--display-name <name>]
|
|
127
|
+
email-identity update <email> --display-name <name>
|
|
126
128
|
email-identity resend <email>
|
|
127
129
|
email-identity set-default <email> [--display-name <name>]
|
|
128
130
|
email-identity remove <email>
|
|
@@ -291,6 +293,8 @@ function printTemplateSummary(data, opts) {
|
|
|
291
293
|
console.log(`Template: ${data.name} (${data.id})`);
|
|
292
294
|
if (data.slug)
|
|
293
295
|
console.log(`Slug: ${data.slug}`);
|
|
296
|
+
if (data.category)
|
|
297
|
+
console.log(`Category: ${data.category}`);
|
|
294
298
|
console.log(`Subject: ${data.subject ?? "-"}`);
|
|
295
299
|
if (data.currentVersion != null)
|
|
296
300
|
console.log(`Version: ${data.currentVersion}`);
|
|
@@ -407,6 +411,92 @@ function printProjectSummary(data) {
|
|
|
407
411
|
console.log(`Environment: ${data.environment.publicKey}`);
|
|
408
412
|
}
|
|
409
413
|
}
|
|
414
|
+
function printCampaignSummary(data) {
|
|
415
|
+
console.log(`Campaign: ${data.name} (${data.id})`);
|
|
416
|
+
if (data.templateName)
|
|
417
|
+
console.log(`Template: ${data.templateName}`);
|
|
418
|
+
console.log(`Status: ${data.status} | Recipients: ${data.totalRecipients ?? 0}`);
|
|
419
|
+
if (data.audienceSummary)
|
|
420
|
+
console.log(`Audience: ${data.audienceSummary}`);
|
|
421
|
+
if (data.scheduledAt)
|
|
422
|
+
console.log(`Scheduled: ${data.scheduledAt}`);
|
|
423
|
+
if (data.sentAt)
|
|
424
|
+
console.log(`Sent: ${data.sentAt}`);
|
|
425
|
+
if (data.fromIdentityEmail)
|
|
426
|
+
console.log(`From: ${data.fromIdentityEmail}`);
|
|
427
|
+
}
|
|
428
|
+
function printCampaignAnalytics(data) {
|
|
429
|
+
const sent = data.sent ?? 0;
|
|
430
|
+
const opened = data.opened ?? 0;
|
|
431
|
+
const clicked = data.clicked ?? 0;
|
|
432
|
+
const bounced = data.bounced ?? 0;
|
|
433
|
+
const rates = data.rates ?? {};
|
|
434
|
+
const fmtRate = (r) => r != null ? ` (${Math.round(r * 100)}%)` : "";
|
|
435
|
+
console.log(`Sent: ${sent} | Opened: ${opened}${fmtRate(rates.openRate)} | Clicked: ${clicked}${fmtRate(rates.clickRate)} | Bounced: ${bounced}`);
|
|
436
|
+
}
|
|
437
|
+
function printAudiencePreview(data) {
|
|
438
|
+
const count = data.count ?? data.total ?? 0;
|
|
439
|
+
console.log(`Matched ${count} recipient(s)`);
|
|
440
|
+
}
|
|
441
|
+
function printWebhookSummary(data, opts) {
|
|
442
|
+
console.log(`Webhook: ${data.id}`);
|
|
443
|
+
const events = Array.isArray(data.events) && data.events.length > 0 ? data.events.join(", ") : "all";
|
|
444
|
+
console.log(`URL: ${data.url} | Status: ${data.status}`);
|
|
445
|
+
console.log(`Events: ${events}`);
|
|
446
|
+
if (opts?.showSecret && data.secret) {
|
|
447
|
+
console.log(`Secret: ${data.secret} (save this — it won't be shown again)`);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
function printWebhookTestResult(data) {
|
|
451
|
+
if (data.success) {
|
|
452
|
+
console.log(`✓ Delivered: HTTP ${data.statusCode}`);
|
|
453
|
+
}
|
|
454
|
+
else if (data.statusCode) {
|
|
455
|
+
console.log(`✗ Failed: HTTP ${data.statusCode}`);
|
|
456
|
+
}
|
|
457
|
+
else {
|
|
458
|
+
console.log(`✗ Failed`);
|
|
459
|
+
}
|
|
460
|
+
if (data.error)
|
|
461
|
+
console.log(data.error);
|
|
462
|
+
}
|
|
463
|
+
function printTemplateVersionSummary(data) {
|
|
464
|
+
const note = data.changeNote || "no note";
|
|
465
|
+
console.log(`Version ${data.versionNumber} — ${note}`);
|
|
466
|
+
console.log(`Subject: ${data.subject ?? "—"}`);
|
|
467
|
+
if (Array.isArray(data.variables) && data.variables.length > 0) {
|
|
468
|
+
console.log(`Variables: ${data.variables.join(", ")}`);
|
|
469
|
+
}
|
|
470
|
+
if (data.createdAt) {
|
|
471
|
+
console.log(`Created: ${data.createdAt}`);
|
|
472
|
+
}
|
|
473
|
+
if (data.bodyHtml) {
|
|
474
|
+
console.log(`\n--- HTML Body ---\n${data.bodyHtml}`);
|
|
475
|
+
}
|
|
476
|
+
if (data.bodyText) {
|
|
477
|
+
console.log(`\n--- Plain Text Body ---\n${data.bodyText}`);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
function printSurveyAnalytics(data) {
|
|
481
|
+
const funnel = data.funnel ?? {};
|
|
482
|
+
const invited = funnel.sent ?? data.totalInvited ?? 0;
|
|
483
|
+
const responded = funnel.submitted ?? data.totalResponded ?? 0;
|
|
484
|
+
const rate = funnel.completionRate ?? data.completionRate ?? 0;
|
|
485
|
+
console.log(`Responses: ${responded}/${invited} (${rate}%)`);
|
|
486
|
+
if (data.npsScore != null)
|
|
487
|
+
console.log(`NPS: ${data.npsScore}`);
|
|
488
|
+
if (data.csatAverage != null)
|
|
489
|
+
console.log(`CSAT: ${data.csatAverage}`);
|
|
490
|
+
}
|
|
491
|
+
function printSurveyInvitations(data) {
|
|
492
|
+
const rows = Array.isArray(data) ? data : [];
|
|
493
|
+
table(rows, [
|
|
494
|
+
{ key: "id", label: "ID", width: 20 },
|
|
495
|
+
{ key: "userId", label: "User", width: 20 },
|
|
496
|
+
{ key: "status", label: "Status", width: 12 },
|
|
497
|
+
{ key: "sentAt", label: "Sent At", width: 22 },
|
|
498
|
+
]);
|
|
499
|
+
}
|
|
410
500
|
function normalizeJourneyRows(data) {
|
|
411
501
|
if (Array.isArray(data)) {
|
|
412
502
|
return data;
|
|
@@ -442,6 +532,16 @@ function table(rows, columns) {
|
|
|
442
532
|
console.log(line);
|
|
443
533
|
}
|
|
444
534
|
}
|
|
535
|
+
function normalizeTemplateCategoryFlag(value) {
|
|
536
|
+
if (value === undefined)
|
|
537
|
+
return undefined;
|
|
538
|
+
if (typeof value != "string")
|
|
539
|
+
return undefined;
|
|
540
|
+
const normalized = value.trim().toLowerCase();
|
|
541
|
+
if (normalized === "marketing" || normalized === "transactional")
|
|
542
|
+
return normalized;
|
|
543
|
+
return undefined;
|
|
544
|
+
}
|
|
445
545
|
async function run() {
|
|
446
546
|
installGlobalHandlers();
|
|
447
547
|
const args = process.argv.slice(2);
|
|
@@ -506,6 +606,14 @@ async function run() {
|
|
|
506
606
|
}
|
|
507
607
|
return;
|
|
508
608
|
}
|
|
609
|
+
// --help anywhere after the action (e.g. ascendkit template update <id> --help)
|
|
610
|
+
if (args.slice(2).some(a => a === "--help" || a === "-h")) {
|
|
611
|
+
if (!printSectionHelp(domain)) {
|
|
612
|
+
console.error(`Unknown command section: ${domain}`);
|
|
613
|
+
return await exitCli(1);
|
|
614
|
+
}
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
509
617
|
// Platform commands (don't need environment key)
|
|
510
618
|
switch (domain) {
|
|
511
619
|
case "init": {
|
|
@@ -857,7 +965,7 @@ async function runEnvironment(action, rest) {
|
|
|
857
965
|
try {
|
|
858
966
|
const result = await platform.updateEnvironment(ctx.projectId, envId, name, description);
|
|
859
967
|
console.log("Environment updated:");
|
|
860
|
-
|
|
968
|
+
printEnvironmentSummary(result);
|
|
861
969
|
}
|
|
862
970
|
catch (err) {
|
|
863
971
|
let message = err instanceof Error ? err.message : String(err);
|
|
@@ -1087,6 +1195,16 @@ async function runAuth(client, action, rest) {
|
|
|
1087
1195
|
}
|
|
1088
1196
|
async function runContent(client, action, rest) {
|
|
1089
1197
|
const flags = parseFlags(rest);
|
|
1198
|
+
// Accept --html/--text as aliases for --body-html/--body-text
|
|
1199
|
+
if (!flags["body-html"] && flags.html)
|
|
1200
|
+
flags["body-html"] = flags.html;
|
|
1201
|
+
if (!flags["body-text"] && flags.text)
|
|
1202
|
+
flags["body-text"] = flags.text;
|
|
1203
|
+
const category = normalizeTemplateCategoryFlag(flags.category);
|
|
1204
|
+
if (flags.category && !category) {
|
|
1205
|
+
console.error("Invalid --category. Use marketing or transactional.");
|
|
1206
|
+
return await exitCli(1);
|
|
1207
|
+
}
|
|
1090
1208
|
if (!action) {
|
|
1091
1209
|
console.log(HELP_SECTION.template);
|
|
1092
1210
|
return;
|
|
@@ -1097,13 +1215,14 @@ async function runContent(client, action, rest) {
|
|
|
1097
1215
|
switch (normalizedAction) {
|
|
1098
1216
|
case "create":
|
|
1099
1217
|
if (!flags.name || !flags.subject || !flags["body-html"] || !flags["body-text"]) {
|
|
1100
|
-
console.error("Usage: ascendkit template create --name <n> --subject <s> --body-html <h> --body-text <t> [--slug <slug>] [--description <desc>]");
|
|
1218
|
+
console.error("Usage: ascendkit template create --name <n> --subject <s> --body-html <h> --body-text <t> [--slug <slug>] [--description <desc>] [--category <marketing|transactional>]");
|
|
1101
1219
|
return await exitCli(1);
|
|
1102
1220
|
}
|
|
1103
1221
|
printTemplateSummary(await content.createTemplate(client, {
|
|
1104
1222
|
name: flags.name, subject: flags.subject,
|
|
1105
1223
|
bodyHtml: flags["body-html"], bodyText: flags["body-text"],
|
|
1106
1224
|
slug: flags.slug, description: flags.description,
|
|
1225
|
+
category,
|
|
1107
1226
|
}));
|
|
1108
1227
|
break;
|
|
1109
1228
|
case "list": {
|
|
@@ -1119,6 +1238,7 @@ async function runContent(client, action, rest) {
|
|
|
1119
1238
|
{ key: "id", label: "ID" },
|
|
1120
1239
|
{ key: "name", label: "Name", width: 30 },
|
|
1121
1240
|
{ key: "slug", label: "Slug", width: 25 },
|
|
1241
|
+
{ key: "category", label: "Category", width: 14 },
|
|
1122
1242
|
{ key: "subject", label: "Subject", width: 30 },
|
|
1123
1243
|
]);
|
|
1124
1244
|
break;
|
|
@@ -1140,6 +1260,7 @@ async function runContent(client, action, rest) {
|
|
|
1140
1260
|
bodyHtml: flags["body-html"],
|
|
1141
1261
|
bodyText: flags["body-text"],
|
|
1142
1262
|
changeNote: flags["change-note"],
|
|
1263
|
+
category,
|
|
1143
1264
|
}));
|
|
1144
1265
|
break;
|
|
1145
1266
|
case "remove":
|
|
@@ -1172,7 +1293,7 @@ async function runContent(client, action, rest) {
|
|
|
1172
1293
|
console.error("Usage: ascendkit template version show <template-id> <n>");
|
|
1173
1294
|
return await exitCli(1);
|
|
1174
1295
|
}
|
|
1175
|
-
|
|
1296
|
+
printTemplateVersionSummary(await content.getVersion(client, templateId, parseInt(versionNumber, 10)));
|
|
1176
1297
|
}
|
|
1177
1298
|
break;
|
|
1178
1299
|
default:
|
|
@@ -1240,7 +1361,7 @@ async function runSurvey(client, action, rest) {
|
|
|
1240
1361
|
console.error("Usage: ascendkit survey distribute <survey-id> --users <usr_id1,usr_id2,...>");
|
|
1241
1362
|
return await exitCli(1);
|
|
1242
1363
|
}
|
|
1243
|
-
|
|
1364
|
+
console.log(formatDistributionResult(await surveys.distributeSurvey(client, rest[0], flags.users.split(","))));
|
|
1244
1365
|
break;
|
|
1245
1366
|
case "invitations":
|
|
1246
1367
|
case "invitation":
|
|
@@ -1250,7 +1371,7 @@ async function runSurvey(client, action, rest) {
|
|
|
1250
1371
|
console.error("Usage: ascendkit survey invitation list <survey-id>");
|
|
1251
1372
|
return await exitCli(1);
|
|
1252
1373
|
}
|
|
1253
|
-
|
|
1374
|
+
printSurveyInvitations(await surveys.listInvitations(client, surveyId));
|
|
1254
1375
|
}
|
|
1255
1376
|
else {
|
|
1256
1377
|
console.error(`Unknown survey invitation command: ${rest[0]}`);
|
|
@@ -1262,7 +1383,7 @@ async function runSurvey(client, action, rest) {
|
|
|
1262
1383
|
console.error("Usage: ascendkit survey analytics <survey-id>");
|
|
1263
1384
|
return await exitCli(1);
|
|
1264
1385
|
}
|
|
1265
|
-
|
|
1386
|
+
printSurveyAnalytics(await surveys.getAnalytics(client, rest[0]));
|
|
1266
1387
|
break;
|
|
1267
1388
|
case "export-definition":
|
|
1268
1389
|
case "import-definition":
|
|
@@ -1327,7 +1448,7 @@ async function runSurvey(client, action, rest) {
|
|
|
1327
1448
|
return await exitCli(1);
|
|
1328
1449
|
}
|
|
1329
1450
|
if (questionAction === "list") {
|
|
1330
|
-
|
|
1451
|
+
console.log(formatQuestionList(await surveys.listQuestions(client, surveyId)));
|
|
1331
1452
|
}
|
|
1332
1453
|
else if (questionAction === "add") {
|
|
1333
1454
|
if (!flags.type || !flags.title) {
|
|
@@ -1343,13 +1464,17 @@ async function runSurvey(client, action, rest) {
|
|
|
1343
1464
|
params.choices = flags.choices.split(",");
|
|
1344
1465
|
if (flags.position != null)
|
|
1345
1466
|
params.position = Number(flags.position);
|
|
1346
|
-
|
|
1467
|
+
console.log(formatSingleQuestion(await surveys.addQuestion(client, surveyId, params), "Added"));
|
|
1347
1468
|
}
|
|
1348
1469
|
else if (questionAction === "update") {
|
|
1349
1470
|
if (!questionName) {
|
|
1350
1471
|
console.error("Usage: ascendkit survey question update <survey-id> <question-name> [--title <title>] [--required <true|false>] [--choices <c1,c2,...>]");
|
|
1351
1472
|
return await exitCli(1);
|
|
1352
1473
|
}
|
|
1474
|
+
if (flags.type) {
|
|
1475
|
+
console.error("Error: question type cannot be changed on an existing question. Remove and re-add it to change the type.");
|
|
1476
|
+
return await exitCli(1);
|
|
1477
|
+
}
|
|
1353
1478
|
const params = {};
|
|
1354
1479
|
if (flags.title)
|
|
1355
1480
|
params.title = flags.title;
|
|
@@ -1357,21 +1482,23 @@ async function runSurvey(client, action, rest) {
|
|
|
1357
1482
|
params.isRequired = flags.required === "true";
|
|
1358
1483
|
if (flags.choices)
|
|
1359
1484
|
params.choices = flags.choices.split(",");
|
|
1360
|
-
|
|
1485
|
+
console.log(formatSingleQuestion(await surveys.editQuestion(client, surveyId, questionName, params), "Updated"));
|
|
1361
1486
|
}
|
|
1362
1487
|
else if (questionAction === "remove") {
|
|
1363
1488
|
if (!questionName) {
|
|
1364
1489
|
console.error("Usage: ascendkit survey question remove <survey-id> <question-name>");
|
|
1365
1490
|
return await exitCli(1);
|
|
1366
1491
|
}
|
|
1367
|
-
|
|
1492
|
+
await surveys.removeQuestion(client, surveyId, questionName);
|
|
1493
|
+
console.log("Question removed.");
|
|
1368
1494
|
}
|
|
1369
1495
|
else if (questionAction === "reorder") {
|
|
1370
1496
|
if (!flags.order) {
|
|
1371
1497
|
console.error("Usage: ascendkit survey question reorder <survey-id> --order <name1,name2,...>");
|
|
1372
1498
|
return await exitCli(1);
|
|
1373
1499
|
}
|
|
1374
|
-
|
|
1500
|
+
await surveys.reorderQuestions(client, surveyId, flags.order.split(","));
|
|
1501
|
+
console.log("Questions reordered.");
|
|
1375
1502
|
}
|
|
1376
1503
|
else {
|
|
1377
1504
|
console.error(`Unknown survey question command: ${questionAction}`);
|
|
@@ -1565,13 +1692,36 @@ async function runJourney(client, action, rest) {
|
|
|
1565
1692
|
}
|
|
1566
1693
|
console.log(formatJourneyWithGuidance(await journeys.activateJourney(client, rest[0])));
|
|
1567
1694
|
break;
|
|
1568
|
-
case "pause":
|
|
1695
|
+
case "pause": {
|
|
1569
1696
|
if (!rest[0]) {
|
|
1570
|
-
console.error("Usage: ascendkit journey pause <journey-id>");
|
|
1697
|
+
console.error("Usage: ascendkit journey pause <journey-id> [--yes]");
|
|
1571
1698
|
return await exitCli(1);
|
|
1572
1699
|
}
|
|
1700
|
+
const journeyForPause = await journeys.getJourney(client, rest[0]);
|
|
1701
|
+
const activeUsers = journeyForPause?.stats?.currentlyActive ?? 0;
|
|
1702
|
+
if (activeUsers > 0 && !flags.yes) {
|
|
1703
|
+
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
1704
|
+
const { createInterface } = await import("node:readline/promises");
|
|
1705
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1706
|
+
try {
|
|
1707
|
+
const answer = (await rl.question(`${activeUsers} user(s) are currently active in this journey. Actions will be queued while paused. Proceed? [y/N] `)).trim().toLowerCase();
|
|
1708
|
+
if (answer !== "y" && answer !== "yes") {
|
|
1709
|
+
console.log("Cancelled.");
|
|
1710
|
+
return;
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
finally {
|
|
1714
|
+
rl.close();
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
else {
|
|
1718
|
+
console.error(`Warning: ${activeUsers} user(s) are currently active. Pass --yes to skip confirmation.`);
|
|
1719
|
+
return await exitCli(1);
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1573
1722
|
console.log(formatJourneyWithGuidance(await journeys.pauseJourney(client, rest[0])));
|
|
1574
1723
|
break;
|
|
1724
|
+
}
|
|
1575
1725
|
case "resume":
|
|
1576
1726
|
if (!rest[0]) {
|
|
1577
1727
|
console.error("Usage: ascendkit journey resume <journey-id>");
|
|
@@ -1617,7 +1767,7 @@ async function runJourney(client, action, rest) {
|
|
|
1617
1767
|
}
|
|
1618
1768
|
if (flags.terminal)
|
|
1619
1769
|
params.terminal = flags.terminal === "true";
|
|
1620
|
-
console.log(formatSingleNode(await journeys.addNode(client, rest[0], params), "Added", flags.name));
|
|
1770
|
+
console.log(formatSingleNode(await journeys.addNode(client, rest[0], params), "Added", flags.name, { suppressWarnings: !!flags.quiet }));
|
|
1621
1771
|
break;
|
|
1622
1772
|
}
|
|
1623
1773
|
case "edit-node": {
|
|
@@ -1692,7 +1842,7 @@ async function runJourney(client, action, rest) {
|
|
|
1692
1842
|
if (flags.name)
|
|
1693
1843
|
params.name = flags.name;
|
|
1694
1844
|
const transitionName = flags.name || `${flags.from}-to-${flags.to}`;
|
|
1695
|
-
console.log(formatSingleTransition(await journeys.addTransition(client, rest[0], params), "Added", transitionName));
|
|
1845
|
+
console.log(formatSingleTransition(await journeys.addTransition(client, rest[0], params), "Added", transitionName, { suppressWarnings: !!flags.quiet }));
|
|
1696
1846
|
break;
|
|
1697
1847
|
}
|
|
1698
1848
|
case "edit-transition": {
|
|
@@ -1735,10 +1885,10 @@ async function runWebhook(client, action, rest) {
|
|
|
1735
1885
|
console.error("Usage: ascendkit webhook create --url <url> [--events <e1,e2,...>]");
|
|
1736
1886
|
return await exitCli(1);
|
|
1737
1887
|
}
|
|
1738
|
-
|
|
1888
|
+
printWebhookSummary(await webhooks.createWebhook(client, {
|
|
1739
1889
|
url: flags.url,
|
|
1740
1890
|
events: flags.events ? flags.events.split(",") : undefined,
|
|
1741
|
-
}));
|
|
1891
|
+
}), { showSecret: true });
|
|
1742
1892
|
break;
|
|
1743
1893
|
case "list":
|
|
1744
1894
|
table(await webhooks.listWebhooks(client), [
|
|
@@ -1753,7 +1903,7 @@ async function runWebhook(client, action, rest) {
|
|
|
1753
1903
|
console.error("Usage: ascendkit webhook get <webhook-id>");
|
|
1754
1904
|
return await exitCli(1);
|
|
1755
1905
|
}
|
|
1756
|
-
|
|
1906
|
+
printWebhookSummary(await webhooks.getWebhook(client, rest[0]));
|
|
1757
1907
|
break;
|
|
1758
1908
|
case "update": {
|
|
1759
1909
|
if (!rest[0]) {
|
|
@@ -1776,14 +1926,15 @@ async function runWebhook(client, action, rest) {
|
|
|
1776
1926
|
console.error("Usage: ascendkit webhook delete <webhook-id>");
|
|
1777
1927
|
return await exitCli(1);
|
|
1778
1928
|
}
|
|
1779
|
-
|
|
1929
|
+
await webhooks.deleteWebhook(client, rest[0]);
|
|
1930
|
+
console.log("Webhook deleted.");
|
|
1780
1931
|
break;
|
|
1781
1932
|
case "test":
|
|
1782
1933
|
if (!rest[0]) {
|
|
1783
1934
|
console.error("Usage: ascendkit webhook test <webhook-id> [--event <event-type>]");
|
|
1784
1935
|
return await exitCli(1);
|
|
1785
1936
|
}
|
|
1786
|
-
|
|
1937
|
+
printWebhookTestResult(await webhooks.testWebhook(client, rest[0], flags.event));
|
|
1787
1938
|
break;
|
|
1788
1939
|
default:
|
|
1789
1940
|
console.error(`Unknown webhook command: ${action}`);
|
|
@@ -1811,7 +1962,7 @@ async function runCampaign(client, action, rest) {
|
|
|
1811
1962
|
console.error("Invalid JSON for --audience flag. Provide a valid JSON object.");
|
|
1812
1963
|
return await exitCli(1);
|
|
1813
1964
|
}
|
|
1814
|
-
|
|
1965
|
+
printCampaignSummary(await campaigns.createCampaign(client, {
|
|
1815
1966
|
name: flags.name,
|
|
1816
1967
|
templateId: flags.template,
|
|
1817
1968
|
audienceFilter: createFilter,
|
|
@@ -1836,7 +1987,7 @@ async function runCampaign(client, action, rest) {
|
|
|
1836
1987
|
console.error("Usage: ascendkit campaign show <campaign-id>");
|
|
1837
1988
|
return await exitCli(1);
|
|
1838
1989
|
}
|
|
1839
|
-
|
|
1990
|
+
printCampaignSummary(await campaigns.getCampaign(client, rest[0]));
|
|
1840
1991
|
break;
|
|
1841
1992
|
case "update": {
|
|
1842
1993
|
if (!rest[0]) {
|
|
@@ -1853,7 +2004,7 @@ async function runCampaign(client, action, rest) {
|
|
|
1853
2004
|
return await exitCli(1);
|
|
1854
2005
|
}
|
|
1855
2006
|
}
|
|
1856
|
-
|
|
2007
|
+
printCampaignSummary(await campaigns.updateCampaign(client, rest[0], {
|
|
1857
2008
|
name: flags.name,
|
|
1858
2009
|
templateId: flags.template,
|
|
1859
2010
|
audienceFilter: updateFilter,
|
|
@@ -1871,7 +2022,7 @@ async function runCampaign(client, action, rest) {
|
|
|
1871
2022
|
console.error("Campaign has no audience filter set.");
|
|
1872
2023
|
return await exitCli(1);
|
|
1873
2024
|
}
|
|
1874
|
-
|
|
2025
|
+
printAudiencePreview(await campaigns.previewAudience(client, detail.audienceFilter));
|
|
1875
2026
|
break;
|
|
1876
2027
|
}
|
|
1877
2028
|
case "schedule":
|
|
@@ -1879,21 +2030,22 @@ async function runCampaign(client, action, rest) {
|
|
|
1879
2030
|
console.error("Usage: ascendkit campaign schedule <campaign-id> --at <datetime>");
|
|
1880
2031
|
return await exitCli(1);
|
|
1881
2032
|
}
|
|
1882
|
-
|
|
2033
|
+
printCampaignSummary(await campaigns.updateCampaign(client, rest[0], { scheduledAt: flags.at }));
|
|
1883
2034
|
break;
|
|
1884
2035
|
case "cancel":
|
|
1885
2036
|
if (!rest[0]) {
|
|
1886
2037
|
console.error("Usage: ascendkit campaign cancel <campaign-id>");
|
|
1887
2038
|
return await exitCli(1);
|
|
1888
2039
|
}
|
|
1889
|
-
|
|
2040
|
+
await campaigns.deleteCampaign(client, rest[0]);
|
|
2041
|
+
console.log("Campaign cancelled.");
|
|
1890
2042
|
break;
|
|
1891
2043
|
case "analytics":
|
|
1892
2044
|
if (!rest[0]) {
|
|
1893
2045
|
console.error("Usage: ascendkit campaign analytics <campaign-id>");
|
|
1894
2046
|
return await exitCli(1);
|
|
1895
2047
|
}
|
|
1896
|
-
|
|
2048
|
+
printCampaignAnalytics(await campaigns.getCampaignAnalytics(client, rest[0]));
|
|
1897
2049
|
break;
|
|
1898
2050
|
default:
|
|
1899
2051
|
console.error(`Unknown campaign command: ${action}`);
|
|
@@ -1975,6 +2127,19 @@ async function runEmail(client, action, rest) {
|
|
|
1975
2127
|
console.log("Verification email sent.");
|
|
1976
2128
|
break;
|
|
1977
2129
|
}
|
|
2130
|
+
case "update": {
|
|
2131
|
+
const identityEmail = rest[0];
|
|
2132
|
+
if (!identityEmail || !flags["display-name"]) {
|
|
2133
|
+
console.error("Usage: ascendkit email-identity update <email> --display-name <name>");
|
|
2134
|
+
return await exitCli(1);
|
|
2135
|
+
}
|
|
2136
|
+
const updated = await email.updateIdentity(client, {
|
|
2137
|
+
email: identityEmail,
|
|
2138
|
+
displayName: flags["display-name"],
|
|
2139
|
+
});
|
|
2140
|
+
printEmailIdentities(updated.identities ?? []);
|
|
2141
|
+
break;
|
|
2142
|
+
}
|
|
1978
2143
|
case "resend": {
|
|
1979
2144
|
const identityEmail = rest[0];
|
|
1980
2145
|
if (!identityEmail) {
|
|
@@ -6,12 +6,14 @@ export interface CreateTemplateParams {
|
|
|
6
6
|
bodyText: string;
|
|
7
7
|
slug?: string;
|
|
8
8
|
description?: string;
|
|
9
|
+
category?: "marketing" | "transactional";
|
|
9
10
|
}
|
|
10
11
|
export interface UpdateTemplateParams {
|
|
11
12
|
subject?: string;
|
|
12
13
|
bodyHtml?: string;
|
|
13
14
|
bodyText?: string;
|
|
14
15
|
changeNote?: string;
|
|
16
|
+
category?: "marketing" | "transactional";
|
|
15
17
|
}
|
|
16
18
|
export declare function createTemplate(client: AscendKitClient, params: CreateTemplateParams): Promise<unknown>;
|
|
17
19
|
export declare function listTemplates(client: AscendKitClient, params?: {
|
package/dist/commands/email.d.ts
CHANGED
|
@@ -80,6 +80,10 @@ export declare function createIdentity(client: AscendKitClient, identity: {
|
|
|
80
80
|
displayName?: string;
|
|
81
81
|
}): Promise<EmailIdentitiesResponse>;
|
|
82
82
|
export declare function resendIdentityVerification(client: AscendKitClient, email: string): Promise<EmailIdentitiesResponse>;
|
|
83
|
+
export declare function updateIdentity(client: AscendKitClient, identity: {
|
|
84
|
+
email: string;
|
|
85
|
+
displayName: string;
|
|
86
|
+
}): Promise<EmailIdentitiesResponse>;
|
|
83
87
|
export declare function setDefaultIdentity(client: AscendKitClient, identity: {
|
|
84
88
|
email: string;
|
|
85
89
|
displayName?: string;
|
package/dist/commands/email.js
CHANGED
|
@@ -37,6 +37,9 @@ export async function createIdentity(client, identity) {
|
|
|
37
37
|
export async function resendIdentityVerification(client, email) {
|
|
38
38
|
return client.managedPost(`/api/email/settings/identities/${encodeURIComponent(email)}/resend`, {});
|
|
39
39
|
}
|
|
40
|
+
export async function updateIdentity(client, identity) {
|
|
41
|
+
return client.managedPost(`/api/email/settings/identities/${encodeURIComponent(identity.email)}/update`, { displayName: identity.displayName });
|
|
42
|
+
}
|
|
40
43
|
export async function setDefaultIdentity(client, identity) {
|
|
41
44
|
return client.managedPost(`/api/email/settings/identities/${encodeURIComponent(identity.email)}/default`, { displayName: identity.displayName ?? "" });
|
|
42
45
|
}
|
package/dist/tools/email.js
CHANGED
|
@@ -64,6 +64,13 @@ export function registerEmailTools(server, client) {
|
|
|
64
64
|
const data = await email.resendIdentityVerification(client, params.email);
|
|
65
65
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
66
66
|
});
|
|
67
|
+
server.tool("email_identity_update", "Update the display name of a sender identity", {
|
|
68
|
+
email: z.string().describe("Sender identity email"),
|
|
69
|
+
displayName: z.string().describe("New display name"),
|
|
70
|
+
}, async (params) => {
|
|
71
|
+
const data = await email.updateIdentity(client, params);
|
|
72
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
73
|
+
});
|
|
67
74
|
server.tool("email_identity_set_default", "Set the default sender identity for this environment", {
|
|
68
75
|
email: z.string().describe("Sender identity email"),
|
|
69
76
|
displayName: z.string().optional().describe("Optional updated display name"),
|
|
@@ -22,6 +22,7 @@ interface JourneyData {
|
|
|
22
22
|
terminal?: boolean;
|
|
23
23
|
}>;
|
|
24
24
|
transitions?: Array<{
|
|
25
|
+
name?: string;
|
|
25
26
|
from?: string;
|
|
26
27
|
from_?: string;
|
|
27
28
|
to?: string;
|
|
@@ -75,7 +76,9 @@ interface AnalyticsData {
|
|
|
75
76
|
exited?: number;
|
|
76
77
|
};
|
|
77
78
|
}
|
|
78
|
-
export declare function formatJourneyWithGuidance(journey: JourneyData
|
|
79
|
+
export declare function formatJourneyWithGuidance(journey: JourneyData, opts?: {
|
|
80
|
+
suppressWarnings?: boolean;
|
|
81
|
+
}): string;
|
|
79
82
|
export declare function formatJourneyList(data: {
|
|
80
83
|
journeys: JourneyData[];
|
|
81
84
|
total: number;
|
|
@@ -109,10 +112,14 @@ interface TransitionListItem {
|
|
|
109
112
|
export declare function formatNodeList(data: {
|
|
110
113
|
nodes: NodeListItem[];
|
|
111
114
|
}): string;
|
|
112
|
-
export declare function formatSingleNode(data: JourneyData, action: string, nodeName: string
|
|
115
|
+
export declare function formatSingleNode(data: JourneyData, action: string, nodeName: string, opts?: {
|
|
116
|
+
suppressWarnings?: boolean;
|
|
117
|
+
}): string;
|
|
113
118
|
export declare function formatTransitionList(data: {
|
|
114
119
|
transitions: TransitionListItem[];
|
|
115
120
|
}): string;
|
|
116
|
-
export declare function formatSingleTransition(data: JourneyData, action: string, transitionName: string
|
|
121
|
+
export declare function formatSingleTransition(data: JourneyData, action: string, transitionName: string, opts?: {
|
|
122
|
+
suppressWarnings?: boolean;
|
|
123
|
+
}): string;
|
|
117
124
|
export declare function formatJourneyAnalytics(data: AnalyticsData): string;
|
|
118
125
|
export {};
|
|
@@ -14,7 +14,7 @@ function nodeCount(journey) {
|
|
|
14
14
|
function transitionCount(journey) {
|
|
15
15
|
return (journey.transitions || []).length;
|
|
16
16
|
}
|
|
17
|
-
export function formatJourneyWithGuidance(journey) {
|
|
17
|
+
export function formatJourneyWithGuidance(journey, opts) {
|
|
18
18
|
const nodes = nodeCount(journey);
|
|
19
19
|
const transitions = transitionCount(journey);
|
|
20
20
|
const stats = journey.stats;
|
|
@@ -50,11 +50,16 @@ export function formatJourneyWithGuidance(journey) {
|
|
|
50
50
|
else {
|
|
51
51
|
desc = trigger?.event || "event";
|
|
52
52
|
}
|
|
53
|
-
|
|
53
|
+
const id = t.name ? ` (${t.name})` : "";
|
|
54
|
+
lines.push(` ${from} → ${t.to} [${desc}]${id}`);
|
|
54
55
|
}
|
|
55
56
|
}
|
|
56
|
-
// Warnings
|
|
57
|
-
|
|
57
|
+
// Warnings — suppressed on paused journeys (mid-construction) or when opts.suppressWarnings is set
|
|
58
|
+
const showWarnings = !opts?.suppressWarnings &&
|
|
59
|
+
journey.status !== "paused" &&
|
|
60
|
+
journey.warnings &&
|
|
61
|
+
journey.warnings.length > 0;
|
|
62
|
+
if (showWarnings) {
|
|
58
63
|
lines.push("");
|
|
59
64
|
lines.push("Warnings:");
|
|
60
65
|
for (const w of journey.warnings) {
|
|
@@ -146,8 +151,8 @@ export function formatNodeList(data) {
|
|
|
146
151
|
}
|
|
147
152
|
return lines.join("\n");
|
|
148
153
|
}
|
|
149
|
-
export function formatSingleNode(data, action, nodeName) {
|
|
150
|
-
return `${action} node '${nodeName}'.\n\n${formatJourneyWithGuidance(data)}`;
|
|
154
|
+
export function formatSingleNode(data, action, nodeName, opts) {
|
|
155
|
+
return `${action} node '${nodeName}'.\n\n${formatJourneyWithGuidance(data, opts)}`;
|
|
151
156
|
}
|
|
152
157
|
export function formatTransitionList(data) {
|
|
153
158
|
const transitions = data.transitions || [];
|
|
@@ -162,8 +167,8 @@ export function formatTransitionList(data) {
|
|
|
162
167
|
}
|
|
163
168
|
return lines.join("\n");
|
|
164
169
|
}
|
|
165
|
-
export function formatSingleTransition(data, action, transitionName) {
|
|
166
|
-
return `${action} transition '${transitionName}'.\n\n${formatJourneyWithGuidance(data)}`;
|
|
170
|
+
export function formatSingleTransition(data, action, transitionName, opts) {
|
|
171
|
+
return `${action} transition '${transitionName}'.\n\n${formatJourneyWithGuidance(data, opts)}`;
|
|
167
172
|
}
|
|
168
173
|
export function formatJourneyAnalytics(data) {
|
|
169
174
|
const { journey, nodes, transitions, terminalDistribution, totals } = data;
|