@letterstory/cli 0.177.17 → 0.177.19

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
@@ -88,7 +88,7 @@ import {
88
88
  import { fetchParkedSections, withoutParked } from "./parked.mjs";
89
89
 
90
90
  // Matches cli/package.json; publish-cli.yml rewrites both to the published version.
91
- export const VERSION = "0.177.17";
91
+ export const VERSION = "0.177.19";
92
92
 
93
93
  /** The exit code for a gate refusal (CARD_REQUIRED, PAYMENT_FAILED, OUT_OF_TOKENS): 1 is a plain error. */
94
94
  export const EXIT_PAYMENT_REQUIRED = 3;
@@ -106,6 +106,10 @@ export const BOOLEAN_FLAGS = new Set([
106
106
  "pull-requests",
107
107
  "make-orgs",
108
108
  "no-refit",
109
+ // build --check: read every gate a start meets and start nothing.
110
+ "check",
111
+ // admin invariants: press every startable row as a check too.
112
+ "presses",
109
113
  // admin targets check: check every target again, checked or not.
110
114
  "recheck",
111
115
  // build list: also move the builds' open changes along while polling.
@@ -1126,6 +1130,10 @@ Admin (STAFF key — --staff-key / LETTERSTORY_STAFF_API_KEY; acts before an org
1126
1130
  admin recipes reread --org <id>,… Read these companies from their sites again: what was
1127
1131
  only ever inferred is replaced, what the company changed
1128
1132
  is kept. Refit them after. Needs recipes:write.
1133
+ admin invariants [--org <id>,…] [--limit 50] [--presses]
1134
+ The Ideas invariants over production, read only: rows a
1135
+ start would refuse, builds whose reading disagrees with
1136
+ what they hold, grouped with ids. Exits 1 on any.
1129
1137
  admin targets check [--org <id>,…] [--recheck] [--limit 10]
1130
1138
  Check ranking targets' phrases (buyer intent, self-named,
1131
1139
  search demand, fit) and store each verdict: --org those
@@ -23,6 +23,7 @@
23
23
  // admin letterpress beyond [--limit N] [--before ISO] (what members asked that went past the library)
24
24
  // admin letterpress endings [--days N] [--write-cases] (how turns and repo changes ended when not done, by owner)
25
25
  // admin letterpress settings|flip|set … (recipes:read / recipes:write)
26
+ // admin invariants [--org <id>,…] [--limit N] [--presses] (staff)
26
27
  // admin demo guide|list|show|set|reset|check|ideas … (demo:read / demo:write)
27
28
  // admin deployments [--health down|failing|blocked|setting_up|live|paused|archived] [--org <id>] [--q <text>] [--archived] [--test] [--no-host] (fleet:read)
28
29
  // admin models runs|run|star|unstar|scoreboard|requests|request|sampling … (models:read / models:write)
@@ -62,7 +63,42 @@ import {
62
63
  compact,
63
64
  ok,
64
65
  requirePositional,
66
+ argsFromFlags,
65
67
  } from "./shared.mjs";
68
+
69
+ // `admin invariants`: the one tool it calls, its flags declared (scripts/parity-check.ts reads this map).
70
+ export const ARGS = {
71
+ check_ideas_invariants: {
72
+ org_ids: { value: (flags) => (flags.org ? flagCsv(flags.org) : undefined) },
73
+ limit: { flag: "limit", type: "num" },
74
+ presses: { flag: "presses", type: "bool" },
75
+ },
76
+ };
77
+
78
+ const INVARIANT_LIMIT = 30;
79
+
80
+ // `admin invariants` — the Ideas invariants over production (docs/architecture/ideas-invariants.md), read only:
81
+ // check_ideas_invariants, grouped by invariant with ids. Exits 1 when any holds false.
82
+ async function cmdAdminInvariants(ctx) {
83
+ const { client, flags, io } = ctx;
84
+ const result = await client.callTool("check_ideas_invariants", argsFromFlags(ARGS.check_ideas_invariants, flags));
85
+ printResult(io, flags, result, (r) =>
86
+ [
87
+ `${r.total} violation(s) on ${r.accounts} account(s)${r.unread.length ? `, ${r.unread.length} not read` : ""}.`,
88
+ ...r.by_invariant.map((g) =>
89
+ [
90
+ `\n${g.invariant} ${g.count} (${g.owner})`,
91
+ ...g.violations
92
+ .slice(0, INVARIANT_LIMIT)
93
+ .map((v) => ` ${v.host} ${v.org_id} ${v.subject}\n ${v.evidence}`),
94
+ ...(g.count > INVARIANT_LIMIT ? [` … ${g.count - INVARIANT_LIMIT} more (--json for all)`] : []),
95
+ ].join("\n")
96
+ ),
97
+ ...r.unread.map((u) => `not read: ${u.org_id}: ${u.error}`),
98
+ ].join("\n")
99
+ );
100
+ return result.total > 0 ? 1 : 0;
101
+ }
66
102
  import { watchDomainJob } from "./onboard.mjs";
67
103
 
68
104
  const SEER_MODES = ["suggest", "skip"];
@@ -7349,10 +7385,11 @@ export async function cmdAdmin(ctx) {
7349
7385
  if (sub === "letterpress") return cmdAdminLetterpress({ ...ctx, positionals: ctx.positionals.slice(1) });
7350
7386
  if (sub === "demo") return cmdAdminDemo({ ...ctx, positionals: ctx.positionals.slice(1) });
7351
7387
  if (sub === "targets") return cmdAdminTargets({ ...ctx, positionals: ctx.positionals.slice(1) });
7388
+ if (sub === "invariants") return cmdAdminInvariants({ ...ctx, positionals: ctx.positionals.slice(1) });
7352
7389
  if (sub === "deployments" || sub === "deploys")
7353
7390
  return cmdAdminDeployments({ ...ctx, positionals: ctx.positionals.slice(1) });
7354
7391
  throw new CliError(
7355
- 'Usage: admin recipes … | admin letterpress … | admin demo … | admin targets … | admin deployments … | admin republish … | admin create-org --domain <bare-domain> [--onboard] … | admin ask "<question>" | admin patterns … | admin experiments … | admin fleet-experiments … | admin crm … | admin spend … | admin evals … | admin brand-detection … | admin tuning … | admin tuning-context | admin replays … (see --help)'
7392
+ 'Usage: admin recipes … | admin letterpress … | admin demo … | admin targets … | admin invariants … | admin deployments … | admin republish … | admin create-org --domain <bare-domain> [--onboard] … | admin ask "<question>" | admin patterns … | admin experiments … | admin fleet-experiments … | admin crm … | admin spend … | admin evals … | admin brand-detection … | admin tuning … | admin tuning-context | admin replays … (see --help)'
7356
7393
  );
7357
7394
  }
7358
7395
 
@@ -105,6 +105,8 @@ const ARGS = {
105
105
  site_name: "site-name",
106
106
  // --plan-only: answer what it would do and start nothing (--json for the plan to send back).
107
107
  plan_only: { flag: "plan-only", type: "bool" },
108
+ // --check: read every gate and say whether a press would start it, starting and recording nothing.
109
+ check: { flag: "check", type: "bool" },
108
110
  // --plan-only always plans what it's given, as /plan does: it was asked for by name.
109
111
  asked_to_plan: { value: (flags) => (flags["plan-only"] ? true : undefined) },
110
112
  // --plan-file <path>: build exactly the plan --plan-only answered.
@@ -281,6 +283,7 @@ async function buildStart(ctx) {
281
283
  "plan-only",
282
284
  "plan-file",
283
285
  "branch",
286
+ "check",
284
287
  ...FILE_FLAGS,
285
288
  ]);
286
289
  const files = await attachedFiles(ctx);
@@ -294,6 +297,7 @@ async function buildStart(ctx) {
294
297
  ...r.plan.changes.map((c) => ` ${c.title ?? c.instructions}`),
295
298
  ...r.plan.actions.map((a) => ` ${a.title}`),
296
299
  ].join("\n");
300
+ if (r.status === "would_start") return r.message ?? "This would start now.";
297
301
  if (r.status !== "started") return r.message ?? r.status;
298
302
  const steps = (r.build?.steps ?? []).map((s) => ` ${s.status} ${s.title}${s.note ? `: ${s.note}` : ""}`);
299
303
  return [`Started: ${r.build.title} (${r.build.id})`, ...steps].join("\n");
@@ -15,7 +15,8 @@
15
15
  // site competitors → list_competitor_sites site watch-competitors [d] → watch_competitor_sites
16
16
  // site highlight <domain> [--off] → set_competitor_highlight
17
17
  // site prospect <url> → read_prospect_site (staff: any company's site, no account, nothing stored)
18
- // site suggest <host> --idea <id> | --draft-id <id>… | --pattern <folder> | --item <id> --path <p> → start_suggestion
18
+ // site suggest <host> --idea <id> | --draft-id <id>… | --pattern <folder> | --item <id> --path <p>
19
+ // | --new-page <folder> --words "<what the page is>" → start_suggestion
19
20
 
20
21
  import { writeFileSync } from "node:fs";
21
22
 
@@ -202,6 +203,13 @@ const ARGS = {
202
203
  pattern_folder: "pattern",
203
204
  observation_id: "item",
204
205
  path: "path",
206
+ new_page: {
207
+ value: (flags) => {
208
+ const folder = flags["new-page"];
209
+ const words = flags.words;
210
+ return folder ? { folder: String(folder), words: String(words ?? "") } : undefined;
211
+ },
212
+ },
205
213
  },
206
214
  fix_site_observations: {
207
215
  host: { positional: 0, required: true, label: "host" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letterstory/cli",
3
- "version": "0.177.17",
3
+ "version": "0.177.19",
4
4
  "description": "Spin up and manage Letter phantom blogs from your terminal.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",