@letterstory/cli 0.70.5 → 0.72.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 +6 -0
- package/lib/cli.mjs +13 -7
- package/lib/commands/admin.mjs +439 -6
- package/lib/commands/connectors.mjs +2 -2
- package/lib/commands/onboard.mjs +1 -1
- package/lib/commands/phantom.mjs +6 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -362,6 +362,12 @@ letterstory admin tuning comparison <id> # per-measure verdicts (b
|
|
|
362
362
|
letterstory admin tuning picks <id> # the blind queue; then: admin tuning pick <id> --cell <key> --pick left|right|tie
|
|
363
363
|
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
|
|
364
364
|
letterstory admin tuning pair <id> --cell <key> # one pair named: live and candidate, both texts, the measures per arm (a key reads every pair)
|
|
365
|
+
letterstory admin tuning bank # the eval bank: every eval (rules and drafts), instances, labels
|
|
366
|
+
letterstory admin tuning bank-pool <eval-id> # draw an eval's pool from our shipped runs and the human corpus
|
|
367
|
+
letterstory admin tuning bank-judge <eval-id> --prompt-file judge.md --mode score --threshold 4 # a judge version (or --live: the rule's own)
|
|
368
|
+
letterstory admin tuning bank-score <eval-id> --version 2 # score it over the pool; prints agreement on held-out labels
|
|
369
|
+
letterstory admin tuning bank-try <eval-id> --version 2 # the judge as a trial rule in candidates, and a comparison against live
|
|
370
|
+
letterstory admin tuning invite --email editor@example.com # invite an editor (Pick and Eval bank only); `people`, `revoke <id>`
|
|
365
371
|
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
|
|
366
372
|
letterstory admin tuning candidates --kernels all --patch '{"writer":"echo/echo"}' --note "Echo" # the same change as a candidate of each kernel
|
|
367
373
|
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.
|
|
59
|
+
export const VERSION = "0.72.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.
|
|
@@ -293,7 +293,7 @@ Connectors:
|
|
|
293
293
|
[--confirm-missing-target] re-publish even though
|
|
294
294
|
the item was deleted/unpublished on the provider
|
|
295
295
|
connectors status --connector <connector> --publish-id <id>
|
|
296
|
-
connectors pull [--collection <uuid>] [--provider <webflow|framer|sanity|contentful>]
|
|
296
|
+
connectors pull [--collection <uuid>] [--provider <webflow|framer|sanity|contentful|github>]
|
|
297
297
|
Pull existing posts FROM a connected CMS into a
|
|
298
298
|
collection, reconciling (adopt/update/create/orphan).
|
|
299
299
|
--collection for a bound collection, --provider to
|
|
@@ -302,7 +302,10 @@ Connectors:
|
|
|
302
302
|
Save + validate CMS credentials. framer also needs
|
|
303
303
|
--project-url (+ [--no-auto-deploy]); sanity needs
|
|
304
304
|
--project-id [--dataset]; contentful needs --space-id
|
|
305
|
-
[--environment]
|
|
305
|
+
[--environment]; github takes a personal access
|
|
306
|
+
token (Contents + Pull requests read/write) — its
|
|
307
|
+
"mappings" are publish targets (repo, branch, path)
|
|
308
|
+
and a publish opens a PR (live once merged).
|
|
306
309
|
connectors disconnect --provider <p> Forget a CMS connection (keeps imported posts)
|
|
307
310
|
connectors connections Show connection status for every provider
|
|
308
311
|
connectors mappings --provider <p> List the org's field mappings for a provider
|
|
@@ -505,7 +508,7 @@ Collection onboarding (your OWN blog, on your org's domain — no provisioning):
|
|
|
505
508
|
Import the existing blog from its sitemap
|
|
506
509
|
(auto-discovered from the org's domain unless
|
|
507
510
|
--sitemap pins one) OR from a connected CMS with
|
|
508
|
-
--cms <webflow|framer|sanity|contentful>, then
|
|
511
|
+
--cms <webflow|framer|sanity|contentful|github>, then
|
|
509
512
|
derive content areas + series, build the topic
|
|
510
513
|
map, bind SEO demand, and stock Headlines & Queue.
|
|
511
514
|
Omit --collection to create a fresh one (--name,
|
|
@@ -634,13 +637,16 @@ Admin (STAFF key — --staff-key / LETTERSTORY_STAFF_API_KEY; acts before an org
|
|
|
634
637
|
registry the Pipeline tab draws (JSON).
|
|
635
638
|
admin tuning <sub> … The console's loop over a staff key: kernels,
|
|
636
639
|
candidate, compare, comparisons, picks, pick,
|
|
637
|
-
ledger, reading-list,
|
|
638
|
-
|
|
640
|
+
ledger, reading-list, finalists,
|
|
641
|
+
finalists-queue, pair, side-by-side,
|
|
642
|
+
evals, eval-add, eval-set, proposals,
|
|
643
|
+
propose-eval, capture-eval, reject-eval, promote,
|
|
639
644
|
sessions, session-new, propose, trial-add,
|
|
640
645
|
trial-run, trial-save, the Write input bank
|
|
641
646
|
(inputs, input, inputs-harvest, input-save),
|
|
642
647
|
screens (screen-new, screens, screen,
|
|
643
|
-
screen-advance),
|
|
648
|
+
screen-advance), the bake-off (bakeoff,
|
|
649
|
+
bakeoff-pick, bakeoff-merge),
|
|
644
650
|
and the arena, shredder
|
|
645
651
|
and enhancer twins (every flag is listed in
|
|
646
652
|
cli/lib/commands/admin.mjs).
|
package/lib/commands/admin.mjs
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
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
13
|
// admin evals scoreboard|cases|runs|run|triage|guard|retire … (fleet:read / tuning:experiment)
|
|
12
14
|
// 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
15
|
// admin replays [--org <id>] [--target <id>] [--status …] [--limit n] (fleet:read)
|
|
@@ -40,7 +42,7 @@ import { flagStr, flagNum, flagBool, flagList, printResult, compact, ok } from "
|
|
|
40
42
|
import { watchDomainJob } from "./onboard.mjs";
|
|
41
43
|
|
|
42
44
|
const SEER_MODES = ["suggest", "skip"];
|
|
43
|
-
const CMS_PROVIDERS = ["webflow", "framer", "sanity", "contentful"];
|
|
45
|
+
const CMS_PROVIDERS = ["webflow", "framer", "sanity", "contentful", "github"];
|
|
44
46
|
|
|
45
47
|
function staffKeyFrom(flags) {
|
|
46
48
|
const key = flagStr(flags["staff-key"]) || process.env.LETTERSTORY_STAFF_API_KEY || null;
|
|
@@ -439,20 +441,38 @@ async function cmdAdminTuningContext(ctx) {
|
|
|
439
441
|
* admin tuning kernels every catalog kernel, live version, writer, candidates
|
|
440
442
|
* admin tuning kernel <service-kernel-id> [--version N|live] one version's file (JSON)
|
|
441
443
|
* admin tuning candidate <service-kernel-id> --file f.json [--note "…"] save a file as a candidate version
|
|
442
|
-
* admin tuning compare --title "…" --candidates Hazel:21,Aspen:20 [--experiment <id>] start a comparison
|
|
444
|
+
* admin tuning compare --title "…" --candidates Hazel:21,Aspen:20 [--experiment <id>] [--pairs 20] start a comparison (--pairs caps it: breadth at 10–20, depth at the full 66)
|
|
443
445
|
* (Hazel:23@22 reads v23 against the test version v22 instead of live; it cannot promote)
|
|
444
446
|
* admin tuning comparisons | comparison <id> list / read one (per-measure verdicts)
|
|
445
447
|
* 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
448
|
* 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
449
|
* admin tuning criteria the Eye Test's criteria: id, label and the question each asks of a pair
|
|
448
|
-
* admin tuning
|
|
450
|
+
* admin tuning loop where the loop stands: what waits at each station, and where to go next
|
|
451
|
+
* 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
|
|
449
452
|
* admin tuning evals the eval list: every defect category with its status and the picks' agreement (n, agreement, suggestion)
|
|
450
453
|
* admin tuning eval-add --id … --label … --how … --layer … --lever … --locality local|structural --axis … [--example "span"] add a category (starts unvetted)
|
|
451
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
|
|
452
461
|
* admin tuning reading-list <comparison-id> [--n 10] your next blind pairs as links, to read off the screen
|
|
462
|
+
* 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
|
+
* 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)
|
|
453
464
|
* admin tuning archive <comparison-id> [--undo] take a read comparison out of the working list (or back); the record stays
|
|
454
465
|
* admin tuning comparisons [--archived only|all] the list; archived ones only when asked
|
|
455
466
|
* admin tuning pair <comparison-id> --cell <key> one pair named: live and candidate, both texts, the measures per arm
|
|
467
|
+
* admin tuning bank the eval bank: every eval (rules and drafts), instances, labels
|
|
468
|
+
* admin tuning bank-eval <eval-id> one eval: its standing, judge versions and the report (JSON)
|
|
469
|
+
* admin tuning bank-draft --name "…" --shape '<regex>' [--flags i] a draft eval, not a rule yet
|
|
470
|
+
* admin tuning bank-shape <eval-id> --shape '<regex>'|--rule [--flags i] widen or narrow the pool's shape (--rule: back to the rule's own)
|
|
471
|
+
* admin tuning bank-pool <eval-id> [--per-source 150] [--days 14] draw the pool from our shipped runs and the human corpus
|
|
472
|
+
* admin tuning bank-judge <eval-id> --live | --prompt-file f.md [--model m] [--mode score|same] [--threshold 4] [--context paragraph|sentence] [--examples id,id]
|
|
473
|
+
* admin tuning bank-score <eval-id> --version N score a judge version over the pool (call again for more than 600)
|
|
474
|
+
* 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
|
+
* admin tuning people | invite --email e [--note "…"] | revoke <id> who an admin invited into the console as an editor
|
|
456
476
|
* 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
|
|
457
477
|
* admin tuning candidates --kernels Hazel,Aspen|all --patch '{…}' [--base N] [--note "…"] the same changes as a candidate of each kernel
|
|
458
478
|
* admin tuning promote <experiment-id> --versions <service-kernel-id>:<version>,… promote from a "ship" decision
|
|
@@ -485,6 +505,15 @@ async function cmdAdminTuningContext(ctx) {
|
|
|
485
505
|
* admin tuning screens | screen <id> list / one screen: per candidate, each measure against the Base, with its noise
|
|
486
506
|
* admin tuning screen-advance <id> queue what the caps allow now (the cron does it every minute)
|
|
487
507
|
*
|
|
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
|
|
510
|
+
* per item tallied across the inputs, and a merge that composes the best of each into one candidate.
|
|
511
|
+
* Deterministic; a person overrides a grade and confirms the merge, which is a hypothesis until the
|
|
512
|
+
* eye test confirms it.
|
|
513
|
+
* admin tuning bakeoff <screen-id> [--regrade] the winners table: item, trusted, leader, wins per version, graded; then the conflicts a merge would meet
|
|
514
|
+
* admin tuning bakeoff-pick <screen-id> --input <key> --item <category:id|axis:id> --winner <version|none> a person's grade on one input and item (replaces the machine's)
|
|
515
|
+
* admin tuning bakeoff-merge <screen-id> [--resolve item=version,…] [--note "…"] [--compare 10] save the merged file as a candidate of the base kernel (never live); --compare starts a comparison against live on that many pairs
|
|
516
|
+
*
|
|
488
517
|
* Research (Phase 3): our arena's versions as files, research comparisons, the deploy split.
|
|
489
518
|
* admin tuning arenas | arena <version-id> our arena's versions / one version's file and diff
|
|
490
519
|
* admin tuning arena-candidate --patch '{…}' | --file f.json [--of <version-id>] [--note "…"] save a candidate
|
|
@@ -638,7 +667,7 @@ async function cmdAdminTuning(ctx) {
|
|
|
638
667
|
const list = flagList(flags.candidates);
|
|
639
668
|
if (!title || !list?.length)
|
|
640
669
|
throw new CliError(
|
|
641
|
-
'Usage: admin tuning compare --title "…" --candidates Hazel:21,Aspen:20 [--experiment <id>]'
|
|
670
|
+
'Usage: admin tuning compare --title "…" --candidates Hazel:21,Aspen:20 [--experiment <id>] [--pairs 20]'
|
|
642
671
|
);
|
|
643
672
|
// kernel:version, or kernel:version@control to read it against a test version instead of live.
|
|
644
673
|
const candidates = list.map((item) => {
|
|
@@ -656,7 +685,12 @@ async function cmdAdminTuning(ctx) {
|
|
|
656
685
|
ctx,
|
|
657
686
|
"POST",
|
|
658
687
|
`${T}/comparisons`,
|
|
659
|
-
compact({
|
|
688
|
+
compact({
|
|
689
|
+
title,
|
|
690
|
+
candidates,
|
|
691
|
+
experiment_id: flagStr(flags.experiment),
|
|
692
|
+
pairs: flags.pairs !== undefined ? Number(flags.pairs) : undefined,
|
|
693
|
+
})
|
|
660
694
|
);
|
|
661
695
|
return json ? printResult(io, flags, r) : io.log(`comparison ${r.comparison?.id ?? r.id ?? ""} started`);
|
|
662
696
|
}
|
|
@@ -769,12 +803,117 @@ async function cmdAdminTuning(ctx) {
|
|
|
769
803
|
? printResult(io, flags, r)
|
|
770
804
|
: io.log(`${r.category.id}: ${r.category.status}, ${(r.category.examples ?? []).length} examples`);
|
|
771
805
|
}
|
|
806
|
+
case "proposals": {
|
|
807
|
+
const status = flagStr(flags.status);
|
|
808
|
+
if (status && !["proposed", "captured", "rejected"].includes(status))
|
|
809
|
+
throw new CliError("Usage: admin tuning proposals [--status proposed|captured|rejected]");
|
|
810
|
+
const qs = status ? `?${new URLSearchParams({ status })}` : "";
|
|
811
|
+
const r = await staffRequest(ctx, "GET", `${T}/proposals${qs}`);
|
|
812
|
+
if (json) return printResult(io, flags, r);
|
|
813
|
+
const rows = r.proposals ?? [];
|
|
814
|
+
if (!rows.length) return io.log("(no proposals)");
|
|
815
|
+
for (const p of rows) {
|
|
816
|
+
const where = [p.cell_key, p.side].filter(Boolean).join(" / ") || "—";
|
|
817
|
+
const edit =
|
|
818
|
+
p.fix != null
|
|
819
|
+
? `\n → "${p.fix}" [${p.edit_class ?? "?"} → ${p.lever ?? "no lever: schema gap"}]`
|
|
820
|
+
: "";
|
|
821
|
+
io.log(
|
|
822
|
+
`${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}` : ""}`
|
|
823
|
+
);
|
|
824
|
+
}
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
case "propose-eval": {
|
|
828
|
+
const span = flagStr(flags.span);
|
|
829
|
+
const side = flagStr(flags.side);
|
|
830
|
+
const kind = flagStr(flags.kind);
|
|
831
|
+
if (
|
|
832
|
+
!span ||
|
|
833
|
+
(side && !["control", "candidate"].includes(side)) ||
|
|
834
|
+
(kind && !["narrow", "wide"].includes(kind))
|
|
835
|
+
)
|
|
836
|
+
throw new CliError(
|
|
837
|
+
'Usage: admin tuning propose-eval --span "…" --comment "…" [--fix "…"] [--comparison <id> --cell <key> --side control|candidate] [--kind narrow|wide]'
|
|
838
|
+
);
|
|
839
|
+
const r = await staffRequest(
|
|
840
|
+
ctx,
|
|
841
|
+
"POST",
|
|
842
|
+
`${T}/proposals`,
|
|
843
|
+
compact({
|
|
844
|
+
span,
|
|
845
|
+
comment: flagStr(flags.comment),
|
|
846
|
+
fix: flagStr(flags.fix),
|
|
847
|
+
comparison_id: flagStr(flags.comparison),
|
|
848
|
+
cell_key: flagStr(flags.cell),
|
|
849
|
+
side,
|
|
850
|
+
kind,
|
|
851
|
+
})
|
|
852
|
+
);
|
|
853
|
+
if (json) return printResult(io, flags, r);
|
|
854
|
+
const p = r.proposal;
|
|
855
|
+
const edit = p.fix != null ? ` — ${p.edit_class}: ${p.lever ?? "no lever maps: a schema gap"}` : "";
|
|
856
|
+
return io.log(`proposed ${p.id} (${p.kind})${edit}`);
|
|
857
|
+
}
|
|
858
|
+
case "capture-eval": {
|
|
859
|
+
const as = flagStr(flags.as);
|
|
860
|
+
const usage =
|
|
861
|
+
'Usage: admin tuning capture-eval <proposal-id> --as category --id … --label … --how … --layer … --lever … --locality local|structural --axis … | --as rule | --as lever [--note "…"]';
|
|
862
|
+
if (!id || !["category", "rule", "lever"].includes(as ?? "")) throw new CliError(usage);
|
|
863
|
+
let body = { as };
|
|
864
|
+
if (as === "lever") body = compact({ as, note: flagStr(flags.note) });
|
|
865
|
+
if (as === "category") {
|
|
866
|
+
const category = {
|
|
867
|
+
id: flagStr(flags.id),
|
|
868
|
+
label: flagStr(flags.label),
|
|
869
|
+
how: flagStr(flags.how),
|
|
870
|
+
layer: flagStr(flags.layer),
|
|
871
|
+
lever: flagStr(flags.lever) ?? "",
|
|
872
|
+
locality: flagStr(flags.locality),
|
|
873
|
+
axis: flagStr(flags.axis),
|
|
874
|
+
};
|
|
875
|
+
if (
|
|
876
|
+
!category.id ||
|
|
877
|
+
!category.label ||
|
|
878
|
+
!category.how ||
|
|
879
|
+
!category.layer ||
|
|
880
|
+
!category.axis ||
|
|
881
|
+
!["local", "structural"].includes(category.locality ?? "")
|
|
882
|
+
)
|
|
883
|
+
throw new CliError(usage);
|
|
884
|
+
body = { as, category };
|
|
885
|
+
}
|
|
886
|
+
const r = await staffRequest(ctx, "POST", `${T}/proposals/${id}/capture`, body);
|
|
887
|
+
return json
|
|
888
|
+
? printResult(io, flags, r)
|
|
889
|
+
: io.log(
|
|
890
|
+
`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"}`
|
|
891
|
+
);
|
|
892
|
+
}
|
|
893
|
+
case "reject-eval": {
|
|
894
|
+
if (!id) throw new CliError("Usage: admin tuning reject-eval <proposal-id>");
|
|
895
|
+
const r = await staffRequest(ctx, "POST", `${T}/proposals/${id}/reject`, {});
|
|
896
|
+
return json ? printResult(io, flags, r) : io.log(`rejected ${r.proposal.id}`);
|
|
897
|
+
}
|
|
772
898
|
case "criteria": {
|
|
773
899
|
const r = await staffRequest(ctx, "GET", `${T}/comparisons/criteria`);
|
|
774
900
|
if (json) return printResult(io, flags, r);
|
|
775
901
|
for (const c of r.criteria ?? []) io.log(`${c.id.padEnd(12)} ${c.label.padEnd(12)} ${c.question}`);
|
|
776
902
|
return;
|
|
777
903
|
}
|
|
904
|
+
case "loop": {
|
|
905
|
+
const r = await staffRequest(ctx, "GET", `${T}/loop`);
|
|
906
|
+
if (json) return printResult(io, flags, r);
|
|
907
|
+
io.log(`propose ${r.propose.running} running`);
|
|
908
|
+
io.log(`read ${r.read.settled_unread} settled, unread`);
|
|
909
|
+
io.log(`eye test ${r.eye_test.finalists} finalists, ${r.eye_test.pairs_to_read} pairs to read`);
|
|
910
|
+
io.log(
|
|
911
|
+
`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)"}`
|
|
912
|
+
);
|
|
913
|
+
io.log(`promote ${r.promote.promotable} won their picks`);
|
|
914
|
+
io.log(`\nnext: ${r.next}`);
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
778
917
|
case "ledger": {
|
|
779
918
|
if (!id) throw new CliError("Usage: admin tuning ledger <comparison-id>");
|
|
780
919
|
const read = await staffRequest(ctx, "GET", `${T}/comparisons/${id}`);
|
|
@@ -800,6 +939,23 @@ async function cmdAdminTuning(ctx) {
|
|
|
800
939
|
if (!ledger.next_arm.length) io.log(" nothing local recurs on two pairs or more");
|
|
801
940
|
for (const n of ledger.next_arm)
|
|
802
941
|
io.log(` ${n.instruction}${n.patch ? ` patch ${JSON.stringify(n.patch)}` : ""}`);
|
|
942
|
+
const edits = read.edits;
|
|
943
|
+
if (edits) {
|
|
944
|
+
io.log("\nreader edits:");
|
|
945
|
+
for (const side of ["control", "candidate"]) {
|
|
946
|
+
const a = edits[side];
|
|
947
|
+
io.log(` ${side}: ${a.n} edit${a.n === 1 ? "" : "s"}`);
|
|
948
|
+
for (const l of a.by_lever) {
|
|
949
|
+
const ex = l.examples[0];
|
|
950
|
+
io.log(
|
|
951
|
+
` ${l.lever} ${l.edit_class} n ${l.n}${ex ? ` e.g. “${String(ex.span).slice(0, 60)}” → “${String(ex.fix ?? "").slice(0, 60)}”` : ""}`
|
|
952
|
+
);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
io.log(
|
|
956
|
+
` unmapped (schema gaps): ${edits.unmapped}${edits.unplaced ? ` · unplaced: ${edits.unplaced}` : ""}`
|
|
957
|
+
);
|
|
958
|
+
}
|
|
803
959
|
return;
|
|
804
960
|
}
|
|
805
961
|
case "reading-list": {
|
|
@@ -812,6 +968,41 @@ async function cmdAdminTuning(ctx) {
|
|
|
812
968
|
);
|
|
813
969
|
return json ? printResult(io, flags, r) : io.log(r.text);
|
|
814
970
|
}
|
|
971
|
+
case "finalists": {
|
|
972
|
+
const limit = flagNum(flags.limit);
|
|
973
|
+
const qs = limit ? `?${new URLSearchParams({ limit: String(limit) })}` : "";
|
|
974
|
+
const r = await staffRequest(ctx, "GET", `${T}/finalists${qs}`);
|
|
975
|
+
if (json) return printResult(io, flags, r);
|
|
976
|
+
const rows = r.finalists ?? [];
|
|
977
|
+
if (!rows.length)
|
|
978
|
+
return io.log(
|
|
979
|
+
"(no finalists: nothing done with the gates clean and no craft loss that the picks have not decided)"
|
|
980
|
+
);
|
|
981
|
+
const cell = (v, w) => String(v ?? "—").padEnd(w);
|
|
982
|
+
const delta = (x) => (x == null ? "—" : x > 0 ? `+${x}` : String(x));
|
|
983
|
+
io.log(
|
|
984
|
+
`${"#".padStart(2)} ${cell("id", 8)} ${cell("title", 40)} ${cell("quality", 12)} ${"Δceil".padStart(6)} ${cell("in band", 12)} ${cell("latency", 12)} ${"picks".padStart(6)} why`
|
|
985
|
+
);
|
|
986
|
+
rows.forEach((f, i) =>
|
|
987
|
+
io.log(
|
|
988
|
+
`${String(i + 1).padStart(2)} ${cell(f.id.slice(0, 8), 8)} ${cell(String(f.title).slice(0, 40), 40)} ${cell(f.quality, 12)} ${delta(f.ceiling_delta).padStart(6)} ${cell(f.in_band, 12)} ${cell(f.latency, 12)} ${`${f.picks.read}/${f.picks.target}`.padStart(6)} ${f.why}`
|
|
989
|
+
)
|
|
990
|
+
);
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
case "finalists-queue": {
|
|
994
|
+
const n = flagNum(flags.n);
|
|
995
|
+
const qs = n ? `?${new URLSearchParams({ limit: String(n) })}` : "";
|
|
996
|
+
const r = await staffRequest(ctx, "GET", `${T}/finalists/queue${qs}`);
|
|
997
|
+
if (json) return printResult(io, flags, r);
|
|
998
|
+
const pairs = r.pairs ?? [];
|
|
999
|
+
if (!pairs.length) return io.log("(nothing to read blind across the finalists)");
|
|
1000
|
+
pairs.forEach((p, i) => {
|
|
1001
|
+
const link = `${ctx.client.url}/superadmin/tuning?${new URLSearchParams({ tab: "compare", comparison: p.comparison_id, view: "read" })}`;
|
|
1002
|
+
io.log(`${i + 1}. ${p.title}\n ${p.cell_key}\n ${link}`);
|
|
1003
|
+
});
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
815
1006
|
case "pair": {
|
|
816
1007
|
const cell = flagStr(flags.cell);
|
|
817
1008
|
if (!id || !cell) throw new CliError("Usage: admin tuning pair <comparison-id> --cell <key>");
|
|
@@ -821,6 +1012,156 @@ async function cmdAdminTuning(ctx) {
|
|
|
821
1012
|
await staffRequest(ctx, "GET", `${T}/comparisons/${id}/pair?${new URLSearchParams({ cell })}`)
|
|
822
1013
|
);
|
|
823
1014
|
}
|
|
1015
|
+
case "bank": {
|
|
1016
|
+
const r = await staffRequest(ctx, "GET", `${T}/bank`);
|
|
1017
|
+
if (json) return printResult(io, flags, r);
|
|
1018
|
+
for (const e of r.evals)
|
|
1019
|
+
io.log(
|
|
1020
|
+
`${e.id} [${e.status}${e.kind ? `, ${e.kind}` : ""}] ${e.name} — ${e.items} instances, ${e.labelled} labelled`
|
|
1021
|
+
);
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
1024
|
+
case "bank-eval": {
|
|
1025
|
+
if (!id) throw new CliError("Usage: admin tuning bank-eval <eval-id>");
|
|
1026
|
+
return printResult(
|
|
1027
|
+
io,
|
|
1028
|
+
{ ...flags, json: true },
|
|
1029
|
+
await staffRequest(ctx, "GET", `${T}/bank/${encodeURIComponent(id)}`)
|
|
1030
|
+
);
|
|
1031
|
+
}
|
|
1032
|
+
case "bank-draft": {
|
|
1033
|
+
const name = flagStr(flags.name);
|
|
1034
|
+
const shape = flagStr(flags.shape);
|
|
1035
|
+
if (!name || !shape)
|
|
1036
|
+
throw new CliError("Usage: admin tuning bank-draft --name \"…\" --shape '<regex>' [--flags i]");
|
|
1037
|
+
return printResult(
|
|
1038
|
+
io,
|
|
1039
|
+
flags,
|
|
1040
|
+
await staffRequest(ctx, "POST", `${T}/bank`, compact({ name, shape, flags: flagStr(flags.flags) }))
|
|
1041
|
+
);
|
|
1042
|
+
}
|
|
1043
|
+
case "bank-shape": {
|
|
1044
|
+
const shape = flagBool(flags.rule) ? null : flagStr(flags.shape);
|
|
1045
|
+
if (!id || shape === undefined)
|
|
1046
|
+
throw new CliError("Usage: admin tuning bank-shape <eval-id> --shape '<regex>'|--rule [--flags i]");
|
|
1047
|
+
return printResult(
|
|
1048
|
+
io,
|
|
1049
|
+
flags,
|
|
1050
|
+
await staffRequest(ctx, "PATCH", `${T}/bank/${encodeURIComponent(id)}`, {
|
|
1051
|
+
shape,
|
|
1052
|
+
...compact({ flags: flagStr(flags.flags) }),
|
|
1053
|
+
})
|
|
1054
|
+
);
|
|
1055
|
+
}
|
|
1056
|
+
case "bank-pool": {
|
|
1057
|
+
if (!id) throw new CliError("Usage: admin tuning bank-pool <eval-id> [--per-source 150] [--days 14]");
|
|
1058
|
+
const r = await staffRequest(
|
|
1059
|
+
ctx,
|
|
1060
|
+
"POST",
|
|
1061
|
+
`${T}/bank/${encodeURIComponent(id)}/pool`,
|
|
1062
|
+
compact({ per_source: flagNum(flags["per-source"]), days: flagNum(flags.days) })
|
|
1063
|
+
);
|
|
1064
|
+
if (json) return printResult(io, flags, r);
|
|
1065
|
+
io.log(`${r.added} new instances.`);
|
|
1066
|
+
for (const [src, s] of Object.entries(r.stats))
|
|
1067
|
+
io.log(
|
|
1068
|
+
`${src}: ${s.matches} matches in ${s.textsWithMatch} of ${s.texts} texts (${s.perText} a text, ${Math.round(100 * s.share)}% of texts)`
|
|
1069
|
+
);
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
1072
|
+
case "bank-judge": {
|
|
1073
|
+
if (!id)
|
|
1074
|
+
throw new CliError(
|
|
1075
|
+
"Usage: admin tuning bank-judge <eval-id> --live | --prompt-file f.md [--model m] [--mode score|same] [--threshold 4] [--context paragraph|sentence] [--examples id,id]"
|
|
1076
|
+
);
|
|
1077
|
+
const file = flagStr(flags["prompt-file"]);
|
|
1078
|
+
const body = flagBool(flags.live)
|
|
1079
|
+
? { from_live: true }
|
|
1080
|
+
: compact({
|
|
1081
|
+
prompt: file ? readFileSync(file, "utf8") : flagStr(flags.prompt),
|
|
1082
|
+
model: flagStr(flags.model),
|
|
1083
|
+
mode: flagStr(flags.mode),
|
|
1084
|
+
threshold: flagNum(flags.threshold),
|
|
1085
|
+
context: flagStr(flags.context),
|
|
1086
|
+
example_item_ids: flagStr(flags.examples)
|
|
1087
|
+
?.split(",")
|
|
1088
|
+
.map((x) => x.trim())
|
|
1089
|
+
.filter(Boolean),
|
|
1090
|
+
note: flagStr(flags.note),
|
|
1091
|
+
});
|
|
1092
|
+
return printResult(
|
|
1093
|
+
io,
|
|
1094
|
+
flags,
|
|
1095
|
+
await staffRequest(ctx, "POST", `${T}/bank/${encodeURIComponent(id)}/judges`, body)
|
|
1096
|
+
);
|
|
1097
|
+
}
|
|
1098
|
+
case "bank-score": {
|
|
1099
|
+
const version = flagNum(flags.version);
|
|
1100
|
+
if (!id || !version) throw new CliError("Usage: admin tuning bank-score <eval-id> --version N");
|
|
1101
|
+
const r = await staffRequest(
|
|
1102
|
+
ctx,
|
|
1103
|
+
"POST",
|
|
1104
|
+
`${T}/bank/${encodeURIComponent(id)}/judges/${version}/score`,
|
|
1105
|
+
{}
|
|
1106
|
+
);
|
|
1107
|
+
if (json) return printResult(io, flags, r);
|
|
1108
|
+
const read = r.report.judges.find((j) => j.version === version);
|
|
1109
|
+
io.log(`scored ${r.scored} (${r.failed} failed).`);
|
|
1110
|
+
if (read)
|
|
1111
|
+
io.log(
|
|
1112
|
+
`v${version} on ${read.held_out.n} held-out labels: agreement ${read.held_out.agreement ?? "—"}, precision ${read.held_out.precision ?? "—"}, recall ${read.held_out.recall ?? "—"}; cuts ours ${read.cut_rate.ours ?? "—"}, human ${read.cut_rate.human ?? "—"}${read.calibrates ? " — calibrates a trial" : ""}`
|
|
1113
|
+
);
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
1116
|
+
case "bank-try": {
|
|
1117
|
+
const version = flagNum(flags.version);
|
|
1118
|
+
if (!id || !version)
|
|
1119
|
+
throw new CliError("Usage: admin tuning bank-try <eval-id> --version N [--kernels Hazel,Aspen]");
|
|
1120
|
+
const kernels = flagStr(flags.kernels)
|
|
1121
|
+
?.split(",")
|
|
1122
|
+
.map((x) => x.trim())
|
|
1123
|
+
.filter(Boolean);
|
|
1124
|
+
const r = await staffRequest(
|
|
1125
|
+
ctx,
|
|
1126
|
+
"POST",
|
|
1127
|
+
`${T}/bank/${encodeURIComponent(id)}/judges/${version}/trial`,
|
|
1128
|
+
compact({ kernels, title: flagStr(flags.title) })
|
|
1129
|
+
);
|
|
1130
|
+
if (json) return printResult(io, flags, r);
|
|
1131
|
+
io.log(`comparison ${r.comparison_id}: ${r.candidates.map((c) => `${c.kernel} v${c.version}`).join(", ")}`);
|
|
1132
|
+
if (r.refused.length)
|
|
1133
|
+
io.log(`refused: ${r.refused.map((x) => `${x.kernel ?? "?"}: ${x.error}`).join("; ")}`);
|
|
1134
|
+
return io.log(
|
|
1135
|
+
`${ctx.client.url}/superadmin/tuning?${new URLSearchParams({ tab: "compare", comparison: r.comparison_id })}`
|
|
1136
|
+
);
|
|
1137
|
+
}
|
|
1138
|
+
case "people": {
|
|
1139
|
+
const r = await staffRequest(ctx, "GET", `${T}/people`);
|
|
1140
|
+
if (json) return printResult(io, flags, r);
|
|
1141
|
+
for (const p of r.people)
|
|
1142
|
+
io.log(
|
|
1143
|
+
`${p.id} ${p.email} ${p.role} — ${p.revoked_at ? "revoked" : p.signed_in ? "signed in" : "invited"}${p.invite_link ? ` ${p.invite_link}` : ""}`
|
|
1144
|
+
);
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
case "invite": {
|
|
1148
|
+
const email = flagStr(flags.email);
|
|
1149
|
+
if (!email) throw new CliError('Usage: admin tuning invite --email e [--note "…"]');
|
|
1150
|
+
const r = await staffRequest(
|
|
1151
|
+
ctx,
|
|
1152
|
+
"POST",
|
|
1153
|
+
`${T}/people`,
|
|
1154
|
+
compact({ email, role: "editor", note: flagStr(flags.note) })
|
|
1155
|
+
);
|
|
1156
|
+
if (json) return printResult(io, flags, r);
|
|
1157
|
+
return io.log(
|
|
1158
|
+
`invited ${r.person.email} as an editor${r.invite_link ? `: ${r.invite_link}` : ""}${r.email_sent ? " (emailed)" : ""}`
|
|
1159
|
+
);
|
|
1160
|
+
}
|
|
1161
|
+
case "revoke": {
|
|
1162
|
+
if (!id) throw new CliError("Usage: admin tuning revoke <person-id>");
|
|
1163
|
+
return printResult(io, flags, await staffRequest(ctx, "DELETE", `${T}/people/${id}`));
|
|
1164
|
+
}
|
|
824
1165
|
case "side-by-side": {
|
|
825
1166
|
const ids = (id ?? "")
|
|
826
1167
|
.split(",")
|
|
@@ -1202,6 +1543,98 @@ async function cmdAdminTuning(ctx) {
|
|
|
1202
1543
|
? printResult(io, flags, r)
|
|
1203
1544
|
: io.log(`queued ${r.queued} run(s)${r.waiting ? `; waiting: ${r.waiting}` : ""}`);
|
|
1204
1545
|
}
|
|
1546
|
+
// --- The bake-off: a screen's versions graded item by item, the winners table, the merge ---
|
|
1547
|
+
case "bakeoff": {
|
|
1548
|
+
if (!id) throw new CliError("Usage: admin tuning bakeoff <screen-id> [--regrade]");
|
|
1549
|
+
const r = await staffRequest(
|
|
1550
|
+
ctx,
|
|
1551
|
+
"GET",
|
|
1552
|
+
`${T}/screens/${id}/bakeoff${flagBool(flags.regrade) ? "?regrade=1" : ""}`
|
|
1553
|
+
);
|
|
1554
|
+
if (json) return printResult(io, flags, r);
|
|
1555
|
+
io.log(
|
|
1556
|
+
`bake-off ${r.screen.title}: v${r.versions.join(", v")} on ${r.inputs.length} input(s); leader ${r.leader === null ? "none" : `v${r.leader}`}`
|
|
1557
|
+
);
|
|
1558
|
+
if (r.unversioned.length) io.log(`left out (not a saved version): ${r.unversioned.join(", ")}`);
|
|
1559
|
+
io.log("");
|
|
1560
|
+
io.log(
|
|
1561
|
+
`${"item".padEnd(32)} ${"trusted".padEnd(8)} ${"leader".padEnd(7)} ${r.versions.map((v) => `v${v}`.padStart(6)).join("")} graded`
|
|
1562
|
+
);
|
|
1563
|
+
for (const row of r.table)
|
|
1564
|
+
io.log(
|
|
1565
|
+
`${row.item.id.padEnd(32)} ${(row.trusted ? "yes" : "no").padEnd(8)} ${(row.leader === null ? "—" : `v${row.leader}`).padEnd(7)} ${r.versions.map((v) => String(row.wins[v] ?? 0).padStart(6)).join("")} ${row.graded}`
|
|
1566
|
+
);
|
|
1567
|
+
io.log("");
|
|
1568
|
+
io.log(
|
|
1569
|
+
r.conflicts_if_merged.length
|
|
1570
|
+
? `conflicts if merged: ${r.conflicts_if_merged.map((c) => `${c.path} ← ${c.candidates.map((x) => `${x.item} v${x.version}`).join(" | ")}`).join("; ")}`
|
|
1571
|
+
: "conflicts if merged: none"
|
|
1572
|
+
);
|
|
1573
|
+
return;
|
|
1574
|
+
}
|
|
1575
|
+
case "bakeoff-pick": {
|
|
1576
|
+
const input = flagStr(flags.input);
|
|
1577
|
+
const item = flagStr(flags.item);
|
|
1578
|
+
const winner = flagStr(flags.winner);
|
|
1579
|
+
if (!id || !input || !item || !winner)
|
|
1580
|
+
throw new CliError(
|
|
1581
|
+
"Usage: admin tuning bakeoff-pick <screen-id> --input <key> --item <category:id|axis:id> --winner <version|none>"
|
|
1582
|
+
);
|
|
1583
|
+
const winnerVersion = winner === "none" ? null : Number(winner);
|
|
1584
|
+
if (winnerVersion !== null && !(Number.isInteger(winnerVersion) && winnerVersion > 0))
|
|
1585
|
+
throw new CliError("--winner is a version number, or none");
|
|
1586
|
+
const r = await staffRequest(ctx, "POST", `${T}/screens/${id}/bakeoff/pick`, {
|
|
1587
|
+
input_key: input,
|
|
1588
|
+
item_id: item,
|
|
1589
|
+
winner_version: winnerVersion,
|
|
1590
|
+
});
|
|
1591
|
+
if (json) return printResult(io, flags, r);
|
|
1592
|
+
const row = r.read.table.find((x) => x.item.id === item);
|
|
1593
|
+
return io.log(
|
|
1594
|
+
`graded ${item} on ${input}: ${winnerVersion === null ? "none" : `v${winnerVersion}`}${row ? ` · leader now ${row.leader === null ? "none" : `v${row.leader}`}` : ""}`
|
|
1595
|
+
);
|
|
1596
|
+
}
|
|
1597
|
+
case "bakeoff-merge": {
|
|
1598
|
+
if (!id)
|
|
1599
|
+
throw new CliError(
|
|
1600
|
+
'Usage: admin tuning bakeoff-merge <screen-id> [--resolve item=version,…] [--note "…"] [--compare 10]'
|
|
1601
|
+
);
|
|
1602
|
+
const resolve = {};
|
|
1603
|
+
for (const pair of flagList(flags.resolve).flatMap((x) =>
|
|
1604
|
+
x
|
|
1605
|
+
.split(",")
|
|
1606
|
+
.map((y) => y.trim())
|
|
1607
|
+
.filter(Boolean)
|
|
1608
|
+
)) {
|
|
1609
|
+
const [item, version] = pair.split("=").map((x) => x.trim());
|
|
1610
|
+
const v = Number(version);
|
|
1611
|
+
if (!item || !(Number.isInteger(v) && v > 0))
|
|
1612
|
+
throw new CliError(`--resolve: "${pair}" is not item=version`);
|
|
1613
|
+
resolve[item] = v;
|
|
1614
|
+
}
|
|
1615
|
+
const compare = flags.compare === undefined ? undefined : { pairs: flagNum(flags.compare) ?? 10 };
|
|
1616
|
+
const r = await staffRequest(
|
|
1617
|
+
ctx,
|
|
1618
|
+
"POST",
|
|
1619
|
+
`${T}/screens/${id}/bakeoff/merge`,
|
|
1620
|
+
compact({
|
|
1621
|
+
resolve: Object.keys(resolve).length ? resolve : undefined,
|
|
1622
|
+
note: flagStr(flags.note),
|
|
1623
|
+
compare,
|
|
1624
|
+
})
|
|
1625
|
+
);
|
|
1626
|
+
if (json) return printResult(io, flags, r);
|
|
1627
|
+
io.log(
|
|
1628
|
+
`merged from v${r.base} → candidate v${r.version}${r.comparison_id ? ` · comparison ${r.comparison_id}` : ""}`
|
|
1629
|
+
);
|
|
1630
|
+
for (const p of r.provenance)
|
|
1631
|
+
io.log(` ${p.applied ? "applied" : `skipped (${p.skipped})`} ${p.item} ${p.path} from v${p.from}`);
|
|
1632
|
+
if (r.conflicts.length)
|
|
1633
|
+
io.log(
|
|
1634
|
+
`conflicts left (the base's value kept): ${r.conflicts.map((c) => `${c.path} ← ${c.candidates.map((x) => `${x.item} v${x.version}`).join(" | ")}`).join("; ")}`
|
|
1635
|
+
);
|
|
1636
|
+
return;
|
|
1637
|
+
}
|
|
1205
1638
|
// --- Research: our arena's versions, research comparisons, the deploy split ---
|
|
1206
1639
|
case "arenas": {
|
|
1207
1640
|
const r = await staffRequest(ctx, "GET", `${T}/arena-files`);
|
|
@@ -1570,7 +2003,7 @@ async function cmdAdminTuning(ctx) {
|
|
|
1570
2003
|
}
|
|
1571
2004
|
default:
|
|
1572
2005
|
throw new CliError(
|
|
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)"
|
|
2006
|
+
"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)"
|
|
1574
2007
|
);
|
|
1575
2008
|
}
|
|
1576
2009
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { CliError } from "../client.mjs";
|
|
6
6
|
import { printResult, ok, argsFromFlags } from "./shared.mjs";
|
|
7
7
|
|
|
8
|
-
const CMS_PROVIDERS = ["webflow", "framer", "sanity", "contentful"];
|
|
8
|
+
const CMS_PROVIDERS = ["webflow", "framer", "sanity", "contentful", "github"];
|
|
9
9
|
const PUBLISH_CONNECTORS = ["webflow", "framer", "google_docs", "contentful", "github"];
|
|
10
10
|
|
|
11
11
|
// How every tool this module calls gets its arguments — see `argsFromFlags` in shared.mjs.
|
|
@@ -121,7 +121,7 @@ async function connectorsPull(ctx) {
|
|
|
121
121
|
// or both (--provider wires an unbound --collection first). At least one required.
|
|
122
122
|
const args = argsFromFlags(ARGS.import_from_cms, flags);
|
|
123
123
|
if (!args.collection_id && !args.provider) {
|
|
124
|
-
throw new CliError("Pass --collection <id> and/or --provider <webflow|framer|sanity|contentful>");
|
|
124
|
+
throw new CliError("Pass --collection <id> and/or --provider <webflow|framer|sanity|contentful|github>");
|
|
125
125
|
}
|
|
126
126
|
const result = await client.callTool("import_from_cms", args);
|
|
127
127
|
ok(
|
package/lib/commands/onboard.mjs
CHANGED
|
@@ -66,7 +66,7 @@ import {
|
|
|
66
66
|
const COVER_STYLES = ["editorial_image", "full_ai", "line_art"];
|
|
67
67
|
const DOMAIN_MODES = ["subdomain", "buy_now", "connect_later"];
|
|
68
68
|
const SEER_MODES = ["suggest", "skip"];
|
|
69
|
-
const CMS_PROVIDERS = ["webflow", "framer", "sanity", "contentful"];
|
|
69
|
+
const CMS_PROVIDERS = ["webflow", "framer", "sanity", "contentful", "github"];
|
|
70
70
|
const APPROVAL_MODES = ["autopilot", "review_window", "manual"];
|
|
71
71
|
|
|
72
72
|
// How every tool this module calls gets its arguments — see `argsFromFlags` in shared.mjs.
|
package/lib/commands/phantom.mjs
CHANGED
|
@@ -73,6 +73,12 @@ export const ARGS = {
|
|
|
73
73
|
theme: "theme",
|
|
74
74
|
customer_ref: { flag: "customer-ref" },
|
|
75
75
|
collection_id: { flag: "collection" },
|
|
76
|
+
redesign: { flag: "redesign", type: "bool", negate: "no-redesign" },
|
|
77
|
+
// `--layout none` clears the choice (the site's theme then suggests one).
|
|
78
|
+
layout: {
|
|
79
|
+
value: (flags) =>
|
|
80
|
+
flags.layout === undefined ? undefined : flags.layout === "none" ? null : String(flags.layout),
|
|
81
|
+
},
|
|
76
82
|
},
|
|
77
83
|
},
|
|
78
84
|
collection: { value: (flags) => readJsonObjectFlag(flags, "collection-config") },
|