@ascendkit/cli 0.3.2 → 0.3.9
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 +203 -35
- 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>
|
|
@@ -140,10 +142,10 @@ Commands:
|
|
|
140
142
|
campaign: `Usage: ascendkit campaign <command>
|
|
141
143
|
|
|
142
144
|
Commands:
|
|
143
|
-
campaign create --name <name> --template <template-id> --audience <json> [--scheduled-at <datetime>]
|
|
145
|
+
campaign create --name <name> --template <template-id> --audience <json> [--from <email>] [--scheduled-at <datetime>]
|
|
144
146
|
campaign list [--status <draft|scheduled|sending|sent|failed|cancelled>]
|
|
145
147
|
campaign show <campaign-id>
|
|
146
|
-
campaign update <campaign-id> [--name <name>] [--template <template-id>] [--audience <json>] [--scheduled-at <datetime>]
|
|
148
|
+
campaign update <campaign-id> [--name <name>] [--template <template-id>] [--audience <json>] [--from <email>] [--scheduled-at <datetime>]
|
|
147
149
|
campaign preview <campaign-id>
|
|
148
150
|
campaign schedule <campaign-id> --at <datetime>
|
|
149
151
|
campaign cancel <campaign-id>
|
|
@@ -151,6 +153,7 @@ Commands:
|
|
|
151
153
|
|
|
152
154
|
Notes:
|
|
153
155
|
- --audience is a JSON filter object, e.g. '{"tags":{"$in":["premium"]}}'
|
|
156
|
+
- --from specifies a verified email identity to send from (defaults to environment default)
|
|
154
157
|
- --scheduled-at / --at accepts ISO 8601 datetime, e.g. 2026-03-15T10:00:00Z
|
|
155
158
|
- cancel deletes a draft/failed campaign or cancels a scheduled/sending campaign`,
|
|
156
159
|
environment: `Usage: ascendkit environment <command>
|
|
@@ -291,6 +294,8 @@ function printTemplateSummary(data, opts) {
|
|
|
291
294
|
console.log(`Template: ${data.name} (${data.id})`);
|
|
292
295
|
if (data.slug)
|
|
293
296
|
console.log(`Slug: ${data.slug}`);
|
|
297
|
+
if (data.category)
|
|
298
|
+
console.log(`Category: ${data.category}`);
|
|
294
299
|
console.log(`Subject: ${data.subject ?? "-"}`);
|
|
295
300
|
if (data.currentVersion != null)
|
|
296
301
|
console.log(`Version: ${data.currentVersion}`);
|
|
@@ -407,6 +412,92 @@ function printProjectSummary(data) {
|
|
|
407
412
|
console.log(`Environment: ${data.environment.publicKey}`);
|
|
408
413
|
}
|
|
409
414
|
}
|
|
415
|
+
function printCampaignSummary(data) {
|
|
416
|
+
console.log(`Campaign: ${data.name} (${data.id})`);
|
|
417
|
+
if (data.templateName)
|
|
418
|
+
console.log(`Template: ${data.templateName}`);
|
|
419
|
+
console.log(`Status: ${data.status} | Recipients: ${data.totalRecipients ?? 0}`);
|
|
420
|
+
if (data.audienceSummary)
|
|
421
|
+
console.log(`Audience: ${data.audienceSummary}`);
|
|
422
|
+
if (data.scheduledAt)
|
|
423
|
+
console.log(`Scheduled: ${data.scheduledAt}`);
|
|
424
|
+
if (data.sentAt)
|
|
425
|
+
console.log(`Sent: ${data.sentAt}`);
|
|
426
|
+
if (data.fromIdentityEmail)
|
|
427
|
+
console.log(`From: ${data.fromIdentityEmail}`);
|
|
428
|
+
}
|
|
429
|
+
function printCampaignAnalytics(data) {
|
|
430
|
+
const sent = data.sent ?? 0;
|
|
431
|
+
const opened = data.opened ?? 0;
|
|
432
|
+
const clicked = data.clicked ?? 0;
|
|
433
|
+
const bounced = data.bounced ?? 0;
|
|
434
|
+
const rates = data.rates ?? {};
|
|
435
|
+
const fmtRate = (r) => r != null ? ` (${Math.round(r * 100)}%)` : "";
|
|
436
|
+
console.log(`Sent: ${sent} | Opened: ${opened}${fmtRate(rates.openRate)} | Clicked: ${clicked}${fmtRate(rates.clickRate)} | Bounced: ${bounced}`);
|
|
437
|
+
}
|
|
438
|
+
function printAudiencePreview(data) {
|
|
439
|
+
const count = data.count ?? data.total ?? 0;
|
|
440
|
+
console.log(`Matched ${count} recipient(s)`);
|
|
441
|
+
}
|
|
442
|
+
function printWebhookSummary(data, opts) {
|
|
443
|
+
console.log(`Webhook: ${data.id}`);
|
|
444
|
+
const events = Array.isArray(data.events) && data.events.length > 0 ? data.events.join(", ") : "all";
|
|
445
|
+
console.log(`URL: ${data.url} | Status: ${data.status}`);
|
|
446
|
+
console.log(`Events: ${events}`);
|
|
447
|
+
if (opts?.showSecret && data.secret) {
|
|
448
|
+
console.log(`Secret: ${data.secret} (save this — it won't be shown again)`);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
function printWebhookTestResult(data) {
|
|
452
|
+
if (data.success) {
|
|
453
|
+
console.log(`✓ Delivered: HTTP ${data.statusCode}`);
|
|
454
|
+
}
|
|
455
|
+
else if (data.statusCode) {
|
|
456
|
+
console.log(`✗ Failed: HTTP ${data.statusCode}`);
|
|
457
|
+
}
|
|
458
|
+
else {
|
|
459
|
+
console.log(`✗ Failed`);
|
|
460
|
+
}
|
|
461
|
+
if (data.error)
|
|
462
|
+
console.log(data.error);
|
|
463
|
+
}
|
|
464
|
+
function printTemplateVersionSummary(data) {
|
|
465
|
+
const note = data.changeNote || "no note";
|
|
466
|
+
console.log(`Version ${data.versionNumber} — ${note}`);
|
|
467
|
+
console.log(`Subject: ${data.subject ?? "—"}`);
|
|
468
|
+
if (Array.isArray(data.variables) && data.variables.length > 0) {
|
|
469
|
+
console.log(`Variables: ${data.variables.join(", ")}`);
|
|
470
|
+
}
|
|
471
|
+
if (data.createdAt) {
|
|
472
|
+
console.log(`Created: ${data.createdAt}`);
|
|
473
|
+
}
|
|
474
|
+
if (data.bodyHtml) {
|
|
475
|
+
console.log(`\n--- HTML Body ---\n${data.bodyHtml}`);
|
|
476
|
+
}
|
|
477
|
+
if (data.bodyText) {
|
|
478
|
+
console.log(`\n--- Plain Text Body ---\n${data.bodyText}`);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
function printSurveyAnalytics(data) {
|
|
482
|
+
const funnel = data.funnel ?? {};
|
|
483
|
+
const invited = funnel.sent ?? data.totalInvited ?? 0;
|
|
484
|
+
const responded = funnel.submitted ?? data.totalResponded ?? 0;
|
|
485
|
+
const rate = funnel.completionRate ?? data.completionRate ?? 0;
|
|
486
|
+
console.log(`Responses: ${responded}/${invited} (${rate}%)`);
|
|
487
|
+
if (data.npsScore != null)
|
|
488
|
+
console.log(`NPS: ${data.npsScore}`);
|
|
489
|
+
if (data.csatAverage != null)
|
|
490
|
+
console.log(`CSAT: ${data.csatAverage}`);
|
|
491
|
+
}
|
|
492
|
+
function printSurveyInvitations(data) {
|
|
493
|
+
const rows = Array.isArray(data) ? data : [];
|
|
494
|
+
table(rows, [
|
|
495
|
+
{ key: "id", label: "ID", width: 20 },
|
|
496
|
+
{ key: "userId", label: "User", width: 20 },
|
|
497
|
+
{ key: "status", label: "Status", width: 12 },
|
|
498
|
+
{ key: "sentAt", label: "Sent At", width: 22 },
|
|
499
|
+
]);
|
|
500
|
+
}
|
|
410
501
|
function normalizeJourneyRows(data) {
|
|
411
502
|
if (Array.isArray(data)) {
|
|
412
503
|
return data;
|
|
@@ -442,6 +533,16 @@ function table(rows, columns) {
|
|
|
442
533
|
console.log(line);
|
|
443
534
|
}
|
|
444
535
|
}
|
|
536
|
+
function normalizeTemplateCategoryFlag(value) {
|
|
537
|
+
if (value === undefined)
|
|
538
|
+
return undefined;
|
|
539
|
+
if (typeof value != "string")
|
|
540
|
+
return undefined;
|
|
541
|
+
const normalized = value.trim().toLowerCase();
|
|
542
|
+
if (normalized === "marketing" || normalized === "transactional")
|
|
543
|
+
return normalized;
|
|
544
|
+
return undefined;
|
|
545
|
+
}
|
|
445
546
|
async function run() {
|
|
446
547
|
installGlobalHandlers();
|
|
447
548
|
const args = process.argv.slice(2);
|
|
@@ -506,6 +607,14 @@ async function run() {
|
|
|
506
607
|
}
|
|
507
608
|
return;
|
|
508
609
|
}
|
|
610
|
+
// --help anywhere after the action (e.g. ascendkit template update <id> --help)
|
|
611
|
+
if (args.slice(2).some(a => a === "--help" || a === "-h")) {
|
|
612
|
+
if (!printSectionHelp(domain)) {
|
|
613
|
+
console.error(`Unknown command section: ${domain}`);
|
|
614
|
+
return await exitCli(1);
|
|
615
|
+
}
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
509
618
|
// Platform commands (don't need environment key)
|
|
510
619
|
switch (domain) {
|
|
511
620
|
case "init": {
|
|
@@ -857,7 +966,7 @@ async function runEnvironment(action, rest) {
|
|
|
857
966
|
try {
|
|
858
967
|
const result = await platform.updateEnvironment(ctx.projectId, envId, name, description);
|
|
859
968
|
console.log("Environment updated:");
|
|
860
|
-
|
|
969
|
+
printEnvironmentSummary(result);
|
|
861
970
|
}
|
|
862
971
|
catch (err) {
|
|
863
972
|
let message = err instanceof Error ? err.message : String(err);
|
|
@@ -1087,6 +1196,16 @@ async function runAuth(client, action, rest) {
|
|
|
1087
1196
|
}
|
|
1088
1197
|
async function runContent(client, action, rest) {
|
|
1089
1198
|
const flags = parseFlags(rest);
|
|
1199
|
+
// Accept --html/--text as aliases for --body-html/--body-text
|
|
1200
|
+
if (!flags["body-html"] && flags.html)
|
|
1201
|
+
flags["body-html"] = flags.html;
|
|
1202
|
+
if (!flags["body-text"] && flags.text)
|
|
1203
|
+
flags["body-text"] = flags.text;
|
|
1204
|
+
const category = normalizeTemplateCategoryFlag(flags.category);
|
|
1205
|
+
if (flags.category && !category) {
|
|
1206
|
+
console.error("Invalid --category. Use marketing or transactional.");
|
|
1207
|
+
return await exitCli(1);
|
|
1208
|
+
}
|
|
1090
1209
|
if (!action) {
|
|
1091
1210
|
console.log(HELP_SECTION.template);
|
|
1092
1211
|
return;
|
|
@@ -1097,13 +1216,14 @@ async function runContent(client, action, rest) {
|
|
|
1097
1216
|
switch (normalizedAction) {
|
|
1098
1217
|
case "create":
|
|
1099
1218
|
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>]");
|
|
1219
|
+
console.error("Usage: ascendkit template create --name <n> --subject <s> --body-html <h> --body-text <t> [--slug <slug>] [--description <desc>] [--category <marketing|transactional>]");
|
|
1101
1220
|
return await exitCli(1);
|
|
1102
1221
|
}
|
|
1103
1222
|
printTemplateSummary(await content.createTemplate(client, {
|
|
1104
1223
|
name: flags.name, subject: flags.subject,
|
|
1105
1224
|
bodyHtml: flags["body-html"], bodyText: flags["body-text"],
|
|
1106
1225
|
slug: flags.slug, description: flags.description,
|
|
1226
|
+
category,
|
|
1107
1227
|
}));
|
|
1108
1228
|
break;
|
|
1109
1229
|
case "list": {
|
|
@@ -1119,6 +1239,7 @@ async function runContent(client, action, rest) {
|
|
|
1119
1239
|
{ key: "id", label: "ID" },
|
|
1120
1240
|
{ key: "name", label: "Name", width: 30 },
|
|
1121
1241
|
{ key: "slug", label: "Slug", width: 25 },
|
|
1242
|
+
{ key: "category", label: "Category", width: 14 },
|
|
1122
1243
|
{ key: "subject", label: "Subject", width: 30 },
|
|
1123
1244
|
]);
|
|
1124
1245
|
break;
|
|
@@ -1140,6 +1261,7 @@ async function runContent(client, action, rest) {
|
|
|
1140
1261
|
bodyHtml: flags["body-html"],
|
|
1141
1262
|
bodyText: flags["body-text"],
|
|
1142
1263
|
changeNote: flags["change-note"],
|
|
1264
|
+
category,
|
|
1143
1265
|
}));
|
|
1144
1266
|
break;
|
|
1145
1267
|
case "remove":
|
|
@@ -1172,7 +1294,7 @@ async function runContent(client, action, rest) {
|
|
|
1172
1294
|
console.error("Usage: ascendkit template version show <template-id> <n>");
|
|
1173
1295
|
return await exitCli(1);
|
|
1174
1296
|
}
|
|
1175
|
-
|
|
1297
|
+
printTemplateVersionSummary(await content.getVersion(client, templateId, parseInt(versionNumber, 10)));
|
|
1176
1298
|
}
|
|
1177
1299
|
break;
|
|
1178
1300
|
default:
|
|
@@ -1240,7 +1362,7 @@ async function runSurvey(client, action, rest) {
|
|
|
1240
1362
|
console.error("Usage: ascendkit survey distribute <survey-id> --users <usr_id1,usr_id2,...>");
|
|
1241
1363
|
return await exitCli(1);
|
|
1242
1364
|
}
|
|
1243
|
-
|
|
1365
|
+
console.log(formatDistributionResult(await surveys.distributeSurvey(client, rest[0], flags.users.split(","))));
|
|
1244
1366
|
break;
|
|
1245
1367
|
case "invitations":
|
|
1246
1368
|
case "invitation":
|
|
@@ -1250,7 +1372,7 @@ async function runSurvey(client, action, rest) {
|
|
|
1250
1372
|
console.error("Usage: ascendkit survey invitation list <survey-id>");
|
|
1251
1373
|
return await exitCli(1);
|
|
1252
1374
|
}
|
|
1253
|
-
|
|
1375
|
+
printSurveyInvitations(await surveys.listInvitations(client, surveyId));
|
|
1254
1376
|
}
|
|
1255
1377
|
else {
|
|
1256
1378
|
console.error(`Unknown survey invitation command: ${rest[0]}`);
|
|
@@ -1262,7 +1384,7 @@ async function runSurvey(client, action, rest) {
|
|
|
1262
1384
|
console.error("Usage: ascendkit survey analytics <survey-id>");
|
|
1263
1385
|
return await exitCli(1);
|
|
1264
1386
|
}
|
|
1265
|
-
|
|
1387
|
+
printSurveyAnalytics(await surveys.getAnalytics(client, rest[0]));
|
|
1266
1388
|
break;
|
|
1267
1389
|
case "export-definition":
|
|
1268
1390
|
case "import-definition":
|
|
@@ -1327,7 +1449,7 @@ async function runSurvey(client, action, rest) {
|
|
|
1327
1449
|
return await exitCli(1);
|
|
1328
1450
|
}
|
|
1329
1451
|
if (questionAction === "list") {
|
|
1330
|
-
|
|
1452
|
+
console.log(formatQuestionList(await surveys.listQuestions(client, surveyId)));
|
|
1331
1453
|
}
|
|
1332
1454
|
else if (questionAction === "add") {
|
|
1333
1455
|
if (!flags.type || !flags.title) {
|
|
@@ -1343,13 +1465,17 @@ async function runSurvey(client, action, rest) {
|
|
|
1343
1465
|
params.choices = flags.choices.split(",");
|
|
1344
1466
|
if (flags.position != null)
|
|
1345
1467
|
params.position = Number(flags.position);
|
|
1346
|
-
|
|
1468
|
+
console.log(formatSingleQuestion(await surveys.addQuestion(client, surveyId, params), "Added"));
|
|
1347
1469
|
}
|
|
1348
1470
|
else if (questionAction === "update") {
|
|
1349
1471
|
if (!questionName) {
|
|
1350
1472
|
console.error("Usage: ascendkit survey question update <survey-id> <question-name> [--title <title>] [--required <true|false>] [--choices <c1,c2,...>]");
|
|
1351
1473
|
return await exitCli(1);
|
|
1352
1474
|
}
|
|
1475
|
+
if (flags.type) {
|
|
1476
|
+
console.error("Error: question type cannot be changed on an existing question. Remove and re-add it to change the type.");
|
|
1477
|
+
return await exitCli(1);
|
|
1478
|
+
}
|
|
1353
1479
|
const params = {};
|
|
1354
1480
|
if (flags.title)
|
|
1355
1481
|
params.title = flags.title;
|
|
@@ -1357,21 +1483,23 @@ async function runSurvey(client, action, rest) {
|
|
|
1357
1483
|
params.isRequired = flags.required === "true";
|
|
1358
1484
|
if (flags.choices)
|
|
1359
1485
|
params.choices = flags.choices.split(",");
|
|
1360
|
-
|
|
1486
|
+
console.log(formatSingleQuestion(await surveys.editQuestion(client, surveyId, questionName, params), "Updated"));
|
|
1361
1487
|
}
|
|
1362
1488
|
else if (questionAction === "remove") {
|
|
1363
1489
|
if (!questionName) {
|
|
1364
1490
|
console.error("Usage: ascendkit survey question remove <survey-id> <question-name>");
|
|
1365
1491
|
return await exitCli(1);
|
|
1366
1492
|
}
|
|
1367
|
-
|
|
1493
|
+
await surveys.removeQuestion(client, surveyId, questionName);
|
|
1494
|
+
console.log("Question removed.");
|
|
1368
1495
|
}
|
|
1369
1496
|
else if (questionAction === "reorder") {
|
|
1370
1497
|
if (!flags.order) {
|
|
1371
1498
|
console.error("Usage: ascendkit survey question reorder <survey-id> --order <name1,name2,...>");
|
|
1372
1499
|
return await exitCli(1);
|
|
1373
1500
|
}
|
|
1374
|
-
|
|
1501
|
+
await surveys.reorderQuestions(client, surveyId, flags.order.split(","));
|
|
1502
|
+
console.log("Questions reordered.");
|
|
1375
1503
|
}
|
|
1376
1504
|
else {
|
|
1377
1505
|
console.error(`Unknown survey question command: ${questionAction}`);
|
|
@@ -1565,13 +1693,36 @@ async function runJourney(client, action, rest) {
|
|
|
1565
1693
|
}
|
|
1566
1694
|
console.log(formatJourneyWithGuidance(await journeys.activateJourney(client, rest[0])));
|
|
1567
1695
|
break;
|
|
1568
|
-
case "pause":
|
|
1696
|
+
case "pause": {
|
|
1569
1697
|
if (!rest[0]) {
|
|
1570
|
-
console.error("Usage: ascendkit journey pause <journey-id>");
|
|
1698
|
+
console.error("Usage: ascendkit journey pause <journey-id> [--yes]");
|
|
1571
1699
|
return await exitCli(1);
|
|
1572
1700
|
}
|
|
1701
|
+
const journeyForPause = await journeys.getJourney(client, rest[0]);
|
|
1702
|
+
const activeUsers = journeyForPause?.stats?.currentlyActive ?? 0;
|
|
1703
|
+
if (activeUsers > 0 && !flags.yes) {
|
|
1704
|
+
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
1705
|
+
const { createInterface } = await import("node:readline/promises");
|
|
1706
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1707
|
+
try {
|
|
1708
|
+
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();
|
|
1709
|
+
if (answer !== "y" && answer !== "yes") {
|
|
1710
|
+
console.log("Cancelled.");
|
|
1711
|
+
return;
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
finally {
|
|
1715
|
+
rl.close();
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
else {
|
|
1719
|
+
console.error(`Warning: ${activeUsers} user(s) are currently active. Pass --yes to skip confirmation.`);
|
|
1720
|
+
return await exitCli(1);
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1573
1723
|
console.log(formatJourneyWithGuidance(await journeys.pauseJourney(client, rest[0])));
|
|
1574
1724
|
break;
|
|
1725
|
+
}
|
|
1575
1726
|
case "resume":
|
|
1576
1727
|
if (!rest[0]) {
|
|
1577
1728
|
console.error("Usage: ascendkit journey resume <journey-id>");
|
|
@@ -1617,7 +1768,7 @@ async function runJourney(client, action, rest) {
|
|
|
1617
1768
|
}
|
|
1618
1769
|
if (flags.terminal)
|
|
1619
1770
|
params.terminal = flags.terminal === "true";
|
|
1620
|
-
console.log(formatSingleNode(await journeys.addNode(client, rest[0], params), "Added", flags.name));
|
|
1771
|
+
console.log(formatSingleNode(await journeys.addNode(client, rest[0], params), "Added", flags.name, { suppressWarnings: !!flags.quiet }));
|
|
1621
1772
|
break;
|
|
1622
1773
|
}
|
|
1623
1774
|
case "edit-node": {
|
|
@@ -1692,7 +1843,7 @@ async function runJourney(client, action, rest) {
|
|
|
1692
1843
|
if (flags.name)
|
|
1693
1844
|
params.name = flags.name;
|
|
1694
1845
|
const transitionName = flags.name || `${flags.from}-to-${flags.to}`;
|
|
1695
|
-
console.log(formatSingleTransition(await journeys.addTransition(client, rest[0], params), "Added", transitionName));
|
|
1846
|
+
console.log(formatSingleTransition(await journeys.addTransition(client, rest[0], params), "Added", transitionName, { suppressWarnings: !!flags.quiet }));
|
|
1696
1847
|
break;
|
|
1697
1848
|
}
|
|
1698
1849
|
case "edit-transition": {
|
|
@@ -1735,10 +1886,10 @@ async function runWebhook(client, action, rest) {
|
|
|
1735
1886
|
console.error("Usage: ascendkit webhook create --url <url> [--events <e1,e2,...>]");
|
|
1736
1887
|
return await exitCli(1);
|
|
1737
1888
|
}
|
|
1738
|
-
|
|
1889
|
+
printWebhookSummary(await webhooks.createWebhook(client, {
|
|
1739
1890
|
url: flags.url,
|
|
1740
1891
|
events: flags.events ? flags.events.split(",") : undefined,
|
|
1741
|
-
}));
|
|
1892
|
+
}), { showSecret: true });
|
|
1742
1893
|
break;
|
|
1743
1894
|
case "list":
|
|
1744
1895
|
table(await webhooks.listWebhooks(client), [
|
|
@@ -1753,7 +1904,7 @@ async function runWebhook(client, action, rest) {
|
|
|
1753
1904
|
console.error("Usage: ascendkit webhook get <webhook-id>");
|
|
1754
1905
|
return await exitCli(1);
|
|
1755
1906
|
}
|
|
1756
|
-
|
|
1907
|
+
printWebhookSummary(await webhooks.getWebhook(client, rest[0]));
|
|
1757
1908
|
break;
|
|
1758
1909
|
case "update": {
|
|
1759
1910
|
if (!rest[0]) {
|
|
@@ -1776,14 +1927,15 @@ async function runWebhook(client, action, rest) {
|
|
|
1776
1927
|
console.error("Usage: ascendkit webhook delete <webhook-id>");
|
|
1777
1928
|
return await exitCli(1);
|
|
1778
1929
|
}
|
|
1779
|
-
|
|
1930
|
+
await webhooks.deleteWebhook(client, rest[0]);
|
|
1931
|
+
console.log("Webhook deleted.");
|
|
1780
1932
|
break;
|
|
1781
1933
|
case "test":
|
|
1782
1934
|
if (!rest[0]) {
|
|
1783
1935
|
console.error("Usage: ascendkit webhook test <webhook-id> [--event <event-type>]");
|
|
1784
1936
|
return await exitCli(1);
|
|
1785
1937
|
}
|
|
1786
|
-
|
|
1938
|
+
printWebhookTestResult(await webhooks.testWebhook(client, rest[0], flags.event));
|
|
1787
1939
|
break;
|
|
1788
1940
|
default:
|
|
1789
1941
|
console.error(`Unknown webhook command: ${action}`);
|
|
@@ -1800,7 +1952,7 @@ async function runCampaign(client, action, rest) {
|
|
|
1800
1952
|
switch (action) {
|
|
1801
1953
|
case "create": {
|
|
1802
1954
|
if (!flags.name || !flags.template || !flags.audience) {
|
|
1803
|
-
console.error("Usage: ascendkit campaign create --name <name> --template <template-id> --audience <json> [--scheduled-at <datetime>]");
|
|
1955
|
+
console.error("Usage: ascendkit campaign create --name <name> --template <template-id> --audience <json> [--from <email>] [--scheduled-at <datetime>]");
|
|
1804
1956
|
return await exitCli(1);
|
|
1805
1957
|
}
|
|
1806
1958
|
let createFilter;
|
|
@@ -1811,10 +1963,11 @@ async function runCampaign(client, action, rest) {
|
|
|
1811
1963
|
console.error("Invalid JSON for --audience flag. Provide a valid JSON object.");
|
|
1812
1964
|
return await exitCli(1);
|
|
1813
1965
|
}
|
|
1814
|
-
|
|
1966
|
+
printCampaignSummary(await campaigns.createCampaign(client, {
|
|
1815
1967
|
name: flags.name,
|
|
1816
1968
|
templateId: flags.template,
|
|
1817
1969
|
audienceFilter: createFilter,
|
|
1970
|
+
fromIdentityEmail: flags.from,
|
|
1818
1971
|
scheduledAt: flags["scheduled-at"],
|
|
1819
1972
|
}));
|
|
1820
1973
|
break;
|
|
@@ -1836,7 +1989,7 @@ async function runCampaign(client, action, rest) {
|
|
|
1836
1989
|
console.error("Usage: ascendkit campaign show <campaign-id>");
|
|
1837
1990
|
return await exitCli(1);
|
|
1838
1991
|
}
|
|
1839
|
-
|
|
1992
|
+
printCampaignSummary(await campaigns.getCampaign(client, rest[0]));
|
|
1840
1993
|
break;
|
|
1841
1994
|
case "update": {
|
|
1842
1995
|
if (!rest[0]) {
|
|
@@ -1853,10 +2006,11 @@ async function runCampaign(client, action, rest) {
|
|
|
1853
2006
|
return await exitCli(1);
|
|
1854
2007
|
}
|
|
1855
2008
|
}
|
|
1856
|
-
|
|
2009
|
+
printCampaignSummary(await campaigns.updateCampaign(client, rest[0], {
|
|
1857
2010
|
name: flags.name,
|
|
1858
2011
|
templateId: flags.template,
|
|
1859
2012
|
audienceFilter: updateFilter,
|
|
2013
|
+
fromIdentityEmail: flags.from,
|
|
1860
2014
|
scheduledAt: flags["scheduled-at"],
|
|
1861
2015
|
}));
|
|
1862
2016
|
break;
|
|
@@ -1871,7 +2025,7 @@ async function runCampaign(client, action, rest) {
|
|
|
1871
2025
|
console.error("Campaign has no audience filter set.");
|
|
1872
2026
|
return await exitCli(1);
|
|
1873
2027
|
}
|
|
1874
|
-
|
|
2028
|
+
printAudiencePreview(await campaigns.previewAudience(client, detail.audienceFilter));
|
|
1875
2029
|
break;
|
|
1876
2030
|
}
|
|
1877
2031
|
case "schedule":
|
|
@@ -1879,21 +2033,22 @@ async function runCampaign(client, action, rest) {
|
|
|
1879
2033
|
console.error("Usage: ascendkit campaign schedule <campaign-id> --at <datetime>");
|
|
1880
2034
|
return await exitCli(1);
|
|
1881
2035
|
}
|
|
1882
|
-
|
|
2036
|
+
printCampaignSummary(await campaigns.updateCampaign(client, rest[0], { scheduledAt: flags.at }));
|
|
1883
2037
|
break;
|
|
1884
2038
|
case "cancel":
|
|
1885
2039
|
if (!rest[0]) {
|
|
1886
2040
|
console.error("Usage: ascendkit campaign cancel <campaign-id>");
|
|
1887
2041
|
return await exitCli(1);
|
|
1888
2042
|
}
|
|
1889
|
-
|
|
2043
|
+
await campaigns.deleteCampaign(client, rest[0]);
|
|
2044
|
+
console.log("Campaign cancelled.");
|
|
1890
2045
|
break;
|
|
1891
2046
|
case "analytics":
|
|
1892
2047
|
if (!rest[0]) {
|
|
1893
2048
|
console.error("Usage: ascendkit campaign analytics <campaign-id>");
|
|
1894
2049
|
return await exitCli(1);
|
|
1895
2050
|
}
|
|
1896
|
-
|
|
2051
|
+
printCampaignAnalytics(await campaigns.getCampaignAnalytics(client, rest[0]));
|
|
1897
2052
|
break;
|
|
1898
2053
|
default:
|
|
1899
2054
|
console.error(`Unknown campaign command: ${action}`);
|
|
@@ -1975,6 +2130,19 @@ async function runEmail(client, action, rest) {
|
|
|
1975
2130
|
console.log("Verification email sent.");
|
|
1976
2131
|
break;
|
|
1977
2132
|
}
|
|
2133
|
+
case "update": {
|
|
2134
|
+
const identityEmail = rest[0];
|
|
2135
|
+
if (!identityEmail || !flags["display-name"]) {
|
|
2136
|
+
console.error("Usage: ascendkit email-identity update <email> --display-name <name>");
|
|
2137
|
+
return await exitCli(1);
|
|
2138
|
+
}
|
|
2139
|
+
const updated = await email.updateIdentity(client, {
|
|
2140
|
+
email: identityEmail,
|
|
2141
|
+
displayName: flags["display-name"],
|
|
2142
|
+
});
|
|
2143
|
+
printEmailIdentities(updated.identities ?? []);
|
|
2144
|
+
break;
|
|
2145
|
+
}
|
|
1978
2146
|
case "resend": {
|
|
1979
2147
|
const identityEmail = rest[0];
|
|
1980
2148
|
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;
|