@zanfia/cli 0.3.63 → 0.4.0

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.
Files changed (2) hide show
  1. package/dist/index.js +81 -28
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -8343,6 +8343,9 @@ function createMockClient() {
8343
8343
  async cancelEmailCampaign() {
8344
8344
  throw new ApiError(501, "email campaigns are not available in mock mode");
8345
8345
  },
8346
+ async sendEmailCampaignTest() {
8347
+ throw new ApiError(501, "email campaigns are not available in mock mode");
8348
+ },
8346
8349
  async resendEmailCampaign() {
8347
8350
  throw new ApiError(501, "email campaigns are not available in mock mode");
8348
8351
  },
@@ -9535,6 +9538,11 @@ function createHttpClient(config) {
9535
9538
  scheduledAt !== void 0 ? { scheduledAt } : {}
9536
9539
  ),
9537
9540
  cancelEmailCampaign: (id) => request("POST", `/campaigns/${encodeURIComponent(id)}/cancel`),
9541
+ sendEmailCampaignTest: (id, recipients) => request(
9542
+ "POST",
9543
+ `/campaigns/${encodeURIComponent(id)}/test-sends`,
9544
+ recipients && recipients.length > 0 ? { recipients } : {}
9545
+ ),
9538
9546
  resendEmailCampaign: (id, target) => request("POST", `/campaigns/${encodeURIComponent(id)}/resend`, target ? { target } : {}),
9539
9547
  listCampaignRecipients: (id, options) => {
9540
9548
  const params = new URLSearchParams();
@@ -12299,6 +12307,9 @@ var RECIPIENT_STATUSES = [
12299
12307
  "complained",
12300
12308
  "clicked"
12301
12309
  ];
12310
+ function collectRepeatable(value, previous) {
12311
+ return [...previous, value];
12312
+ }
12302
12313
  function parsePositiveInt2(value, flag) {
12303
12314
  const n = Number.parseInt(value, 10);
12304
12315
  if (!Number.isInteger(n) || n <= 0) {
@@ -12534,6 +12545,25 @@ function registerCampaignsCommands(program2) {
12534
12545
  `);
12535
12546
  });
12536
12547
  });
12548
+ campaigns.command("test").description(
12549
+ "Send a TEST of the saved campaign to up to 3 addresses (default: your own) \u2014 subject prefixed [Test], sample personalization values; never the audience"
12550
+ ).argument("<id>", "campaign id").option(
12551
+ "--to <email>",
12552
+ "test recipient \u2014 repeatable or comma-separated, max 3 (default: your own address)",
12553
+ collectRepeatable,
12554
+ []
12555
+ ).action(async (id, options, command) => {
12556
+ const flags = getGlobalFlags(command);
12557
+ const recipients = options.to.flatMap((entry) => entry.split(",")).map((email) => email.trim()).filter(Boolean);
12558
+ const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
12559
+ const result = await client.sendEmailCampaignTest(id, recipients);
12560
+ emit(flags, result, () => {
12561
+ process.stdout.write(
12562
+ `Test of campaign ${id} queued \u2192 ${result.recipients.join(", ")} (subject prefixed [Test]). Nothing was sent to the audience.
12563
+ `
12564
+ );
12565
+ });
12566
+ });
12537
12567
  campaigns.command("send").description(
12538
12568
  "\u26A0\uFE0F SENDS the campaign \u2014 REAL bulk email to the entire resolved audience, IRREVERSIBLY. Delivered mail cannot be recalled; `campaigns cancel` only stops remaining sends. Requires --confirm. Omit --at to send NOW; --at schedules (max 30 days out)."
12539
12569
  ).argument("<id>", "campaign id").option(
@@ -12543,7 +12573,7 @@ function registerCampaignsCommands(program2) {
12543
12573
  const flags = getGlobalFlags(command);
12544
12574
  if (!options.confirm) {
12545
12575
  throw new Error(
12546
- `Refusing to send without --confirm. \`campaigns send\` emails the ENTIRE resolved audience for real and cannot be undone once mail is delivered. Re-run with --confirm to proceed (check the draft first with \`campaigns get ${id}\`).`
12576
+ `Refusing to send without --confirm. \`campaigns send\` emails the ENTIRE resolved audience for real and cannot be undone once mail is delivered. Re-run with --confirm to proceed (check the draft first with \`campaigns get ${id}\` and \`campaigns test ${id}\`).`
12547
12577
  );
12548
12578
  }
12549
12579
  if (options.at !== void 0 && Number.isNaN(Date.parse(options.at))) {
@@ -13567,7 +13597,7 @@ function parsePositiveInt4(value, flag) {
13567
13597
  }
13568
13598
  return n;
13569
13599
  }
13570
- function collectRepeatable(value, previous) {
13600
+ function collectRepeatable2(value, previous) {
13571
13601
  return [...previous, value];
13572
13602
  }
13573
13603
  function parseCsv(raw) {
@@ -14051,7 +14081,7 @@ Next page: --cursor ${page.nextCursor}
14051
14081
  ).argument("<csvFile>", "path to the CSV file").option(
14052
14082
  "--tag <tagId>",
14053
14083
  "existing tag id applied to every imported row (repeat for several; unknown ids fail)",
14054
- collectRepeatable,
14084
+ collectRepeatable2,
14055
14085
  []
14056
14086
  ).action(async (csvFile, options, command) => {
14057
14087
  const flags = getGlobalFlags(command);
@@ -15969,7 +15999,7 @@ function assertStatus(status) {
15969
15999
  throw new Error(`Invalid status "${status}". Use one of: ${LESSON_STATUSES.join(", ")}`);
15970
16000
  }
15971
16001
  }
15972
- function collectRepeatable2(value, previous) {
16002
+ function collectRepeatable3(value, previous) {
15973
16003
  return [...previous, value];
15974
16004
  }
15975
16005
  function parseNonNegativeInt(value, flag) {
@@ -16577,7 +16607,7 @@ ${NOT_SAVED_NOTE} --moments=${options.out ?? "<file>"} --confirm\`.
16577
16607
  ).argument("<productId>", "course product id").argument("<lessonId>", "quiz lesson id").option("--type <type>", "single | multi | truefalse").option("--question <text>", "the question prompt").option("--explanation <text>", "explanation shown on the results screen").option("--points <n>", "points awarded (default 1)").option(
16578
16608
  "--option <text>",
16579
16609
  "an answer option (repeat for several; choice types only)",
16580
- collectRepeatable2,
16610
+ collectRepeatable3,
16581
16611
  []
16582
16612
  ).option("--correct <indices>", "1-based, comma-separated indices of the correct option(s)").option("--correct-answer <bool>", "true/false questions: the correct answer").option("--json <fileOrJson>", "full QuizQuestionInput object as inline JSON or a .json path").action(
16583
16613
  async (productId, lessonId, options, command) => {
@@ -16593,7 +16623,7 @@ ${NOT_SAVED_NOTE} --moments=${options.out ?? "<file>"} --confirm\`.
16593
16623
  quiz.command("update").description("Replace a question in full (same flags as `quiz add`, or --json)").argument("<productId>", "course product id").argument("<lessonId>", "quiz lesson id").argument("<questionId>", "question id").option("--type <type>", "single | multi | truefalse").option("--question <text>", "the question prompt").option("--explanation <text>", "explanation shown on the results screen").option("--points <n>", "points awarded (default 1)").option(
16594
16624
  "--option <text>",
16595
16625
  "an answer option (repeat for several; choice types only)",
16596
- collectRepeatable2,
16626
+ collectRepeatable3,
16597
16627
  []
16598
16628
  ).option("--correct <indices>", "1-based, comma-separated indices of the correct option(s)").option("--correct-answer <bool>", "true/false questions: the correct answer").option("--json <fileOrJson>", "full QuizQuestionInput object as inline JSON or a .json path").action(
16599
16629
  async (productId, lessonId, questionId, options, command) => {
@@ -16777,7 +16807,7 @@ function parseIsoDate(value, flag) {
16777
16807
  }
16778
16808
  return value;
16779
16809
  }
16780
- function collectRepeatable3(value, previous) {
16810
+ function collectRepeatable4(value, previous) {
16781
16811
  return [...previous, value];
16782
16812
  }
16783
16813
  function registerDiscountsCommands(program2) {
@@ -16802,7 +16832,7 @@ function registerDiscountsCommands(program2) {
16802
16832
  discounts2.command("create").description("Create a discount code (exactly one of --percent-off / --amount-off)").requiredOption("--code <code>", "discount code (e.g. LAUNCH20)").option("--percent-off <pct>", "percentage discount (> 0, <= 100)").option("--amount-off <minorUnits>", "fixed discount in minor units (e.g. 1000 = 10.00)").option("--currency <currency>", "ISO currency code (required with --amount-off)").option(
16803
16833
  "--product <id>",
16804
16834
  "limit to a product (repeat the flag for several)",
16805
- collectRepeatable3,
16835
+ collectRepeatable4,
16806
16836
  []
16807
16837
  ).option("--max-redemptions <n>", "maximum number of redemptions").option("--ends <isoDate>", "date the code stops working (ISO, e.g. 2026-12-31)").option("--starts <isoDate>", "date the code starts working (ISO, e.g. 2026-12-01)").option("--forever", "apply to every renewal of a subscription (default: first payment only)").option("--inactive", "create the code deactivated (default: active)").action(
16808
16838
  async (options, command) => {
@@ -16852,7 +16882,7 @@ function registerDiscountsCommands(program2) {
16852
16882
  discounts2.command("update").description("Update a discount code (value, products, limits, dates, active state)").argument("<discountId>", "discount id").option("--percent-off <pct>", "switch to a percentage discount (> 0, <= 100)").option("--amount-off <minorUnits>", "switch to a fixed discount in minor units").option("--currency <currency>", "ISO currency code for --amount-off").option(
16853
16883
  "--product <id>",
16854
16884
  "replace the product restriction (repeat the flag for several)",
16855
- collectRepeatable3,
16885
+ collectRepeatable4,
16856
16886
  []
16857
16887
  ).option("--max-redemptions <n>", "maximum number of redemptions").option("--clear-max-redemptions", "remove the redemption limit").option("--ends <isoDate>", "date the code stops working (ISO, e.g. 2026-12-31)").option("--clear-ends", "remove the end date").option("--forever", "apply to every renewal of a subscription").option("--active", "activate the code").option("--inactive", "deactivate the code").action(
16858
16888
  async (discountId, options, command) => {
@@ -23043,7 +23073,7 @@ function parseJsonFileOrInline5(value, flag) {
23043
23073
  throw new Error(`Invalid JSON for ${flag}: ${error.message}`);
23044
23074
  }
23045
23075
  }
23046
- function collectRepeatable4(value, previous) {
23076
+ function collectRepeatable5(value, previous) {
23047
23077
  return [...previous, value];
23048
23078
  }
23049
23079
  function registerNewslettersCommands(program2) {
@@ -23374,7 +23404,7 @@ Next: \`newsletters issues draft save ${productId} ${issue.id} --content <markdo
23374
23404
  ).option("--double-opt-in <mode>", `subscription confirmation (${DOUBLE_OPT_INS.join("|")})`).option(
23375
23405
  "--client-tag <tag>",
23376
23406
  "tag applied to new subscribers (repeat the flag; replaces the list)",
23377
- collectRepeatable4,
23407
+ collectRepeatable5,
23378
23408
  []
23379
23409
  ).option(
23380
23410
  "--delivery <mode>",
@@ -25064,6 +25094,22 @@ import { readFile as readFile3 } from "fs/promises";
25064
25094
  import { basename as basename2 } from "path";
25065
25095
  var KB_TREE_CHANGED_ERROR = "tree-changed";
25066
25096
  var JSON_FLAG_HINT3 = "(write it as --json=<value> \u2014 a bare --json is the global JSON-output flag)";
25097
+ var PRODUCT_TYPE_INPUTS = [
25098
+ "downloads",
25099
+ "download",
25100
+ "newsletter",
25101
+ "course",
25102
+ "mailing",
25103
+ "community",
25104
+ "membership",
25105
+ "knowledge_base",
25106
+ "one_time",
25107
+ "custom",
25108
+ "event",
25109
+ "consultation",
25110
+ "service",
25111
+ "bundle"
25112
+ ];
25067
25113
  function parseCoverRef(value) {
25068
25114
  const separator = value.indexOf(":");
25069
25115
  if (separator < 0) {
@@ -25174,7 +25220,7 @@ function parseJsonFileOrInline8(value, flag) {
25174
25220
  throw new Error(`Invalid JSON for ${flag}: ${error.message}`);
25175
25221
  }
25176
25222
  }
25177
- function collectRepeatable5(value, previous) {
25223
+ function collectRepeatable6(value, previous) {
25178
25224
  return [...previous, value];
25179
25225
  }
25180
25226
  var HTTP_URL_PATTERN2 = /^https?:\/\//i;
@@ -25262,7 +25308,10 @@ function registerProductsCommands(program2) {
25262
25308
  });
25263
25309
  products2.command("create").description(
25264
25310
  "Create a new product. Community/newsletter types seed their full content (community: channels doc, built-in roles, owner member). Add --free or --amount to also seed a default offer \u2014 e.g. a paid community: --type community --amount 49.99 --currency usd --period monthly"
25265
- ).requiredOption("--name <name>", "product name").requiredOption("--type <type>", "product type (course, newsletter, community, ...)").option("--description <description>", "product description").option("--free", "seed a free default offer").option("--amount <amount>", "seed a paid default offer \u2014 price in MAJOR units (49.99)").option("--currency <currency>", "currency for --amount (pln, usd, eur, gbp)").option(
25311
+ ).requiredOption("--name <name>", "product name").requiredOption(
25312
+ "--type <type>",
25313
+ `product type: ${PRODUCT_TYPE_INPUTS.join(", ")} (downloads/download are stored as one_time, newsletter as mailing)`
25314
+ ).option("--description <description>", "product description").option("--free", "seed a free default offer").option("--amount <amount>", "seed a paid default offer \u2014 price in MAJOR units (49.99)").option("--currency <currency>", "currency for --amount (pln, usd, eur, gbp)").option(
25266
25315
  "--period <period>",
25267
25316
  "billing period for --amount: one_time, monthly or yearly (default one_time)"
25268
25317
  ).option("--description-file <path>", "read the product description from a file").option(
@@ -25271,7 +25320,7 @@ function registerProductsCommands(program2) {
25271
25320
  ).option(
25272
25321
  "--file <path>",
25273
25322
  "local file to upload and attach to the product (downloads products; repeat for several)",
25274
- collectRepeatable5,
25323
+ collectRepeatable6,
25275
25324
  []
25276
25325
  ).action(
25277
25326
  async (options, command) => {
@@ -25606,7 +25655,7 @@ function registerProductsCommands(program2) {
25606
25655
  ).argument("<productId>", "appointment product id").option("--config <fileOrJson>", "full config object as inline JSON or a path to a .json file").option("--booking-method <method>", `consultation: ${BOOKING_METHODS.join("|")}`).option("--booking-url <url>", "consultation: external scheduler URL").option("--duration <minutes>", "consultation: session length in minutes").option("--location <text>", "consultation location / event join URL or address").option("--location-details <text>", "consultation: extra location details").option("--confirmation-message <text>", "consultation: post-booking confirmation message").option(
25607
25656
  "--deliverable <text>",
25608
25657
  "service: a deliverable line (repeat the flag; replaces the list)",
25609
- collectRepeatable5,
25658
+ collectRepeatable6,
25610
25659
  []
25611
25660
  ).option("--delivery-time <n>", "service: delivery time value").option("--delivery-unit <unit>", `service: ${DELIVERY_UNITS.join("|")} (with --delivery-time)`).option("--revision-rounds <n|unlimited>", 'service: revision rounds count or "unlimited"').option("--brief-method <method>", `service: ${BRIEF_METHODS.join("|")}`).option("--brief-url <url>", "service: external brief/intake form URL").option("--event-mode <mode>", `event: ${EVENT_MODES3.join("|")}`).option("--starts <iso>", "event: start (ISO 8601)").option("--ends <iso>", "event: end (ISO 8601)").option("--capacity <n>", "event: attendee capacity").option("--show-on-success <on|off>", "show the post-purchase CTA on the success page").action(
25612
25661
  async (productId, options, command) => {
@@ -25802,7 +25851,7 @@ function registerProductsCommands(program2) {
25802
25851
  ).argument("<productId>", "product id").requiredOption(
25803
25852
  "--image <url>",
25804
25853
  "an image URL (repeat the flag for several; the set fully replaces the current images)",
25805
- collectRepeatable5,
25854
+ collectRepeatable6,
25806
25855
  []
25807
25856
  ).action(async (productId, options, command) => {
25808
25857
  const flags = getGlobalFlags(command);
@@ -26531,7 +26580,7 @@ Failed deliveries (${result.failedDeliveries.length})
26531
26580
  // src/commands/segments/index.ts
26532
26581
  import { readFileSync as readFileSync24 } from "fs";
26533
26582
  var SUBSCRIPTION_STATUSES2 = ["pending", "subscribed", "unsubscribed"];
26534
- function collectRepeatable6(value, previous) {
26583
+ function collectRepeatable7(value, previous) {
26535
26584
  return [...previous, value];
26536
26585
  }
26537
26586
  function parseMinorUnits(value, flag) {
@@ -26623,42 +26672,42 @@ function withConditionOptions(command) {
26623
26672
  return command.option(
26624
26673
  "--has-tag <tagId>",
26625
26674
  "client carries this tag (repeat for several \u2014 conditions are ANDed)",
26626
- collectRepeatable6,
26675
+ collectRepeatable7,
26627
26676
  []
26628
26677
  ).option(
26629
26678
  "--without-tag <tagId>",
26630
26679
  "client does NOT carry this tag (repeatable)",
26631
- collectRepeatable6,
26680
+ collectRepeatable7,
26632
26681
  []
26633
26682
  ).option(
26634
26683
  "--has-any-tag <tagId>",
26635
26684
  "client carries AT LEAST ONE of these tags (repeat the flag; one OR condition, max 10 ids) - one send to several tags without double-mailing",
26636
- collectRepeatable6,
26685
+ collectRepeatable7,
26637
26686
  []
26638
26687
  ).option(
26639
26688
  "--has-none-tag <tagId>",
26640
26689
  "client carries NONE of these tags (repeat the flag; one condition, max 10 ids)",
26641
- collectRepeatable6,
26690
+ collectRepeatable7,
26642
26691
  []
26643
26692
  ).option(
26644
26693
  "--owns <productId>",
26645
26694
  "client has ACTIVE access to this product - a direct grant OR through a bundle (repeatable)",
26646
- collectRepeatable6,
26695
+ collectRepeatable7,
26647
26696
  []
26648
26697
  ).option(
26649
26698
  "--not-owns <productId>",
26650
26699
  "client has NO active access to this product, direct or through a bundle (repeatable)",
26651
- collectRepeatable6,
26700
+ collectRepeatable7,
26652
26701
  []
26653
26702
  ).option(
26654
26703
  "--owns-direct <productId>",
26655
26704
  "client has an ACTIVE grant on this product itself (bought / granted by hand) - bundle access does not count; use to mail only the buyers of that one product (repeatable)",
26656
- collectRepeatable6,
26705
+ collectRepeatable7,
26657
26706
  []
26658
26707
  ).option(
26659
26708
  "--not-owns-direct <productId>",
26660
26709
  "client has NO active direct grant on this product - bundle holders DO match (repeatable)",
26661
- collectRepeatable6,
26710
+ collectRepeatable7,
26662
26711
  []
26663
26712
  ).option(
26664
26713
  "--spent-gte <minorUnits>",
@@ -29662,7 +29711,11 @@ function describeDelay(config) {
29662
29711
  switch (mode) {
29663
29712
  case void 0:
29664
29713
  case "duration":
29665
- return `wait ${typeof config.amount === "number" ? config.amount : "?"} ${optionalString(config.unit) ?? ""}`.trimEnd();
29714
+ return `wait ${typeof config.amount === "number" ? config.amount : "?"} ${optionalString(config.unit) ?? ""}${optionalString(config.atTime) ? `, then at ${config.atTime}` : ""}`.trimEnd();
29715
+ case "untilTime": {
29716
+ const weekdays = Array.isArray(config.weekdays) ? config.weekdays.join(",") : "";
29717
+ return `wait until the next ${optionalString(config.time) ?? "?"}${weekdays ? ` on weekdays ${weekdays}` : ""}${optionalString(config.timezone) ? ` (${config.timezone})` : ""}`;
29718
+ }
29666
29719
  case "untilWeekday":
29667
29720
  return `wait until weekday ${typeof config.weekday === "number" ? config.weekday : "?"} at ${optionalString(config.time) ?? "?"}${optionalString(config.timezone) ? ` (${config.timezone})` : ""}`;
29668
29721
  case "untilDate":
@@ -29829,8 +29882,8 @@ function applyShortHelp(program2) {
29829
29882
 
29830
29883
  // src/program.ts
29831
29884
  function resolveCliVersion() {
29832
- if ("0.3.63".length > 0) {
29833
- return "0.3.63";
29885
+ if ("0.4.0".length > 0) {
29886
+ return "0.4.0";
29834
29887
  }
29835
29888
  try {
29836
29889
  const raw = readFileSync28(new URL("../package.json", import.meta.url), "utf8");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zanfia/cli",
3
- "version": "0.3.63",
3
+ "version": "0.4.0",
4
4
  "private": false,
5
5
  "description": "Zanfia CLI — thin HTTP client over the Zanfia Public API",
6
6
  "type": "module",