@letterstory/cli 0.102.3 → 0.103.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 +13 -6
- package/lib/commands/repo.mjs +75 -5
- package/lib/commands/site.mjs +34 -2
- package/package.json +1 -1
package/lib/cli.mjs
CHANGED
|
@@ -60,15 +60,16 @@ import {
|
|
|
60
60
|
} from "./commands.mjs";
|
|
61
61
|
|
|
62
62
|
// Keep in sync with cli/package.json.
|
|
63
|
-
export const VERSION = "0.
|
|
63
|
+
export const VERSION = "0.103.0";
|
|
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
|
|
67
67
|
// `negate` spelling) in a command's ARGS map must be listed; boolean-flags.test.ts enforces it.
|
|
68
68
|
export const BOOLEAN_FLAGS = new Set([
|
|
69
69
|
"json",
|
|
70
|
-
// site tree: include children a fold-by-default family would hide.
|
|
70
|
+
// site tree: include children a fold-by-default family would hide; items over the whole subtree.
|
|
71
71
|
"unfold",
|
|
72
|
+
"subtree",
|
|
72
73
|
// site map: do not start an audit for a host that has none.
|
|
73
74
|
"no-audit",
|
|
74
75
|
// site refresh / observe: the loop's switches.
|
|
@@ -1169,14 +1170,20 @@ The Site (one model of your whole marketing site, keyed by host and path):
|
|
|
1169
1170
|
Repo changes (Claude Code edits your repo in a sandbox; the Letterstory GitHub App opens a PR):
|
|
1170
1171
|
repo repos Repos the Letterstory GitHub App can reach
|
|
1171
1172
|
repo branches --repo owner/name Branches a change can start from (default first)
|
|
1173
|
+
repo suggestions Fixes found and not acted on yet, and how to start each
|
|
1174
|
+
repo settings [--action build|build_pr|slack] [--branch <name>|none] [--slack-agent <name|U…>|none]
|
|
1175
|
+
The org's defaults for every fix: build (preview first),
|
|
1176
|
+
build_pr, or slack (post it for your own agent, tagging
|
|
1177
|
+
--slack-agent), and the branch changes go to
|
|
1172
1178
|
repo start --repo <owner/name> --instructions <text>|--instructions-file <path|->
|
|
1173
|
-
[--branch <base>] [--attach <path>]... [--attach-url <name>=<https url>]...
|
|
1179
|
+
[--branch <base>] [--attach <path>]... [--attach-url <name>=<https url>]...
|
|
1180
|
+
[--action build|build_pr|slack] [--wait]
|
|
1174
1181
|
Start a change (attached files go to the sandbox);
|
|
1175
1182
|
--wait follows it to the PR
|
|
1176
|
-
repo fix-audit --issue <issue-type
|
|
1177
|
-
One PR fixing
|
|
1183
|
+
repo fix-audit --issue <issue-type>... [--audit <id>] [--branch <base>] [--action build|build_pr|slack] [--wait]
|
|
1184
|
+
One PR fixing site-audit issues (repeat --issue to bundle) on every affected page,
|
|
1178
1185
|
in the repo verified to build the site
|
|
1179
|
-
repo install-letterprove --repo <owner/name> [--branch <base>] [--wait]
|
|
1186
|
+
repo install-letterprove --repo <owner/name> [--branch <base>] [--action build|build_pr|slack] [--wait]
|
|
1180
1187
|
One PR adding Letterprove's script and identify calls
|
|
1181
1188
|
(pick the repo where users sign in)
|
|
1182
1189
|
repo sites [--detect [--force]] Which website each repo builds, and the evidence
|
package/lib/commands/repo.mjs
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
// repo publish → publish_repo_change (Make PR on a preview)
|
|
6
6
|
// repo repos → list_github_app_repos repo branches → list_repo_branches
|
|
7
7
|
// repo install-letterprove → letterprove_open_install_pr
|
|
8
|
+
// repo suggestions → list_suggested_fixes
|
|
9
|
+
// repo settings → get_repo_change_settings, or set_repo_change_settings with --action / --branch
|
|
8
10
|
|
|
9
11
|
import { readFileSync } from "node:fs";
|
|
10
12
|
import { basename } from "node:path";
|
|
@@ -29,6 +31,13 @@ function splitRepo(flags) {
|
|
|
29
31
|
return { owner, name };
|
|
30
32
|
}
|
|
31
33
|
|
|
34
|
+
function issueList(flags) {
|
|
35
|
+
return flagList(flags.issue)
|
|
36
|
+
.flatMap((v) => v.split(","))
|
|
37
|
+
.map((v) => v.trim())
|
|
38
|
+
.filter(Boolean);
|
|
39
|
+
}
|
|
40
|
+
|
|
32
41
|
const ARGS = {
|
|
33
42
|
start_repo_change: {
|
|
34
43
|
repo_owner: { value: (flags) => splitRepo(flags).owner },
|
|
@@ -38,6 +47,7 @@ const ARGS = {
|
|
|
38
47
|
instructions: { value: (flags) => readTextInput(flags, "instructions", "instructions-file") },
|
|
39
48
|
// --attach <path> (uploaded first) and --attach-url <name>=<https url>, both repeatable.
|
|
40
49
|
files: { value: (_flags, _positionals, extra) => (extra.files?.length ? extra.files : undefined) },
|
|
50
|
+
action: { flag: "action", enum: ["build", "build_pr", "slack"] },
|
|
41
51
|
preview: { flag: "preview", type: "bool" },
|
|
42
52
|
},
|
|
43
53
|
create_repo_change_upload: { file_names: { internal: "the basenames of the --attach files" } },
|
|
@@ -55,21 +65,34 @@ const ARGS = {
|
|
|
55
65
|
site_host: { flag: "site", required: true },
|
|
56
66
|
},
|
|
57
67
|
fix_audit_issue: {
|
|
58
|
-
|
|
68
|
+
// --issue <type>, repeatable or comma-separated: several go into one PR (issue_types).
|
|
69
|
+
issue_type: { value: (flags) => (issueList(flags).length === 1 ? issueList(flags)[0] : undefined) },
|
|
70
|
+
issue_types: { value: (flags) => (issueList(flags).length > 1 ? issueList(flags) : undefined) },
|
|
59
71
|
audit_id: "audit",
|
|
60
72
|
base_branch: "branch",
|
|
73
|
+
action: { flag: "action", enum: ["build", "build_pr", "slack"] },
|
|
61
74
|
preview: { flag: "preview", type: "bool" },
|
|
62
75
|
},
|
|
63
76
|
letterprove_open_install_pr: {
|
|
64
77
|
repo_owner: { value: (flags) => splitRepo(flags).owner },
|
|
65
78
|
repo_name: { value: (flags) => splitRepo(flags).name },
|
|
66
79
|
base_branch: "branch",
|
|
80
|
+
action: { flag: "action", enum: ["build", "build_pr", "slack"] },
|
|
81
|
+
},
|
|
82
|
+
get_repo_change_settings: {},
|
|
83
|
+
list_suggested_fixes: {},
|
|
84
|
+
set_repo_change_settings: {
|
|
85
|
+
default_action: { flag: "action", enum: ["build", "build_pr", "slack"] },
|
|
86
|
+
// --branch <name>, or --branch none for each repo's own default.
|
|
87
|
+
default_branch: { value: (flags) => (flags.branch === "none" ? null : flagStr(flags.branch)) },
|
|
88
|
+
// --slack-agent Devin (or a U… member id), or --slack-agent none to tag nobody.
|
|
89
|
+
slack_agent: { value: (flags) => (flags["slack-agent"] === "none" ? null : flagStr(flags["slack-agent"])) },
|
|
67
90
|
},
|
|
68
91
|
get_repo_change: { run_id: { positional: 0, required: true, label: "run-id" } },
|
|
69
92
|
cancel_repo_change: { run_id: { positional: 0, required: true, label: "run-id" } },
|
|
70
93
|
publish_repo_change: { run_id: { positional: 0, required: true, label: "run-id" } },
|
|
71
94
|
list_repo_changes: {
|
|
72
|
-
status: { flag: "status", enum: ["running", "succeeded", "no_changes", "failed", "cancelled"] },
|
|
95
|
+
status: { flag: "status", enum: ["running", "succeeded", "no_changes", "failed", "cancelled", "sent"] },
|
|
73
96
|
limit: { flag: "limit", type: "num" },
|
|
74
97
|
audit_id: "audit",
|
|
75
98
|
source_kind: { flag: "source", enum: ["site_audit_issue", "site_quick_check", "letterprove_install"] },
|
|
@@ -124,9 +147,13 @@ export async function cmdRepo(ctx) {
|
|
|
124
147
|
return repoRepos(rest);
|
|
125
148
|
case "branches":
|
|
126
149
|
return repoBranches(rest);
|
|
150
|
+
case "settings":
|
|
151
|
+
return repoSettings(rest);
|
|
152
|
+
case "suggestions":
|
|
153
|
+
return repoSuggestions(rest);
|
|
127
154
|
default:
|
|
128
155
|
throw new CliError(
|
|
129
|
-
`Unknown repo subcommand: ${sub ?? "(none)"}. Try: start, fix-audit, install-letterprove, sites, set-site, reset-site, status, list, publish, cancel, repos, branches`
|
|
156
|
+
`Unknown repo subcommand: ${sub ?? "(none)"}. Try: start, fix-audit, install-letterprove, sites, set-site, reset-site, status, list, publish, cancel, repos, branches, settings, suggestions`
|
|
130
157
|
);
|
|
131
158
|
}
|
|
132
159
|
}
|
|
@@ -180,6 +207,7 @@ async function repoStart(ctx) {
|
|
|
180
207
|
"instructions-file",
|
|
181
208
|
"attach",
|
|
182
209
|
"attach-url",
|
|
210
|
+
"action",
|
|
183
211
|
"preview",
|
|
184
212
|
"wait",
|
|
185
213
|
]);
|
|
@@ -235,7 +263,9 @@ async function followRun(ctx, run) {
|
|
|
235
263
|
/** `repo fix-audit --issue <type> [--audit <id>] [--wait]`: one audit issue → one PR, in the repo verified to build the site. */
|
|
236
264
|
async function repoFixAudit(ctx) {
|
|
237
265
|
const { client, flags, io } = ctx;
|
|
238
|
-
refuseUnknownFlags(ctx, {}, ["issue", "audit", "branch", "preview", "wait"]);
|
|
266
|
+
refuseUnknownFlags(ctx, {}, ["issue", "audit", "branch", "action", "preview", "wait"]);
|
|
267
|
+
if (issueList(flags).length === 0)
|
|
268
|
+
throw new CliError("--issue <issue-type> is required (repeat it to fix several in one PR)");
|
|
239
269
|
const result = await client.callTool(
|
|
240
270
|
"fix_audit_issue",
|
|
241
271
|
argsFromFlags(ARGS.fix_audit_issue, flags, ctx.positionals)
|
|
@@ -251,7 +281,7 @@ async function repoFixAudit(ctx) {
|
|
|
251
281
|
/** `repo install-letterprove --repo owner/name [--branch b] [--wait]`: Letterprove's script + identify calls → one PR. */
|
|
252
282
|
async function repoInstallLetterprove(ctx) {
|
|
253
283
|
const { client, flags, io } = ctx;
|
|
254
|
-
refuseUnknownFlags(ctx, {}, ["repo", "owner", "name", "branch", "wait"]);
|
|
284
|
+
refuseUnknownFlags(ctx, {}, ["repo", "owner", "name", "branch", "action", "wait"]);
|
|
255
285
|
const result = await client.callTool(
|
|
256
286
|
"letterprove_open_install_pr",
|
|
257
287
|
argsFromFlags(ARGS.letterprove_open_install_pr, flags, ctx.positionals)
|
|
@@ -371,4 +401,44 @@ async function repoBranches(ctx) {
|
|
|
371
401
|
return 0;
|
|
372
402
|
}
|
|
373
403
|
|
|
404
|
+
/**
|
|
405
|
+
* `repo settings` shows the org's defaults; `--action build|build_pr` and/or `--branch <name>`
|
|
406
|
+
* (`--branch none`: each repo's own default) change them.
|
|
407
|
+
*/
|
|
408
|
+
async function repoSettings(ctx) {
|
|
409
|
+
const { client, flags, io } = ctx;
|
|
410
|
+
refuseUnknownFlags(ctx, {}, ["action", "branch", "slack-agent"]);
|
|
411
|
+
const setting = flags.action !== undefined || flags.branch !== undefined || flags["slack-agent"] !== undefined;
|
|
412
|
+
const result = setting
|
|
413
|
+
? await client.callTool("set_repo_change_settings", argsFromFlags(ARGS.set_repo_change_settings, flags))
|
|
414
|
+
: await client.callTool("get_repo_change_settings", {});
|
|
415
|
+
ok(ctx, result.message);
|
|
416
|
+
printResult(io, flags, result);
|
|
417
|
+
return 0;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/** `repo suggestions`: fixes found and not acted on yet, and the command that starts each. */
|
|
421
|
+
async function repoSuggestions(ctx) {
|
|
422
|
+
const { client, flags, io } = ctx;
|
|
423
|
+
refuseUnknownFlags(ctx, {}, []);
|
|
424
|
+
const result = await client.callTool("list_suggested_fixes", {});
|
|
425
|
+
printResult(io, flags, result, (r) =>
|
|
426
|
+
r.suggestions.length === 0
|
|
427
|
+
? "Nothing to fix right now."
|
|
428
|
+
: r.suggestions
|
|
429
|
+
.map((s) => {
|
|
430
|
+
const where = s.repo ? `${s.repo} → ${s.siteHost}` : `${s.siteHost} (pick a repo)`;
|
|
431
|
+
const start =
|
|
432
|
+
s.start.tool === "fix_audit_issue"
|
|
433
|
+
? `letterstory repo fix-audit --audit ${s.start.args.audit_id} --issue ${s.start.args.issue_type}`
|
|
434
|
+
: s.start.args
|
|
435
|
+
? `letterstory repo install-letterprove --repo ${s.start.args.repo_owner}/${s.start.args.repo_name}`
|
|
436
|
+
: "letterstory repo install-letterprove --repo <owner/name>";
|
|
437
|
+
return `[${s.severity}] ${s.title} ${where}\n ${start}`;
|
|
438
|
+
})
|
|
439
|
+
.join("\n")
|
|
440
|
+
);
|
|
441
|
+
return 0;
|
|
442
|
+
}
|
|
443
|
+
|
|
374
444
|
export { ARGS };
|
package/lib/commands/site.mjs
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// site dismiss <host> <id> → dismiss_site_observation
|
|
7
7
|
// site families <host> → list_site_families site decide-family <id> → decide_site_family
|
|
8
8
|
// site proposals <host> → list_site_proposals site propose <host> → create_site_proposal
|
|
9
|
-
// site decide <proposal> → decide_site_proposal
|
|
9
|
+
// site decide <proposal> → decide_site_proposal site fix <host> <id>… → fix_site_observations
|
|
10
10
|
|
|
11
11
|
import { CliError } from "../client.mjs";
|
|
12
12
|
import { argsFromFlags, flagList, flagStr, ok, printResult, readTextInput, refuseUnknownFlags } from "./shared.mjs";
|
|
@@ -55,6 +55,7 @@ const ARGS = {
|
|
|
55
55
|
path: { positional: 1, label: "path" },
|
|
56
56
|
depth: { flag: "depth", type: "num" },
|
|
57
57
|
unfold: { flag: "unfold", type: "bool" },
|
|
58
|
+
subtree: { flag: "subtree", type: "bool" },
|
|
58
59
|
},
|
|
59
60
|
get_site_page: {
|
|
60
61
|
host: { positional: 0, required: true, label: "host" },
|
|
@@ -116,6 +117,18 @@ const ARGS = {
|
|
|
116
117
|
decision: { flag: "decision", required: true, enum: ["accept", "dismiss"] },
|
|
117
118
|
reason: "reason",
|
|
118
119
|
},
|
|
120
|
+
fix_site_observations: {
|
|
121
|
+
host: { positional: 0, required: true, label: "host" },
|
|
122
|
+
// Every positional after the host, plus --observation <id> (repeatable).
|
|
123
|
+
observation_ids: {
|
|
124
|
+
value: (flags, positionals) => {
|
|
125
|
+
const ids = [...positionals.slice(1), ...flagList(flags.observation)];
|
|
126
|
+
return ids.length ? ids : undefined;
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
base_branch: { flag: "base-branch" },
|
|
130
|
+
preview: { flag: "preview", type: "bool" },
|
|
131
|
+
},
|
|
119
132
|
};
|
|
120
133
|
|
|
121
134
|
export async function cmdSite(ctx) {
|
|
@@ -150,9 +163,11 @@ export async function cmdSite(ctx) {
|
|
|
150
163
|
return sitePropose(rest);
|
|
151
164
|
case "decide":
|
|
152
165
|
return siteDecide(rest);
|
|
166
|
+
case "fix":
|
|
167
|
+
return siteFix(rest);
|
|
153
168
|
default:
|
|
154
169
|
throw new CliError(
|
|
155
|
-
`Unknown site subcommand: ${sub ?? "(none)"}. Try: list, map, refresh, unmap, tree, page, observations, observe, dismiss, families, decide-family, proposals, propose, decide`
|
|
170
|
+
`Unknown site subcommand: ${sub ?? "(none)"}. Try: list, map, refresh, unmap, tree, page, observations, observe, dismiss, families, decide-family, proposals, propose, decide, fix`
|
|
156
171
|
);
|
|
157
172
|
}
|
|
158
173
|
}
|
|
@@ -373,4 +388,21 @@ async function siteDecide(ctx) {
|
|
|
373
388
|
return 0;
|
|
374
389
|
}
|
|
375
390
|
|
|
391
|
+
async function siteFix(ctx) {
|
|
392
|
+
const { client, flags, io } = ctx;
|
|
393
|
+
refuseUnknownFlags(ctx, ARGS.fix_site_observations, ["observation"]);
|
|
394
|
+
const args = argsFromFlags(ARGS.fix_site_observations, flags, ctx.positionals);
|
|
395
|
+
if (!args.observation_ids)
|
|
396
|
+
throw new CliError("Give at least one observation id: site fix <host> <id>… (or --observation <id>)");
|
|
397
|
+
const result = await client.callTool("fix_site_observations", args);
|
|
398
|
+
ok(ctx, result.message);
|
|
399
|
+
printResult(io, flags, result, (r) => {
|
|
400
|
+
const lines = [`${r.run.id} ${r.run.repo} [${r.run.status}]${r.reused ? " (already running)" : ""}`];
|
|
401
|
+
for (const id of r.fixing) lines.push(` fixing ${id}`);
|
|
402
|
+
for (const s of r.skipped) lines.push(` skipped ${s.id} ${s.reason}`);
|
|
403
|
+
return lines.join("\n");
|
|
404
|
+
});
|
|
405
|
+
return 0;
|
|
406
|
+
}
|
|
407
|
+
|
|
376
408
|
export { ARGS };
|