@letterstory/cli 0.72.1 → 0.73.0

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/README.md CHANGED
@@ -368,6 +368,8 @@ letterstory admin tuning bank-judge <eval-id> --prompt-file judge.md --mode scor
368
368
  letterstory admin tuning bank-score <eval-id> --version 2 # score it over the pool; prints agreement on held-out labels
369
369
  letterstory admin tuning bank-try <eval-id> --version 2 # the judge as a trial rule in candidates, and a comparison against live
370
370
  letterstory admin tuning invite --email editor@example.com # invite an editor (Pick and Eval bank only); `people`, `revoke <id>`
371
+ letterstory admin tuning assign --pick <comparison-id> --instructions "Read for the opening" # a task for every editor (--to <email> for one)
372
+ letterstory admin tuning assign --label bank-draft-x-not-y --target 50 # editors cut or keep 50 of this eval's sentences
371
373
  letterstory admin tuning side-by-side <id>,<id>,<id> [--cell <key>] # several writers as one board: each one's measures against live; with --cell, every writer's text on that input
372
374
  letterstory admin tuning candidates --kernels all --patch '{"writer":"echo/echo"}' --note "Echo" # the same change as a candidate of each kernel
373
375
  letterstory admin tuning session-new --title "Echo round 1" --kernel Hazel --input-file brief.md --kind outline [--version 22] [--versions 23,29] # --versions: saved candidates, a trial each
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.72.1";
59
+ export const VERSION = "0.73.0";
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.
@@ -646,7 +646,9 @@ Admin (STAFF key — --staff-key / LETTERSTORY_STAFF_API_KEY; acts before an org
646
646
  (inputs, input, inputs-harvest, input-save),
647
647
  screens (screen-new, screens, screen,
648
648
  screen-advance), the bake-off (bakeoff,
649
- bakeoff-pick, bakeoff-merge),
649
+ bakeoff-pick, bakeoff-merge), programmes
650
+ (programmes, programme, programme-new,
651
+ programme-stop, programme-advance),
650
652
  and the arena, shredder
651
653
  and enhancer twins (every flag is listed in
652
654
  cli/lib/commands/admin.mjs).
@@ -473,6 +473,8 @@ async function cmdAdminTuningContext(ctx) {
473
473
  * admin tuning bank-score <eval-id> --version N score a judge version over the pool (call again for more than 600)
474
474
  * admin tuning bank-try <eval-id> --version N [--kernels Hazel,Aspen] the judge as a trial rule in candidates, and a comparison against live
475
475
  * admin tuning people | invite --email e [--note "…"] | revoke <id> who an admin invited into the console as an editor
476
+ * admin tuning assignments | assign --pick <comparison-id>|--label <eval-id> [--to e] [--instructions "…"] [--target N] | unassign <id> what editors are asked to do
477
+ * admin tuning tasks your own open tasks, with progress
476
478
  * admin tuning side-by-side <id>,<id>[,…] [--cell <key>] several comparisons as one board: each writer's measures against live, and one input's texts
477
479
  * admin tuning candidates --kernels Hazel,Aspen|all --patch '{…}' [--base N] [--note "…"] the same changes as a candidate of each kernel
478
480
  * admin tuning promote <experiment-id> --versions <service-kernel-id>:<version>,… promote from a "ship" decision
@@ -514,6 +516,15 @@ async function cmdAdminTuningContext(ctx) {
514
516
  * 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
517
  * 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
518
  *
519
+ * Programmes: the bake-off run by the machine, generation after generation, on the cron, with a run
520
+ * budget and stop rules (the budget; N flat generations; a finalist that won its picks). Each
521
+ * generation screens the base against its challengers, merges the winners, compares the merge
522
+ * against live; a merge that cleared the gates and lost no craft becomes the base. People pick.
523
+ * admin tuning programmes | programme <id> list / one programme: every generation, its table, merge and comparison, and what happens next
524
+ * admin tuning programme-new --title "…" --kernel <kernel-id|service-kernel-id> --seeds 21,22,23 [--inputs id,…|--n 8] [--filter "…"] [--pairs 10] --budget 200 [--flat 2]
525
+ * admin tuning programme-stop <id> [--reason "…"] stop it by hand
526
+ * admin tuning programme-advance <id> one manual tick (the cron takes the same step every minute)
527
+ *
517
528
  * Research (Phase 3): our arena's versions as files, research comparisons, the deploy split.
518
529
  * admin tuning arenas | arena <version-id> our arena's versions / one version's file and diff
519
530
  * admin tuning arena-candidate --patch '{…}' | --file f.json [--of <version-id>] [--note "…"] save a candidate
@@ -573,6 +584,49 @@ function tagFilterParams(raw) {
573
584
  * A session as an agent reads it: each trial's changes and runs with their headline numbers, and
574
585
  * (with `texts`) the latest finished text per trial. The full session (traces, records) is `--json`.
575
586
  */
587
+ /** A programme as a person reads it: the row, then one block per generation, then what happens next. */
588
+ function printProgramme(io, r) {
589
+ const p = r.programme;
590
+ io.log(
591
+ `programme ${p.title} (${p.id}): ${p.status}${p.stop_reason ? ` — ${p.stop_reason}` : ""} · generation ${p.generation} · base ${p.base_version === null ? "live" : `v${p.base_version}`} · ${p.runs_used}/${p.budget_runs} runs · seeds v${p.seed_versions.join(", v")} · ${p.pairs} pairs/generation · stop after ${p.max_flat_generations} flat`
592
+ );
593
+ for (const g of r.generations) {
594
+ const sm = g.summary ?? {};
595
+ io.log("");
596
+ io.log(
597
+ `generation ${g.generation}: ${g.status} · screen ${g.screen_id} · v${g.versions.join(", v")}${g.merged_version !== null ? ` · merged v${g.merged_version}` : ""}${g.comparison_id ? ` · comparison ${g.comparison_id}` : ""}`
598
+ );
599
+ if (sm.from)
600
+ io.log(
601
+ ` from: base ${sm.from.base === null ? "live" : `v${sm.from.base}`}${sm.from.seeds ? ` · seeds v${sm.from.seeds.join(", v")}` : ""}${sm.from.strongest?.length ? ` · strongest v${sm.from.strongest.join(", v")}` : ""}${sm.from.merged != null ? ` · merged v${sm.from.merged}` : ""}${sm.from.mutations?.length ? ` · mutations ${sm.from.mutations.map((m) => `${m.category} → v${m.version}`).join(", ")}` : ""}`
602
+ );
603
+ if (sm.leader !== undefined)
604
+ io.log(
605
+ ` leader ${sm.leader === null ? "none" : `v${sm.leader}`}${sm.flat ? " · flat (nothing moved off the base)" : ""}`
606
+ );
607
+ for (const row of sm.table ?? [])
608
+ if (row.leader !== null && row.leader !== sm.base)
609
+ io.log(
610
+ ` ${row.item.id.padEnd(32)} ${row.trusted ? "trusted " : "unvetted "} v${row.leader} ${Object.entries(
611
+ row.wins
612
+ )
613
+ .map(([v, n]) => `v${v}:${n}`)
614
+ .join(" ")}`
615
+ );
616
+ if (sm.merge)
617
+ io.log(
618
+ ` merge: from v${sm.merge.base} → v${sm.merge.version}; applied ${sm.merge.applied.map((a) => `${a.item} from v${a.from}`).join(", ") || "nothing"}${sm.merge.conflicts.length ? `; conflicts kept the base: ${sm.merge.conflicts.map((c) => c.path).join(", ")}` : ""}`
619
+ );
620
+ if (sm.comparison)
621
+ io.log(
622
+ ` comparison: quality ${sm.comparison.quality ?? "unread"}${sm.comparison.guards_worse.length ? ` · guards worse: ${sm.comparison.guards_worse.join(", ")}` : " · gates clean"} · ${sm.comparison.finalist ? "finalist" : "not a finalist"}${sm.comparison.picks ? ` · picks ${sm.comparison.picks}` : ""}${sm.comparison.delta?.ceiling != null ? ` · ceiling ${sm.comparison.delta.ceiling > 0 ? "+" : ""}${sm.comparison.delta.ceiling}` : ""}${sm.comparison.next_arm.length ? ` · next arm: ${sm.comparison.next_arm.map((a) => `${a.category}${a.patch ? " (patch)" : ""}`).join(", ")}` : ""}`
623
+ );
624
+ if (sm.error) io.log(` error: ${sm.error}`);
625
+ }
626
+ io.log("");
627
+ io.log(`next: ${r.next}`);
628
+ }
629
+
576
630
  function sessionSummary(session, texts) {
577
631
  const runsOf = (trialId) => session.runs.filter((r) => r.trialId === trialId);
578
632
  return {
@@ -1135,6 +1189,48 @@ async function cmdAdminTuning(ctx) {
1135
1189
  `${ctx.client.url}/superadmin/tuning?${new URLSearchParams({ tab: "compare", comparison: r.comparison_id })}`
1136
1190
  );
1137
1191
  }
1192
+ case "assignments": {
1193
+ const r = await staffRequest(ctx, "GET", `${T}/assignments${flagBool(flags.closed) ? "?closed=1" : ""}`);
1194
+ if (json) return printResult(io, flags, r);
1195
+ for (const a of r.assignments)
1196
+ io.log(
1197
+ `${a.id} ${a.kind} "${a.title}" → ${a.assignee_email ?? "every editor"}${a.target ? `, ${a.target} asked` : ""}${a.closed_at ? " (closed)" : ""}`
1198
+ );
1199
+ return;
1200
+ }
1201
+ case "assign": {
1202
+ const pick = flagStr(flags.pick);
1203
+ const label = flagStr(flags.label);
1204
+ if (!pick === !label)
1205
+ throw new CliError(
1206
+ 'Usage: admin tuning assign --pick <comparison-id> | --label <eval-id> [--to email] [--instructions "…"] [--title "…"] [--target N]'
1207
+ );
1208
+ const body = compact({
1209
+ ...(pick ? { kind: "pick", comparison_id: pick } : { kind: "label", eval_id: label }),
1210
+ assignee_email: flagStr(flags.to),
1211
+ instructions: flagStr(flags.instructions),
1212
+ title: flagStr(flags.title),
1213
+ target: flagNum(flags.target),
1214
+ });
1215
+ const r = await staffRequest(ctx, "POST", `${T}/assignments`, body);
1216
+ if (json) return printResult(io, flags, r);
1217
+ return io.log(
1218
+ `assigned ${r.assignment.kind} task "${r.assignment.title}" to ${r.assignment.assignee_email ?? "every editor"} (${r.assignment.id})`
1219
+ );
1220
+ }
1221
+ case "unassign": {
1222
+ if (!id) throw new CliError("Usage: admin tuning unassign <assignment-id>");
1223
+ return printResult(io, flags, await staffRequest(ctx, "DELETE", `${T}/assignments/${id}`));
1224
+ }
1225
+ case "tasks": {
1226
+ const r = await staffRequest(ctx, "GET", `${T}/editor/tasks`);
1227
+ if (json) return printResult(io, flags, r);
1228
+ for (const t of r.tasks)
1229
+ io.log(
1230
+ `${t.id} ${t.kind} "${t.title}": ${t.done} done, ${t.remaining} left${t.complete ? " (complete)" : ""}`
1231
+ );
1232
+ return;
1233
+ }
1138
1234
  case "people": {
1139
1235
  const r = await staffRequest(ctx, "GET", `${T}/people`);
1140
1236
  if (json) return printResult(io, flags, r);
@@ -1635,6 +1731,95 @@ async function cmdAdminTuning(ctx) {
1635
1731
  );
1636
1732
  return;
1637
1733
  }
1734
+ // --- Programmes: the bake-off run by the machine over generations ---
1735
+ case "programmes": {
1736
+ const r = await staffRequest(ctx, "GET", `${T}/programmes?limit=${flagNum(flags.limit) ?? 30}`);
1737
+ if (json) return printResult(io, flags, r);
1738
+ return io.log(
1739
+ r.programmes
1740
+ .map(
1741
+ (x) =>
1742
+ `${x.id} ${x.status.padEnd(7)} ${x.title} · generation ${x.generation} · base ${x.base_version === null ? "live" : `v${x.base_version}`} · ${x.runs_used}/${x.budget_runs} runs${x.stop_reason ? ` · ${x.stop_reason}` : ""}`
1743
+ )
1744
+ .join("\n") || "(no programmes)"
1745
+ );
1746
+ }
1747
+ case "programme": {
1748
+ if (!id) throw new CliError("Usage: admin tuning programme <programme-id>");
1749
+ const r = await staffRequest(ctx, "GET", `${T}/programmes/${id}`);
1750
+ if (json) return printResult(io, flags, r);
1751
+ printProgramme(io, r);
1752
+ return;
1753
+ }
1754
+ case "programme-new": {
1755
+ const title = flagStr(flags.title);
1756
+ const kernel = flagStr(flags.kernel);
1757
+ const seeds = flagList(flags.seeds)
1758
+ .flatMap((x) => x.split(","))
1759
+ .map((x) => Number(x.trim()))
1760
+ .filter((x) => Number.isFinite(x));
1761
+ const budget = flagNum(flags.budget);
1762
+ if (!title || !kernel || !seeds.length || budget === undefined)
1763
+ throw new CliError(
1764
+ 'Usage: admin tuning programme-new --title "…" --kernel <kernel-id|service-kernel-id> --seeds 21,22,23 [--inputs id,…|--n 8] [--filter "…"] [--pairs 10] --budget 200 [--flat 2]'
1765
+ );
1766
+ const filter = Object.fromEntries(
1767
+ Object.entries(tagFilterParams(flagStr(flags.filter))).map(([k, v]) => [
1768
+ k,
1769
+ v
1770
+ .split(",")
1771
+ .map((x) => x.trim())
1772
+ .filter(Boolean),
1773
+ ])
1774
+ );
1775
+ const inputIds = flagList(flags.inputs).flatMap((x) =>
1776
+ x
1777
+ .split(",")
1778
+ .map((y) => y.trim())
1779
+ .filter(Boolean)
1780
+ );
1781
+ const r = await staffRequest(
1782
+ ctx,
1783
+ "POST",
1784
+ `${T}/programmes`,
1785
+ compact({
1786
+ title,
1787
+ base_kernel_id: kernel,
1788
+ seed_versions: seeds,
1789
+ input_ids: inputIds.length ? inputIds : undefined,
1790
+ n: flagNum(flags.n),
1791
+ filter: Object.keys(filter).length ? filter : undefined,
1792
+ pairs: flagNum(flags.pairs),
1793
+ budget_runs: budget,
1794
+ max_flat_generations: flagNum(flags.flat),
1795
+ })
1796
+ );
1797
+ if (json) return printResult(io, flags, r);
1798
+ return io.log(
1799
+ `programme ${r.programme.id}: generation 1 screening v${r.programme.seed_versions.join(", v")} against live; budget ${r.programme.budget_runs} runs · ${r.next}`
1800
+ );
1801
+ }
1802
+ case "programme-stop": {
1803
+ if (!id) throw new CliError('Usage: admin tuning programme-stop <programme-id> [--reason "…"]');
1804
+ const r = await staffRequest(
1805
+ ctx,
1806
+ "POST",
1807
+ `${T}/programmes/${id}/stop`,
1808
+ compact({ reason: flagStr(flags.reason) })
1809
+ );
1810
+ if (json) return printResult(io, flags, r);
1811
+ return io.log(`programme ${r.programme.id} ${r.programme.status}: ${r.programme.stop_reason}`);
1812
+ }
1813
+ case "programme-advance": {
1814
+ if (!id) throw new CliError("Usage: admin tuning programme-advance <programme-id>");
1815
+ const r = await staffRequest(ctx, "POST", `${T}/programmes/${id}/advance`, {});
1816
+ if (json) return printResult(io, flags, r);
1817
+ if (!r.transitions.length) io.log("nothing to do yet");
1818
+ for (const t of r.transitions)
1819
+ io.log(`generation ${t.generation}: ${t.from} → ${t.to}${t.note ? ` (${t.note})` : ""}`);
1820
+ if (r.read) io.log(`next: ${r.read.next}`);
1821
+ return;
1822
+ }
1638
1823
  // --- Research: our arena's versions, research comparisons, the deploy split ---
1639
1824
  case "arenas": {
1640
1825
  const r = await staffRequest(ctx, "GET", `${T}/arena-files`);
@@ -2003,7 +2188,7 @@ async function cmdAdminTuning(ctx) {
2003
2188
  }
2004
2189
  default:
2005
2190
  throw new CliError(
2006
- "Usage: admin tuning kernels|kernel|candidate|compare|comparisons|comparison|picks|pick|loop|ledger|evals|eval-add|eval-set|proposals|propose-eval|capture-eval|reject-eval|reading-list|finalists|finalists-queue|pair|bank|bank-eval|bank-draft|bank-shape|bank-pool|bank-judge|bank-score|bank-try|people|invite|revoke|side-by-side|candidates|base-kernels|base-versions|sessions|session|session-new|propose|trial-add|trial-run|trial-save|promote|arenas|arena|arena-candidate|arena-dials|comparison-extend|research-compare|research-inputs|research-split|research-standing|promote-arena|shred-compare|shred-inputs|shred-split|shred-standing|shredders|shredder|shredder-candidate|promote-shredder|enhance-compare|enhance-inputs|enhance-split|enhance-standing|enhancers|enhancer|enhancer-candidate|promote-enhancer (see --help)"
2191
+ "Usage: admin tuning kernels|kernel|candidate|compare|comparisons|comparison|picks|pick|loop|ledger|evals|eval-add|eval-set|proposals|propose-eval|capture-eval|reject-eval|reading-list|finalists|finalists-queue|pair|bank|bank-eval|bank-draft|bank-shape|bank-pool|bank-judge|bank-score|bank-try|people|invite|revoke|assignments|assign|unassign|tasks|side-by-side|candidates|base-kernels|base-versions|sessions|session|session-new|propose|trial-add|trial-run|trial-save|promote|arenas|arena|arena-candidate|arena-dials|comparison-extend|research-compare|research-inputs|research-split|research-standing|promote-arena|shred-compare|shred-inputs|shred-split|shred-standing|shredders|shredder|shredder-candidate|promote-shredder|enhance-compare|enhance-inputs|enhance-split|enhance-standing|enhancers|enhancer|enhancer-candidate|promote-enhancer (see --help)"
2007
2192
  );
2008
2193
  }
2009
2194
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letterstory/cli",
3
- "version": "0.72.1",
3
+ "version": "0.73.0",
4
4
  "description": "Spin up and manage Letterstory phantom blogs from your terminal.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",