@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 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.62.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]
@@ -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 named = [versionId !== undefined, patch !== undefined, nullCheck === true].filter(Boolean).length;
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 candidate = versionId ? { version_id: versionId } : patch ? { patch } : { null_check: true };
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`) and phantom Page
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(`Unknown insights subcommand: ${sub ?? "(none)"}. Try: site, post, top, bank, access`);
44
+ throw new CliError(
45
+ `Unknown insights subcommand: ${sub ?? "(none)"}. Try: summary, site, post, top, bank, access`
46
+ );
40
47
  }
41
48
  }
42
49
 
@@ -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
- const args = compact({ title, body, collection_id: collection, article_id: articleId });
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letterstory/cli",
3
- "version": "0.62.0",
3
+ "version": "0.64.0",
4
4
  "description": "Spin up and manage Letterstory phantom blogs from your terminal.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",