@letterstory/cli 0.107.0 → 0.108.1

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
@@ -60,7 +60,7 @@ import {
60
60
  } from "./commands.mjs";
61
61
 
62
62
  // Keep in sync with cli/package.json.
63
- export const VERSION = "0.107.0";
63
+ export const VERSION = "0.108.1";
64
64
 
65
65
  // Flags that never take a value. Listing them explicitly means `deploy get --json <id>`
66
66
  // can't accidentally swallow the id as --json's value. Every `type: "bool"` flag (and its
@@ -1160,7 +1160,8 @@ The Site (one model of your whole marketing site, keyed by host and path):
1160
1160
  site refresh <host> [--force-map] [--force-audit] [--skip-observe]
1161
1161
  Run the daily loop now: re-map, re-audit, signals, Jev
1162
1162
  site unmap <host> Remove a host and everything hanging off it
1163
- site tree <host> [path] [--depth <n>] [--unfold] The tree one folder at a time, one item per node
1163
+ site tree <host> [path] [--depth <n>] [--unfold] [--since <iso>]
1164
+ The tree one folder at a time; --since adds +N/-N per folder
1164
1165
  site page <host> <path> One page: facts, bindings, raw URLs, open items
1165
1166
  site observations <host> [path] [--state open|dismissed|resolved] [--tone risk|warn|info] [--key <k>] [--since <iso>] [--min-reach <0..1>]
1166
1167
  site observe <host> [path] [--max-pages <n>] Jev pass: meta accuracy, title, outdated
@@ -1172,6 +1173,8 @@ The Site (one model of your whole marketing site, keyed by host and path):
1172
1173
  site propose <host> [path] --tool <tool> --input '<json>'|--input-file <p> --label <text> [--why <text>]
1173
1174
  [--observation <id>]... Propose a registry tool call; input is validated now
1174
1175
  site decide <proposal-id> --decision accept|dismiss [--reason <why>]
1176
+ site competitors [--since <iso>] [--limit <n>] Tracked competitors' sites: new and removed pages, by folder
1177
+ site watch-competitors [domain] Map competitors' sitemaps (every unmapped one, or one now)
1175
1178
 
1176
1179
  Repo changes (Claude Code edits your repo in a sandbox; the Letterstory GitHub App opens a PR):
1177
1180
  repo repos Repos the Letterstory GitHub App can reach
@@ -1198,7 +1201,7 @@ Repo changes (Claude Code edits your repo in a sandbox; the Letterstory GitHub A
1198
1201
  One PR adding Letterprove's script and identify calls
1199
1202
  (pick the repo where users sign in)
1200
1203
  repo sites [--detect [--force]] Which website each repo builds, and the evidence
1201
- repo set-site --repo <owner/name> --site <host> [--not]
1204
+ repo set-site --repo <owner/name> --site <host> [--not] [--branch <name>|none]
1202
1205
  Say which site a repo builds (--not: correct a wrong label)
1203
1206
  repo reset-site --repo <owner/name> --site <host> Back to automatic detection
1204
1207
  repo status <run-id> Phase, spend, PR link and Claude's summary
@@ -60,6 +60,8 @@ const ARGS = {
60
60
  repo_name: { value: (flags) => splitRepo(flags).name },
61
61
  site_host: { flag: "site", required: true },
62
62
  builds: { flag: "not", type: "bool", invert: true },
63
+ // --branch <name>: the branch that deploys the site (one repo, a site per branch); none = default.
64
+ branch: { value: (flags) => (flags.branch === "none" ? null : flagStr(flags.branch)) },
63
65
  },
64
66
  reset_repo_site: {
65
67
  repo_owner: { value: (flags) => splitRepo(flags).owner },
@@ -345,7 +347,7 @@ async function repoSites(ctx) {
345
347
  /** `repo set-site --repo owner/name --site acme.com [--not]`: say which site a repo builds (or doesn't). */
346
348
  async function repoSetSite(ctx) {
347
349
  const { client, flags, io } = ctx;
348
- refuseUnknownFlags(ctx, {}, ["repo", "owner", "name", "site", "not"]);
350
+ refuseUnknownFlags(ctx, {}, ["repo", "owner", "name", "site", "not", "branch"]);
349
351
  const result = await client.callTool("set_repo_site", argsFromFlags(ARGS.set_repo_site, flags));
350
352
  ok(ctx, result.message);
351
353
  printResult(io, flags, result.site);
@@ -7,6 +7,7 @@
7
7
  // site families <host> → list_site_families site decide-family <id> → decide_site_family
8
8
  // site proposals <host> → list_site_proposals site propose <host> → create_site_proposal
9
9
  // site decide <proposal> → decide_site_proposal site fix <host> <id>… → fix_site_observations
10
+ // site competitors → list_competitor_sites site watch-competitors [d] → watch_competitor_sites
10
11
 
11
12
  import { CliError } from "../client.mjs";
12
13
  import { argsFromFlags, flagList, flagStr, ok, printResult, readTextInput, refuseUnknownFlags } from "./shared.mjs";
@@ -56,7 +57,13 @@ const ARGS = {
56
57
  depth: { flag: "depth", type: "num" },
57
58
  unfold: { flag: "unfold", type: "bool" },
58
59
  subtree: { flag: "subtree", type: "bool" },
60
+ since: { flag: "since" },
59
61
  },
62
+ list_competitor_sites: {
63
+ since: { flag: "since" },
64
+ limit: { flag: "limit", type: "num" },
65
+ },
66
+ watch_competitor_sites: { domain: { positional: 0, label: "domain" } },
60
67
  get_site_page: {
61
68
  host: { positional: 0, required: true, label: "host" },
62
69
  path: { positional: 1, required: true, label: "path" },
@@ -166,9 +173,13 @@ export async function cmdSite(ctx) {
166
173
  return siteDecide(rest);
167
174
  case "fix":
168
175
  return siteFix(rest);
176
+ case "competitors":
177
+ return siteCompetitors(rest);
178
+ case "watch-competitors":
179
+ return siteWatchCompetitors(rest);
169
180
  default:
170
181
  throw new CliError(
171
- `Unknown site subcommand: ${sub ?? "(none)"}. Try: list, map, refresh, unmap, tree, page, observations, observe, dismiss, families, decide-family, proposals, propose, decide, fix`
182
+ `Unknown site subcommand: ${sub ?? "(none)"}. Try: list, map, refresh, unmap, tree, page, observations, observe, dismiss, families, decide-family, proposals, propose, decide, fix, competitors, watch-competitors`
172
183
  );
173
184
  }
174
185
  }
@@ -180,7 +191,8 @@ function describeNode(node, indent = "") {
180
191
  const line = node.item
181
192
  ? `${node.item.summary}${node.more ? ` and ${node.more} more` : ""}${node.item.action ? ` → ${node.item.action.label}` : ""}`
182
193
  : "";
183
- const counts = node.hasChildren ? ` (${node.rollup.pages} pages)` : "";
194
+ const changed = `${node.rollup.added ? ` +${node.rollup.added}` : ""}${node.rollup.removed ? ` -${node.rollup.removed}` : ""}`;
195
+ const counts = node.hasChildren ? ` (${node.rollup.pages} pages${changed})` : node.page?.added ? " (new)" : "";
184
196
  const out = [`${indent}${glyph.padEnd(2)} ${node.name}${counts}${line ? ` ${line}` : ""}`];
185
197
  for (const child of node.children ?? []) out.push(describeNode(child, `${indent} `));
186
198
  return out.join("\n");
@@ -203,6 +215,43 @@ async function siteList(ctx) {
203
215
  return 0;
204
216
  }
205
217
 
218
+ async function siteCompetitors(ctx) {
219
+ const { client, flags, io } = ctx;
220
+ refuseUnknownFlags(ctx, ARGS.list_competitor_sites);
221
+ const result = await client.callTool(
222
+ "list_competitor_sites",
223
+ argsFromFlags(ARGS.list_competitor_sites, flags, ctx.positionals)
224
+ );
225
+ printResult(io, flags, result, (r) => {
226
+ const lines = [`Since ${r.since.slice(0, 10)}:`];
227
+ for (const c of r.competitors) {
228
+ if (!c.changes) {
229
+ lines.push(` ${c.name} ${c.domain} not mapped (letterstory site watch-competitors ${c.domain})`);
230
+ continue;
231
+ }
232
+ const folders = c.changes.folders.map((f) => `${f.path} +${f.added}${f.removed ? ` -${f.removed}` : ""}`);
233
+ lines.push(
234
+ ` ${c.name} ${c.host} ${c.changes.pages} pages +${c.changes.added} -${c.changes.removed}${folders.length ? ` ${folders.join(", ")}` : ""}`
235
+ );
236
+ for (const p of c.changes.addedPages.slice(0, 5)) lines.push(` + ${p.path} ${p.at.slice(0, 10)}`);
237
+ }
238
+ return r.competitors.length ? lines.join("\n") : "No competitors tracked yet.";
239
+ });
240
+ return 0;
241
+ }
242
+
243
+ async function siteWatchCompetitors(ctx) {
244
+ const { client, flags, io } = ctx;
245
+ refuseUnknownFlags(ctx, ARGS.watch_competitor_sites);
246
+ const result = await client.callTool(
247
+ "watch_competitor_sites",
248
+ argsFromFlags(ARGS.watch_competitor_sites, flags, ctx.positionals)
249
+ );
250
+ ok(ctx, result.message);
251
+ printResult(io, flags, result, () => "");
252
+ return 0;
253
+ }
254
+
206
255
  async function siteMap(ctx) {
207
256
  const { client, flags, io } = ctx;
208
257
  refuseUnknownFlags(ctx, ARGS.map_site);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letterstory/cli",
3
- "version": "0.107.0",
3
+ "version": "0.108.1",
4
4
  "description": "Spin up and manage Letterstory phantom blogs from your terminal.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",