@letterstory/cli 0.71.0 → 0.72.1

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
@@ -56,7 +56,7 @@ import {
56
56
  } from "./commands.mjs";
57
57
 
58
58
  // Keep in sync with cli/package.json.
59
- export const VERSION = "0.71.0";
59
+ export const VERSION = "0.72.1";
60
60
 
61
61
  // Flags that never take a value. Listing them explicitly means `deploy get --json <id>`
62
62
  // can't accidentally swallow the id as --json's value.
@@ -645,7 +645,8 @@ Admin (STAFF key — --staff-key / LETTERSTORY_STAFF_API_KEY; acts before an org
645
645
  trial-run, trial-save, the Write input bank
646
646
  (inputs, input, inputs-harvest, input-save),
647
647
  screens (screen-new, screens, screen,
648
- screen-advance),
648
+ screen-advance), the bake-off (bakeoff,
649
+ bakeoff-pick, bakeoff-merge),
649
650
  and the arena, shredder
650
651
  and enhancer twins (every flag is listed in
651
652
  cli/lib/commands/admin.mjs).
@@ -505,6 +505,15 @@ async function cmdAdminTuningContext(ctx) {
505
505
  * admin tuning screens | screen <id> list / one screen: per candidate, each measure against the Base, with its noise
506
506
  * admin tuning screen-advance <id> queue what the caps allow now (the cron does it every minute)
507
507
  *
508
+ * The bake-off: a screen's versions graded item by item against the checklist (the eval list's
509
+ * confirmed and unvetted categories, and the craft axes commitment / economy / movement), the winner
510
+ * per item tallied across the inputs, and a merge that composes the best of each into one candidate.
511
+ * Deterministic; a person overrides a grade and confirms the merge, which is a hypothesis until the
512
+ * eye test confirms it.
513
+ * admin tuning bakeoff <screen-id> [--regrade] the winners table: item, trusted, leader, wins per version, graded; then the conflicts a merge would meet
514
+ * admin tuning bakeoff-pick <screen-id> --input <key> --item <category:id|axis:id> --winner <version|none> a person's grade on one input and item (replaces the machine's)
515
+ * admin tuning bakeoff-merge <screen-id> [--resolve item=version,…] [--note "…"] [--compare 10] save the merged file as a candidate of the base kernel (never live); --compare starts a comparison against live on that many pairs
516
+ *
508
517
  * Research (Phase 3): our arena's versions as files, research comparisons, the deploy split.
509
518
  * admin tuning arenas | arena <version-id> our arena's versions / one version's file and diff
510
519
  * admin tuning arena-candidate --patch '{…}' | --file f.json [--of <version-id>] [--note "…"] save a candidate
@@ -1534,6 +1543,98 @@ async function cmdAdminTuning(ctx) {
1534
1543
  ? printResult(io, flags, r)
1535
1544
  : io.log(`queued ${r.queued} run(s)${r.waiting ? `; waiting: ${r.waiting}` : ""}`);
1536
1545
  }
1546
+ // --- The bake-off: a screen's versions graded item by item, the winners table, the merge ---
1547
+ case "bakeoff": {
1548
+ if (!id) throw new CliError("Usage: admin tuning bakeoff <screen-id> [--regrade]");
1549
+ const r = await staffRequest(
1550
+ ctx,
1551
+ "GET",
1552
+ `${T}/screens/${id}/bakeoff${flagBool(flags.regrade) ? "?regrade=1" : ""}`
1553
+ );
1554
+ if (json) return printResult(io, flags, r);
1555
+ io.log(
1556
+ `bake-off ${r.screen.title}: v${r.versions.join(", v")} on ${r.inputs.length} input(s); leader ${r.leader === null ? "none" : `v${r.leader}`}`
1557
+ );
1558
+ if (r.unversioned.length) io.log(`left out (not a saved version): ${r.unversioned.join(", ")}`);
1559
+ io.log("");
1560
+ io.log(
1561
+ `${"item".padEnd(32)} ${"trusted".padEnd(8)} ${"leader".padEnd(7)} ${r.versions.map((v) => `v${v}`.padStart(6)).join("")} graded`
1562
+ );
1563
+ for (const row of r.table)
1564
+ io.log(
1565
+ `${row.item.id.padEnd(32)} ${(row.trusted ? "yes" : "no").padEnd(8)} ${(row.leader === null ? "—" : `v${row.leader}`).padEnd(7)} ${r.versions.map((v) => String(row.wins[v] ?? 0).padStart(6)).join("")} ${row.graded}`
1566
+ );
1567
+ io.log("");
1568
+ io.log(
1569
+ r.conflicts_if_merged.length
1570
+ ? `conflicts if merged: ${r.conflicts_if_merged.map((c) => `${c.path} ← ${c.candidates.map((x) => `${x.item} v${x.version}`).join(" | ")}`).join("; ")}`
1571
+ : "conflicts if merged: none"
1572
+ );
1573
+ return;
1574
+ }
1575
+ case "bakeoff-pick": {
1576
+ const input = flagStr(flags.input);
1577
+ const item = flagStr(flags.item);
1578
+ const winner = flagStr(flags.winner);
1579
+ if (!id || !input || !item || !winner)
1580
+ throw new CliError(
1581
+ "Usage: admin tuning bakeoff-pick <screen-id> --input <key> --item <category:id|axis:id> --winner <version|none>"
1582
+ );
1583
+ const winnerVersion = winner === "none" ? null : Number(winner);
1584
+ if (winnerVersion !== null && !(Number.isInteger(winnerVersion) && winnerVersion > 0))
1585
+ throw new CliError("--winner is a version number, or none");
1586
+ const r = await staffRequest(ctx, "POST", `${T}/screens/${id}/bakeoff/pick`, {
1587
+ input_key: input,
1588
+ item_id: item,
1589
+ winner_version: winnerVersion,
1590
+ });
1591
+ if (json) return printResult(io, flags, r);
1592
+ const row = r.read.table.find((x) => x.item.id === item);
1593
+ return io.log(
1594
+ `graded ${item} on ${input}: ${winnerVersion === null ? "none" : `v${winnerVersion}`}${row ? ` · leader now ${row.leader === null ? "none" : `v${row.leader}`}` : ""}`
1595
+ );
1596
+ }
1597
+ case "bakeoff-merge": {
1598
+ if (!id)
1599
+ throw new CliError(
1600
+ 'Usage: admin tuning bakeoff-merge <screen-id> [--resolve item=version,…] [--note "…"] [--compare 10]'
1601
+ );
1602
+ const resolve = {};
1603
+ for (const pair of flagList(flags.resolve).flatMap((x) =>
1604
+ x
1605
+ .split(",")
1606
+ .map((y) => y.trim())
1607
+ .filter(Boolean)
1608
+ )) {
1609
+ const [item, version] = pair.split("=").map((x) => x.trim());
1610
+ const v = Number(version);
1611
+ if (!item || !(Number.isInteger(v) && v > 0))
1612
+ throw new CliError(`--resolve: "${pair}" is not item=version`);
1613
+ resolve[item] = v;
1614
+ }
1615
+ const compare = flags.compare === undefined ? undefined : { pairs: flagNum(flags.compare) ?? 10 };
1616
+ const r = await staffRequest(
1617
+ ctx,
1618
+ "POST",
1619
+ `${T}/screens/${id}/bakeoff/merge`,
1620
+ compact({
1621
+ resolve: Object.keys(resolve).length ? resolve : undefined,
1622
+ note: flagStr(flags.note),
1623
+ compare,
1624
+ })
1625
+ );
1626
+ if (json) return printResult(io, flags, r);
1627
+ io.log(
1628
+ `merged from v${r.base} → candidate v${r.version}${r.comparison_id ? ` · comparison ${r.comparison_id}` : ""}`
1629
+ );
1630
+ for (const p of r.provenance)
1631
+ io.log(` ${p.applied ? "applied" : `skipped (${p.skipped})`} ${p.item} ${p.path} from v${p.from}`);
1632
+ if (r.conflicts.length)
1633
+ io.log(
1634
+ `conflicts left (the base's value kept): ${r.conflicts.map((c) => `${c.path} ← ${c.candidates.map((x) => `${x.item} v${x.version}`).join(" | ")}`).join("; ")}`
1635
+ );
1636
+ return;
1637
+ }
1537
1638
  // --- Research: our arena's versions, research comparisons, the deploy split ---
1538
1639
  case "arenas": {
1539
1640
  const r = await staffRequest(ctx, "GET", `${T}/arena-files`);
@@ -73,6 +73,12 @@ export const ARGS = {
73
73
  theme: "theme",
74
74
  customer_ref: { flag: "customer-ref" },
75
75
  collection_id: { flag: "collection" },
76
+ redesign: { flag: "redesign", type: "bool", negate: "no-redesign" },
77
+ // `--layout none` clears the choice (the site's theme then suggests one).
78
+ layout: {
79
+ value: (flags) =>
80
+ flags.layout === undefined ? undefined : flags.layout === "none" ? null : String(flags.layout),
81
+ },
76
82
  },
77
83
  },
78
84
  collection: { value: (flags) => readJsonObjectFlag(flags, "collection-config") },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letterstory/cli",
3
- "version": "0.71.0",
3
+ "version": "0.72.1",
4
4
  "description": "Spin up and manage Letterstory phantom blogs from your terminal.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",