@letterstory/cli 0.98.1 → 0.98.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 +22 -1
- package/lib/commands/admin.mjs +7 -3
- package/lib/commands/repo.mjs +305 -0
- package/lib/commands.mjs +1 -0
- package/lib/documents.mjs +2 -0
- package/package.json +1 -1
package/lib/cli.mjs
CHANGED
|
@@ -51,6 +51,7 @@ import {
|
|
|
51
51
|
cmdMailers,
|
|
52
52
|
cmdAdmin,
|
|
53
53
|
cmdFeedback,
|
|
54
|
+
cmdRepo,
|
|
54
55
|
cmdEvals,
|
|
55
56
|
cmdOfferings,
|
|
56
57
|
cmdTargets,
|
|
@@ -58,7 +59,7 @@ import {
|
|
|
58
59
|
} from "./commands.mjs";
|
|
59
60
|
|
|
60
61
|
// Keep in sync with cli/package.json.
|
|
61
|
-
export const VERSION = "0.98.
|
|
62
|
+
export const VERSION = "0.98.3";
|
|
62
63
|
|
|
63
64
|
// Flags that never take a value. Listing them explicitly means `deploy get --json <id>`
|
|
64
65
|
// can't accidentally swallow the id as --json's value. Every `type: "bool"` flag (and its
|
|
@@ -144,6 +145,8 @@ export const BOOLEAN_FLAGS = new Set([
|
|
|
144
145
|
"no-enrich",
|
|
145
146
|
// `offerings set-collection <id> --none` (the org itself is the subject again).
|
|
146
147
|
"none",
|
|
148
|
+
// `repo set-site --repo <owner/name> --site <host> --not` (the repo does not build that site).
|
|
149
|
+
"not",
|
|
147
150
|
// `prospects import|source --one-per-company`, `prospects source --enrich`, `prospects import --find-emails`.
|
|
148
151
|
"one-per-company",
|
|
149
152
|
"enrich",
|
|
@@ -1097,6 +1100,23 @@ Insights:
|
|
|
1097
1100
|
insights access [--period 14d|30d|90d] [--collection <uuid>]
|
|
1098
1101
|
Page access: who's reading your phantom sites
|
|
1099
1102
|
|
|
1103
|
+
Repo changes (Claude Code edits your repo in a sandbox; the Letterstory GitHub App opens a PR):
|
|
1104
|
+
repo repos Repos the Letterstory GitHub App can reach
|
|
1105
|
+
repo start --repo <owner/name> --instructions <text>|--instructions-file <path|->
|
|
1106
|
+
[--branch <base>] [--attach <path>]... [--attach-url <name>=<https url>]... [--wait]
|
|
1107
|
+
Start a change (attached files go to the sandbox);
|
|
1108
|
+
--wait follows it to the PR
|
|
1109
|
+
repo fix-audit --issue <issue-type> [--audit <id>] [--branch <base>] [--wait]
|
|
1110
|
+
One PR fixing a site-audit issue on every affected page,
|
|
1111
|
+
in the repo verified to build the site
|
|
1112
|
+
repo sites [--detect [--force]] Which website each repo builds, and the evidence
|
|
1113
|
+
repo set-site --repo <owner/name> --site <host> [--not]
|
|
1114
|
+
Say which site a repo builds (--not: correct a wrong label)
|
|
1115
|
+
repo reset-site --repo <owner/name> --site <host> Back to automatic detection
|
|
1116
|
+
repo status <run-id> Phase, spend, PR link and Claude's summary
|
|
1117
|
+
repo list [--status running|succeeded|no_changes|failed|cancelled] [--limit n] [--audit <id>]
|
|
1118
|
+
repo cancel <run-id> Stop it and delete the sandbox; no PR
|
|
1119
|
+
|
|
1100
1120
|
Feedback (tell us what's broken, missing, unclear, or wanted):
|
|
1101
1121
|
feedback submit --category bug|missing_feature|unclear|feature_request|praise|other
|
|
1102
1122
|
[--message <text>|--body <text>|--file <path>|--file -] [--rating 1-5]
|
|
@@ -1201,6 +1221,7 @@ const CLIENT_COMMANDS = {
|
|
|
1201
1221
|
mail: cmdMail,
|
|
1202
1222
|
mailers: cmdMailers,
|
|
1203
1223
|
feedback: cmdFeedback,
|
|
1224
|
+
repo: cmdRepo,
|
|
1204
1225
|
evals: cmdEvals,
|
|
1205
1226
|
visits: cmdVisits,
|
|
1206
1227
|
// Platform-scoped (staff key) — the only commands that act BEFORE an org exists.
|
package/lib/commands/admin.mjs
CHANGED
|
@@ -3824,7 +3824,7 @@ async function cmdAdminFleetExperiments(ctx) {
|
|
|
3824
3824
|
const org = rest.find(Boolean);
|
|
3825
3825
|
if (!id || !org)
|
|
3826
3826
|
throw new CliError(
|
|
3827
|
-
"Usage: admin fleet-experiments spin-up <experiment-id> <org-domain-or-id> [--no-control] [--own-beat] [--posts n]"
|
|
3827
|
+
"Usage: admin fleet-experiments spin-up <experiment-id> <org-domain-or-id> [--no-control] [--own-beat] [--cadence n] [--posts n]"
|
|
3828
3828
|
);
|
|
3829
3829
|
const body = {
|
|
3830
3830
|
action: "spin_up",
|
|
@@ -3833,13 +3833,17 @@ async function cmdAdminFleetExperiments(ctx) {
|
|
|
3833
3833
|
control: !flagBool(flags["no-control"]),
|
|
3834
3834
|
match: flagBool(flags["own-beat"]) ? "own_beat" : "twin",
|
|
3835
3835
|
...(flagStr(flags.posts) ? { posts_to_seed: Number(flagStr(flags.posts)) } : {}),
|
|
3836
|
+
...(flagStr(flags.cadence) ? { cadence_per_week: Number(flagStr(flags.cadence)) } : {}),
|
|
3836
3837
|
};
|
|
3837
3838
|
return out(await action(body), (r) =>
|
|
3838
3839
|
[
|
|
3839
3840
|
r.twins
|
|
3840
3841
|
? `launching twins for ${r.org.name} on "${r.sites[0]?.beat}":`
|
|
3841
3842
|
: `launching for ${r.org.name}:`,
|
|
3842
|
-
|
|
3843
|
+
` ${r.cadencePerWeek} posts a week each`,
|
|
3844
|
+
...r.sites.map(
|
|
3845
|
+
(s) => ` ${s.arm}: ${s.name ?? s.beat} [${s.theme} template] (onboarding ${s.jobId})`
|
|
3846
|
+
),
|
|
3843
3847
|
].join("\n")
|
|
3844
3848
|
);
|
|
3845
3849
|
}
|
|
@@ -3855,7 +3859,7 @@ async function cmdAdminFleetExperiments(ctx) {
|
|
|
3855
3859
|
}
|
|
3856
3860
|
default:
|
|
3857
3861
|
throw new CliError(
|
|
3858
|
-
'Usage: admin fleet-experiments list | articles [--experiment <id>] [--org <id>] [--filter all|assigned|unassigned] [--arm <arm>] [--q …] [--limit n] [--offset n] | mark <experiment-id> <article-id…> --arm <arm> | unmark <experiment-id> <article-id…> | new --template <key> | clients | spin-up <experiment-id> <org> [--no-control] [--own-beat] | start|stop <experiment-id> | decide <experiment-id> --decision "…"'
|
|
3862
|
+
'Usage: admin fleet-experiments list | articles [--experiment <id>] [--org <id>] [--filter all|assigned|unassigned] [--arm <arm>] [--q …] [--limit n] [--offset n] | mark <experiment-id> <article-id…> --arm <arm> | unmark <experiment-id> <article-id…> | new --template <key> | clients | spin-up <experiment-id> <org> [--no-control] [--own-beat] [--cadence n] | start|stop <experiment-id> | decide <experiment-id> --decision "…"'
|
|
3859
3863
|
);
|
|
3860
3864
|
}
|
|
3861
3865
|
}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
// `repo` — changes to the org's own repos, made by Claude Code in a sandbox and opened as a
|
|
2
|
+
// pull request by the Letterstory GitHub App (src/lib/repo-changes). One subcommand per tool:
|
|
3
|
+
// repo start → start_repo_change repo status → get_repo_change
|
|
4
|
+
// repo list → list_repo_changes repo cancel → cancel_repo_change
|
|
5
|
+
// repo repos → list_github_app_repos
|
|
6
|
+
|
|
7
|
+
import { readFileSync } from "node:fs";
|
|
8
|
+
import { basename } from "node:path";
|
|
9
|
+
import { CliError } from "../client.mjs";
|
|
10
|
+
import {
|
|
11
|
+
argsFromFlags,
|
|
12
|
+
flagList,
|
|
13
|
+
flagStr,
|
|
14
|
+
followJob,
|
|
15
|
+
ok,
|
|
16
|
+
printResult,
|
|
17
|
+
readTextInput,
|
|
18
|
+
refuseUnknownFlags,
|
|
19
|
+
} from "./shared.mjs";
|
|
20
|
+
|
|
21
|
+
/** `--repo owner/name` → the two schema keys; --owner/--name also work. */
|
|
22
|
+
function splitRepo(flags) {
|
|
23
|
+
const full = flagStr(flags.repo);
|
|
24
|
+
if (full === undefined) return { owner: flagStr(flags.owner), name: flagStr(flags.name) };
|
|
25
|
+
const [owner, name, ...extra] = full.split("/");
|
|
26
|
+
if (!owner || !name || extra.length) throw new CliError("--repo must be owner/name");
|
|
27
|
+
return { owner, name };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const ARGS = {
|
|
31
|
+
start_repo_change: {
|
|
32
|
+
repo_owner: { value: (flags) => splitRepo(flags).owner },
|
|
33
|
+
repo_name: { value: (flags) => splitRepo(flags).name },
|
|
34
|
+
base_branch: "branch",
|
|
35
|
+
// --instructions, or a longer brief from --instructions-file <path> / - (stdin).
|
|
36
|
+
instructions: { value: (flags) => readTextInput(flags, "instructions", "instructions-file") },
|
|
37
|
+
// --attach <path> (uploaded first) and --attach-url <name>=<https url>, both repeatable.
|
|
38
|
+
files: { value: (_flags, _positionals, extra) => (extra.files?.length ? extra.files : undefined) },
|
|
39
|
+
},
|
|
40
|
+
create_repo_change_upload: { file_names: { internal: "the basenames of the --attach files" } },
|
|
41
|
+
list_repo_sites: {},
|
|
42
|
+
detect_repo_sites: { force: { flag: "force", type: "bool" } },
|
|
43
|
+
set_repo_site: {
|
|
44
|
+
repo_owner: { value: (flags) => splitRepo(flags).owner },
|
|
45
|
+
repo_name: { value: (flags) => splitRepo(flags).name },
|
|
46
|
+
site_host: { flag: "site", required: true },
|
|
47
|
+
builds: { flag: "not", type: "bool", invert: true },
|
|
48
|
+
},
|
|
49
|
+
reset_repo_site: {
|
|
50
|
+
repo_owner: { value: (flags) => splitRepo(flags).owner },
|
|
51
|
+
repo_name: { value: (flags) => splitRepo(flags).name },
|
|
52
|
+
site_host: { flag: "site", required: true },
|
|
53
|
+
},
|
|
54
|
+
fix_audit_issue: {
|
|
55
|
+
issue_type: { flag: "issue", required: true },
|
|
56
|
+
audit_id: "audit",
|
|
57
|
+
base_branch: "branch",
|
|
58
|
+
},
|
|
59
|
+
get_repo_change: { run_id: { positional: 0, required: true, label: "run-id" } },
|
|
60
|
+
cancel_repo_change: { run_id: { positional: 0, required: true, label: "run-id" } },
|
|
61
|
+
list_repo_changes: {
|
|
62
|
+
status: { flag: "status", enum: ["running", "succeeded", "no_changes", "failed", "cancelled"] },
|
|
63
|
+
limit: { flag: "limit", type: "num" },
|
|
64
|
+
audit_id: "audit",
|
|
65
|
+
},
|
|
66
|
+
list_github_app_repos: {},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const TERMINAL = new Set(["succeeded", "no_changes", "failed", "cancelled"]);
|
|
70
|
+
|
|
71
|
+
function describe(run) {
|
|
72
|
+
const lines = [
|
|
73
|
+
`${run.id} ${run.repo}@${run.baseBranch} ${run.status} (${run.phase}) $${run.costUsd.toFixed(2)}`,
|
|
74
|
+
];
|
|
75
|
+
if (run.prUrl) lines.push(` PR: ${run.prUrl}`);
|
|
76
|
+
if (run.errorCode) lines.push(` Error (${run.errorCode}): ${run.errorDetail ?? ""}`);
|
|
77
|
+
if (run.summary) lines.push(...run.summary.split("\n").map((l) => ` ${l}`));
|
|
78
|
+
return lines.join("\n");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function cmdRepo(ctx) {
|
|
82
|
+
const sub = ctx.positionals[0];
|
|
83
|
+
const rest = { ...ctx, positionals: ctx.positionals.slice(1) };
|
|
84
|
+
switch (sub) {
|
|
85
|
+
case "start":
|
|
86
|
+
return repoStart(rest);
|
|
87
|
+
case "status":
|
|
88
|
+
return repoStatus(rest);
|
|
89
|
+
case "fix-audit":
|
|
90
|
+
return repoFixAudit(rest);
|
|
91
|
+
case "sites":
|
|
92
|
+
return repoSites(rest);
|
|
93
|
+
case "set-site":
|
|
94
|
+
return repoSetSite(rest);
|
|
95
|
+
case "reset-site":
|
|
96
|
+
return repoResetSite(rest);
|
|
97
|
+
case "list":
|
|
98
|
+
return repoList(rest);
|
|
99
|
+
case "cancel":
|
|
100
|
+
return repoCancel(rest);
|
|
101
|
+
case "repos":
|
|
102
|
+
return repoRepos(rest);
|
|
103
|
+
default:
|
|
104
|
+
throw new CliError(
|
|
105
|
+
`Unknown repo subcommand: ${sub ?? "(none)"}. Try: start, fix-audit, sites, set-site, reset-site, status, list, cancel, repos`
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** `--attach-url name=https://…` → `{ name, url }`. */
|
|
111
|
+
function attachmentUrls(specs) {
|
|
112
|
+
return specs.map((spec) => {
|
|
113
|
+
const at = spec.indexOf("=");
|
|
114
|
+
if (at < 1) throw new CliError(`--attach-url must be name=https://… (got ${spec})`);
|
|
115
|
+
return { name: spec.slice(0, at), url: spec.slice(at + 1) };
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Reserve an upload per --attach file, PUT the bytes, and return `{ name, upload_path }` for each. */
|
|
120
|
+
async function uploadAttachments(ctx, paths) {
|
|
121
|
+
if (!paths.length) return [];
|
|
122
|
+
const names = paths.map((p) =>
|
|
123
|
+
basename(p)
|
|
124
|
+
.replace(/[^A-Za-z0-9._ -]/g, "-")
|
|
125
|
+
.replace(/^[^A-Za-z0-9]+/, "")
|
|
126
|
+
);
|
|
127
|
+
if (new Set(names).size !== names.length) throw new CliError("Two --attach files share a name; rename one.");
|
|
128
|
+
const bytes = paths.map((p) => {
|
|
129
|
+
try {
|
|
130
|
+
return readFileSync(p);
|
|
131
|
+
} catch (err) {
|
|
132
|
+
throw new CliError(`Could not read --attach ${p}: ${err.message}`);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
const { uploads } = await ctx.client.callTool("create_repo_change_upload", { file_names: names });
|
|
136
|
+
for (const [i, slot] of uploads.entries()) {
|
|
137
|
+
const res = await fetch(slot.upload_url, {
|
|
138
|
+
method: "PUT",
|
|
139
|
+
headers: { "content-type": "application/octet-stream" },
|
|
140
|
+
body: bytes[i],
|
|
141
|
+
});
|
|
142
|
+
if (!res.ok) throw new CliError(`Uploading ${slot.name} failed (${res.status}).`);
|
|
143
|
+
ok(ctx, `Uploaded ${slot.name}`);
|
|
144
|
+
}
|
|
145
|
+
return uploads.map((slot) => ({ name: slot.name, upload_path: slot.path }));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function repoStart(ctx) {
|
|
149
|
+
const { client, flags, io } = ctx;
|
|
150
|
+
refuseUnknownFlags(ctx, {}, [
|
|
151
|
+
"repo",
|
|
152
|
+
"owner",
|
|
153
|
+
"name",
|
|
154
|
+
"branch",
|
|
155
|
+
"instructions",
|
|
156
|
+
"instructions-file",
|
|
157
|
+
"attach",
|
|
158
|
+
"attach-url",
|
|
159
|
+
"wait",
|
|
160
|
+
]);
|
|
161
|
+
const files = [
|
|
162
|
+
...(await uploadAttachments(ctx, flagList(flags.attach))),
|
|
163
|
+
...attachmentUrls(flagList(flags["attach-url"])),
|
|
164
|
+
];
|
|
165
|
+
const args = argsFromFlags(ARGS.start_repo_change, flags, ctx.positionals, { files });
|
|
166
|
+
if (!args.repo_owner || !args.repo_name) throw new CliError("Missing --repo owner/name");
|
|
167
|
+
if (!args.instructions) throw new CliError("Missing --instructions (or --instructions-file <path|->)");
|
|
168
|
+
|
|
169
|
+
const started = await client.callTool("start_repo_change", args);
|
|
170
|
+
ok(ctx, started.message);
|
|
171
|
+
if (!flags.wait) {
|
|
172
|
+
printResult(io, flags, started.run, describe);
|
|
173
|
+
return 0;
|
|
174
|
+
}
|
|
175
|
+
const { settled, value } = await followJob(
|
|
176
|
+
io,
|
|
177
|
+
async () => {
|
|
178
|
+
const polled = await client.callTool("get_repo_change", { run_id: started.run.id });
|
|
179
|
+
ok(ctx, `… ${polled.run.status} (${polled.run.phase})`);
|
|
180
|
+
return { done: TERMINAL.has(polled.run.status), value: polled.run };
|
|
181
|
+
},
|
|
182
|
+
{ intervalMs: 30000, ceilingMs: 60 * 60 * 1000 }
|
|
183
|
+
);
|
|
184
|
+
if (!settled) ok(ctx, `Still running; check later with: letterstory repo status ${started.run.id}`);
|
|
185
|
+
printResult(io, flags, value, describe);
|
|
186
|
+
return value.status === "failed" ? 1 : 0;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function followRun(ctx, run) {
|
|
190
|
+
const { client, flags, io } = ctx;
|
|
191
|
+
const { settled, value } = await followJob(
|
|
192
|
+
io,
|
|
193
|
+
async () => {
|
|
194
|
+
const polled = await client.callTool("get_repo_change", { run_id: run.id });
|
|
195
|
+
ok(ctx, `… ${polled.run.status} (${polled.run.phase})`);
|
|
196
|
+
return { done: TERMINAL.has(polled.run.status), value: polled.run };
|
|
197
|
+
},
|
|
198
|
+
{ intervalMs: 30000, ceilingMs: 60 * 60 * 1000 }
|
|
199
|
+
);
|
|
200
|
+
if (!settled) ok(ctx, `Still running; check later with: letterstory repo status ${run.id}`);
|
|
201
|
+
printResult(io, flags, value, describe);
|
|
202
|
+
return value.status === "failed" ? 1 : 0;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** `repo fix-audit --issue <type> [--audit <id>] [--wait]`: one audit issue → one PR, in the repo verified to build the site. */
|
|
206
|
+
async function repoFixAudit(ctx) {
|
|
207
|
+
const { client, flags, io } = ctx;
|
|
208
|
+
refuseUnknownFlags(ctx, {}, ["issue", "audit", "branch", "wait"]);
|
|
209
|
+
const result = await client.callTool(
|
|
210
|
+
"fix_audit_issue",
|
|
211
|
+
argsFromFlags(ARGS.fix_audit_issue, flags, ctx.positionals)
|
|
212
|
+
);
|
|
213
|
+
ok(ctx, result.message);
|
|
214
|
+
if (!flags.wait) {
|
|
215
|
+
printResult(io, flags, result.run, describe);
|
|
216
|
+
return 0;
|
|
217
|
+
}
|
|
218
|
+
return followRun(ctx, result.run);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** `repo sites [--detect] [--force]`: which website each repo builds, and why. */
|
|
222
|
+
async function repoSites(ctx) {
|
|
223
|
+
const { client, flags, io } = ctx;
|
|
224
|
+
const result = flags.detect
|
|
225
|
+
? await client.callTool("detect_repo_sites", argsFromFlags(ARGS.detect_repo_sites, flags))
|
|
226
|
+
: await client.callTool("list_repo_sites", argsFromFlags(ARGS.list_repo_sites, flags));
|
|
227
|
+
printResult(io, flags, result, (r) => {
|
|
228
|
+
const serving = r.sites.filter((s) => s.verdict === "serves");
|
|
229
|
+
const lines = serving.map((s) =>
|
|
230
|
+
s.decidedBy === "member"
|
|
231
|
+
? `${s.repo} builds ${s.siteHost} (set by a member)`
|
|
232
|
+
: `${s.repo} builds ${s.siteHost} (${Math.round(s.score * 100)}%: ${s.evidence.signals.join(", ")})`
|
|
233
|
+
);
|
|
234
|
+
if (r.failed?.length) lines.push(...r.failed.map((f) => `! ${f.repo}: ${f.error}`));
|
|
235
|
+
return lines.length ? lines.join("\n") : "No repo is verified to build any of this organization's sites yet.";
|
|
236
|
+
});
|
|
237
|
+
return 0;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** `repo set-site --repo owner/name --site acme.com [--not]`: say which site a repo builds (or doesn't). */
|
|
241
|
+
async function repoSetSite(ctx) {
|
|
242
|
+
const { client, flags, io } = ctx;
|
|
243
|
+
refuseUnknownFlags(ctx, {}, ["repo", "owner", "name", "site", "not"]);
|
|
244
|
+
const result = await client.callTool("set_repo_site", argsFromFlags(ARGS.set_repo_site, flags));
|
|
245
|
+
ok(ctx, result.message);
|
|
246
|
+
printResult(io, flags, result.site);
|
|
247
|
+
return 0;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** `repo reset-site --repo owner/name --site acme.com`: back to automatic detection. */
|
|
251
|
+
async function repoResetSite(ctx) {
|
|
252
|
+
const { client, flags, io } = ctx;
|
|
253
|
+
refuseUnknownFlags(ctx, {}, ["repo", "owner", "name", "site"]);
|
|
254
|
+
const result = await client.callTool("reset_repo_site", argsFromFlags(ARGS.reset_repo_site, flags));
|
|
255
|
+
ok(ctx, result.message);
|
|
256
|
+
printResult(io, flags, result);
|
|
257
|
+
return 0;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async function repoStatus(ctx) {
|
|
261
|
+
const { client, flags, io } = ctx;
|
|
262
|
+
const result = await client.callTool(
|
|
263
|
+
"get_repo_change",
|
|
264
|
+
argsFromFlags(ARGS.get_repo_change, flags, ctx.positionals)
|
|
265
|
+
);
|
|
266
|
+
ok(ctx, result.message);
|
|
267
|
+
printResult(io, flags, result.run, describe);
|
|
268
|
+
return 0;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
async function repoList(ctx) {
|
|
272
|
+
const { client, flags, io } = ctx;
|
|
273
|
+
const result = await client.callTool(
|
|
274
|
+
"list_repo_changes",
|
|
275
|
+
argsFromFlags(ARGS.list_repo_changes, flags, ctx.positionals)
|
|
276
|
+
);
|
|
277
|
+
printResult(io, flags, result.runs, (runs) =>
|
|
278
|
+
runs.length ? runs.map(describe).join("\n") : "No repo changes yet."
|
|
279
|
+
);
|
|
280
|
+
return 0;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
async function repoCancel(ctx) {
|
|
284
|
+
const { client, flags, io } = ctx;
|
|
285
|
+
const result = await client.callTool(
|
|
286
|
+
"cancel_repo_change",
|
|
287
|
+
argsFromFlags(ARGS.cancel_repo_change, flags, ctx.positionals)
|
|
288
|
+
);
|
|
289
|
+
ok(ctx, result.message);
|
|
290
|
+
printResult(io, flags, result.run, describe);
|
|
291
|
+
return 0;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async function repoRepos(ctx) {
|
|
295
|
+
const { client, flags, io } = ctx;
|
|
296
|
+
const result = await client.callTool("list_github_app_repos", argsFromFlags(ARGS.list_github_app_repos, flags));
|
|
297
|
+
printResult(io, flags, result, (r) =>
|
|
298
|
+
r.repos.length
|
|
299
|
+
? r.repos.map((repo) => `${repo.fullName} (default ${repo.defaultBranch})`).join("\n")
|
|
300
|
+
: r.message
|
|
301
|
+
);
|
|
302
|
+
return 0;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export { ARGS };
|
package/lib/commands.mjs
CHANGED
|
@@ -40,5 +40,6 @@ export * from "./commands/offerings.mjs";
|
|
|
40
40
|
export * from "./commands/targets.mjs";
|
|
41
41
|
export * from "./commands/admin.mjs";
|
|
42
42
|
export * from "./commands/feedback.mjs";
|
|
43
|
+
export * from "./commands/repo.mjs";
|
|
43
44
|
export * from "./commands/evals.mjs";
|
|
44
45
|
export * from "./commands/visits.mjs";
|
package/lib/documents.mjs
CHANGED
|
@@ -61,6 +61,8 @@ export const DOCUMENT_FLAG_PAIRS = [
|
|
|
61
61
|
["text", "text-file"],
|
|
62
62
|
// An offering's (or the company's) manifesto is prose — a PDF or a .docx is fair game.
|
|
63
63
|
["manifesto", "manifesto-file"],
|
|
64
|
+
// A repo change's brief (repo.mjs `repo start`) — prose, so a .docx or PDF spec is fine.
|
|
65
|
+
["instructions", "instructions-file"],
|
|
64
66
|
];
|
|
65
67
|
|
|
66
68
|
/**
|