@letterstory/cli 0.70.2 → 0.70.5
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 +3 -2
- package/lib/commands/admin.mjs +123 -9
- package/package.json +1 -1
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.70.
|
|
59
|
+
export const VERSION = "0.70.5";
|
|
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.
|
|
@@ -634,7 +634,8 @@ Admin (STAFF key — --staff-key / LETTERSTORY_STAFF_API_KEY; acts before an org
|
|
|
634
634
|
registry the Pipeline tab draws (JSON).
|
|
635
635
|
admin tuning <sub> … The console's loop over a staff key: kernels,
|
|
636
636
|
candidate, compare, comparisons, picks, pick,
|
|
637
|
-
reading-list, pair, side-by-side,
|
|
637
|
+
ledger, reading-list, pair, side-by-side,
|
|
638
|
+
evals, eval-add, eval-set, promote,
|
|
638
639
|
sessions, session-new, propose, trial-add,
|
|
639
640
|
trial-run, trial-save, the Write input bank
|
|
640
641
|
(inputs, input, inputs-harvest, input-save),
|
package/lib/commands/admin.mjs
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// admin experiments list|show|new|update|note|started|spend|conclude|abandon … (tuning:experiment)
|
|
8
8
|
// admin tuning-context [--q "<question>"] (tuning:experiment)
|
|
9
9
|
// admin tuning-import <brief-results.json> [--experiment <id>] (tuning:experiment)
|
|
10
|
-
// admin tuning kernels|kernel|candidate|compare|comparisons|comparison|picks|pick|promote … (tuning:experiment)
|
|
10
|
+
// admin tuning kernels|kernel|candidate|compare|comparisons|comparison|picks|pick|criteria|promote … (tuning:experiment)
|
|
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)
|
|
@@ -442,8 +442,13 @@ async function cmdAdminTuningContext(ctx) {
|
|
|
442
442
|
* admin tuning compare --title "…" --candidates Hazel:21,Aspen:20 [--experiment <id>] start a comparison
|
|
443
443
|
* (Hazel:23@22 reads v23 against the test version v22 instead of live; it cannot promote)
|
|
444
444
|
* admin tuning comparisons | comparison <id> list / read one (per-measure verdicts)
|
|
445
|
-
* admin tuning picks <comparison-id>
|
|
446
|
-
* admin tuning pick <comparison-id> --cell <key> --pick left|right|tie record a blind pick
|
|
445
|
+
* admin tuning picks <comparison-id> [--criterion opening] the blind queue (next pairs); with a criterion, the pairs not yet read for it (Eye Test mode)
|
|
446
|
+
* admin tuning pick <comparison-id> --cell <key> --pick left|right|tie [--criterion opening] [--note "why"] record a blind pick (the note is the reader's reason; a criterion pick steers the next arm, never promotion)
|
|
447
|
+
* admin tuning criteria the Eye Test's criteria: id, label and the question each asks of a pair
|
|
448
|
+
* 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
|
|
449
|
+
* admin tuning evals the eval list: every defect category with its status and the picks' agreement (n, agreement, suggestion)
|
|
450
|
+
* admin tuning eval-add --id … --label … --how … --layer … --lever … --locality local|structural --axis … [--example "span"] add a category (starts unvetted)
|
|
451
|
+
* admin tuning eval-set <id> [--status confirmed|rejected|unvetted] [--example "span" [--fix "…"]] confirm / reject (a person's call), or add an example
|
|
447
452
|
* admin tuning reading-list <comparison-id> [--n 10] your next blind pairs as links, to read off the screen
|
|
448
453
|
* admin tuning archive <comparison-id> [--undo] take a read comparison out of the working list (or back); the record stays
|
|
449
454
|
* admin tuning comparisons [--archived only|all] the list; archived ones only when asked
|
|
@@ -670,24 +675,133 @@ async function cmdAdminTuning(ctx) {
|
|
|
670
675
|
case "comparison":
|
|
671
676
|
if (!id) throw new CliError("Usage: admin tuning comparison <id>");
|
|
672
677
|
return printResult(io, { ...flags, json: true }, await staffRequest(ctx, "GET", `${T}/comparisons/${id}`));
|
|
673
|
-
case "picks":
|
|
674
|
-
if (!id) throw new CliError("Usage: admin tuning picks <comparison-id>");
|
|
678
|
+
case "picks": {
|
|
679
|
+
if (!id) throw new CliError("Usage: admin tuning picks <comparison-id> [--criterion opening]");
|
|
680
|
+
const criterion = flagStr(flags.criterion);
|
|
681
|
+
const qs = criterion ? `?${new URLSearchParams({ criterion })}` : "";
|
|
675
682
|
return printResult(
|
|
676
683
|
io,
|
|
677
684
|
{ ...flags, json: true },
|
|
678
|
-
await staffRequest(ctx, "GET", `${T}/comparisons/${id}/picks`)
|
|
685
|
+
await staffRequest(ctx, "GET", `${T}/comparisons/${id}/picks${qs}`)
|
|
679
686
|
);
|
|
687
|
+
}
|
|
680
688
|
case "pick": {
|
|
681
689
|
const cell = flagStr(flags.cell);
|
|
682
690
|
const pick = flagStr(flags.pick);
|
|
691
|
+
const note = flagStr(flags.note);
|
|
692
|
+
const criterion = flagStr(flags.criterion);
|
|
683
693
|
if (!id || !cell || !["left", "right", "tie"].includes(pick ?? ""))
|
|
684
|
-
throw new CliError(
|
|
694
|
+
throw new CliError(
|
|
695
|
+
'Usage: admin tuning pick <comparison-id> --cell <key> --pick left|right|tie [--criterion opening] [--note "why"]'
|
|
696
|
+
);
|
|
685
697
|
return printResult(
|
|
686
698
|
io,
|
|
687
699
|
flags,
|
|
688
|
-
await staffRequest(ctx, "POST", `${T}/comparisons/${id}/picks`, {
|
|
700
|
+
await staffRequest(ctx, "POST", `${T}/comparisons/${id}/picks`, {
|
|
701
|
+
cell_key: cell,
|
|
702
|
+
outcome: pick,
|
|
703
|
+
...(criterion ? { criterion } : {}),
|
|
704
|
+
...(note ? { note } : {}),
|
|
705
|
+
})
|
|
689
706
|
);
|
|
690
707
|
}
|
|
708
|
+
case "evals": {
|
|
709
|
+
const r = await staffRequest(ctx, "GET", `${T}/evals`);
|
|
710
|
+
if (json) return printResult(io, flags, r);
|
|
711
|
+
const rows = r.categories ?? [];
|
|
712
|
+
if (!rows.length) return io.log("(no categories)");
|
|
713
|
+
const cell = (v, w) => String(v ?? "—").padEnd(w);
|
|
714
|
+
io.log(
|
|
715
|
+
`${cell("id", 24)} ${cell("status", 10)} ${cell("layer", 10)} ${cell("lever", 30)} ${"n".padStart(4)} ${"agree".padStart(6)} ${cell("suggest", 10)} ${"ex".padStart(3)}`
|
|
716
|
+
);
|
|
717
|
+
for (const c of rows) {
|
|
718
|
+
const v = c.vetting ?? {};
|
|
719
|
+
io.log(
|
|
720
|
+
`${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)}`
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
case "eval-add": {
|
|
726
|
+
const body = {
|
|
727
|
+
id: flagStr(flags.id),
|
|
728
|
+
label: flagStr(flags.label),
|
|
729
|
+
how: flagStr(flags.how),
|
|
730
|
+
layer: flagStr(flags.layer),
|
|
731
|
+
lever: flagStr(flags.lever) ?? "",
|
|
732
|
+
locality: flagStr(flags.locality),
|
|
733
|
+
axis: flagStr(flags.axis),
|
|
734
|
+
};
|
|
735
|
+
const example = flagStr(flags.example);
|
|
736
|
+
if (
|
|
737
|
+
!body.id ||
|
|
738
|
+
!body.label ||
|
|
739
|
+
!body.how ||
|
|
740
|
+
!body.layer ||
|
|
741
|
+
!body.axis ||
|
|
742
|
+
!["local", "structural"].includes(body.locality ?? "")
|
|
743
|
+
)
|
|
744
|
+
throw new CliError(
|
|
745
|
+
'Usage: admin tuning eval-add --id … --label … --how … --layer … --lever … --locality local|structural --axis … [--example "span"]'
|
|
746
|
+
);
|
|
747
|
+
if (example) body.examples = [{ span: example, ...(flags.fix ? { fix: flagStr(flags.fix) } : {}) }];
|
|
748
|
+
const r = await staffRequest(ctx, "POST", `${T}/evals`, body);
|
|
749
|
+
return json ? printResult(io, flags, r) : io.log(`added ${r.category.id} (${r.category.status})`);
|
|
750
|
+
}
|
|
751
|
+
case "eval-set": {
|
|
752
|
+
const status = flagStr(flags.status);
|
|
753
|
+
const example = flagStr(flags.example);
|
|
754
|
+
if (!id || (!status && !example) || (status && !["confirmed", "rejected", "unvetted"].includes(status)))
|
|
755
|
+
throw new CliError(
|
|
756
|
+
'Usage: admin tuning eval-set <id> [--status confirmed|rejected|unvetted] [--example "span" [--fix "…"]]'
|
|
757
|
+
);
|
|
758
|
+
const patch = {};
|
|
759
|
+
if (status) patch.status = status;
|
|
760
|
+
if (example) {
|
|
761
|
+
// The PATCH replaces the whole list: read what is stored and append.
|
|
762
|
+
const current = (await staffRequest(ctx, "GET", `${T}/evals`)).categories?.find((c) => c.id === id);
|
|
763
|
+
if (!current) throw new CliError(`No category ${id} in the eval list`);
|
|
764
|
+
const fix = flagStr(flags.fix);
|
|
765
|
+
patch.examples = [...(current.examples ?? []), { span: example, ...(fix ? { fix } : {}) }];
|
|
766
|
+
}
|
|
767
|
+
const r = await staffRequest(ctx, "PATCH", `${T}/evals/${encodeURIComponent(id)}`, patch);
|
|
768
|
+
return json
|
|
769
|
+
? printResult(io, flags, r)
|
|
770
|
+
: io.log(`${r.category.id}: ${r.category.status}, ${(r.category.examples ?? []).length} examples`);
|
|
771
|
+
}
|
|
772
|
+
case "criteria": {
|
|
773
|
+
const r = await staffRequest(ctx, "GET", `${T}/comparisons/criteria`);
|
|
774
|
+
if (json) return printResult(io, flags, r);
|
|
775
|
+
for (const c of r.criteria ?? []) io.log(`${c.id.padEnd(12)} ${c.label.padEnd(12)} ${c.question}`);
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
case "ledger": {
|
|
779
|
+
if (!id) throw new CliError("Usage: admin tuning ledger <comparison-id>");
|
|
780
|
+
const read = await staffRequest(ctx, "GET", `${T}/comparisons/${id}`);
|
|
781
|
+
const ledger = read.ledger;
|
|
782
|
+
if (!ledger)
|
|
783
|
+
throw new CliError(
|
|
784
|
+
"This comparison has no ledger (not a Write comparison, or read before the diagnosis read existed)."
|
|
785
|
+
);
|
|
786
|
+
if (json) return printResult(io, flags, ledger);
|
|
787
|
+
const arm = (a) =>
|
|
788
|
+
`${a.arm}: ${a.read} read · floor ${a.floor ?? "—"} · ceiling ${a.ceiling ?? "—"} · local/pair ${a.local_per_pair ?? "—"} · structural/pair ${a.structural_per_pair ?? "—"}`;
|
|
789
|
+
io.log(arm(ledger.control));
|
|
790
|
+
io.log(arm(ledger.candidate));
|
|
791
|
+
io.log(`Δ floor ${ledger.delta.floor ?? "—"} · Δ ceiling ${ledger.delta.ceiling ?? "—"}`);
|
|
792
|
+
for (const a of [ledger.control, ledger.candidate]) {
|
|
793
|
+
io.log(`\n${a.arm} — by category (cost, pairs, lever):`);
|
|
794
|
+
for (const c of a.categories.slice(0, 12))
|
|
795
|
+
io.log(
|
|
796
|
+
` ${c.category}${c.locality === "structural" ? " (structural)" : ""} cost ${c.cost} on ${c.pairs} pairs → ${c.lever ?? "—"}${c.example ? ` e.g. “${String(c.example).slice(0, 90)}”` : ""}`
|
|
797
|
+
);
|
|
798
|
+
}
|
|
799
|
+
io.log("\nnext arm:");
|
|
800
|
+
if (!ledger.next_arm.length) io.log(" nothing local recurs on two pairs or more");
|
|
801
|
+
for (const n of ledger.next_arm)
|
|
802
|
+
io.log(` ${n.instruction}${n.patch ? ` patch ${JSON.stringify(n.patch)}` : ""}`);
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
691
805
|
case "reading-list": {
|
|
692
806
|
if (!id) throw new CliError("Usage: admin tuning reading-list <comparison-id> [--n 10]");
|
|
693
807
|
const n = flagStr(flags.n);
|
|
@@ -1456,7 +1570,7 @@ async function cmdAdminTuning(ctx) {
|
|
|
1456
1570
|
}
|
|
1457
1571
|
default:
|
|
1458
1572
|
throw new CliError(
|
|
1459
|
-
"Usage: admin tuning kernels|kernel|candidate|compare|comparisons|comparison|picks|pick|reading-list|pair|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)"
|
|
1573
|
+
"Usage: admin tuning kernels|kernel|candidate|compare|comparisons|comparison|picks|pick|ledger|evals|eval-add|eval-set|reading-list|pair|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)"
|
|
1460
1574
|
);
|
|
1461
1575
|
}
|
|
1462
1576
|
}
|