@letterstory/cli 0.98.0 → 0.98.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 +5 -1
- package/lib/commands/admin.mjs +195 -4
- package/package.json +1 -1
package/lib/cli.mjs
CHANGED
|
@@ -58,7 +58,7 @@ import {
|
|
|
58
58
|
} from "./commands.mjs";
|
|
59
59
|
|
|
60
60
|
// Keep in sync with cli/package.json.
|
|
61
|
-
export const VERSION = "0.98.
|
|
61
|
+
export const VERSION = "0.98.1";
|
|
62
62
|
|
|
63
63
|
// Flags that never take a value. Listing them explicitly means `deploy get --json <id>`
|
|
64
64
|
// can't accidentally swallow the id as --json's value. Every `type: "bool"` flag (and its
|
|
@@ -92,6 +92,10 @@ export const BOOLEAN_FLAGS = new Set([
|
|
|
92
92
|
"stdin",
|
|
93
93
|
// `authors generate <id> --with-bios`.
|
|
94
94
|
"with-bios",
|
|
95
|
+
// `admin tuning climb-new … --no-run`; `rung-propose --accept --with-replay` / `rung-accept --with-replay`
|
|
96
|
+
// (not `--replay`: `evals` takes `--replay <stage>`).
|
|
97
|
+
"no-run",
|
|
98
|
+
"with-replay",
|
|
95
99
|
// `covers generate <id> --regenerate` and `arena run --gate --article <id>`
|
|
96
100
|
// both put a value-less flag next to a value: without these entries the parser
|
|
97
101
|
// hands the following token to the flag and the real argument disappears.
|
package/lib/commands/admin.mjs
CHANGED
|
@@ -599,6 +599,20 @@ async function cmdAdminTuningContext(ctx) {
|
|
|
599
599
|
* admin tuning trial-run <trial-id> [--repeats 2] queue runs (guardrails apply)
|
|
600
600
|
* admin tuning trial-save <trial-id> [--note "…"] save a trial as a candidate version (never live)
|
|
601
601
|
*
|
|
602
|
+
* The climb (docs/architecture/candidate-climb-2026-10-14.md): a candidate built from a kernel's FLOOR
|
|
603
|
+
* (the writer alone under the record's constraints) one rung of feedback at a time. Mark lines, rewrite
|
|
604
|
+
* them, pick sections, say what is wrong in a sentence; the proposer answers in your words and the dials';
|
|
605
|
+
* accept, and the next rung runs. Earlier feedback is watched on every later rung (gone / still there).
|
|
606
|
+
* admin tuning climb-new --title "…" --kernel <id|name> (--bank <input-id> | --input-file f.md | --input "…") [--kind …] [--version N] [--no-run]
|
|
607
|
+
* a session whose Base is the kernel's floor; its first run is queued unless --no-run
|
|
608
|
+
* admin tuning climb <session-id> the trail: each rung, what it changed, its run, how many earlier wishes it met; the ladder
|
|
609
|
+
* admin tuning ladder the floor, and the rungs a climb can add back by key (voice, rules-repair, house-style, …)
|
|
610
|
+
* admin tuning feedback-add --run <run-id> --span "…" [--comment "…"] [--fix "…"] [--section <key>] [--locus span|section|piece] [--polarity issue|strength]
|
|
611
|
+
* feedback on a rung's run (a climb); --locus piece with --comment for the whole piece; --locus section --section <key> --polarity strength keeps a section
|
|
612
|
+
* admin tuning rung-propose <session-id> [--trial <id>] [--feedback id,id] [--accept [--with-replay]] the next rung from the feedback on the head's run; --accept takes the first rung proposed
|
|
613
|
+
* admin tuning rung-accept <session-id> --parent <trial-id> (--preset <key> | --settings '[{dial,scope,value,plain,feedback_ids}]') [--name "…"] [--with-replay [--replay-n 4]]
|
|
614
|
+
* admin tuning watches <run-id> read every earlier feedback item against this run's text (gone / still there), and list the readings
|
|
615
|
+
*
|
|
602
616
|
* The Write input bank: real kernel requests (above all the arena outlines Auto-Pen sends), as sent.
|
|
603
617
|
* admin tuning inputs [--filter "origin=arena_outline;material=thin,typical"] [--q "…"] list by tag, with coverage per bucket
|
|
604
618
|
* admin tuning input <input-id> one input in full: text, constraints, tags, run defaults, origin
|
|
@@ -1005,25 +1019,37 @@ async function cmdAdminTuning(ctx) {
|
|
|
1005
1019
|
const side = flagStr(flags.side);
|
|
1006
1020
|
const kind = flagStr(flags.kind);
|
|
1007
1021
|
if (
|
|
1008
|
-
!span ||
|
|
1022
|
+
(!span && flagStr(flags.locus) !== "piece") ||
|
|
1009
1023
|
(side && !["control", "candidate"].includes(side)) ||
|
|
1010
1024
|
(kind && !["narrow", "wide"].includes(kind))
|
|
1011
1025
|
)
|
|
1012
1026
|
throw new CliError(
|
|
1013
|
-
'Usage: admin tuning feedback-add --span "…" --comment "…" [--fix "…"] [--comparison <id> --cell <key> --side control|candidate] [--kind narrow|wide]'
|
|
1027
|
+
'Usage: admin tuning feedback-add --span "…" --comment "…" [--fix "…"] [--comparison <id> --cell <key> --side control|candidate] [--kind narrow|wide] | --run <run-id> [--locus span|section|piece] [--section <key>] [--polarity issue|strength]'
|
|
1014
1028
|
);
|
|
1029
|
+
const locus = flagStr(flags.locus);
|
|
1030
|
+
const polarity = flagStr(flags.polarity);
|
|
1031
|
+
if (
|
|
1032
|
+
(locus && !["span", "section", "piece"].includes(locus)) ||
|
|
1033
|
+
(polarity && !["issue", "strength"].includes(polarity))
|
|
1034
|
+
)
|
|
1035
|
+
throw new CliError("--locus is span|section|piece; --polarity is issue|strength");
|
|
1015
1036
|
const r = await staffRequest(
|
|
1016
1037
|
ctx,
|
|
1017
1038
|
"POST",
|
|
1018
1039
|
`${T}/proposals`,
|
|
1019
1040
|
compact({
|
|
1020
|
-
span,
|
|
1041
|
+
span: locus === "piece" && !flagStr(flags.span) ? "(whole piece)" : span,
|
|
1021
1042
|
comment: flagStr(flags.comment),
|
|
1022
1043
|
fix: flagStr(flags.fix),
|
|
1023
1044
|
comparison_id: flagStr(flags.comparison),
|
|
1024
1045
|
cell_key: flagStr(flags.cell),
|
|
1025
1046
|
side,
|
|
1026
1047
|
kind,
|
|
1048
|
+
// A climb: feedback on a rung's run, about a span, one section or the whole piece.
|
|
1049
|
+
run_id: flagStr(flags.run),
|
|
1050
|
+
locus,
|
|
1051
|
+
section_key: flagStr(flags.section),
|
|
1052
|
+
polarity,
|
|
1027
1053
|
})
|
|
1028
1054
|
);
|
|
1029
1055
|
if (json) return printResult(io, flags, r);
|
|
@@ -1603,6 +1629,171 @@ async function cmdAdminTuning(ctx) {
|
|
|
1603
1629
|
`${r.kernel}: saved as candidate v${r.version}${r.baseVersion ? ` (from v${r.baseVersion})` : ""}${r.leftBehind.length ? `; per-run, not saved: ${r.leftBehind.join(", ")}` : ""}`
|
|
1604
1630
|
);
|
|
1605
1631
|
}
|
|
1632
|
+
// --- The climb: from the floor, one rung of feedback at a time (lib/tuning/climb.ts) ---
|
|
1633
|
+
case "climb-new": {
|
|
1634
|
+
const title = flagStr(flags.title);
|
|
1635
|
+
const kernel = flagStr(flags.kernel);
|
|
1636
|
+
const path = flagStr(flags["input-file"]);
|
|
1637
|
+
let input = flagStr(flags.input);
|
|
1638
|
+
if (path) {
|
|
1639
|
+
try {
|
|
1640
|
+
input = readFileSync(path, "utf8");
|
|
1641
|
+
} catch (err) {
|
|
1642
|
+
throw new CliError(`--input-file ${path}: ${err.message}`);
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
const bank = flagStr(flags.bank);
|
|
1646
|
+
if (!title || !kernel || (!input && !bank))
|
|
1647
|
+
throw new CliError(
|
|
1648
|
+
'Usage: admin tuning climb-new --title "…" --kernel <id|name> (--bank <input-id> | --input-file f.md | --input "…") [--kind article|outline|brief] [--version N] [--no-run]'
|
|
1649
|
+
);
|
|
1650
|
+
let kernelId = kernel;
|
|
1651
|
+
if (!/^[0-9a-f-]{36}$/i.test(kernel)) {
|
|
1652
|
+
const { kernels } = await staffRequest(ctx, "GET", `${T}/kernels`);
|
|
1653
|
+
const matches = kernels.filter((k) => k.name.toLowerCase() === kernel.toLowerCase());
|
|
1654
|
+
const shared = matches.find((k) => !k.orgName) ?? matches[0];
|
|
1655
|
+
if (!shared) throw new CliError(`No kernel named ${kernel} (admin tuning base-kernels lists them).`);
|
|
1656
|
+
kernelId = shared.id;
|
|
1657
|
+
}
|
|
1658
|
+
const { session } = await staffRequest(
|
|
1659
|
+
ctx,
|
|
1660
|
+
"POST",
|
|
1661
|
+
T,
|
|
1662
|
+
compact({
|
|
1663
|
+
title,
|
|
1664
|
+
input_text: bank ? undefined : input,
|
|
1665
|
+
input_kind: flagStr(flags.kind) ?? "unknown",
|
|
1666
|
+
material: bank ? { source: "bank", input_id: bank } : undefined,
|
|
1667
|
+
base_kernel_id: kernelId,
|
|
1668
|
+
base_version: flagNum(flags.version),
|
|
1669
|
+
started_from: "floor",
|
|
1670
|
+
})
|
|
1671
|
+
);
|
|
1672
|
+
const floor = session.trials[0];
|
|
1673
|
+
let runs = [];
|
|
1674
|
+
if (!flagBool(flags["no-run"]) && floor && session.material?.ready !== false) {
|
|
1675
|
+
const r = await staffRequest(ctx, "POST", `${T}/trials/${floor.id}/run`, { repeats: 1 });
|
|
1676
|
+
runs = r.runs;
|
|
1677
|
+
}
|
|
1678
|
+
if (json) return printResult(io, flags, { session, runs });
|
|
1679
|
+
return io.log(
|
|
1680
|
+
`climb ${session.id} on ${session.baseKernelName}${session.baseVersion ? ` v${session.baseVersion}` : " (live)"}: floor trial ${floor?.id}${runs.length ? `, run ${runs[0].id} queued` : ""} → ${ctx.client.url}/superadmin/tuning/${session.id}`
|
|
1681
|
+
);
|
|
1682
|
+
}
|
|
1683
|
+
case "climb": {
|
|
1684
|
+
if (!id) throw new CliError("Usage: admin tuning climb <session-id>");
|
|
1685
|
+
const r = await staffRequest(ctx, "GET", `${T}/sessions/${id}/climb`);
|
|
1686
|
+
if (json) return printResult(io, flags, r);
|
|
1687
|
+
const lines = [`climb ${r.session_id} (from the ${r.started_from}); head ${r.head_trial_id ?? "—"}`];
|
|
1688
|
+
r.trail.forEach((rung, i) => {
|
|
1689
|
+
const w = rung.watches;
|
|
1690
|
+
const n = w.met + w.unmet + w.unread;
|
|
1691
|
+
lines.push(
|
|
1692
|
+
`${String(i).padStart(2)} ${rung.name} [${rung.trial_id}]${rung.run ? ` run ${rung.run.id} ${rung.run.status}${rung.run.words ? ` · ${rung.run.words} words` : ""}` : " (no run)"}${n ? ` · ${w.met} of ${n} wishes met` : ""}${rung.replay_screen_id ? ` · replay ${rung.replay_screen_id}` : ""}`
|
|
1693
|
+
);
|
|
1694
|
+
for (const s of rung.settings ?? []) lines.push(` ${s.plain}`);
|
|
1695
|
+
if (rung.changes.length) lines.push(` (${rung.changes.join("; ")})`);
|
|
1696
|
+
});
|
|
1697
|
+
const open = r.feedback.filter((f) => f.status === "proposed");
|
|
1698
|
+
lines.push(`feedback: ${r.feedback.length} (${open.length} not yet answered)`);
|
|
1699
|
+
for (const f of open.slice(0, 10))
|
|
1700
|
+
lines.push(
|
|
1701
|
+
` ${f.id} ${f.locus}${f.section_key ? ` ${f.section_key}` : ""} ${f.polarity}: ${f.comment || f.span.slice(0, 80)}`
|
|
1702
|
+
);
|
|
1703
|
+
lines.push(`ladder: ${r.ladder.map((l) => `${l.key}${l.on ? " (on)" : ""}`).join(", ")}`);
|
|
1704
|
+
return io.log(lines.join("\n"));
|
|
1705
|
+
}
|
|
1706
|
+
case "ladder": {
|
|
1707
|
+
const r = await staffRequest(ctx, "GET", `${T}/ladder`);
|
|
1708
|
+
if (json) return printResult(io, flags, r);
|
|
1709
|
+
return io.log(
|
|
1710
|
+
[`floor: ${r.floor.what}`, ...r.ladder.map((l) => `${l.key.padEnd(16)} ${l.label} — ${l.help}`)].join(
|
|
1711
|
+
"\n"
|
|
1712
|
+
)
|
|
1713
|
+
);
|
|
1714
|
+
}
|
|
1715
|
+
case "rung-propose": {
|
|
1716
|
+
if (!id)
|
|
1717
|
+
throw new CliError(
|
|
1718
|
+
"Usage: admin tuning rung-propose <session-id> [--trial <id>] [--feedback id,id] [--accept [--with-replay]]"
|
|
1719
|
+
);
|
|
1720
|
+
const r = await staffRequest(
|
|
1721
|
+
ctx,
|
|
1722
|
+
"POST",
|
|
1723
|
+
`${T}/sessions/${id}/climb/propose`,
|
|
1724
|
+
compact({ trial_id: flagStr(flags.trial), feedback_ids: flagList(flags.feedback) ?? undefined })
|
|
1725
|
+
);
|
|
1726
|
+
let accepted = null;
|
|
1727
|
+
if (flagBool(flags.accept) && r.rungs[0]) {
|
|
1728
|
+
accepted = await staffRequest(
|
|
1729
|
+
ctx,
|
|
1730
|
+
"POST",
|
|
1731
|
+
`${T}/sessions/${id}/climb/rungs`,
|
|
1732
|
+
compact({
|
|
1733
|
+
parent_trial_id: r.parent_trial_id,
|
|
1734
|
+
name: r.rungs[0].name,
|
|
1735
|
+
settings: r.rungs[0].settings,
|
|
1736
|
+
replay: flagBool(flags["with-replay"]) || undefined,
|
|
1737
|
+
replay_n: flagNum(flags["replay-n"]),
|
|
1738
|
+
})
|
|
1739
|
+
);
|
|
1740
|
+
}
|
|
1741
|
+
if (json) return printResult(io, flags, { ...r, accepted });
|
|
1742
|
+
const lines = [];
|
|
1743
|
+
r.rungs.forEach((rung, i) => {
|
|
1744
|
+
lines.push(`rung ${i + 1}: ${rung.name}`);
|
|
1745
|
+
for (const s of rung.settings)
|
|
1746
|
+
lines.push(` ${s.plain} (${s.scope}:${s.dial}; answers ${s.feedback_ids.join(", ") || "—"})`);
|
|
1747
|
+
lines.push(` changes: ${rung.changes.join("; ")}`);
|
|
1748
|
+
});
|
|
1749
|
+
for (const u of r.unsupported)
|
|
1750
|
+
lines.push(`no setting expresses${u.feedback_id ? ` ${u.feedback_id}` : ""}: ${u.what}`);
|
|
1751
|
+
for (const x of r.rejected) lines.push(`dropped: ${x.dial} (${x.reason})`);
|
|
1752
|
+
if (accepted)
|
|
1753
|
+
lines.push(
|
|
1754
|
+
`accepted as trial ${accepted.trial.id} (${accepted.trial.name}); run ${accepted.runs[0]?.id ?? "—"} queued${accepted.replay_screen_id ? `; replay ${accepted.replay_screen_id}` : ""}`
|
|
1755
|
+
);
|
|
1756
|
+
return io.log(lines.join("\n") || "(nothing proposed)");
|
|
1757
|
+
}
|
|
1758
|
+
case "rung-accept": {
|
|
1759
|
+
const parent = flagStr(flags.parent);
|
|
1760
|
+
const preset = flagStr(flags.preset);
|
|
1761
|
+
const settings = flags.settings ? jsonFlag(flags.settings, "--settings") : undefined;
|
|
1762
|
+
if (!id || !parent || (!preset && !settings))
|
|
1763
|
+
throw new CliError(
|
|
1764
|
+
"Usage: admin tuning rung-accept <session-id> --parent <trial-id> (--preset <key> | --settings '[…]') [--name \"…\"] [--with-replay [--replay-n 4]]"
|
|
1765
|
+
);
|
|
1766
|
+
const r = await staffRequest(
|
|
1767
|
+
ctx,
|
|
1768
|
+
"POST",
|
|
1769
|
+
`${T}/sessions/${id}/climb/rungs`,
|
|
1770
|
+
compact({
|
|
1771
|
+
parent_trial_id: parent,
|
|
1772
|
+
name: flagStr(flags.name),
|
|
1773
|
+
preset,
|
|
1774
|
+
settings,
|
|
1775
|
+
replay: flagBool(flags["with-replay"]) || undefined,
|
|
1776
|
+
replay_n: flagNum(flags["replay-n"]),
|
|
1777
|
+
})
|
|
1778
|
+
);
|
|
1779
|
+
if (json) return printResult(io, flags, r);
|
|
1780
|
+
return io.log(
|
|
1781
|
+
`rung ${r.trial.id} (${r.trial.name}); run ${r.runs[0]?.id ?? "—"} queued${r.replay_screen_id ? `; replay ${r.replay_screen_id}` : ""}${r.filed.length ? `; ${r.filed.length} feedback filed as config` : ""}`
|
|
1782
|
+
);
|
|
1783
|
+
}
|
|
1784
|
+
case "watches": {
|
|
1785
|
+
if (!id) throw new CliError("Usage: admin tuning watches <run-id>");
|
|
1786
|
+
const r = await staffRequest(ctx, "POST", `${T}/runs/${id}/watches`, {});
|
|
1787
|
+
if (json) return printResult(io, flags, r);
|
|
1788
|
+
return io.log(
|
|
1789
|
+
r.readings
|
|
1790
|
+
.map(
|
|
1791
|
+
(w) =>
|
|
1792
|
+
`${w.proposal_id} ${w.verdict}${w.evidence ? ` “${w.evidence.slice(0, 100)}”` : ""} (${w.method})`
|
|
1793
|
+
)
|
|
1794
|
+
.join("\n") || "(nothing to watch yet)"
|
|
1795
|
+
);
|
|
1796
|
+
}
|
|
1606
1797
|
// --- The Write input bank: real kernel requests a session can start from ---
|
|
1607
1798
|
case "inputs": {
|
|
1608
1799
|
const q = new URLSearchParams(
|
|
@@ -2319,7 +2510,7 @@ async function cmdAdminTuning(ctx) {
|
|
|
2319
2510
|
}
|
|
2320
2511
|
default:
|
|
2321
2512
|
throw new CliError(
|
|
2322
|
-
"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)"
|
|
2513
|
+
"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|climb-new|climb|ladder|rung-propose|rung-accept|watches|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)"
|
|
2323
2514
|
);
|
|
2324
2515
|
}
|
|
2325
2516
|
}
|