@letterstory/cli 0.176.90 → 0.176.91
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 +4 -2
- package/lib/commands/admin.mjs +43 -6
- package/package.json +1 -1
package/lib/cli.mjs
CHANGED
|
@@ -86,7 +86,7 @@ import {
|
|
|
86
86
|
import { fetchParkedSections, withoutParked } from "./parked.mjs";
|
|
87
87
|
|
|
88
88
|
// Matches cli/package.json; publish-cli.yml rewrites both to the published version.
|
|
89
|
-
export const VERSION = "0.176.
|
|
89
|
+
export const VERSION = "0.176.91";
|
|
90
90
|
|
|
91
91
|
/** The exit code for a gate refusal (CARD_REQUIRED, PAYMENT_FAILED, OUT_OF_TOKENS): 1 is a plain error. */
|
|
92
92
|
export const EXIT_PAYMENT_REQUIRED = 3;
|
|
@@ -1276,7 +1276,7 @@ Admin (STAFF key — --staff-key / LETTERSTORY_STAFF_API_KEY; acts before an org
|
|
|
1276
1276
|
admin billing free exposure | backfill --why "…" [--apply]
|
|
1277
1277
|
Who the free paywall would stop today; the one-time
|
|
1278
1278
|
signup grant for self-serve orgs with none
|
|
1279
|
-
admin billing free grant <org-id> <tokens> --why "…" [--key k] | grants [--org <org-id>] | revoke <org-id> <ref> --why "…"
|
|
1279
|
+
admin billing free grant <org-id|name> <tokens> --why "…" [--key k] | grants [--org <org-id>] | revoke <org-id> <ref> --why "…"
|
|
1280
1280
|
Give an org free tokens (a Lago org: a one-off wallet),
|
|
1281
1281
|
list staff grants, take one back; posted to #billing
|
|
1282
1282
|
admin billing desk [--card-forms-since ISO] [--json] The Billing desk: switches, failed payments, the
|
|
@@ -1286,6 +1286,8 @@ Admin (STAFF key — --staff-key / LETTERSTORY_STAFF_API_KEY; acts before an org
|
|
|
1286
1286
|
error codes, one verdict. Read only.
|
|
1287
1287
|
admin billing free preflight [--json] Who the paywall stops the moment it is enforced:
|
|
1288
1288
|
out of tokens, low, paying but covered, undecided
|
|
1289
|
+
admin billing org find <name> [--limit n] Orgs whose name holds the words, with each one's id and
|
|
1290
|
+
how it is billed (free, lago, legacy, …). Read only.
|
|
1289
1291
|
admin billing org test <org-id> --why "…" [--off] Mark an org a test org (never gated or billed), or with
|
|
1290
1292
|
--off not one; kept with who and why. Not for a payer.
|
|
1291
1293
|
admin billing emails [--bounced] [--org <org-id>] [--limit N]
|
package/lib/commands/admin.mjs
CHANGED
|
@@ -6131,7 +6131,7 @@ async function cmdAdminBillingLegacyUnmark(ctx) {
|
|
|
6131
6131
|
const FREE_USAGE = `Usage: admin billing free exposure [--json] who the free paywall covers, and who is unassigned
|
|
6132
6132
|
admin billing free backfill --why "…" [--apply] signup grant for self-serve orgs with none; a dry run unless --apply
|
|
6133
6133
|
admin billing free repair-subjects --why "…" [--apply] staff-address signup subjects fixed, refused orgs granted (R1); a dry run unless --apply
|
|
6134
|
-
admin billing free grant <org-id> <tokens> --why "…" [--key k] give an org free tokens (a Lago org: a one-off wallet); the same key is one grant
|
|
6134
|
+
admin billing free grant <org-id|name> <tokens> --why "…" [--key k] give an org free tokens (a Lago org: a one-off wallet); the same key is one grant
|
|
6135
6135
|
admin billing free grants [--org <org-id>] [--limit n] staff grants, newest first, live and revoked
|
|
6136
6136
|
admin billing free revoke <org-id> <ref> --why "…" take a staff grant back (ref from the list: grant:<id> or wallet:staff_…)
|
|
6137
6137
|
admin billing free preflight [--json] who the paywall stops the moment it is enforced, and whether it is ready`;
|
|
@@ -6237,12 +6237,13 @@ async function cmdAdminBillingFree(ctx) {
|
|
|
6237
6237
|
return 0;
|
|
6238
6238
|
}
|
|
6239
6239
|
if (positionals[1] === "grant") {
|
|
6240
|
-
const [
|
|
6240
|
+
const [orgArg, tokensArg] = [positionals[2], positionals[3]];
|
|
6241
6241
|
const tokens = Number(String(tokensArg ?? "").replace(/[,_]/g, ""));
|
|
6242
|
-
if (!
|
|
6242
|
+
if (!orgArg || !Number.isInteger(tokens) || tokens <= 0) throw new CliError(FREE_USAGE);
|
|
6243
6243
|
const why = flagStr(flags.why);
|
|
6244
6244
|
if (!why) throw new CliError(`Say why (--why "…"): it is kept with the grant.\n${FREE_USAGE}`);
|
|
6245
6245
|
const key = flagStr(flags.key);
|
|
6246
|
+
const { orgId, name } = await resolveBillingOrg(ctx, orgArg);
|
|
6246
6247
|
const r = await staffPost(ctx, "/api/superadmin/billing/free-tokens/grants", {
|
|
6247
6248
|
orgId,
|
|
6248
6249
|
tokens,
|
|
@@ -6260,7 +6261,7 @@ async function cmdAdminBillingFree(ctx) {
|
|
|
6260
6261
|
const what = x.kind === "wallet" ? "as a one-off wallet on Lago" : "as a free-token grant";
|
|
6261
6262
|
const lines = [
|
|
6262
6263
|
x.granted
|
|
6263
|
-
? `Gave ${x.tokens.toLocaleString("en-US")} tokens ${what} (${x.ref}).`
|
|
6264
|
+
? `Gave ${name ? `${name} ` : ""}${x.tokens.toLocaleString("en-US")} tokens ${what} (${x.ref}).`
|
|
6264
6265
|
: `Already given under this key (${x.ref}); nothing new.`,
|
|
6265
6266
|
];
|
|
6266
6267
|
if (x.standing.before !== x.standing.after)
|
|
@@ -6441,13 +6442,49 @@ async function cmdAdminBillingEmails(ctx) {
|
|
|
6441
6442
|
return 0;
|
|
6442
6443
|
}
|
|
6443
6444
|
|
|
6444
|
-
const ORG_TEST_USAGE = `Usage: admin billing org
|
|
6445
|
+
const ORG_TEST_USAGE = `Usage: admin billing org find <name> [--limit n]
|
|
6446
|
+
Orgs whose name holds the words (or the org with that id), each with its id and how it is billed.
|
|
6447
|
+
admin billing org test <org-id> --why "…" [--off]
|
|
6445
6448
|
Mark an org a test org (never gated or billed, out of the fleet's customer numbers), or with --off not
|
|
6446
6449
|
one any more. Kept with who and why, posted to #billing. Refused for an org that pays us. Billing admins.`;
|
|
6447
6450
|
|
|
6448
|
-
/**
|
|
6451
|
+
/** GET /api/superadmin/billing/orgs: orgs by name (or id), each with how it is billed (lib/billing/find-orgs.ts). */
|
|
6452
|
+
async function findBillingOrgs(ctx, q, limit) {
|
|
6453
|
+
const qs = new URLSearchParams({ q, ...(limit ? { limit } : {}) });
|
|
6454
|
+
return staffRequest(ctx, "GET", `/api/superadmin/billing/orgs?${qs}`);
|
|
6455
|
+
}
|
|
6456
|
+
|
|
6457
|
+
const orgLine = (o) => ` ${o.orgId} ${o.name} (${o.standing}, made ${o.createdAt.slice(0, 10)})`;
|
|
6458
|
+
|
|
6459
|
+
/**
|
|
6460
|
+
* An org named by its id or by its name: the id as given, or the one org the name finds (the exact name when
|
|
6461
|
+
* several hold it). Anything else is refused with the orgs it found, so nothing is done to a guessed org.
|
|
6462
|
+
*/
|
|
6463
|
+
async function resolveBillingOrg(ctx, words) {
|
|
6464
|
+
if (UUID_PATTERN.test(words)) return { orgId: words, name: null };
|
|
6465
|
+
const { orgs } = await findBillingOrgs(ctx, words);
|
|
6466
|
+
const exact = orgs.filter((o) => o.name.toLowerCase() === words.trim().toLowerCase());
|
|
6467
|
+
const one = exact.length === 1 ? exact[0] : orgs.length === 1 ? orgs[0] : null;
|
|
6468
|
+
if (one) return { orgId: one.orgId, name: one.name };
|
|
6469
|
+
if (!orgs.length) throw new CliError(`No org's name holds "${words}".`);
|
|
6470
|
+
throw new CliError(`"${words}" could be any of these; give the org's id:\n${orgs.map(orgLine).join("\n")}`);
|
|
6471
|
+
}
|
|
6472
|
+
|
|
6473
|
+
/**
|
|
6474
|
+
* admin billing org find <name> — findBillingOrgs; admin billing org test <org-id> --why "…" [--off] —
|
|
6475
|
+
* setOrgTest (lib/billing/org-test.ts).
|
|
6476
|
+
*/
|
|
6449
6477
|
async function cmdAdminBillingOrg(ctx) {
|
|
6450
6478
|
const { flags, positionals, io } = ctx;
|
|
6479
|
+
if (positionals[1] === "find") {
|
|
6480
|
+
const q = positionals.slice(2).join(" ").trim();
|
|
6481
|
+
if (!q) throw new CliError(ORG_TEST_USAGE);
|
|
6482
|
+
const r = await findBillingOrgs(ctx, q, flagStr(flags.limit));
|
|
6483
|
+
printResult(io, flags, r, (x) =>
|
|
6484
|
+
x.orgs.length ? x.orgs.map(orgLine).join("\n") : `No org's name holds "${q}".`
|
|
6485
|
+
);
|
|
6486
|
+
return 0;
|
|
6487
|
+
}
|
|
6451
6488
|
const orgId = positionals[2];
|
|
6452
6489
|
if (positionals[1] !== "test" || !orgId) throw new CliError(ORG_TEST_USAGE);
|
|
6453
6490
|
const why = flagStr(flags.why);
|