@letterstory/cli 0.176.36 → 0.176.38

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
@@ -83,7 +83,7 @@ import {
83
83
  import { fetchParkedSections, withoutParked } from "./parked.mjs";
84
84
 
85
85
  // Matches cli/package.json; publish-cli.yml rewrites both to the published version.
86
- export const VERSION = "0.176.36";
86
+ export const VERSION = "0.176.38";
87
87
 
88
88
  /** The exit code for a gate refusal (CARD_REQUIRED, PAYMENT_FAILED, OUT_OF_TOKENS): 1 is a plain error. */
89
89
  export const EXIT_PAYMENT_REQUIRED = 3;
@@ -121,8 +121,10 @@ export const BOOLEAN_FLAGS = new Set([
121
121
  "fresh",
122
122
  // admin letterpress telemetry | prs: every org, test and demo orgs included.
123
123
  "all-orgs",
124
- // admin letterpress prs: since the first PR (all_time).
124
+ // admin letterpress prs | failures: since the first PR or build (all_time).
125
125
  "all-time",
126
+ // admin letterpress failures: the builds that took far longer than usual.
127
+ "slow",
126
128
  // admin letterpress status | gaps: the customers' reading (no test or demo orgs).
127
129
  "customers",
128
130
  // build: repeat an open build's ask; post its progress in the org's Slack channel.
@@ -1691,6 +1693,9 @@ Insights:
1691
1693
  insights question <key> [--period 14d|30d|90d] [--filter all|named_you|rival_not_you|no_one] [--limit n]
1692
1694
  One By-question row: the wordings asked and the AI answers
1693
1695
  insights detection How your brand is detected in AI answers (terms, who set them)
1696
+ insights properties Search Console properties the connected Google account can read, the
1697
+ unverified ones, and the one tracked
1698
+ insights property <site-url> Pick the Search Console property to track (a key minted by an admin)
1694
1699
  insights site [--period 14d|30d|90d] [--collection <uuid>]
1695
1700
  insights post <article-id> [--period 14d|30d|90d]
1696
1701
  insights top [--period 14d|30d|90d] [--collection <uuid>] [--limit <n>] [--sort clicks|impressions]
@@ -5241,8 +5241,9 @@ const ENGINE = "/api/superadmin/engine-settings";
5241
5241
  const LETTERPRESS = "/api/superadmin/letterpress";
5242
5242
  const CODING_TELEMETRY = "/api/superadmin/coding-telemetry";
5243
5243
  const PULL_REQUESTS = "/api/superadmin/pull-requests";
5244
+ const BUILD_FAILURES = "/api/superadmin/build-failures";
5244
5245
  const ENGINE_USAGE =
5245
- "Usage: admin letterpress status | gaps | telemetry [--days N] [--harness h] [--all-orgs] | prs [--days N | --from YYYY-MM-DD [--to YYYY-MM-DD] | --all-time] [--day YYYY-MM-DD] [--org id] [--state merged|open|closed] [--top N] [--tz zone] [--all-orgs] | blocked [--days N] [--org id] [--all-orgs] | unblock <org-id> <host> <subject> | decisions <org-id> [--site host] [--surface plan|fix|build|site] | undo <org-id> <host> <decision-id> | runs [--status s] [--error-code c] [--first-review-cause agent|context|setup|unknown] [--pr merged|closed|open|none] [--harness h] [--days N] | beyond [--limit N] [--before ISO] | companies | company <org-id> [--site host] | facts <org-id> [--site host] | fits [--recipe id] [--org id] [--site host] [--fits yes|no|unknown|waiting] [--limit N] | replay <run-id> | mark <run-id> <item-id> <right|weak|wrong|lead|gap|recipe|clear> [--note …] | settings | set <weights.<ask>.<reading>|lenses.<lens>.bar.<reading>|lenses.<lens>.bar|lenses.<lens>.research.turns|lenses.<lens>.research.ms|asks.<lens>.ask|asks.<lens>.guidance|dismissals.block_days|preflight.off|explore.ask|explore.research.turns|explore.research.ms|explore.every_days> <value>";
5246
+ "Usage: admin letterpress status | gaps | telemetry [--days N] [--harness h] [--all-orgs] | prs [--days N | --from YYYY-MM-DD [--to YYYY-MM-DD] | --all-time] [--day YYYY-MM-DD] [--org id] [--state merged|open|closed] [--top N] [--tz zone] [--all-orgs] | failures [--days N | --from YYYY-MM-DD [--to YYYY-MM-DD] | --all-time] [--day YYYY-MM-DD] [--org id] [--failure ours|agent|setup|undelivered] [--cause c] [--stopped-at step] [--repo owner/name] [--slow] [--tz zone] [--all-orgs] | blocked [--days N] [--org id] [--all-orgs] | unblock <org-id> <host> <subject> | decisions <org-id> [--site host] [--surface plan|fix|build|site] | undo <org-id> <host> <decision-id> | runs [--status s] [--error-code c] [--first-review-cause agent|context|setup|unknown] [--pr merged|closed|open|none] [--harness h] [--days N] | beyond [--limit N] [--before ISO] | companies | company <org-id> [--site host] | facts <org-id> [--site host] | fits [--recipe id] [--org id] [--site host] [--fits yes|no|unknown|waiting] [--limit N] | replay <run-id> | mark <run-id> <item-id> <right|weak|wrong|lead|gap|recipe|clear> [--note …] | settings | set <weights.<ask>.<reading>|lenses.<lens>.bar.<reading>|lenses.<lens>.bar|lenses.<lens>.research.turns|lenses.<lens>.research.ms|asks.<lens>.ask|asks.<lens>.guidance|dismissals.block_days|preflight.off|explore.ask|explore.research.turns|explore.research.ms|explore.every_days> <value>";
5246
5247
 
5247
5248
  /** One number of the settings from a dotted path and a value: the PUT body save_engine_settings reads. */
5248
5249
  function engineSettingsPatch(path, value) {
@@ -5527,6 +5528,73 @@ async function cmdAdminLetterpress(ctx) {
5527
5528
  );
5528
5529
  return 0;
5529
5530
  }
5531
+ case "failures": {
5532
+ // get_build_failures (the days, causes, steps, odd cases, slow builds) or, with a filter,
5533
+ // list_build_failures (the builds behind them).
5534
+ const filters = [
5535
+ ["day", "day"],
5536
+ ["org", "org_id"],
5537
+ ["failure", "failure"],
5538
+ ["cause", "cause"],
5539
+ ["stopped-at", "stopped_at"],
5540
+ ["repo", "repo"],
5541
+ ];
5542
+ const slow = flagBool(flags.slow);
5543
+ const listing = slow || filters.some(([flag]) => flags[flag]);
5544
+ const q = new URLSearchParams({
5545
+ ...(listing ? { view: "list" } : {}),
5546
+ days: String(flags.days ?? 30),
5547
+ });
5548
+ for (const [flag, key] of [...filters, ["from", "from"], ["to", "to"], ["tz", "tz"], ["limit", "limit"]])
5549
+ if (flags[flag]) q.set(key, String(flags[flag]));
5550
+ if (slow) q.set("slow", "1");
5551
+ if (flagBool(flags["all-orgs"])) q.set("customers", "0");
5552
+ if (flagBool(flags["all-time"])) q.set("all_time", "1");
5553
+ const r = await staffRequest(ctx, "GET", `${BUILD_FAILURES}?${q}`);
5554
+ const pct = (v) => (v == null ? "—" : `${Math.round(100 * v)}%`);
5555
+ const mins = (ms) =>
5556
+ ms == null ? "—" : ms >= 3_600_000 ? `${(ms / 3_600_000).toFixed(1)}h` : `${Math.round(ms / 60_000)}m`;
5557
+ printResult(io, flags, r, (x) =>
5558
+ listing
5559
+ ? x.builds.length
5560
+ ? x.builds
5561
+ .map(
5562
+ (b) =>
5563
+ `${b.day} ${(b.failure ?? "delivered").padEnd(11)} ${(b.cause_label ?? "").padEnd(28)} ${b.door_label.padEnd(9)} ${(b.org_name ?? b.org_id).padEnd(20)} ${mins(b.work_ms)}${b.typical_ms ? ` (usually ${mins(b.typical_ms)})` : ""} ${b.line ?? ""} ${b.run_id}`
5564
+ )
5565
+ .join("\n")
5566
+ : "No builds match."
5567
+ : [
5568
+ `Builds failed, ${x.since_day} to ${x.until_day} (${x.days} days, ${x.tz})${x.customer_only ? ", customers only" : ""}: ${x.totals.failed} of ${x.totals.builds} (${pct(x.totals.rate)}; ${x.previous.failed} of ${x.previous.builds} the period before); ours ${x.totals.ours}, the agent's ${x.totals.agent}, customer's setup ${x.totals.setup}, didn't deliver ${x.totals.undelivered}`,
5569
+ ...x.series
5570
+ .filter((d) => d.builds)
5571
+ .map(
5572
+ (d) =>
5573
+ `${d.day} ${String(d.builds).padStart(3)} builds ${String(d.ours + d.agent + d.setup + d.undelivered).padStart(3)} failed (ours ${d.ours}, agent ${d.agent}, setup ${d.setup}, undelivered ${d.undelivered})`
5574
+ ),
5575
+ "why:",
5576
+ ...x.causes.map(
5577
+ (c) =>
5578
+ ` ${String(c.count).padStart(3)} ${c.label} [${c.cause}, ${c.kind}] ${c.previous} before ${c.lines[0]?.line ?? ""}`
5579
+ ),
5580
+ `where they stop: ${
5581
+ x.steps
5582
+ .filter((s) => s.stopped)
5583
+ .map((s) => `${s.label} ${s.stopped}`)
5584
+ .join(", ") || "—"
5585
+ }; delivered ${x.delivered}`,
5586
+ `odd cases: rare ${x.odd.rare.length}, new ${x.odd.new.length}, one customer ${x.odd.one_customer.length}, keeps failing ${x.odd.keeps_failing.length}`,
5587
+ `took longer than usual: ${x.slow.slow_count}`,
5588
+ ...x.slow.runs
5589
+ .slice(0, 10)
5590
+ .map(
5591
+ (b) =>
5592
+ ` ${mins(b.work_ms)} (usually ${mins(b.typical_ms)}, ${b.ratio?.toFixed(1)}×) ${b.door_label} ${b.org_name ?? b.org_id} ${b.run_id}`
5593
+ ),
5594
+ ].join("\n")
5595
+ );
5596
+ return 0;
5597
+ }
5530
5598
  case "runs": {
5531
5599
  // list_coding_runs with fleet.
5532
5600
  const q = new URLSearchParams({
@@ -3,7 +3,8 @@
3
3
  // insights.ts); plus Measure's Overview (`summary`), GEO → Competitors
4
4
  // (`competitors`), AI Visibility Bank (`bank`) and phantom Page Access (`access`);
5
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
- // is reading the phantom sites (`activity`) — the same reads the web app renders.
6
+ // is reading the phantom sites (`activity`) — the same reads the web app renders. Search Console's
7
+ // property: which ones the connected Google account can read (`properties`) and the one tracked (`property`).
7
8
 
8
9
  import { CliError } from "../client.mjs";
9
10
  import { flagStr, flagNum, requirePositional, printResult, compact, argsFromFlags } from "./shared.mjs";
@@ -56,6 +57,10 @@ export const ARGS = {
56
57
  phantom_id: { flag: "phantom" },
57
58
  limit: { flag: "limit", type: "num" },
58
59
  },
60
+ list_gsc_properties: {},
61
+ select_gsc_property: {
62
+ site_url: { positional: 0, label: "site-url", required: true },
63
+ },
59
64
  get_share_of_voice_question: {
60
65
  question_key: { positional: 0, label: "question-key", required: true },
61
66
  period: { flag: "period", enum: PERIODS },
@@ -98,9 +103,13 @@ export async function cmdInsights(ctx) {
98
103
  return call(rest, "list_site_mentions");
99
104
  case "activity":
100
105
  return call(rest, "get_recent_activity");
106
+ case "properties":
107
+ return call(rest, "list_gsc_properties");
108
+ case "property":
109
+ return call(rest, "select_gsc_property");
101
110
  default:
102
111
  throw new CliError(
103
- `Unknown insights subcommand: ${sub ?? "(none)"}. Try: summary, reads, questions, pages, page, mentions, competitors, question, detection, activity, site, post, top, bank, access`
112
+ `Unknown insights subcommand: ${sub ?? "(none)"}. Try: summary, reads, questions, pages, page, mentions, competitors, question, detection, activity, site, post, top, bank, access, properties, property`
104
113
  );
105
114
  }
106
115
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letterstory/cli",
3
- "version": "0.176.36",
3
+ "version": "0.176.38",
4
4
  "description": "Spin up and manage Letter phantom blogs from your terminal.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",