@letterstory/cli 0.62.0 → 0.64.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 +6 -0
- package/lib/cli.mjs +3 -2
- package/lib/commands/admin.mjs +59 -4
- package/lib/commands/insights.mjs +10 -3
- package/lib/commands/posts.mjs +3 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -144,6 +144,7 @@ letterstory phantom domain connect <phantom-id> myblog.com # attach one you
|
|
|
144
144
|
letterstory posts list [--limit <n>] [--collection <uuid>]
|
|
145
145
|
letterstory posts show <article-id>
|
|
146
146
|
letterstory posts new --title "…" --collection <uuid> --body "…" [--article-id <uuid>] # or --file <path|->; --article-id updates that article instead of creating one
|
|
147
|
+
letterstory posts new --title "…" --collection <uuid> --body "…" --context "Internal data from 120 accounts: disputes fell 38%." # your own data: stated in the post with your attribution
|
|
147
148
|
letterstory posts publish <article-id>
|
|
148
149
|
letterstory posts unpublish <article-id>
|
|
149
150
|
letterstory posts delete --id <uuid> [--id <uuid> ...] # permanent; content, history, and any published copy are all removed
|
|
@@ -725,6 +726,11 @@ letterstory insights top [--period 14d|30d|90d] [--collection <uuid>] [--limit <
|
|
|
725
726
|
AI visibility and page access — the Measure tabs' reads, for scripts and agents:
|
|
726
727
|
|
|
727
728
|
```bash
|
|
729
|
+
# The Measure Overview: how often AI answers name you and cite your pages (rates
|
|
730
|
+
# 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
|
+
letterstory insights summary [--period 14d|30d|90d]
|
|
733
|
+
|
|
728
734
|
# The AI Visibility Bank: which questions named you, cited your pages, or both (a
|
|
729
735
|
# breakthrough). All time unless --period; --source picks the phantom network
|
|
730
736
|
# (default), your marketing site, or the whole portfolio; --phantom narrows to
|
package/lib/cli.mjs
CHANGED
|
@@ -55,7 +55,7 @@ import {
|
|
|
55
55
|
} from "./commands.mjs";
|
|
56
56
|
|
|
57
57
|
// Keep in sync with cli/package.json.
|
|
58
|
-
export const VERSION = "0.
|
|
58
|
+
export const VERSION = "0.64.0";
|
|
59
59
|
|
|
60
60
|
// Flags that never take a value. Listing them explicitly means `deploy get --json <id>`
|
|
61
61
|
// can't accidentally swallow the id as --json's value.
|
|
@@ -240,7 +240,7 @@ Phantomstory (a collection Letterstory hosts as an independent publication):
|
|
|
240
240
|
Content:
|
|
241
241
|
posts list [--limit <n>] [--collection <uuid>] List your articles
|
|
242
242
|
posts show <article-id> Show one article
|
|
243
|
-
posts new --title <t> --collection <uuid> (--body <text> | --file <path|->) [--article-id <uuid>]
|
|
243
|
+
posts new --title <t> --collection <uuid> (--body <text> | --file <path|->) [--article-id <uuid>] [--context <text>]
|
|
244
244
|
posts publish <article-id> Publish an article
|
|
245
245
|
posts unpublish <article-id> Unpublish an article
|
|
246
246
|
posts delete --id <uuid> [--id <uuid> ...] Permanently delete article(s)
|
|
@@ -862,6 +862,7 @@ Mail:
|
|
|
862
862
|
mail postal-address "<address>" Set the org's CAN-SPAM mailing address (email footers)
|
|
863
863
|
|
|
864
864
|
Insights:
|
|
865
|
+
insights summary [--period 14d|30d|90d] Measure Overview: AI named/cited rates, share of voice, search, targets
|
|
865
866
|
insights site [--period 14d|30d|90d] [--collection <uuid>]
|
|
866
867
|
insights post <article-id> [--period 14d|30d|90d]
|
|
867
868
|
insights top [--period 14d|30d|90d] [--collection <uuid>] [--limit <n>] [--sort clicks|impressions]
|
package/lib/commands/admin.mjs
CHANGED
|
@@ -452,6 +452,10 @@ async function cmdAdminTuningContext(ctx) {
|
|
|
452
452
|
* admin tuning arenas | arena <version-id> our arena's versions / one version's file and diff
|
|
453
453
|
* admin tuning arena-candidate --patch '{…}' | --file f.json [--of <version-id>] [--note "…"] save a candidate
|
|
454
454
|
* admin tuning research-compare --title "…" (--candidate <version-id> | --patch '{…}' | --null-check) [--keys …]
|
|
455
|
+
* admin tuning research-compare --title "…" --candidates <version-id>,… | --patches '[{…},{…}]' several
|
|
456
|
+
* candidates against ONE control arm (k + 1 runs per input, not 2k)
|
|
457
|
+
* admin tuning arena-dials every arena setting: help, default, evidence, live's value
|
|
458
|
+
* admin tuning comparison-extend <comparison-id> --keys k1,k2 | --all add inputs to a comparison on fixed inputs
|
|
455
459
|
* admin tuning research-inputs [--set v1] research's fixed inputs
|
|
456
460
|
* admin tuning research-split [--since-hours 72] [--at <iso>,…] what each deploy did to research in production
|
|
457
461
|
* admin tuning research-standing live's level on the fixed inputs, week by week
|
|
@@ -899,12 +903,34 @@ async function cmdAdminTuning(ctx) {
|
|
|
899
903
|
const versionId = flagStr(flags.candidate);
|
|
900
904
|
const patch = jsonFlag(flags.patch, "--patch");
|
|
901
905
|
const nullCheck = flagBool(flags["null-check"]);
|
|
902
|
-
const
|
|
906
|
+
const versionIds = flagList(flags.candidates);
|
|
907
|
+
const patches = jsonFlag(flags.patches, "--patches");
|
|
908
|
+
if (patches !== undefined && !Array.isArray(patches))
|
|
909
|
+
throw new CliError("--patches is a JSON array of patches: '[{…},{…}]'");
|
|
910
|
+
const named = [
|
|
911
|
+
versionId !== undefined,
|
|
912
|
+
patch !== undefined,
|
|
913
|
+
nullCheck === true,
|
|
914
|
+
(versionIds?.length ?? 0) + (patches?.length ?? 0) > 0,
|
|
915
|
+
].filter(Boolean).length;
|
|
903
916
|
if (!title || named !== 1)
|
|
904
917
|
throw new CliError(
|
|
905
|
-
"Usage: admin tuning research-compare --title \"…\" (--candidate <version-id> | --patch '{…}' | --null-check) [--keys k1,k2] [--concurrency 6] [--experiment <id>]"
|
|
918
|
+
"Usage: admin tuning research-compare --title \"…\" (--candidate <version-id> | --patch '{…}' | --null-check | --candidates <id>,… [--patches '[{…},…]']) [--keys k1,k2] [--concurrency 6] [--experiment <id>]"
|
|
906
919
|
);
|
|
907
|
-
const
|
|
920
|
+
const several =
|
|
921
|
+
versionIds?.length || patches?.length
|
|
922
|
+
? [
|
|
923
|
+
...(versionIds ?? []).map((v) => ({ version_id: v })),
|
|
924
|
+
...(patches ?? []).map((p) => ({ patch: p })),
|
|
925
|
+
]
|
|
926
|
+
: undefined;
|
|
927
|
+
const candidate = several
|
|
928
|
+
? undefined
|
|
929
|
+
: versionId
|
|
930
|
+
? { version_id: versionId }
|
|
931
|
+
: patch
|
|
932
|
+
? { patch }
|
|
933
|
+
: { null_check: true };
|
|
908
934
|
const r = await staffRequest(
|
|
909
935
|
ctx,
|
|
910
936
|
"POST",
|
|
@@ -913,6 +939,7 @@ async function cmdAdminTuning(ctx) {
|
|
|
913
939
|
primitive: "research",
|
|
914
940
|
title,
|
|
915
941
|
candidate,
|
|
942
|
+
candidates: several,
|
|
916
943
|
keys: flagList(flags.keys),
|
|
917
944
|
concurrency: flagNum(flags.concurrency),
|
|
918
945
|
experiment_id: flagStr(flags.experiment),
|
|
@@ -920,6 +947,34 @@ async function cmdAdminTuning(ctx) {
|
|
|
920
947
|
);
|
|
921
948
|
return json ? printResult(io, flags, r) : io.log(`research comparison ${r.comparison?.id ?? ""} started`);
|
|
922
949
|
}
|
|
950
|
+
case "arena-dials": {
|
|
951
|
+
const r = await staffRequest(ctx, "GET", `${T}/arena-files/dials`);
|
|
952
|
+
if (json) return printResult(io, flags, r);
|
|
953
|
+
const valueOf = (key) => key.split(".").reduce((v, k) => (v == null ? v : v[k]), r.live.file);
|
|
954
|
+
const lines = [];
|
|
955
|
+
for (const panel of r.panels) {
|
|
956
|
+
lines.push(`${panel.title} — ${panel.question}`);
|
|
957
|
+
for (const d of r.dials.filter((x) => x.panel === panel.id))
|
|
958
|
+
lines.push(
|
|
959
|
+
` ${d.key.padEnd(36)} live ${JSON.stringify(valueOf(d.key))} (default ${JSON.stringify(d.fleetDefault)}) ${d.label}`
|
|
960
|
+
);
|
|
961
|
+
}
|
|
962
|
+
lines.push(
|
|
963
|
+
`providers: ${r.providers.map((p) => `${p.id}${p.configured ? "" : " (no key here)"}`).join(", ")}`
|
|
964
|
+
);
|
|
965
|
+
return io.log(lines.join("\n"));
|
|
966
|
+
}
|
|
967
|
+
case "comparison-extend": {
|
|
968
|
+
const keys = flagBool(flags.all) ? "all" : flagList(flags.keys);
|
|
969
|
+
if (!id || !keys?.length)
|
|
970
|
+
throw new CliError("Usage: admin tuning comparison-extend <comparison-id> --keys k1,k2 | --all");
|
|
971
|
+
const r = await staffRequest(ctx, "POST", `${T}/comparisons/${id}/extend`, { keys });
|
|
972
|
+
return json
|
|
973
|
+
? printResult(io, flags, r)
|
|
974
|
+
: io.log(
|
|
975
|
+
`comparison ${r.comparison.id}: ${r.comparison.added.length} input(s) added, ${r.comparison.pairs} in all`
|
|
976
|
+
);
|
|
977
|
+
}
|
|
923
978
|
case "research-inputs": {
|
|
924
979
|
const set = flagStr(flags.set);
|
|
925
980
|
return printResult(
|
|
@@ -1182,7 +1237,7 @@ async function cmdAdminTuning(ctx) {
|
|
|
1182
1237
|
}
|
|
1183
1238
|
default:
|
|
1184
1239
|
throw new CliError(
|
|
1185
|
-
"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)"
|
|
1240
|
+
"Usage: admin tuning kernels|kernel|candidate|compare|comparisons|comparison|picks|pick|reading-list|pair|side-by-side|candidates|base-kernels|base-versions|sessions|session|session-new|propose|trial-add|trial-run|trial-save|promote|arenas|arena|arena-candidate|arena-dials|comparison-extend|research-compare|research-inputs|research-split|research-standing|promote-arena|shred-compare|shred-inputs|shred-split|shred-standing|shredders|shredder|shredder-candidate|promote-shredder|enhance-compare|enhance-inputs|enhance-split|enhance-standing|enhancers|enhancer|enhancer-candidate|promote-enhancer (see --help)"
|
|
1186
1241
|
);
|
|
1187
1242
|
}
|
|
1188
1243
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// `insights` — Search Console impressions/clicks across the phantom network, at the
|
|
2
2
|
// network, single-post, and top-posts levels (ported from phantomstory-cli's
|
|
3
|
-
// insights.ts); plus Measure's AI Visibility Bank (`bank`)
|
|
4
|
-
// Access (`access`) — the same reads the web app's Measure tabs render.
|
|
3
|
+
// insights.ts); plus Measure's Overview (`summary`), AI Visibility Bank (`bank`)
|
|
4
|
+
// and phantom Page Access (`access`) — the same reads the web app's Measure tabs render.
|
|
5
5
|
|
|
6
6
|
import { CliError } from "../client.mjs";
|
|
7
7
|
import { flagStr, flagNum, requirePositional, printResult, compact, argsFromFlags } from "./shared.mjs";
|
|
@@ -19,6 +19,9 @@ export const ARGS = {
|
|
|
19
19
|
period: { flag: "period", enum: PERIODS },
|
|
20
20
|
collection_id: { flag: "collection" },
|
|
21
21
|
},
|
|
22
|
+
get_measure_summary: {
|
|
23
|
+
period: { flag: "period", enum: PERIODS },
|
|
24
|
+
},
|
|
22
25
|
};
|
|
23
26
|
|
|
24
27
|
export async function cmdInsights(ctx) {
|
|
@@ -35,8 +38,12 @@ export async function cmdInsights(ctx) {
|
|
|
35
38
|
return call(rest, "get_ai_visibility_bank");
|
|
36
39
|
case "access":
|
|
37
40
|
return call(rest, "get_page_access");
|
|
41
|
+
case "summary":
|
|
42
|
+
return call(rest, "get_measure_summary");
|
|
38
43
|
default:
|
|
39
|
-
throw new CliError(
|
|
44
|
+
throw new CliError(
|
|
45
|
+
`Unknown insights subcommand: ${sub ?? "(none)"}. Try: summary, site, post, top, bank, access`
|
|
46
|
+
);
|
|
40
47
|
}
|
|
41
48
|
}
|
|
42
49
|
|
package/lib/commands/posts.mjs
CHANGED
|
@@ -73,7 +73,9 @@ async function postsNew(ctx) {
|
|
|
73
73
|
const body = readBodyInput(flags);
|
|
74
74
|
if (!body) throw new CliError("Provide --body or --file <path>.");
|
|
75
75
|
const articleId = flagStr(flags["article-id"]);
|
|
76
|
-
|
|
76
|
+
// Steering and/or your own data: figures in it are stated in the post with your attribution.
|
|
77
|
+
const context = flagStr(flags.context);
|
|
78
|
+
const args = compact({ title, body, collection_id: collection, article_id: articleId, context });
|
|
77
79
|
const result = await client.callTool("ingest_article", args);
|
|
78
80
|
ok(ctx, articleId ? `Updated "${title}".` : `Created draft "${title}".`);
|
|
79
81
|
printResult(io, flags, result);
|