@letterstory/cli 0.66.0 → 0.69.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
@@ -369,14 +369,17 @@ letterstory admin tuning trial-run <trial> --repeats 2 # runs advance on the se
369
369
  letterstory admin tuning trial-save <trial> --note "…" # a trial as a candidate version (never live); then `admin tuning compare`
370
370
  letterstory admin tuning promote <experiment-id> --versions <service-kernel-id>:21 # from a concluded "ship" decision
371
371
  letterstory admin experiments list --q colons # what we've tried
372
+ letterstory admin experiments list --primitive write --stage rarity # what we learned about one stage
373
+ letterstory admin experiments new "A different writer, voice rewritten, rarity rewrite off" --primitive write # an investigation in Write's notebook
372
374
  letterstory admin experiments new "We use too many colons" --as "claude-code"
373
375
  letterstory admin experiments update <id> --plan-file plan.json --budget 120
374
- # ...a person approves the plan in Kernel Tuning -> Experiments...
376
+ # ...a person approves the plan on the investigation's page (approval gates spending a budget; a person's
377
+ # investigation needs none and is concluded straight away)...
375
378
  letterstory admin experiments spend <id> 54
376
379
  letterstory admin tuning-import lab/results/colons/publish/<brief>.json --experiment <id>
377
380
  letterstory admin experiments conclude <id> --decision drop \
378
381
  --conclusion "Colons fell 60%; P(machine) did not move; quality held." \
379
- --evidence lab:"kernels lab/results/colons/report.md"
382
+ --evidence lab:"kernels lab/results/colons/report.md" --stages rules
380
383
  ```
381
384
 
382
385
  The ledger behind Kernel Tuning's Experiments tab
@@ -728,8 +731,10 @@ AI visibility and page access — the Measure tabs' reads, for scripts and agent
728
731
  ```bash
729
732
  # The Measure Overview: how often AI answers name you and cite your pages (rates
730
733
  # with 95% intervals and gated trends), share of voice, search for your site and
731
- # the phantom network kept apart, and each ranking target's standing.
732
- letterstory insights summary [--period 14d|30d|90d]
734
+ # the phantom network kept apart, and each ranking target's standing. --part
735
+ # (repeatable) returns only those blocks: audience, ai, shareOfVoice, pageAccess,
736
+ # search, actions, targets — `ai` is the slow one on a large org.
737
+ letterstory insights summary [--period 14d|30d|90d] [--part <block>]...
733
738
 
734
739
  # The AI Visibility Bank: which questions named you, cited your pages, or both (a
735
740
  # breakthrough). All time unless --period; --source picks the phantom network
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.66.0";
59
+ export const VERSION = "0.69.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.
@@ -554,19 +554,24 @@ Admin (STAFF key — --staff-key / LETTERSTORY_STAFF_API_KEY; acts before an org
554
554
  Live answer replays: breakthroughs re-asked in ChatGPT /
555
555
  Gemini / Google / Perplexity / Claude, with screenshots.
556
556
  Omit --org for the whole fleet. Needs fleet:read.
557
- admin replay --org <uuid> (<response-id> | --target <uuid>) [--surface openai|anthropic|google-aio|google-gemini|perplexity]
557
+ admin replay --org <uuid> [<response-id> | --target <uuid>] [--surface openai|anthropic|google-aio|google-gemini|perplexity]
558
558
  Replay a breakthrough live now (background; poll
559
- admin replays). Needs fleet:replay.
560
- admin experiments list [--q colons] [--status proposed] | show <id>
561
- | new "<question>" [--hypothesis …] [--plan-file p.json] [--tags a,b] [--as "<agent>"]
562
- | update <id> [--plan-file …] [--result-file …] [--evidence kind:ref,…] [--budget N]
559
+ admin replays). With neither, the client's newest
560
+ breakthrough. Needs fleet:replay.
561
+ admin experiments list [--q colons] [--status proposed] [--primitive write] [--stage rarity] | show <id>
562
+ | new "<what you are investigating>" [--primitive write|research|shred|enhance] [--stages a,b]
563
+ [--hypothesis …] [--plan-file p.json] [--tags a,b] [--as "<agent>"]
564
+ | update <id> [--plan-file …] [--result-file …] [--evidence kind:ref,…] [--stages a,b] [--budget N]
563
565
  | note|started <id> "<text>" | spend <id> <runs>
564
- | conclude <id> --decision ship|drop|follow-up|inconclusive --conclusion "…" [--evidence …]
566
+ | conclude <id> --decision ship|drop|follow-up|inconclusive --conclusion "…" [--stages a,b] [--evidence …]
565
567
  | abandon <id> --reason "…"
566
- The kernel experiment ledger: what we tried,
567
- how, what it found and why. Approving a plan
568
- stays with a signed-in person. Needs the
569
- tuning:experiment capability.
568
+ Each primitive's notebook of investigations:
569
+ what we tried, how, what it found and why,
570
+ and the pipeline stages it is about. A plan
571
+ is approved by a signed-in person before a
572
+ budget is spent; concluding needs evidence,
573
+ not approval. "ship" hands off: a promoter
574
+ makes it live. Needs tuning:experiment.
570
575
  admin tuning-import <brief-results.json> [--experiment <id>]
571
576
  One brief's kernels lab results as a console
572
577
  session (a trial per arm), linked to its
@@ -630,7 +635,11 @@ Admin (STAFF key — --staff-key / LETTERSTORY_STAFF_API_KEY; acts before an org
630
635
  candidate, compare, comparisons, picks, pick,
631
636
  reading-list, pair, side-by-side, promote,
632
637
  sessions, session-new, propose, trial-add,
633
- trial-run, trial-save, and the arena, shredder
638
+ trial-run, trial-save, the Write input bank
639
+ (inputs, input, inputs-harvest, input-save),
640
+ screens (screen-new, screens, screen,
641
+ screen-advance),
642
+ and the arena, shredder
634
643
  and enhancer twins (every flag is listed in
635
644
  cli/lib/commands/admin.mjs).
636
645
  admin create-org --domain <bare-domain> [--name "Acme"] [--test] [--no-key]
@@ -882,7 +891,7 @@ Mail:
882
891
  mail postal-address "<address>" Set the org's CAN-SPAM mailing address (email footers)
883
892
 
884
893
  Insights:
885
- insights summary [--period 14d|30d|90d] Measure Overview: AI named/cited rates, share of voice, search, targets
894
+ insights summary [--period …] [--part <block>]... Measure Overview: AI named/cited rates, share of voice, search, targets
886
895
  insights site [--period 14d|30d|90d] [--collection <uuid>]
887
896
  insights post <article-id> [--period 14d|30d|90d]
888
897
  insights top [--period 14d|30d|90d] [--collection <uuid>] [--limit <n>] [--sort clicks|impressions]
@@ -11,7 +11,7 @@
11
11
  // admin evals scoreboard|cases|runs|run|triage|guard|retire … (fleet:read / tuning:experiment)
12
12
  // admin crm import|capture|signals|record|signal|accounts|account|score|slack-channels|link-slack|white-glove|set-white-glove|churn|record-churn|undo-churn|onboard|onboarding|retry-onboarding|onboard-plan|payment-link … (crm:read / crm:write)
13
13
  // admin replays [--org <id>] [--target <id>] [--status …] [--limit n] (fleet:read)
14
- // admin replay --org <id> (<response-id> | --target <id>) [--surface …] (fleet:replay)
14
+ // admin replay --org <id> [<response-id> | --target <id>] [--surface …] (fleet:replay)
15
15
  // admin create-org --domain acme.com [--name "Acme"] [--test] [--no-key] [--key-name N]
16
16
  // [--capabilities a,b] [--onboard] [--about "…"] [--sitemap <url>]
17
17
  // [--cms <provider>] [--posts N] [--seers suggest|skip] [--no-visuals]
@@ -268,7 +268,8 @@ function evidenceFrom(flags) {
268
268
 
269
269
  function experimentLine(e) {
270
270
  const decision = e.decision ? ` → ${e.decision}` : "";
271
- return `${e.id} [${e.status}${decision}] ${e.question}${e.gist ? `\n ${e.gist}` : ""}`;
271
+ const where = e.primitive ? `${e.primitive}${e.stages?.length ? `: ${e.stages.join(", ")}` : ""} ` : "";
272
+ return `${e.id} [${e.status}${decision}] ${where}${e.question}${e.gist ? `\n ${e.gist}` : ""}`;
272
273
  }
273
274
 
274
275
  /**
@@ -293,7 +294,13 @@ async function cmdAdminExperiments(ctx) {
293
294
  switch (sub) {
294
295
  case "list": {
295
296
  const params = new URLSearchParams(
296
- compact({ q: flagStr(flags.q), status: flagStr(flags.status), limit: flagStr(flags.limit) })
297
+ compact({
298
+ q: flagStr(flags.q),
299
+ status: flagStr(flags.status),
300
+ primitive: flagStr(flags.primitive),
301
+ stage: flagStr(flags.stage),
302
+ limit: flagStr(flags.limit),
303
+ })
297
304
  );
298
305
  const result = await staffRequest(ctx, "GET", `${EXPERIMENTS}?${params}`);
299
306
  return out(result, (r) =>
@@ -306,9 +313,13 @@ async function cmdAdminExperiments(ctx) {
306
313
  case "new": {
307
314
  const question = [id, ...rest].filter(Boolean).join(" ").trim();
308
315
  if (question.length < 3)
309
- throw new CliError('Usage: admin experiments new "<question>" [--plan-file plan.json] [--tags a,b]');
316
+ throw new CliError(
317
+ 'Usage: admin experiments new "<what you are investigating>" [--primitive write|research|shred|enhance] [--stages write,rarity] [--plan-file plan.json] [--tags a,b]'
318
+ );
310
319
  const body = compact({
311
320
  question,
321
+ primitive: flagStr(flags.primitive),
322
+ stages: flagList(flags.stages),
312
323
  restated: flagStr(flags.hypothesis),
313
324
  plan: jsonFile(flags, "plan"),
314
325
  method: flagStr(flags.method),
@@ -331,6 +342,7 @@ async function cmdAdminExperiments(ctx) {
331
342
  result: jsonFile(flags, "result"),
332
343
  method: flagStr(flags.method),
333
344
  tags: flagList(flags.tags),
345
+ stages: flagList(flags.stages),
334
346
  budget_runs: flagNum(flags.budget),
335
347
  engine_build: flagStr(flags.build),
336
348
  add_evidence: evidenceFrom(flags),
@@ -357,12 +369,13 @@ async function cmdAdminExperiments(ctx) {
357
369
  const conclusion = flagStr(flags.conclusion);
358
370
  if (!id || !decision || !conclusion)
359
371
  throw new CliError(
360
- 'Usage: admin experiments conclude <id> --decision ship|drop|follow-up|inconclusive --conclusion "<what we learned and why>" [--evidence kind:ref,…] [--result-file r.json]'
372
+ 'Usage: admin experiments conclude <id> --decision ship|drop|follow-up|inconclusive --conclusion "<what we learned and why>" [--stages write,rarity] [--evidence kind:ref,…] [--result-file r.json]'
361
373
  );
362
374
  const body = compact({
363
375
  action: "conclude",
364
376
  decision,
365
377
  conclusion,
378
+ stages: flagList(flags.stages),
366
379
  result: jsonFile(flags, "result"),
367
380
  add_evidence: evidenceFrom(flags),
368
381
  actor: actorFrom(flags),
@@ -443,13 +456,28 @@ async function cmdAdminTuningContext(ctx) {
443
456
  * admin tuning base-kernels the kernels a session can run on (ids for --kernel)
444
457
  * admin tuning base-versions <kernel-id> the versions a session on it can run against
445
458
  * admin tuning sessions | session <id> [--texts] list / one session: trials, runs and their numbers (--texts adds each trial's latest text)
446
- * admin tuning session-new --title "…" --kernel <id|name> (--input-file f.md | --input "…") [--kind article|outline|brief]
459
+ * admin tuning session-new --title "…" --kernel <id|name> (--input-file f.md | --input "…" | --bank <input-id>) [--kind article|outline|brief]
447
460
  * [--version N] [--writers echo/echo,…] [--trials-from <session-id>]
461
+ * (--bank: a bank input's text, and its request's constraints under every trial)
448
462
  * admin tuning propose <session-id> --text "Echo with rarity on; live with the shred after" [--create]
449
463
  * admin tuning trial-add <session-id> [--name "…"] [--patch '{…}'] [--options '{…}'] [--from <trial-id>]
450
464
  * admin tuning trial-run <trial-id> [--repeats 2] queue runs (guardrails apply)
451
465
  * admin tuning trial-save <trial-id> [--note "…"] save a trial as a candidate version (never live)
452
466
  *
467
+ * The Write input bank: real kernel requests (above all the arena outlines Auto-Pen sends), as sent.
468
+ * admin tuning inputs [--filter "origin=arena_outline;material=thin,typical"] [--q "…"] list by tag, with coverage per bucket
469
+ * admin tuning input <input-id> one input in full: text, constraints, tags, run defaults, origin
470
+ * admin tuning inputs-harvest [--n 40] [--pool 400] [--callers auto-pen,endpoint] [--since-days 21] [--min-per-bucket 3]
471
+ * freeze recent production runs, stratified; idempotent by key
472
+ * admin tuning input-save <session-id> [--note "…"] save a session's pasted input or arena outline to the bank
473
+ *
474
+ * Screens: "is this candidate worth a full comparison?", on a slice of the bank (it does not decide).
475
+ * admin tuning screen-new --title "…" (--from <session-id> | --kernel <kernel-id> --versions 23,29) [--version N]
476
+ * [--n 8] [--filter "origin=arena_outline;material=thin"] [--inputs <id>,…] [--repeats 1] [--lift-budget]
477
+ * one session per input with the same trials; runs queue as the console's caps allow
478
+ * admin tuning screens | screen <id> list / one screen: per candidate, each measure against the Base, with its noise
479
+ * admin tuning screen-advance <id> queue what the caps allow now (the cron does it every minute)
480
+ *
453
481
  * Research (Phase 3): our arena's versions as files, research comparisons, the deploy split.
454
482
  * admin tuning arenas | arena <version-id> our arena's versions / one version's file and diff
455
483
  * admin tuning arena-candidate --patch '{…}' | --file f.json [--of <version-id>] [--note "…"] save a candidate
@@ -492,6 +520,19 @@ function jsonFlag(raw, name) {
492
520
  }
493
521
  }
494
522
 
523
+ /**
524
+ * `--filter "origin=arena_outline;material=thin,typical"` → the bank's query params (one comma list
525
+ * per tag dimension; lib/tuning/input-tags.ts `parseTagFilter` reads the same form).
526
+ */
527
+ function tagFilterParams(raw) {
528
+ const out = {};
529
+ for (const part of (raw ?? "").split(";")) {
530
+ const [k, v] = part.split("=");
531
+ if (k?.trim() && v?.trim()) out[k.trim()] = v.trim();
532
+ }
533
+ return out;
534
+ }
535
+
495
536
  /**
496
537
  * A session as an agent reads it: each trial's changes and runs with their headline numbers, and
497
538
  * (with `texts`) the latest finished text per trial. The full session (traces, records) is `--json`.
@@ -770,9 +811,11 @@ async function cmdAdminTuning(ctx) {
770
811
  throw new CliError(`--input-file ${path}: ${err.message}`);
771
812
  }
772
813
  }
773
- if (!title || !kernel || !input)
814
+ // A bank input instead of a paste: its text, and its request's constraints under every trial.
815
+ const bank = flagStr(flags.bank);
816
+ if (!title || !kernel || (!input && !bank))
774
817
  throw new CliError(
775
- 'Usage: admin tuning session-new --title "…" --kernel <id|name> (--input-file f.md | --input "…") [--kind article|outline|brief] [--version N] [--writers a,b] [--versions 23,29] [--trials-from <session-id>]'
818
+ 'Usage: admin tuning session-new --title "…" --kernel <id|name> (--input-file f.md | --input "…" | --bank <input-id>) [--kind article|outline|brief] [--version N] [--writers a,b] [--versions 23,29] [--trials-from <session-id>]'
776
819
  );
777
820
  let kernelId = kernel;
778
821
  if (!/^[0-9a-f-]{36}$/i.test(kernel)) {
@@ -788,8 +831,9 @@ async function cmdAdminTuning(ctx) {
788
831
  T,
789
832
  compact({
790
833
  title,
791
- input_text: input,
834
+ input_text: bank ? undefined : input,
792
835
  input_kind: flagStr(flags.kind) ?? "unknown",
836
+ material: bank ? { source: "bank", input_id: bank } : undefined,
793
837
  base_kernel_id: kernelId,
794
838
  base_version: flagNum(flags.version),
795
839
  trials_from_session_id: flagStr(flags["trials-from"]),
@@ -871,6 +915,167 @@ async function cmdAdminTuning(ctx) {
871
915
  `${r.kernel}: saved as candidate v${r.version}${r.baseVersion ? ` (from v${r.baseVersion})` : ""}${r.leftBehind.length ? `; per-run, not saved: ${r.leftBehind.join(", ")}` : ""}`
872
916
  );
873
917
  }
918
+ // --- The Write input bank: real kernel requests a session can start from ---
919
+ case "inputs": {
920
+ const q = new URLSearchParams(
921
+ compact({ ...tagFilterParams(flagStr(flags.filter)), q: flagStr(flags.q), limit: flagStr(flags.limit) })
922
+ );
923
+ const r = await staffRequest(ctx, "GET", `${T}/inputs${q.size ? `?${q}` : ""}`);
924
+ if (json) return printResult(io, flags, r);
925
+ const coverage = Object.entries(r.coverage)
926
+ .map(
927
+ ([dim, buckets]) =>
928
+ ` ${dim.padEnd(11)} ${Object.entries(buckets)
929
+ .map(([b, n]) => `${b} ${n}`)
930
+ .join(" · ")}`
931
+ )
932
+ .join("\n");
933
+ return io.log(
934
+ [
935
+ `bank ${r.set}: ${r.inputs.length} of ${r.total} input(s)`,
936
+ ...r.inputs.map(
937
+ (i) =>
938
+ `${i.id} ${i.source.padEnd(10)} ${String(i.tags.origin ?? "").padEnd(13)} ${String(i.tags.material?.bucket ?? "").padEnd(7)} ${String(i.tags.shape?.bucket ?? "").padEnd(5)} ${i.title}`
939
+ ),
940
+ "coverage (the whole bank):",
941
+ coverage,
942
+ ].join("\n")
943
+ );
944
+ }
945
+ case "input": {
946
+ if (!id) throw new CliError("Usage: admin tuning input <input-id>");
947
+ return printResult(io, { ...flags, json: true }, await staffRequest(ctx, "GET", `${T}/inputs/${id}`));
948
+ }
949
+ case "inputs-harvest": {
950
+ const r = await staffRequest(
951
+ ctx,
952
+ "POST",
953
+ `${T}/inputs/harvest`,
954
+ compact({
955
+ n: flagNum(flags.n),
956
+ pool: flagNum(flags.pool),
957
+ callers: flagStr(flags.callers)
958
+ ?.split(",")
959
+ .map((c) => c.trim())
960
+ .filter(Boolean),
961
+ since_days: flagNum(flags["since-days"]),
962
+ min_per_bucket: flagNum(flags["min-per-bucket"]),
963
+ })
964
+ );
965
+ if (json) return printResult(io, flags, r);
966
+ return io.log(
967
+ [
968
+ `added ${r.added.length} input(s) to ${r.set} from a pool of ${r.pool} run(s); ${r.alreadyHeld} already held`,
969
+ ...r.added.map((a) => ` ${a.key} (${a.reason})`),
970
+ ...Object.entries(r.skipped).map(([why, n]) => ` skipped ${n}: ${why}`),
971
+ ].join("\n")
972
+ );
973
+ }
974
+ case "input-save": {
975
+ if (!id) throw new CliError('Usage: admin tuning input-save <session-id> [--note "…"]');
976
+ const r = await staffRequest(
977
+ ctx,
978
+ "POST",
979
+ `${T}/sessions/${id}/bank`,
980
+ compact({ note: flagStr(flags.note) })
981
+ );
982
+ return json ? printResult(io, flags, r) : io.log(`bank input ${r.input.id} (${r.input.key})`);
983
+ }
984
+ // --- Screens: candidates across a slice of the bank, read against the Base, beside the noise ---
985
+ case "screen-new": {
986
+ const title = flagStr(flags.title);
987
+ const from = flagStr(flags.from);
988
+ const versions = (flagStr(flags.versions) ?? "")
989
+ .split(",")
990
+ .map((v) => Number(v.trim()))
991
+ .filter((v) => Number.isInteger(v) && v > 0);
992
+ if (!title || (!from && !(flagStr(flags.kernel) && versions.length)))
993
+ throw new CliError(
994
+ 'Usage: admin tuning screen-new --title "…" (--from <session-id> | --kernel <kernel-id> --versions 23,29) [--version N] [--n 8] [--filter "origin=arena_outline;material=thin"] [--inputs <id>,…] [--repeats 1] [--lift-budget]'
995
+ );
996
+ const filter = Object.fromEntries(
997
+ Object.entries(tagFilterParams(flagStr(flags.filter))).map(([k, v]) => [
998
+ k,
999
+ v
1000
+ .split(",")
1001
+ .map((x) => x.trim())
1002
+ .filter(Boolean),
1003
+ ])
1004
+ );
1005
+ const inputIds = (flagStr(flags.inputs) ?? "")
1006
+ .split(",")
1007
+ .map((x) => x.trim())
1008
+ .filter(Boolean);
1009
+ const r = await staffRequest(
1010
+ ctx,
1011
+ "POST",
1012
+ `${T}/screens`,
1013
+ compact({
1014
+ title,
1015
+ from_session_id: from,
1016
+ base_kernel_id: flagStr(flags.kernel),
1017
+ base_version: flagNum(flags.version),
1018
+ versions: versions.length ? versions : undefined,
1019
+ n: flagNum(flags.n),
1020
+ filter: Object.keys(filter).length ? filter : undefined,
1021
+ input_ids: inputIds.length ? inputIds : undefined,
1022
+ repeats: flagNum(flags.repeats),
1023
+ lift_budget: flagBool(flags["lift-budget"]) || undefined,
1024
+ })
1025
+ );
1026
+ if (json) return printResult(io, flags, r);
1027
+ return io.log(
1028
+ `screen ${r.screen.id}: ${r.screen.inputs} input(s), ${r.aggregate.columns.length} column(s); ${r.screen.pending} run(s) waiting${r.screen.waiting ? ` (${r.screen.waiting})` : ""} → ${ctx.client.url}/superadmin/tuning/screens/${r.screen.id}`
1029
+ );
1030
+ }
1031
+ case "screens": {
1032
+ const r = await staffRequest(ctx, "GET", `${T}/screens?limit=${flagNum(flags.limit) ?? 30}`);
1033
+ if (json) return printResult(io, flags, r);
1034
+ return io.log(
1035
+ r.screens
1036
+ .map(
1037
+ (x) =>
1038
+ `${x.id} ${x.status.padEnd(7)} ${x.title} · ${x.inputs} inputs${x.pending ? ` · ${x.pending} waiting` : ""}`
1039
+ )
1040
+ .join("\n") || "(no screens)"
1041
+ );
1042
+ }
1043
+ case "screen": {
1044
+ if (!id) throw new CliError("Usage: admin tuning screen <screen-id>");
1045
+ const r = await staffRequest(ctx, "GET", `${T}/screens/${id}`);
1046
+ if (json) return printResult(io, flags, r);
1047
+ // The reading as an agent reads it: per candidate, each measure's difference from the Base and its noise.
1048
+ return printResult(
1049
+ io,
1050
+ { ...flags, json: true },
1051
+ {
1052
+ screen: r.screen,
1053
+ note: r.aggregate.note,
1054
+ columns: r.aggregate.columns,
1055
+ measures: r.aggregate.measures.map((m) => ({
1056
+ measure: m.label,
1057
+ judge: m.judgeId,
1058
+ standing: m.standing,
1059
+ sigma: m.sigma,
1060
+ per_column: m.perTrial.map((p) =>
1061
+ compact({
1062
+ column: p.column,
1063
+ n: p.n,
1064
+ mean: p.mean,
1065
+ delta: p.delta ?? undefined,
1066
+ })
1067
+ ),
1068
+ })),
1069
+ }
1070
+ );
1071
+ }
1072
+ case "screen-advance": {
1073
+ if (!id) throw new CliError("Usage: admin tuning screen-advance <screen-id>");
1074
+ const r = await staffRequest(ctx, "POST", `${T}/screens/${id}/advance`, {});
1075
+ return json
1076
+ ? printResult(io, flags, r)
1077
+ : io.log(`queued ${r.queued} run(s)${r.waiting ? `; waiting: ${r.waiting}` : ""}`);
1078
+ }
874
1079
  // --- Research: our arena's versions, research comparisons, the deploy split ---
875
1080
  case "arenas": {
876
1081
  const r = await staffRequest(ctx, "GET", `${T}/arena-files`);
@@ -1977,7 +2182,6 @@ async function cmdAdminReplays(ctx, sub) {
1977
2182
  const responseId = positionals[0];
1978
2183
  const target = flagStr(flags.target);
1979
2184
  if (!org) throw new CliError("admin replay needs --org <org-id>");
1980
- if (!responseId && !target) throw new CliError("Pass a breakthrough <response-id>, or --target <target-id>");
1981
2185
  const result = await staffPost(
1982
2186
  ctx,
1983
2187
  REPLAYS,
@@ -21,6 +21,7 @@ export const ARGS = {
21
21
  },
22
22
  get_measure_summary: {
23
23
  period: { flag: "period", enum: PERIODS },
24
+ parts: { flag: "part", type: "list" },
24
25
  },
25
26
  };
26
27
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letterstory/cli",
3
- "version": "0.66.0",
3
+ "version": "0.69.0",
4
4
  "description": "Spin up and manage Letterstory phantom blogs from your terminal.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",