@letterstory/cli 0.148.3 → 0.148.4

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
@@ -66,7 +66,7 @@ import {
66
66
 
67
67
  // Matches cli/package.json; publish-cli.yml rewrites both to the published version.
68
68
  // Matches cli/package.json; publish-cli.yml rewrites both to the published version.
69
- export const VERSION = "0.148.3";
69
+ export const VERSION = "0.148.4";
70
70
 
71
71
  // Flags that never take a value. Listing them explicitly means `deploy get --json <id>`
72
72
  // can't accidentally swallow the id as --json's value. Every `type: "bool"` flag (and its
@@ -134,6 +134,12 @@ export const BOOLEAN_FLAGS = new Set([
134
134
  "verbose",
135
135
  "no-color",
136
136
  "dry-run",
137
+ // admin demo list --enrollable, show --moves, set --enroll|--unenroll
138
+ "enrollable",
139
+ "moves",
140
+ "enroll",
141
+ "unenroll",
142
+ "clean-slate",
137
143
  // admin recipes list --enabled-only, save --enable|--disable, import --retire-missing
138
144
  "enabled-only",
139
145
  "enable",
@@ -850,6 +856,19 @@ Admin (STAFF key — --staff-key / LETTERSTORY_STAFF_API_KEY; acts before an org
850
856
  [--primitives a,b|none] [--goals a,b|none] [--company-types a,b|none] [--enable|--disable] [--position n]
851
857
  Create one (no id) or change one. Needs recipes:write.
852
858
  admin recipes delete <id> --yes Delete for good (save --disable retires and keeps it).
859
+ admin demo guide How to run a demo, step by step, and what every knob and
860
+ seed shows on screen. Read it first.
861
+ admin demo list [--enrollable] | show <org-id> [--moves]
862
+ Demo orgs (/superadmin/demo): what each shows beside
863
+ what it really has. Needs demo:read.
864
+ admin demo set <org-id> [--enroll] [--phantom parked] [--search-console hidden] [--inject <key>]... [--save-as n]
865
+ Set what a demo org shows (merged); 'shown' puts a thing
866
+ back. --file takes a scenario as JSON. Needs demo:write.
867
+ admin demo reset <org-id> <scenario> [--clean-slate] [--close-prs since_applied|all_open|none]
868
+ Undo the last take (its PRs closed, builds archived), then
869
+ show a scenario: real, fresh_geo, fresh_seo, after_approve
870
+ or a saved one. --clean-slate closes every open PR and
871
+ archives every build.
853
872
  admin suggestion-recipes list|export [--out f.json]|import <f.json> [--dry-run] [--retire-missing]|stats
854
873
  Build's suggestion recipes: plain-language ways
855
874
  Letterpress finds what a site should build (look at,
@@ -19,6 +19,7 @@
19
19
  // admin brand-detection <org-id> (fleet:read)
20
20
  // admin brand-detection <org-id> [--term t]... [--exact t]... [--exclude p]... [--note "…"] | --clear [--note "…"] (fleet:instrument)
21
21
  // admin recipes list|show|vocab|export|import|save|delete … (recipes:read / recipes:write)
22
+ // admin demo guide|list|show|set|reset … (demo:read / demo:write)
22
23
  // admin models runs|run|star|unstar|scoreboard|requests|request|sampling … (models:read / models:write)
23
24
  // admin replays [--org <id>] [--target <id>] [--status …] [--limit n] (fleet:read)
24
25
  // admin replay --org <id> [<response-id> | --target <id>] [--surface …] (fleet:replay)
@@ -5385,6 +5386,134 @@ async function cmdAdminBilling(ctx) {
5385
5386
  }
5386
5387
  }
5387
5388
 
5389
+ // `admin demo` — demo control (docs/architecture/demo-control.md): what each demo org shows,
5390
+ // set, rehearsed and reset. The staff routes behind /superadmin/demo, the same functions and
5391
+ // schema as list_demo_orgs / get_demo_state / set_demo_state / reset_demo_state.
5392
+ const DEMO = "/api/superadmin/demo";
5393
+ const DEMO_ITEM_FLAGS = [
5394
+ "phantom",
5395
+ "main-blog",
5396
+ "visitors",
5397
+ "probes",
5398
+ "cms",
5399
+ "publish-target",
5400
+ "search-console",
5401
+ "github",
5402
+ "domain",
5403
+ "setup",
5404
+ ];
5405
+ const DEMO_USAGE =
5406
+ "Usage: admin demo guide | list [--enrollable] | show <org-id> [--moves] | set <org-id> [--enroll] [--unenroll] [--file <state.json>] [--phantom parked|shown] [--main-blog …] [--visitors …] [--probes not_run|shown] [--cms hidden|shown] [--publish-target …] [--search-console …] [--github …] [--domain …] [--setup fresh|finished] [--domain-cap n] [--inject <key>]... [--hide <key>]... [--save-as <name>] | reset <org-id> <scenario> [--clean-slate] [--close-prs since_applied|all_open|none]";
5407
+
5408
+ async function cmdAdminDemo(ctx) {
5409
+ const { flags, positionals, io } = ctx;
5410
+ const sub = positionals[0];
5411
+ switch (sub) {
5412
+ case "guide": {
5413
+ const guide = await staffRequest(ctx, "GET", `${DEMO}/guide`);
5414
+ printResult(io, flags, guide, (g) =>
5415
+ [
5416
+ "How to run a demo",
5417
+ ...g.steps.map(
5418
+ (s) => `\n${s.title}\n ${s.do}\n page: ${s.page}\n cli: ${s.cli}\n -> ${s.result}`
5419
+ ),
5420
+ "\nWhat each knob and seed shows",
5421
+ ...g.entries.map(
5422
+ (e) =>
5423
+ `\n${e.label} (${e.values})\n ${e.what}\n${e.shows_as.map((x) => ` · ${x}`).join("\n")}${
5424
+ e.on_camera ? `\n on camera: ${e.on_camera}` : ""
5425
+ }${e.mind.length ? `\n${e.mind.map((m) => ` ! ${m}`).join("\n")}` : ""}`
5426
+ ),
5427
+ ].join("\n")
5428
+ );
5429
+ return 0;
5430
+ }
5431
+ case "list": {
5432
+ const result = await staffRequest(
5433
+ ctx,
5434
+ "GET",
5435
+ `${DEMO}${flagBool(flags.enrollable) ? "?candidates=1" : ""}`
5436
+ );
5437
+ printResult(io, flags, result, (r) =>
5438
+ r.orgs.length
5439
+ ? r.orgs
5440
+ .map(
5441
+ (o) =>
5442
+ `${o.enrolled ? "demo" : "test"} ${o.name} ${o.domain ?? "—"} ${o.scenario ?? ""}${
5443
+ o.differs?.length ? ` [${o.differs.join(", ")}]` : ""
5444
+ } ${o.org_id}`
5445
+ )
5446
+ .join("\n")
5447
+ : "No demo orgs yet."
5448
+ );
5449
+ return 0;
5450
+ }
5451
+ case "show": {
5452
+ const orgId = requirePositional(positionals, 1, "org-id");
5453
+ const result = await staffRequest(
5454
+ ctx,
5455
+ "GET",
5456
+ `${DEMO}/${encodeURIComponent(orgId)}${flagBool(flags.moves) ? "?moves=1" : ""}`
5457
+ );
5458
+ printResult(io, flags, result, (r) =>
5459
+ [
5460
+ `${r.org.name} ${r.org.domain ?? "no domain"} scenario ${r.state.scenario ?? "—"}`,
5461
+ ...r.knobs.map((k) => ` ${k.item.padEnd(16)} real: ${k.real} · shown: ${k.shown} (${k.how})`),
5462
+ ` observations open: ${r.observations.open.map((o) => `${o.key}${o.injected ? " (demo)" : ""}`).join(", ") || "none"}`,
5463
+ ` answers: ${r.answers.map((q) => `${q.named}/${q.answers} ${q.text}`).join("; ") || "never probed"}`,
5464
+ ].join("\n")
5465
+ );
5466
+ return 0;
5467
+ }
5468
+ case "set": {
5469
+ const orgId = requirePositional(positionals, 1, "org-id");
5470
+ const file = flagStr(flags.file);
5471
+ const state = file ? JSON.parse(readFileSync(file, "utf8")) : {};
5472
+ for (const flag of DEMO_ITEM_FLAGS) {
5473
+ const value = flagStr(flags[flag]);
5474
+ if (value) state[flag.replace(/-/g, "_")] = value;
5475
+ }
5476
+ const cap = flagNum(flags["domain-cap"]);
5477
+ if (cap !== undefined && cap !== null) state.domain_cap = cap;
5478
+ const inject = flagList(flags.inject);
5479
+ const hide = flagList(flags.hide);
5480
+ if (inject.length || hide.length)
5481
+ state.observations = {
5482
+ ...state.observations,
5483
+ ...(inject.length ? { inject } : {}),
5484
+ ...(hide.length ? { hide } : {}),
5485
+ };
5486
+ const body = compact({
5487
+ ...(Object.keys(state).length ? { state } : {}),
5488
+ enroll: flagBool(flags.enroll) || undefined,
5489
+ unenroll: flagBool(flags.unenroll) || undefined,
5490
+ save_as: flagStr(flags["save-as"]),
5491
+ });
5492
+ if (!Object.keys(body).length) throw new CliError(DEMO_USAGE);
5493
+ const result = await staffRequest(ctx, "PATCH", `${DEMO}/${encodeURIComponent(orgId)}`, body);
5494
+ printResult(io, flags, result, (r) => r.notes.join("\n") || "No change.");
5495
+ return 0;
5496
+ }
5497
+ case "reset": {
5498
+ const orgId = requirePositional(positionals, 1, "org-id");
5499
+ const scenario = requirePositional(positionals, 2, "scenario");
5500
+ const result = await staffPost(
5501
+ ctx,
5502
+ `${DEMO}/${encodeURIComponent(orgId)}/reset`,
5503
+ compact({
5504
+ scenario,
5505
+ close_prs: flagStr(flags["close-prs"]),
5506
+ clean_slate: flagBool(flags["clean-slate"]) || undefined,
5507
+ })
5508
+ );
5509
+ printResult(io, flags, result, (r) => r.notes.join("\n"));
5510
+ return 0;
5511
+ }
5512
+ default:
5513
+ throw new CliError(DEMO_USAGE);
5514
+ }
5515
+ }
5516
+
5388
5517
  export async function cmdAdmin(ctx) {
5389
5518
  const sub = ctx.positionals[0];
5390
5519
  if (sub === "replays" || sub === "replay")
@@ -5405,10 +5534,11 @@ export async function cmdAdmin(ctx) {
5405
5534
  if (sub === "brand-detection") return cmdAdminBrandDetection({ ...ctx, positionals: ctx.positionals.slice(1) });
5406
5535
  if (sub === "republish") return cmdAdminRepublish({ ...ctx, positionals: ctx.positionals.slice(1) });
5407
5536
  if (sub === "recipes") return cmdAdminRecipes({ ...ctx, positionals: ctx.positionals.slice(1) });
5537
+ if (sub === "demo") return cmdAdminDemo({ ...ctx, positionals: ctx.positionals.slice(1) });
5408
5538
  if (sub === "suggestion-recipes")
5409
5539
  return cmdAdminSuggestionRecipes({ ...ctx, positionals: ctx.positionals.slice(1) });
5410
5540
  throw new CliError(
5411
- 'Usage: admin recipes … | admin suggestion-recipes … | admin republish … | admin create-org --domain <bare-domain> [--onboard] … | admin ask "<question>" | admin patterns … | admin experiments … | admin fleet-experiments … | admin crm … | admin spend … | admin evals … | admin brand-detection … | admin tuning … | admin tuning-context | admin replays … (see --help)'
5541
+ 'Usage: admin recipes … | admin demo … | admin suggestion-recipes … | admin republish … | admin create-org --domain <bare-domain> [--onboard] … | admin ask "<question>" | admin patterns … | admin experiments … | admin fleet-experiments … | admin crm … | admin spend … | admin evals … | admin brand-detection … | admin tuning … | admin tuning-context | admin replays … (see --help)'
5412
5542
  );
5413
5543
  }
5414
5544
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letterstory/cli",
3
- "version": "0.148.3",
3
+ "version": "0.148.4",
4
4
  "description": "Spin up and manage Letter phantom blogs from your terminal.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",