@letterstory/cli 0.121.1 → 0.121.3
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 +8 -1
- package/lib/commands/repo.mjs +108 -1
- package/lib/documents.mjs +2 -0
- package/package.json +1 -1
package/lib/cli.mjs
CHANGED
|
@@ -60,7 +60,7 @@ import {
|
|
|
60
60
|
} from "./commands.mjs";
|
|
61
61
|
|
|
62
62
|
// Keep in sync with cli/package.json.
|
|
63
|
-
export const VERSION = "0.121.
|
|
63
|
+
export const VERSION = "0.121.3";
|
|
64
64
|
|
|
65
65
|
// Flags that never take a value. Listing them explicitly means `deploy get --json <id>`
|
|
66
66
|
// can't accidentally swallow the id as --json's value. Every `type: "bool"` flag (and its
|
|
@@ -1261,7 +1261,14 @@ Repo changes (Claude Code edits your repo in a sandbox; the Letterstory GitHub A
|
|
|
1261
1261
|
repo set-site --repo <owner/name> --site <host> [--not] [--branch <name>|none]
|
|
1262
1262
|
Say which site a repo builds (--not: correct a wrong label)
|
|
1263
1263
|
repo reset-site --repo <owner/name> --site <host> Back to automatic detection
|
|
1264
|
+
repo secrets --repo <owner/name> Preview secrets: names and when set (never values)
|
|
1265
|
+
repo set-secret --repo <owner/name> --secret <KEY> --value <v> | --value-file <path|->
|
|
1266
|
+
Set an env var the preview site gets (use test credentials:
|
|
1267
|
+
the preview URL is public to anyone with the link)
|
|
1268
|
+
repo delete-secret --repo <owner/name> --secret <KEY>
|
|
1269
|
+
Delete one
|
|
1264
1270
|
repo status <run-id> Phase, spend, PR link and Claude's summary
|
|
1271
|
+
repo activity <run-id> [--follow] What the sandbox is doing, line by line (--follow: until it stops)
|
|
1265
1272
|
repo list [--status running|succeeded|no_changes|failed|cancelled] [--limit n] [--audit <id>]
|
|
1266
1273
|
[--source site_audit_issue|site_quick_check|letterprove_install]
|
|
1267
1274
|
repo publish <run-id> Make PR on a preview in review
|
package/lib/commands/repo.mjs
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
// `repo` — changes to the org's own repos, made by Claude Code in a sandbox and opened as a
|
|
2
2
|
// pull request by the Letterstory GitHub App (src/lib/repo-changes). One subcommand per tool:
|
|
3
3
|
// repo start → start_repo_change repo status → get_repo_change
|
|
4
|
+
// repo activity → get_repo_change_activity (what the sandbox is doing; --follow polls)
|
|
4
5
|
// repo list → list_repo_changes repo cancel → cancel_repo_change
|
|
5
6
|
// repo publish → publish_repo_change (Make PR on a preview)
|
|
6
7
|
// repo repos → list_github_app_repos repo branches → list_repo_branches
|
|
7
8
|
// repo prs → list_open_pull_requests (every open PR, whoever opened it)
|
|
8
9
|
// repo install-letterprove → letterprove_open_install_pr
|
|
10
|
+
// repo secrets → list_repo_preview_secrets repo set-secret → set_repo_preview_secret
|
|
11
|
+
// repo delete-secret → delete_repo_preview_secret
|
|
9
12
|
// repo suggestions → list_suggested_fixes
|
|
10
13
|
// repo settings → get_repo_change_settings, or set_repo_change_settings with --action / --branch
|
|
11
14
|
// repo slack-targets → list_slack_fix_targets repo slack-test → send_slack_test_message
|
|
@@ -24,6 +27,14 @@ import {
|
|
|
24
27
|
refuseUnknownFlags,
|
|
25
28
|
} from "./shared.mjs";
|
|
26
29
|
|
|
30
|
+
/** `--value <v>` or `--value-file <path|->`; a file's single trailing newline is dropped. */
|
|
31
|
+
function secretValue(flags) {
|
|
32
|
+
const inline = flagStr(flags.value);
|
|
33
|
+
const value = readTextInput(flags, "value", "value-file");
|
|
34
|
+
if (value === undefined) throw new CliError("Pass the value with --value <value> or --value-file <path|->.");
|
|
35
|
+
return inline === undefined ? value.replace(/\r?\n$/, "") : value;
|
|
36
|
+
}
|
|
37
|
+
|
|
27
38
|
/** `--repo owner/name` → the two schema keys; --owner/--name also work. */
|
|
28
39
|
function splitRepo(flags) {
|
|
29
40
|
const full = flagStr(flags.repo);
|
|
@@ -68,6 +79,21 @@ const ARGS = {
|
|
|
68
79
|
repo_name: { value: (flags) => splitRepo(flags).name },
|
|
69
80
|
site_host: { flag: "site", required: true },
|
|
70
81
|
},
|
|
82
|
+
list_repo_preview_secrets: {
|
|
83
|
+
repo_owner: { value: (flags) => splitRepo(flags).owner },
|
|
84
|
+
repo_name: { value: (flags) => splitRepo(flags).name },
|
|
85
|
+
},
|
|
86
|
+
set_repo_preview_secret: {
|
|
87
|
+
repo_owner: { value: (flags) => splitRepo(flags).owner },
|
|
88
|
+
repo_name: { value: (flags) => splitRepo(flags).name },
|
|
89
|
+
key: { flag: "secret", required: true },
|
|
90
|
+
value: { value: (flags) => secretValue(flags) },
|
|
91
|
+
},
|
|
92
|
+
delete_repo_preview_secret: {
|
|
93
|
+
repo_owner: { value: (flags) => splitRepo(flags).owner },
|
|
94
|
+
repo_name: { value: (flags) => splitRepo(flags).name },
|
|
95
|
+
key: { flag: "secret", required: true },
|
|
96
|
+
},
|
|
71
97
|
fix_audit_issue: {
|
|
72
98
|
// --issue <type>, repeatable or comma-separated: several go into one PR (issue_types).
|
|
73
99
|
issue_type: { value: (flags) => (issueList(flags).length === 1 ? issueList(flags)[0] : undefined) },
|
|
@@ -138,6 +164,7 @@ const ARGS = {
|
|
|
138
164
|
autopilot_min_severity: { flag: "autopilot-severity", enum: ["critical", "warning"] },
|
|
139
165
|
},
|
|
140
166
|
get_repo_change: { run_id: { positional: 0, required: true, label: "run-id" } },
|
|
167
|
+
get_repo_change_activity: { run_id: { positional: 0, required: true, label: "run-id" } },
|
|
141
168
|
cancel_repo_change: { run_id: { positional: 0, required: true, label: "run-id" } },
|
|
142
169
|
publish_repo_change: { run_id: { positional: 0, required: true, label: "run-id" } },
|
|
143
170
|
list_repo_changes: {
|
|
@@ -184,6 +211,8 @@ export async function cmdRepo(ctx) {
|
|
|
184
211
|
return repoStart(rest);
|
|
185
212
|
case "status":
|
|
186
213
|
return repoStatus(rest);
|
|
214
|
+
case "activity":
|
|
215
|
+
return repoActivity(rest);
|
|
187
216
|
case "fix-audit":
|
|
188
217
|
return repoFixAudit(rest);
|
|
189
218
|
case "install-letterprove":
|
|
@@ -194,6 +223,12 @@ export async function cmdRepo(ctx) {
|
|
|
194
223
|
return repoSetSite(rest);
|
|
195
224
|
case "reset-site":
|
|
196
225
|
return repoResetSite(rest);
|
|
226
|
+
case "secrets":
|
|
227
|
+
return repoSecrets(rest);
|
|
228
|
+
case "set-secret":
|
|
229
|
+
return repoSetSecret(rest);
|
|
230
|
+
case "delete-secret":
|
|
231
|
+
return repoDeleteSecret(rest);
|
|
197
232
|
case "list":
|
|
198
233
|
return repoList(rest);
|
|
199
234
|
case "cancel":
|
|
@@ -216,7 +251,7 @@ export async function cmdRepo(ctx) {
|
|
|
216
251
|
return repoSlackTest(rest);
|
|
217
252
|
default:
|
|
218
253
|
throw new CliError(
|
|
219
|
-
`Unknown repo subcommand: ${sub ?? "(none)"}. Try: start, fix-audit, install-letterprove, sites, set-site, reset-site, status, list, publish, cancel, repos, branches, prs, settings, suggestions, slack-targets, slack-test`
|
|
254
|
+
`Unknown repo subcommand: ${sub ?? "(none)"}. Try: start, fix-audit, install-letterprove, sites, set-site, reset-site, secrets, set-secret, delete-secret, status, list, publish, cancel, repos, branches, prs, settings, suggestions, slack-targets, slack-test`
|
|
220
255
|
);
|
|
221
256
|
}
|
|
222
257
|
}
|
|
@@ -396,6 +431,45 @@ async function repoResetSite(ctx) {
|
|
|
396
431
|
return 0;
|
|
397
432
|
}
|
|
398
433
|
|
|
434
|
+
/** `repo secrets --repo owner/name`: the repo's preview secrets, names and when each was set (never values). */
|
|
435
|
+
async function repoSecrets(ctx) {
|
|
436
|
+
const { client, flags, io } = ctx;
|
|
437
|
+
refuseUnknownFlags(ctx, {}, ["repo", "owner", "name"]);
|
|
438
|
+
const result = await client.callTool(
|
|
439
|
+
"list_repo_preview_secrets",
|
|
440
|
+
argsFromFlags(ARGS.list_repo_preview_secrets, flags)
|
|
441
|
+
);
|
|
442
|
+
printResult(io, flags, result, (r) =>
|
|
443
|
+
r.secrets.length
|
|
444
|
+
? r.secrets.map((s) => `${s.key} (set ${s.updatedAt})`).join("\n")
|
|
445
|
+
: "No preview secrets for this repo."
|
|
446
|
+
);
|
|
447
|
+
return 0;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/** `repo set-secret --repo owner/name --secret KEY --value v | --value-file path|-`: set or overwrite one. */
|
|
451
|
+
async function repoSetSecret(ctx) {
|
|
452
|
+
const { client, flags, io } = ctx;
|
|
453
|
+
refuseUnknownFlags(ctx, {}, ["repo", "owner", "name", "secret", "value", "value-file"]);
|
|
454
|
+
const result = await client.callTool("set_repo_preview_secret", argsFromFlags(ARGS.set_repo_preview_secret, flags));
|
|
455
|
+
ok(ctx, result.message);
|
|
456
|
+
printResult(io, flags, result.secret);
|
|
457
|
+
return 0;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/** `repo delete-secret --repo owner/name --secret KEY`: delete one. */
|
|
461
|
+
async function repoDeleteSecret(ctx) {
|
|
462
|
+
const { client, flags, io } = ctx;
|
|
463
|
+
refuseUnknownFlags(ctx, {}, ["repo", "owner", "name", "secret"]);
|
|
464
|
+
const result = await client.callTool(
|
|
465
|
+
"delete_repo_preview_secret",
|
|
466
|
+
argsFromFlags(ARGS.delete_repo_preview_secret, flags)
|
|
467
|
+
);
|
|
468
|
+
ok(ctx, result.message);
|
|
469
|
+
printResult(io, flags, result);
|
|
470
|
+
return 0;
|
|
471
|
+
}
|
|
472
|
+
|
|
399
473
|
async function repoStatus(ctx) {
|
|
400
474
|
const { client, flags, io } = ctx;
|
|
401
475
|
const result = await client.callTool(
|
|
@@ -407,6 +481,39 @@ async function repoStatus(ctx) {
|
|
|
407
481
|
return 0;
|
|
408
482
|
}
|
|
409
483
|
|
|
484
|
+
const ACTIVITY_MARK = { progress: "·", stage: "▸", say: "»", tool: "$", error: "!" };
|
|
485
|
+
|
|
486
|
+
/** `repo activity <run-id> [--follow]`: the build's feed; --follow prints new lines until it stops. */
|
|
487
|
+
async function repoActivity(ctx) {
|
|
488
|
+
const { client, flags, io } = ctx;
|
|
489
|
+
refuseUnknownFlags(ctx, {}, ["follow"]);
|
|
490
|
+
const args = argsFromFlags(ARGS.get_repo_change_activity, flags, ctx.positionals);
|
|
491
|
+
const line = (l) => `${l.at.slice(11, 19)} ${ACTIVITY_MARK[l.kind] ?? " "} ${l.text}`;
|
|
492
|
+
let { activity } = await client.callTool("get_repo_change_activity", args);
|
|
493
|
+
if (flags.json && !flags.follow) {
|
|
494
|
+
printResult(io, flags, activity);
|
|
495
|
+
return 0;
|
|
496
|
+
}
|
|
497
|
+
let seen = 0;
|
|
498
|
+
const print = () => {
|
|
499
|
+
for (const l of activity.lines.slice(seen)) io.log(flags.json ? JSON.stringify(l) : line(l));
|
|
500
|
+
seen = activity.lines.length;
|
|
501
|
+
};
|
|
502
|
+
print();
|
|
503
|
+
if (!flags.follow) return 0;
|
|
504
|
+
await followJob(
|
|
505
|
+
io,
|
|
506
|
+
async () => {
|
|
507
|
+
({ activity } = await client.callTool("get_repo_change_activity", args));
|
|
508
|
+
print();
|
|
509
|
+
return { done: !activity.live, value: activity };
|
|
510
|
+
},
|
|
511
|
+
{ intervalMs: 3000, ceilingMs: 60 * 60 * 1000 }
|
|
512
|
+
);
|
|
513
|
+
ok(ctx, `Run ${activity.runId} is ${activity.status} (${activity.phase}).`);
|
|
514
|
+
return 0;
|
|
515
|
+
}
|
|
516
|
+
|
|
410
517
|
async function repoList(ctx) {
|
|
411
518
|
const { client, flags, io } = ctx;
|
|
412
519
|
const result = await client.callTool(
|
package/lib/documents.mjs
CHANGED
|
@@ -83,6 +83,8 @@ export const JSON_ARG_FLAG_PAIRS = [
|
|
|
83
83
|
["csv", "csv-file"],
|
|
84
84
|
// A site proposal's tool input, parsed as JSON (site.mjs proposalInput).
|
|
85
85
|
["input", "input-file"],
|
|
86
|
+
// A preview secret's value, sent byte for byte (repo.mjs `repo set-secret`); never extracted.
|
|
87
|
+
["value", "value-file"],
|
|
86
88
|
// A Vercel API token read verbatim, so it stays out of shell history (outbound.mjs mail vercel-key set).
|
|
87
89
|
["token", "token-file"],
|
|
88
90
|
];
|