@letterstory/cli 0.129.2 → 0.131.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
@@ -118,6 +118,8 @@ letterstory phantom config <phantom-id> --theme commons # patch the sit
118
118
  letterstory phantom config <phantom-id> --collection-config '{"cadence":{"posts_per_week":4}}'
119
119
  letterstory phantom config <phantom-id> --configure --dry-run # what re-configuring would decide, applying nothing
120
120
  letterstory phantom rebuild <phantom-id> # after publishing outside a collection run
121
+ letterstory phantom pause <phantom-id> # stop new posts and probing; the site stays live (resume to undo)
122
+ letterstory phantom resume <phantom-id>
121
123
  letterstory phantom diagnostics <phantom-id> # why is my site empty?
122
124
  letterstory phantom balance # sites live / granted, and the cadence cap
123
125
  letterstory phantom spread-looks [--apply] # a different look per site; dry run unless --apply
package/lib/cli.mjs CHANGED
@@ -62,7 +62,7 @@ import {
62
62
  } from "./commands.mjs";
63
63
 
64
64
  // Keep in sync with cli/package.json.
65
- export const VERSION = "0.129.2";
65
+ export const VERSION = "0.131.0";
66
66
 
67
67
  // Flags that never take a value. Listing them explicitly means `deploy get --json <id>`
68
68
  // can't accidentally swallow the id as --json's value. Every `type: "bool"` flag (and its
@@ -447,6 +447,7 @@ Reviewers:
447
447
  reviewers assigned Posts waiting on your review, across letterbases
448
448
  automations list --collection <id> What a letterbase writes, how often, and why
449
449
  automations show <automation-id> --collection <id> What it covers, its queued posts, its next ideas
450
+ automations ideas --collection <id> The ideas waiting, grouped by the automation that owns each
450
451
  automations suggest --collection <id> What it could add: topics, series formats, signals
451
452
  automations preview topic|series --collection <id> [--name <name>|--topic <id>] [--brief <text>]
452
453
  What it would write, before adding it
@@ -1305,6 +1306,9 @@ Mail:
1305
1306
 
1306
1307
  Insights:
1307
1308
  insights summary [--period …] [--part <block>]... Measure Overview: AI named/cited rates, share of voice, search, targets
1309
+ insights reads [--period …] [--site all|own|phantoms|<key>]... [--path <path>] [--engine <operator>]
1310
+ Measure → Reads: AI engines and people reading your sites, counted one way
1311
+ insights questions [--period 14d|30d|90d] Measure → Questions: every question we ask, named / share / change / cited
1308
1312
  insights competitors [--period 14d|30d|90d] GEO → Competitors: share of mentions per brand, is it moving, by engine/question
1309
1313
  insights question <key> [--period 14d|30d|90d] [--filter all|named_you|rival_not_you|no_one] [--limit n]
1310
1314
  One By-question row: the wordings asked and the AI answers
@@ -1316,6 +1320,7 @@ Insights:
1316
1320
  AI Visibility Bank: named / cited / breakthrough evidence (all time by default)
1317
1321
  insights access [--period 14d|30d|90d] [--collection <uuid>]
1318
1322
  Page access: who's reading your phantom sites
1323
+ insights activity [--phantom <uuid>] [--limit <n>] What hit your phantom sites in the last 7 days, newest first
1319
1324
 
1320
1325
  The Site (one model of your whole marketing site, keyed by host and path):
1321
1326
  site list Mapped hosts, and hosts you already have elsewhere
@@ -1350,10 +1355,11 @@ Repo changes (Claude Code edits your repo in a sandbox; the Letterstory GitHub A
1350
1355
  repo suggestions Fixes found and not acted on yet, and how to start each
1351
1356
  repo settings [--action build|build_pr|build_merge|slack|linear] [--branch <name>|none] [--slack-agent <name|U…>|none]
1352
1357
  [--slack-channel <C…>|none] [--slack-reports off|as_found|daily] [--reports-channel <C…>|none] [--autopilot-channel <C…>|none] [--listen <C…,C…>|none] [--autopilot on|off] [--autopilot-weekly <1-20>]
1353
- [--autopilot-severity critical|warning]
1358
+ [--autopilot-severity critical|warning] [--auto-merge 1h|12h|24h|3d|off]
1354
1359
  The org's defaults for every fix: build (preview first),
1355
1360
  build_pr, or slack (post it to --slack-channel for your
1356
- own agent, tagging --slack-agent), and the branch changes go to
1361
+ own agent, tagging --slack-agent), and the branch changes go to;
1362
+ --auto-merge merges Letter's PRs once open that long
1357
1363
  repo slack-targets Slack channels and people Send to Slack can use, with ids
1358
1364
  repo slack-test Post a test into the Send to Slack channel
1359
1365
  repo start --repo <owner/name> --instructions <text>|--instructions-file <path|->
@@ -1,5 +1,5 @@
1
1
  // `automations` — a letterbase's automations (docs/architecture/automations.md): what it writes,
2
- // how often, and why. list / show / suggest / preview / more / add / set / dismiss / remove, through the same tools the app uses.
2
+ // how often, and why. list / show / ideas / suggest / preview / more / add / set / dismiss / remove, through the same tools the app uses.
3
3
  // Same flag/positional/io conventions as posts.mjs — no commander, no color.
4
4
 
5
5
  import { CliError } from "../client.mjs";
@@ -10,6 +10,9 @@ export const ARGS = {
10
10
  list_automations: {
11
11
  collection_id: { flag: "collection", required: true },
12
12
  },
13
+ list_automation_ideas: {
14
+ collection_id: { flag: "collection", required: true },
15
+ },
13
16
  suggest_automations: {
14
17
  collection_id: { flag: "collection", required: true },
15
18
  },
@@ -74,6 +77,8 @@ export async function cmdAutomations(ctx) {
74
77
  return run(rest, "list_automations");
75
78
  case "show":
76
79
  return run(rest, "get_automation");
80
+ case "ideas":
81
+ return run(rest, "list_automation_ideas");
77
82
  case "suggest":
78
83
  return run(rest, "suggest_automations");
79
84
  case "preview":
@@ -92,7 +97,7 @@ export async function cmdAutomations(ctx) {
92
97
  return run(rest, "delete_automation", "Removed.");
93
98
  default:
94
99
  throw new CliError(
95
- `Unknown automations subcommand: ${sub ?? "(none)"}. Try: list, show, suggest, preview, more, add, set, dismiss, remove`
100
+ `Unknown automations subcommand: ${sub ?? "(none)"}. Try: list, show, ideas, suggest, preview, more, add, set, dismiss, remove`
96
101
  );
97
102
  }
98
103
  }
@@ -1,8 +1,9 @@
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
3
  // insights.ts); plus Measure's Overview (`summary`), GEO → Competitors
4
- // (`competitors`), AI Visibility Bank (`bank`) and phantom Page Access (`access`)
5
- // — the same reads the web app's Measure tabs render.
4
+ // (`competitors`), AI Visibility Bank (`bank`) and phantom Page Access (`access`);
5
+ // Measure → Reads (`reads`) and Questions (`questions`), and the live feed of what
6
+ // is reading the phantom sites (`activity`) — the same reads the web app renders.
6
7
 
7
8
  import { CliError } from "../client.mjs";
8
9
  import { flagStr, flagNum, requirePositional, printResult, compact, argsFromFlags } from "./shared.mjs";
@@ -28,6 +29,19 @@ export const ARGS = {
28
29
  period: { flag: "period", enum: PERIODS },
29
30
  },
30
31
  get_brand_detection: {},
32
+ get_reads: {
33
+ period: { flag: "period", enum: PERIODS },
34
+ sites: { flag: "site", type: "list" },
35
+ path: { flag: "path" },
36
+ engine: { flag: "engine" },
37
+ },
38
+ list_measure_questions: {
39
+ period: { flag: "period", enum: PERIODS },
40
+ },
41
+ get_recent_activity: {
42
+ phantom_id: { flag: "phantom" },
43
+ limit: { flag: "limit", type: "num" },
44
+ },
31
45
  get_share_of_voice_question: {
32
46
  question_key: { positional: 0, label: "question-key", required: true },
33
47
  period: { flag: "period", enum: PERIODS },
@@ -58,9 +72,15 @@ export async function cmdInsights(ctx) {
58
72
  return call(rest, "get_share_of_voice_question");
59
73
  case "detection":
60
74
  return call(rest, "get_brand_detection");
75
+ case "reads":
76
+ return call(rest, "get_reads");
77
+ case "questions":
78
+ return call(rest, "list_measure_questions");
79
+ case "activity":
80
+ return call(rest, "get_recent_activity");
61
81
  default:
62
82
  throw new CliError(
63
- `Unknown insights subcommand: ${sub ?? "(none)"}. Try: summary, competitors, question, detection, site, post, top, bank, access`
83
+ `Unknown insights subcommand: ${sub ?? "(none)"}. Try: summary, reads, questions, competitors, question, detection, activity, site, post, top, bank, access`
64
84
  );
65
85
  }
66
86
  }
@@ -111,6 +111,11 @@ export const ARGS = {
111
111
  },
112
112
  delete_phantom: { phantom_id: { positional: 0, label: "phantom-id", required: true } },
113
113
  rebuild_phantom: { phantom_id: { positional: 0, label: "phantom-id", required: true } },
114
+ // `phantom pause <id>` / `phantom resume <id>` — the verb sets `paused`.
115
+ set_phantom_paused: {
116
+ phantom_id: { positional: 0, label: "phantom-id", required: true },
117
+ paused: { value: (flags, positionals, extra) => extra.paused },
118
+ },
114
119
  get_phantom_diagnostics: { phantom_id: { positional: 0, label: "phantom-id", required: true } },
115
120
  get_phantom_balance: {},
116
121
  spread_phantom_looks: { apply: { flag: "apply", type: "bool" } },
@@ -154,6 +159,9 @@ export async function cmdPhantom(ctx) {
154
159
  return call(rest, "delete_phantom");
155
160
  case "rebuild":
156
161
  return call(rest, "rebuild_phantom");
162
+ case "pause":
163
+ case "resume":
164
+ return phantomPause(rest, sub === "pause");
157
165
  case "diagnostics":
158
166
  case "diag":
159
167
  return call(rest, "get_phantom_diagnostics");
@@ -165,11 +173,17 @@ export async function cmdPhantom(ctx) {
165
173
  return phantomDomain(rest);
166
174
  default:
167
175
  throw new CliError(
168
- `Unknown phantom subcommand: ${sub ?? "(none)"}. Try: list, get, new, config, visitors, delete, rebuild, diagnostics, balance, spread-looks, domain`
176
+ `Unknown phantom subcommand: ${sub ?? "(none)"}. Try: list, get, new, config, visitors, delete, rebuild, pause, resume, diagnostics, balance, spread-looks, domain`
169
177
  );
170
178
  }
171
179
  }
172
180
 
181
+ async function phantomPause(ctx, paused) {
182
+ const args = argsFromFlags(ARGS.set_phantom_paused, ctx.flags, ctx.positionals, { paused });
183
+ printResult(ctx.io, ctx.flags, await ctx.client.callTool("set_phantom_paused", args));
184
+ return 0;
185
+ }
186
+
173
187
  async function call(ctx, tool) {
174
188
  const result = await ctx.client.callTool(tool, argsFromFlags(ARGS[tool], ctx.flags, ctx.positionals));
175
189
  printResult(ctx.io, ctx.flags, result);
@@ -165,6 +165,8 @@ const ARGS = {
165
165
  value: (flags) => (flags["autopilot-weekly"] === undefined ? undefined : Number(flags["autopilot-weekly"])),
166
166
  },
167
167
  autopilot_min_severity: { flag: "autopilot-severity", enum: ["critical", "warning"] },
168
+ // --auto-merge 1h|12h|24h|3d (or the hours: 1, 12, 24, 72), or --auto-merge off
169
+ auto_merge_after_hours: { value: (flags) => autoMergeHours(flags["auto-merge"]) },
168
170
  },
169
171
  get_repo_change: { run_id: { positional: 0, required: true, label: "run-id" } },
170
172
  get_repo_change_activity: { run_id: { positional: 0, required: true, label: "run-id" } },
@@ -195,6 +197,16 @@ const ARGS = {
195
197
  },
196
198
  };
197
199
 
200
+ /** `--auto-merge`: off → null, 1h / 12h / 24h / 3d or plain hours → the hours; anything else is passed on for the tool to refuse. */
201
+ function autoMergeHours(value) {
202
+ if (value === undefined) return undefined;
203
+ const text = String(value).trim().toLowerCase();
204
+ if (["off", "none", "false", "0"].includes(text)) return null;
205
+ const match = /^(\d+)\s*(h|hours?|d|days?)?$/.exec(text);
206
+ if (!match) return text;
207
+ return Number(match[1]) * (match[2]?.startsWith("d") ? 24 : 1);
208
+ }
209
+
198
210
  const TERMINAL = new Set(["succeeded", "no_changes", "failed", "cancelled"]);
199
211
 
200
212
  function describe(run) {
@@ -634,6 +646,7 @@ async function repoSettings(ctx) {
634
646
  "autopilot",
635
647
  "autopilot-weekly",
636
648
  "autopilot-severity",
649
+ "auto-merge",
637
650
  ];
638
651
  refuseUnknownFlags(ctx, {}, settingFlags);
639
652
  const setting = settingFlags.some((f) => flags[f] !== undefined);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letterstory/cli",
3
- "version": "0.129.2",
3
+ "version": "0.131.0",
4
4
  "description": "Spin up and manage Letterstory phantom blogs from your terminal.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",