misterpropre 0.0.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/LICENSE +21 -0
- package/README.md +85 -0
- package/dist/chunk-FDTKUXXE.js +418 -0
- package/dist/chunk-HMGF6JWH.js +884 -0
- package/dist/chunk-JCB4UDCP.js +1348 -0
- package/dist/cli.js +256 -0
- package/dist/dashboard-W6QCV3NV.js +335 -0
- package/dist/setup-R2IL4RHH.js +8 -0
- package/package.json +64 -0
- package/skills/dependabot-purge/SKILL.md.tmpl +137 -0
- package/skills/dependabot-purge/references/fix-matrix.md.tmpl +106 -0
- package/skills/dependabot-purge/scripts/ensure_docker.sh +33 -0
- package/skills/dependabot-purge/scripts/wait_checks.sh +66 -0
- package/skills/security-dependabot-fix/SKILL.md.tmpl +171 -0
|
@@ -0,0 +1,884 @@
|
|
|
1
|
+
// src/config/store.ts
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
3
|
+
import { homedir } from "os";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
|
|
6
|
+
// src/config/schema.ts
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
var LangSchema = z.enum(["fr", "en"]);
|
|
9
|
+
var ProviderIdSchema = z.enum(["claude", "kiro", "q"]);
|
|
10
|
+
var NotifyChannelSchema = z.enum(["none", "telegram", "slack"]);
|
|
11
|
+
var ExecutionSchema = z.object({
|
|
12
|
+
maxParallel: z.number().int().min(1).max(16).default(3),
|
|
13
|
+
model: z.string().default("opus"),
|
|
14
|
+
// 'opus' = latest Opus, resolved per provider (see resolveModel)
|
|
15
|
+
effort: z.string().default(""),
|
|
16
|
+
timeouts: z.object({
|
|
17
|
+
fix: z.number().int().positive().default(3600),
|
|
18
|
+
purge: z.number().int().positive().default(7200)
|
|
19
|
+
}).default({}),
|
|
20
|
+
maxTurns: z.object({
|
|
21
|
+
fix: z.number().int().positive().default(120),
|
|
22
|
+
purge: z.number().int().positive().default(220)
|
|
23
|
+
}).default({})
|
|
24
|
+
}).default({});
|
|
25
|
+
var ProviderConfigSchema = z.object({
|
|
26
|
+
default: ProviderIdSchema.default("claude"),
|
|
27
|
+
scheduled: ProviderIdSchema.default("claude")
|
|
28
|
+
}).default({});
|
|
29
|
+
var NotificationsSchema = z.object({
|
|
30
|
+
channel: NotifyChannelSchema.default("none"),
|
|
31
|
+
scheduledDigest: z.boolean().default(true),
|
|
32
|
+
perRepoError: z.boolean().default(false)
|
|
33
|
+
}).default({});
|
|
34
|
+
var ScheduleSchema = z.object({
|
|
35
|
+
enabled: z.boolean().default(false),
|
|
36
|
+
time: z.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/, "time must be HH:MM (24h)").default("10:00"),
|
|
37
|
+
phase: z.enum(["fix", "purge", "both"]).default("both"),
|
|
38
|
+
// Repos for the scheduled run; empty = every configured repo (`run --all`).
|
|
39
|
+
repos: z.array(z.string().min(1)).default([])
|
|
40
|
+
}).default({});
|
|
41
|
+
var ConfigSchema = z.object({
|
|
42
|
+
lang: LangSchema.default("en"),
|
|
43
|
+
org: z.string().min(1).default("mistertemp"),
|
|
44
|
+
repoBaseDir: z.string().min(1).default("~/Mistertemp/dev"),
|
|
45
|
+
packageScope: z.string().default("@mistertemp"),
|
|
46
|
+
baseBranch: z.string().min(1).default("dev"),
|
|
47
|
+
repos: z.array(z.string().min(1)).default([]),
|
|
48
|
+
provider: ProviderConfigSchema,
|
|
49
|
+
execution: ExecutionSchema,
|
|
50
|
+
notifications: NotificationsSchema,
|
|
51
|
+
schedule: ScheduleSchema
|
|
52
|
+
});
|
|
53
|
+
function defaultConfig() {
|
|
54
|
+
return ConfigSchema.parse({});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/config/store.ts
|
|
58
|
+
function configDir() {
|
|
59
|
+
const base = process.env.XDG_CONFIG_HOME?.trim() || join(homedir(), ".config");
|
|
60
|
+
return join(base, "misterpropre");
|
|
61
|
+
}
|
|
62
|
+
function configPath() {
|
|
63
|
+
return join(configDir(), "config.json");
|
|
64
|
+
}
|
|
65
|
+
function isConfigured() {
|
|
66
|
+
return existsSync(configPath());
|
|
67
|
+
}
|
|
68
|
+
function readConfig() {
|
|
69
|
+
const path = configPath();
|
|
70
|
+
if (!existsSync(path)) return null;
|
|
71
|
+
const raw = JSON.parse(readFileSync(path, "utf8"));
|
|
72
|
+
return ConfigSchema.parse(raw);
|
|
73
|
+
}
|
|
74
|
+
function writeConfig(config) {
|
|
75
|
+
const validated = ConfigSchema.parse(config);
|
|
76
|
+
mkdirSync(configDir(), { recursive: true });
|
|
77
|
+
writeFileSync(configPath(), JSON.stringify(validated, null, 2) + "\n", "utf8");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// src/skills/installer.ts
|
|
81
|
+
import { cpSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
82
|
+
import { homedir as homedir2 } from "os";
|
|
83
|
+
import { dirname, join as join2 } from "path";
|
|
84
|
+
import { fileURLToPath } from "url";
|
|
85
|
+
|
|
86
|
+
// src/skills/templating.ts
|
|
87
|
+
function renderTemplate(tmpl, vars) {
|
|
88
|
+
return tmpl.replace(/\{\{(\w+)\}\}/g, (_match, key) => {
|
|
89
|
+
if (!(key in vars)) throw new Error(`renderTemplate: missing variable {{${key}}}`);
|
|
90
|
+
return vars[key];
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// src/skills/installer.ts
|
|
95
|
+
var MANAGED_MARKER = "managed-by: misterpropre";
|
|
96
|
+
var FIX_INSTALL_NAME = "mrp-security-fix";
|
|
97
|
+
var PURGE_INSTALL_NAME = "mrp-dependabot-purge";
|
|
98
|
+
var BUNDLED_SKILLS = [
|
|
99
|
+
{ sourceName: "security-dependabot-fix", installName: FIX_INSTALL_NAME },
|
|
100
|
+
{ sourceName: "dependabot-purge", installName: PURGE_INSTALL_NAME }
|
|
101
|
+
];
|
|
102
|
+
function packageRoot() {
|
|
103
|
+
let dir = dirname(fileURLToPath(import.meta.url));
|
|
104
|
+
for (let i = 0; i < 10; i++) {
|
|
105
|
+
if (existsSync2(join2(dir, "package.json"))) return dir;
|
|
106
|
+
const parent = dirname(dir);
|
|
107
|
+
if (parent === dir) break;
|
|
108
|
+
dir = parent;
|
|
109
|
+
}
|
|
110
|
+
return dir;
|
|
111
|
+
}
|
|
112
|
+
function bundledSkillsRoot() {
|
|
113
|
+
return join2(packageRoot(), "skills");
|
|
114
|
+
}
|
|
115
|
+
function claudeSkillsRoot() {
|
|
116
|
+
return join2(homedir2(), ".claude", "skills");
|
|
117
|
+
}
|
|
118
|
+
function kiroSkillsRoot() {
|
|
119
|
+
return join2(homedir2(), ".kiro", "skills");
|
|
120
|
+
}
|
|
121
|
+
function skillsRootFor(provider) {
|
|
122
|
+
return provider === "kiro" ? kiroSkillsRoot() : claudeSkillsRoot();
|
|
123
|
+
}
|
|
124
|
+
function renderBundledSkill(installName, vars) {
|
|
125
|
+
const entry = BUNDLED_SKILLS.find((s) => s.installName === installName);
|
|
126
|
+
if (!entry) return null;
|
|
127
|
+
const tmpl = join2(bundledSkillsRoot(), entry.sourceName, "SKILL.md.tmpl");
|
|
128
|
+
if (!existsSync2(tmpl)) return null;
|
|
129
|
+
return renderTemplate(readFileSync2(tmpl, "utf8"), { ...vars, INSTALL_NAME: installName, MANAGED_MARKER });
|
|
130
|
+
}
|
|
131
|
+
function isManaged(skillMdPath) {
|
|
132
|
+
try {
|
|
133
|
+
return readFileSync2(skillMdPath, "utf8").includes(MANAGED_MARKER);
|
|
134
|
+
} catch {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function installTree(srcDir, destDir, vars) {
|
|
139
|
+
mkdirSync2(destDir, { recursive: true });
|
|
140
|
+
for (const entry of readdirSync(srcDir, { withFileTypes: true })) {
|
|
141
|
+
const src = join2(srcDir, entry.name);
|
|
142
|
+
if (entry.isDirectory()) {
|
|
143
|
+
installTree(src, join2(destDir, entry.name), vars);
|
|
144
|
+
} else if (entry.name.endsWith(".tmpl")) {
|
|
145
|
+
const rendered = renderTemplate(readFileSync2(src, "utf8"), vars);
|
|
146
|
+
writeFileSync2(join2(destDir, entry.name.slice(0, -".tmpl".length)), rendered, "utf8");
|
|
147
|
+
} else {
|
|
148
|
+
cpSync(src, join2(destDir, entry.name));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function installSkill(opts) {
|
|
153
|
+
const targetDir = join2(opts.targetRoot, opts.installName);
|
|
154
|
+
const targetSkillMd = join2(targetDir, "SKILL.md");
|
|
155
|
+
if (!existsSync2(join2(opts.sourceDir, "SKILL.md.tmpl"))) {
|
|
156
|
+
return { installName: opts.installName, path: "", action: "missing-source" };
|
|
157
|
+
}
|
|
158
|
+
const existed = existsSync2(targetSkillMd);
|
|
159
|
+
if (existed && !isManaged(targetSkillMd) && !opts.force) {
|
|
160
|
+
return { installName: opts.installName, path: targetDir, action: "skipped-unmanaged" };
|
|
161
|
+
}
|
|
162
|
+
installTree(opts.sourceDir, targetDir, { ...opts.vars, INSTALL_NAME: opts.installName, MANAGED_MARKER });
|
|
163
|
+
return { installName: opts.installName, path: targetDir, action: existed ? "updated" : "installed" };
|
|
164
|
+
}
|
|
165
|
+
function skillStatuses(targetRoot = claudeSkillsRoot()) {
|
|
166
|
+
return BUNDLED_SKILLS.map((s) => {
|
|
167
|
+
const bundled = existsSync2(join2(bundledSkillsRoot(), s.sourceName, "SKILL.md.tmpl"));
|
|
168
|
+
const installedMd = join2(targetRoot, s.installName, "SKILL.md");
|
|
169
|
+
const installed = existsSync2(installedMd);
|
|
170
|
+
return { installName: s.installName, bundled, installed, managed: installed && isManaged(installedMd) };
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
function installAllSkills(opts) {
|
|
174
|
+
const targetRoot = opts.targetRoot ?? claudeSkillsRoot();
|
|
175
|
+
return BUNDLED_SKILLS.map(
|
|
176
|
+
(s) => installSkill({
|
|
177
|
+
sourceDir: join2(bundledSkillsRoot(), s.sourceName),
|
|
178
|
+
installName: s.installName,
|
|
179
|
+
targetRoot,
|
|
180
|
+
vars: opts.vars,
|
|
181
|
+
force: opts.force
|
|
182
|
+
})
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// src/core/login.ts
|
|
187
|
+
import { execFileSync as execFileSync2, spawn } from "child_process";
|
|
188
|
+
import { createInterface } from "readline";
|
|
189
|
+
|
|
190
|
+
// src/lib/system.ts
|
|
191
|
+
import { execFileSync } from "child_process";
|
|
192
|
+
function commandVersion(cmd, args) {
|
|
193
|
+
try {
|
|
194
|
+
const out = execFileSync(cmd, args, {
|
|
195
|
+
encoding: "utf8",
|
|
196
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
197
|
+
});
|
|
198
|
+
return out.split("\n")[0]?.trim() || null;
|
|
199
|
+
} catch {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
var REQUIRED_TOOLS = [
|
|
204
|
+
{ label: "node", cmd: "node", args: ["--version"] },
|
|
205
|
+
{ label: "npm", cmd: "npm", args: ["--version"] },
|
|
206
|
+
{ label: "git", cmd: "git", args: ["--version"] },
|
|
207
|
+
{ label: "gh", cmd: "gh", args: ["--version"] }
|
|
208
|
+
];
|
|
209
|
+
function checkSystem() {
|
|
210
|
+
return REQUIRED_TOOLS.map(({ label, cmd, args }) => {
|
|
211
|
+
const version = commandVersion(cmd, args);
|
|
212
|
+
return { label, ok: version !== null, detail: version ?? "not found" };
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// src/core/login.ts
|
|
217
|
+
var BIN = { claude: "claude", kiro: "kiro-cli", q: "q" };
|
|
218
|
+
var LABEL = { claude: "Claude Code", kiro: "Kiro", q: "Amazon Q" };
|
|
219
|
+
var INSTALL_CMD = {
|
|
220
|
+
claude: "curl -fsSL https://claude.ai/install.sh | bash",
|
|
221
|
+
kiro: "curl -fsSL https://cli.kiro.dev/install | bash"
|
|
222
|
+
};
|
|
223
|
+
async function linkProvider(provider, deps) {
|
|
224
|
+
const label = LABEL[provider];
|
|
225
|
+
if (!deps.isInstalled(provider)) {
|
|
226
|
+
deps.log(`${label} CLI is not installed.`);
|
|
227
|
+
if (!deps.canInstall(provider)) {
|
|
228
|
+
deps.log(`Install ${label} yourself (see its docs), then re-run \`mrp login ${provider}\`.`);
|
|
229
|
+
return { provider, installed: false, loggedIn: false, message: `${label} not installed` };
|
|
230
|
+
}
|
|
231
|
+
const yes = await deps.confirm(`Install ${label} now?`);
|
|
232
|
+
if (!yes) {
|
|
233
|
+
deps.log(`${label} is required to continue.`);
|
|
234
|
+
return { provider, installed: false, loggedIn: false, message: `${label} install declined` };
|
|
235
|
+
}
|
|
236
|
+
const ok = await deps.install(provider);
|
|
237
|
+
if (!ok) return { provider, installed: false, loggedIn: false, message: `${label} install failed` };
|
|
238
|
+
}
|
|
239
|
+
if (deps.isLoggedIn(provider)) {
|
|
240
|
+
deps.log(`\u2713 ${label} already logged in.`);
|
|
241
|
+
return { provider, installed: true, loggedIn: true, message: `${label} already linked` };
|
|
242
|
+
}
|
|
243
|
+
deps.log(`Opening ${label} login\u2026`);
|
|
244
|
+
await deps.login(provider);
|
|
245
|
+
const loggedIn = deps.isLoggedIn(provider);
|
|
246
|
+
deps.log(loggedIn ? `\u2713 ${label} linked.` : `\u2717 ${label} login did not complete.`);
|
|
247
|
+
return {
|
|
248
|
+
provider,
|
|
249
|
+
installed: true,
|
|
250
|
+
loggedIn,
|
|
251
|
+
message: loggedIn ? `${label} linked` : `${label} login failed`
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
function spawnInteractive(command, args) {
|
|
255
|
+
return new Promise((resolve) => {
|
|
256
|
+
const child = spawn(command, args, { stdio: "inherit" });
|
|
257
|
+
child.on("error", () => resolve(-1));
|
|
258
|
+
child.on("close", (code) => resolve(code ?? -1));
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
function realLinkDeps(log = (m) => console.log(m)) {
|
|
262
|
+
const isInstalled = (provider) => commandVersion(BIN[provider], ["--version"]) !== null;
|
|
263
|
+
const isLoggedIn = (provider) => {
|
|
264
|
+
const args = provider === "claude" ? ["auth", "status"] : ["whoami"];
|
|
265
|
+
try {
|
|
266
|
+
execFileSync2(BIN[provider], args, { stdio: "ignore" });
|
|
267
|
+
return true;
|
|
268
|
+
} catch {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
return {
|
|
273
|
+
log,
|
|
274
|
+
isInstalled,
|
|
275
|
+
isLoggedIn,
|
|
276
|
+
canInstall: (provider) => INSTALL_CMD[provider] !== void 0,
|
|
277
|
+
confirm: async (message) => {
|
|
278
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
279
|
+
process.stdin.resume();
|
|
280
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
281
|
+
try {
|
|
282
|
+
const answer = await new Promise((res) => rl.question(`${message} [y/N] `, res));
|
|
283
|
+
return /^y(es)?$/i.test(answer.trim());
|
|
284
|
+
} finally {
|
|
285
|
+
rl.close();
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
install: async (provider) => {
|
|
289
|
+
const cmd = INSTALL_CMD[provider];
|
|
290
|
+
if (!cmd) {
|
|
291
|
+
log(`Install ${LABEL[provider]} first (see its docs), then re-run \`mrp login ${provider}\`.`);
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
log(`Installing ${LABEL[provider]}\u2026`);
|
|
295
|
+
await spawnInteractive("bash", ["-c", cmd]);
|
|
296
|
+
return isInstalled(provider);
|
|
297
|
+
},
|
|
298
|
+
login: async (provider) => {
|
|
299
|
+
const args = provider === "claude" ? ["auth", "login"] : ["login"];
|
|
300
|
+
await spawnInteractive(BIN[provider], args);
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// src/core/github.ts
|
|
306
|
+
import { execFile } from "child_process";
|
|
307
|
+
import { promisify } from "util";
|
|
308
|
+
var pexec = promisify(execFile);
|
|
309
|
+
var AUTOMATION_BRANCH_PREFIX = "fix/dependabot-security-";
|
|
310
|
+
var isAutomationBranch = (ref) => ref.startsWith(AUTOMATION_BRANCH_PREFIX);
|
|
311
|
+
function parseNdjson(s) {
|
|
312
|
+
const out = [];
|
|
313
|
+
for (const line of s.split("\n")) {
|
|
314
|
+
const t = line.trim();
|
|
315
|
+
if (!t) continue;
|
|
316
|
+
try {
|
|
317
|
+
out.push(JSON.parse(t));
|
|
318
|
+
} catch {
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return out;
|
|
322
|
+
}
|
|
323
|
+
function parseJsonArray(s) {
|
|
324
|
+
try {
|
|
325
|
+
const v = JSON.parse(s);
|
|
326
|
+
return Array.isArray(v) ? v : [];
|
|
327
|
+
} catch {
|
|
328
|
+
return [];
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
var GhCli = class {
|
|
332
|
+
constructor(org, env = process.env) {
|
|
333
|
+
this.org = org;
|
|
334
|
+
this.env = env;
|
|
335
|
+
}
|
|
336
|
+
org;
|
|
337
|
+
env;
|
|
338
|
+
async gh(args) {
|
|
339
|
+
const { stdout } = await pexec("gh", args, {
|
|
340
|
+
env: this.env,
|
|
341
|
+
encoding: "utf8",
|
|
342
|
+
maxBuffer: 64 * 1024 * 1024
|
|
343
|
+
});
|
|
344
|
+
return stdout;
|
|
345
|
+
}
|
|
346
|
+
async listOpenAlerts(repo) {
|
|
347
|
+
const jq = ".[] | {number, severity: .security_advisory.severity, ghsaId: .security_advisory.ghsa_id, cveId: .security_advisory.cve_id, package: .dependency.package.name}";
|
|
348
|
+
const out = await this.gh([
|
|
349
|
+
"api",
|
|
350
|
+
`repos/${this.org}/${repo}/dependabot/alerts?state=open&per_page=100`,
|
|
351
|
+
"--paginate",
|
|
352
|
+
"--jq",
|
|
353
|
+
jq
|
|
354
|
+
]);
|
|
355
|
+
return parseNdjson(out);
|
|
356
|
+
}
|
|
357
|
+
async listAutomationPRs(repo) {
|
|
358
|
+
const out = await this.gh([
|
|
359
|
+
"pr",
|
|
360
|
+
"list",
|
|
361
|
+
"--repo",
|
|
362
|
+
`${this.org}/${repo}`,
|
|
363
|
+
"--state",
|
|
364
|
+
"open",
|
|
365
|
+
"--json",
|
|
366
|
+
"number,url,headRefName,headRefOid,state"
|
|
367
|
+
]);
|
|
368
|
+
return parseJsonArray(out).filter((p) => typeof p.headRefName === "string" && isAutomationBranch(p.headRefName)).map((p) => ({
|
|
369
|
+
number: p.number,
|
|
370
|
+
url: p.url,
|
|
371
|
+
headRefName: p.headRefName,
|
|
372
|
+
headSha: p.headRefOid,
|
|
373
|
+
state: p.state
|
|
374
|
+
}));
|
|
375
|
+
}
|
|
376
|
+
async listBotPRs(repo) {
|
|
377
|
+
const fields = "number,url,headRefName,state,title,labels";
|
|
378
|
+
const byAuthor = async (author) => {
|
|
379
|
+
const out2 = await this.gh([
|
|
380
|
+
"pr",
|
|
381
|
+
"list",
|
|
382
|
+
"--repo",
|
|
383
|
+
`${this.org}/${repo}`,
|
|
384
|
+
"--author",
|
|
385
|
+
author,
|
|
386
|
+
"--state",
|
|
387
|
+
"open",
|
|
388
|
+
"--json",
|
|
389
|
+
fields
|
|
390
|
+
]);
|
|
391
|
+
return parseJsonArray(out2).map((p) => ({
|
|
392
|
+
number: p.number,
|
|
393
|
+
url: p.url,
|
|
394
|
+
headRefName: p.headRefName,
|
|
395
|
+
state: p.state,
|
|
396
|
+
title: p.title,
|
|
397
|
+
labels: (p.labels ?? []).map((l) => l.name)
|
|
398
|
+
}));
|
|
399
|
+
};
|
|
400
|
+
const [renovate, dependabot] = await Promise.all([byAuthor("app/renovate"), byAuthor("app/dependabot")]);
|
|
401
|
+
const seen = /* @__PURE__ */ new Set();
|
|
402
|
+
const out = [];
|
|
403
|
+
for (const p of [...renovate, ...dependabot]) {
|
|
404
|
+
if (seen.has(p.number)) continue;
|
|
405
|
+
seen.add(p.number);
|
|
406
|
+
out.push(p);
|
|
407
|
+
}
|
|
408
|
+
return out;
|
|
409
|
+
}
|
|
410
|
+
async pullState(repo, n) {
|
|
411
|
+
try {
|
|
412
|
+
const out = (await this.gh(["pr", "view", String(n), "--repo", `${this.org}/${repo}`, "--json", "state", "--jq", ".state"])).trim();
|
|
413
|
+
return out === "OPEN" || out === "MERGED" || out === "CLOSED" ? out : "UNKNOWN";
|
|
414
|
+
} catch {
|
|
415
|
+
return "UNKNOWN";
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
async function fetchOrgRepoNames(org, env = process.env) {
|
|
420
|
+
try {
|
|
421
|
+
const { stdout } = await pexec(
|
|
422
|
+
"gh",
|
|
423
|
+
["repo", "list", org, "--json", "name", "--limit", "300", "--jq", ".[].name"],
|
|
424
|
+
{ env, encoding: "utf8", maxBuffer: 16 * 1024 * 1024 }
|
|
425
|
+
);
|
|
426
|
+
return stdout.split("\n").map((s) => s.trim()).filter(Boolean);
|
|
427
|
+
} catch {
|
|
428
|
+
return [];
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// src/ui/theme.ts
|
|
433
|
+
var theme = {
|
|
434
|
+
primary: "cyan",
|
|
435
|
+
accent: "magenta",
|
|
436
|
+
success: "green",
|
|
437
|
+
warn: "yellow",
|
|
438
|
+
error: "red",
|
|
439
|
+
dim: "gray",
|
|
440
|
+
text: "white"
|
|
441
|
+
};
|
|
442
|
+
var wordmarkGradient = ["#7aa2f7", "#bb9af7"];
|
|
443
|
+
|
|
444
|
+
// src/core/history.ts
|
|
445
|
+
import { appendFileSync, existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3 } from "fs";
|
|
446
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
447
|
+
import { z as z2 } from "zod";
|
|
448
|
+
var RepoOutcomeSchema = z2.object({
|
|
449
|
+
repo: z2.string(),
|
|
450
|
+
phase: z2.enum(["fix", "purge"]),
|
|
451
|
+
kind: z2.string(),
|
|
452
|
+
prUrl: z2.string().optional(),
|
|
453
|
+
detail: z2.string().optional(),
|
|
454
|
+
hintAgreed: z2.boolean().optional(),
|
|
455
|
+
durationMs: z2.number().optional(),
|
|
456
|
+
// structured counts for own-runs stats
|
|
457
|
+
fixed: z2.number().int().nonnegative().optional(),
|
|
458
|
+
// vulns the fix PR addresses (skill RESULT hint)
|
|
459
|
+
merged: z2.number().int().nonnegative().optional(),
|
|
460
|
+
// bot PRs merged (authoritative)
|
|
461
|
+
handedOff: z2.number().int().nonnegative().optional(),
|
|
462
|
+
stillOpen: z2.number().int().nonnegative().optional()
|
|
463
|
+
});
|
|
464
|
+
var RunRecordSchema = z2.object({
|
|
465
|
+
id: z2.string(),
|
|
466
|
+
startedAt: z2.string(),
|
|
467
|
+
finishedAt: z2.string(),
|
|
468
|
+
trigger: z2.enum(["manual", "scheduled"]),
|
|
469
|
+
provider: z2.string(),
|
|
470
|
+
phases: z2.array(z2.enum(["fix", "purge"])),
|
|
471
|
+
repos: z2.array(RepoOutcomeSchema)
|
|
472
|
+
});
|
|
473
|
+
function runsPath() {
|
|
474
|
+
return join3(configDir(), "runs.jsonl");
|
|
475
|
+
}
|
|
476
|
+
function appendRun(record) {
|
|
477
|
+
const validated = RunRecordSchema.parse(record);
|
|
478
|
+
mkdirSync3(dirname2(runsPath()), { recursive: true });
|
|
479
|
+
appendFileSync(runsPath(), JSON.stringify(validated) + "\n", "utf8");
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// src/core/stats.ts
|
|
483
|
+
var EN_STATUS = {
|
|
484
|
+
prOpened: "security PR opened \u2014 awaiting review",
|
|
485
|
+
prUpToDate: "PR already up to date",
|
|
486
|
+
noAlerts: "no open alerts",
|
|
487
|
+
noFixable: "no auto-fixable alerts",
|
|
488
|
+
skippedBusy: "skipped \u2014 another run held it",
|
|
489
|
+
leftLocal: "fix left local",
|
|
490
|
+
failed: "failed",
|
|
491
|
+
error: "error",
|
|
492
|
+
merged: (m, h) => `merged ${m}, handed off ${h}`
|
|
493
|
+
};
|
|
494
|
+
function summarizeOutcome(o, t = EN_STATUS) {
|
|
495
|
+
const base = { repo: o.repo, phase: o.phase, kind: o.kind, prUrl: o.prUrl };
|
|
496
|
+
switch (o.kind) {
|
|
497
|
+
case "NEW_PR":
|
|
498
|
+
case "UPDATED_PR":
|
|
499
|
+
return { ...base, status: t.prOpened, ok: true };
|
|
500
|
+
case "PR_UP_TO_DATE":
|
|
501
|
+
return { ...base, status: t.prUpToDate, ok: true };
|
|
502
|
+
case "NO_ALERTS":
|
|
503
|
+
return { ...base, status: t.noAlerts, ok: true };
|
|
504
|
+
case "NO_FIXABLE":
|
|
505
|
+
return { ...base, status: t.noFixable, ok: true };
|
|
506
|
+
case "DONE":
|
|
507
|
+
return { ...base, status: t.merged(o.merged ?? 0, o.handedOff ?? 0), ok: true };
|
|
508
|
+
case "SKIPPED_BUSY":
|
|
509
|
+
return { ...base, status: t.skippedBusy, ok: true };
|
|
510
|
+
case "LEFT_LOCAL":
|
|
511
|
+
return { ...base, status: o.detail ? `${t.leftLocal} (${o.detail})` : t.leftLocal, ok: false };
|
|
512
|
+
case "ERROR":
|
|
513
|
+
return { ...base, status: o.detail ? `${t.error}: ${o.detail}` : t.error, ok: false };
|
|
514
|
+
case "FAIL":
|
|
515
|
+
return { ...base, status: o.detail ? `${t.failed}: ${o.detail}` : t.failed, ok: false };
|
|
516
|
+
default:
|
|
517
|
+
return { ...base, status: o.detail ?? o.kind.toLowerCase(), ok: true };
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// src/ui/i18n.ts
|
|
522
|
+
var en = {
|
|
523
|
+
orgPrompt: "Enter the GitHub organisation that owns the repositories you\u2019ll automate.",
|
|
524
|
+
baseDirPrompt: "Choose where your repositories are cloned locally.",
|
|
525
|
+
useCurrentDir: "Use the current directory",
|
|
526
|
+
enterPath: "Enter a different path\u2026",
|
|
527
|
+
pathPrompt: "Type the full path to the folder where your repositories are cloned.",
|
|
528
|
+
providerPrompt: "Which agent should run the automation?",
|
|
529
|
+
providerLoginNote: "You\u2019ll be signed in to the agent\u2019s CLI right after you choose.",
|
|
530
|
+
providerFound: "is installed.",
|
|
531
|
+
providerMissing: "CLI not found on PATH \u2014 install it before running.",
|
|
532
|
+
providerAuthPrompt: "Add a durable token for scheduled/unattended runs? Interactive runs use your existing login.",
|
|
533
|
+
skipLogin: "Skip \u2014 use my existing login",
|
|
534
|
+
addToken: "Add a token",
|
|
535
|
+
claudeTokenPrompt: "Paste your Claude Code OAuth token (hidden as you type).",
|
|
536
|
+
kiroTokenPrompt: "Paste your Kiro API key (hidden as you type).",
|
|
537
|
+
reposPrompt: "Select the repositories to automate.",
|
|
538
|
+
reposLoading: "Loading repositories\u2026",
|
|
539
|
+
reposEmpty: "No repositories found (check `gh auth status`). You can add them later in config.",
|
|
540
|
+
reposHint: "type to search \xB7 space to toggle \xB7 enter to confirm",
|
|
541
|
+
continueLabel: "Continue",
|
|
542
|
+
skillsInstalled: "Skills installed:",
|
|
543
|
+
done: "Setup complete. Run mrp doctor to verify, then a fix run.",
|
|
544
|
+
editTitle: "Configuration",
|
|
545
|
+
editHint: "Select a setting to change it \u2014 saved as you go.",
|
|
546
|
+
editDone: "Done",
|
|
547
|
+
labels: {
|
|
548
|
+
language: "Language",
|
|
549
|
+
org: "Org",
|
|
550
|
+
repoDir: "Repo dir",
|
|
551
|
+
provider: "Provider",
|
|
552
|
+
repos: "Repos",
|
|
553
|
+
saved: "\u2713 Saved"
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
var fr = {
|
|
557
|
+
orgPrompt: "Saisissez l\u2019organisation GitHub propri\xE9taire des repos \xE0 automatiser.",
|
|
558
|
+
baseDirPrompt: "Choisissez l\u2019emplacement o\xF9 vos repos sont clon\xE9s localement.",
|
|
559
|
+
useCurrentDir: "Utiliser le dossier actuel",
|
|
560
|
+
enterPath: "Saisir un autre chemin\u2026",
|
|
561
|
+
pathPrompt: "Saisissez le chemin complet du dossier o\xF9 vos repos sont clon\xE9s.",
|
|
562
|
+
providerPrompt: "Quel agent doit ex\xE9cuter l\u2019automatisation ?",
|
|
563
|
+
providerLoginNote: "Vous serez connect\xE9 \xE0 la CLI de l\u2019agent juste apr\xE8s votre choix.",
|
|
564
|
+
providerFound: "est install\xE9.",
|
|
565
|
+
providerMissing: "CLI introuvable dans le PATH \u2014 installez-le avant de lancer.",
|
|
566
|
+
providerAuthPrompt: "Ajouter un token durable pour les runs planifi\xE9s/non supervis\xE9s ? Les runs interactifs utilisent votre session.",
|
|
567
|
+
skipLogin: "Ignorer \u2014 utiliser ma session",
|
|
568
|
+
addToken: "Ajouter un token",
|
|
569
|
+
claudeTokenPrompt: "Collez votre token OAuth Claude Code (masqu\xE9 pendant la saisie).",
|
|
570
|
+
kiroTokenPrompt: "Collez votre cl\xE9 API Kiro (masqu\xE9e pendant la saisie).",
|
|
571
|
+
reposPrompt: "S\xE9lectionnez les repos \xE0 automatiser.",
|
|
572
|
+
reposLoading: "Chargement des repos\u2026",
|
|
573
|
+
reposEmpty: "Aucun repo trouv\xE9 (v\xE9rifiez `gh auth status`). Vous pourrez les ajouter plus tard.",
|
|
574
|
+
reposHint: "tapez pour filtrer \xB7 espace pour cocher \xB7 entr\xE9e pour valider",
|
|
575
|
+
continueLabel: "Continuer",
|
|
576
|
+
skillsInstalled: "Skills install\xE9s :",
|
|
577
|
+
done: "Configuration termin\xE9e. Lancez mrp doctor pour v\xE9rifier, puis un run.",
|
|
578
|
+
editTitle: "Configuration",
|
|
579
|
+
editHint: "S\xE9lectionnez une option \xE0 modifier (enregistr\xE9e automatiquement).",
|
|
580
|
+
editDone: "Valider",
|
|
581
|
+
labels: {
|
|
582
|
+
language: "Langue",
|
|
583
|
+
org: "Org",
|
|
584
|
+
repoDir: "Dossier",
|
|
585
|
+
provider: "Agent",
|
|
586
|
+
repos: "Repos",
|
|
587
|
+
saved: "\u2713 Enregistr\xE9"
|
|
588
|
+
}
|
|
589
|
+
};
|
|
590
|
+
var messages = { en, fr };
|
|
591
|
+
function wizardT(lang) {
|
|
592
|
+
return messages[lang];
|
|
593
|
+
}
|
|
594
|
+
var enApp = {
|
|
595
|
+
tagline: "Security fixes + Renovate/Dependabot triage, on autopilot",
|
|
596
|
+
actions: "Actions",
|
|
597
|
+
actionRunSecurity: "Run security fix",
|
|
598
|
+
actionRunDependabot: "Run Dependabot purge",
|
|
599
|
+
actionRunBoth: "Run both (purge \u2192 fix)",
|
|
600
|
+
actionSchedule: "Schedule daily run",
|
|
601
|
+
actionScheduleEdit: "Configure daily run",
|
|
602
|
+
actionSetup: "Setup / reconfigure",
|
|
603
|
+
actionQuit: "Quit",
|
|
604
|
+
mode: "Mode",
|
|
605
|
+
modeAll: "All repos",
|
|
606
|
+
modeSingle: "Single repo",
|
|
607
|
+
modeSwitchHint: "Ctrl+Tab to switch mode (Single / Multiple repo)",
|
|
608
|
+
chooseRepo: "Choose a repo to run on",
|
|
609
|
+
noReposConfigured: "No repos configured \u2014 add some in the configuration.",
|
|
610
|
+
escBack: "Esc to go back",
|
|
611
|
+
runDone: "Run finished",
|
|
612
|
+
returnToMain: "Return to main screen",
|
|
613
|
+
handoffsHeader: (n) => `${n} PR(s) handed off \u2014 manual review needed:`,
|
|
614
|
+
schedule: {
|
|
615
|
+
title: "Schedule daily run",
|
|
616
|
+
reposPrompt: "Repos to include",
|
|
617
|
+
reposHint: "space to toggle \xB7 enter to confirm \xB7 none = all repos",
|
|
618
|
+
timePrompt: "Daily trigger time (HH:MM, 24h)",
|
|
619
|
+
timeLabel: "time",
|
|
620
|
+
timeInvalid: "Invalid time \u2014 use HH:MM (24h).",
|
|
621
|
+
modify: "Edit the schedule",
|
|
622
|
+
disable: "Disable daily run",
|
|
623
|
+
cancel: "Cancel",
|
|
624
|
+
noRepos: "No repos configured \u2014 add some in the configuration first.",
|
|
625
|
+
active: (time) => `Scheduled run active (${time})`,
|
|
626
|
+
setOk: (time, n) => `Daily run scheduled at ${time} \xB7 ${n} repo(s)`,
|
|
627
|
+
cleared: "Daily run disabled."
|
|
628
|
+
},
|
|
629
|
+
grid: {
|
|
630
|
+
queued: "queued",
|
|
631
|
+
purging: "Processing renovate/dependabot PRs",
|
|
632
|
+
fixing: "Fixing security issues",
|
|
633
|
+
done: "done",
|
|
634
|
+
failed: "failed",
|
|
635
|
+
skipped: "skipped"
|
|
636
|
+
},
|
|
637
|
+
status: EN_STATUS
|
|
638
|
+
};
|
|
639
|
+
var frStatus = {
|
|
640
|
+
prOpened: "PR de s\xE9curit\xE9 ouverte \u2014 en attente de revue",
|
|
641
|
+
prUpToDate: "PR d\xE9j\xE0 \xE0 jour",
|
|
642
|
+
noAlerts: "aucune alerte ouverte",
|
|
643
|
+
noFixable: "aucune alerte corrigeable automatiquement",
|
|
644
|
+
skippedBusy: "ignor\xE9 \u2014 une autre ex\xE9cution la traitait",
|
|
645
|
+
leftLocal: "correctif laiss\xE9 en local",
|
|
646
|
+
failed: "\xE9chec",
|
|
647
|
+
error: "erreur",
|
|
648
|
+
merged: (m, h) => `${m} merg\xE9e(s), ${h} \xE0 revoir`
|
|
649
|
+
};
|
|
650
|
+
var frApp = {
|
|
651
|
+
tagline: "Correctifs de s\xE9curit\xE9 et PR Renovate/Dependabot, automatis\xE9s",
|
|
652
|
+
actions: "Actions",
|
|
653
|
+
actionRunSecurity: "Traiter les alertes de s\xE9curit\xE9",
|
|
654
|
+
actionRunDependabot: "Traiter les PR renovate/dependabot",
|
|
655
|
+
actionRunBoth: "Lancer un traitement complet (s\xE9curit\xE9 + renovate/dependabot)",
|
|
656
|
+
actionSchedule: "Planifier un run quotidien",
|
|
657
|
+
actionScheduleEdit: "Configurer le run quotidien",
|
|
658
|
+
actionSetup: "Modifier la configuration",
|
|
659
|
+
actionQuit: "Quitter",
|
|
660
|
+
mode: "Mode",
|
|
661
|
+
modeAll: "Tous les repos",
|
|
662
|
+
modeSingle: "Repo unique",
|
|
663
|
+
modeSwitchHint: "Ctrl+Tab pour changer de mode (Seul / Multiple repo)",
|
|
664
|
+
chooseRepo: "Choisissez le repo \xE0 traiter",
|
|
665
|
+
noReposConfigured: "Aucun repo configur\xE9 \u2014 ajoutez-en dans la configuration.",
|
|
666
|
+
escBack: "\xC9chap pour revenir",
|
|
667
|
+
runDone: "Ex\xE9cution termin\xE9e",
|
|
668
|
+
returnToMain: "Revenir \xE0 l\u2019\xE9cran principal",
|
|
669
|
+
handoffsHeader: (n) => `${n} PR \xE0 revoir manuellement :`,
|
|
670
|
+
schedule: {
|
|
671
|
+
title: "Planifier un run quotidien",
|
|
672
|
+
reposPrompt: "Repos concern\xE9s",
|
|
673
|
+
reposHint: "espace pour cocher \xB7 entr\xE9e pour valider \xB7 aucun = tous les repos",
|
|
674
|
+
timePrompt: "Heure de d\xE9clenchement (HH:MM, 24 h)",
|
|
675
|
+
timeLabel: "heure",
|
|
676
|
+
timeInvalid: "Heure invalide \u2014 format HH:MM (24 h).",
|
|
677
|
+
modify: "Modifier la planification",
|
|
678
|
+
disable: "D\xE9sactiver le run quotidien",
|
|
679
|
+
cancel: "Annuler",
|
|
680
|
+
noRepos: "Aucun repo configur\xE9 \u2014 ajoutez-en d\u2019abord dans la configuration.",
|
|
681
|
+
active: (time) => `Run quotidien planifi\xE9 (${time})`,
|
|
682
|
+
setOk: (time, n) => `Run quotidien planifi\xE9 \xE0 ${time} \xB7 ${n} repo(s)`,
|
|
683
|
+
cleared: "Run quotidien d\xE9sactiv\xE9."
|
|
684
|
+
},
|
|
685
|
+
grid: {
|
|
686
|
+
queued: "en file",
|
|
687
|
+
purging: "Traitement des PR renovate/dependabot",
|
|
688
|
+
fixing: "R\xE9solution des probl\xE8mes de s\xE9curit\xE9",
|
|
689
|
+
done: "termin\xE9",
|
|
690
|
+
failed: "\xE9chec",
|
|
691
|
+
skipped: "ignor\xE9"
|
|
692
|
+
},
|
|
693
|
+
status: frStatus
|
|
694
|
+
};
|
|
695
|
+
var appMessages = { en: enApp, fr: frApp };
|
|
696
|
+
function appT(lang) {
|
|
697
|
+
return appMessages[lang];
|
|
698
|
+
}
|
|
699
|
+
function phaseLabel(phase, lang) {
|
|
700
|
+
if (phase === "fix") return lang === "fr" ? "s\xE9curit\xE9" : "security";
|
|
701
|
+
if (phase === "purge") return "renovate/dependabot";
|
|
702
|
+
return phase;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// src/ui/components.tsx
|
|
706
|
+
import { useEffect, useState } from "react";
|
|
707
|
+
import { Box, Text, useInput } from "ink";
|
|
708
|
+
import Gradient from "ink-gradient";
|
|
709
|
+
import BigText from "ink-big-text";
|
|
710
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
711
|
+
function Banner({ lang = "en" } = {}) {
|
|
712
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
713
|
+
/* @__PURE__ */ jsx(Gradient, { colors: wordmarkGradient, children: /* @__PURE__ */ jsx(BigText, { text: "mister", font: "block", space: false }) }),
|
|
714
|
+
/* @__PURE__ */ jsx(Box, { marginTop: -4, children: /* @__PURE__ */ jsx(Gradient, { colors: wordmarkGradient, children: /* @__PURE__ */ jsx(BigText, { text: "propre", font: "block", space: false }) }) }),
|
|
715
|
+
/* @__PURE__ */ jsx(Text, { color: theme.dim, children: appT(lang).tagline })
|
|
716
|
+
] });
|
|
717
|
+
}
|
|
718
|
+
function Panel({
|
|
719
|
+
title,
|
|
720
|
+
color = theme.primary,
|
|
721
|
+
children
|
|
722
|
+
}) {
|
|
723
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: color, paddingX: 1, marginBottom: 1, children: [
|
|
724
|
+
/* @__PURE__ */ jsx(Box, { marginTop: -1, children: /* @__PURE__ */ jsx(Text, { backgroundColor: color, color: "black", bold: true, children: ` ${title} ` }) }),
|
|
725
|
+
children
|
|
726
|
+
] });
|
|
727
|
+
}
|
|
728
|
+
function KeyVal({ label, value }) {
|
|
729
|
+
return /* @__PURE__ */ jsxs(Text, { children: [
|
|
730
|
+
/* @__PURE__ */ jsxs(Text, { color: theme.success, bold: true, children: [
|
|
731
|
+
label,
|
|
732
|
+
":"
|
|
733
|
+
] }),
|
|
734
|
+
" ",
|
|
735
|
+
value
|
|
736
|
+
] });
|
|
737
|
+
}
|
|
738
|
+
function Answered({ label, value }) {
|
|
739
|
+
return /* @__PURE__ */ jsxs(Text, { children: [
|
|
740
|
+
/* @__PURE__ */ jsx(Text, { color: theme.success, children: "\u2713" }),
|
|
741
|
+
" ",
|
|
742
|
+
/* @__PURE__ */ jsxs(Text, { color: theme.dim, children: [
|
|
743
|
+
label,
|
|
744
|
+
":"
|
|
745
|
+
] }),
|
|
746
|
+
" ",
|
|
747
|
+
value
|
|
748
|
+
] });
|
|
749
|
+
}
|
|
750
|
+
var isPrintable = (s) => [...s].every((c) => c.charCodeAt(0) >= 32);
|
|
751
|
+
function Select({
|
|
752
|
+
items,
|
|
753
|
+
onSelect,
|
|
754
|
+
onHighlight
|
|
755
|
+
}) {
|
|
756
|
+
const [index, setIndex] = useState(0);
|
|
757
|
+
useEffect(() => {
|
|
758
|
+
onHighlight?.(items[0].value);
|
|
759
|
+
}, []);
|
|
760
|
+
useInput((_input, key) => {
|
|
761
|
+
if (key.return) {
|
|
762
|
+
onSelect(items[index].value);
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
const delta = key.upArrow ? -1 : key.downArrow ? 1 : 0;
|
|
766
|
+
if (delta === 0) return;
|
|
767
|
+
const next = (index + delta + items.length) % items.length;
|
|
768
|
+
setIndex(next);
|
|
769
|
+
onHighlight?.(items[next].value);
|
|
770
|
+
});
|
|
771
|
+
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: items.map((it, i) => /* @__PURE__ */ jsxs(Text, { color: i === index ? theme.primary : theme.text, children: [
|
|
772
|
+
i === index ? "\u276F " : " ",
|
|
773
|
+
it.label
|
|
774
|
+
] }, it.value)) });
|
|
775
|
+
}
|
|
776
|
+
function MultiSelect({
|
|
777
|
+
items,
|
|
778
|
+
hint,
|
|
779
|
+
initial,
|
|
780
|
+
onConfirm
|
|
781
|
+
}) {
|
|
782
|
+
const [query, setQuery] = useState("");
|
|
783
|
+
const [cursor, setCursor] = useState(0);
|
|
784
|
+
const [selected, setSelected] = useState(new Set(initial ?? []));
|
|
785
|
+
const filtered = items.filter((it) => it.toLowerCase().includes(query.toLowerCase()));
|
|
786
|
+
const cur = Math.min(cursor, Math.max(0, filtered.length - 1));
|
|
787
|
+
const WINDOW = 8;
|
|
788
|
+
const start = Math.max(0, Math.min(cur - Math.floor(WINDOW / 2), Math.max(0, filtered.length - WINDOW)));
|
|
789
|
+
const view = filtered.slice(start, start + WINDOW);
|
|
790
|
+
useInput((input, key) => {
|
|
791
|
+
if (key.return) {
|
|
792
|
+
onConfirm([...selected]);
|
|
793
|
+
} else if (key.upArrow) {
|
|
794
|
+
setCursor(Math.max(0, cur - 1));
|
|
795
|
+
} else if (key.downArrow) {
|
|
796
|
+
setCursor(Math.min(filtered.length - 1, cur + 1));
|
|
797
|
+
} else if (input === " ") {
|
|
798
|
+
const it = filtered[cur];
|
|
799
|
+
if (it) {
|
|
800
|
+
const next = new Set(selected);
|
|
801
|
+
if (next.has(it)) next.delete(it);
|
|
802
|
+
else next.add(it);
|
|
803
|
+
setSelected(next);
|
|
804
|
+
}
|
|
805
|
+
} else if (key.backspace || key.delete) {
|
|
806
|
+
setQuery((q) => q.slice(0, -1));
|
|
807
|
+
setCursor(0);
|
|
808
|
+
} else if (input && input !== " " && !key.ctrl && !key.meta && isPrintable(input)) {
|
|
809
|
+
setQuery((q) => q + input);
|
|
810
|
+
setCursor(0);
|
|
811
|
+
}
|
|
812
|
+
});
|
|
813
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
814
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
815
|
+
/* @__PURE__ */ jsx(Text, { color: theme.dim, children: "search: " }),
|
|
816
|
+
/* @__PURE__ */ jsx(Text, { children: query || " " })
|
|
817
|
+
] }),
|
|
818
|
+
view.map((it, i) => {
|
|
819
|
+
const isCur = start + i === cur;
|
|
820
|
+
return /* @__PURE__ */ jsxs(Text, { color: isCur ? theme.primary : theme.text, children: [
|
|
821
|
+
isCur ? "\u276F " : " ",
|
|
822
|
+
selected.has(it) ? "\u25C9" : "\u25EF",
|
|
823
|
+
" ",
|
|
824
|
+
it
|
|
825
|
+
] }, it);
|
|
826
|
+
}),
|
|
827
|
+
/* @__PURE__ */ jsxs(Text, { color: theme.dim, children: [
|
|
828
|
+
filtered.length,
|
|
829
|
+
" match",
|
|
830
|
+
filtered.length === 1 ? "" : "es",
|
|
831
|
+
" \xB7 ",
|
|
832
|
+
selected.size,
|
|
833
|
+
" selected",
|
|
834
|
+
hint ? ` \xB7 ${hint}` : ""
|
|
835
|
+
] })
|
|
836
|
+
] });
|
|
837
|
+
}
|
|
838
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
839
|
+
function Spinner({ color = theme.primary }) {
|
|
840
|
+
const [frame, setFrame] = useState(0);
|
|
841
|
+
useEffect(() => {
|
|
842
|
+
const id = setInterval(() => setFrame((f) => (f + 1) % SPINNER_FRAMES.length), 80);
|
|
843
|
+
return () => clearInterval(id);
|
|
844
|
+
}, []);
|
|
845
|
+
return /* @__PURE__ */ jsx(Text, { color, children: SPINNER_FRAMES[frame] });
|
|
846
|
+
}
|
|
847
|
+
function Chips({ items, color = theme.primary }) {
|
|
848
|
+
return /* @__PURE__ */ jsx(Box, { flexWrap: "wrap", marginTop: 1, children: items.map((it) => /* @__PURE__ */ jsx(Box, { marginRight: 1, children: /* @__PURE__ */ jsx(Text, { backgroundColor: color, color: "black", children: ` ${it} ` }) }, it)) });
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
export {
|
|
852
|
+
checkSystem,
|
|
853
|
+
defaultConfig,
|
|
854
|
+
configDir,
|
|
855
|
+
configPath,
|
|
856
|
+
isConfigured,
|
|
857
|
+
readConfig,
|
|
858
|
+
writeConfig,
|
|
859
|
+
FIX_INSTALL_NAME,
|
|
860
|
+
PURGE_INSTALL_NAME,
|
|
861
|
+
skillsRootFor,
|
|
862
|
+
renderBundledSkill,
|
|
863
|
+
skillStatuses,
|
|
864
|
+
installAllSkills,
|
|
865
|
+
GhCli,
|
|
866
|
+
fetchOrgRepoNames,
|
|
867
|
+
theme,
|
|
868
|
+
appendRun,
|
|
869
|
+
summarizeOutcome,
|
|
870
|
+
wizardT,
|
|
871
|
+
appT,
|
|
872
|
+
phaseLabel,
|
|
873
|
+
Banner,
|
|
874
|
+
Panel,
|
|
875
|
+
KeyVal,
|
|
876
|
+
Answered,
|
|
877
|
+
Select,
|
|
878
|
+
MultiSelect,
|
|
879
|
+
Spinner,
|
|
880
|
+
Chips,
|
|
881
|
+
linkProvider,
|
|
882
|
+
realLinkDeps
|
|
883
|
+
};
|
|
884
|
+
//# sourceMappingURL=chunk-HMGF6JWH.js.map
|