@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 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.90";
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]
@@ -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.primitives_access ? [" letter's tools: its build may use them (primitives_access)"] : []),
5190
- ...(r.immediate ? [" immediate: a quick win: the Plan puts one in every top three"] : []),
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 true marks a quick win (live within a day): the Plan puts one in every top three.
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 [orgId, tokensArg] = [positionals[2], positionals[3]];
6247
+ const [orgArg, tokensArg] = [positionals[2], positionals[3]];
6241
6248
  const tokens = Number(String(tokensArg ?? "").replace(/[,_]/g, ""));
6242
- if (!orgId || !Number.isInteger(tokens) || tokens <= 0) throw new CliError(FREE_USAGE);
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 test <org-id> --why "…" [--off]
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
- /** admin billing org test <org-id> --why "…" [--off] — setOrgTest (lib/billing/org-test.ts). */
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letterstory/cli",
3
- "version": "0.176.90",
3
+ "version": "0.176.92",
4
4
  "description": "Spin up and manage Letter phantom blogs from your terminal.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",