@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 +10 -3
- package/lib/commands/admin.mjs +76 -2
- package/lib/commands/repo.mjs +49 -1
- package/package.json +1 -1
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.
|
|
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
|
-
|
|
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)
|
package/lib/commands/admin.mjs
CHANGED
|
@@ -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 …
|
|
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");
|
package/lib/commands/repo.mjs
CHANGED
|
@@ -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",
|