@zanfia/cli 0.6.4 → 0.9.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 +127 -37
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -5249,9 +5249,9 @@ var require_lodash7 = __commonJS({
5249
5249
  }
5250
5250
  });
5251
5251
 
5252
- // ../../node_modules/combine-errors/node_modules/lodash.uniqby/index.js
5252
+ // ../../node_modules/lodash.uniqby/index.js
5253
5253
  var require_lodash8 = __commonJS({
5254
- "../../node_modules/combine-errors/node_modules/lodash.uniqby/index.js"(exports, module) {
5254
+ "../../node_modules/lodash.uniqby/index.js"(exports, module) {
5255
5255
  "use strict";
5256
5256
  var baseIteratee = require_lodash4();
5257
5257
  var baseUniq = require_lodash7();
@@ -10324,11 +10324,11 @@ var listCertificateTemplatesColumns = ["id", "name", "version", "isDefault", "up
10324
10324
  function registerCertificatesCommands(program2) {
10325
10325
  const certificates = program2.command("certificates");
10326
10326
  certificates.description(
10327
- "Course-completion certificates \u2014 issue for course clients, revoke/reinstate, resend the email, and manage designer templates (`certificates templates \u2026`)"
10327
+ "Course-completion and event-participation certificates \u2014 issue for course or event clients, revoke/reinstate, resend the email, and manage designer templates (`certificates templates \u2026`)"
10328
10328
  );
10329
10329
  certificates.command("list").summary("List certificates").description(
10330
- "Issued course-completion certificates of the workspace, newest first, optionally filtered to one course product (`productId`); `limit` caps the rows (service default applies). Each row is the credential: public `certificateId` (`cert_\u2026`, the id every certificate write takes), recipient name/email, course and issuer names, completion/issued dates, `issuedBy` (manual vs automatic), template id/version, PDF and verification URLs, `status` (active/revoked), `expiresAt` and delivery/engagement signals (email status, views, downloads, LinkedIn add). Also the polling endpoint after issue-certificates \u2014 a newly enqueued client shows up here once its PDF has been rendered."
10331
- ).option("--product <value>", "Course product id \u2014 omit for all courses.").option(
10330
+ "Issued certificates of the workspace (course completion and event participation), newest first, optionally filtered to one product (`productId`); `limit` caps the rows (service default applies). Each row is the credential: public `certificateId` (`cert_\u2026`, the id every certificate write takes), `productType` (`course` / `event`), recipient name/email, product (`courseName`) and issuer names, completion/issued dates, `issuedBy` (manual vs automatic), template id/version, PDF and verification URLs, `status` (active/revoked), `expiresAt` and delivery/engagement signals (email status, views, downloads, LinkedIn add). Also the polling endpoint after issue-certificates \u2014 a newly enqueued client shows up here once its PDF has been rendered."
10331
+ ).option("--product <value>", "Course or event product id \u2014 omit for all products.").option(
10332
10332
  "--limit <n>",
10333
10333
  "Max rows, newest first (max 500; the service default applies when omitted).",
10334
10334
  parseNumberOption("--limit", NumberFormat.Integer)
@@ -10345,17 +10345,17 @@ function registerCertificatesCommands(program2) {
10345
10345
  emit(flags, result ?? {}, () => printTable(rows, listCertificatesColumns));
10346
10346
  });
10347
10347
  certificates.command("issue").summary("Issue certificates (async)").description(
10348
- 'Issues course-completion certificates to the listed clients of ONE course product. The call validates the batch (404 unknown product or `templateId`, 400 when the product is not a course), skips clients who already hold a certificate for it (one certificate per client per course, ever) and enqueues one issuance task per remaining client \u2014 PDF rendering and the "certificate issued" email happen asynchronously, so the response is 202 with `enqueued` / `skippedExistingCertificate` counts, not the certificates. Poll list-certificates (`productId` filter) a little later for the issued rows. A single issuance is this call with one `clientId`. With `onlyCompleted: true` a client only gets a certificate if their course progress is completed (checked per client at issuance time). `clientIds` is capped at 1000 and de-duplicated. Every issuance EMAILS the student \u2014 the emails cannot be recalled.'
10349
- ).argument("<productId>", "Course product id (must be a course).").requiredOption(
10348
+ 'Issues certificates to the listed clients of ONE product: a course (completion certificate) or an event \u2014 webinar, workshop, also one held outside Zanfia (participation certificate, "for taking part in", no attendance check). The call validates the batch (404 unknown product or `templateId`, 400 when the product is neither a course nor an event), skips clients who already hold a certificate for it (one certificate per client per product, ever) and enqueues one issuance task per remaining client \u2014 PDF rendering and the "certificate issued" email happen asynchronously, so the response is 202 with `enqueued` / `skippedExistingCertificate` counts, not the certificates. Poll list-certificates (`productId` filter) a little later for the issued rows. A single issuance is this call with one `clientId`. With `onlyCompleted: true` a client only gets a certificate if their course progress is completed (checked per client at issuance time; courses only, 400 for an event). `clientIds` is capped at 1000 and de-duplicated. Every issuance EMAILS the student \u2014 the emails cannot be recalled.'
10349
+ ).argument("<productId>", "Course or event product id (other product types answer 400).").requiredOption(
10350
10350
  "--client <a,b,...>",
10351
10351
  "Workspace client ids; one id = single issuance. Max 1000, de-duplicated.",
10352
10352
  parseCommaList("--client")
10353
10353
  ).option(
10354
10354
  "--only-completed",
10355
- "Skip clients who have not completed the course (checked per client at issuance time). Default false = issue to everyone listed."
10355
+ "Course products only (400 for an event): skip clients who have not completed the course (checked per client at issuance time). Default false = issue to everyone listed."
10356
10356
  ).option("--no-only-completed", "Send `onlyCompleted: false` explicitly.").option(
10357
10357
  "--template <value>",
10358
- "Explicit template id; omitted = the course template, else the workspace default."
10358
+ "Explicit template id; omitted = the product template, else the workspace default."
10359
10359
  ).option(
10360
10360
  "--reissue-revoked",
10361
10361
  "Re-issue clients whose current certificate is REVOKED (new document in the current template, new number and verification link, the revoked one stays revoked). ACTIVE certificates are still skipped. Default false = any existing certificate skips the client."
@@ -10593,7 +10593,10 @@ function registerBlogCommands(program2) {
10593
10593
  ).option("--content <value>", "The body as Markdown (headings, lists, links, images by URL).").option(
10594
10594
  "--excerpt <value>",
10595
10595
  "Short plain-text summary (list, RSS, meta description); derived from the body on publish when empty."
10596
- ).option("--cover <value>", "Cover image URL (Media Library).").option("--tags <a,b,...>", "Lowercase labels, at most 10.", parseCommaList("--tags")).option("--seo <json|file>", "seo", parseJsonObjectFileOrInline("--seo")).option("--publish", "Publish right away (needs a non-empty body); default false = draft.").option("--no-publish", "Send `publish: false` explicitly.").action(async (options, command) => {
10596
+ ).option("--cover <value>", "Cover image URL (Media Library).").option("--tags <a,b,...>", "Lowercase labels, at most 10.", parseCommaList("--tags")).option("--seo <json|file>", "seo", parseJsonObjectFileOrInline("--seo")).option("--publish", "Publish right away (needs a non-empty body); default false = draft.").option("--no-publish", "Send `publish: false` explicitly.").option(
10597
+ "--published-at <value>",
10598
+ "The post's date, ISO 8601 date (2025-10-16) or date-time, not in the future - e.g. the original date of an imported post. Omitted = the moment of the first publication."
10599
+ ).action(async (options, command) => {
10597
10600
  const flags = getGlobalFlags(command);
10598
10601
  const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
10599
10602
  const params = {};
@@ -10605,7 +10608,8 @@ function registerBlogCommands(program2) {
10605
10608
  coverImageUrl: options.cover,
10606
10609
  tags: options.tags,
10607
10610
  seo: options.seo,
10608
- publish: options.publish
10611
+ publish: options.publish,
10612
+ publishedAt: options.publishedAt
10609
10613
  };
10610
10614
  const result = await client.call("POST", "/blog-posts", params, void 0, payload);
10611
10615
  emit(flags, result ?? {}, () => printJson(result ?? {}));
@@ -10633,6 +10637,9 @@ function registerBlogCommands(program2) {
10633
10637
  "--seo <json|file>",
10634
10638
  "Replaces the whole SEO section.",
10635
10639
  parseJsonObjectFileOrInline("--seo")
10640
+ ).option(
10641
+ "--published-at <value>",
10642
+ "The post's date (ISO 8601, not in the future); null clears it on a draft - a published post keeps one."
10636
10643
  ).action(async (id, options, command) => {
10637
10644
  const flags = getGlobalFlags(command);
10638
10645
  const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
@@ -10646,7 +10653,8 @@ function registerBlogCommands(program2) {
10646
10653
  excerpt: options.excerpt,
10647
10654
  coverImageUrl: options.cover,
10648
10655
  tags: options.tags,
10649
- seo: options.seo
10656
+ seo: options.seo,
10657
+ publishedAt: options.publishedAt
10650
10658
  };
10651
10659
  const result = await client.call("PATCH", "/blog-posts/:postId", params, void 0, payload);
10652
10660
  emit(flags, result ?? {}, () => printJson(result ?? {}));
@@ -13248,6 +13256,11 @@ function buildConsentInput(options) {
13248
13256
  import { readFileSync as readFileSync7, writeFileSync as writeFileSync2 } from "fs";
13249
13257
 
13250
13258
  // src/client/types.ts
13259
+ var GRANT_PAYMENT_METHODS = [
13260
+ "bankTransfer",
13261
+ "cash",
13262
+ "other"
13263
+ ];
13251
13264
  var AIImageAspectRatio = /* @__PURE__ */ ((AIImageAspectRatio2) => {
13252
13265
  AIImageAspectRatio2["Square"] = "1:1";
13253
13266
  AIImageAspectRatio2["Portrait"] = "4:5";
@@ -13776,6 +13789,40 @@ function registerClientTagsCommands(clients2) {
13776
13789
  var IMPORT_CHUNK_SIZE = 100;
13777
13790
  var SCHEDULER_UNAVAILABLE_STATUS = 503;
13778
13791
  var MAX_BULK_FILTERED_PRODUCTS = 10;
13792
+ function parseGrantPayment(options) {
13793
+ const { paid, currency, paidAt, paymentMethod, paymentNote } = options;
13794
+ if (paid === void 0) {
13795
+ if (currency || paidAt || paymentMethod || paymentNote) {
13796
+ throw new Error("--currency, --paid-at, --payment-method and --payment-note need --paid.");
13797
+ }
13798
+ return void 0;
13799
+ }
13800
+ if (!/^\d+([.,]\d{1,2})?$/.test(paid)) {
13801
+ throw new Error(`Invalid --paid amount: ${paid} (expected e.g. 99 or 99.50)`);
13802
+ }
13803
+ const amountCents = Math.round(Number(paid.replace(",", ".")) * 100);
13804
+ if (amountCents <= 0) {
13805
+ throw new Error("--paid must be above 0.");
13806
+ }
13807
+ if (!currency) {
13808
+ throw new Error("--paid needs --currency (pln, eur, usd or gbp).");
13809
+ }
13810
+ if (paidAt !== void 0 && Number.isNaN(Date.parse(paidAt))) {
13811
+ throw new Error(`Invalid --paid-at date: ${paidAt} (expected ISO, e.g. 2026-10-14)`);
13812
+ }
13813
+ if (paymentMethod !== void 0 && !GRANT_PAYMENT_METHODS.includes(paymentMethod)) {
13814
+ throw new Error(
13815
+ `Invalid --payment-method: ${paymentMethod} (one of ${GRANT_PAYMENT_METHODS.join(", ")})`
13816
+ );
13817
+ }
13818
+ return {
13819
+ amountCents,
13820
+ currency: currency.toLowerCase(),
13821
+ ...paidAt !== void 0 ? { paidAt: new Date(paidAt).toISOString() } : {},
13822
+ ...paymentMethod !== void 0 ? { method: paymentMethod } : {},
13823
+ ...paymentNote !== void 0 ? { note: paymentNote } : {}
13824
+ };
13825
+ }
13779
13826
  function requireConfirm4(confirm, what) {
13780
13827
  if (!confirm) {
13781
13828
  throw new Error(`Refusing to ${what} without --confirm. Re-run with --confirm to proceed.`);
@@ -14263,7 +14310,13 @@ Next page: --cursor ${page.nextCursor}
14263
14310
  });
14264
14311
  }
14265
14312
  );
14266
- clients2.command("grant").description("Grant a client access to a product without a purchase").argument("<productId>", "product id").requiredOption("--email <email>", "client email").option("--first-name <name>", "client first name").option("--last-name <name>", "client last name").option("--price <priceId>", "enroll under an existing price without billing").option("--plan <planId>", "plan to enroll in (multi-plan products)").option("--until <date>", "ISO date the access ends (free grants only); omit for lifetime").option("--welcome-email", "send the welcome/sign-in email (default: grant silently)").action(
14313
+ clients2.command("grant").description("Grant a client access to a product without a purchase").argument("<productId>", "product id").requiredOption("--email <email>", "client email").option("--first-name <name>", "client first name").option("--last-name <name>", "client last name").option("--price <priceId>", "enroll under an existing price without billing").option("--plan <planId>", "plan to enroll in (multi-plan products)").option("--until <date>", "ISO date the access ends (free grants only); omit for lifetime").option("--welcome-email", "send the welcome/sign-in email (default: grant silently)").option(
14314
+ "--run-purchase-automations",
14315
+ "also run the product's purchase automations, marked as a grant (default: off)"
14316
+ ).option(
14317
+ "--paid <amount>",
14318
+ "the client already paid outside Zanfia (e.g. 99.50): records one paid order"
14319
+ ).option("--currency <code>", "currency of --paid: pln, eur, usd or gbp").option("--paid-at <date>", "ISO date the client paid (default: now; not in the future)").option("--payment-method <method>", `how they paid: ${GRANT_PAYMENT_METHODS.join(" | ")}`).option("--payment-note <text>", "note on the payment, e.g. the proforma number (max 300)").action(
14267
14320
  async (productId, options, command) => {
14268
14321
  const flags = getGlobalFlags(command);
14269
14322
  if (options.until !== void 0 && Number.isNaN(Date.parse(options.until))) {
@@ -14272,6 +14325,7 @@ Next page: --cursor ${page.nextCursor}
14272
14325
  if (options.until !== void 0 && options.price !== void 0) {
14273
14326
  throw new Error("--until applies to free grants only \u2014 omit --price.");
14274
14327
  }
14328
+ const payment = parseGrantPayment(options);
14275
14329
  const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
14276
14330
  const result = await client.grantClientAccess(productId, {
14277
14331
  email: options.email,
@@ -14280,13 +14334,14 @@ Next page: --cursor ${page.nextCursor}
14280
14334
  priceId: options.price,
14281
14335
  planId: options.plan,
14282
14336
  accessUntil: options.until,
14283
- sendWelcomeEmail: options.welcomeEmail
14337
+ sendWelcomeEmail: options.welcomeEmail,
14338
+ runPurchaseAutomations: options.runPurchaseAutomations,
14339
+ ...payment ? { payment } : {}
14284
14340
  });
14285
14341
  emit(flags, result, () => {
14286
14342
  const kind = result.isFree ? "free" : "paid";
14287
14343
  process.stdout.write(
14288
- `Granted ${options.email} ${kind} access to product ${productId}.
14289
- `
14344
+ `Granted ${options.email} ${kind} access to product ${productId}` + (payment ? " and recorded the payment as a paid order" : "") + ".\n"
14290
14345
  );
14291
14346
  });
14292
14347
  }
@@ -19905,7 +19960,7 @@ var NoopUrlStorage = /* @__PURE__ */ (function() {
19905
19960
  }]);
19906
19961
  })();
19907
19962
 
19908
- // ../../node_modules/tus-js-client/node_modules/js-base64/base64.mjs
19963
+ // ../../node_modules/js-base64/base64.mjs
19909
19964
  var version = "3.7.8";
19910
19965
  var VERSION = version;
19911
19966
  var _hasBuffer = typeof Buffer === "function";
@@ -25028,6 +25083,7 @@ var PAGE_INTENTS = [
25028
25083
  var SOURCE_SITE_MODES = ["rebuild", "inspired"];
25029
25084
  var EVENT_MODES2 = ["live", "on_demand"];
25030
25085
  var OPT_IN_MODES2 = ["single", "double"];
25086
+ var TRACKING_MODES = ["inherit", "selected", "none"];
25031
25087
  var SCHEDULER_UNAVAILABLE_STATUS3 = 503;
25032
25088
  function parseChoice5(value, allowed, flag) {
25033
25089
  const match = allowed.find((candidate) => candidate === value);
@@ -25164,6 +25220,11 @@ function registerPagesCommands(program2) {
25164
25220
  `);
25165
25221
  process.stdout.write(` Cookie banner: ${page.hideCookieBanner ? "hidden" : "shown"}
25166
25222
  `);
25223
+ const trackingIds = page.tracking?.integrationIds ?? [];
25224
+ process.stdout.write(
25225
+ ` Tracking: ${page.tracking?.mode ?? "inherit"}${trackingIds.length > 0 ? ` (${trackingIds.join(", ")})` : ""}
25226
+ `
25227
+ );
25167
25228
  });
25168
25229
  });
25169
25230
  pages.command("create").description("Create a hosted page from an HTML file or inline HTML").requiredOption("--slug <slug>", "public URL slug (served at /page/<slug>)").requiredOption("--title <title>", "page title").option("--file <path>", "path to an .html file to host").option("--html <html>", "inline HTML body (alternative to --file)").option("--publish", "publish immediately instead of saving as draft").action(async (options, command) => {
@@ -25185,17 +25246,33 @@ function registerPagesCommands(program2) {
25185
25246
  `);
25186
25247
  });
25187
25248
  });
25188
- pages.command("update").description("Update a page (slug, title, HTML body, or cookie-banner visibility)").argument("<id>", "page id").option("--slug <slug>", "new slug").option("--title <title>", "new title").option("--file <path>", "path to a new .html file").option("--html <html>", "new inline HTML body").option("--cookie-banner", "show the workspace cookie banner on this page again").option(
25249
+ pages.command("update").description(
25250
+ "Update a page (slug, title, HTML body, cookie-banner visibility, or its tracking tools)"
25251
+ ).argument("<id>", "page id").option("--slug <slug>", "new slug").option("--title <title>", "new title").option("--file <path>", "path to a new .html file").option("--html <html>", "new inline HTML body").option("--cookie-banner", "show the workspace cookie banner on this page again").option(
25189
25252
  "--no-cookie-banner",
25190
25253
  "hide the workspace cookie banner on this page (pixels still wait for consent given elsewhere)"
25254
+ ).option(
25255
+ "--tracking <mode>",
25256
+ "tracking tools on this page: inherit (workspace defaults), selected (--tracking-integrations) or none"
25257
+ ).option(
25258
+ "--tracking-integrations <ids>",
25259
+ "comma-separated tracking integration ids for --tracking selected (one per platform)"
25191
25260
  ).action(async (id, options, command) => {
25192
25261
  const flags = getGlobalFlags(command);
25193
25262
  const html = await resolveHtml2(options);
25263
+ const trackingIds = options.trackingIntegrations?.split(",").map((value) => value.trim()).filter((value) => value.length > 0);
25264
+ const trackingMode = options.tracking !== void 0 ? parseChoice5(options.tracking, TRACKING_MODES, "--tracking") : trackingIds !== void 0 ? "selected" : void 0;
25194
25265
  const input = {
25195
25266
  slug: options.slug,
25196
25267
  title: options.title,
25197
25268
  html,
25198
- ...options.cookieBanner !== void 0 ? { hideCookieBanner: !options.cookieBanner } : {}
25269
+ ...options.cookieBanner !== void 0 ? { hideCookieBanner: !options.cookieBanner } : {},
25270
+ ...trackingMode !== void 0 ? {
25271
+ tracking: {
25272
+ mode: trackingMode,
25273
+ ...trackingIds !== void 0 ? { integrationIds: trackingIds } : {}
25274
+ }
25275
+ } : {}
25199
25276
  };
25200
25277
  const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
25201
25278
  const result = await client.updatePage(id, input);
@@ -28365,7 +28442,8 @@ var CONVERSION_EVENT_STATUSES = [
28365
28442
  "sent",
28366
28443
  "delivered",
28367
28444
  "failed",
28368
- "consent_blocked"
28445
+ "consent_blocked",
28446
+ "test_skipped"
28369
28447
  ];
28370
28448
  var CONVERSION_TRACKING_CLIENTS = [
28371
28449
  "metaConversionApi",
@@ -28468,12 +28546,12 @@ function registerStatsCommands(program2) {
28468
28546
  const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
28469
28547
  const report = await client.statsSales(range);
28470
28548
  emit(flags, report, () => {
28471
- const c = report.primaryCurrency;
28549
+ const c = report.currency;
28472
28550
  process.stdout.write(
28473
28551
  `Sales (${report.period.from} \u2192 ${report.period.to}), currency ${c}
28474
- Net revenue: ${money(report.primaryNetRevenueCents)} ${c}
28552
+ Net revenue: ${money(report.totalRevenueCents)} ${c}
28475
28553
  Transactions: ${report.transactions} Refunds: ${report.refunds}
28476
- AOV: ${money(report.aovCents)} ${c} New subscribers: ${report.totalNewSubscribers}
28554
+ AOV: ${money(report.averageOrderValueCents)} ${c} New subscribers: ${report.newSubscribers}
28477
28555
 
28478
28556
  `
28479
28557
  );
@@ -28483,7 +28561,7 @@ function registerStatsCommands(program2) {
28483
28561
  report.perProduct.map((r) => ({
28484
28562
  id: r.id,
28485
28563
  name: r.name,
28486
- revenue: money(r.primaryRevenueCents),
28564
+ revenue: money(r.revenueCents),
28487
28565
  transactions: r.transactions,
28488
28566
  refunds: r.refunds
28489
28567
  })),
@@ -28497,11 +28575,11 @@ function registerStatsCommands(program2) {
28497
28575
  const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
28498
28576
  const report = await client.statsRecurring(options.currency);
28499
28577
  emit(flags, report, () => {
28500
- const c = report.primaryCurrency;
28578
+ const c = report.currency;
28501
28579
  process.stdout.write(
28502
28580
  `Recurring revenue (currency ${c})
28503
- MRR: ${money(report.primaryMrrCents)} ${c} ARR: ${money(report.primaryArrCents)} ${c}
28504
- Active subscriptions: ${report.activeRecurringSubscriptions} Ending soon: ${report.endingSoonCount}
28581
+ MRR: ${money(report.mrrCents)} ${c} ARR: ${money(report.arrCents)} ${c}
28582
+ Active subscriptions: ${report.activeSubscriptions} Ending soon: ${report.endingSoonCount}
28505
28583
 
28506
28584
  `
28507
28585
  );
@@ -28510,8 +28588,8 @@ function registerStatsCommands(program2) {
28510
28588
  report.perProduct.map((r) => ({
28511
28589
  id: r.productId,
28512
28590
  name: r.productName,
28513
- mrr: money(r.primaryMrrCents),
28514
- subs: r.activeRecurringSubscriptions
28591
+ mrr: money(r.mrrCents),
28592
+ subs: r.activeSubscriptions
28515
28593
  })),
28516
28594
  ["id", "name", "mrr", "subs"]
28517
28595
  );
@@ -28526,13 +28604,13 @@ function registerStatsCommands(program2) {
28526
28604
  const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
28527
28605
  const report = await client.statsMrrHistory(range);
28528
28606
  emit(flags, report, () => {
28529
- process.stdout.write(`MRR history (currency ${report.primaryCurrency})
28607
+ process.stdout.write(`MRR history (currency ${report.currency})
28530
28608
  `);
28531
28609
  printTable(
28532
28610
  report.points.map((p) => ({
28533
28611
  date: p.date,
28534
28612
  mrr: money(p.mrrCents),
28535
- subs: p.activeRecurringSubscriptions,
28613
+ subs: p.activeSubscriptions,
28536
28614
  new: money(p.newMrrCents),
28537
28615
  churned: money(p.churnedMrrCents),
28538
28616
  churnRate: `${(p.churnRate * 100).toFixed(1)}%`
@@ -28612,11 +28690,23 @@ function registerStatsCommands(program2) {
28612
28690
  checkout: s.checkoutId,
28613
28691
  stage: s.furthestStage,
28614
28692
  status: s.status,
28615
- visitor: s.visitorLabel,
28616
- source: s.source ?? "",
28693
+ converted: s.converted ? "yes" : "",
28694
+ source: s.source.utmSource ?? s.source.referrer ?? "",
28695
+ country: s.country ?? "",
28696
+ declined: s.paymentFailure?.reason ?? "",
28617
28697
  startedAt: s.startedAt
28618
28698
  })),
28619
- ["id", "checkout", "stage", "status", "visitor", "source", "startedAt"]
28699
+ [
28700
+ "id",
28701
+ "checkout",
28702
+ "stage",
28703
+ "status",
28704
+ "converted",
28705
+ "source",
28706
+ "country",
28707
+ "declined",
28708
+ "startedAt"
28709
+ ]
28620
28710
  );
28621
28711
  });
28622
28712
  });
@@ -28625,7 +28715,7 @@ function registerStatsCommands(program2) {
28625
28715
  const client = createApiClient({ apiKey: flags.apiKey, verbose: flags.verbose });
28626
28716
  const report = await client.statsProduct(productId, options.currency);
28627
28717
  emit(flags, report, () => {
28628
- const c = report.primaryCurrency;
28718
+ const c = report.currency;
28629
28719
  process.stdout.write(
28630
28720
  `Product ${report.productId} (${report.window.startDate} \u2192 ${report.window.endDate}), currency ${c}
28631
28721
  Revenue: ${money(report.revenue.totalCents)} ${c} (prev ${money(report.revenue.previousTotalCents)} ${c})
@@ -30286,8 +30376,8 @@ function applyShortHelp(program2) {
30286
30376
 
30287
30377
  // src/program.ts
30288
30378
  function resolveCliVersion() {
30289
- if ("0.6.4".length > 0) {
30290
- return "0.6.4";
30379
+ if ("0.9.0".length > 0) {
30380
+ return "0.9.0";
30291
30381
  }
30292
30382
  try {
30293
30383
  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.6.4",
3
+ "version": "0.9.0",
4
4
  "private": false,
5
5
  "description": "Zanfia CLI — thin HTTP client over the Zanfia Public API",
6
6
  "type": "module",