@letterstory/cli 0.176.76 → 0.176.77

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/lib/cli.mjs CHANGED
@@ -85,7 +85,7 @@ import {
85
85
  import { fetchParkedSections, withoutParked } from "./parked.mjs";
86
86
 
87
87
  // Matches cli/package.json; publish-cli.yml rewrites both to the published version.
88
- export const VERSION = "0.176.76";
88
+ export const VERSION = "0.176.77";
89
89
 
90
90
  /** The exit code for a gate refusal (CARD_REQUIRED, PAYMENT_FAILED, OUT_OF_TOKENS): 1 is a plain error. */
91
91
  export const EXIT_PAYMENT_REQUIRED = 3;
@@ -1253,8 +1253,11 @@ Admin (STAFF key — --staff-key / LETTERSTORY_STAFF_API_KEY; acts before an org
1253
1253
  admin billing free grant <org-id> <tokens> --why "…" [--key k] | grants [--org <org-id>] | revoke <org-id> <ref> --why "…"
1254
1254
  Give an org free tokens (a Lago org: a one-off wallet),
1255
1255
  list staff grants, take one back; posted to #billing
1256
- admin billing desk [--json] The Billing desk: switches, failed payments, the
1257
- nightly reconcile, refusals, signups, grants
1256
+ admin billing desk [--card-forms-since ISO] [--json] The Billing desk: switches, failed payments, the
1257
+ nightly reconcile, refusals, signups, card forms, grants
1258
+ admin billing card-forms [--since ISO] [--json] Where each card form stopped (default the last 7
1259
+ days): the browser's last step, Stripe's status and
1260
+ error codes, one verdict. Read only.
1258
1261
  admin billing free preflight [--json] Who the paywall stops the moment it is enforced:
1259
1262
  out of tokens, low, paying but covered, undecided
1260
1263
  admin billing org test <org-id> --why "…" [--off] Mark an org a test org (never gated or billed), or with
@@ -6013,6 +6013,7 @@ async function cmdAdminRecipes(ctx) {
6013
6013
 
6014
6014
  const LAGO = "/api/superadmin/billing/lago";
6015
6015
  const LAGO_USAGE = `Usage: admin billing desk [--json] the Billing desk: switches, failed payments, reconcile, refusals, grants
6016
+ admin billing card-forms [--since ISO] where each card form stopped: browser step, Stripe's status, verdict
6016
6017
  admin billing org test <org-id> --why "…" [--off] mark an org a test org (or not), kept with who and why
6017
6018
  admin billing lago <command>
6018
6019
  catalog [--apply] [--include-drafts] what the plan catalog would change in Lago; --apply makes it so
@@ -6299,14 +6300,33 @@ async function cmdAdminBillingFree(ctx) {
6299
6300
  throw new CliError(FREE_USAGE);
6300
6301
  }
6301
6302
 
6303
+ /** The desk's GET, with the card forms' window when one is named (--since / --card-forms-since). */
6304
+ function deskPath(since) {
6305
+ return `/api/superadmin/billing/desk${since ? `?${new URLSearchParams({ card_forms_since: since })}` : ""}`;
6306
+ }
6307
+
6302
6308
  /**
6303
- * admin billing desk [--json] — the Billing desk in one read (lib/billing/desk.ts, docs/billing/billing-desk.md):
6304
- * the switches, what needs a person now, and the week's numbers. The same as GET /api/superadmin/billing/desk
6305
- * and MCP billing_desk.
6309
+ * Where each card form stopped (the desk's signups.cardForms, lib/billing/lago/card-form.ts): one line per
6310
+ * signup with its verdict, the browser's last step and Stripe's status.
6311
+ */
6312
+ function cardFormLines(d) {
6313
+ const forms = d.signups.cardForms ?? [];
6314
+ const lines = [`Card forms since ${String(d.signups.cardFormsSince ?? "").slice(0, 10)}: ${forms.length}`];
6315
+ for (const c of forms)
6316
+ lines.push(
6317
+ ` ${c.orgName ?? c.orgId.slice(0, 8)} ${c.planCode} ${c.status} ${c.words} (browser: ${c.browser.lastStep ?? "nothing"}, Stripe: ${c.stripe ? (c.stripe.status ?? "no setup") : "not read yet"})${c.alertedAt ? " #billing told" : ""}`
6318
+ );
6319
+ return lines;
6320
+ }
6321
+
6322
+ /**
6323
+ * admin billing desk [--card-forms-since ISO] [--json] — the Billing desk in one read (lib/billing/desk.ts,
6324
+ * docs/billing/billing-desk.md): the switches, what needs a person now, and the week's numbers. The same as
6325
+ * GET /api/superadmin/billing/desk and MCP billing_desk.
6306
6326
  */
6307
6327
  async function cmdAdminBillingDesk(ctx) {
6308
6328
  const { flags, io } = ctx;
6309
- const r = await staffRequest(ctx, "GET", "/api/superadmin/billing/desk");
6329
+ const r = await staffRequest(ctx, "GET", deskPath(flagStr(flags["card-forms-since"])));
6310
6330
  printResult(io, flags, r, (d) => {
6311
6331
  const n = (x) => Number(x ?? 0).toLocaleString("en-US");
6312
6332
  const sw = d.switches;
@@ -6336,8 +6356,9 @@ async function cmdAdminBillingDesk(ctx) {
6336
6356
  if (d.signups.stuck.length) lines.push(`Stuck signups: ${d.signups.stuck.length}`);
6337
6357
  for (const s of d.signups.stuck)
6338
6358
  lines.push(
6339
- ` ${s.orgName ?? s.orgId.slice(0, 8)} ${s.planCode} ${s.status}${s.lastError ? `: ${s.lastError}` : ""}`
6359
+ ` ${s.orgName ?? s.orgId.slice(0, 8)} ${s.planCode} ${s.status}${s.lastError ? `: ${s.lastError}` : ""}${s.cardForm ? ` (card form: ${s.cardForm})` : ""}`
6340
6360
  );
6361
+ lines.push(...cardFormLines(d));
6341
6362
  const refusalWords = Object.entries(d.refusals.byReason)
6342
6363
  .map(([k, c]) => `${c} ${k}`)
6343
6364
  .join(", ");
@@ -6361,6 +6382,22 @@ async function cmdAdminBillingDesk(ctx) {
6361
6382
  return 0;
6362
6383
  }
6363
6384
 
6385
+ const CARD_FORMS_USAGE = `Usage: admin billing card-forms [--since 2026-11-08 | 2026-11-08T09:30:00Z] [--json]
6386
+ where each card form started since --since (a date is 00:00 UTC; default the last 7 days) stopped: the browser's last step,
6387
+ Stripe's SetupIntent status and error codes, and one verdict ("opened, never submitted",
6388
+ "submitted, Stripe refused: card_declined / insufficient_funds"). The desk's card forms, alone. Read only.`;
6389
+
6390
+ /** admin billing card-forms [--since ISO] — the desk's card forms alone (lib/billing/lago/card-form.ts). */
6391
+ async function cmdAdminBillingCardForms(ctx) {
6392
+ const { flags, positionals, io } = ctx;
6393
+ if (positionals.length > 1) throw new CliError(CARD_FORMS_USAGE);
6394
+ const r = await staffRequest(ctx, "GET", deskPath(flagStr(flags.since)));
6395
+ printResult(io, flags, { cardFormsSince: r.signups.cardFormsSince, cardForms: r.signups.cardForms }, () =>
6396
+ cardFormLines(r).join("\n")
6397
+ );
6398
+ return 0;
6399
+ }
6400
+
6364
6401
  const EMAILS_USAGE = `Usage: admin billing emails [--bounced] [--org <org-id>] [--limit N]
6365
6402
  the invoice and receipt emails we sent for Lago and what became of each, bounced first
6366
6403
  (--bounced: only the ones that bounced, were marked as spam or failed)`;
@@ -6420,6 +6457,7 @@ async function cmdAdminBilling(ctx) {
6420
6457
  if (positionals[0] === "legacy") return cmdAdminBillingLegacy(ctx);
6421
6458
  if (positionals[0] === "free") return cmdAdminBillingFree(ctx);
6422
6459
  if (positionals[0] === "desk") return cmdAdminBillingDesk(ctx);
6460
+ if (positionals[0] === "card-forms") return cmdAdminBillingCardForms(ctx);
6423
6461
  if (positionals[0] === "emails") return cmdAdminBillingEmails(ctx);
6424
6462
  if (positionals[0] === "org") return cmdAdminBillingOrg(ctx);
6425
6463
  if (positionals[0] !== "lago") throw new CliError(LAGO_USAGE);
@@ -6553,7 +6591,7 @@ async function cmdAdminBilling(ctx) {
6553
6591
  ...(r.signups.length
6554
6592
  ? r.signups.map(
6555
6593
  (x) =>
6556
- ` ${x.orgName ?? x.orgId} ${x.planCode}${x.ratio !== 1 ? ` ${x.ratio}x` : ""} ${x.status} ${x.source}${x.lastError ? ` (${x.lastError})` : ""}`
6594
+ ` ${x.orgName ?? x.orgId} ${x.planCode}${x.ratio !== 1 ? ` ${x.ratio}x` : ""} ${x.status} ${x.source}${x.cardForm ? ` ${x.cardForm}` : ""}${x.lastError ? ` (${x.lastError})` : ""}`
6557
6595
  )
6558
6596
  : [" None."]),
6559
6597
  ].join("\n")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letterstory/cli",
3
- "version": "0.176.76",
3
+ "version": "0.176.77",
4
4
  "description": "Spin up and manage Letter phantom blogs from your terminal.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",