@letterstory/cli 0.72.2 → 0.73.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/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.2";
59
+ export const VERSION = "0.73.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.
@@ -639,8 +639,9 @@ Admin (STAFF key — --staff-key / LETTERSTORY_STAFF_API_KEY; acts before an org
639
639
  candidate, compare, comparisons, picks, pick,
640
640
  ledger, reading-list, finalists,
641
641
  finalists-queue, pair, side-by-side,
642
- evals, eval-add, eval-set, proposals,
643
- propose-eval, capture-eval, reject-eval, promote,
642
+ checklist, checklist-add, checklist-set,
643
+ checklist-labels, feedback, feedback-add,
644
+ feedback-file, feedback-reject, promote,
644
645
  sessions, session-new, propose, trial-add,
645
646
  trial-run, trial-save, the Write input bank
646
647
  (inputs, input, inputs-harvest, input-save),
@@ -8,8 +8,9 @@
8
8
  // admin tuning-context [--q "<question>"] (tuning:experiment)
9
9
  // admin tuning-import <brief-results.json> [--experiment <id>] (tuning:experiment)
10
10
  // admin tuning kernels|kernel|candidate|compare|comparisons|comparison|picks|pick|criteria|promote … (tuning:experiment)
11
- // admin tuning evals|eval-add|eval-set|proposals|propose-eval|capture-eval|reject-eval … (tuning:experiment)
12
- // (propose-eval --fix "…" files the reader's rewrite; the diff is classified to a lever, or to a schema gap: kind lever)
11
+ // admin tuning checklist|checklist-add|checklist-set|checklist-labels|feedback|feedback-add|feedback-file|feedback-reject … (tuning:experiment)
12
+ // (feedback-add --fix "…" files the reader's rewrite; the diff is classified and a disposition suggested.
13
+ // The old names — evals, eval-add, eval-set, proposals, propose-eval, capture-eval, reject-eval — still work.)
13
14
  // admin evals scoreboard|cases|runs|run|triage|guard|retire … (fleet:read / tuning:experiment)
14
15
  // 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)
15
16
  // admin replays [--org <id>] [--target <id>] [--status …] [--limit n] (fleet:read)
@@ -449,15 +450,19 @@ async function cmdAdminTuningContext(ctx) {
449
450
  * admin tuning criteria the Eye Test's criteria: id, label and the question each asks of a pair
450
451
  * admin tuning loop where the loop stands: what waits at each station, and where to go next
451
452
  * admin tuning ledger <comparison-id> the defect ledger: per arm, what the diagnosis read named, the floor and the ceiling, the candidate's next arm; then the reader edits per arm by lever, and the schema gaps
452
- * admin tuning evals the eval list: every defect category with its status and the picks' agreement (n, agreement, suggestion)
453
- * admin tuning eval-add --id … --label … --how … --layer … --lever … --locality local|structural --axis … [--example "span"] add a category (starts unvetted)
454
- * admin tuning eval-set <id> [--status confirmed|rejected|unvetted] [--example "span" [--fix "…"]] confirm / reject (a person's call), or add an example
455
- * admin tuning proposals [--status proposed|captured|rejected] the eval proposals: spans readers marked in a pair's text with their words, and what became of each
456
- * admin tuning propose-eval --span "…" --comment "…" [--fix "…"] [--comparison <id> --cell <key> --side control|candidate] [--kind narrow|wide] mark a span as an eval (no --side from a blind read); --fix is the reader's rewrite, classified to a lever (or a schema gap: kind lever)
457
- * admin tuning capture-eval <proposal-id> --as category --id … --label … --how … --layer … --lever … --locality local|structural --axis … capture it into the eval list, the span (and its fix) as the category's example (appends when the id exists)
458
- * admin tuning capture-eval <proposal-id> --as rule file it as narrow; the rule itself is written in the kernels rule lab
459
- * admin tuning capture-eval <proposal-id> --as lever [--note "…"] file it as a schema gap: an edit no kernel-file lever can produce, for the kernels repo
460
- * admin tuning reject-eval <proposal-id> a span that was not an eval after all
453
+ * admin tuning checklist the checklist: every defect category with its status, the picks' agreement (n, agreement, suggestion) and its labels (+/−) (alias: evals)
454
+ * admin tuning checklist-add --id … --label … --how … --layer … --lever … --locality local|structural --axis … [--example "span"] add a category (starts unvetted) (alias: eval-add)
455
+ * admin tuning checklist-set <id> [--status confirmed|rejected|unvetted] [--example "span" [--fix "…"]] confirm / reject (a person's call), or add an example (alias: eval-set)
456
+ * admin tuning checklist-labels <id> the labelled set of one category: positives (examples + feedback filed into it), negatives (rejected feedback suggested for it), grid overrules
457
+ * admin tuning feedback [--status proposed|captured|rejected] the feedback: spans readers marked in a pair's text with their words, each with its disposition (alias: proposals)
458
+ * admin tuning feedback-add --span "…" --comment "…" [--fix "…"] [--comparison <id> --cell <key> --side control|candidate] [--kind narrow|wide] add feedback (no --side from a blind read); --fix is the reader's rewrite, classified and a disposition suggested (alias: propose-eval)
459
+ * admin tuning feedback-file <id> --as checklist --id … --label … --how … --layer … --lever … --locality local|structural --axis … file it on the checklist, the span (and its fix) as the category's example (appends when the id exists) (alias: capture-eval; --as category)
460
+ * admin tuning feedback-file <id> --as rule file it as a rule; the rule itself is written in the kernels rule lab
461
+ * admin tuning feedback-file <id> --as config [--note "…"] file it as a lever this candidate should pull
462
+ * admin tuning feedback-file <id> --as implementation [--note "…"] file it as a schema gap no setting can express, for the kernels repo (--as lever still works)
463
+ * admin tuning feedback-file <id> --as material [--note "…"] file it as the material's: the input never carried it
464
+ * admin tuning feedback-file <id> --as note keep it; no action
465
+ * admin tuning feedback-reject <id> feedback that was not worth keeping after all (alias: reject-eval)
461
466
  * admin tuning reading-list <comparison-id> [--n 10] your next blind pairs as links, to read off the screen
462
467
  * admin tuning finalists [--limit 10] the finalists feed: done, gates clean, no craft loss, not yet decided by the picks; ranked by the objective's order
463
468
  * admin tuning finalists-queue [--n 10] your next blind pairs round-robin across the finalists (a key reads the queue of the person who owns it; recording a pick still needs a person)
@@ -473,6 +478,8 @@ async function cmdAdminTuningContext(ctx) {
473
478
  * admin tuning bank-score <eval-id> --version N score a judge version over the pool (call again for more than 600)
474
479
  * 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
480
  * admin tuning people | invite --email e [--note "…"] | revoke <id> who an admin invited into the console as an editor
481
+ * admin tuning assignments | assign --pick <comparison-id>|--label <eval-id> [--to e] [--instructions "…"] [--target N] | unassign <id> what editors are asked to do
482
+ * admin tuning tasks your own open tasks, with progress
476
483
  * 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
484
  * admin tuning candidates --kernels Hazel,Aspen|all --patch '{…}' [--base N] [--note "…"] the same changes as a candidate of each kernel
478
485
  * admin tuning promote <experiment-id> --versions <service-kernel-id>:<version>,… promote from a "ship" decision
@@ -505,8 +512,8 @@ async function cmdAdminTuningContext(ctx) {
505
512
  * admin tuning screens | screen <id> list / one screen: per candidate, each measure against the Base, with its noise
506
513
  * admin tuning screen-advance <id> queue what the caps allow now (the cron does it every minute)
507
514
  *
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
515
+ * The bake-off: a screen's versions graded item by item against the checklist's confirmed and
516
+ * unvetted categories and the craft axes commitment / economy / movement, the winner
510
517
  * per item tallied across the inputs, and a merge that composes the best of each into one candidate.
511
518
  * Deterministic; a person overrides a grade and confirms the merge, which is a hypothesis until the
512
519
  * eye test confirms it.
@@ -791,6 +798,7 @@ async function cmdAdminTuning(ctx) {
791
798
  })
792
799
  );
793
800
  }
801
+ case "checklist":
794
802
  case "evals": {
795
803
  const r = await staffRequest(ctx, "GET", `${T}/evals`);
796
804
  if (json) return printResult(io, flags, r);
@@ -798,16 +806,29 @@ async function cmdAdminTuning(ctx) {
798
806
  if (!rows.length) return io.log("(no categories)");
799
807
  const cell = (v, w) => String(v ?? "—").padEnd(w);
800
808
  io.log(
801
- `${cell("id", 24)} ${cell("status", 10)} ${cell("layer", 10)} ${cell("lever", 30)} ${"n".padStart(4)} ${"agree".padStart(6)} ${cell("suggest", 10)} ${"ex".padStart(3)}`
809
+ `${cell("id", 24)} ${cell("status", 10)} ${cell("layer", 10)} ${cell("lever", 30)} ${"n".padStart(4)} ${"agree".padStart(6)} ${cell("suggest", 10)} ${"+".padStart(3)} ${"−".padStart(3)}`
802
810
  );
803
811
  for (const c of rows) {
804
812
  const v = c.vetting ?? {};
813
+ const l = c.labels ?? { positives: (c.examples ?? []).length, negatives: 0 };
805
814
  io.log(
806
- `${cell(c.id, 24)} ${cell(c.status, 10)} ${cell(c.layer, 10)} ${cell(c.lever || "—", 30)} ${String(v.n ?? 0).padStart(4)} ${String(v.agreement ?? "—").padStart(6)} ${cell(v.suggestion, 10)} ${String((c.examples ?? []).length).padStart(3)}`
815
+ `${cell(c.id, 24)} ${cell(c.status, 10)} ${cell(c.layer, 10)} ${cell(c.lever || "—", 30)} ${String(v.n ?? 0).padStart(4)} ${String(v.agreement ?? "—").padStart(6)} ${cell(v.suggestion, 10)} ${String(l.positives).padStart(3)} ${String(l.negatives).padStart(3)}`
807
816
  );
808
817
  }
809
818
  return;
810
819
  }
820
+ case "checklist-labels": {
821
+ if (!id) throw new CliError("Usage: admin tuning checklist-labels <category-id>");
822
+ const r = await staffRequest(ctx, "GET", `${T}/evals/${encodeURIComponent(id)}/labels`);
823
+ if (json) return printResult(io, flags, r);
824
+ io.log(
825
+ `${r.category_id}: ${r.counts.positives} positive, ${r.counts.negatives} negative (${r.grid_overrules} grid overrule${r.grid_overrules === 1 ? "" : "s"})`
826
+ );
827
+ for (const x of r.positives ?? []) io.log(` + [${x.source}] "${x.span}"${x.fix ? ` → "${x.fix}"` : ""}`);
828
+ for (const x of r.negatives ?? []) io.log(` − [${x.source}] "${x.span}"${x.fix ? ` → "${x.fix}"` : ""}`);
829
+ return;
830
+ }
831
+ case "checklist-add":
811
832
  case "eval-add": {
812
833
  const body = {
813
834
  id: flagStr(flags.id),
@@ -828,25 +849,26 @@ async function cmdAdminTuning(ctx) {
828
849
  !["local", "structural"].includes(body.locality ?? "")
829
850
  )
830
851
  throw new CliError(
831
- 'Usage: admin tuning eval-add --id … --label … --how … --layer … --lever … --locality local|structural --axis … [--example "span"]'
852
+ 'Usage: admin tuning checklist-add --id … --label … --how … --layer … --lever … --locality local|structural --axis … [--example "span"]'
832
853
  );
833
854
  if (example) body.examples = [{ span: example, ...(flags.fix ? { fix: flagStr(flags.fix) } : {}) }];
834
855
  const r = await staffRequest(ctx, "POST", `${T}/evals`, body);
835
856
  return json ? printResult(io, flags, r) : io.log(`added ${r.category.id} (${r.category.status})`);
836
857
  }
858
+ case "checklist-set":
837
859
  case "eval-set": {
838
860
  const status = flagStr(flags.status);
839
861
  const example = flagStr(flags.example);
840
862
  if (!id || (!status && !example) || (status && !["confirmed", "rejected", "unvetted"].includes(status)))
841
863
  throw new CliError(
842
- 'Usage: admin tuning eval-set <id> [--status confirmed|rejected|unvetted] [--example "span" [--fix "…"]]'
864
+ 'Usage: admin tuning checklist-set <id> [--status confirmed|rejected|unvetted] [--example "span" [--fix "…"]]'
843
865
  );
844
866
  const patch = {};
845
867
  if (status) patch.status = status;
846
868
  if (example) {
847
869
  // The PATCH replaces the whole list: read what is stored and append.
848
870
  const current = (await staffRequest(ctx, "GET", `${T}/evals`)).categories?.find((c) => c.id === id);
849
- if (!current) throw new CliError(`No category ${id} in the eval list`);
871
+ if (!current) throw new CliError(`No category ${id} on the checklist`);
850
872
  const fix = flagStr(flags.fix);
851
873
  patch.examples = [...(current.examples ?? []), { span: example, ...(fix ? { fix } : {}) }];
852
874
  }
@@ -855,27 +877,32 @@ async function cmdAdminTuning(ctx) {
855
877
  ? printResult(io, flags, r)
856
878
  : io.log(`${r.category.id}: ${r.category.status}, ${(r.category.examples ?? []).length} examples`);
857
879
  }
880
+ case "feedback":
858
881
  case "proposals": {
859
882
  const status = flagStr(flags.status);
860
883
  if (status && !["proposed", "captured", "rejected"].includes(status))
861
- throw new CliError("Usage: admin tuning proposals [--status proposed|captured|rejected]");
884
+ throw new CliError("Usage: admin tuning feedback [--status proposed|captured|rejected]");
862
885
  const qs = status ? `?${new URLSearchParams({ status })}` : "";
863
886
  const r = await staffRequest(ctx, "GET", `${T}/proposals${qs}`);
864
887
  if (json) return printResult(io, flags, r);
865
888
  const rows = r.proposals ?? [];
866
- if (!rows.length) return io.log("(no proposals)");
889
+ if (!rows.length) return io.log("(no feedback)");
867
890
  for (const p of rows) {
868
891
  const where = [p.cell_key, p.side].filter(Boolean).join(" / ") || "—";
892
+ const suggested = p.suggested_disposition
893
+ ? `; suggested ${p.suggested_disposition}${p.suggested_category ? ` (${p.suggested_category})` : ""}`
894
+ : "";
869
895
  const edit =
870
896
  p.fix != null
871
- ? `\n → "${p.fix}" [${p.edit_class ?? "?"} → ${p.lever ?? "no lever: schema gap"}]`
897
+ ? `\n → "${p.fix}" [${p.edit_class ?? "?"} → ${p.lever ?? "no lever: schema gap"}${suggested}]`
872
898
  : "";
873
899
  io.log(
874
- `${p.id} ${p.status.padEnd(9)} ${p.kind.padEnd(6)} ${where}${p.category_id ? ` → ${p.category_id}` : ""}\n "${p.span}"${edit}${p.comment ? `\n ${p.comment}` : ""}`
900
+ `${p.id} ${p.status.padEnd(9)} ${(p.disposition ?? p.kind).padEnd(14)} ${where}${p.category_id ? ` → ${p.category_id}` : ""}\n "${p.span}"${edit}${p.comment ? `\n ${p.comment}` : ""}`
875
901
  );
876
902
  }
877
903
  return;
878
904
  }
905
+ case "feedback-add":
879
906
  case "propose-eval": {
880
907
  const span = flagStr(flags.span);
881
908
  const side = flagStr(flags.side);
@@ -886,7 +913,7 @@ async function cmdAdminTuning(ctx) {
886
913
  (kind && !["narrow", "wide"].includes(kind))
887
914
  )
888
915
  throw new CliError(
889
- 'Usage: admin tuning propose-eval --span "…" --comment "…" [--fix "…"] [--comparison <id> --cell <key> --side control|candidate] [--kind narrow|wide]'
916
+ 'Usage: admin tuning feedback-add --span "…" --comment "…" [--fix "…"] [--comparison <id> --cell <key> --side control|candidate] [--kind narrow|wide]'
890
917
  );
891
918
  const r = await staffRequest(
892
919
  ctx,
@@ -904,17 +931,27 @@ async function cmdAdminTuning(ctx) {
904
931
  );
905
932
  if (json) return printResult(io, flags, r);
906
933
  const p = r.proposal;
907
- const edit = p.fix != null ? ` — ${p.edit_class}: ${p.lever ?? "no lever maps: a schema gap"}` : "";
908
- return io.log(`proposed ${p.id} (${p.kind})${edit}`);
909
- }
934
+ const suggested = p.suggested_disposition
935
+ ? `; suggested ${p.suggested_disposition}${p.suggested_category ? ` (${p.suggested_category})` : ""}`
936
+ : "";
937
+ const edit =
938
+ p.fix != null ? ` — ${p.edit_class}: ${p.lever ?? "no lever maps: a schema gap"}${suggested}` : "";
939
+ return io.log(`added feedback ${p.id} (${p.disposition ?? p.kind})${edit}`);
940
+ }
941
+ case "feedback-file":
910
942
  case "capture-eval": {
911
- const as = flagStr(flags.as);
943
+ // The first names of checklist and implementation, still accepted.
944
+ const AS_ALIAS = { category: "checklist", lever: "implementation" };
945
+ const asGiven = flagStr(flags.as);
946
+ const as = AS_ALIAS[asGiven] ?? asGiven;
912
947
  const usage =
913
- 'Usage: admin tuning capture-eval <proposal-id> --as category --id … --label … --how … --layer … --lever … --locality local|structural --axis … | --as rule | --as lever [--note "…"]';
914
- if (!id || !["category", "rule", "lever"].includes(as ?? "")) throw new CliError(usage);
948
+ 'Usage: admin tuning feedback-file <id> --as checklist --id … --label … --how … --layer … --lever … --locality local|structural --axis … | --as rule | --as config|implementation|material [--note "…"] | --as note';
949
+ if (!id || !["checklist", "rule", "config", "implementation", "material", "note"].includes(as ?? ""))
950
+ throw new CliError(usage);
915
951
  let body = { as };
916
- if (as === "lever") body = compact({ as, note: flagStr(flags.note) });
917
- if (as === "category") {
952
+ if (as === "config" || as === "implementation" || as === "material")
953
+ body = compact({ as, note: flagStr(flags.note) });
954
+ if (as === "checklist") {
918
955
  const category = {
919
956
  id: flagStr(flags.id),
920
957
  label: flagStr(flags.label),
@@ -936,14 +973,22 @@ async function cmdAdminTuning(ctx) {
936
973
  body = { as, category };
937
974
  }
938
975
  const r = await staffRequest(ctx, "POST", `${T}/proposals/${id}/capture`, body);
939
- return json
940
- ? printResult(io, flags, r)
941
- : io.log(
942
- `captured ${r.proposal.id} as ${r.proposal.kind}${r.category ? ` → ${r.category.id} (${(r.category.examples ?? []).length} examples)` : r.proposal.kind === "lever" ? "; a schema gap, filed for the kernels repo" : "; write the rule in the kernels rule lab"}`
943
- );
976
+ if (json) return printResult(io, flags, r);
977
+ const FILED = {
978
+ rule: "; write the rule in the kernels rule lab",
979
+ implementation: "; a schema gap, filed for the kernels repo",
980
+ config: "; a lever for this candidate",
981
+ material: "; the input never carried it",
982
+ note: "; kept, no action",
983
+ };
984
+ const d = r.proposal.disposition ?? as;
985
+ return io.log(
986
+ `filed ${r.proposal.id} as ${d}${r.category ? ` → ${r.category.id} (${(r.category.examples ?? []).length} examples)` : (FILED[d] ?? "")}`
987
+ );
944
988
  }
989
+ case "feedback-reject":
945
990
  case "reject-eval": {
946
- if (!id) throw new CliError("Usage: admin tuning reject-eval <proposal-id>");
991
+ if (!id) throw new CliError("Usage: admin tuning feedback-reject <id>");
947
992
  const r = await staffRequest(ctx, "POST", `${T}/proposals/${id}/reject`, {});
948
993
  return json ? printResult(io, flags, r) : io.log(`rejected ${r.proposal.id}`);
949
994
  }
@@ -956,11 +1001,13 @@ async function cmdAdminTuning(ctx) {
956
1001
  case "loop": {
957
1002
  const r = await staffRequest(ctx, "GET", `${T}/loop`);
958
1003
  if (json) return printResult(io, flags, r);
959
- io.log(`propose ${r.propose.running} running`);
1004
+ const week = r.propose.candidates_week ?? "?";
1005
+ const progs = r.propose.programmes_running ?? "?";
1006
+ io.log(`propose ${r.propose.running} running, ${week} candidates this week, ${progs} programmes running`);
960
1007
  io.log(`read ${r.read.settled_unread} settled, unread`);
961
1008
  io.log(`eye test ${r.eye_test.finalists} finalists, ${r.eye_test.pairs_to_read} pairs to read`);
962
1009
  io.log(
963
- `evals ${r.evals.proposals_waiting} proposals waiting, ${r.evals.unvetted} unvetted, ${r.evals.suggested} with a suggestion${r.evals.reachable ? "" : " (the eval list could not be read)"}`
1010
+ `checklist ${r.evals.proposals_waiting} feedback waiting, ${r.evals.unvetted} unvetted, ${r.evals.suggested} with a suggestion${r.evals.reachable ? "" : " (the checklist could not be read)"}`
964
1011
  );
965
1012
  io.log(`promote ${r.promote.promotable} won their picks`);
966
1013
  io.log(`\nnext: ${r.next}`);
@@ -1187,6 +1234,48 @@ async function cmdAdminTuning(ctx) {
1187
1234
  `${ctx.client.url}/superadmin/tuning?${new URLSearchParams({ tab: "compare", comparison: r.comparison_id })}`
1188
1235
  );
1189
1236
  }
1237
+ case "assignments": {
1238
+ const r = await staffRequest(ctx, "GET", `${T}/assignments${flagBool(flags.closed) ? "?closed=1" : ""}`);
1239
+ if (json) return printResult(io, flags, r);
1240
+ for (const a of r.assignments)
1241
+ io.log(
1242
+ `${a.id} ${a.kind} "${a.title}" → ${a.assignee_email ?? "every editor"}${a.target ? `, ${a.target} asked` : ""}${a.closed_at ? " (closed)" : ""}`
1243
+ );
1244
+ return;
1245
+ }
1246
+ case "assign": {
1247
+ const pick = flagStr(flags.pick);
1248
+ const label = flagStr(flags.label);
1249
+ if (!pick === !label)
1250
+ throw new CliError(
1251
+ 'Usage: admin tuning assign --pick <comparison-id> | --label <eval-id> [--to email] [--instructions "…"] [--title "…"] [--target N]'
1252
+ );
1253
+ const body = compact({
1254
+ ...(pick ? { kind: "pick", comparison_id: pick } : { kind: "label", eval_id: label }),
1255
+ assignee_email: flagStr(flags.to),
1256
+ instructions: flagStr(flags.instructions),
1257
+ title: flagStr(flags.title),
1258
+ target: flagNum(flags.target),
1259
+ });
1260
+ const r = await staffRequest(ctx, "POST", `${T}/assignments`, body);
1261
+ if (json) return printResult(io, flags, r);
1262
+ return io.log(
1263
+ `assigned ${r.assignment.kind} task "${r.assignment.title}" to ${r.assignment.assignee_email ?? "every editor"} (${r.assignment.id})`
1264
+ );
1265
+ }
1266
+ case "unassign": {
1267
+ if (!id) throw new CliError("Usage: admin tuning unassign <assignment-id>");
1268
+ return printResult(io, flags, await staffRequest(ctx, "DELETE", `${T}/assignments/${id}`));
1269
+ }
1270
+ case "tasks": {
1271
+ const r = await staffRequest(ctx, "GET", `${T}/editor/tasks`);
1272
+ if (json) return printResult(io, flags, r);
1273
+ for (const t of r.tasks)
1274
+ io.log(
1275
+ `${t.id} ${t.kind} "${t.title}": ${t.done} done, ${t.remaining} left${t.complete ? " (complete)" : ""}`
1276
+ );
1277
+ return;
1278
+ }
1190
1279
  case "people": {
1191
1280
  const r = await staffRequest(ctx, "GET", `${T}/people`);
1192
1281
  if (json) return printResult(io, flags, r);
@@ -2144,7 +2233,7 @@ async function cmdAdminTuning(ctx) {
2144
2233
  }
2145
2234
  default:
2146
2235
  throw new CliError(
2147
- "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)"
2236
+ "Usage: admin tuning kernels|kernel|candidate|compare|comparisons|comparison|picks|pick|loop|ledger|checklist|checklist-add|checklist-set|checklist-labels|feedback|feedback-add|feedback-file|feedback-reject|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)"
2148
2237
  );
2149
2238
  }
2150
2239
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letterstory/cli",
3
- "version": "0.72.2",
3
+ "version": "0.73.1",
4
4
  "description": "Spin up and manage Letterstory phantom blogs from your terminal.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",