@letterstory/cli 0.176.24 → 0.176.26

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/lib/cli.mjs CHANGED
@@ -82,7 +82,7 @@ import {
82
82
  import { fetchParkedSections, withoutParked } from "./parked.mjs";
83
83
 
84
84
  // Matches cli/package.json; publish-cli.yml rewrites both to the published version.
85
- export const VERSION = "0.176.24";
85
+ export const VERSION = "0.176.26";
86
86
 
87
87
  /** The exit code for a gate refusal (CARD_REQUIRED, PAYMENT_FAILED, OUT_OF_TOKENS): 1 is a plain error. */
88
88
  export const EXIT_PAYMENT_REQUIRED = 3;
@@ -1088,12 +1088,14 @@ Admin (STAFF key — --staff-key / LETTERSTORY_STAFF_API_KEY; acts before an org
1088
1088
  domain typed into Set-up maps and reads it all afresh.
1089
1089
  admin demo domains The farm domains a new demo org could take (live
1090
1090
  phantoms; the phantom is kept). Needs demo:read.
1091
- admin demo new <domain> [--name <n>] [--brief <t>] [--full] [--no-wait]
1091
+ admin demo new <domain> [--name <n>] [--brief <t>] [--full] [--host vercel|pages] [--no-wait]
1092
1092
  A new demo org on a farm phantom's domain: a made-up
1093
1093
  company, a one-page site in the demo GitHub org served by
1094
1094
  Pages, the domain moved off its phantom (kept), the org
1095
- enrolled and shown as blank. Takes minutes; calls again
1096
- until done (--no-wait: once). Auto-Plan stays off.
1095
+ enrolled and shown as blank. Served from Vercel (a project
1096
+ linked to the repo: a merged PR redeploys it); --host pages
1097
+ for GitHub Pages, and --host vercel on an org that has a Pages
1098
+ site moves it. Calls again until done (--no-wait: once).
1097
1099
  admin demo teardown <org-id> [--keep-repo] Put the site back: the domain to its phantom, the repo gone.
1098
1100
  admin demo check <org-id> Is it ready to show? Competitors, targets, answers, the
1099
1101
  Plan's rows, the issues open, no PR open, GitHub, the
@@ -1668,6 +1670,8 @@ Insights:
1668
1670
  Measure → Reads: AI engines and people reading your sites, counted one way
1669
1671
  insights questions [--period 14d|30d|90d] Measure → Questions: every question we ask, named / share / change / cited
1670
1672
  insights pages [--period 14d|30d|90d] [--site own|phantoms|<key>] Measure → Pages: your pages AI answers cited, won questions first
1673
+ insights page <page> [--period 14d|30d|90d] [--question <prompt-id>] [--filter all|won] [--limit n]
1674
+ One cited page: the AI answers that linked it, word for word
1671
1675
  insights mentions [--site phantoms|<key>]... Measure → Pages → Naming you: which posts on your phantom sites name you
1672
1676
  insights competitors [--period 14d|30d|90d] GEO → Competitors: share of mentions per brand, is it moving, by engine/question
1673
1677
  insights question <key> [--period 14d|30d|90d] [--filter all|named_you|rival_not_you|no_one] [--limit n]
@@ -1801,8 +1805,10 @@ Repo changes (Claude Code edits your repo in a sandbox; the Letter GitHub App op
1801
1805
  repo publish <run-id> Make PR on a preview in review
1802
1806
  repo merge <run-id> Merge its open PR (a squash; the repo's branch rules decide)
1803
1807
  repo revise <run-id> --instructions <text>|--instructions-file <path|-> [--copy-edits-file <path|->]
1808
+ [--attach <path>]… [--attach-ref <path>]… [--attach-url name=url]… [--attach-url-ref name=url]…
1804
1809
  Ask for more on a preview (it takes over the preview's link) or an
1805
- open PR (one more commit); copy edits: JSON [{ path, before, after }]
1810
+ open PR (one more commit); copy edits: JSON [{ path, before, after }];
1811
+ files as repo start takes them (--attach-ref: read, never committed)
1806
1812
  repo cancel <run-id> [--force] Stop it and delete the sandbox; no PR (Discard a preview)
1807
1813
 
1808
1814
  Feedback (tell us what's broken, missing, unclear, or wanted):
@@ -6518,7 +6518,7 @@ const DEMO_ITEM_FLAGS = [
6518
6518
  "setup",
6519
6519
  ];
6520
6520
  const DEMO_USAGE =
6521
- "Usage: admin demo guide | list [--enrollable] | show <org-id> [--moves] | set <org-id> [--enroll] [--unenroll] [--file <state.json>] [--phantom parked|shown] [--main-blog …] [--visitors …] [--probes not_run|shown] [--cms hidden|shown] [--publish-target …] [--search-console …] [--github …] [--domain …] [--setup fresh|finished] [--domain-cap n] [--inject <key>]... [--hide <key>]... [--save-as <name>] [--baseline <owner/repo>[@sha] | --clear-baseline] [--copy-from <org-id> [--copy-scenarios a,b] [--overwrite]] [--auto-reset <scenario>|off] [--every <minutes>] [--release] | domains | new <domain> [--name <n>] [--brief <t>] [--full] [--no-wait] | teardown <org-id> [--keep-repo] | bootstrap <org-id>... --from <org-id> [--scenarios a,b] [--overwrite] | reset <org-id> <scenario> [--clean-slate] [--close-prs since_applied|all_open|none] [--hold <minutes>] [--restore-repo revert|force] [--rebuild] [--reread-company] [--forget-company] [--forget-site] | check <org-id>";
6521
+ "Usage: admin demo guide | list [--enrollable] | show <org-id> [--moves] | set <org-id> [--enroll] [--unenroll] [--file <state.json>] [--phantom parked|shown] [--main-blog …] [--visitors …] [--probes not_run|shown] [--cms hidden|shown] [--publish-target …] [--search-console …] [--github …] [--domain …] [--setup fresh|finished] [--domain-cap n] [--inject <key>]... [--hide <key>]... [--save-as <name>] [--baseline <owner/repo>[@sha] | --clear-baseline] [--copy-from <org-id> [--copy-scenarios a,b] [--overwrite]] [--auto-reset <scenario>|off] [--every <minutes>] [--release] | domains | new <domain> [--name <n>] [--brief <t>] [--full] [--host vercel|pages] [--no-wait] | teardown <org-id> [--keep-repo] | bootstrap <org-id>... --from <org-id> [--scenarios a,b] [--overwrite] | reset <org-id> <scenario> [--clean-slate] [--close-prs since_applied|all_open|none] [--hold <minutes>] [--restore-repo revert|force] [--rebuild] [--reread-company] [--forget-company] [--forget-site] | check <org-id>";
6522
6522
 
6523
6523
  const readinessLines = (r) =>
6524
6524
  r
@@ -6668,6 +6668,7 @@ async function cmdAdminDemo(ctx) {
6668
6668
  name: flagStr(flags.name),
6669
6669
  brief: flagStr(flags.brief),
6670
6670
  full: flagBool(flags.full) || undefined,
6671
+ host: flagStr(flags.host),
6671
6672
  });
6672
6673
  for (let attempt = 0; ; attempt++) {
6673
6674
  const r = await staffPost(ctx, `${DEMO}/orgs`, body);
@@ -32,6 +32,8 @@ const ARGS = {
32
32
  delivery: { flag: "action", enum: ["build", "build_pr", "build_merge", "slack", "linear"] },
33
33
  notify_slack: { flag: "notify-slack", type: "bool" },
34
34
  harness: { flag: "harness", enum: ["letterpress-1", "claude-code", "codex"] },
35
+ // --branch <name>: where its changes start and its pull requests go; omitted, the branch that builds the site.
36
+ base_branch: "branch",
35
37
  // --effort low|medium|high|xhigh: how hard the coding agent works on each change; omitted, its own default.
36
38
  effort: { flag: "effort", enum: ["low", "medium", "high", "xhigh"] },
37
39
  again: { flag: "again", type: "bool" },
@@ -59,6 +61,8 @@ const ARGS = {
59
61
  conversation_id: "conversation",
60
62
  build_id: "build",
61
63
  domain: "site",
64
+ // --branch <name>: where a build this message starts works from.
65
+ base_branch: "branch",
62
66
  subjects: { flag: "subject", type: "list" },
63
67
  // --recipe <id>: the recipe the words carry out (a Plan row's), recorded on the build it starts.
64
68
  recipe: "recipe",
@@ -189,6 +193,7 @@ async function buildStart(ctx) {
189
193
  "again",
190
194
  "plan-only",
191
195
  "plan-file",
196
+ "branch",
192
197
  ...FILE_FLAGS,
193
198
  ]);
194
199
  const files = await attachedFiles(ctx);
@@ -220,6 +225,7 @@ async function buildTalk(ctx) {
220
225
  "subject",
221
226
  "harness",
222
227
  "repo",
228
+ "branch",
223
229
  "action",
224
230
  "intent",
225
231
  ...FILE_FLAGS,
@@ -2,7 +2,7 @@
2
2
  // network, single-post, and top-posts levels (ported from phantomstory-cli's
3
3
  // insights.ts); plus Measure's Overview (`summary`), GEO → Competitors
4
4
  // (`competitors`), AI Visibility Bank (`bank`) and phantom Page Access (`access`);
5
- // Measure → Reads (`reads`), Questions (`questions`), Pages (`pages`) and the posts naming you (`mentions`), and the live feed of what
5
+ // Measure → Reads (`reads`), Questions (`questions`), Pages (`pages`, one page's answers `page`) and the posts naming you (`mentions`), and the live feed of what
6
6
  // is reading the phantom sites (`activity`) — the same reads the web app renders.
7
7
 
8
8
  import { CliError } from "../client.mjs";
@@ -42,6 +42,13 @@ export const ARGS = {
42
42
  period: { flag: "period", enum: PERIODS },
43
43
  sites: { flag: "site", type: "list" },
44
44
  },
45
+ get_cited_page_answers: {
46
+ page: { positional: 0, label: "page", required: true },
47
+ period: { flag: "period", enum: PERIODS },
48
+ prompt_id: { flag: "question" },
49
+ filter: { flag: "filter", enum: ["all", "won"] },
50
+ limit: { flag: "limit", type: "num" },
51
+ },
45
52
  list_site_mentions: {
46
53
  sites: { flag: "site", type: "list" },
47
54
  },
@@ -85,13 +92,15 @@ export async function cmdInsights(ctx) {
85
92
  return call(rest, "list_measure_questions");
86
93
  case "pages":
87
94
  return call(rest, "list_cited_pages");
95
+ case "page":
96
+ return call(rest, "get_cited_page_answers");
88
97
  case "mentions":
89
98
  return call(rest, "list_site_mentions");
90
99
  case "activity":
91
100
  return call(rest, "get_recent_activity");
92
101
  default:
93
102
  throw new CliError(
94
- `Unknown insights subcommand: ${sub ?? "(none)"}. Try: summary, reads, questions, pages, mentions, competitors, question, detection, activity, site, post, top, bank, access`
103
+ `Unknown insights subcommand: ${sub ?? "(none)"}. Try: summary, reads, questions, pages, page, mentions, competitors, question, detection, activity, site, post, top, bank, access`
95
104
  );
96
105
  }
97
106
  }
@@ -257,6 +257,7 @@ const ARGS = {
257
257
  run_id: { positional: 0, required: true, label: "run-id" },
258
258
  instructions: { value: (flags) => readTextInput(flags, "instructions", "instructions-file") },
259
259
  copy_edits: { value: (flags) => copyEdits(flags) },
260
+ files: { value: (_flags, _positionals, extra) => (extra.files?.length ? extra.files : undefined) },
260
261
  },
261
262
  list_repo_changes: {
262
263
  status: { flag: "status", enum: ["running", "succeeded", "no_changes", "failed", "cancelled", "sent"] },
@@ -437,6 +438,19 @@ export async function uploadAttachments(ctx, paths) {
437
438
  return uploads.map((slot) => ({ name: slot.name, upload_path: slot.path }));
438
439
  }
439
440
 
441
+ /** --attach / --attach-ref (uploaded) and --attach-url / --attach-url-ref, as a change's `files`. */
442
+ async function attachedFiles(ctx) {
443
+ const { flags } = ctx;
444
+ // One upload for every file (names must be unique across both), then the references marked.
445
+ const place = flagList(flags.attach);
446
+ const uploaded = await uploadAttachments(ctx, [...place, ...flagList(flags["attach-ref"])]);
447
+ return [
448
+ ...uploaded.map((f, i) => (i < place.length ? f : { ...f, reference: true })),
449
+ ...attachmentUrls(flagList(flags["attach-url"])),
450
+ ...attachmentUrls(flagList(flags["attach-url-ref"])).map((f) => ({ ...f, reference: true })),
451
+ ];
452
+ }
453
+
440
454
  async function repoStart(ctx) {
441
455
  const { client, flags, io } = ctx;
442
456
  refuseUnknownFlags(ctx, {}, [
@@ -460,15 +474,7 @@ async function repoStart(ctx) {
460
474
  "page",
461
475
  "wait",
462
476
  ]);
463
- // One upload for every file (names must be unique across both), then the references marked.
464
- const place = flagList(flags.attach);
465
- const uploaded = await uploadAttachments(ctx, [...place, ...flagList(flags["attach-ref"])]);
466
- const files = [
467
- ...uploaded.map((f, i) => (i < place.length ? f : { ...f, reference: true })),
468
- ...attachmentUrls(flagList(flags["attach-url"])),
469
- ...attachmentUrls(flagList(flags["attach-url-ref"])).map((f) => ({ ...f, reference: true })),
470
- ];
471
- const args = argsFromFlags(ARGS.start_repo_change, flags, ctx.positionals, { files });
477
+ const args = argsFromFlags(ARGS.start_repo_change, flags, ctx.positionals, { files: await attachedFiles(ctx) });
472
478
  if (!args.repo_owner || !args.repo_name) throw new CliError("Missing --repo owner/name");
473
479
  if (!args.instructions) throw new CliError("Missing --instructions (or --instructions-file <path|->)");
474
480
 
@@ -795,10 +801,13 @@ function copyEdits(flags) {
795
801
  return list;
796
802
  }
797
803
 
798
- /** `repo revise <run-id> --instructions <text> [--copy-edits-file <path|->]`: a follow-up, built on top of the change. */
804
+ /**
805
+ * `repo revise <run-id> --instructions <text> [--copy-edits-file <path|->] [--attach-ref <path>]…`: a follow-up,
806
+ * built on top of the change, with any files it needs (a screenshot to read, an image to place) as `repo start` takes them.
807
+ */
799
808
  async function repoRevise(ctx) {
800
809
  const { client, flags, io } = ctx;
801
- const args = argsFromFlags(ARGS.revise_repo_change, flags, ctx.positionals);
810
+ const args = argsFromFlags(ARGS.revise_repo_change, flags, ctx.positionals, { files: await attachedFiles(ctx) });
802
811
  if (!args.instructions && !args.copy_edits) {
803
812
  throw new CliError("Missing --instructions (or --instructions-file <path|->), or --copy-edits-file <path|->");
804
813
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letterstory/cli",
3
- "version": "0.176.24",
3
+ "version": "0.176.26",
4
4
  "description": "Spin up and manage Letter phantom blogs from your terminal.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",