@letterstory/cli 0.57.0 → 0.59.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 +18 -0
- package/lib/cli.mjs +4 -1
- package/lib/commands/admin.mjs +137 -1
- package/lib/commands/autopen.mjs +2 -0
- package/lib/commands/enhance.mjs +152 -0
- package/lib/commands/evals.mjs +1 -1
- package/lib/commands.mjs +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -360,6 +360,7 @@ letterstory admin tuning comparison <id> # per-measure verdicts (b
|
|
|
360
360
|
letterstory admin tuning picks <id> # the blind queue; then: admin tuning pick <id> --cell <key> --pick left|right|tie
|
|
361
361
|
letterstory admin tuning reading-list <id> --n 10 # your next blind pairs as links (Text A / Text B), to read off the screen; pick in the console
|
|
362
362
|
letterstory admin tuning pair <id> --cell <key> # one pair named: live and candidate, both texts, the measures per arm (a key reads every pair)
|
|
363
|
+
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
|
|
363
364
|
letterstory admin tuning candidates --kernels all --patch '{"writer":"echo/echo"}' --note "Echo" # the same change as a candidate of each kernel
|
|
364
365
|
letterstory admin tuning session-new --title "Echo round 1" --kernel Hazel --input-file brief.md --kind outline [--version 22]
|
|
365
366
|
letterstory admin tuning propose <session> --text "Echo with rarity on; live with the shred after" --create # trials from words, over the dials
|
|
@@ -595,6 +596,23 @@ it to `0` in CI so a test does not wait on a real interval.
|
|
|
595
596
|
`collection run --topic "…" --collection <uuid>` runs topic → draft → publish → rebuild
|
|
596
597
|
end to end, with `collection run-status <job-id>` to follow it.
|
|
597
598
|
|
|
599
|
+
### Enhancing
|
|
600
|
+
|
|
601
|
+
The passes that run after the writer — visuals, internal links, external links (citations),
|
|
602
|
+
metadata — as one versioned file per collection (models and settings), each run recorded.
|
|
603
|
+
|
|
604
|
+
```bash
|
|
605
|
+
letterstory enhance run <article-id> --pass internal_links # run one pass on a post now
|
|
606
|
+
letterstory enhance runs --article <article-id> # what each pass did (also --auto-pen, --outcome skipped)
|
|
607
|
+
letterstory enhance get <run-id> # one run: version, models, what it added, cost
|
|
608
|
+
letterstory enhance file <collection-id> > enhancer.json # the file the collection's passes run
|
|
609
|
+
letterstory enhance file <collection-id> --patch '{"internalLinks":{"maxPerPost":3}}'
|
|
610
|
+
letterstory enhance versions <collection-id> # your copy's versions and our releases
|
|
611
|
+
letterstory enhance pin <collection-id> <version-id|none> # pin one of our releases, or follow live
|
|
612
|
+
letterstory enhance offer <collection-id> [--accept] # our newer version, over your own changes
|
|
613
|
+
letterstory autopen run --article <id> --skip finish,metadata # leave passes out of one run
|
|
614
|
+
```
|
|
615
|
+
|
|
598
616
|
## Seers — event-driven signals
|
|
599
617
|
|
|
600
618
|
A **seer** watches an external source (a GitHub repo, a Notion workspace, a spec URL, a
|
package/lib/cli.mjs
CHANGED
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
cmdTemplates,
|
|
41
41
|
cmdCompositions,
|
|
42
42
|
cmdShredder,
|
|
43
|
+
cmdEnhance,
|
|
43
44
|
cmdLede,
|
|
44
45
|
cmdLandings,
|
|
45
46
|
cmdSequences,
|
|
@@ -54,7 +55,7 @@ import {
|
|
|
54
55
|
} from "./commands.mjs";
|
|
55
56
|
|
|
56
57
|
// Keep in sync with cli/package.json.
|
|
57
|
-
export const VERSION = "0.
|
|
58
|
+
export const VERSION = "0.59.0";
|
|
58
59
|
|
|
59
60
|
// Flags that never take a value. Listing them explicitly means `deploy get --json <id>`
|
|
60
61
|
// can't accidentally swallow the id as --json's value.
|
|
@@ -936,6 +937,8 @@ const CLIENT_COMMANDS = {
|
|
|
936
937
|
templates: cmdTemplates,
|
|
937
938
|
compositions: cmdCompositions,
|
|
938
939
|
shredder: cmdShredder,
|
|
940
|
+
enhance: cmdEnhance,
|
|
941
|
+
enhancer: cmdEnhance,
|
|
939
942
|
lede: cmdLede,
|
|
940
943
|
ledes: cmdLede,
|
|
941
944
|
landings: cmdLandings,
|
package/lib/commands/admin.mjs
CHANGED
|
@@ -430,6 +430,7 @@ async function cmdAdminTuningContext(ctx) {
|
|
|
430
430
|
* admin tuning pick <comparison-id> --cell <key> --pick left|right|tie record a blind pick
|
|
431
431
|
* admin tuning reading-list <comparison-id> [--n 10] your next blind pairs as links, to read off the screen
|
|
432
432
|
* admin tuning pair <comparison-id> --cell <key> one pair named: live and candidate, both texts, the measures per arm
|
|
433
|
+
* 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
|
|
433
434
|
* admin tuning candidates --kernels Hazel,Aspen|all --patch '{…}' [--base N] [--note "…"] the same changes as a candidate of each kernel
|
|
434
435
|
* admin tuning promote <experiment-id> --versions <service-kernel-id>:<version>,… promote from a "ship" decision
|
|
435
436
|
*
|
|
@@ -463,6 +464,13 @@ async function cmdAdminTuningContext(ctx) {
|
|
|
463
464
|
* admin tuning shredders | shredder <version-id> our shredder's versions / one version's file and diff
|
|
464
465
|
* admin tuning shredder-candidate --patch '{…}' | --file f.json [--of <version-id>] [--note "…"] save a candidate
|
|
465
466
|
* admin tuning promote-shredder <experiment-id> --version <shredder-version-id> promote our shredder from a "ship" decision (blind picks required)
|
|
467
|
+
*
|
|
468
|
+
* Enhance (Phase 5): our enhancer's versions as files, enhance comparisons, the deploy split.
|
|
469
|
+
* admin tuning enhancers | enhancer <version-id> our enhancer's versions / one version's file and diff
|
|
470
|
+
* admin tuning enhancer-candidate --patch '{…}' | --file f.json [--of <version-id>] [--note "…"] save a candidate
|
|
471
|
+
* admin tuning enhance-compare --title "…" (--candidate <version-id> | --patch '{…}' | --null-check) [--keys …]
|
|
472
|
+
* admin tuning enhance-inputs [--set v1] | enhance-split [--since-hours 72] [--at <iso>,…] | enhance-standing
|
|
473
|
+
* admin tuning promote-enhancer <experiment-id> --version <enhancer-version-id> promote our enhancer from a "ship" decision (picks when it changes visuals)
|
|
466
474
|
*/
|
|
467
475
|
/** `--flag '<json>'` → the parsed object, or undefined when the flag is absent. */
|
|
468
476
|
function jsonFlag(raw, name) {
|
|
@@ -633,6 +641,34 @@ async function cmdAdminTuning(ctx) {
|
|
|
633
641
|
await staffRequest(ctx, "GET", `${T}/comparisons/${id}/pair?${new URLSearchParams({ cell })}`)
|
|
634
642
|
);
|
|
635
643
|
}
|
|
644
|
+
case "side-by-side": {
|
|
645
|
+
const ids = (id ?? "")
|
|
646
|
+
.split(",")
|
|
647
|
+
.map((x) => x.trim())
|
|
648
|
+
.filter(Boolean);
|
|
649
|
+
if (!ids?.length) throw new CliError("Usage: admin tuning side-by-side <id>,<id>[,…] [--cell <key>]");
|
|
650
|
+
const cell = flagStr(flags.cell);
|
|
651
|
+
const r = await staffRequest(
|
|
652
|
+
ctx,
|
|
653
|
+
"GET",
|
|
654
|
+
`${T}/comparisons/side-by-side?${new URLSearchParams(compact({ ids: ids.join(","), cell }))}`
|
|
655
|
+
);
|
|
656
|
+
if (json) return printResult(io, flags, r);
|
|
657
|
+
for (const w of r.writers) {
|
|
658
|
+
const m = (measure) => w.measures.find((x) => x.measure === measure);
|
|
659
|
+
const q = m("quality");
|
|
660
|
+
io.log(
|
|
661
|
+
`${w.label} (${w.writers.join(", ") || "—"}, ${w.settled_pairs} pairs, ${w.status}): quality ${q?.mean_delta ?? "—"} [${q?.ci95?.join(" … ") ?? "—"}] ${q?.verdict ?? ""} · seconds ${m("latency_s")?.mean_candidate ?? "—"} vs ${m("latency_s")?.mean_control ?? "—"} · picks ${w.picks.candidate}–${w.picks.control}`
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
if (r.input) {
|
|
665
|
+
for (const t of [r.input.live, ...r.input.texts].filter(Boolean))
|
|
666
|
+
io.log(
|
|
667
|
+
`\n=== ${t.label} ${t.writer ?? ""}${t.version ? ` v${t.version}` : ""} (${t.access})\n${t.text ?? "(no text)"}`
|
|
668
|
+
);
|
|
669
|
+
} else io.log(`\ninputs: ${r.inputs.map((i) => i.key).join(", ")} (add --cell <key> for the texts)`);
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
636
672
|
case "candidates": {
|
|
637
673
|
const patch = jsonFlag(flags.patch, "--patch");
|
|
638
674
|
const wanted = flagList(flags.kernels);
|
|
@@ -1011,6 +1047,106 @@ async function cmdAdminTuning(ctx) {
|
|
|
1011
1047
|
)
|
|
1012
1048
|
);
|
|
1013
1049
|
}
|
|
1050
|
+
// --- Enhance: our enhancer's versions as files, comparisons, the deploy split ---
|
|
1051
|
+
case "enhance-compare": {
|
|
1052
|
+
const title = flagStr(flags.title);
|
|
1053
|
+
const versionId = flagStr(flags.candidate);
|
|
1054
|
+
const patch = jsonFlag(flags.patch, "--patch");
|
|
1055
|
+
const nullCheck = flagBool(flags["null-check"]);
|
|
1056
|
+
const named = [versionId !== undefined, patch !== undefined, nullCheck === true].filter(Boolean).length;
|
|
1057
|
+
if (!title || named !== 1)
|
|
1058
|
+
throw new CliError(
|
|
1059
|
+
"Usage: admin tuning enhance-compare --title \"…\" (--candidate <version-id> | --patch '{…}' | --null-check) [--keys k1,k2] [--concurrency 6] [--experiment <id>]"
|
|
1060
|
+
);
|
|
1061
|
+
const candidate = versionId ? { version_id: versionId } : patch ? { patch } : { null_check: true };
|
|
1062
|
+
const r = await staffRequest(
|
|
1063
|
+
ctx,
|
|
1064
|
+
"POST",
|
|
1065
|
+
`${T}/comparisons`,
|
|
1066
|
+
compact({
|
|
1067
|
+
primitive: "enhance",
|
|
1068
|
+
title,
|
|
1069
|
+
candidate,
|
|
1070
|
+
keys: flagList(flags.keys),
|
|
1071
|
+
concurrency: flagNum(flags.concurrency),
|
|
1072
|
+
experiment_id: flagStr(flags.experiment),
|
|
1073
|
+
})
|
|
1074
|
+
);
|
|
1075
|
+
return json ? printResult(io, flags, r) : io.log(`enhance comparison ${r.comparison?.id ?? ""} started`);
|
|
1076
|
+
}
|
|
1077
|
+
case "enhance-inputs": {
|
|
1078
|
+
const set = flagStr(flags.set);
|
|
1079
|
+
return printResult(
|
|
1080
|
+
io,
|
|
1081
|
+
{ ...flags, json: true },
|
|
1082
|
+
await staffRequest(ctx, "GET", `${T}/enhance/inputs${set ? `?${new URLSearchParams({ set })}` : ""}`)
|
|
1083
|
+
);
|
|
1084
|
+
}
|
|
1085
|
+
case "enhance-split": {
|
|
1086
|
+
const q = new URLSearchParams(
|
|
1087
|
+
compact({
|
|
1088
|
+
since_hours: flagStr(flags["since-hours"]),
|
|
1089
|
+
window_hours: flagStr(flags["window-hours"]),
|
|
1090
|
+
at: flagStr(flags.at),
|
|
1091
|
+
})
|
|
1092
|
+
);
|
|
1093
|
+
return printResult(
|
|
1094
|
+
io,
|
|
1095
|
+
{ ...flags, json: true },
|
|
1096
|
+
await staffRequest(ctx, "GET", `${T}/enhance/deploy-split${q.size ? `?${q}` : ""}`)
|
|
1097
|
+
);
|
|
1098
|
+
}
|
|
1099
|
+
case "enhance-standing":
|
|
1100
|
+
return printResult(io, { ...flags, json: true }, await staffRequest(ctx, "GET", `${T}/enhance/standing`));
|
|
1101
|
+
case "enhancers": {
|
|
1102
|
+
const r = await staffRequest(ctx, "GET", `${T}/enhancer-files`);
|
|
1103
|
+
if (json) return printResult(io, flags, r);
|
|
1104
|
+
return io.log(
|
|
1105
|
+
r.versions
|
|
1106
|
+
.map((v) => `${v.label.padEnd(18)} ${v.status.padEnd(9)} ${v.id}${v.note ? ` ${v.note}` : ""}`)
|
|
1107
|
+
.join("\n") || "(no versions)"
|
|
1108
|
+
);
|
|
1109
|
+
}
|
|
1110
|
+
case "enhancer": {
|
|
1111
|
+
if (!id) throw new CliError("Usage: admin tuning enhancer <version-id>");
|
|
1112
|
+
return printResult(
|
|
1113
|
+
io,
|
|
1114
|
+
{ ...flags, json: true },
|
|
1115
|
+
await staffRequest(ctx, "GET", `${T}/enhancer-files/${id}`)
|
|
1116
|
+
);
|
|
1117
|
+
}
|
|
1118
|
+
case "enhancer-candidate": {
|
|
1119
|
+
const patch = jsonFlag(flags.patch, "--patch");
|
|
1120
|
+
const file = jsonFile({ "enhancer-file": flags.file }, "enhancer");
|
|
1121
|
+
if ((patch === undefined) === (file === undefined))
|
|
1122
|
+
throw new CliError(
|
|
1123
|
+
'Usage: admin tuning enhancer-candidate --patch \'{"internalLinks":{"maxPerPost":3}}\' | --file enhancer.json [--of <version-id>] [--note "…"]'
|
|
1124
|
+
);
|
|
1125
|
+
const r = await staffRequest(
|
|
1126
|
+
ctx,
|
|
1127
|
+
"POST",
|
|
1128
|
+
`${T}/enhancer-files`,
|
|
1129
|
+
compact({ patch, file, patch_of: flagStr(flags.of), note: flagStr(flags.note) })
|
|
1130
|
+
);
|
|
1131
|
+
return json ? printResult(io, flags, r) : io.log(`saved candidate ${r.version.label} (${r.version.id})`);
|
|
1132
|
+
}
|
|
1133
|
+
case "promote-enhancer": {
|
|
1134
|
+
const version = flagStr(flags.version);
|
|
1135
|
+
if (!id || !version)
|
|
1136
|
+
throw new CliError(
|
|
1137
|
+
"Usage: admin tuning promote-enhancer <experiment-id> --version <enhancer-version-id>"
|
|
1138
|
+
);
|
|
1139
|
+
return printResult(
|
|
1140
|
+
io,
|
|
1141
|
+
{ ...flags, json: true },
|
|
1142
|
+
await staffRequest(
|
|
1143
|
+
ctx,
|
|
1144
|
+
"POST",
|
|
1145
|
+
`${EXPERIMENTS}/${id}/promote`,
|
|
1146
|
+
compact({ enhancer_version_id: version, actor: actorFrom(flags) })
|
|
1147
|
+
)
|
|
1148
|
+
);
|
|
1149
|
+
}
|
|
1014
1150
|
case "promote": {
|
|
1015
1151
|
const list = flagList(flags.versions);
|
|
1016
1152
|
if (!id || !list?.length)
|
|
@@ -1034,7 +1170,7 @@ async function cmdAdminTuning(ctx) {
|
|
|
1034
1170
|
}
|
|
1035
1171
|
default:
|
|
1036
1172
|
throw new CliError(
|
|
1037
|
-
"Usage: admin tuning kernels|kernel|candidate|compare|comparisons|comparison|picks|pick|reading-list|pair|candidates|base-kernels|base-versions|sessions|session|session-new|propose|trial-add|trial-run|trial-save|promote|arenas|arena|arena-candidate|research-compare|research-inputs|research-split|research-standing|promote-arena|shred-compare|shred-inputs|shred-split|shred-standing|shredders|shredder|shredder-candidate|promote-shredder (see --help)"
|
|
1173
|
+
"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|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)"
|
|
1038
1174
|
);
|
|
1039
1175
|
}
|
|
1040
1176
|
}
|
package/lib/commands/autopen.mjs
CHANGED
|
@@ -18,6 +18,8 @@ export const ARGS = {
|
|
|
18
18
|
run_auto_pen: {
|
|
19
19
|
article_id: { flag: "article", required: true },
|
|
20
20
|
from_scratch: { flag: "from-scratch", type: "bool" },
|
|
21
|
+
// --skip finish,metadata: leave those steps out of this run only.
|
|
22
|
+
skip_steps: { flag: "skip", type: "list" },
|
|
21
23
|
},
|
|
22
24
|
};
|
|
23
25
|
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// `enhance` — the passes that run after the writer: visuals, internal links, external links
|
|
2
|
+
// (citations) and metadata. Run one on a post now, read what each run did, and read or change the
|
|
3
|
+
// enhance file a collection's passes run (its models and settings), the same versioned-file pattern
|
|
4
|
+
// as a kernel, an arena and a shredder. Every command is a projection of one tool (docs/api).
|
|
5
|
+
|
|
6
|
+
import { readFileSync } from "node:fs";
|
|
7
|
+
import { CliError } from "../client.mjs";
|
|
8
|
+
import { flagStr, printResult, ok, toolField, argsFromFlags } from "./shared.mjs";
|
|
9
|
+
|
|
10
|
+
const PASSES = ["visuals", "internal_links", "external_links", "metadata"];
|
|
11
|
+
const OUTCOMES = ["added", "none", "skipped", "failed"];
|
|
12
|
+
|
|
13
|
+
/** `--file <path>` → the file's parsed JSON, or undefined when the flag is not set. */
|
|
14
|
+
function readJsonFile(flags) {
|
|
15
|
+
const path = flagStr(flags.file);
|
|
16
|
+
if (path === undefined) return undefined;
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
19
|
+
} catch (error) {
|
|
20
|
+
throw new CliError(`--file ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** `--patch '<json>'` → a JSON object, or undefined. */
|
|
25
|
+
function readPatch(flags) {
|
|
26
|
+
const inline = flagStr(flags.patch);
|
|
27
|
+
if (inline === undefined) return undefined;
|
|
28
|
+
let parsed;
|
|
29
|
+
try {
|
|
30
|
+
parsed = JSON.parse(inline);
|
|
31
|
+
} catch (err) {
|
|
32
|
+
throw new CliError(`--patch is not valid JSON: ${err.message}`);
|
|
33
|
+
}
|
|
34
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
|
|
35
|
+
throw new CliError("--patch must be a JSON object");
|
|
36
|
+
return parsed;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* How every command sources its tool's inputs (shared.mjs `argsFromFlags`; scripts/parity-check.ts
|
|
41
|
+
* diffs these keys against each tool's published schema).
|
|
42
|
+
*/
|
|
43
|
+
export const ARGS = {
|
|
44
|
+
run_enhance_pass: {
|
|
45
|
+
article_id: { positional: 0, label: "article-id", required: true },
|
|
46
|
+
pass: { flag: "pass", required: true, enum: PASSES },
|
|
47
|
+
},
|
|
48
|
+
get_enhance_run: {
|
|
49
|
+
run_id: { positional: 0, label: "run-id", required: true },
|
|
50
|
+
},
|
|
51
|
+
list_enhance_runs: {
|
|
52
|
+
article_id: "article",
|
|
53
|
+
auto_pen_id: "auto-pen",
|
|
54
|
+
collection_id: "collection",
|
|
55
|
+
pass: { flag: "pass", enum: PASSES },
|
|
56
|
+
outcome: { flag: "outcome", enum: OUTCOMES },
|
|
57
|
+
since: "since",
|
|
58
|
+
limit: { flag: "limit", type: "num" },
|
|
59
|
+
},
|
|
60
|
+
get_enhancer_file: {
|
|
61
|
+
collection_id: { positional: 0, label: "collection-id", required: true },
|
|
62
|
+
},
|
|
63
|
+
save_enhancer_file: {
|
|
64
|
+
collection_id: { positional: 0, label: "collection-id", required: true },
|
|
65
|
+
file: { value: (flags) => readJsonFile(flags) },
|
|
66
|
+
patch: { value: (flags) => readPatch(flags) },
|
|
67
|
+
note: "note",
|
|
68
|
+
},
|
|
69
|
+
list_enhancer_versions: {
|
|
70
|
+
collection_id: { positional: 0, label: "collection-id", required: true },
|
|
71
|
+
},
|
|
72
|
+
pin_enhancer_version: {
|
|
73
|
+
collection_id: { positional: 0, label: "collection-id", required: true },
|
|
74
|
+
version_id: {
|
|
75
|
+
value: (_flags, positionals) => {
|
|
76
|
+
const v = positionals[1];
|
|
77
|
+
if (v === undefined) throw new CliError("Missing required <version-id|none>");
|
|
78
|
+
return v === "none" ? null : v;
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
accept_enhancer_offer: {
|
|
83
|
+
collection_id: { positional: 0, label: "collection-id", required: true },
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export async function cmdEnhance(ctx) {
|
|
88
|
+
const sub = ctx.positionals[0];
|
|
89
|
+
const rest = { ...ctx, positionals: ctx.positionals.slice(1) };
|
|
90
|
+
switch (sub) {
|
|
91
|
+
case "run":
|
|
92
|
+
return enhanceTool(rest, "run_enhance_pass");
|
|
93
|
+
case "runs":
|
|
94
|
+
return enhanceTool(rest, "list_enhance_runs");
|
|
95
|
+
case "get":
|
|
96
|
+
case "show":
|
|
97
|
+
return enhanceTool(rest, "get_enhance_run");
|
|
98
|
+
case "file":
|
|
99
|
+
return enhanceFile(rest);
|
|
100
|
+
case "versions":
|
|
101
|
+
return enhanceTool(rest, "list_enhancer_versions");
|
|
102
|
+
case "pin":
|
|
103
|
+
return enhanceTool(rest, "pin_enhancer_version");
|
|
104
|
+
case "offer":
|
|
105
|
+
return rest.flags.accept === true ? enhanceTool(rest, "accept_enhancer_offer") : enhanceOffer(rest);
|
|
106
|
+
default:
|
|
107
|
+
throw new CliError(
|
|
108
|
+
`Unknown enhance subcommand: ${sub ?? "(none)"}. Try: run <article-id> --pass <pass>, runs, get <run-id>, file <collection-id>, versions, pin, offer`
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function enhanceTool(ctx, tool) {
|
|
114
|
+
const result = await ctx.client.callTool(tool, argsFromFlags(ARGS[tool], ctx.flags, ctx.positionals));
|
|
115
|
+
printResult(ctx.io, ctx.flags, result);
|
|
116
|
+
return 0;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function enhanceFile(ctx) {
|
|
120
|
+
const { client, flags, io } = ctx;
|
|
121
|
+
const saving = flagStr(flags.file) !== undefined || flagStr(flags.patch) !== undefined;
|
|
122
|
+
if (!saving) {
|
|
123
|
+
const result = await client.callTool(
|
|
124
|
+
"get_enhancer_file",
|
|
125
|
+
argsFromFlags(ARGS.get_enhancer_file, flags, ctx.positionals)
|
|
126
|
+
);
|
|
127
|
+
printResult(io, flags, result, (r) => JSON.stringify(r?.file ?? r, null, 2));
|
|
128
|
+
return 0;
|
|
129
|
+
}
|
|
130
|
+
const result = await client.callTool(
|
|
131
|
+
"save_enhancer_file",
|
|
132
|
+
argsFromFlags(ARGS.save_enhancer_file, flags, ctx.positionals)
|
|
133
|
+
);
|
|
134
|
+
ok(
|
|
135
|
+
ctx,
|
|
136
|
+
`Saved ${toolField(result, "version")?.label ?? "a new version"}; the collection's passes run it from now on.`
|
|
137
|
+
);
|
|
138
|
+
printResult(io, flags, result);
|
|
139
|
+
return 0;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function enhanceOffer(ctx) {
|
|
143
|
+
const { client, flags, io } = ctx;
|
|
144
|
+
const result = await client.callTool(
|
|
145
|
+
"get_enhancer_file",
|
|
146
|
+
argsFromFlags(ARGS.get_enhancer_file, flags, ctx.positionals)
|
|
147
|
+
);
|
|
148
|
+
const offer = toolField(result, "offer");
|
|
149
|
+
if (!offer) ok(ctx, "No offer: the collection runs our latest version, or a copy based on it.");
|
|
150
|
+
printResult(io, flags, result, (r) => JSON.stringify(r?.offer ?? null, null, 2));
|
|
151
|
+
return 0;
|
|
152
|
+
}
|
package/lib/commands/evals.mjs
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
import { CliError } from "../client.mjs";
|
|
17
17
|
import { flagStr, readBodyInput, printResult, ok, argsFromFlags } from "./shared.mjs";
|
|
18
18
|
|
|
19
|
-
const STAGES = ["research", "brief", "links", "mentions", "accuracy", "pipeline", "shred"];
|
|
19
|
+
const STAGES = ["research", "brief", "links", "mentions", "accuracy", "pipeline", "shred", "enhance"];
|
|
20
20
|
const STATUSES = ["proposed", "open", "guarding", "retired", "rejected"];
|
|
21
21
|
|
|
22
22
|
/** `--keys a,b,c` (or repeated `--keys`) → ["a", "b", "c"]; undefined when unset. */
|
package/lib/commands.mjs
CHANGED
|
@@ -32,6 +32,7 @@ export * from "./commands/lede.mjs";
|
|
|
32
32
|
export * from "./commands/onboard.mjs";
|
|
33
33
|
export * from "./commands/seers.mjs";
|
|
34
34
|
export * from "./commands/shredder.mjs";
|
|
35
|
+
export * from "./commands/enhance.mjs";
|
|
35
36
|
export * from "./commands/landings.mjs";
|
|
36
37
|
export * from "./commands/outbound.mjs";
|
|
37
38
|
export * from "./commands/offerings.mjs";
|