@letterstory/cli 0.66.0 → 0.70.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 +10 -4
- package/lib/cli.mjs +23 -13
- package/lib/commands/admin.mjs +228 -12
- package/lib/commands/collection.mjs +14 -1
- package/lib/commands/insights.mjs +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -161,6 +161,7 @@ publishing, targets, measurement) that produces and publishes posts on a cadence
|
|
|
161
161
|
|
|
162
162
|
```bash
|
|
163
163
|
letterstory collection list # public_id = the HTTP door's address
|
|
164
|
+
letterstory collection overview # site, state, posts, in Google, AI reads per collection
|
|
164
165
|
letterstory collection new --name "Launches" --config-file cfg.json
|
|
165
166
|
letterstory collection config <id> # print the one file
|
|
166
167
|
letterstory collection config <id> --config '{"targets":{"enabled":true,"tier":"ramping"}}' # patch it
|
|
@@ -369,14 +370,17 @@ letterstory admin tuning trial-run <trial> --repeats 2 # runs advance on the se
|
|
|
369
370
|
letterstory admin tuning trial-save <trial> --note "…" # a trial as a candidate version (never live); then `admin tuning compare`
|
|
370
371
|
letterstory admin tuning promote <experiment-id> --versions <service-kernel-id>:21 # from a concluded "ship" decision
|
|
371
372
|
letterstory admin experiments list --q colons # what we've tried
|
|
373
|
+
letterstory admin experiments list --primitive write --stage rarity # what we learned about one stage
|
|
374
|
+
letterstory admin experiments new "A different writer, voice rewritten, rarity rewrite off" --primitive write # an investigation in Write's notebook
|
|
372
375
|
letterstory admin experiments new "We use too many colons" --as "claude-code"
|
|
373
376
|
letterstory admin experiments update <id> --plan-file plan.json --budget 120
|
|
374
|
-
# ...a person approves the plan
|
|
377
|
+
# ...a person approves the plan on the investigation's page (approval gates spending a budget; a person's
|
|
378
|
+
# investigation needs none and is concluded straight away)...
|
|
375
379
|
letterstory admin experiments spend <id> 54
|
|
376
380
|
letterstory admin tuning-import lab/results/colons/publish/<brief>.json --experiment <id>
|
|
377
381
|
letterstory admin experiments conclude <id> --decision drop \
|
|
378
382
|
--conclusion "Colons fell 60%; P(machine) did not move; quality held." \
|
|
379
|
-
--evidence lab:"kernels lab/results/colons/report.md"
|
|
383
|
+
--evidence lab:"kernels lab/results/colons/report.md" --stages rules
|
|
380
384
|
```
|
|
381
385
|
|
|
382
386
|
The ledger behind Kernel Tuning's Experiments tab
|
|
@@ -728,8 +732,10 @@ AI visibility and page access — the Measure tabs' reads, for scripts and agent
|
|
|
728
732
|
```bash
|
|
729
733
|
# The Measure Overview: how often AI answers name you and cite your pages (rates
|
|
730
734
|
# with 95% intervals and gated trends), share of voice, search for your site and
|
|
731
|
-
# the phantom network kept apart, and each ranking target's standing.
|
|
732
|
-
|
|
735
|
+
# the phantom network kept apart, and each ranking target's standing. --part
|
|
736
|
+
# (repeatable) returns only those blocks: audience, ai, shareOfVoice, pageAccess,
|
|
737
|
+
# search, actions, targets — `ai` is the slow one on a large org.
|
|
738
|
+
letterstory insights summary [--period 14d|30d|90d] [--part <block>]...
|
|
733
739
|
|
|
734
740
|
# The AI Visibility Bank: which questions named you, cited your pages, or both (a
|
|
735
741
|
# breakthrough). All time unless --period; --source picks the phantom network
|
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.70.0";
|
|
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.
|
|
@@ -201,6 +201,7 @@ Auth:
|
|
|
201
201
|
|
|
202
202
|
Collections (the content engine as a primitive — one door, 'collection run'):
|
|
203
203
|
collection list Your collections (public_id = the HTTP door's address)
|
|
204
|
+
collection overview Each collection's site, state, posts, posts in Google, AI reads
|
|
204
205
|
collection new --name <n> [--description <t>] [--config '<json>'|--config-file cfg.json]
|
|
205
206
|
collection config <id> [--config '<json>'|--config-file cfg.json]
|
|
206
207
|
Print the ONE FILE (identity, content, cadence,
|
|
@@ -554,19 +555,24 @@ Admin (STAFF key — --staff-key / LETTERSTORY_STAFF_API_KEY; acts before an org
|
|
|
554
555
|
Live answer replays: breakthroughs re-asked in ChatGPT /
|
|
555
556
|
Gemini / Google / Perplexity / Claude, with screenshots.
|
|
556
557
|
Omit --org for the whole fleet. Needs fleet:read.
|
|
557
|
-
admin replay --org <uuid>
|
|
558
|
+
admin replay --org <uuid> [<response-id> | --target <uuid>] [--surface openai|anthropic|google-aio|google-gemini|perplexity]
|
|
558
559
|
Replay a breakthrough live now (background; poll
|
|
559
|
-
admin replays).
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
|
560
|
+
admin replays). With neither, the client's newest
|
|
561
|
+
breakthrough. Needs fleet:replay.
|
|
562
|
+
admin experiments list [--q colons] [--status proposed] [--primitive write] [--stage rarity] | show <id>
|
|
563
|
+
| new "<what you are investigating>" [--primitive write|research|shred|enhance] [--stages a,b]
|
|
564
|
+
[--hypothesis …] [--plan-file p.json] [--tags a,b] [--as "<agent>"]
|
|
565
|
+
| update <id> [--plan-file …] [--result-file …] [--evidence kind:ref,…] [--stages a,b] [--budget N]
|
|
563
566
|
| note|started <id> "<text>" | spend <id> <runs>
|
|
564
|
-
| conclude <id> --decision ship|drop|follow-up|inconclusive --conclusion "…" [--evidence …]
|
|
567
|
+
| conclude <id> --decision ship|drop|follow-up|inconclusive --conclusion "…" [--stages a,b] [--evidence …]
|
|
565
568
|
| abandon <id> --reason "…"
|
|
566
|
-
|
|
567
|
-
how, what it found and why
|
|
568
|
-
|
|
569
|
-
|
|
569
|
+
Each primitive's notebook of investigations:
|
|
570
|
+
what we tried, how, what it found and why,
|
|
571
|
+
and the pipeline stages it is about. A plan
|
|
572
|
+
is approved by a signed-in person before a
|
|
573
|
+
budget is spent; concluding needs evidence,
|
|
574
|
+
not approval. "ship" hands off: a promoter
|
|
575
|
+
makes it live. Needs tuning:experiment.
|
|
570
576
|
admin tuning-import <brief-results.json> [--experiment <id>]
|
|
571
577
|
One brief's kernels lab results as a console
|
|
572
578
|
session (a trial per arm), linked to its
|
|
@@ -630,7 +636,11 @@ Admin (STAFF key — --staff-key / LETTERSTORY_STAFF_API_KEY; acts before an org
|
|
|
630
636
|
candidate, compare, comparisons, picks, pick,
|
|
631
637
|
reading-list, pair, side-by-side, promote,
|
|
632
638
|
sessions, session-new, propose, trial-add,
|
|
633
|
-
trial-run, trial-save,
|
|
639
|
+
trial-run, trial-save, the Write input bank
|
|
640
|
+
(inputs, input, inputs-harvest, input-save),
|
|
641
|
+
screens (screen-new, screens, screen,
|
|
642
|
+
screen-advance),
|
|
643
|
+
and the arena, shredder
|
|
634
644
|
and enhancer twins (every flag is listed in
|
|
635
645
|
cli/lib/commands/admin.mjs).
|
|
636
646
|
admin create-org --domain <bare-domain> [--name "Acme"] [--test] [--no-key]
|
|
@@ -882,7 +892,7 @@ Mail:
|
|
|
882
892
|
mail postal-address "<address>" Set the org's CAN-SPAM mailing address (email footers)
|
|
883
893
|
|
|
884
894
|
Insights:
|
|
885
|
-
insights summary [--period
|
|
895
|
+
insights summary [--period …] [--part <block>]... Measure Overview: AI named/cited rates, share of voice, search, targets
|
|
886
896
|
insights site [--period 14d|30d|90d] [--collection <uuid>]
|
|
887
897
|
insights post <article-id> [--period 14d|30d|90d]
|
|
888
898
|
insights top [--period 14d|30d|90d] [--collection <uuid>] [--limit <n>] [--sort clicks|impressions]
|
package/lib/commands/admin.mjs
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
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)
|
|
14
|
-
// admin replay --org <id>
|
|
14
|
+
// admin replay --org <id> [<response-id> | --target <id>] [--surface …] (fleet:replay)
|
|
15
15
|
// admin create-org --domain acme.com [--name "Acme"] [--test] [--no-key] [--key-name N]
|
|
16
16
|
// [--capabilities a,b] [--onboard] [--about "…"] [--sitemap <url>]
|
|
17
17
|
// [--cms <provider>] [--posts N] [--seers suggest|skip] [--no-visuals]
|
|
@@ -268,7 +268,8 @@ function evidenceFrom(flags) {
|
|
|
268
268
|
|
|
269
269
|
function experimentLine(e) {
|
|
270
270
|
const decision = e.decision ? ` → ${e.decision}` : "";
|
|
271
|
-
|
|
271
|
+
const where = e.primitive ? `${e.primitive}${e.stages?.length ? `: ${e.stages.join(", ")}` : ""} ` : "";
|
|
272
|
+
return `${e.id} [${e.status}${decision}] ${where}${e.question}${e.gist ? `\n ${e.gist}` : ""}`;
|
|
272
273
|
}
|
|
273
274
|
|
|
274
275
|
/**
|
|
@@ -293,7 +294,13 @@ async function cmdAdminExperiments(ctx) {
|
|
|
293
294
|
switch (sub) {
|
|
294
295
|
case "list": {
|
|
295
296
|
const params = new URLSearchParams(
|
|
296
|
-
compact({
|
|
297
|
+
compact({
|
|
298
|
+
q: flagStr(flags.q),
|
|
299
|
+
status: flagStr(flags.status),
|
|
300
|
+
primitive: flagStr(flags.primitive),
|
|
301
|
+
stage: flagStr(flags.stage),
|
|
302
|
+
limit: flagStr(flags.limit),
|
|
303
|
+
})
|
|
297
304
|
);
|
|
298
305
|
const result = await staffRequest(ctx, "GET", `${EXPERIMENTS}?${params}`);
|
|
299
306
|
return out(result, (r) =>
|
|
@@ -306,9 +313,13 @@ async function cmdAdminExperiments(ctx) {
|
|
|
306
313
|
case "new": {
|
|
307
314
|
const question = [id, ...rest].filter(Boolean).join(" ").trim();
|
|
308
315
|
if (question.length < 3)
|
|
309
|
-
throw new CliError(
|
|
316
|
+
throw new CliError(
|
|
317
|
+
'Usage: admin experiments new "<what you are investigating>" [--primitive write|research|shred|enhance] [--stages write,rarity] [--plan-file plan.json] [--tags a,b]'
|
|
318
|
+
);
|
|
310
319
|
const body = compact({
|
|
311
320
|
question,
|
|
321
|
+
primitive: flagStr(flags.primitive),
|
|
322
|
+
stages: flagList(flags.stages),
|
|
312
323
|
restated: flagStr(flags.hypothesis),
|
|
313
324
|
plan: jsonFile(flags, "plan"),
|
|
314
325
|
method: flagStr(flags.method),
|
|
@@ -331,6 +342,7 @@ async function cmdAdminExperiments(ctx) {
|
|
|
331
342
|
result: jsonFile(flags, "result"),
|
|
332
343
|
method: flagStr(flags.method),
|
|
333
344
|
tags: flagList(flags.tags),
|
|
345
|
+
stages: flagList(flags.stages),
|
|
334
346
|
budget_runs: flagNum(flags.budget),
|
|
335
347
|
engine_build: flagStr(flags.build),
|
|
336
348
|
add_evidence: evidenceFrom(flags),
|
|
@@ -357,12 +369,13 @@ async function cmdAdminExperiments(ctx) {
|
|
|
357
369
|
const conclusion = flagStr(flags.conclusion);
|
|
358
370
|
if (!id || !decision || !conclusion)
|
|
359
371
|
throw new CliError(
|
|
360
|
-
'Usage: admin experiments conclude <id> --decision ship|drop|follow-up|inconclusive --conclusion "<what we learned and why>" [--evidence kind:ref,…] [--result-file r.json]'
|
|
372
|
+
'Usage: admin experiments conclude <id> --decision ship|drop|follow-up|inconclusive --conclusion "<what we learned and why>" [--stages write,rarity] [--evidence kind:ref,…] [--result-file r.json]'
|
|
361
373
|
);
|
|
362
374
|
const body = compact({
|
|
363
375
|
action: "conclude",
|
|
364
376
|
decision,
|
|
365
377
|
conclusion,
|
|
378
|
+
stages: flagList(flags.stages),
|
|
366
379
|
result: jsonFile(flags, "result"),
|
|
367
380
|
add_evidence: evidenceFrom(flags),
|
|
368
381
|
actor: actorFrom(flags),
|
|
@@ -432,6 +445,8 @@ async function cmdAdminTuningContext(ctx) {
|
|
|
432
445
|
* admin tuning picks <comparison-id> the blind queue (next pairs)
|
|
433
446
|
* admin tuning pick <comparison-id> --cell <key> --pick left|right|tie record a blind pick
|
|
434
447
|
* admin tuning reading-list <comparison-id> [--n 10] your next blind pairs as links, to read off the screen
|
|
448
|
+
* admin tuning archive <comparison-id> [--undo] take a read comparison out of the working list (or back); the record stays
|
|
449
|
+
* admin tuning comparisons [--archived only|all] the list; archived ones only when asked
|
|
435
450
|
* admin tuning pair <comparison-id> --cell <key> one pair named: live and candidate, both texts, the measures per arm
|
|
436
451
|
* 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
|
|
437
452
|
* admin tuning candidates --kernels Hazel,Aspen|all --patch '{…}' [--base N] [--note "…"] the same changes as a candidate of each kernel
|
|
@@ -443,13 +458,28 @@ async function cmdAdminTuningContext(ctx) {
|
|
|
443
458
|
* admin tuning base-kernels the kernels a session can run on (ids for --kernel)
|
|
444
459
|
* admin tuning base-versions <kernel-id> the versions a session on it can run against
|
|
445
460
|
* admin tuning sessions | session <id> [--texts] list / one session: trials, runs and their numbers (--texts adds each trial's latest text)
|
|
446
|
-
* admin tuning session-new --title "…" --kernel <id|name> (--input-file f.md | --input "…") [--kind article|outline|brief]
|
|
461
|
+
* admin tuning session-new --title "…" --kernel <id|name> (--input-file f.md | --input "…" | --bank <input-id>) [--kind article|outline|brief]
|
|
447
462
|
* [--version N] [--writers echo/echo,…] [--trials-from <session-id>]
|
|
463
|
+
* (--bank: a bank input's text, and its request's constraints under every trial)
|
|
448
464
|
* admin tuning propose <session-id> --text "Echo with rarity on; live with the shred after" [--create]
|
|
449
465
|
* admin tuning trial-add <session-id> [--name "…"] [--patch '{…}'] [--options '{…}'] [--from <trial-id>]
|
|
450
466
|
* admin tuning trial-run <trial-id> [--repeats 2] queue runs (guardrails apply)
|
|
451
467
|
* admin tuning trial-save <trial-id> [--note "…"] save a trial as a candidate version (never live)
|
|
452
468
|
*
|
|
469
|
+
* The Write input bank: real kernel requests (above all the arena outlines Auto-Pen sends), as sent.
|
|
470
|
+
* admin tuning inputs [--filter "origin=arena_outline;material=thin,typical"] [--q "…"] list by tag, with coverage per bucket
|
|
471
|
+
* admin tuning input <input-id> one input in full: text, constraints, tags, run defaults, origin
|
|
472
|
+
* admin tuning inputs-harvest [--n 40] [--pool 400] [--callers auto-pen,endpoint] [--since-days 21] [--min-per-bucket 3]
|
|
473
|
+
* freeze recent production runs, stratified; idempotent by key
|
|
474
|
+
* admin tuning input-save <session-id> [--note "…"] save a session's pasted input or arena outline to the bank
|
|
475
|
+
*
|
|
476
|
+
* Screens: "is this candidate worth a full comparison?", on a slice of the bank (it does not decide).
|
|
477
|
+
* admin tuning screen-new --title "…" (--from <session-id> | --kernel <kernel-id> --versions 23,29) [--version N]
|
|
478
|
+
* [--n 8] [--filter "origin=arena_outline;material=thin"] [--inputs <id>,…] [--repeats 1] [--lift-budget]
|
|
479
|
+
* one session per input with the same trials; runs queue as the console's caps allow
|
|
480
|
+
* admin tuning screens | screen <id> list / one screen: per candidate, each measure against the Base, with its noise
|
|
481
|
+
* admin tuning screen-advance <id> queue what the caps allow now (the cron does it every minute)
|
|
482
|
+
*
|
|
453
483
|
* Research (Phase 3): our arena's versions as files, research comparisons, the deploy split.
|
|
454
484
|
* admin tuning arenas | arena <version-id> our arena's versions / one version's file and diff
|
|
455
485
|
* admin tuning arena-candidate --patch '{…}' | --file f.json [--of <version-id>] [--note "…"] save a candidate
|
|
@@ -492,6 +522,19 @@ function jsonFlag(raw, name) {
|
|
|
492
522
|
}
|
|
493
523
|
}
|
|
494
524
|
|
|
525
|
+
/**
|
|
526
|
+
* `--filter "origin=arena_outline;material=thin,typical"` → the bank's query params (one comma list
|
|
527
|
+
* per tag dimension; lib/tuning/input-tags.ts `parseTagFilter` reads the same form).
|
|
528
|
+
*/
|
|
529
|
+
function tagFilterParams(raw) {
|
|
530
|
+
const out = {};
|
|
531
|
+
for (const part of (raw ?? "").split(";")) {
|
|
532
|
+
const [k, v] = part.split("=");
|
|
533
|
+
if (k?.trim() && v?.trim()) out[k.trim()] = v.trim();
|
|
534
|
+
}
|
|
535
|
+
return out;
|
|
536
|
+
}
|
|
537
|
+
|
|
495
538
|
/**
|
|
496
539
|
* A session as an agent reads it: each trial's changes and runs with their headline numbers, and
|
|
497
540
|
* (with `texts`) the latest finished text per trial. The full session (traces, records) is `--json`.
|
|
@@ -612,8 +655,18 @@ async function cmdAdminTuning(ctx) {
|
|
|
612
655
|
);
|
|
613
656
|
return json ? printResult(io, flags, r) : io.log(`comparison ${r.comparison?.id ?? r.id ?? ""} started`);
|
|
614
657
|
}
|
|
615
|
-
case "comparisons":
|
|
616
|
-
|
|
658
|
+
case "comparisons": {
|
|
659
|
+
const archived = flagStr(flags.archived);
|
|
660
|
+
const qs = archived ? `?${new URLSearchParams({ archived })}` : "";
|
|
661
|
+
return printResult(io, { ...flags, json: true }, await staffRequest(ctx, "GET", `${T}/comparisons${qs}`));
|
|
662
|
+
}
|
|
663
|
+
case "archive": {
|
|
664
|
+
if (!id) throw new CliError("Usage: admin tuning archive <comparison-id> [--undo]");
|
|
665
|
+
const r = await staffRequest(ctx, "POST", `${T}/comparisons/${id}/archive`, { archived: !flags.undo });
|
|
666
|
+
return json
|
|
667
|
+
? printResult(io, flags, r)
|
|
668
|
+
: io.log(`comparison ${id} ${flags.undo ? "back in the list" : "archived"}`);
|
|
669
|
+
}
|
|
617
670
|
case "comparison":
|
|
618
671
|
if (!id) throw new CliError("Usage: admin tuning comparison <id>");
|
|
619
672
|
return printResult(io, { ...flags, json: true }, await staffRequest(ctx, "GET", `${T}/comparisons/${id}`));
|
|
@@ -770,9 +823,11 @@ async function cmdAdminTuning(ctx) {
|
|
|
770
823
|
throw new CliError(`--input-file ${path}: ${err.message}`);
|
|
771
824
|
}
|
|
772
825
|
}
|
|
773
|
-
|
|
826
|
+
// A bank input instead of a paste: its text, and its request's constraints under every trial.
|
|
827
|
+
const bank = flagStr(flags.bank);
|
|
828
|
+
if (!title || !kernel || (!input && !bank))
|
|
774
829
|
throw new CliError(
|
|
775
|
-
'Usage: admin tuning session-new --title "…" --kernel <id|name> (--input-file f.md | --input "…") [--kind article|outline|brief] [--version N] [--writers a,b] [--versions 23,29] [--trials-from <session-id>]'
|
|
830
|
+
'Usage: admin tuning session-new --title "…" --kernel <id|name> (--input-file f.md | --input "…" | --bank <input-id>) [--kind article|outline|brief] [--version N] [--writers a,b] [--versions 23,29] [--trials-from <session-id>]'
|
|
776
831
|
);
|
|
777
832
|
let kernelId = kernel;
|
|
778
833
|
if (!/^[0-9a-f-]{36}$/i.test(kernel)) {
|
|
@@ -788,8 +843,9 @@ async function cmdAdminTuning(ctx) {
|
|
|
788
843
|
T,
|
|
789
844
|
compact({
|
|
790
845
|
title,
|
|
791
|
-
input_text: input,
|
|
846
|
+
input_text: bank ? undefined : input,
|
|
792
847
|
input_kind: flagStr(flags.kind) ?? "unknown",
|
|
848
|
+
material: bank ? { source: "bank", input_id: bank } : undefined,
|
|
793
849
|
base_kernel_id: kernelId,
|
|
794
850
|
base_version: flagNum(flags.version),
|
|
795
851
|
trials_from_session_id: flagStr(flags["trials-from"]),
|
|
@@ -871,6 +927,167 @@ async function cmdAdminTuning(ctx) {
|
|
|
871
927
|
`${r.kernel}: saved as candidate v${r.version}${r.baseVersion ? ` (from v${r.baseVersion})` : ""}${r.leftBehind.length ? `; per-run, not saved: ${r.leftBehind.join(", ")}` : ""}`
|
|
872
928
|
);
|
|
873
929
|
}
|
|
930
|
+
// --- The Write input bank: real kernel requests a session can start from ---
|
|
931
|
+
case "inputs": {
|
|
932
|
+
const q = new URLSearchParams(
|
|
933
|
+
compact({ ...tagFilterParams(flagStr(flags.filter)), q: flagStr(flags.q), limit: flagStr(flags.limit) })
|
|
934
|
+
);
|
|
935
|
+
const r = await staffRequest(ctx, "GET", `${T}/inputs${q.size ? `?${q}` : ""}`);
|
|
936
|
+
if (json) return printResult(io, flags, r);
|
|
937
|
+
const coverage = Object.entries(r.coverage)
|
|
938
|
+
.map(
|
|
939
|
+
([dim, buckets]) =>
|
|
940
|
+
` ${dim.padEnd(11)} ${Object.entries(buckets)
|
|
941
|
+
.map(([b, n]) => `${b} ${n}`)
|
|
942
|
+
.join(" · ")}`
|
|
943
|
+
)
|
|
944
|
+
.join("\n");
|
|
945
|
+
return io.log(
|
|
946
|
+
[
|
|
947
|
+
`bank ${r.set}: ${r.inputs.length} of ${r.total} input(s)`,
|
|
948
|
+
...r.inputs.map(
|
|
949
|
+
(i) =>
|
|
950
|
+
`${i.id} ${i.source.padEnd(10)} ${String(i.tags.origin ?? "").padEnd(13)} ${String(i.tags.material?.bucket ?? "").padEnd(7)} ${String(i.tags.shape?.bucket ?? "").padEnd(5)} ${i.title}`
|
|
951
|
+
),
|
|
952
|
+
"coverage (the whole bank):",
|
|
953
|
+
coverage,
|
|
954
|
+
].join("\n")
|
|
955
|
+
);
|
|
956
|
+
}
|
|
957
|
+
case "input": {
|
|
958
|
+
if (!id) throw new CliError("Usage: admin tuning input <input-id>");
|
|
959
|
+
return printResult(io, { ...flags, json: true }, await staffRequest(ctx, "GET", `${T}/inputs/${id}`));
|
|
960
|
+
}
|
|
961
|
+
case "inputs-harvest": {
|
|
962
|
+
const r = await staffRequest(
|
|
963
|
+
ctx,
|
|
964
|
+
"POST",
|
|
965
|
+
`${T}/inputs/harvest`,
|
|
966
|
+
compact({
|
|
967
|
+
n: flagNum(flags.n),
|
|
968
|
+
pool: flagNum(flags.pool),
|
|
969
|
+
callers: flagStr(flags.callers)
|
|
970
|
+
?.split(",")
|
|
971
|
+
.map((c) => c.trim())
|
|
972
|
+
.filter(Boolean),
|
|
973
|
+
since_days: flagNum(flags["since-days"]),
|
|
974
|
+
min_per_bucket: flagNum(flags["min-per-bucket"]),
|
|
975
|
+
})
|
|
976
|
+
);
|
|
977
|
+
if (json) return printResult(io, flags, r);
|
|
978
|
+
return io.log(
|
|
979
|
+
[
|
|
980
|
+
`added ${r.added.length} input(s) to ${r.set} from a pool of ${r.pool} run(s); ${r.alreadyHeld} already held`,
|
|
981
|
+
...r.added.map((a) => ` ${a.key} (${a.reason})`),
|
|
982
|
+
...Object.entries(r.skipped).map(([why, n]) => ` skipped ${n}: ${why}`),
|
|
983
|
+
].join("\n")
|
|
984
|
+
);
|
|
985
|
+
}
|
|
986
|
+
case "input-save": {
|
|
987
|
+
if (!id) throw new CliError('Usage: admin tuning input-save <session-id> [--note "…"]');
|
|
988
|
+
const r = await staffRequest(
|
|
989
|
+
ctx,
|
|
990
|
+
"POST",
|
|
991
|
+
`${T}/sessions/${id}/bank`,
|
|
992
|
+
compact({ note: flagStr(flags.note) })
|
|
993
|
+
);
|
|
994
|
+
return json ? printResult(io, flags, r) : io.log(`bank input ${r.input.id} (${r.input.key})`);
|
|
995
|
+
}
|
|
996
|
+
// --- Screens: candidates across a slice of the bank, read against the Base, beside the noise ---
|
|
997
|
+
case "screen-new": {
|
|
998
|
+
const title = flagStr(flags.title);
|
|
999
|
+
const from = flagStr(flags.from);
|
|
1000
|
+
const versions = (flagStr(flags.versions) ?? "")
|
|
1001
|
+
.split(",")
|
|
1002
|
+
.map((v) => Number(v.trim()))
|
|
1003
|
+
.filter((v) => Number.isInteger(v) && v > 0);
|
|
1004
|
+
if (!title || (!from && !(flagStr(flags.kernel) && versions.length)))
|
|
1005
|
+
throw new CliError(
|
|
1006
|
+
'Usage: admin tuning screen-new --title "…" (--from <session-id> | --kernel <kernel-id> --versions 23,29) [--version N] [--n 8] [--filter "origin=arena_outline;material=thin"] [--inputs <id>,…] [--repeats 1] [--lift-budget]'
|
|
1007
|
+
);
|
|
1008
|
+
const filter = Object.fromEntries(
|
|
1009
|
+
Object.entries(tagFilterParams(flagStr(flags.filter))).map(([k, v]) => [
|
|
1010
|
+
k,
|
|
1011
|
+
v
|
|
1012
|
+
.split(",")
|
|
1013
|
+
.map((x) => x.trim())
|
|
1014
|
+
.filter(Boolean),
|
|
1015
|
+
])
|
|
1016
|
+
);
|
|
1017
|
+
const inputIds = (flagStr(flags.inputs) ?? "")
|
|
1018
|
+
.split(",")
|
|
1019
|
+
.map((x) => x.trim())
|
|
1020
|
+
.filter(Boolean);
|
|
1021
|
+
const r = await staffRequest(
|
|
1022
|
+
ctx,
|
|
1023
|
+
"POST",
|
|
1024
|
+
`${T}/screens`,
|
|
1025
|
+
compact({
|
|
1026
|
+
title,
|
|
1027
|
+
from_session_id: from,
|
|
1028
|
+
base_kernel_id: flagStr(flags.kernel),
|
|
1029
|
+
base_version: flagNum(flags.version),
|
|
1030
|
+
versions: versions.length ? versions : undefined,
|
|
1031
|
+
n: flagNum(flags.n),
|
|
1032
|
+
filter: Object.keys(filter).length ? filter : undefined,
|
|
1033
|
+
input_ids: inputIds.length ? inputIds : undefined,
|
|
1034
|
+
repeats: flagNum(flags.repeats),
|
|
1035
|
+
lift_budget: flagBool(flags["lift-budget"]) || undefined,
|
|
1036
|
+
})
|
|
1037
|
+
);
|
|
1038
|
+
if (json) return printResult(io, flags, r);
|
|
1039
|
+
return io.log(
|
|
1040
|
+
`screen ${r.screen.id}: ${r.screen.inputs} input(s), ${r.aggregate.columns.length} column(s); ${r.screen.pending} run(s) waiting${r.screen.waiting ? ` (${r.screen.waiting})` : ""} → ${ctx.client.url}/superadmin/tuning/screens/${r.screen.id}`
|
|
1041
|
+
);
|
|
1042
|
+
}
|
|
1043
|
+
case "screens": {
|
|
1044
|
+
const r = await staffRequest(ctx, "GET", `${T}/screens?limit=${flagNum(flags.limit) ?? 30}`);
|
|
1045
|
+
if (json) return printResult(io, flags, r);
|
|
1046
|
+
return io.log(
|
|
1047
|
+
r.screens
|
|
1048
|
+
.map(
|
|
1049
|
+
(x) =>
|
|
1050
|
+
`${x.id} ${x.status.padEnd(7)} ${x.title} · ${x.inputs} inputs${x.pending ? ` · ${x.pending} waiting` : ""}`
|
|
1051
|
+
)
|
|
1052
|
+
.join("\n") || "(no screens)"
|
|
1053
|
+
);
|
|
1054
|
+
}
|
|
1055
|
+
case "screen": {
|
|
1056
|
+
if (!id) throw new CliError("Usage: admin tuning screen <screen-id>");
|
|
1057
|
+
const r = await staffRequest(ctx, "GET", `${T}/screens/${id}`);
|
|
1058
|
+
if (json) return printResult(io, flags, r);
|
|
1059
|
+
// The reading as an agent reads it: per candidate, each measure's difference from the Base and its noise.
|
|
1060
|
+
return printResult(
|
|
1061
|
+
io,
|
|
1062
|
+
{ ...flags, json: true },
|
|
1063
|
+
{
|
|
1064
|
+
screen: r.screen,
|
|
1065
|
+
note: r.aggregate.note,
|
|
1066
|
+
columns: r.aggregate.columns,
|
|
1067
|
+
measures: r.aggregate.measures.map((m) => ({
|
|
1068
|
+
measure: m.label,
|
|
1069
|
+
judge: m.judgeId,
|
|
1070
|
+
standing: m.standing,
|
|
1071
|
+
sigma: m.sigma,
|
|
1072
|
+
per_column: m.perTrial.map((p) =>
|
|
1073
|
+
compact({
|
|
1074
|
+
column: p.column,
|
|
1075
|
+
n: p.n,
|
|
1076
|
+
mean: p.mean,
|
|
1077
|
+
delta: p.delta ?? undefined,
|
|
1078
|
+
})
|
|
1079
|
+
),
|
|
1080
|
+
})),
|
|
1081
|
+
}
|
|
1082
|
+
);
|
|
1083
|
+
}
|
|
1084
|
+
case "screen-advance": {
|
|
1085
|
+
if (!id) throw new CliError("Usage: admin tuning screen-advance <screen-id>");
|
|
1086
|
+
const r = await staffRequest(ctx, "POST", `${T}/screens/${id}/advance`, {});
|
|
1087
|
+
return json
|
|
1088
|
+
? printResult(io, flags, r)
|
|
1089
|
+
: io.log(`queued ${r.queued} run(s)${r.waiting ? `; waiting: ${r.waiting}` : ""}`);
|
|
1090
|
+
}
|
|
874
1091
|
// --- Research: our arena's versions, research comparisons, the deploy split ---
|
|
875
1092
|
case "arenas": {
|
|
876
1093
|
const r = await staffRequest(ctx, "GET", `${T}/arena-files`);
|
|
@@ -1977,7 +2194,6 @@ async function cmdAdminReplays(ctx, sub) {
|
|
|
1977
2194
|
const responseId = positionals[0];
|
|
1978
2195
|
const target = flagStr(flags.target);
|
|
1979
2196
|
if (!org) throw new CliError("admin replay needs --org <org-id>");
|
|
1980
|
-
if (!responseId && !target) throw new CliError("Pass a breakthrough <response-id>, or --target <target-id>");
|
|
1981
2197
|
const result = await staffPost(
|
|
1982
2198
|
ctx,
|
|
1983
2199
|
REPLAYS,
|
|
@@ -54,6 +54,7 @@ const TERMINAL = new Set(["published", "failed", "skipped", "cancelled"]);
|
|
|
54
54
|
*/
|
|
55
55
|
export const ARGS = {
|
|
56
56
|
list_collections: {},
|
|
57
|
+
get_collections_overview: {},
|
|
57
58
|
create_collection: {
|
|
58
59
|
name: { flag: "name", required: true },
|
|
59
60
|
description: "description",
|
|
@@ -153,11 +154,13 @@ export async function cmdCollection(ctx) {
|
|
|
153
154
|
return collectionCancel(rest);
|
|
154
155
|
case "inventory":
|
|
155
156
|
return collectionInventory(rest);
|
|
157
|
+
case "overview":
|
|
158
|
+
return collectionOverview(rest);
|
|
156
159
|
case "links":
|
|
157
160
|
return collectionLinks(rest);
|
|
158
161
|
default:
|
|
159
162
|
throw new CliError(
|
|
160
|
-
`Unknown collection subcommand: ${sub ?? "(none)"}. Try: list, new, config, delete, run, run-status, runs, approve, cancel, inventory, links`
|
|
163
|
+
`Unknown collection subcommand: ${sub ?? "(none)"}. Try: list, overview, new, config, delete, run, run-status, runs, approve, cancel, inventory, links`
|
|
161
164
|
);
|
|
162
165
|
}
|
|
163
166
|
}
|
|
@@ -168,6 +171,16 @@ async function collectionList(ctx) {
|
|
|
168
171
|
return 0;
|
|
169
172
|
}
|
|
170
173
|
|
|
174
|
+
// `collection overview` — each collection's site, state, posts, posts in Google and AI reads.
|
|
175
|
+
async function collectionOverview(ctx) {
|
|
176
|
+
const result = await ctx.client.callTool(
|
|
177
|
+
"get_collections_overview",
|
|
178
|
+
argsFromFlags(ARGS.get_collections_overview, ctx.flags)
|
|
179
|
+
);
|
|
180
|
+
printResult(ctx.io, ctx.flags, result);
|
|
181
|
+
return 0;
|
|
182
|
+
}
|
|
183
|
+
|
|
171
184
|
async function collectionCreate(ctx) {
|
|
172
185
|
const result = await ctx.client.callTool(
|
|
173
186
|
"create_collection",
|