@letterstory/cli 0.176.90 → 0.176.92
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 +53 -9
- 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.92";
|
|
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
|
@@ -5172,6 +5172,8 @@ function recipeDetail(r) {
|
|
|
5172
5172
|
`${pad}recipe: ${v.recipe || "—"}`,
|
|
5173
5173
|
`${pad}when to use: ${v.when_to_use || "—"}`,
|
|
5174
5174
|
`${pad}when not to use: ${v.when_not_to_use || "—"}`,
|
|
5175
|
+
// The facts it cannot run without (the fit holds it back in code); unset is the legacy rule until reviewed.
|
|
5176
|
+
`${pad}required inputs: ${v.requires == null ? "not reviewed (the legacy rule)" : v.requires.join(", ") || "none"}`,
|
|
5175
5177
|
];
|
|
5176
5178
|
return [
|
|
5177
5179
|
`${r.name} weight ${r.weight}/10 ${r.id}`,
|
|
@@ -5186,8 +5188,13 @@ function recipeDetail(r) {
|
|
|
5186
5188
|
` its prompt: ${r.automation.prompt}`,
|
|
5187
5189
|
]
|
|
5188
5190
|
: []),
|
|
5189
|
-
...(r.
|
|
5190
|
-
|
|
5191
|
+
...(r.tool_access
|
|
5192
|
+
? [
|
|
5193
|
+
` letter's tools: its build needs ${r.tool_access.required?.join(", ") || "none named"}; may write ${r.tool_access.writes?.join(", ") || "nothing"} (tool_access)`,
|
|
5194
|
+
]
|
|
5195
|
+
: r.primitives_access
|
|
5196
|
+
? [" letter's tools: its build may read the account through them (primitives_access)"]
|
|
5197
|
+
: []),
|
|
5191
5198
|
...(variations.length > 1
|
|
5192
5199
|
? variations.flatMap((v, i) => [` variation ${i + 1}: ${v.name}`, ...fields(v, " ")])
|
|
5193
5200
|
: fields(r, " ")),
|
|
@@ -5240,7 +5247,7 @@ function recipeFromFlags(flags) {
|
|
|
5240
5247
|
// --primitives-access true hands a start's build Letter's own tools; false takes them away.
|
|
5241
5248
|
primitives_access:
|
|
5242
5249
|
flags["primitives-access"] === undefined ? undefined : String(flags["primitives-access"]) === "true",
|
|
5243
|
-
// --immediate
|
|
5250
|
+
// --immediate is accepted for old scripts and read by nothing: a quick row is one whose runs is empty.
|
|
5244
5251
|
immediate: flags.immediate === undefined ? undefined : String(flags.immediate) === "true",
|
|
5245
5252
|
// --reads llms_txt,github declares the facts its conditions rest on; --reads "" clears them.
|
|
5246
5253
|
reads: flagStr(flags.reads)
|
|
@@ -6131,7 +6138,7 @@ async function cmdAdminBillingLegacyUnmark(ctx) {
|
|
|
6131
6138
|
const FREE_USAGE = `Usage: admin billing free exposure [--json] who the free paywall covers, and who is unassigned
|
|
6132
6139
|
admin billing free backfill --why "…" [--apply] signup grant for self-serve orgs with none; a dry run unless --apply
|
|
6133
6140
|
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
|
|
6141
|
+
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
6142
|
admin billing free grants [--org <org-id>] [--limit n] staff grants, newest first, live and revoked
|
|
6136
6143
|
admin billing free revoke <org-id> <ref> --why "…" take a staff grant back (ref from the list: grant:<id> or wallet:staff_…)
|
|
6137
6144
|
admin billing free preflight [--json] who the paywall stops the moment it is enforced, and whether it is ready`;
|
|
@@ -6237,12 +6244,13 @@ async function cmdAdminBillingFree(ctx) {
|
|
|
6237
6244
|
return 0;
|
|
6238
6245
|
}
|
|
6239
6246
|
if (positionals[1] === "grant") {
|
|
6240
|
-
const [
|
|
6247
|
+
const [orgArg, tokensArg] = [positionals[2], positionals[3]];
|
|
6241
6248
|
const tokens = Number(String(tokensArg ?? "").replace(/[,_]/g, ""));
|
|
6242
|
-
if (!
|
|
6249
|
+
if (!orgArg || !Number.isInteger(tokens) || tokens <= 0) throw new CliError(FREE_USAGE);
|
|
6243
6250
|
const why = flagStr(flags.why);
|
|
6244
6251
|
if (!why) throw new CliError(`Say why (--why "…"): it is kept with the grant.\n${FREE_USAGE}`);
|
|
6245
6252
|
const key = flagStr(flags.key);
|
|
6253
|
+
const { orgId, name } = await resolveBillingOrg(ctx, orgArg);
|
|
6246
6254
|
const r = await staffPost(ctx, "/api/superadmin/billing/free-tokens/grants", {
|
|
6247
6255
|
orgId,
|
|
6248
6256
|
tokens,
|
|
@@ -6260,7 +6268,7 @@ async function cmdAdminBillingFree(ctx) {
|
|
|
6260
6268
|
const what = x.kind === "wallet" ? "as a one-off wallet on Lago" : "as a free-token grant";
|
|
6261
6269
|
const lines = [
|
|
6262
6270
|
x.granted
|
|
6263
|
-
? `Gave ${x.tokens.toLocaleString("en-US")} tokens ${what} (${x.ref}).`
|
|
6271
|
+
? `Gave ${name ? `${name} ` : ""}${x.tokens.toLocaleString("en-US")} tokens ${what} (${x.ref}).`
|
|
6264
6272
|
: `Already given under this key (${x.ref}); nothing new.`,
|
|
6265
6273
|
];
|
|
6266
6274
|
if (x.standing.before !== x.standing.after)
|
|
@@ -6441,13 +6449,49 @@ async function cmdAdminBillingEmails(ctx) {
|
|
|
6441
6449
|
return 0;
|
|
6442
6450
|
}
|
|
6443
6451
|
|
|
6444
|
-
const ORG_TEST_USAGE = `Usage: admin billing org
|
|
6452
|
+
const ORG_TEST_USAGE = `Usage: admin billing org find <name> [--limit n]
|
|
6453
|
+
Orgs whose name holds the words (or the org with that id), each with its id and how it is billed.
|
|
6454
|
+
admin billing org test <org-id> --why "…" [--off]
|
|
6445
6455
|
Mark an org a test org (never gated or billed, out of the fleet's customer numbers), or with --off not
|
|
6446
6456
|
one any more. Kept with who and why, posted to #billing. Refused for an org that pays us. Billing admins.`;
|
|
6447
6457
|
|
|
6448
|
-
/**
|
|
6458
|
+
/** GET /api/superadmin/billing/orgs: orgs by name (or id), each with how it is billed (lib/billing/find-orgs.ts). */
|
|
6459
|
+
async function findBillingOrgs(ctx, q, limit) {
|
|
6460
|
+
const qs = new URLSearchParams({ q, ...(limit ? { limit } : {}) });
|
|
6461
|
+
return staffRequest(ctx, "GET", `/api/superadmin/billing/orgs?${qs}`);
|
|
6462
|
+
}
|
|
6463
|
+
|
|
6464
|
+
const orgLine = (o) => ` ${o.orgId} ${o.name} (${o.standing}, made ${o.createdAt.slice(0, 10)})`;
|
|
6465
|
+
|
|
6466
|
+
/**
|
|
6467
|
+
* 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
|
|
6468
|
+
* several hold it). Anything else is refused with the orgs it found, so nothing is done to a guessed org.
|
|
6469
|
+
*/
|
|
6470
|
+
async function resolveBillingOrg(ctx, words) {
|
|
6471
|
+
if (UUID_PATTERN.test(words)) return { orgId: words, name: null };
|
|
6472
|
+
const { orgs } = await findBillingOrgs(ctx, words);
|
|
6473
|
+
const exact = orgs.filter((o) => o.name.toLowerCase() === words.trim().toLowerCase());
|
|
6474
|
+
const one = exact.length === 1 ? exact[0] : orgs.length === 1 ? orgs[0] : null;
|
|
6475
|
+
if (one) return { orgId: one.orgId, name: one.name };
|
|
6476
|
+
if (!orgs.length) throw new CliError(`No org's name holds "${words}".`);
|
|
6477
|
+
throw new CliError(`"${words}" could be any of these; give the org's id:\n${orgs.map(orgLine).join("\n")}`);
|
|
6478
|
+
}
|
|
6479
|
+
|
|
6480
|
+
/**
|
|
6481
|
+
* admin billing org find <name> — findBillingOrgs; admin billing org test <org-id> --why "…" [--off] —
|
|
6482
|
+
* setOrgTest (lib/billing/org-test.ts).
|
|
6483
|
+
*/
|
|
6449
6484
|
async function cmdAdminBillingOrg(ctx) {
|
|
6450
6485
|
const { flags, positionals, io } = ctx;
|
|
6486
|
+
if (positionals[1] === "find") {
|
|
6487
|
+
const q = positionals.slice(2).join(" ").trim();
|
|
6488
|
+
if (!q) throw new CliError(ORG_TEST_USAGE);
|
|
6489
|
+
const r = await findBillingOrgs(ctx, q, flagStr(flags.limit));
|
|
6490
|
+
printResult(io, flags, r, (x) =>
|
|
6491
|
+
x.orgs.length ? x.orgs.map(orgLine).join("\n") : `No org's name holds "${q}".`
|
|
6492
|
+
);
|
|
6493
|
+
return 0;
|
|
6494
|
+
}
|
|
6451
6495
|
const orgId = positionals[2];
|
|
6452
6496
|
if (positionals[1] !== "test" || !orgId) throw new CliError(ORG_TEST_USAGE);
|
|
6453
6497
|
const why = flagStr(flags.why);
|