@letterstory/cli 0.143.0 → 0.145.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
@@ -64,7 +64,7 @@ import {
64
64
  } from "./commands.mjs";
65
65
 
66
66
  // Keep in sync with cli/package.json.
67
- export const VERSION = "0.143.0";
67
+ export const VERSION = "0.145.1";
68
68
 
69
69
  // Flags that never take a value. Listing them explicitly means `deploy get --json <id>`
70
70
  // can't accidentally swallow the id as --json's value. Every `type: "bool"` flag (and its
@@ -907,7 +907,8 @@ Admin (STAFF key — --staff-key / LETTERSTORY_STAFF_API_KEY; acts before an org
907
907
  ledger entry. Written by \`npm run lab --
908
908
  publish\` in the kernels repo.
909
909
  admin models runs [--search T] [--model M] [--starred] | run <id> | star|unstar <id> | scoreboard --model M
910
- The model hub's saved runs and blind arena (models.letter.dev)
910
+ | requests [--model M] [--outcome O] [--run ID] | request <id> | sampling [--model M --percent P]
911
+ The model hub's saved runs, blind arena and request log (models.letter.dev)
911
912
  admin spend summary [--range day|week|month|year | --from D --to D] [--grain day|week|month]
912
913
  | reconcile [--from D --to D] | pnl [--range day|week|month|year | --from D --to D]
913
914
  | reference add --vendor V [--key K] --metric billed_usd --value N --from D [--to D] --ref "…"
@@ -1392,10 +1393,15 @@ Repo changes (Claude Code edits your repo in a sandbox; the Letterstory GitHub A
1392
1393
  repo settings [--action build|build_pr|build_merge|slack|linear] [--branch <name>|none] [--slack-agent <name|U…>|none]
1393
1394
  [--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>]
1394
1395
  [--autopilot-severity critical|warning] [--auto-merge 1h|12h|24h|3d|off]
1396
+ [--reports-remedy report|fix_pr|fix_merge] [--recommendations on|off] [--recommendations-channel <C…>|none]
1397
+ [--competitor-pages on|off] [--competitor-pages-mode relevant|all] [--competitor-pages-channel <C…>|none]
1395
1398
  The org's defaults for every fix: build (preview first),
1396
1399
  build_pr, or slack (post it to --slack-channel for your
1397
1400
  own agent, tagging --slack-agent), and the branch changes go to;
1398
- --auto-merge merges Letter's PRs once open that long
1401
+ --auto-merge merges Letter's PRs once open that long;
1402
+ Slack's automations: new issues (--slack-reports, and
1403
+ --reports-remedy to fix them on their own), recommended
1404
+ actions, and competitors' new pages, each with a channel
1399
1405
  repo slack-targets Slack channels and people Send to Slack can use, with ids
1400
1406
  repo slack-test Post a test into the Send to Slack channel
1401
1407
  repo start --repo <owner/name> --instructions <text>|--instructions-file <path|->
@@ -1435,6 +1441,7 @@ Repo changes (Claude Code edits your repo in a sandbox; the Letterstory GitHub A
1435
1441
  repo list [--status running|succeeded|no_changes|failed|cancelled] [--limit n] [--audit <id>]
1436
1442
  [--source site_audit_issue|site_quick_check|letterprove_install|visitors_install]
1437
1443
  repo publish <run-id> Make PR on a preview in review
1444
+ repo merge <run-id> Merge its open PR (a squash; the repo's branch rules decide)
1438
1445
  repo revise <run-id> --instructions <text>|--instructions-file <path|->
1439
1446
  Ask for more on a preview; it takes over the preview's link
1440
1447
  repo cancel <run-id> Stop it and delete the sandbox; no PR (Discard a preview)
@@ -19,7 +19,7 @@
19
19
  // admin brand-detection <org-id> (fleet:read)
20
20
  // admin brand-detection <org-id> [--term t]... [--exact t]... [--exclude p]... [--note "…"] | --clear [--note "…"] (fleet:instrument)
21
21
  // admin recipes list|show|vocab|export|import|save|delete … (recipes:read / recipes:write)
22
- // admin models runs|run|star|unstar|scoreboard … (models:read / models:write)
22
+ // admin models runs|run|star|unstar|scoreboard|requests|request|sampling … (models:read / models:write)
23
23
  // admin replays [--org <id>] [--target <id>] [--status …] [--limit n] (fleet:read)
24
24
  // admin replay --org <id> [<response-id> | --target <id>] [--surface …] (fleet:replay)
25
25
  // admin create-org --domain acme.com [--name "Acme"] [--test] [--no-key] [--key-name N]
@@ -4067,7 +4067,7 @@ function spendEntryFromFlags(flags) {
4067
4067
 
4068
4068
  const MODELS = "/api/superadmin/models";
4069
4069
  const MODELS_USAGE =
4070
- "Usage: admin models runs [--search TEXT] [--model ballpoint-1] [--starred] [--limit N] | run <id> | star <id> | unstar <id> | scoreboard --model ballpoint-1";
4070
+ "Usage: admin models runs [--search TEXT] [--model ballpoint-1] [--starred] [--limit N] | run <id> | star <id> | unstar <id> | scoreboard --model ballpoint-1 | requests [--model M] [--consumer C] [--org ORG_ID] [--outcome ok|error|waking|rejected] [--operation generate] [--run ID] [--from ISO] [--to ISO] [--limit N] [--cursor NEXT] | request <id> | sampling [--model M --percent P]";
4071
4071
 
4072
4072
  /** The model hub's saved runs and blind arena (models.letter.dev), same functions as its pages. */
4073
4073
  async function cmdAdminModels(ctx) {
@@ -4118,6 +4118,80 @@ async function cmdAdminModels(ctx) {
4118
4118
  printResult(io, flags, result, () => "");
4119
4119
  return 0;
4120
4120
  }
4121
+ case "requests": {
4122
+ const params = new URLSearchParams(
4123
+ compact({
4124
+ model: flagStr(flags.model),
4125
+ consumer: flagStr(flags.consumer),
4126
+ org_id: flagStr(flags.org),
4127
+ outcome: flagStr(flags.outcome),
4128
+ operation: flagStr(flags.operation),
4129
+ run: flagStr(flags.run),
4130
+ from: flagStr(flags.from),
4131
+ to: flagStr(flags.to),
4132
+ limit: flagNum(flags.limit)?.toString(),
4133
+ cursor: flagStr(flags.cursor),
4134
+ })
4135
+ );
4136
+ const result = await staffRequest(ctx, "GET", `${MODELS}/requests${params.size ? `?${params}` : ""}`);
4137
+ printResult(io, flags, result, (r) =>
4138
+ (r.requests ?? [])
4139
+ .map(
4140
+ (x) =>
4141
+ `${x.created_at.slice(0, 19)} ${x.model.padEnd(16)} ${x.consumer.padEnd(18)} ${String(x.status).padStart(3)} ${x.outcome.padEnd(8)} ${String(x.latency_ms).padStart(6)} ms ${x.error ? `${x.error.slice(0, 60)} ` : ""}${x.id}`
4142
+ )
4143
+ .concat(r.next ? [`Older: --cursor '${r.next}'`] : [])
4144
+ .join("\n")
4145
+ );
4146
+ return 0;
4147
+ }
4148
+ case "request": {
4149
+ const id = positionals[1];
4150
+ if (!id) throw new CliError("Usage: admin models request <id>");
4151
+ const result = await staffRequest(ctx, "GET", `${MODELS}/requests/${id}`);
4152
+ printResult(io, flags, result, (r) =>
4153
+ [
4154
+ `${r.model} ${r.operation} · ${r.created_at} · ${r.consumer}${r.org_id ? ` · org ${r.org_id}` : ""}${r.run_id ? ` · run ${r.run_id}` : ""}`,
4155
+ `${r.status} ${r.outcome} · ${r.latency_ms} ms${r.error ? ` · ${r.error}` : ""}`,
4156
+ ...(r.calls ?? []).map(
4157
+ (c, i) =>
4158
+ ` call ${i + 1}: ${c.outcome} ${c.ms} ms ${c.input_tokens ?? 0} → ${c.output_tokens ?? 0} tokens${c.error ? ` · ${c.error}` : ""}`
4159
+ ),
4160
+ r.has_bodies
4161
+ ? [
4162
+ `request: ${JSON.stringify(r.request)}`,
4163
+ `response: ${JSON.stringify(r.response)}`,
4164
+ ...(r.raw ?? []).map((t, i) => `raw ${i + 1}: ${t ?? "—"}`),
4165
+ ].join("\n")
4166
+ : "bodies: gone (kept 14 days)",
4167
+ ].join("\n")
4168
+ );
4169
+ return 0;
4170
+ }
4171
+ case "sampling": {
4172
+ const model = flagStr(flags.model);
4173
+ const percent = flagNum(flags.percent);
4174
+ if (percent != null) {
4175
+ if (!model) throw new CliError("Usage: admin models sampling --model ballpoint-1 --percent 30");
4176
+ const result = await staffRequest(ctx, "PUT", `${MODELS}/request-sampling`, { model, percent });
4177
+ ok(ctx, `${result.model} keeps ${result.percent}% of its requests`);
4178
+ printResult(io, flags, result, () => "");
4179
+ return 0;
4180
+ }
4181
+ const result = await staffRequest(ctx, "GET", `${MODELS}/request-sampling`);
4182
+ printResult(io, flags, result, (r) =>
4183
+ [
4184
+ `Every model keeps ${r.default_percent}% unless set below; bodies are kept ${r.body_days} days.`,
4185
+ ...(r.models ?? [])
4186
+ .filter((m) => !model || m.model === model)
4187
+ .map(
4188
+ (m) =>
4189
+ ` ${m.model.padEnd(18)} ${String(m.percent).padStart(6)}% set ${m.updated_at.slice(0, 10)}`
4190
+ ),
4191
+ ].join("\n")
4192
+ );
4193
+ return 0;
4194
+ }
4121
4195
  case "scoreboard": {
4122
4196
  const model = flagStr(flags.model);
4123
4197
  if (!model) throw new CliError("Usage: admin models scoreboard --model ballpoint-1");
@@ -4,6 +4,7 @@
4
4
  // repo activity → get_repo_change_activity (what the sandbox is doing; --follow polls)
5
5
  // repo list → list_repo_changes repo cancel → cancel_repo_change
6
6
  // repo publish → publish_repo_change (Make PR on a preview)
7
+ // repo merge → merge_repo_change (merge its open PR, as a squash)
7
8
  // repo revise → revise_repo_change (ask for more on a preview, before the PR)
8
9
  // repo say → send_repo_change_message (steer a build while its agent works)
9
10
  // repo repos → list_github_app_repos repo branches → list_repo_branches
@@ -146,6 +147,31 @@ const ARGS = {
146
147
  slack_reports_channel: {
147
148
  value: (flags) => (flags["reports-channel"] === "none" ? null : flagStr(flags["reports-channel"])),
148
149
  },
150
+ // --reports-remedy report|fix_pr|fix_merge: ask, or fix it (PR, or PR and merge) and report the change
151
+ slack_reports_remedy: { flag: "reports-remedy", enum: ["report", "fix_pr", "fix_merge"] },
152
+ // --recommendations on|off, --recommendations-channel C…|none
153
+ slack_recommendations: {
154
+ value: (flags) =>
155
+ flags.recommendations === undefined
156
+ ? undefined
157
+ : ["on", "true", "1"].includes(String(flags.recommendations)),
158
+ },
159
+ slack_recommendations_channel: {
160
+ value: (flags) =>
161
+ flags["recommendations-channel"] === "none" ? null : flagStr(flags["recommendations-channel"]),
162
+ },
163
+ // --competitor-pages on|off, --competitor-pages-mode relevant|all, --competitor-pages-channel C…|none
164
+ slack_competitor_pages: {
165
+ value: (flags) =>
166
+ flags["competitor-pages"] === undefined
167
+ ? undefined
168
+ : ["on", "true", "1"].includes(String(flags["competitor-pages"])),
169
+ },
170
+ slack_competitor_pages_mode: { flag: "competitor-pages-mode", enum: ["relevant", "all"] },
171
+ slack_competitor_pages_channel: {
172
+ value: (flags) =>
173
+ flags["competitor-pages-channel"] === "none" ? null : flagStr(flags["competitor-pages-channel"]),
174
+ },
149
175
  // --listen C…,C… (the full list), or --listen none
150
176
  slack_listening_channels: {
151
177
  value: (flags) =>
@@ -195,6 +221,7 @@ const ARGS = {
195
221
  },
196
222
  cancel_repo_change: { run_id: { positional: 0, required: true, label: "run-id" } },
197
223
  publish_repo_change: { run_id: { positional: 0, required: true, label: "run-id" } },
224
+ merge_repo_change: { run_id: { positional: 0, required: true, label: "run-id" } },
198
225
  revise_repo_change: {
199
226
  run_id: { positional: 0, required: true, label: "run-id" },
200
227
  instructions: { value: (flags) => readTextInput(flags, "instructions", "instructions-file") },
@@ -252,6 +279,7 @@ function describe(run) {
252
279
  lines.push(` Ask for more: letterstory repo revise ${run.id} --instructions "…"`);
253
280
  }
254
281
  if (run.prUrl) lines.push(` PR: ${run.prUrl} (${run.prState ?? "state unknown"})`);
282
+ if (run.prState === "open") lines.push(` Merge: letterstory repo merge ${run.id}`);
255
283
  if (run.errorCode) lines.push(` Error (${run.errorCode}): ${run.errorDetail ?? ""}`);
256
284
  if (run.summary) lines.push(...run.summary.split("\n").map((l) => ` ${l}`));
257
285
  return lines.join("\n");
@@ -291,6 +319,8 @@ export async function cmdRepo(ctx) {
291
319
  return repoCancel(rest);
292
320
  case "publish":
293
321
  return repoPublish(rest);
322
+ case "merge":
323
+ return repoMerge(rest);
294
324
  case "revise":
295
325
  return repoRevise(rest);
296
326
  case "say":
@@ -311,7 +341,7 @@ export async function cmdRepo(ctx) {
311
341
  return repoSlackTest(rest);
312
342
  default:
313
343
  throw new CliError(
314
- `Unknown repo subcommand: ${sub ?? "(none)"}. Try: start, fix-audit, install-letterprove, install-visitors, sites, set-site, reset-site, secrets, set-secret, delete-secret, status, list, publish, revise, say, cancel, repos, branches, prs, settings, suggestions, slack-targets, slack-test`
344
+ `Unknown repo subcommand: ${sub ?? "(none)"}. Try: start, fix-audit, install-letterprove, install-visitors, sites, set-site, reset-site, secrets, set-secret, delete-secret, status, list, publish, merge, revise, say, cancel, repos, branches, prs, settings, suggestions, slack-targets, slack-test`
315
345
  );
316
346
  }
317
347
  }
@@ -635,6 +665,18 @@ async function repoPublish(ctx) {
635
665
  return 0;
636
666
  }
637
667
 
668
+ /** `repo merge <run-id>`: merge its open pull request, as a squash; the repo's branch rules decide. */
669
+ async function repoMerge(ctx) {
670
+ const { client, flags, io } = ctx;
671
+ const result = await client.callTool(
672
+ "merge_repo_change",
673
+ argsFromFlags(ARGS.merge_repo_change, flags, ctx.positionals)
674
+ );
675
+ ok(ctx, result.message);
676
+ printResult(io, flags, result.run, describe);
677
+ return 0;
678
+ }
679
+
638
680
  /** `repo say <run-id> <message…>` (or --message): steer a build while its agent works. */
639
681
  async function repoSay(ctx) {
640
682
  const { client, flags, io } = ctx;
@@ -711,6 +753,12 @@ async function repoSettings(ctx) {
711
753
  "slack-channel",
712
754
  "slack-reports",
713
755
  "reports-channel",
756
+ "reports-remedy",
757
+ "recommendations",
758
+ "recommendations-channel",
759
+ "competitor-pages",
760
+ "competitor-pages-mode",
761
+ "competitor-pages-channel",
714
762
  "autopilot-channel",
715
763
  "listen",
716
764
  "autopilot",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letterstory/cli",
3
- "version": "0.143.0",
3
+ "version": "0.145.1",
4
4
  "description": "Spin up and manage Letterstory phantom blogs from your terminal.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",