@letterstory/cli 0.100.2 → 0.100.5
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 +6 -4
- package/lib/commands/org.mjs +10 -2
- package/lib/commands/repo.mjs +32 -5
- package/package.json +1 -1
package/lib/cli.mjs
CHANGED
|
@@ -59,7 +59,7 @@ import {
|
|
|
59
59
|
} from "./commands.mjs";
|
|
60
60
|
|
|
61
61
|
// Keep in sync with cli/package.json.
|
|
62
|
-
export const VERSION = "0.100.
|
|
62
|
+
export const VERSION = "0.100.5";
|
|
63
63
|
|
|
64
64
|
// Flags that never take a value. Listing them explicitly means `deploy get --json <id>`
|
|
65
65
|
// can't accidentally swallow the id as --json's value. Every `type: "bool"` flag (and its
|
|
@@ -77,6 +77,7 @@ export const BOOLEAN_FLAGS = new Set([
|
|
|
77
77
|
"no-control",
|
|
78
78
|
"own-beat",
|
|
79
79
|
"refresh",
|
|
80
|
+
"preview",
|
|
80
81
|
"accept",
|
|
81
82
|
"help",
|
|
82
83
|
"version",
|
|
@@ -1120,10 +1121,10 @@ Repo changes (Claude Code edits your repo in a sandbox; the Letterstory GitHub A
|
|
|
1120
1121
|
repo repos Repos the Letterstory GitHub App can reach
|
|
1121
1122
|
repo branches --repo owner/name Branches a change can start from (default first)
|
|
1122
1123
|
repo start --repo <owner/name> --instructions <text>|--instructions-file <path|->
|
|
1123
|
-
[--branch <base>] [--attach <path>]... [--attach-url <name>=<https url>]... [--wait]
|
|
1124
|
+
[--branch <base>] [--attach <path>]... [--attach-url <name>=<https url>]... [--preview] [--wait]
|
|
1124
1125
|
Start a change (attached files go to the sandbox);
|
|
1125
1126
|
--wait follows it to the PR
|
|
1126
|
-
repo fix-audit --issue <issue-type> [--audit <id>] [--branch <base>] [--wait]
|
|
1127
|
+
repo fix-audit --issue <issue-type> [--audit <id>] [--branch <base>] [--preview] [--wait]
|
|
1127
1128
|
One PR fixing a site-audit issue on every affected page,
|
|
1128
1129
|
in the repo verified to build the site
|
|
1129
1130
|
repo sites [--detect [--force]] Which website each repo builds, and the evidence
|
|
@@ -1132,7 +1133,8 @@ Repo changes (Claude Code edits your repo in a sandbox; the Letterstory GitHub A
|
|
|
1132
1133
|
repo reset-site --repo <owner/name> --site <host> Back to automatic detection
|
|
1133
1134
|
repo status <run-id> Phase, spend, PR link and Claude's summary
|
|
1134
1135
|
repo list [--status running|succeeded|no_changes|failed|cancelled] [--limit n] [--audit <id>]
|
|
1135
|
-
repo
|
|
1136
|
+
repo publish <run-id> Make PR on a preview in review
|
|
1137
|
+
repo cancel <run-id> Stop it and delete the sandbox; no PR (Discard a preview)
|
|
1136
1138
|
|
|
1137
1139
|
Feedback (tell us what's broken, missing, unclear, or wanted):
|
|
1138
1140
|
feedback submit --category bug|missing_feature|unclear|feature_request|praise|other
|
package/lib/commands/org.mjs
CHANGED
|
@@ -220,9 +220,17 @@ export function formatApply(result) {
|
|
|
220
220
|
for (const a of result.skipped) lines.push(` ${SYMBOL[a.op]} ${a.section}/${a.name} — ${a.reason}`);
|
|
221
221
|
if (result.failed.length) lines.push("Failed:");
|
|
222
222
|
for (const a of result.failed) lines.push(` ${SYMBOL[a.op]} ${a.section}/${a.name} — ${a.error}`);
|
|
223
|
-
|
|
223
|
+
// `gated` lists every gated change, allowed or not; after a real apply, show only those still held.
|
|
224
|
+
// A field-gated record can land in `applied` with its gated field skipped, so check `skipped` too.
|
|
225
|
+
const key = (a) => `${a.op} ${a.section}/${a.name}`;
|
|
226
|
+
const done = new Set(result.dryRun ? [] : result.applied.map(key));
|
|
227
|
+
const held = (result.gated ?? []).filter(
|
|
228
|
+
(a) =>
|
|
229
|
+
!done.has(key(a)) || result.skipped.some((s) => key(s) === key(a) && s.reason.includes(`gated (${a.gate})`))
|
|
230
|
+
);
|
|
231
|
+
if (held.length) {
|
|
224
232
|
lines.push(`Gated (allow with --allow; confirm ${result.confirm}):`);
|
|
225
|
-
for (const a of
|
|
233
|
+
for (const a of held) lines.push(` ${SYMBOL[a.op]} ${a.section}/${a.name} — ${a.gate}`);
|
|
226
234
|
}
|
|
227
235
|
for (const w of result.plan.warnings) lines.push(`warning: ${w}`);
|
|
228
236
|
return lines.join("\n");
|
package/lib/commands/repo.mjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
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
4
|
// repo list → list_repo_changes repo cancel → cancel_repo_change
|
|
5
|
+
// repo publish → publish_repo_change (Make PR on a preview)
|
|
5
6
|
// repo repos → list_github_app_repos repo branches → list_repo_branches
|
|
6
7
|
|
|
7
8
|
import { readFileSync } from "node:fs";
|
|
@@ -36,6 +37,7 @@ const ARGS = {
|
|
|
36
37
|
instructions: { value: (flags) => readTextInput(flags, "instructions", "instructions-file") },
|
|
37
38
|
// --attach <path> (uploaded first) and --attach-url <name>=<https url>, both repeatable.
|
|
38
39
|
files: { value: (_flags, _positionals, extra) => (extra.files?.length ? extra.files : undefined) },
|
|
40
|
+
preview: { flag: "preview", type: "bool" },
|
|
39
41
|
},
|
|
40
42
|
create_repo_change_upload: { file_names: { internal: "the basenames of the --attach files" } },
|
|
41
43
|
list_repo_sites: {},
|
|
@@ -55,9 +57,11 @@ const ARGS = {
|
|
|
55
57
|
issue_type: { flag: "issue", required: true },
|
|
56
58
|
audit_id: "audit",
|
|
57
59
|
base_branch: "branch",
|
|
60
|
+
preview: { flag: "preview", type: "bool" },
|
|
58
61
|
},
|
|
59
62
|
get_repo_change: { run_id: { positional: 0, required: true, label: "run-id" } },
|
|
60
63
|
cancel_repo_change: { run_id: { positional: 0, required: true, label: "run-id" } },
|
|
64
|
+
publish_repo_change: { run_id: { positional: 0, required: true, label: "run-id" } },
|
|
61
65
|
list_repo_changes: {
|
|
62
66
|
status: { flag: "status", enum: ["running", "succeeded", "no_changes", "failed", "cancelled"] },
|
|
63
67
|
limit: { flag: "limit", type: "num" },
|
|
@@ -76,7 +80,10 @@ function describe(run) {
|
|
|
76
80
|
const lines = [
|
|
77
81
|
`${run.id} ${run.repo}@${run.baseBranch} ${run.status} (${run.phase}) $${run.costUsd.toFixed(2)}`,
|
|
78
82
|
];
|
|
79
|
-
if (run.
|
|
83
|
+
if (run.preview?.url) lines.push(` Preview: ${run.preview.url}`);
|
|
84
|
+
if (run.preview?.state === "failed") lines.push(` Preview failed: ${run.preview.detail ?? ""}`);
|
|
85
|
+
if (run.phase === "review") lines.push(` Make PR: letterstory repo publish ${run.id}`);
|
|
86
|
+
if (run.prUrl) lines.push(` PR: ${run.prUrl} (${run.prState ?? "state unknown"})`);
|
|
80
87
|
if (run.errorCode) lines.push(` Error (${run.errorCode}): ${run.errorDetail ?? ""}`);
|
|
81
88
|
if (run.summary) lines.push(...run.summary.split("\n").map((l) => ` ${l}`));
|
|
82
89
|
return lines.join("\n");
|
|
@@ -102,13 +109,15 @@ export async function cmdRepo(ctx) {
|
|
|
102
109
|
return repoList(rest);
|
|
103
110
|
case "cancel":
|
|
104
111
|
return repoCancel(rest);
|
|
112
|
+
case "publish":
|
|
113
|
+
return repoPublish(rest);
|
|
105
114
|
case "repos":
|
|
106
115
|
return repoRepos(rest);
|
|
107
116
|
case "branches":
|
|
108
117
|
return repoBranches(rest);
|
|
109
118
|
default:
|
|
110
119
|
throw new CliError(
|
|
111
|
-
`Unknown repo subcommand: ${sub ?? "(none)"}. Try: start, fix-audit, sites, set-site, reset-site, status, list, cancel, repos, branches`
|
|
120
|
+
`Unknown repo subcommand: ${sub ?? "(none)"}. Try: start, fix-audit, sites, set-site, reset-site, status, list, publish, cancel, repos, branches`
|
|
112
121
|
);
|
|
113
122
|
}
|
|
114
123
|
}
|
|
@@ -162,6 +171,7 @@ async function repoStart(ctx) {
|
|
|
162
171
|
"instructions-file",
|
|
163
172
|
"attach",
|
|
164
173
|
"attach-url",
|
|
174
|
+
"preview",
|
|
165
175
|
"wait",
|
|
166
176
|
]);
|
|
167
177
|
const files = [
|
|
@@ -183,7 +193,7 @@ async function repoStart(ctx) {
|
|
|
183
193
|
async () => {
|
|
184
194
|
const polled = await client.callTool("get_repo_change", { run_id: started.run.id });
|
|
185
195
|
ok(ctx, `… ${polled.run.status} (${polled.run.phase})`);
|
|
186
|
-
return { done:
|
|
196
|
+
return { done: settledFor(polled.run), value: polled.run };
|
|
187
197
|
},
|
|
188
198
|
{ intervalMs: 30000, ceilingMs: 60 * 60 * 1000 }
|
|
189
199
|
);
|
|
@@ -192,6 +202,11 @@ async function repoStart(ctx) {
|
|
|
192
202
|
return value.status === "failed" ? 1 : 0;
|
|
193
203
|
}
|
|
194
204
|
|
|
205
|
+
/** --wait stops at a terminal state, or at review: a preview waits on a person, not on time. */
|
|
206
|
+
function settledFor(run) {
|
|
207
|
+
return TERMINAL.has(run.status) || run.phase === "review";
|
|
208
|
+
}
|
|
209
|
+
|
|
195
210
|
async function followRun(ctx, run) {
|
|
196
211
|
const { client, flags, io } = ctx;
|
|
197
212
|
const { settled, value } = await followJob(
|
|
@@ -199,7 +214,7 @@ async function followRun(ctx, run) {
|
|
|
199
214
|
async () => {
|
|
200
215
|
const polled = await client.callTool("get_repo_change", { run_id: run.id });
|
|
201
216
|
ok(ctx, `… ${polled.run.status} (${polled.run.phase})`);
|
|
202
|
-
return { done:
|
|
217
|
+
return { done: settledFor(polled.run), value: polled.run };
|
|
203
218
|
},
|
|
204
219
|
{ intervalMs: 30000, ceilingMs: 60 * 60 * 1000 }
|
|
205
220
|
);
|
|
@@ -211,7 +226,7 @@ async function followRun(ctx, run) {
|
|
|
211
226
|
/** `repo fix-audit --issue <type> [--audit <id>] [--wait]`: one audit issue → one PR, in the repo verified to build the site. */
|
|
212
227
|
async function repoFixAudit(ctx) {
|
|
213
228
|
const { client, flags, io } = ctx;
|
|
214
|
-
refuseUnknownFlags(ctx, {}, ["issue", "audit", "branch", "wait"]);
|
|
229
|
+
refuseUnknownFlags(ctx, {}, ["issue", "audit", "branch", "preview", "wait"]);
|
|
215
230
|
const result = await client.callTool(
|
|
216
231
|
"fix_audit_issue",
|
|
217
232
|
argsFromFlags(ARGS.fix_audit_issue, flags, ctx.positionals)
|
|
@@ -297,6 +312,18 @@ async function repoCancel(ctx) {
|
|
|
297
312
|
return 0;
|
|
298
313
|
}
|
|
299
314
|
|
|
315
|
+
/** `repo publish <run-id>`: Make PR on a preview in review. */
|
|
316
|
+
async function repoPublish(ctx) {
|
|
317
|
+
const { client, flags, io } = ctx;
|
|
318
|
+
const result = await client.callTool(
|
|
319
|
+
"publish_repo_change",
|
|
320
|
+
argsFromFlags(ARGS.publish_repo_change, flags, ctx.positionals)
|
|
321
|
+
);
|
|
322
|
+
ok(ctx, result.message);
|
|
323
|
+
printResult(io, flags, result.run, describe);
|
|
324
|
+
return 0;
|
|
325
|
+
}
|
|
326
|
+
|
|
300
327
|
async function repoRepos(ctx) {
|
|
301
328
|
const { client, flags, io } = ctx;
|
|
302
329
|
const result = await client.callTool("list_github_app_repos", argsFromFlags(ARGS.list_github_app_repos, flags));
|