@letterstory/cli 0.177.38 → 0.177.40

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
@@ -88,7 +88,7 @@ import {
88
88
  import { fetchParkedSections, withoutParked } from "./parked.mjs";
89
89
 
90
90
  // Matches cli/package.json; publish-cli.yml rewrites both to the published version.
91
- export const VERSION = "0.177.38";
91
+ export const VERSION = "0.177.40";
92
92
 
93
93
  /** The exit code for a gate refusal (CARD_REQUIRED, PAYMENT_FAILED, OUT_OF_TOKENS): 1 is a plain error. */
94
94
  export const EXIT_PAYMENT_REQUIRED = 3;
@@ -5179,12 +5179,14 @@ const RECIPE_FILE_FIELDS = [
5179
5179
  "immediate",
5180
5180
  // Whether its row must name what it acts on (2026-11-11); left out here, an export would drop it.
5181
5181
  "names_subject",
5182
+ // Where its row shows: ideas or fix (a deficit, 2026-11-13); left out here, an export would drop it.
5183
+ "shows_in",
5182
5184
  "status",
5183
5185
  "needs",
5184
5186
  "variations",
5185
5187
  ];
5186
5188
  const RECIPES_USAGE =
5187
- "Usage: admin recipes list [--kind recipe|known_issue] [--status live|parked] [--approach seo|geo|agent_conviction|inbound_conversion|outbound|other] | show <id|name> | runs <id|name> [--days 1..90] [--limit n] | vocab | stats | export [--out <file>] | import <file> [--dry-run] | refit [--org <id>,…] [--recipe <id|name>] [--limit n] | save [<id>] [--file <recipe.json>] [--kind recipe|known_issue] [--name …] [--description …] [--recipe …] [--when-to-use …] [--when-not-to-use …] [--weight 1..10] [--approaches seo,geo,…] [--reads llms_txt,github,…] [--runs <catalog action id>] [--primitives-access true|false] [--immediate true|false] [--names-subject true|false] [--status live|parked] [--needs <what a parked recipe is missing>] | delete <id> --yes";
5189
+ "Usage: admin recipes list [--kind recipe|known_issue] [--status live|parked] [--approach seo|geo|agent_conviction|inbound_conversion|outbound|other] | show <id|name> | runs <id|name> [--days 1..90] [--limit n] | vocab | stats | export [--out <file>] | import <file> [--dry-run] | refit [--org <id>,…] [--recipe <id|name>] [--limit n] | save [<id>] [--file <recipe.json>] [--kind recipe|known_issue] [--name …] [--description …] [--recipe …] [--when-to-use …] [--when-not-to-use …] [--weight 1..10] [--approaches seo,geo,…] [--reads llms_txt,github,…] [--runs <catalog action id>] [--primitives-access true|false] [--immediate true|false] [--names-subject true|false] [--shows-in ideas|fix] [--status live|parked] [--needs <what a parked recipe is missing>] | delete <id> --yes";
5188
5190
 
5189
5191
  const recipeForFile = (r) => Object.fromEntries(RECIPE_FILE_FIELDS.map((k) => [k, r[k]]));
5190
5192
 
@@ -5235,6 +5237,7 @@ function recipeDetail(r) {
5235
5237
  ? [" letter's tools: its build may read the account through them (primitives_access)"]
5236
5238
  : []),
5237
5239
  ...(r.names_subject ? [" title: names what it acts on, or it is a no (names_subject)"] : []),
5240
+ ...(r.shows_in === "fix" ? [" shows in: Fix, as a deficit (shows_in)"] : []),
5238
5241
  ...(variations.length > 1
5239
5242
  ? variations.flatMap((v, i) => [` variation ${i + 1}: ${v.name}`, ...fields(v, " ")])
5240
5243
  : fields(r, " ")),
@@ -5291,6 +5294,8 @@ function recipeFromFlags(flags) {
5291
5294
  immediate: flags.immediate === undefined ? undefined : String(flags.immediate) === "true",
5292
5295
  // --names-subject true: its row names what it acts on, or is a no; false: it may keep its general name.
5293
5296
  names_subject: flags["names-subject"] === undefined ? undefined : String(flags["names-subject"]) === "true",
5297
+ // --shows-in fix: its row is a deficit, in Fix's Deficits; ideas (the default): a thing to build, on Ideas.
5298
+ shows_in: flagStr(flags["shows-in"]),
5294
5299
  // --reads llms_txt,github declares the facts its conditions rest on; --reads "" clears them.
5295
5300
  reads: flagStr(flags.reads)
5296
5301
  ?.split(",")
@@ -6355,7 +6360,9 @@ const FREE_USAGE = `Usage: admin billing free exposure [--json] wh
6355
6360
  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
6356
6361
  admin billing free grants [--org <org-id>] [--limit n] staff grants, newest first, live and revoked
6357
6362
  admin billing free revoke <org-id> <ref> --why "…" take a staff grant back (ref from the list: grant:<id> or wallet:staff_…)
6358
- admin billing free preflight [--json] who the paywall stops the moment it is enforced, and whether it is ready`;
6363
+ admin billing free preflight [--json] who the paywall stops the moment it is enforced, and whether it is ready
6364
+ admin billing free pool <org-id|name>... --why "…" link orgs on free tokens so they spend from one balance
6365
+ admin billing free unpool <org-id|name> --why "…" take an org out of its shared balance`;
6359
6366
 
6360
6367
  const EXPOSURE_WORDS = {
6361
6368
  needs_legacy: "pay today: need a legacy mark (admin billing legacy mark --all)",
@@ -6457,6 +6464,28 @@ async function cmdAdminBillingFree(ctx) {
6457
6464
  });
6458
6465
  return 0;
6459
6466
  }
6467
+ if (positionals[1] === "pool" || positionals[1] === "unpool") {
6468
+ const pool = positionals[1] === "pool";
6469
+ const args = positionals.slice(2);
6470
+ if (pool ? args.length < 2 : args.length !== 1) throw new CliError(FREE_USAGE);
6471
+ const why = flagStr(flags.why);
6472
+ if (!why) throw new CliError(`Say why (--why "…"): it is kept with the change.\n${FREE_USAGE}`);
6473
+ const orgIds = [];
6474
+ for (const a of args) orgIds.push((await resolveBillingOrg(ctx, a)).orgId);
6475
+ const r = pool
6476
+ ? await staffPost(ctx, "/api/superadmin/billing/token-pools", { orgIds, why })
6477
+ : await staffRequest(ctx, "DELETE", "/api/superadmin/billing/token-pools", { orgId: orgIds[0], why });
6478
+ printResult(io, flags, r, (x) =>
6479
+ pool
6480
+ ? `Sharing one token balance (${x.poolId}):\n${x.orgs.map((o) => ` ${o.name} ${o.orgId}`).join("\n")}`
6481
+ : !x.changed
6482
+ ? `${orgIds[0]} is in no pool; nothing changed.`
6483
+ : x.left.length
6484
+ ? `Taken out. Still sharing: ${x.left.map((o) => o.name).join(", ")}.`
6485
+ : "Taken out. The pool had one org left, so it ended."
6486
+ );
6487
+ return 0;
6488
+ }
6460
6489
  if (positionals[1] === "grant") {
6461
6490
  const [orgArg, tokensArg] = [positionals[2], positionals[3]];
6462
6491
  const tokens = Number(String(tokensArg ?? "").replace(/[,_]/g, ""));
@@ -26,6 +26,7 @@ const TEMPLATES = [
26
26
  "competitor_pages",
27
27
  "competitor_takes",
28
28
  "competitor_changes",
29
+ "competitor_flags",
29
30
  "icp_visitors",
30
31
  "vc_visitors",
31
32
  "plan_updates",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letterstory/cli",
3
- "version": "0.177.38",
3
+ "version": "0.177.40",
4
4
  "description": "Spin up and manage Letter phantom blogs from your terminal.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",