@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 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
- console.log(JSON.stringify(result, null, 2));
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
- output(await content.getVersion(client, templateId, parseInt(versionNumber, 10)));
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
- output(await surveys.distributeSurvey(client, rest[0], flags.users.split(",")));
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
- output(await surveys.listInvitations(client, surveyId));
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
- output(await surveys.getAnalytics(client, rest[0]));
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
- output(await surveys.listQuestions(client, surveyId));
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
- output(await surveys.addQuestion(client, surveyId, params));
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
- output(await surveys.editQuestion(client, surveyId, questionName, params));
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
- output(await surveys.removeQuestion(client, surveyId, questionName));
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
- output(await surveys.reorderQuestions(client, surveyId, flags.order.split(",")));
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
- output(await webhooks.createWebhook(client, {
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
- output(await webhooks.getWebhook(client, rest[0]));
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
- output(await webhooks.deleteWebhook(client, rest[0]));
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
- output(await webhooks.testWebhook(client, rest[0], flags.event));
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
- output(await campaigns.createCampaign(client, {
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
- output(await campaigns.getCampaign(client, rest[0]));
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
- output(await campaigns.updateCampaign(client, rest[0], {
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
- output(await campaigns.previewAudience(client, detail.audienceFilter));
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
- output(await campaigns.updateCampaign(client, rest[0], { scheduledAt: flags.at }));
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
- output(await campaigns.deleteCampaign(client, rest[0]));
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
- output(await campaigns.getCampaignAnalytics(client, rest[0]));
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?: {
@@ -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;
@@ -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
  }
@@ -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): string;
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): 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): 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
- lines.push(` ${from} ${t.to} [${desc}]`);
53
+ const id = t.name ? ` (${t.name})` : "";
54
+ lines.push(` ${from} → ${t.to} [${desc}]${id}`);
54
55
  }
55
56
  }
56
- // Warnings
57
- if (journey.warnings && journey.warnings.length > 0) {
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ascendkit/cli",
3
- "version": "0.3.2",
3
+ "version": "0.3.8",
4
4
  "description": "AscendKit CLI and MCP server",
5
5
  "author": "ascendkit.dev",
6
6
  "license": "MIT",