@letterstory/cli 0.123.0 → 0.125.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/lib/cli.mjs CHANGED
@@ -61,13 +61,15 @@ import {
61
61
  } from "./commands.mjs";
62
62
 
63
63
  // Keep in sync with cli/package.json.
64
- export const VERSION = "0.123.0";
64
+ export const VERSION = "0.125.0";
65
65
 
66
66
  // Flags that never take a value. Listing them explicitly means `deploy get --json <id>`
67
67
  // can't accidentally swallow the id as --json's value. Every `type: "bool"` flag (and its
68
68
  // `negate` spelling) in a command's ARGS map must be listed; boolean-flags.test.ts enforces it.
69
69
  export const BOOLEAN_FLAGS = new Set([
70
70
  "json",
71
+ // admin models runs: only the starred ones.
72
+ "starred",
71
73
  // shredder file / arena file: save the version as a candidate beside live.
72
74
  "candidate",
73
75
  // site tree: include children a fold-by-default family would hide; items over the whole subtree.
@@ -843,6 +845,8 @@ Admin (STAFF key — --staff-key / LETTERSTORY_STAFF_API_KEY; acts before an org
843
845
  session (a trial per arm), linked to its
844
846
  ledger entry. Written by \`npm run lab --
845
847
  publish\` in the kernels repo.
848
+ admin models runs [--model M] [--starred] | run <id> | star|unstar <id> | scoreboard --model M
849
+ The model hub's saved runs and blind arena (models.letter.dev)
846
850
  admin spend summary [--range day|week|month|year | --from D --to D] [--grain day|week|month]
847
851
  | reconcile [--from D --to D] | pnl [--range day|week|month|year | --from D --to D]
848
852
  | reference add --vendor V [--key K] --metric billed_usd --value N --from D [--to D] --ref "…"
@@ -1332,6 +1336,8 @@ Repo changes (Claude Code edits your repo in a sandbox; the Letterstory GitHub A
1332
1336
  repo list [--status running|succeeded|no_changes|failed|cancelled] [--limit n] [--audit <id>]
1333
1337
  [--source site_audit_issue|site_quick_check|letterprove_install]
1334
1338
  repo publish <run-id> Make PR on a preview in review
1339
+ repo revise <run-id> --instructions <text>|--instructions-file <path|->
1340
+ Ask for more on a preview; it takes over the preview's link
1335
1341
  repo cancel <run-id> Stop it and delete the sandbox; no PR (Discard a preview)
1336
1342
 
1337
1343
  Feedback (tell us what's broken, missing, unclear, or wanted):
@@ -19,6 +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
23
  // admin replays [--org <id>] [--target <id>] [--status …] [--limit n] (fleet:read)
23
24
  // admin replay --org <id> [<response-id> | --target <id>] [--surface …] (fleet:replay)
24
25
  // admin create-org --domain acme.com [--name "Acme"] [--test] [--no-key] [--key-name N]
@@ -3962,6 +3963,83 @@ function spendEntryFromFlags(flags) {
3962
3963
  });
3963
3964
  }
3964
3965
 
3966
+ const MODELS = "/api/superadmin/models";
3967
+ const MODELS_USAGE =
3968
+ "Usage: admin models runs [--model ballpoint-1] [--starred] [--limit N] | run <id> | star <id> | unstar <id> | scoreboard --model ballpoint-1";
3969
+
3970
+ /** The model hub's saved runs and blind arena (models.letter.dev), same functions as its pages. */
3971
+ async function cmdAdminModels(ctx) {
3972
+ const { flags, positionals, io } = ctx;
3973
+ const sub = positionals[0];
3974
+ switch (sub) {
3975
+ case "runs": {
3976
+ const params = new URLSearchParams(
3977
+ compact({
3978
+ model: flagStr(flags.model),
3979
+ starred: flagBool(flags.starred) ? "true" : undefined,
3980
+ limit: flagNum(flags.limit)?.toString(),
3981
+ })
3982
+ );
3983
+ const result = await staffRequest(ctx, "GET", `${MODELS}/runs${params.size ? `?${params}` : ""}`);
3984
+ printResult(io, flags, result, (r) =>
3985
+ (r.runs ?? [])
3986
+ .map(
3987
+ (x) =>
3988
+ `${x.created_at.slice(0, 16)} ${x.starred ? "★" : " "} ${x.model.padEnd(14)} ${String(x.outputs).padStart(3)} outputs ${String(x.votes).padStart(3)} votes ${x.contenders.join(", ")} ${x.id}`
3989
+ )
3990
+ .join("\n")
3991
+ );
3992
+ return 0;
3993
+ }
3994
+ case "run": {
3995
+ const id = positionals[1];
3996
+ if (!id) throw new CliError("Usage: admin models run <id>");
3997
+ const result = await staffRequest(ctx, "GET", `${MODELS}/runs/${id}`);
3998
+ printResult(io, flags, result, (r) =>
3999
+ [
4000
+ `${r.model} · ${r.created_at} · ${JSON.stringify(r.input)}`,
4001
+ ...(r.outputs ?? []).map(
4002
+ (o) =>
4003
+ ` [${o.slot}] ${o.contender}${o.ours ? " (ours)" : ""}: ${o.error ? `error: ${o.error}` : Object.values(o.output).join(" — ")}`
4004
+ ),
4005
+ ].join("\n")
4006
+ );
4007
+ return 0;
4008
+ }
4009
+ case "star":
4010
+ case "unstar": {
4011
+ const id = positionals[1];
4012
+ if (!id) throw new CliError(`Usage: admin models ${sub} <id>`);
4013
+ const result = await staffRequest(ctx, "PATCH", `${MODELS}/runs/${id}`, { starred: sub === "star" });
4014
+ ok(ctx, `${sub === "star" ? "Starred" : "Unstarred"} ${result.id}`);
4015
+ printResult(io, flags, result, () => "");
4016
+ return 0;
4017
+ }
4018
+ case "scoreboard": {
4019
+ const model = flagStr(flags.model);
4020
+ if (!model) throw new CliError("Usage: admin models scoreboard --model ballpoint-1");
4021
+ const result = await staffRequest(
4022
+ ctx,
4023
+ "GET",
4024
+ `${MODELS}/arena/scoreboard?model=${encodeURIComponent(model)}`
4025
+ );
4026
+ printResult(io, flags, result, (r) =>
4027
+ (r.opponents ?? []).length
4028
+ ? r.opponents
4029
+ .map(
4030
+ (o) =>
4031
+ `${r.model} vs ${o.opponent}: ${o.wins}-${o.losses} (${o.ties} ties, ${o.both_bad} both bad) · ${o.win_rate == null ? "—" : `${Math.round(o.win_rate * 100)}% [${Math.round(o.low * 100)}–${Math.round(o.high * 100)}]`} · ${o.voters} voters`
4032
+ )
4033
+ .join("\n")
4034
+ : `No votes on ${r.model} yet.`
4035
+ );
4036
+ return 0;
4037
+ }
4038
+ default:
4039
+ throw new CliError(MODELS_USAGE);
4040
+ }
4041
+ }
4042
+
3965
4043
  async function cmdAdminSpend(ctx) {
3966
4044
  const { flags, positionals, io } = ctx;
3967
4045
  const sub = positionals[0];
@@ -4767,6 +4845,7 @@ export async function cmdAdmin(ctx) {
4767
4845
  return cmdAdminReplays({ ...ctx, positionals: ctx.positionals.slice(1) }, sub);
4768
4846
  if (sub === "crm") return cmdAdminCrm({ ...ctx, positionals: ctx.positionals.slice(1) });
4769
4847
  if (sub === "spend") return cmdAdminSpend({ ...ctx, positionals: ctx.positionals.slice(1) });
4848
+ if (sub === "models") return cmdAdminModels({ ...ctx, positionals: ctx.positionals.slice(1) });
4770
4849
  if (sub === "create-org") return cmdAdminCreateOrg({ ...ctx, positionals: ctx.positionals.slice(1) });
4771
4850
  if (sub === "ask") return cmdAdminAsk({ ...ctx, positionals: ctx.positionals.slice(1) });
4772
4851
  if (sub === "patterns") return cmdAdminPatterns({ ...ctx, positionals: ctx.positionals.slice(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 revise → revise_repo_change (ask for more on a preview, before the PR)
7
8
  // repo repos → list_github_app_repos repo branches → list_repo_branches
8
9
  // repo prs → list_open_pull_requests (every open PR, whoever opened it)
9
10
  // repo install-letterprove → letterprove_open_install_pr
@@ -167,6 +168,10 @@ const ARGS = {
167
168
  get_repo_change_activity: { run_id: { positional: 0, required: true, label: "run-id" } },
168
169
  cancel_repo_change: { run_id: { positional: 0, required: true, label: "run-id" } },
169
170
  publish_repo_change: { run_id: { positional: 0, required: true, label: "run-id" } },
171
+ revise_repo_change: {
172
+ run_id: { positional: 0, required: true, label: "run-id" },
173
+ instructions: { value: (flags) => readTextInput(flags, "instructions", "instructions-file") },
174
+ },
170
175
  list_repo_changes: {
171
176
  status: { flag: "status", enum: ["running", "succeeded", "no_changes", "failed", "cancelled", "sent"] },
172
177
  limit: { flag: "limit", type: "num" },
@@ -195,8 +200,14 @@ function describe(run) {
195
200
  `${run.id} ${run.repo}@${run.baseBranch} ${run.status} (${run.phase}) $${run.costUsd.toFixed(2)}`,
196
201
  ];
197
202
  if (run.preview?.url) lines.push(` Preview: ${run.preview.url}`);
203
+ for (const [i, change] of (run.preview?.changes ?? []).entries())
204
+ lines.push(` ${i + 1}. ${change.path} ${change.note}${change.url ? ` ${change.url}` : ""}`);
198
205
  if (run.preview?.state === "failed") lines.push(` Preview failed: ${run.preview.detail ?? ""}`);
199
- if (run.phase === "review") lines.push(` Make PR: letterstory repo publish ${run.id}`);
206
+ if (run.preview?.revisionRunId) lines.push(` Follow-up being built: ${run.preview.revisionRunId}`);
207
+ else if (run.phase === "review") {
208
+ lines.push(` Make PR: letterstory repo publish ${run.id}`);
209
+ lines.push(` Ask for more: letterstory repo revise ${run.id} --instructions "…"`);
210
+ }
200
211
  if (run.prUrl) lines.push(` PR: ${run.prUrl} (${run.prState ?? "state unknown"})`);
201
212
  if (run.errorCode) lines.push(` Error (${run.errorCode}): ${run.errorDetail ?? ""}`);
202
213
  if (run.summary) lines.push(...run.summary.split("\n").map((l) => ` ${l}`));
@@ -235,6 +246,8 @@ export async function cmdRepo(ctx) {
235
246
  return repoCancel(rest);
236
247
  case "publish":
237
248
  return repoPublish(rest);
249
+ case "revise":
250
+ return repoRevise(rest);
238
251
  case "repos":
239
252
  return repoRepos(rest);
240
253
  case "branches":
@@ -549,6 +562,17 @@ async function repoPublish(ctx) {
549
562
  return 0;
550
563
  }
551
564
 
565
+ /** `repo revise <run-id> --instructions <text>`: a follow-up on a preview, built on top of it. */
566
+ async function repoRevise(ctx) {
567
+ const { client, flags, io } = ctx;
568
+ const args = argsFromFlags(ARGS.revise_repo_change, flags, ctx.positionals);
569
+ if (!args.instructions) throw new CliError("Missing --instructions (or --instructions-file <path|->)");
570
+ const result = await client.callTool("revise_repo_change", args);
571
+ ok(ctx, result.message);
572
+ printResult(io, flags, result.run, describe);
573
+ return 0;
574
+ }
575
+
552
576
  async function repoRepos(ctx) {
553
577
  const { client, flags, io } = ctx;
554
578
  const result = await client.callTool("list_github_app_repos", argsFromFlags(ARGS.list_github_app_repos, flags));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letterstory/cli",
3
- "version": "0.123.0",
3
+ "version": "0.125.0",
4
4
  "description": "Spin up and manage Letterstory phantom blogs from your terminal.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",