snapfail 0.0.25 → 0.0.27
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/dist/index.js +1564 -1012
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -34,8 +34,8 @@ var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports,
|
|
|
34
34
|
|
|
35
35
|
// ../../node_modules/.bun/sisteransi@1.0.5/node_modules/sisteransi/src/index.js
|
|
36
36
|
var require_src = __commonJS((exports, module) => {
|
|
37
|
-
var
|
|
38
|
-
var CSI2 = `${
|
|
37
|
+
var ESC2 = "\x1B";
|
|
38
|
+
var CSI2 = `${ESC2}[`;
|
|
39
39
|
var beep = "\x07";
|
|
40
40
|
var cursor = {
|
|
41
41
|
to(x, y) {
|
|
@@ -64,8 +64,8 @@ var require_src = __commonJS((exports, module) => {
|
|
|
64
64
|
left: `${CSI2}G`,
|
|
65
65
|
hide: `${CSI2}?25l`,
|
|
66
66
|
show: `${CSI2}?25h`,
|
|
67
|
-
save: `${
|
|
68
|
-
restore: `${
|
|
67
|
+
save: `${ESC2}7`,
|
|
68
|
+
restore: `${ESC2}8`
|
|
69
69
|
};
|
|
70
70
|
var scroll = {
|
|
71
71
|
up: (count = 1) => `${CSI2}S`.repeat(count),
|
|
@@ -93,368 +93,6 @@ var require_src = __commonJS((exports, module) => {
|
|
|
93
93
|
// src/config.ts
|
|
94
94
|
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
95
95
|
import { resolve } from "path";
|
|
96
|
-
var DEFAULT_ENDPOINT = "https://app.snapfail.com";
|
|
97
|
-
var ENV_FILE = ".env";
|
|
98
|
-
function loadConfig(cwd = process.cwd(), pk) {
|
|
99
|
-
if (pk)
|
|
100
|
-
return { projectKey: pk, endpoint: DEFAULT_ENDPOINT };
|
|
101
|
-
const fromEnv = process.env["SNAPFAIL_PROJECT_KEY"];
|
|
102
|
-
if (fromEnv)
|
|
103
|
-
return { projectKey: fromEnv, endpoint: DEFAULT_ENDPOINT };
|
|
104
|
-
const envPath = resolve(cwd, ENV_FILE);
|
|
105
|
-
if (existsSync(envPath)) {
|
|
106
|
-
const match = readFileSync(envPath, "utf-8").match(/^SNAPFAIL_PROJECT_KEY=(.+)$/m);
|
|
107
|
-
if (match)
|
|
108
|
-
return { projectKey: match[1].trim(), endpoint: DEFAULT_ENDPOINT };
|
|
109
|
-
}
|
|
110
|
-
throw new Error('No project key found. Run "snapfail init" or set SNAPFAIL_PROJECT_KEY.');
|
|
111
|
-
}
|
|
112
|
-
function writeProjectKey(projectKey, cwd = process.cwd(), keyName = "SNAPFAIL_PROJECT_KEY") {
|
|
113
|
-
const envPath = resolve(cwd, ENV_FILE);
|
|
114
|
-
const line = `${keyName}=${projectKey}`;
|
|
115
|
-
const pattern = new RegExp(`^${keyName}=.*`, "m");
|
|
116
|
-
if (existsSync(envPath)) {
|
|
117
|
-
let content = readFileSync(envPath, "utf-8");
|
|
118
|
-
if (pattern.test(content)) {
|
|
119
|
-
content = content.replace(pattern, line);
|
|
120
|
-
} else {
|
|
121
|
-
content = content.trimEnd() + `
|
|
122
|
-
${line}
|
|
123
|
-
`;
|
|
124
|
-
}
|
|
125
|
-
writeFileSync(envPath, content, "utf-8");
|
|
126
|
-
} else {
|
|
127
|
-
writeFileSync(envPath, `${line}
|
|
128
|
-
`, "utf-8");
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// src/session.ts
|
|
133
|
-
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync, existsSync as existsSync2, rmSync } from "fs";
|
|
134
|
-
import { join } from "path";
|
|
135
|
-
import { homedir } from "os";
|
|
136
|
-
var SESSION_DIR = join(homedir(), ".snapfail");
|
|
137
|
-
var SESSION_FILE = join(SESSION_DIR, "auth.json");
|
|
138
|
-
function readSession() {
|
|
139
|
-
try {
|
|
140
|
-
if (!existsSync2(SESSION_FILE))
|
|
141
|
-
return null;
|
|
142
|
-
const raw = JSON.parse(readFileSync2(SESSION_FILE, "utf-8"));
|
|
143
|
-
if (!raw.token || !raw.endpoint)
|
|
144
|
-
return null;
|
|
145
|
-
return raw;
|
|
146
|
-
} catch {
|
|
147
|
-
return null;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
function writeSession(session) {
|
|
151
|
-
mkdirSync(SESSION_DIR, { recursive: true });
|
|
152
|
-
writeFileSync2(SESSION_FILE, JSON.stringify(session, null, 2) + `
|
|
153
|
-
`, "utf-8");
|
|
154
|
-
}
|
|
155
|
-
function clearSession() {
|
|
156
|
-
try {
|
|
157
|
-
if (existsSync2(SESSION_FILE))
|
|
158
|
-
rmSync(SESSION_FILE);
|
|
159
|
-
} catch {}
|
|
160
|
-
}
|
|
161
|
-
function requireSession() {
|
|
162
|
-
const session = readSession();
|
|
163
|
-
if (!session) {
|
|
164
|
-
throw new Error('No active session. Run "snapfail init" to authenticate.');
|
|
165
|
-
}
|
|
166
|
-
return session;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// src/api.ts
|
|
170
|
-
async function apiFetch(url, options) {
|
|
171
|
-
let res;
|
|
172
|
-
try {
|
|
173
|
-
res = await fetch(url, options);
|
|
174
|
-
} catch (err) {
|
|
175
|
-
throw new Error(`Network error: ${err instanceof Error ? err.message : String(err)}`);
|
|
176
|
-
}
|
|
177
|
-
if (res.status === 401)
|
|
178
|
-
throw new Error("Unauthorized: check your project key.");
|
|
179
|
-
if (res.status === 404)
|
|
180
|
-
return null;
|
|
181
|
-
if (!res.ok) {
|
|
182
|
-
const body = await res.text().catch(() => "");
|
|
183
|
-
throw new Error(`API error ${res.status}: ${body}`);
|
|
184
|
-
}
|
|
185
|
-
return res.json();
|
|
186
|
-
}
|
|
187
|
-
async function fetchIncidents(config, opts = {}) {
|
|
188
|
-
const url = new URL(`${config.endpoint}/api/incidents`);
|
|
189
|
-
url.searchParams.set("projectKey", config.projectKey);
|
|
190
|
-
if (opts.status)
|
|
191
|
-
url.searchParams.set("status", opts.status);
|
|
192
|
-
if (opts.limit != null)
|
|
193
|
-
url.searchParams.set("limit", String(opts.limit));
|
|
194
|
-
if (opts.offset != null)
|
|
195
|
-
url.searchParams.set("offset", String(opts.offset));
|
|
196
|
-
const data = await apiFetch(url.toString());
|
|
197
|
-
return data;
|
|
198
|
-
}
|
|
199
|
-
async function fetchIncident(config, id) {
|
|
200
|
-
const url = `${config.endpoint}/api/incidents/${id}`;
|
|
201
|
-
const data = await apiFetch(url, {
|
|
202
|
-
headers: { "X-Project-Key": config.projectKey }
|
|
203
|
-
});
|
|
204
|
-
return data;
|
|
205
|
-
}
|
|
206
|
-
async function deleteGroup(config, id) {
|
|
207
|
-
const url = `${config.endpoint}/api/incidents/${id}`;
|
|
208
|
-
const data = await apiFetch(url, {
|
|
209
|
-
method: "DELETE",
|
|
210
|
-
headers: { "X-Project-Key": config.projectKey }
|
|
211
|
-
});
|
|
212
|
-
return data !== null;
|
|
213
|
-
}
|
|
214
|
-
async function fetchSample(config, groupId, sampleIndex) {
|
|
215
|
-
const url = `${config.endpoint}/api/incidents/${groupId}/samples/${sampleIndex}`;
|
|
216
|
-
const data = await apiFetch(url, {
|
|
217
|
-
headers: { "X-Project-Key": config.projectKey }
|
|
218
|
-
});
|
|
219
|
-
return data;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// src/format.ts
|
|
223
|
-
var ESC = "\x1B[";
|
|
224
|
-
var bold = (s) => `${ESC}1m${s}${ESC}0m`;
|
|
225
|
-
var dim = (s) => `${ESC}2m${s}${ESC}0m`;
|
|
226
|
-
var red = (s) => `${ESC}31m${s}${ESC}0m`;
|
|
227
|
-
var green = (s) => `${ESC}32m${s}${ESC}0m`;
|
|
228
|
-
var yellow = (s) => `${ESC}33m${s}${ESC}0m`;
|
|
229
|
-
var cyan = (s) => `${ESC}36m${s}${ESC}0m`;
|
|
230
|
-
function truncate(s, n) {
|
|
231
|
-
if (s.length <= n)
|
|
232
|
-
return s;
|
|
233
|
-
return s.slice(0, n - 1) + "\u2026";
|
|
234
|
-
}
|
|
235
|
-
function relativeTime(ts) {
|
|
236
|
-
const diff = Date.now() - ts;
|
|
237
|
-
const s = Math.floor(diff / 1000);
|
|
238
|
-
if (s < 60)
|
|
239
|
-
return `${s}s ago`;
|
|
240
|
-
const m = Math.floor(s / 60);
|
|
241
|
-
if (m < 60)
|
|
242
|
-
return `${m}m ago`;
|
|
243
|
-
const h = Math.floor(m / 60);
|
|
244
|
-
if (h < 24)
|
|
245
|
-
return `${h}h ago`;
|
|
246
|
-
const d = Math.floor(h / 24);
|
|
247
|
-
return `${d}d ago`;
|
|
248
|
-
}
|
|
249
|
-
function formatTable(rows, headers) {
|
|
250
|
-
const allRows = [headers, ...rows];
|
|
251
|
-
const widths = headers.map((_, i) => Math.max(...allRows.map((r) => (r[i] ?? "").length)));
|
|
252
|
-
const pad = (s, w) => s + " ".repeat(Math.max(0, w - s.length));
|
|
253
|
-
const header = " " + headers.map((h, i) => bold(pad(h, widths[i]))).join(" ");
|
|
254
|
-
const divider = " " + widths.map((w) => dim("\u2500".repeat(w))).join(" ");
|
|
255
|
-
const body = rows.map((r) => " " + r.map((cell, i) => pad(cell, widths[i])).join(" ")).join(`
|
|
256
|
-
`);
|
|
257
|
-
return [header, divider, body].join(`
|
|
258
|
-
`);
|
|
259
|
-
}
|
|
260
|
-
function formatIncidentList(data, total, status) {
|
|
261
|
-
if (data.length === 0) {
|
|
262
|
-
return dim(`No ${status ?? "unresolved"} incidents found.`);
|
|
263
|
-
}
|
|
264
|
-
const rows = data.map((g) => [
|
|
265
|
-
cyan(truncate(g.id, 12)),
|
|
266
|
-
truncate(g.title, 38),
|
|
267
|
-
String(g.count),
|
|
268
|
-
g.environments.join(","),
|
|
269
|
-
relativeTime(g.lastSeen)
|
|
270
|
-
]);
|
|
271
|
-
const table = formatTable(rows, ["ID", "TITLE", "COUNT", "ENV", "LAST SEEN"]);
|
|
272
|
-
const summary = `
|
|
273
|
-
${bold(String(total))} ${status ?? "unresolved"} incident${total !== 1 ? "s" : ""}`;
|
|
274
|
-
return table + summary;
|
|
275
|
-
}
|
|
276
|
-
function formatLLMContext(group, samples) {
|
|
277
|
-
const lines = [];
|
|
278
|
-
lines.push(`# Incident: ${group.title}`);
|
|
279
|
-
lines.push(`Group ID: ${group.id}`);
|
|
280
|
-
lines.push(`Fingerprint: ${group.fingerprint}`);
|
|
281
|
-
lines.push(`Occurrences: ${group.count} | First: ${new Date(group.firstSeen).toISOString()} | Last: ${new Date(group.lastSeen).toISOString()}`);
|
|
282
|
-
lines.push(`Environments: ${group.environments.join(", ")}`);
|
|
283
|
-
lines.push(`Status: ${group.status}`);
|
|
284
|
-
lines.push("");
|
|
285
|
-
lines.push("## Error");
|
|
286
|
-
lines.push(`Type: ${group.errorType}`);
|
|
287
|
-
lines.push(`Message: ${samples[0]?.errorMessage ?? group.title}`);
|
|
288
|
-
lines.push(`Normalized: ${samples[0]?.normalizedMessage ?? group.title}`);
|
|
289
|
-
if (samples[0] && samples[0].stackFrames.length > 0) {
|
|
290
|
-
lines.push("");
|
|
291
|
-
lines.push("## Stack");
|
|
292
|
-
for (const f of samples[0].stackFrames) {
|
|
293
|
-
lines.push(` ${f.fn ?? "(anonymous)"} ${f.file}:${f.line}${f.col != null ? `:${f.col}` : ""}`);
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
for (let i = 0;i < samples.length; i++) {
|
|
297
|
-
const s = samples[i];
|
|
298
|
-
lines.push("");
|
|
299
|
-
lines.push(`## Sample ${i + 1} / ${group.sampleIds.length}`);
|
|
300
|
-
lines.push(`Device: ${s.device.userAgent}`);
|
|
301
|
-
if (s.device.viewport) {
|
|
302
|
-
lines.push(`Viewport: ${s.device.viewport.width}x${s.device.viewport.height} Language: ${s.device.language ?? "unknown"}`);
|
|
303
|
-
}
|
|
304
|
-
lines.push(`URL: ${s.url}`);
|
|
305
|
-
if (s.route)
|
|
306
|
-
lines.push(`Route: ${s.route}`);
|
|
307
|
-
lines.push(`Environment: ${s.environmentMode}`);
|
|
308
|
-
if (s.consoleEntries.length > 0) {
|
|
309
|
-
lines.push("");
|
|
310
|
-
lines.push("### Console");
|
|
311
|
-
for (const e of s.consoleEntries) {
|
|
312
|
-
const args = e.args.map((a) => String(a)).join(" ");
|
|
313
|
-
lines.push(` [${e.level}] ${args}`);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
if (s.networkEntries.length > 0) {
|
|
317
|
-
lines.push("");
|
|
318
|
-
lines.push("### Network");
|
|
319
|
-
for (const n of s.networkEntries) {
|
|
320
|
-
const status = n.status ? ` \u2192 ${n.status}` : n.error ? ` \u2192 ERROR` : "";
|
|
321
|
-
lines.push(` ${n.method} ${n.url}${status} (${n.durationMs}ms)`);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
if (s.timeline.length > 0) {
|
|
325
|
-
lines.push("");
|
|
326
|
-
lines.push("### Timeline");
|
|
327
|
-
for (const e of s.timeline) {
|
|
328
|
-
lines.push(` +${e.t}ms ${e.kind}: ${e.summary}`);
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
if (i < samples.length - 1)
|
|
332
|
-
lines.push(`
|
|
333
|
-
---`);
|
|
334
|
-
}
|
|
335
|
-
return lines.join(`
|
|
336
|
-
`);
|
|
337
|
-
}
|
|
338
|
-
function formatIncidentDetail(group, sample) {
|
|
339
|
-
const lines = [];
|
|
340
|
-
lines.push(bold(`${group.errorType}: ${truncate(group.title, 80)}`));
|
|
341
|
-
lines.push(`${dim("Status:")} ${group.status} ${dim("\xB7")} ${group.count} occurrences ${dim("\xB7")} ${group.environments.join(", ")}`);
|
|
342
|
-
lines.push(`${dim("First seen:")} ${new Date(group.firstSeen).toISOString().slice(0, 10)} ${dim("\xB7")} ${dim("Last seen:")} ${relativeTime(group.lastSeen)}`);
|
|
343
|
-
if (sample.stackFrames.length > 0) {
|
|
344
|
-
lines.push("");
|
|
345
|
-
lines.push(bold("Stack"));
|
|
346
|
-
for (const f of sample.stackFrames.slice(0, 6)) {
|
|
347
|
-
const fn = f.fn ? `${cyan(f.fn)} ` : "";
|
|
348
|
-
lines.push(` ${fn}${dim(f.file + ":" + f.line)}`);
|
|
349
|
-
if (f.codeSnippet) {
|
|
350
|
-
for (const snippetLine of f.codeSnippet.split(`
|
|
351
|
-
`)) {
|
|
352
|
-
const isError = snippetLine.startsWith("\u25BA");
|
|
353
|
-
lines.push(isError ? ` ${bold(snippetLine)}` : ` ${dim(snippetLine)}`);
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
if (sample.consoleEntries.length > 0) {
|
|
359
|
-
lines.push("");
|
|
360
|
-
lines.push(bold("Console"));
|
|
361
|
-
for (const e of sample.consoleEntries.slice(-5)) {
|
|
362
|
-
const level = e.level === "error" ? red(`[${e.level}]`) : yellow(`[${e.level}]`);
|
|
363
|
-
const args = e.args.map((a) => truncate(String(a), 60)).join(" ");
|
|
364
|
-
lines.push(` ${level} ${args}`);
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
if (sample.networkEntries.length > 0) {
|
|
368
|
-
lines.push("");
|
|
369
|
-
lines.push(bold("Network"));
|
|
370
|
-
for (const n of sample.networkEntries.slice(-5)) {
|
|
371
|
-
const status = n.status ? n.status >= 400 ? red(String(n.status)) : green(String(n.status)) : dim("???");
|
|
372
|
-
lines.push(` ${dim(n.method)} ${truncate(n.url, 50)} ${dim("\u2192")} ${status} ${dim(`(${n.durationMs}ms)`)}`);
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
if (sample.timeline.length > 0) {
|
|
376
|
-
lines.push("");
|
|
377
|
-
lines.push(bold("Timeline"));
|
|
378
|
-
for (const e of sample.timeline.slice(0, 10)) {
|
|
379
|
-
const t = String(e.t).padStart(5);
|
|
380
|
-
lines.push(` ${dim(t + "ms")} ${e.summary}`);
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
return lines.join(`
|
|
384
|
-
`);
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
// src/commands/incidents.ts
|
|
388
|
-
async function runIncidents(opts) {
|
|
389
|
-
requireSession();
|
|
390
|
-
const config = loadConfig(process.cwd(), opts.pk);
|
|
391
|
-
const result = await fetchIncidents(config, {
|
|
392
|
-
status: opts.status ?? "unresolved",
|
|
393
|
-
limit: opts.limit,
|
|
394
|
-
offset: opts.offset
|
|
395
|
-
});
|
|
396
|
-
if (opts.json) {
|
|
397
|
-
process.stdout.write(JSON.stringify(result, null, 2) + `
|
|
398
|
-
`);
|
|
399
|
-
return;
|
|
400
|
-
}
|
|
401
|
-
console.log(formatIncidentList(result.data, result.total, opts.status));
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
// src/commands/incident.ts
|
|
405
|
-
async function runIncident(opts) {
|
|
406
|
-
requireSession();
|
|
407
|
-
const config = loadConfig(process.cwd(), opts.pk);
|
|
408
|
-
if (opts.delete) {
|
|
409
|
-
const ok = await deleteGroup(config, opts.id);
|
|
410
|
-
if (!ok) {
|
|
411
|
-
console.error(`Incident group ${opts.id} not found.`);
|
|
412
|
-
process.exit(1);
|
|
413
|
-
}
|
|
414
|
-
if (opts.json) {
|
|
415
|
-
process.stdout.write(JSON.stringify({ ok: true, deleted: opts.id }) + `
|
|
416
|
-
`);
|
|
417
|
-
} else {
|
|
418
|
-
console.log(`Deleted incident group ${opts.id} and all related data.`);
|
|
419
|
-
}
|
|
420
|
-
return;
|
|
421
|
-
}
|
|
422
|
-
if (opts.sample != null) {
|
|
423
|
-
const s = await fetchSample(config, opts.id, opts.sample);
|
|
424
|
-
if (!s) {
|
|
425
|
-
console.error(`Sample ${opts.sample} not found for incident ${opts.id}`);
|
|
426
|
-
process.exit(1);
|
|
427
|
-
}
|
|
428
|
-
if (opts.json) {
|
|
429
|
-
process.stdout.write(JSON.stringify(s, null, 2) + `
|
|
430
|
-
`);
|
|
431
|
-
return;
|
|
432
|
-
}
|
|
433
|
-
const detail = await fetchIncident(config, opts.id);
|
|
434
|
-
if (!detail) {
|
|
435
|
-
console.error(`Incident ${opts.id} not found.`);
|
|
436
|
-
process.exit(1);
|
|
437
|
-
}
|
|
438
|
-
console.log(formatIncidentDetail(detail.group, s));
|
|
439
|
-
return;
|
|
440
|
-
}
|
|
441
|
-
const result = await fetchIncident(config, opts.id);
|
|
442
|
-
if (!result) {
|
|
443
|
-
console.error(`Incident ${opts.id} not found.`);
|
|
444
|
-
process.exit(1);
|
|
445
|
-
}
|
|
446
|
-
if (opts.json) {
|
|
447
|
-
process.stdout.write(JSON.stringify(result, null, 2) + `
|
|
448
|
-
`);
|
|
449
|
-
return;
|
|
450
|
-
}
|
|
451
|
-
console.log(formatIncidentDetail(result.group, result.sample));
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
// src/commands/init.ts
|
|
455
|
-
import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2 } from "fs";
|
|
456
|
-
import { resolve as resolve2, join as join2 } from "path";
|
|
457
|
-
import { execSync as execSync2 } from "child_process";
|
|
458
96
|
|
|
459
97
|
// ../../node_modules/.bun/@clack+core@1.4.1/node_modules/@clack/core/dist/index.mjs
|
|
460
98
|
import { styleText } from "util";
|
|
@@ -592,7 +230,7 @@ var fastStringWidth = (input, options = {}) => {
|
|
|
592
230
|
var dist_default2 = fastStringWidth;
|
|
593
231
|
|
|
594
232
|
// ../../node_modules/.bun/fast-wrap-ansi@0.2.2/node_modules/fast-wrap-ansi/lib/main.js
|
|
595
|
-
var
|
|
233
|
+
var ESC = "\x1B";
|
|
596
234
|
var CSI = "\x9B";
|
|
597
235
|
var END_CODE = 39;
|
|
598
236
|
var ANSI_ESCAPE_BELL = "\x07";
|
|
@@ -626,8 +264,8 @@ var getClosingCode = (openingCode) => {
|
|
|
626
264
|
return 0;
|
|
627
265
|
return;
|
|
628
266
|
};
|
|
629
|
-
var wrapAnsiCode = (code) => `${
|
|
630
|
-
var wrapAnsiHyperlink = (url) => `${
|
|
267
|
+
var wrapAnsiCode = (code) => `${ESC}${ANSI_CSI}${code}${ANSI_SGR_TERMINATOR}`;
|
|
268
|
+
var wrapAnsiHyperlink = (url) => `${ESC}${ANSI_ESCAPE_LINK}${url}${ANSI_ESCAPE_BELL}`;
|
|
631
269
|
var wrapWord = (rows, word, columns) => {
|
|
632
270
|
const characters = word[Symbol.iterator]();
|
|
633
271
|
let isInsideEscape = false;
|
|
@@ -646,7 +284,7 @@ var wrapWord = (rows, word, columns) => {
|
|
|
646
284
|
rows.push(character);
|
|
647
285
|
visible = 0;
|
|
648
286
|
}
|
|
649
|
-
if (character ===
|
|
287
|
+
if (character === ESC || character === CSI) {
|
|
650
288
|
isInsideEscape = true;
|
|
651
289
|
isInsideLinkEscape = word.startsWith(ANSI_ESCAPE_LINK, rawCharacterIndex + 1);
|
|
652
290
|
}
|
|
@@ -765,7 +403,7 @@ var exec = (string, columns, options = {}) => {
|
|
|
765
403
|
} else {
|
|
766
404
|
inSurrogate = false;
|
|
767
405
|
}
|
|
768
|
-
if (character ===
|
|
406
|
+
if (character === ESC || character === CSI) {
|
|
769
407
|
GROUP_REGEX.lastIndex = i + 1;
|
|
770
408
|
const groupsResult = GROUP_REGEX.exec(preString);
|
|
771
409
|
const groups = groupsResult?.groups;
|
|
@@ -1254,694 +892,1188 @@ class U extends V {
|
|
|
1254
892
|
get segmentCursor() {
|
|
1255
893
|
return { ...this.#e };
|
|
1256
894
|
}
|
|
1257
|
-
get segmentValues() {
|
|
1258
|
-
return { ...this.#t };
|
|
895
|
+
get segmentValues() {
|
|
896
|
+
return { ...this.#t };
|
|
897
|
+
}
|
|
898
|
+
get segments() {
|
|
899
|
+
return this.#i;
|
|
900
|
+
}
|
|
901
|
+
get separator() {
|
|
902
|
+
return this.#o;
|
|
903
|
+
}
|
|
904
|
+
get formattedValue() {
|
|
905
|
+
return this.#l(this.#t);
|
|
906
|
+
}
|
|
907
|
+
#l(t2) {
|
|
908
|
+
return this.#i.map((i) => t2[i.type]).join(this.#o);
|
|
909
|
+
}
|
|
910
|
+
#r() {
|
|
911
|
+
this._setUserInput(this.#l(this.#t)), this._setValue(C(this.#t) ?? undefined);
|
|
912
|
+
}
|
|
913
|
+
constructor(t2) {
|
|
914
|
+
const i = t2.format ? { segments: M(t2.format), separator: t2.separator ?? "/" } : P(t2.locale), s = t2.separator ?? i.separator, n = t2.format ? M(t2.format) : i.segments, e = t2.initialValue ?? t2.defaultValue, m2 = e ? {
|
|
915
|
+
year: String(e.getUTCFullYear()).padStart(4, "0"),
|
|
916
|
+
month: String(e.getUTCMonth() + 1).padStart(2, "0"),
|
|
917
|
+
day: String(e.getUTCDate()).padStart(2, "0")
|
|
918
|
+
} : { year: "____", month: "__", day: "__" }, o = n.map((a) => m2[a.type]).join(s);
|
|
919
|
+
super({ ...t2, initialUserInput: o }, false), this.#i = n, this.#o = s, this.#t = m2, this.#h = t2.minDate, this.#u = t2.maxDate, this.#r(), this.on("cursor", (a) => this.#f(a)), this.on("key", (a, u) => this.#y(a, u)), this.on("finalize", () => this.#p(t2));
|
|
920
|
+
}
|
|
921
|
+
#a() {
|
|
922
|
+
const t2 = Math.max(0, Math.min(this.#e.segmentIndex, this.#i.length - 1)), i = this.#i[t2];
|
|
923
|
+
if (i)
|
|
924
|
+
return this.#e.positionInSegment = Math.max(0, Math.min(this.#e.positionInSegment, i.len - 1)), { segment: i, index: t2 };
|
|
925
|
+
}
|
|
926
|
+
#m(t2) {
|
|
927
|
+
this.inlineError = "", this.#s = null;
|
|
928
|
+
const i = this.#a();
|
|
929
|
+
i && (this.#e.segmentIndex = Math.max(0, Math.min(this.#i.length - 1, i.index + t2)), this.#e.positionInSegment = 0, this.#n = true);
|
|
930
|
+
}
|
|
931
|
+
#d(t2) {
|
|
932
|
+
const i = this.#a();
|
|
933
|
+
if (!i)
|
|
934
|
+
return;
|
|
935
|
+
const { segment: s } = i, n = this.#t[s.type], e = !n || n.replace(/_/g, "") === "", m2 = Number.parseInt((n || "0").replace(/_/g, "0"), 10) || 0, o = T2(s.type, f(this.#t), this.#h, this.#u);
|
|
936
|
+
let a;
|
|
937
|
+
e ? a = t2 === 1 ? o.min : o.max : a = Math.max(Math.min(o.max, m2 + t2), o.min), this.#t = {
|
|
938
|
+
...this.#t,
|
|
939
|
+
[s.type]: a.toString().padStart(s.len, "0")
|
|
940
|
+
}, this.#n = true, this.#s = null, this.#r();
|
|
941
|
+
}
|
|
942
|
+
#f(t2) {
|
|
943
|
+
if (t2)
|
|
944
|
+
switch (t2) {
|
|
945
|
+
case "right":
|
|
946
|
+
return this.#m(1);
|
|
947
|
+
case "left":
|
|
948
|
+
return this.#m(-1);
|
|
949
|
+
case "up":
|
|
950
|
+
return this.#d(1);
|
|
951
|
+
case "down":
|
|
952
|
+
return this.#d(-1);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
#y(t2, i) {
|
|
956
|
+
if (i?.name === "backspace" || i?.sequence === "\x7F" || i?.sequence === "\b" || t2 === "\x7F" || t2 === "\b") {
|
|
957
|
+
this.inlineError = "";
|
|
958
|
+
const n = this.#a();
|
|
959
|
+
if (!n)
|
|
960
|
+
return;
|
|
961
|
+
if (!this.#t[n.segment.type].replace(/_/g, "")) {
|
|
962
|
+
this.#m(-1);
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
this.#t[n.segment.type] = "_".repeat(n.segment.len), this.#n = true, this.#e.positionInSegment = 0, this.#r();
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
if (i?.name === "tab") {
|
|
969
|
+
this.inlineError = "";
|
|
970
|
+
const n = this.#a();
|
|
971
|
+
if (!n)
|
|
972
|
+
return;
|
|
973
|
+
const e = i.shift ? -1 : 1, m2 = n.index + e;
|
|
974
|
+
m2 >= 0 && m2 < this.#i.length && (this.#e.segmentIndex = m2, this.#e.positionInSegment = 0, this.#n = true);
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
if (t2 && /^[0-9]$/.test(t2)) {
|
|
978
|
+
const n = this.#a();
|
|
979
|
+
if (!n)
|
|
980
|
+
return;
|
|
981
|
+
const { segment: e } = n, m2 = !this.#t[e.type].replace(/_/g, "");
|
|
982
|
+
if (this.#n && this.#s !== null && !m2) {
|
|
983
|
+
const h = this.#s + t2, d = { ...this.#t, [e.type]: h }, g2 = this.#g(d, e);
|
|
984
|
+
if (g2) {
|
|
985
|
+
this.inlineError = g2, this.#s = null, this.#n = false;
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
this.inlineError = "", this.#t[e.type] = h, this.#s = null, this.#n = false, this.#r(), n.index < this.#i.length - 1 && (this.#e.segmentIndex = n.index + 1, this.#e.positionInSegment = 0, this.#n = true);
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
991
|
+
this.#n && !m2 && (this.#t[e.type] = "_".repeat(e.len), this.#e.positionInSegment = 0), this.#n = false, this.#s = null;
|
|
992
|
+
const o = this.#t[e.type], a = o.indexOf("_"), u = a >= 0 ? a : Math.min(this.#e.positionInSegment, e.len - 1);
|
|
993
|
+
if (u < 0 || u >= e.len)
|
|
994
|
+
return;
|
|
995
|
+
let l2 = o.slice(0, u) + t2 + o.slice(u + 1), D = false;
|
|
996
|
+
if (u === 0 && o === "__" && (e.type === "month" || e.type === "day")) {
|
|
997
|
+
const h = Number.parseInt(t2, 10);
|
|
998
|
+
l2 = `0${t2}`, D = h <= (e.type === "month" ? 1 : 2);
|
|
999
|
+
}
|
|
1000
|
+
if (e.type === "year" && (l2 = (o.replace(/_/g, "") + t2).padStart(e.len, "_")), !l2.includes("_")) {
|
|
1001
|
+
const h = { ...this.#t, [e.type]: l2 }, d = this.#g(h, e);
|
|
1002
|
+
if (d) {
|
|
1003
|
+
this.inlineError = d;
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
this.inlineError = "", this.#t[e.type] = l2;
|
|
1008
|
+
const y = l2.includes("_") ? undefined : b(this.#t);
|
|
1009
|
+
if (y) {
|
|
1010
|
+
const { year: h, month: d } = y, g2 = c(h, d);
|
|
1011
|
+
this.#t = {
|
|
1012
|
+
year: String(Math.max(0, Math.min(9999, h))).padStart(4, "0"),
|
|
1013
|
+
month: String(Math.max(1, Math.min(12, d))).padStart(2, "0"),
|
|
1014
|
+
day: String(Math.max(1, Math.min(g2, y.day))).padStart(2, "0")
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
this.#r();
|
|
1018
|
+
const S = l2.indexOf("_");
|
|
1019
|
+
D ? (this.#n = true, this.#s = t2) : S >= 0 ? this.#e.positionInSegment = S : a >= 0 && n.index < this.#i.length - 1 ? (this.#e.segmentIndex = n.index + 1, this.#e.positionInSegment = 0, this.#n = true) : this.#e.positionInSegment = Math.min(u + 1, e.len - 1);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
#g(t2, i) {
|
|
1023
|
+
const { month: s, day: n } = f(t2);
|
|
1024
|
+
if (i.type === "month" && (s < 0 || s > 12))
|
|
1025
|
+
return settings.date.messages.invalidMonth;
|
|
1026
|
+
if (i.type === "day" && (n < 0 || n > 31))
|
|
1027
|
+
return settings.date.messages.invalidDay(31, "any month");
|
|
1028
|
+
}
|
|
1029
|
+
#p(t2) {
|
|
1030
|
+
const { year: i, month: s, day: n } = f(this.#t);
|
|
1031
|
+
if (i && s && n) {
|
|
1032
|
+
const e = c(i, s);
|
|
1033
|
+
this.#t = {
|
|
1034
|
+
...this.#t,
|
|
1035
|
+
day: String(Math.min(n, e)).padStart(2, "0")
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
1038
|
+
this.value = C(this.#t) ?? t2.defaultValue ?? undefined;
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
var u$1 = class u extends V {
|
|
1042
|
+
options;
|
|
1043
|
+
cursor = 0;
|
|
1044
|
+
#t;
|
|
1045
|
+
getGroupItems(t2) {
|
|
1046
|
+
return this.options.filter((r) => r.group === t2);
|
|
1047
|
+
}
|
|
1048
|
+
isGroupSelected(t2) {
|
|
1049
|
+
const r = this.getGroupItems(t2), e = this.value;
|
|
1050
|
+
return e === undefined ? false : r.every((s) => e.includes(s.value));
|
|
1051
|
+
}
|
|
1052
|
+
toggleValue() {
|
|
1053
|
+
const t2 = this.options[this.cursor];
|
|
1054
|
+
if (this.value === undefined && (this.value = []), t2.group === true) {
|
|
1055
|
+
const r = t2.value, e = this.getGroupItems(r);
|
|
1056
|
+
this.isGroupSelected(r) ? this.value = this.value.filter((s) => e.findIndex((i) => i.value === s) === -1) : this.value = [...this.value, ...e.map((s) => s.value)], this.value = Array.from(new Set(this.value));
|
|
1057
|
+
} else {
|
|
1058
|
+
const r = this.value.includes(t2.value);
|
|
1059
|
+
this.value = r ? this.value.filter((e) => e !== t2.value) : [...this.value, t2.value];
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
constructor(t2) {
|
|
1063
|
+
super(t2, false);
|
|
1064
|
+
const { options: r } = t2;
|
|
1065
|
+
this.#t = t2.selectableGroups !== false, this.options = Object.entries(r).flatMap(([e, s]) => [
|
|
1066
|
+
{ value: e, group: true, label: e },
|
|
1067
|
+
...s.map((i) => ({ ...i, group: e }))
|
|
1068
|
+
]), this.value = [...t2.initialValues ?? []], this.cursor = Math.max(this.options.findIndex(({ value: e }) => e === t2.cursorAt), this.#t ? 0 : 1), this.on("cursor", (e) => {
|
|
1069
|
+
switch (e) {
|
|
1070
|
+
case "left":
|
|
1071
|
+
case "up": {
|
|
1072
|
+
this.cursor = this.cursor === 0 ? this.options.length - 1 : this.cursor - 1;
|
|
1073
|
+
const s = this.options[this.cursor]?.group === true;
|
|
1074
|
+
!this.#t && s && (this.cursor = this.cursor === 0 ? this.options.length - 1 : this.cursor - 1);
|
|
1075
|
+
break;
|
|
1076
|
+
}
|
|
1077
|
+
case "down":
|
|
1078
|
+
case "right": {
|
|
1079
|
+
this.cursor = this.cursor === this.options.length - 1 ? 0 : this.cursor + 1;
|
|
1080
|
+
const s = this.options[this.cursor]?.group === true;
|
|
1081
|
+
!this.#t && s && (this.cursor = this.cursor === this.options.length - 1 ? 0 : this.cursor + 1);
|
|
1082
|
+
break;
|
|
1083
|
+
}
|
|
1084
|
+
case "space":
|
|
1085
|
+
this.toggleValue();
|
|
1086
|
+
break;
|
|
1087
|
+
}
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
1090
|
+
};
|
|
1091
|
+
var o$1 = /* @__PURE__ */ new Set(["up", "down", "left", "right"]);
|
|
1092
|
+
|
|
1093
|
+
class h extends V {
|
|
1094
|
+
#s = false;
|
|
1095
|
+
#t;
|
|
1096
|
+
focused = "editor";
|
|
1097
|
+
get userInputWithCursor() {
|
|
1098
|
+
if (this.state === "submit")
|
|
1099
|
+
return this.userInput;
|
|
1100
|
+
const t2 = this.userInput;
|
|
1101
|
+
if (this.cursor >= t2.length)
|
|
1102
|
+
return `${t2}\u2588`;
|
|
1103
|
+
const s = t2.slice(0, this.cursor), r = t2[this.cursor], e = t2.slice(this.cursor + 1);
|
|
1104
|
+
return r === `
|
|
1105
|
+
` ? `${s}\u2588
|
|
1106
|
+
${e}` : `${s}${styleText("inverse", r)}${e}`;
|
|
1107
|
+
}
|
|
1108
|
+
get cursor() {
|
|
1109
|
+
return this._cursor;
|
|
1110
|
+
}
|
|
1111
|
+
#r(t2) {
|
|
1112
|
+
if (this.userInput.length === 0) {
|
|
1113
|
+
this._setUserInput(t2);
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
1116
|
+
this._setUserInput(this.userInput.slice(0, this.cursor) + t2 + this.userInput.slice(this.cursor));
|
|
1117
|
+
}
|
|
1118
|
+
#i(t2) {
|
|
1119
|
+
const s = this.value ?? "";
|
|
1120
|
+
switch (t2) {
|
|
1121
|
+
case "up":
|
|
1122
|
+
this._cursor = findTextCursor(this._cursor, 0, -1, s);
|
|
1123
|
+
return;
|
|
1124
|
+
case "down":
|
|
1125
|
+
this._cursor = findTextCursor(this._cursor, 0, 1, s);
|
|
1126
|
+
return;
|
|
1127
|
+
case "left":
|
|
1128
|
+
this._cursor = findTextCursor(this._cursor, -1, 0, s);
|
|
1129
|
+
return;
|
|
1130
|
+
case "right":
|
|
1131
|
+
this._cursor = findTextCursor(this._cursor, 1, 0, s);
|
|
1132
|
+
return;
|
|
1133
|
+
}
|
|
1259
1134
|
}
|
|
1260
|
-
|
|
1261
|
-
|
|
1135
|
+
_shouldSubmit(t2, s) {
|
|
1136
|
+
if (this.#t)
|
|
1137
|
+
return this.focused === "submit" ? true : (this.#r(`
|
|
1138
|
+
`), this._cursor++, false);
|
|
1139
|
+
const r = this.#s;
|
|
1140
|
+
return this.#s = true, r ? (this.userInput[this.cursor - 1] === `
|
|
1141
|
+
` && (this._setUserInput(this.userInput.slice(0, this.cursor - 1) + this.userInput.slice(this.cursor)), this._cursor--), true) : (this.#r(`
|
|
1142
|
+
`), this._cursor++, false);
|
|
1262
1143
|
}
|
|
1263
|
-
|
|
1264
|
-
|
|
1144
|
+
constructor(t2) {
|
|
1145
|
+
super(t2, false), this.#t = t2.showSubmit ?? false, this.on("key", (s, r) => {
|
|
1146
|
+
if (r?.name && o$1.has(r.name)) {
|
|
1147
|
+
this.#i(r.name);
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
if (s === "\t" && this.#t) {
|
|
1151
|
+
this.focused = this.focused === "editor" ? "submit" : "editor";
|
|
1152
|
+
return;
|
|
1153
|
+
}
|
|
1154
|
+
if (r?.name !== "return") {
|
|
1155
|
+
if (this.#s = false, r?.name === "backspace" && this.cursor > 0) {
|
|
1156
|
+
this._setUserInput(this.userInput.slice(0, this.cursor - 1) + this.userInput.slice(this.cursor)), this._cursor--;
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
if (r?.name === "delete" && this.cursor < this.userInput.length) {
|
|
1160
|
+
this._setUserInput(this.userInput.slice(0, this.cursor) + this.userInput.slice(this.cursor + 1));
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
s && (this.#t && this.focused === "submit" && (this.focused = "editor"), this.#r(s ?? ""), this._cursor++);
|
|
1164
|
+
}
|
|
1165
|
+
}), this.on("userInput", (s) => {
|
|
1166
|
+
this._setValue(s);
|
|
1167
|
+
}), this.on("finalize", () => {
|
|
1168
|
+
this.value || (this.value = t2.defaultValue), this.value === undefined && (this.value = "");
|
|
1169
|
+
});
|
|
1265
1170
|
}
|
|
1266
|
-
|
|
1267
|
-
|
|
1171
|
+
}
|
|
1172
|
+
class a extends V {
|
|
1173
|
+
options;
|
|
1174
|
+
cursor = 0;
|
|
1175
|
+
get _selectedValue() {
|
|
1176
|
+
return this.options[this.cursor];
|
|
1268
1177
|
}
|
|
1269
|
-
|
|
1270
|
-
|
|
1178
|
+
changeValue() {
|
|
1179
|
+
this.value = this._selectedValue.value;
|
|
1271
1180
|
}
|
|
1272
|
-
|
|
1273
|
-
|
|
1181
|
+
constructor(t2) {
|
|
1182
|
+
super(t2, false), this.options = t2.options;
|
|
1183
|
+
const i = this.options.findIndex(({ value: s }) => s === t2.initialValue), e = i === -1 ? 0 : i;
|
|
1184
|
+
this.cursor = this.options[e].disabled ? findCursor(e, 1, this.options) : e, this.changeValue(), this.on("cursor", (s) => {
|
|
1185
|
+
switch (s) {
|
|
1186
|
+
case "left":
|
|
1187
|
+
case "up":
|
|
1188
|
+
this.cursor = findCursor(this.cursor, -1, this.options);
|
|
1189
|
+
break;
|
|
1190
|
+
case "down":
|
|
1191
|
+
case "right":
|
|
1192
|
+
this.cursor = findCursor(this.cursor, 1, this.options);
|
|
1193
|
+
break;
|
|
1194
|
+
}
|
|
1195
|
+
this.changeValue();
|
|
1196
|
+
});
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
class n extends V {
|
|
1200
|
+
get userInputWithCursor() {
|
|
1201
|
+
if (this.state === "submit")
|
|
1202
|
+
return this.userInput;
|
|
1203
|
+
const t2 = this.userInput;
|
|
1204
|
+
if (this.cursor >= t2.length)
|
|
1205
|
+
return `${this.userInput}\u2588`;
|
|
1206
|
+
const e = t2.slice(0, this.cursor), [s, ...r] = t2.slice(this.cursor);
|
|
1207
|
+
return `${e}${styleText("inverse", s)}${r.join("")}`;
|
|
1208
|
+
}
|
|
1209
|
+
get cursor() {
|
|
1210
|
+
return this._cursor;
|
|
1274
1211
|
}
|
|
1275
1212
|
constructor(t2) {
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1213
|
+
super({
|
|
1214
|
+
...t2,
|
|
1215
|
+
initialUserInput: t2.initialUserInput ?? t2.initialValue
|
|
1216
|
+
}), this.on("userInput", (e) => {
|
|
1217
|
+
this._setValue(e);
|
|
1218
|
+
}), this.on("finalize", () => {
|
|
1219
|
+
this.value || (this.value = t2.defaultValue), this.value === undefined && (this.value = "");
|
|
1220
|
+
});
|
|
1282
1221
|
}
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
// ../../node_modules/.bun/@clack+prompts@1.5.1/node_modules/@clack/prompts/dist/index.mjs
|
|
1225
|
+
import { styleText as styleText2, stripVTControlCharacters } from "util";
|
|
1226
|
+
import process$1 from "process";
|
|
1227
|
+
var import_sisteransi2 = __toESM(require_src(), 1);
|
|
1228
|
+
function isUnicodeSupported() {
|
|
1229
|
+
if (process$1.platform !== "win32") {
|
|
1230
|
+
return process$1.env.TERM !== "linux";
|
|
1287
1231
|
}
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1232
|
+
return Boolean(process$1.env.CI) || Boolean(process$1.env.WT_SESSION) || Boolean(process$1.env.TERMINUS_SUBLIME) || process$1.env.ConEmuTask === "{cmd::Cmder}" || process$1.env.TERM_PROGRAM === "Terminus-Sublime" || process$1.env.TERM_PROGRAM === "vscode" || process$1.env.TERM === "xterm-256color" || process$1.env.TERM === "alacritty" || process$1.env.TERMINAL_EMULATOR === "JetBrains-JediTerm";
|
|
1233
|
+
}
|
|
1234
|
+
var unicode = isUnicodeSupported();
|
|
1235
|
+
var isCI = () => process.env.CI === "true";
|
|
1236
|
+
var unicodeOr = (e, o2) => unicode ? e : o2;
|
|
1237
|
+
var S_STEP_ACTIVE = unicodeOr("\u25C6", "*");
|
|
1238
|
+
var S_STEP_CANCEL = unicodeOr("\u25A0", "x");
|
|
1239
|
+
var S_STEP_ERROR = unicodeOr("\u25B2", "x");
|
|
1240
|
+
var S_STEP_SUBMIT = unicodeOr("\u25C7", "o");
|
|
1241
|
+
var S_BAR_START = unicodeOr("\u250C", "T");
|
|
1242
|
+
var S_BAR = unicodeOr("\u2502", "|");
|
|
1243
|
+
var S_BAR_END = unicodeOr("\u2514", "\u2014");
|
|
1244
|
+
var S_BAR_START_RIGHT = unicodeOr("\u2510", "T");
|
|
1245
|
+
var S_BAR_END_RIGHT = unicodeOr("\u2518", "\u2014");
|
|
1246
|
+
var S_RADIO_ACTIVE = unicodeOr("\u25CF", ">");
|
|
1247
|
+
var S_RADIO_INACTIVE = unicodeOr("\u25CB", " ");
|
|
1248
|
+
var S_CHECKBOX_ACTIVE = unicodeOr("\u25FB", "[\u2022]");
|
|
1249
|
+
var S_CHECKBOX_SELECTED = unicodeOr("\u25FC", "[+]");
|
|
1250
|
+
var S_CHECKBOX_INACTIVE = unicodeOr("\u25FB", "[ ]");
|
|
1251
|
+
var S_PASSWORD_MASK = unicodeOr("\u25AA", "\u2022");
|
|
1252
|
+
var S_BAR_H = unicodeOr("\u2500", "-");
|
|
1253
|
+
var S_CORNER_TOP_RIGHT = unicodeOr("\u256E", "+");
|
|
1254
|
+
var S_CONNECT_LEFT = unicodeOr("\u251C", "+");
|
|
1255
|
+
var S_CORNER_BOTTOM_RIGHT = unicodeOr("\u256F", "+");
|
|
1256
|
+
var S_CORNER_BOTTOM_LEFT = unicodeOr("\u2570", "+");
|
|
1257
|
+
var S_CORNER_TOP_LEFT = unicodeOr("\u256D", "+");
|
|
1258
|
+
var S_INFO = unicodeOr("\u25CF", "\u2022");
|
|
1259
|
+
var S_SUCCESS = unicodeOr("\u25C6", "*");
|
|
1260
|
+
var S_WARN = unicodeOr("\u25B2", "!");
|
|
1261
|
+
var S_ERROR = unicodeOr("\u25A0", "x");
|
|
1262
|
+
var symbol = (e) => {
|
|
1263
|
+
switch (e) {
|
|
1264
|
+
case "initial":
|
|
1265
|
+
case "active":
|
|
1266
|
+
return styleText2("cyan", S_STEP_ACTIVE);
|
|
1267
|
+
case "cancel":
|
|
1268
|
+
return styleText2("red", S_STEP_CANCEL);
|
|
1269
|
+
case "error":
|
|
1270
|
+
return styleText2("yellow", S_STEP_ERROR);
|
|
1271
|
+
case "submit":
|
|
1272
|
+
return styleText2("green", S_STEP_SUBMIT);
|
|
1273
|
+
}
|
|
1274
|
+
};
|
|
1275
|
+
var symbolBar = (e) => {
|
|
1276
|
+
switch (e) {
|
|
1277
|
+
case "initial":
|
|
1278
|
+
case "active":
|
|
1279
|
+
return styleText2("cyan", S_BAR);
|
|
1280
|
+
case "cancel":
|
|
1281
|
+
return styleText2("red", S_BAR);
|
|
1282
|
+
case "error":
|
|
1283
|
+
return styleText2("yellow", S_BAR);
|
|
1284
|
+
case "submit":
|
|
1285
|
+
return styleText2("green", S_BAR);
|
|
1286
|
+
}
|
|
1287
|
+
};
|
|
1288
|
+
var E$1 = (l2, o2, g2, c2, h2, O = false) => {
|
|
1289
|
+
let r2 = o2, w = 0;
|
|
1290
|
+
if (O)
|
|
1291
|
+
for (let i = c2 - 1;i >= g2 && (r2 -= l2[i].length, w++, !(r2 <= h2)); i--)
|
|
1292
|
+
;
|
|
1293
|
+
else
|
|
1294
|
+
for (let i = g2;i < c2 && (r2 -= l2[i].length, w++, !(r2 <= h2)); i++)
|
|
1295
|
+
;
|
|
1296
|
+
return { lineCount: r2, removals: w };
|
|
1297
|
+
};
|
|
1298
|
+
var limitOptions = ({
|
|
1299
|
+
cursor: l2,
|
|
1300
|
+
options: o2,
|
|
1301
|
+
style: g2,
|
|
1302
|
+
output: c2 = process.stdout,
|
|
1303
|
+
maxItems: h2 = Number.POSITIVE_INFINITY,
|
|
1304
|
+
columnPadding: O = 0,
|
|
1305
|
+
rowPadding: r2 = 4
|
|
1306
|
+
}) => {
|
|
1307
|
+
const i = getColumns(c2) - O, I = getRows(c2), C2 = styleText2("dim", "..."), x = Math.max(I - r2, 0), m2 = Math.max(Math.min(h2, x), 5);
|
|
1308
|
+
let p2 = 0;
|
|
1309
|
+
l2 >= m2 - 3 && (p2 = Math.max(Math.min(l2 - m2 + 3, o2.length - m2), 0));
|
|
1310
|
+
let f2 = m2 < o2.length && p2 > 0, u3 = m2 < o2.length && p2 + m2 < o2.length;
|
|
1311
|
+
const W = Math.min(p2 + m2, o2.length), e = [];
|
|
1312
|
+
let d = 0;
|
|
1313
|
+
f2 && d++, u3 && d++;
|
|
1314
|
+
const v = p2 + (f2 ? 1 : 0), P2 = W - (u3 ? 1 : 0);
|
|
1315
|
+
for (let t2 = v;t2 < P2; t2++) {
|
|
1316
|
+
const n2 = wrapAnsi(g2(o2[t2], t2 === l2), i, {
|
|
1317
|
+
hard: true,
|
|
1318
|
+
trim: false
|
|
1319
|
+
}).split(`
|
|
1320
|
+
`);
|
|
1321
|
+
e.push(n2), d += n2.length;
|
|
1322
|
+
}
|
|
1323
|
+
if (d > x) {
|
|
1324
|
+
let t2 = 0, n2 = 0, s = d;
|
|
1325
|
+
const M2 = l2 - v;
|
|
1326
|
+
let a2 = x;
|
|
1327
|
+
const T3 = () => E$1(e, s, 0, M2, a2), L = () => E$1(e, s, M2 + 1, e.length, a2, true);
|
|
1328
|
+
f2 ? ({ lineCount: s, removals: t2 } = T3(), s > a2 && (u3 || (a2 -= 1), { lineCount: s, removals: n2 } = L())) : (u3 || (a2 -= 1), { lineCount: s, removals: n2 } = L(), s > a2 && (a2 -= 1, { lineCount: s, removals: t2 } = T3())), t2 > 0 && (f2 = true, e.splice(0, t2)), n2 > 0 && (u3 = true, e.splice(e.length - n2, n2));
|
|
1329
|
+
}
|
|
1330
|
+
const b2 = [];
|
|
1331
|
+
f2 && b2.push(C2);
|
|
1332
|
+
for (const t2 of e)
|
|
1333
|
+
for (const n2 of t2)
|
|
1334
|
+
b2.push(n2);
|
|
1335
|
+
return u3 && b2.push(C2), b2;
|
|
1336
|
+
};
|
|
1337
|
+
var log = {
|
|
1338
|
+
message: (s = [], {
|
|
1339
|
+
symbol: e = styleText2("gray", S_BAR),
|
|
1340
|
+
secondarySymbol: r2 = styleText2("gray", S_BAR),
|
|
1341
|
+
output: m2 = process.stdout,
|
|
1342
|
+
spacing: l2 = 1,
|
|
1343
|
+
withGuide: c2
|
|
1344
|
+
} = {}) => {
|
|
1345
|
+
const t2 = [], o2 = c2 ?? settings.withGuide, f2 = o2 ? r2 : "", O = o2 ? `${e} ` : "", u3 = o2 ? `${r2} ` : "";
|
|
1346
|
+
for (let i = 0;i < l2; i++)
|
|
1347
|
+
t2.push(f2);
|
|
1348
|
+
const g2 = Array.isArray(s) ? s : s.split(`
|
|
1349
|
+
`);
|
|
1350
|
+
if (g2.length > 0) {
|
|
1351
|
+
const [i, ...y] = g2;
|
|
1352
|
+
i.length > 0 ? t2.push(`${O}${i}`) : t2.push(o2 ? e : "");
|
|
1353
|
+
for (const p2 of y)
|
|
1354
|
+
p2.length > 0 ? t2.push(`${u3}${p2}`) : t2.push(o2 ? r2 : "");
|
|
1355
|
+
}
|
|
1356
|
+
m2.write(`${t2.join(`
|
|
1357
|
+
`)}
|
|
1358
|
+
`);
|
|
1359
|
+
},
|
|
1360
|
+
info: (s, e) => {
|
|
1361
|
+
log.message(s, { ...e, symbol: styleText2("blue", S_INFO) });
|
|
1362
|
+
},
|
|
1363
|
+
success: (s, e) => {
|
|
1364
|
+
log.message(s, { ...e, symbol: styleText2("green", S_SUCCESS) });
|
|
1365
|
+
},
|
|
1366
|
+
step: (s, e) => {
|
|
1367
|
+
log.message(s, { ...e, symbol: styleText2("green", S_STEP_SUBMIT) });
|
|
1368
|
+
},
|
|
1369
|
+
warn: (s, e) => {
|
|
1370
|
+
log.message(s, { ...e, symbol: styleText2("yellow", S_WARN) });
|
|
1371
|
+
},
|
|
1372
|
+
warning: (s, e) => {
|
|
1373
|
+
log.warn(s, e);
|
|
1374
|
+
},
|
|
1375
|
+
error: (s, e) => {
|
|
1376
|
+
log.message(s, { ...e, symbol: styleText2("red", S_ERROR) });
|
|
1292
1377
|
}
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1378
|
+
};
|
|
1379
|
+
var cancel = (o2 = "", t2) => {
|
|
1380
|
+
const i = t2?.output ?? process.stdout, e = t2?.withGuide ?? settings.withGuide ? `${styleText2("gray", S_BAR_END)} ` : "";
|
|
1381
|
+
i.write(`${e}${styleText2("red", o2)}
|
|
1382
|
+
|
|
1383
|
+
`);
|
|
1384
|
+
};
|
|
1385
|
+
var intro = (o2 = "", t2) => {
|
|
1386
|
+
const i = t2?.output ?? process.stdout, e = t2?.withGuide ?? settings.withGuide ? `${styleText2("gray", S_BAR_START)} ` : "";
|
|
1387
|
+
i.write(`${e}${o2}
|
|
1388
|
+
`);
|
|
1389
|
+
};
|
|
1390
|
+
var outro = (o2 = "", t2) => {
|
|
1391
|
+
const i = t2?.output ?? process.stdout, e = t2?.withGuide ?? settings.withGuide ? `${styleText2("gray", S_BAR)}
|
|
1392
|
+
${styleText2("gray", S_BAR_END)} ` : "";
|
|
1393
|
+
i.write(`${e}${o2}
|
|
1394
|
+
|
|
1395
|
+
`);
|
|
1396
|
+
};
|
|
1397
|
+
var W = (l2) => styleText2("magenta", l2);
|
|
1398
|
+
var spinner = ({
|
|
1399
|
+
indicator: l2 = "dots",
|
|
1400
|
+
onCancel: h2,
|
|
1401
|
+
output: n2 = process.stdout,
|
|
1402
|
+
cancelMessage: G,
|
|
1403
|
+
errorMessage: O,
|
|
1404
|
+
frames: E = unicode ? ["\u25D2", "\u25D0", "\u25D3", "\u25D1"] : ["\u2022", "o", "O", "0"],
|
|
1405
|
+
delay: F = unicode ? 80 : 120,
|
|
1406
|
+
signal: m2,
|
|
1407
|
+
...I
|
|
1408
|
+
} = {}) => {
|
|
1409
|
+
const u3 = isCI();
|
|
1410
|
+
let M2, T3, d = false, S = false, s = "", p2, w = performance.now();
|
|
1411
|
+
const x = getColumns(n2), k = I?.styleFrame ?? W, g2 = (e) => {
|
|
1412
|
+
const r2 = e > 1 ? O ?? settings.messages.error : G ?? settings.messages.cancel;
|
|
1413
|
+
S = e === 1, d && (a2(r2, e), S && typeof h2 == "function" && h2());
|
|
1414
|
+
}, f2 = () => g2(2), i = () => g2(1), A = () => {
|
|
1415
|
+
process.on("uncaughtExceptionMonitor", f2), process.on("unhandledRejection", f2), process.on("SIGINT", i), process.on("SIGTERM", i), process.on("exit", g2), m2 && m2.addEventListener("abort", i);
|
|
1416
|
+
}, H = () => {
|
|
1417
|
+
process.removeListener("uncaughtExceptionMonitor", f2), process.removeListener("unhandledRejection", f2), process.removeListener("SIGINT", i), process.removeListener("SIGTERM", i), process.removeListener("exit", g2), m2 && m2.removeEventListener("abort", i);
|
|
1418
|
+
}, y = () => {
|
|
1419
|
+
if (p2 === undefined)
|
|
1296
1420
|
return;
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
}
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
return this.#d(-1);
|
|
1315
|
-
}
|
|
1316
|
-
}
|
|
1317
|
-
#y(t2, i) {
|
|
1318
|
-
if (i?.name === "backspace" || i?.sequence === "\x7F" || i?.sequence === "\b" || t2 === "\x7F" || t2 === "\b") {
|
|
1319
|
-
this.inlineError = "";
|
|
1320
|
-
const n = this.#a();
|
|
1321
|
-
if (!n)
|
|
1322
|
-
return;
|
|
1323
|
-
if (!this.#t[n.segment.type].replace(/_/g, "")) {
|
|
1324
|
-
this.#m(-1);
|
|
1421
|
+
u3 && n2.write(`
|
|
1422
|
+
`);
|
|
1423
|
+
const r2 = wrapAnsi(p2, x, {
|
|
1424
|
+
hard: true,
|
|
1425
|
+
trim: false
|
|
1426
|
+
}).split(`
|
|
1427
|
+
`);
|
|
1428
|
+
r2.length > 1 && n2.write(import_sisteransi2.cursor.up(r2.length - 1)), n2.write(import_sisteransi2.cursor.to(0)), n2.write(import_sisteransi2.erase.down());
|
|
1429
|
+
}, C2 = (e) => e.replace(/\.+$/, ""), _2 = (e) => {
|
|
1430
|
+
const r2 = (performance.now() - e) / 1000, t2 = Math.floor(r2 / 60), o2 = Math.floor(r2 % 60);
|
|
1431
|
+
return t2 > 0 ? `[${t2}m ${o2}s]` : `[${o2}s]`;
|
|
1432
|
+
}, N = I.withGuide ?? settings.withGuide, P2 = (e = "") => {
|
|
1433
|
+
d = true, M2 = block({ output: n2 }), s = C2(e), w = performance.now(), N && n2.write(`${styleText2("gray", S_BAR)}
|
|
1434
|
+
`);
|
|
1435
|
+
let r2 = 0, t2 = 0;
|
|
1436
|
+
A(), T3 = setInterval(() => {
|
|
1437
|
+
if (u3 && s === p2)
|
|
1325
1438
|
return;
|
|
1439
|
+
y(), p2 = s;
|
|
1440
|
+
const o2 = k(E[r2]);
|
|
1441
|
+
let v;
|
|
1442
|
+
if (u3)
|
|
1443
|
+
v = `${o2} ${s}...`;
|
|
1444
|
+
else if (l2 === "timer")
|
|
1445
|
+
v = `${o2} ${s} ${_2(w)}`;
|
|
1446
|
+
else {
|
|
1447
|
+
const B = ".".repeat(Math.floor(t2)).slice(0, 3);
|
|
1448
|
+
v = `${o2} ${s}${B}`;
|
|
1326
1449
|
}
|
|
1327
|
-
|
|
1450
|
+
const j = wrapAnsi(v, x, {
|
|
1451
|
+
hard: true,
|
|
1452
|
+
trim: false
|
|
1453
|
+
});
|
|
1454
|
+
n2.write(j), r2 = r2 + 1 < E.length ? r2 + 1 : 0, t2 = t2 < 4 ? t2 + 0.125 : 0;
|
|
1455
|
+
}, F);
|
|
1456
|
+
}, a2 = (e = "", r2 = 0, t2 = false) => {
|
|
1457
|
+
if (!d)
|
|
1328
1458
|
return;
|
|
1459
|
+
d = false, clearInterval(T3), y();
|
|
1460
|
+
const o2 = r2 === 0 ? styleText2("green", S_STEP_SUBMIT) : r2 === 1 ? styleText2("red", S_STEP_CANCEL) : styleText2("red", S_STEP_ERROR);
|
|
1461
|
+
s = e ?? s, t2 || (l2 === "timer" ? n2.write(`${o2} ${s} ${_2(w)}
|
|
1462
|
+
`) : n2.write(`${o2} ${s}
|
|
1463
|
+
`)), H(), M2();
|
|
1464
|
+
};
|
|
1465
|
+
return {
|
|
1466
|
+
start: P2,
|
|
1467
|
+
stop: (e = "") => a2(e, 0),
|
|
1468
|
+
message: (e = "") => {
|
|
1469
|
+
s = C2(e ?? s);
|
|
1470
|
+
},
|
|
1471
|
+
cancel: (e = "") => a2(e, 1),
|
|
1472
|
+
error: (e = "") => a2(e, 2),
|
|
1473
|
+
clear: () => a2("", 0, true),
|
|
1474
|
+
get isCancelled() {
|
|
1475
|
+
return S;
|
|
1329
1476
|
}
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1477
|
+
};
|
|
1478
|
+
};
|
|
1479
|
+
var u3 = {
|
|
1480
|
+
light: unicodeOr("\u2500", "-"),
|
|
1481
|
+
heavy: unicodeOr("\u2501", "="),
|
|
1482
|
+
block: unicodeOr("\u2588", "#")
|
|
1483
|
+
};
|
|
1484
|
+
var c2 = (e, a2) => e.includes(`
|
|
1485
|
+
`) ? e.split(`
|
|
1486
|
+
`).map((t2) => a2(t2)).join(`
|
|
1487
|
+
`) : a2(e);
|
|
1488
|
+
var select = (e) => {
|
|
1489
|
+
const a2 = (t2, d) => {
|
|
1490
|
+
const s = t2.label ?? String(t2.value);
|
|
1491
|
+
switch (d) {
|
|
1492
|
+
case "disabled":
|
|
1493
|
+
return `${styleText2("gray", S_RADIO_INACTIVE)} ${c2(s, (n2) => styleText2("gray", n2))}${t2.hint ? ` ${styleText2("dim", `(${t2.hint ?? "disabled"})`)}` : ""}`;
|
|
1494
|
+
case "selected":
|
|
1495
|
+
return `${c2(s, (n2) => styleText2("dim", n2))}`;
|
|
1496
|
+
case "active":
|
|
1497
|
+
return `${styleText2("green", S_RADIO_ACTIVE)} ${s}${t2.hint ? ` ${styleText2("dim", `(${t2.hint})`)}` : ""}`;
|
|
1498
|
+
case "cancelled":
|
|
1499
|
+
return `${c2(s, (n2) => styleText2(["strikethrough", "dim"], n2))}`;
|
|
1500
|
+
default:
|
|
1501
|
+
return `${styleText2("dim", S_RADIO_INACTIVE)} ${c2(s, (n2) => styleText2("dim", n2))}`;
|
|
1338
1502
|
}
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1503
|
+
};
|
|
1504
|
+
return new a({
|
|
1505
|
+
options: e.options,
|
|
1506
|
+
signal: e.signal,
|
|
1507
|
+
input: e.input,
|
|
1508
|
+
output: e.output,
|
|
1509
|
+
initialValue: e.initialValue,
|
|
1510
|
+
render() {
|
|
1511
|
+
const t2 = e.withGuide ?? settings.withGuide, d = `${symbol(this.state)} `, s = `${symbolBar(this.state)} `, n2 = wrapTextWithPrefix(e.output, e.message, s, d), u4 = `${t2 ? `${styleText2("gray", S_BAR)}
|
|
1512
|
+
` : ""}${n2}
|
|
1513
|
+
`;
|
|
1514
|
+
switch (this.state) {
|
|
1515
|
+
case "submit": {
|
|
1516
|
+
const r2 = t2 ? `${styleText2("gray", S_BAR)} ` : "", l2 = wrapTextWithPrefix(e.output, a2(this.options[this.cursor], "selected"), r2);
|
|
1517
|
+
return `${u4}${l2}`;
|
|
1518
|
+
}
|
|
1519
|
+
case "cancel": {
|
|
1520
|
+
const r2 = t2 ? `${styleText2("gray", S_BAR)} ` : "", l2 = wrapTextWithPrefix(e.output, a2(this.options[this.cursor], "cancelled"), r2);
|
|
1521
|
+
return `${u4}${l2}${t2 ? `
|
|
1522
|
+
${styleText2("gray", S_BAR)}` : ""}`;
|
|
1523
|
+
}
|
|
1524
|
+
default: {
|
|
1525
|
+
const r2 = t2 ? `${styleText2("cyan", S_BAR)} ` : "", l2 = t2 ? styleText2("cyan", S_BAR_END) : "", g2 = u4.split(`
|
|
1526
|
+
`).length, h2 = t2 ? 2 : 1;
|
|
1527
|
+
return `${u4}${r2}${limitOptions({
|
|
1528
|
+
output: e.output,
|
|
1529
|
+
cursor: this.cursor,
|
|
1530
|
+
options: this.options,
|
|
1531
|
+
maxItems: e.maxItems,
|
|
1532
|
+
columnPadding: r2.length,
|
|
1533
|
+
rowPadding: g2 + h2,
|
|
1534
|
+
style: (p2, b2) => a2(p2, p2.disabled ? "disabled" : b2 ? "active" : "inactive")
|
|
1535
|
+
}).join(`
|
|
1536
|
+
${r2}`)}
|
|
1537
|
+
${l2}
|
|
1538
|
+
`;
|
|
1349
1539
|
}
|
|
1350
|
-
this.inlineError = "", this.#t[e.type] = h, this.#s = null, this.#n = false, this.#r(), n.index < this.#i.length - 1 && (this.#e.segmentIndex = n.index + 1, this.#e.positionInSegment = 0, this.#n = true);
|
|
1351
|
-
return;
|
|
1352
1540
|
}
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1541
|
+
}
|
|
1542
|
+
}).prompt();
|
|
1543
|
+
};
|
|
1544
|
+
var i = `${styleText2("gray", S_BAR)} `;
|
|
1545
|
+
var text = (t2) => new n({
|
|
1546
|
+
validate: t2.validate,
|
|
1547
|
+
placeholder: t2.placeholder,
|
|
1548
|
+
defaultValue: t2.defaultValue,
|
|
1549
|
+
initialValue: t2.initialValue,
|
|
1550
|
+
output: t2.output,
|
|
1551
|
+
signal: t2.signal,
|
|
1552
|
+
input: t2.input,
|
|
1553
|
+
render() {
|
|
1554
|
+
const i2 = t2?.withGuide ?? settings.withGuide, s = `${`${i2 ? `${styleText2("gray", S_BAR)}
|
|
1555
|
+
` : ""}${symbol(this.state)} `}${t2.message}
|
|
1556
|
+
`, c3 = t2.placeholder ? styleText2("inverse", t2.placeholder[0]) + styleText2("dim", t2.placeholder.slice(1)) : styleText2(["inverse", "hidden"], "_"), o2 = this.userInput ? this.userInputWithCursor : c3, a2 = this.value ?? "";
|
|
1557
|
+
switch (this.state) {
|
|
1558
|
+
case "error": {
|
|
1559
|
+
const n2 = this.error ? ` ${styleText2("yellow", this.error)}` : "", r2 = i2 ? `${styleText2("yellow", S_BAR)} ` : "", d = i2 ? styleText2("yellow", S_BAR_END) : "";
|
|
1560
|
+
return `${s.trim()}
|
|
1561
|
+
${r2}${o2}
|
|
1562
|
+
${d}${n2}
|
|
1563
|
+
`;
|
|
1361
1564
|
}
|
|
1362
|
-
|
|
1363
|
-
const
|
|
1364
|
-
|
|
1365
|
-
this.inlineError = d;
|
|
1366
|
-
return;
|
|
1367
|
-
}
|
|
1565
|
+
case "submit": {
|
|
1566
|
+
const n2 = a2 ? ` ${styleText2("dim", a2)}` : "", r2 = i2 ? styleText2("gray", S_BAR) : "";
|
|
1567
|
+
return `${s}${r2}${n2}`;
|
|
1368
1568
|
}
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1569
|
+
case "cancel": {
|
|
1570
|
+
const n2 = a2 ? ` ${styleText2(["strikethrough", "dim"], a2)}` : "", r2 = i2 ? styleText2("gray", S_BAR) : "";
|
|
1571
|
+
return `${s}${r2}${n2}${a2.trim() ? `
|
|
1572
|
+
${r2}` : ""}`;
|
|
1573
|
+
}
|
|
1574
|
+
default: {
|
|
1575
|
+
const n2 = i2 ? `${styleText2("cyan", S_BAR)} ` : "", r2 = i2 ? styleText2("cyan", S_BAR_END) : "";
|
|
1576
|
+
return `${s}${n2}${o2}
|
|
1577
|
+
${r2}
|
|
1578
|
+
`;
|
|
1378
1579
|
}
|
|
1379
|
-
this.#r();
|
|
1380
|
-
const S = l2.indexOf("_");
|
|
1381
|
-
D ? (this.#n = true, this.#s = t2) : S >= 0 ? this.#e.positionInSegment = S : a >= 0 && n.index < this.#i.length - 1 ? (this.#e.segmentIndex = n.index + 1, this.#e.positionInSegment = 0, this.#n = true) : this.#e.positionInSegment = Math.min(u + 1, e.len - 1);
|
|
1382
1580
|
}
|
|
1383
1581
|
}
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
this.value = C(this.#t) ?? t2.defaultValue ?? undefined;
|
|
1582
|
+
}).prompt();
|
|
1583
|
+
|
|
1584
|
+
// src/config.ts
|
|
1585
|
+
var DEFAULT_ENDPOINT = "https://app.snapfail.com";
|
|
1586
|
+
var ENV_FILE = ".env";
|
|
1587
|
+
function tryLoadProjectKey(cwd = process.cwd(), pk) {
|
|
1588
|
+
if (pk)
|
|
1589
|
+
return { projectKey: pk, endpoint: DEFAULT_ENDPOINT };
|
|
1590
|
+
const fromEnv = process.env["SNAPFAIL_PROJECT_KEY"];
|
|
1591
|
+
if (fromEnv)
|
|
1592
|
+
return { projectKey: fromEnv, endpoint: DEFAULT_ENDPOINT };
|
|
1593
|
+
const envPath = resolve(cwd, ENV_FILE);
|
|
1594
|
+
if (existsSync(envPath)) {
|
|
1595
|
+
const match = readFileSync(envPath, "utf-8").match(/^SNAPFAIL_PROJECT_KEY=(.+)$/m);
|
|
1596
|
+
if (match)
|
|
1597
|
+
return { projectKey: match[1].trim(), endpoint: DEFAULT_ENDPOINT };
|
|
1401
1598
|
}
|
|
1599
|
+
return null;
|
|
1402
1600
|
}
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
return
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
const t2 = this.options[this.cursor];
|
|
1416
|
-
if (this.value === undefined && (this.value = []), t2.group === true) {
|
|
1417
|
-
const r = t2.value, e = this.getGroupItems(r);
|
|
1418
|
-
this.isGroupSelected(r) ? this.value = this.value.filter((s) => e.findIndex((i) => i.value === s) === -1) : this.value = [...this.value, ...e.map((s) => s.value)], this.value = Array.from(new Set(this.value));
|
|
1419
|
-
} else {
|
|
1420
|
-
const r = this.value.includes(t2.value);
|
|
1421
|
-
this.value = r ? this.value.filter((e) => e !== t2.value) : [...this.value, t2.value];
|
|
1422
|
-
}
|
|
1423
|
-
}
|
|
1424
|
-
constructor(t2) {
|
|
1425
|
-
super(t2, false);
|
|
1426
|
-
const { options: r } = t2;
|
|
1427
|
-
this.#t = t2.selectableGroups !== false, this.options = Object.entries(r).flatMap(([e, s]) => [
|
|
1428
|
-
{ value: e, group: true, label: e },
|
|
1429
|
-
...s.map((i) => ({ ...i, group: e }))
|
|
1430
|
-
]), this.value = [...t2.initialValues ?? []], this.cursor = Math.max(this.options.findIndex(({ value: e }) => e === t2.cursorAt), this.#t ? 0 : 1), this.on("cursor", (e) => {
|
|
1431
|
-
switch (e) {
|
|
1432
|
-
case "left":
|
|
1433
|
-
case "up": {
|
|
1434
|
-
this.cursor = this.cursor === 0 ? this.options.length - 1 : this.cursor - 1;
|
|
1435
|
-
const s = this.options[this.cursor]?.group === true;
|
|
1436
|
-
!this.#t && s && (this.cursor = this.cursor === 0 ? this.options.length - 1 : this.cursor - 1);
|
|
1437
|
-
break;
|
|
1438
|
-
}
|
|
1439
|
-
case "down":
|
|
1440
|
-
case "right": {
|
|
1441
|
-
this.cursor = this.cursor === this.options.length - 1 ? 0 : this.cursor + 1;
|
|
1442
|
-
const s = this.options[this.cursor]?.group === true;
|
|
1443
|
-
!this.#t && s && (this.cursor = this.cursor === this.options.length - 1 ? 0 : this.cursor + 1);
|
|
1444
|
-
break;
|
|
1445
|
-
}
|
|
1446
|
-
case "space":
|
|
1447
|
-
this.toggleValue();
|
|
1448
|
-
break;
|
|
1449
|
-
}
|
|
1450
|
-
});
|
|
1601
|
+
function loadConfig(cwd = process.cwd(), pk) {
|
|
1602
|
+
const key = tryLoadProjectKey(cwd, pk);
|
|
1603
|
+
if (key)
|
|
1604
|
+
return key;
|
|
1605
|
+
throw new Error('No project key found. Run "snapfail init" or set SNAPFAIL_PROJECT_KEY.');
|
|
1606
|
+
}
|
|
1607
|
+
async function resolveProjectKey(pk, interactive, sessionToken) {
|
|
1608
|
+
const config = tryLoadProjectKey(process.cwd(), pk);
|
|
1609
|
+
if (config)
|
|
1610
|
+
return config.projectKey;
|
|
1611
|
+
if (!interactive || !sessionToken) {
|
|
1612
|
+
throw new Error('No project key found. Run "snapfail init" or pass --pk.');
|
|
1451
1613
|
}
|
|
1452
|
-
}
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
if (this.state === "submit")
|
|
1461
|
-
return this.userInput;
|
|
1462
|
-
const t2 = this.userInput;
|
|
1463
|
-
if (this.cursor >= t2.length)
|
|
1464
|
-
return `${t2}\u2588`;
|
|
1465
|
-
const s = t2.slice(0, this.cursor), r = t2[this.cursor], e = t2.slice(this.cursor + 1);
|
|
1466
|
-
return r === `
|
|
1467
|
-
` ? `${s}\u2588
|
|
1468
|
-
${e}` : `${s}${styleText("inverse", r)}${e}`;
|
|
1614
|
+
const res = await fetch(`${DEFAULT_ENDPOINT}/api/cli/projects`, {
|
|
1615
|
+
headers: { Authorization: `Bearer ${sessionToken}` }
|
|
1616
|
+
});
|
|
1617
|
+
if (!res.ok)
|
|
1618
|
+
throw new Error(`Failed to fetch projects (${res.status})`);
|
|
1619
|
+
const { projects } = await res.json();
|
|
1620
|
+
if (projects.length === 0) {
|
|
1621
|
+
throw new Error('No projects found. Run "snapfail init" to create one.');
|
|
1469
1622
|
}
|
|
1470
|
-
|
|
1471
|
-
|
|
1623
|
+
if (projects.length === 1) {
|
|
1624
|
+
console.log(`Using project: ${projects[0].name}`);
|
|
1625
|
+
return projects[0].key;
|
|
1472
1626
|
}
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1627
|
+
const pick = await select({
|
|
1628
|
+
message: "Select a project",
|
|
1629
|
+
options: projects.map((proj) => ({ value: proj.key, label: proj.name, hint: proj.key }))
|
|
1630
|
+
});
|
|
1631
|
+
if (isCancel(pick)) {
|
|
1632
|
+
cancel("Cancelled.");
|
|
1633
|
+
process.exit(0);
|
|
1479
1634
|
}
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
return;
|
|
1635
|
+
return pick;
|
|
1636
|
+
}
|
|
1637
|
+
function writeProjectKey(projectKey, cwd = process.cwd(), keyName = "SNAPFAIL_PROJECT_KEY") {
|
|
1638
|
+
const envPath = resolve(cwd, ENV_FILE);
|
|
1639
|
+
const line = `${keyName}=${projectKey}`;
|
|
1640
|
+
const pattern = new RegExp(`^${keyName}=.*`, "m");
|
|
1641
|
+
if (existsSync(envPath)) {
|
|
1642
|
+
let content = readFileSync(envPath, "utf-8");
|
|
1643
|
+
if (pattern.test(content)) {
|
|
1644
|
+
content = content.replace(pattern, line);
|
|
1645
|
+
} else {
|
|
1646
|
+
content = content.trimEnd() + `
|
|
1647
|
+
${line}
|
|
1648
|
+
`;
|
|
1495
1649
|
}
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
`), this._cursor++, false);
|
|
1501
|
-
const r = this.#s;
|
|
1502
|
-
return this.#s = true, r ? (this.userInput[this.cursor - 1] === `
|
|
1503
|
-
` && (this._setUserInput(this.userInput.slice(0, this.cursor - 1) + this.userInput.slice(this.cursor)), this._cursor--), true) : (this.#r(`
|
|
1504
|
-
`), this._cursor++, false);
|
|
1505
|
-
}
|
|
1506
|
-
constructor(t2) {
|
|
1507
|
-
super(t2, false), this.#t = t2.showSubmit ?? false, this.on("key", (s, r) => {
|
|
1508
|
-
if (r?.name && o$1.has(r.name)) {
|
|
1509
|
-
this.#i(r.name);
|
|
1510
|
-
return;
|
|
1511
|
-
}
|
|
1512
|
-
if (s === "\t" && this.#t) {
|
|
1513
|
-
this.focused = this.focused === "editor" ? "submit" : "editor";
|
|
1514
|
-
return;
|
|
1515
|
-
}
|
|
1516
|
-
if (r?.name !== "return") {
|
|
1517
|
-
if (this.#s = false, r?.name === "backspace" && this.cursor > 0) {
|
|
1518
|
-
this._setUserInput(this.userInput.slice(0, this.cursor - 1) + this.userInput.slice(this.cursor)), this._cursor--;
|
|
1519
|
-
return;
|
|
1520
|
-
}
|
|
1521
|
-
if (r?.name === "delete" && this.cursor < this.userInput.length) {
|
|
1522
|
-
this._setUserInput(this.userInput.slice(0, this.cursor) + this.userInput.slice(this.cursor + 1));
|
|
1523
|
-
return;
|
|
1524
|
-
}
|
|
1525
|
-
s && (this.#t && this.focused === "submit" && (this.focused = "editor"), this.#r(s ?? ""), this._cursor++);
|
|
1526
|
-
}
|
|
1527
|
-
}), this.on("userInput", (s) => {
|
|
1528
|
-
this._setValue(s);
|
|
1529
|
-
}), this.on("finalize", () => {
|
|
1530
|
-
this.value || (this.value = t2.defaultValue), this.value === undefined && (this.value = "");
|
|
1531
|
-
});
|
|
1650
|
+
writeFileSync(envPath, content, "utf-8");
|
|
1651
|
+
} else {
|
|
1652
|
+
writeFileSync(envPath, `${line}
|
|
1653
|
+
`, "utf-8");
|
|
1532
1654
|
}
|
|
1533
1655
|
}
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
const
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
break;
|
|
1552
|
-
case "down":
|
|
1553
|
-
case "right":
|
|
1554
|
-
this.cursor = findCursor(this.cursor, 1, this.options);
|
|
1555
|
-
break;
|
|
1556
|
-
}
|
|
1557
|
-
this.changeValue();
|
|
1558
|
-
});
|
|
1656
|
+
|
|
1657
|
+
// src/session.ts
|
|
1658
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync, existsSync as existsSync2, rmSync } from "fs";
|
|
1659
|
+
import { join } from "path";
|
|
1660
|
+
import { homedir } from "os";
|
|
1661
|
+
var SESSION_DIR = join(homedir(), ".snapfail");
|
|
1662
|
+
var SESSION_FILE = join(SESSION_DIR, "auth.json");
|
|
1663
|
+
function readSession() {
|
|
1664
|
+
try {
|
|
1665
|
+
if (!existsSync2(SESSION_FILE))
|
|
1666
|
+
return null;
|
|
1667
|
+
const raw = JSON.parse(readFileSync2(SESSION_FILE, "utf-8"));
|
|
1668
|
+
if (!raw.token || !raw.endpoint)
|
|
1669
|
+
return null;
|
|
1670
|
+
return raw;
|
|
1671
|
+
} catch {
|
|
1672
|
+
return null;
|
|
1559
1673
|
}
|
|
1560
1674
|
}
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1675
|
+
function writeSession(session) {
|
|
1676
|
+
mkdirSync(SESSION_DIR, { recursive: true });
|
|
1677
|
+
writeFileSync2(SESSION_FILE, JSON.stringify(session, null, 2) + `
|
|
1678
|
+
`, "utf-8");
|
|
1679
|
+
}
|
|
1680
|
+
function clearSession() {
|
|
1681
|
+
try {
|
|
1682
|
+
if (existsSync2(SESSION_FILE))
|
|
1683
|
+
rmSync(SESSION_FILE);
|
|
1684
|
+
} catch {}
|
|
1685
|
+
}
|
|
1686
|
+
function requireSession() {
|
|
1687
|
+
const session = readSession();
|
|
1688
|
+
if (!session) {
|
|
1689
|
+
throw new Error('No active session. Run "snapfail init" to authenticate.');
|
|
1570
1690
|
}
|
|
1571
|
-
|
|
1572
|
-
|
|
1691
|
+
return session;
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
// src/api.ts
|
|
1695
|
+
async function listSuppressionRules(token, projectKey) {
|
|
1696
|
+
const url = `${DEFAULT_ENDPOINT}/api/cli/suppress?projectKey=${encodeURIComponent(projectKey)}`;
|
|
1697
|
+
const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
|
|
1698
|
+
if (!res.ok)
|
|
1699
|
+
throw new Error(`API error ${res.status}`);
|
|
1700
|
+
const { rules } = await res.json();
|
|
1701
|
+
return rules;
|
|
1702
|
+
}
|
|
1703
|
+
async function addSuppressionRule(token, projectKey, field, pattern) {
|
|
1704
|
+
const res = await fetch(`${DEFAULT_ENDPOINT}/api/cli/suppress`, {
|
|
1705
|
+
method: "POST",
|
|
1706
|
+
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
|
|
1707
|
+
body: JSON.stringify({ projectKey, field, pattern })
|
|
1708
|
+
});
|
|
1709
|
+
if (!res.ok) {
|
|
1710
|
+
const body = await res.text().catch(() => "");
|
|
1711
|
+
throw new Error(`API error ${res.status}: ${body}`);
|
|
1573
1712
|
}
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1713
|
+
const { rule } = await res.json();
|
|
1714
|
+
return rule;
|
|
1715
|
+
}
|
|
1716
|
+
async function removeSuppressionRule(token, projectKey, id) {
|
|
1717
|
+
const url = `${DEFAULT_ENDPOINT}/api/cli/suppress/${id}?projectKey=${encodeURIComponent(projectKey)}`;
|
|
1718
|
+
const res = await fetch(url, {
|
|
1719
|
+
method: "DELETE",
|
|
1720
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
1721
|
+
});
|
|
1722
|
+
if (!res.ok)
|
|
1723
|
+
throw new Error(`API error ${res.status}`);
|
|
1724
|
+
}
|
|
1725
|
+
async function apiFetch(url, options) {
|
|
1726
|
+
let res;
|
|
1727
|
+
try {
|
|
1728
|
+
res = await fetch(url, options);
|
|
1729
|
+
} catch (err) {
|
|
1730
|
+
throw new Error(`Network error: ${err instanceof Error ? err.message : String(err)}`);
|
|
1731
|
+
}
|
|
1732
|
+
if (res.status === 401)
|
|
1733
|
+
throw new Error("Unauthorized: check your project key.");
|
|
1734
|
+
if (res.status === 404)
|
|
1735
|
+
return null;
|
|
1736
|
+
if (!res.ok) {
|
|
1737
|
+
const body = await res.text().catch(() => "");
|
|
1738
|
+
throw new Error(`API error ${res.status}: ${body}`);
|
|
1583
1739
|
}
|
|
1740
|
+
return res.json();
|
|
1741
|
+
}
|
|
1742
|
+
async function fetchIncidents(config, opts = {}) {
|
|
1743
|
+
const url = new URL(`${config.endpoint}/api/incidents`);
|
|
1744
|
+
url.searchParams.set("projectKey", config.projectKey);
|
|
1745
|
+
if (opts.status)
|
|
1746
|
+
url.searchParams.set("status", opts.status);
|
|
1747
|
+
if (opts.limit != null)
|
|
1748
|
+
url.searchParams.set("limit", String(opts.limit));
|
|
1749
|
+
if (opts.offset != null)
|
|
1750
|
+
url.searchParams.set("offset", String(opts.offset));
|
|
1751
|
+
const data = await apiFetch(url.toString());
|
|
1752
|
+
return data;
|
|
1753
|
+
}
|
|
1754
|
+
async function fetchIncident(config, id) {
|
|
1755
|
+
const url = `${config.endpoint}/api/incidents/${id}`;
|
|
1756
|
+
const data = await apiFetch(url, {
|
|
1757
|
+
headers: { "X-Project-Key": config.projectKey }
|
|
1758
|
+
});
|
|
1759
|
+
return data;
|
|
1760
|
+
}
|
|
1761
|
+
async function deleteGroup(config, id) {
|
|
1762
|
+
const url = `${config.endpoint}/api/incidents/${id}`;
|
|
1763
|
+
const data = await apiFetch(url, {
|
|
1764
|
+
method: "DELETE",
|
|
1765
|
+
headers: { "X-Project-Key": config.projectKey }
|
|
1766
|
+
});
|
|
1767
|
+
return data !== null;
|
|
1768
|
+
}
|
|
1769
|
+
async function updateIncidentStatus(config, id, status) {
|
|
1770
|
+
const url = `${config.endpoint}/api/incidents/${id}`;
|
|
1771
|
+
const data = await apiFetch(url, {
|
|
1772
|
+
method: "PATCH",
|
|
1773
|
+
headers: { "X-Project-Key": config.projectKey, "Content-Type": "application/json" },
|
|
1774
|
+
body: JSON.stringify({ status })
|
|
1775
|
+
});
|
|
1776
|
+
return data !== null;
|
|
1777
|
+
}
|
|
1778
|
+
async function fetchSample(config, groupId, sampleIndex) {
|
|
1779
|
+
const url = `${config.endpoint}/api/incidents/${groupId}/samples/${sampleIndex}`;
|
|
1780
|
+
const data = await apiFetch(url, {
|
|
1781
|
+
headers: { "X-Project-Key": config.projectKey }
|
|
1782
|
+
});
|
|
1783
|
+
return data;
|
|
1584
1784
|
}
|
|
1585
1785
|
|
|
1586
|
-
//
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
var
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1786
|
+
// src/format.ts
|
|
1787
|
+
var ESC2 = "\x1B[";
|
|
1788
|
+
var bold = (s) => `${ESC2}1m${s}${ESC2}0m`;
|
|
1789
|
+
var dim = (s) => `${ESC2}2m${s}${ESC2}0m`;
|
|
1790
|
+
var red = (s) => `${ESC2}31m${s}${ESC2}0m`;
|
|
1791
|
+
var green = (s) => `${ESC2}32m${s}${ESC2}0m`;
|
|
1792
|
+
var yellow = (s) => `${ESC2}33m${s}${ESC2}0m`;
|
|
1793
|
+
var cyan = (s) => `${ESC2}36m${s}${ESC2}0m`;
|
|
1794
|
+
function truncate(s, n2) {
|
|
1795
|
+
if (s.length <= n2)
|
|
1796
|
+
return s;
|
|
1797
|
+
return s.slice(0, n2 - 1) + "\u2026";
|
|
1595
1798
|
}
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
var symbol = (e) => {
|
|
1625
|
-
switch (e) {
|
|
1626
|
-
case "initial":
|
|
1627
|
-
case "active":
|
|
1628
|
-
return styleText2("cyan", S_STEP_ACTIVE);
|
|
1629
|
-
case "cancel":
|
|
1630
|
-
return styleText2("red", S_STEP_CANCEL);
|
|
1631
|
-
case "error":
|
|
1632
|
-
return styleText2("yellow", S_STEP_ERROR);
|
|
1633
|
-
case "submit":
|
|
1634
|
-
return styleText2("green", S_STEP_SUBMIT);
|
|
1799
|
+
function relativeTime(ts) {
|
|
1800
|
+
const diff = Date.now() - ts;
|
|
1801
|
+
const s = Math.floor(diff / 1000);
|
|
1802
|
+
if (s < 60)
|
|
1803
|
+
return `${s}s ago`;
|
|
1804
|
+
const m2 = Math.floor(s / 60);
|
|
1805
|
+
if (m2 < 60)
|
|
1806
|
+
return `${m2}m ago`;
|
|
1807
|
+
const h2 = Math.floor(m2 / 60);
|
|
1808
|
+
if (h2 < 24)
|
|
1809
|
+
return `${h2}h ago`;
|
|
1810
|
+
const d = Math.floor(h2 / 24);
|
|
1811
|
+
return `${d}d ago`;
|
|
1812
|
+
}
|
|
1813
|
+
function formatTable(rows, headers) {
|
|
1814
|
+
const allRows = [headers, ...rows];
|
|
1815
|
+
const widths = headers.map((_2, i2) => Math.max(...allRows.map((r2) => (r2[i2] ?? "").length)));
|
|
1816
|
+
const pad = (s, w) => s + " ".repeat(Math.max(0, w - s.length));
|
|
1817
|
+
const header = " " + headers.map((h2, i2) => bold(pad(h2, widths[i2]))).join(" ");
|
|
1818
|
+
const divider = " " + widths.map((w) => dim("\u2500".repeat(w))).join(" ");
|
|
1819
|
+
const body = rows.map((r2) => " " + r2.map((cell, i2) => pad(cell, widths[i2])).join(" ")).join(`
|
|
1820
|
+
`);
|
|
1821
|
+
return [header, divider, body].join(`
|
|
1822
|
+
`);
|
|
1823
|
+
}
|
|
1824
|
+
function formatIncidentList(data, total, status) {
|
|
1825
|
+
if (data.length === 0) {
|
|
1826
|
+
return dim(`No ${status ?? "unresolved"} incidents found.`);
|
|
1635
1827
|
}
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1828
|
+
const rows = data.map((g2) => [
|
|
1829
|
+
cyan(truncate(g2.id, 12)),
|
|
1830
|
+
truncate(g2.title, 38),
|
|
1831
|
+
String(g2.count),
|
|
1832
|
+
g2.environments.join(","),
|
|
1833
|
+
relativeTime(g2.lastSeen)
|
|
1834
|
+
]);
|
|
1835
|
+
const table = formatTable(rows, ["ID", "TITLE", "COUNT", "ENV", "LAST SEEN"]);
|
|
1836
|
+
const summary = `
|
|
1837
|
+
${bold(String(total))} ${status ?? "unresolved"} incident${total !== 1 ? "s" : ""}`;
|
|
1838
|
+
return table + summary;
|
|
1839
|
+
}
|
|
1840
|
+
function formatLLMContext(group, samples) {
|
|
1841
|
+
const lines = [];
|
|
1842
|
+
lines.push(`# Incident: ${group.title}`);
|
|
1843
|
+
lines.push(`Group ID: ${group.id}`);
|
|
1844
|
+
lines.push(`Fingerprint: ${group.fingerprint}`);
|
|
1845
|
+
lines.push(`Occurrences: ${group.count} | First: ${new Date(group.firstSeen).toISOString()} | Last: ${new Date(group.lastSeen).toISOString()}`);
|
|
1846
|
+
lines.push(`Environments: ${group.environments.join(", ")}`);
|
|
1847
|
+
lines.push(`Status: ${group.status}`);
|
|
1848
|
+
lines.push("");
|
|
1849
|
+
lines.push("## Error");
|
|
1850
|
+
lines.push(`Type: ${group.errorType}`);
|
|
1851
|
+
lines.push(`Message: ${samples[0]?.errorMessage ?? group.title}`);
|
|
1852
|
+
lines.push(`Normalized: ${samples[0]?.normalizedMessage ?? group.title}`);
|
|
1853
|
+
if (samples[0] && samples[0].stackFrames.length > 0) {
|
|
1854
|
+
lines.push("");
|
|
1855
|
+
lines.push("## Stack");
|
|
1856
|
+
for (const f2 of samples[0].stackFrames) {
|
|
1857
|
+
lines.push(` ${f2.fn ?? "(anonymous)"} ${f2.file}:${f2.line}${f2.col != null ? `:${f2.col}` : ""}`);
|
|
1858
|
+
}
|
|
1648
1859
|
}
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
};
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
}
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1860
|
+
for (let i2 = 0;i2 < samples.length; i2++) {
|
|
1861
|
+
const s = samples[i2];
|
|
1862
|
+
lines.push("");
|
|
1863
|
+
lines.push(`## Sample ${i2 + 1} / ${group.sampleIds.length}`);
|
|
1864
|
+
lines.push(`Device: ${s.device.userAgent}`);
|
|
1865
|
+
if (s.device.viewport) {
|
|
1866
|
+
lines.push(`Viewport: ${s.device.viewport.width}x${s.device.viewport.height} Language: ${s.device.language ?? "unknown"}`);
|
|
1867
|
+
}
|
|
1868
|
+
lines.push(`URL: ${s.url}`);
|
|
1869
|
+
if (s.route)
|
|
1870
|
+
lines.push(`Route: ${s.route}`);
|
|
1871
|
+
lines.push(`Environment: ${s.environmentMode}`);
|
|
1872
|
+
if (s.consoleEntries.length > 0) {
|
|
1873
|
+
lines.push("");
|
|
1874
|
+
lines.push("### Console");
|
|
1875
|
+
for (const e of s.consoleEntries) {
|
|
1876
|
+
const args = e.args.map((a2) => String(a2)).join(" ");
|
|
1877
|
+
lines.push(` [${e.level}] ${args}`);
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
if (s.networkEntries.length > 0) {
|
|
1881
|
+
lines.push("");
|
|
1882
|
+
lines.push("### Network");
|
|
1883
|
+
for (const n2 of s.networkEntries) {
|
|
1884
|
+
const status = n2.status ? ` \u2192 ${n2.status}` : n2.error ? ` \u2192 ERROR` : "";
|
|
1885
|
+
lines.push(` ${n2.method} ${n2.url}${status} (${n2.durationMs}ms)`);
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
if (s.timeline.length > 0) {
|
|
1889
|
+
lines.push("");
|
|
1890
|
+
lines.push("### Timeline");
|
|
1891
|
+
for (const e of s.timeline) {
|
|
1892
|
+
lines.push(` +${e.t}ms ${e.kind}: ${e.summary}`);
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
if (i2 < samples.length - 1)
|
|
1896
|
+
lines.push(`
|
|
1897
|
+
---`);
|
|
1898
|
+
}
|
|
1899
|
+
return lines.join(`
|
|
1682
1900
|
`);
|
|
1683
|
-
|
|
1901
|
+
}
|
|
1902
|
+
function formatIncidentDetail(group, sample) {
|
|
1903
|
+
const lines = [];
|
|
1904
|
+
lines.push(bold(`${group.errorType}: ${truncate(group.title, 80)}`));
|
|
1905
|
+
lines.push(`${dim("Status:")} ${group.status} ${dim("\xB7")} ${group.count} occurrences ${dim("\xB7")} ${group.environments.join(", ")}`);
|
|
1906
|
+
lines.push(`${dim("First seen:")} ${new Date(group.firstSeen).toISOString().slice(0, 10)} ${dim("\xB7")} ${dim("Last seen:")} ${relativeTime(group.lastSeen)}`);
|
|
1907
|
+
if (sample.stackFrames.length > 0) {
|
|
1908
|
+
lines.push("");
|
|
1909
|
+
lines.push(bold("Stack"));
|
|
1910
|
+
for (const f2 of sample.stackFrames.slice(0, 6)) {
|
|
1911
|
+
const fn = f2.fn ? `${cyan(f2.fn)} ` : "";
|
|
1912
|
+
lines.push(` ${fn}${dim(f2.file + ":" + f2.line)}`);
|
|
1913
|
+
if (f2.codeSnippet) {
|
|
1914
|
+
for (const snippetLine of f2.codeSnippet.split(`
|
|
1915
|
+
`)) {
|
|
1916
|
+
const isError = snippetLine.startsWith("\u25BA");
|
|
1917
|
+
lines.push(isError ? ` ${bold(snippetLine)}` : ` ${dim(snippetLine)}`);
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1684
1921
|
}
|
|
1685
|
-
if (
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1922
|
+
if (sample.consoleEntries.length > 0) {
|
|
1923
|
+
lines.push("");
|
|
1924
|
+
lines.push(bold("Console"));
|
|
1925
|
+
for (const e of sample.consoleEntries.slice(-5)) {
|
|
1926
|
+
const level = e.level === "error" ? red(`[${e.level}]`) : yellow(`[${e.level}]`);
|
|
1927
|
+
const args = e.args.map((a2) => truncate(String(a2), 60)).join(" ");
|
|
1928
|
+
lines.push(` ${level} ${args}`);
|
|
1929
|
+
}
|
|
1691
1930
|
}
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
for (const n2 of
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
};
|
|
1699
|
-
var log = {
|
|
1700
|
-
message: (s = [], {
|
|
1701
|
-
symbol: e = styleText2("gray", S_BAR),
|
|
1702
|
-
secondarySymbol: r2 = styleText2("gray", S_BAR),
|
|
1703
|
-
output: m2 = process.stdout,
|
|
1704
|
-
spacing: l2 = 1,
|
|
1705
|
-
withGuide: c2
|
|
1706
|
-
} = {}) => {
|
|
1707
|
-
const t2 = [], o2 = c2 ?? settings.withGuide, f2 = o2 ? r2 : "", O = o2 ? `${e} ` : "", u3 = o2 ? `${r2} ` : "";
|
|
1708
|
-
for (let i = 0;i < l2; i++)
|
|
1709
|
-
t2.push(f2);
|
|
1710
|
-
const g2 = Array.isArray(s) ? s : s.split(`
|
|
1711
|
-
`);
|
|
1712
|
-
if (g2.length > 0) {
|
|
1713
|
-
const [i, ...y] = g2;
|
|
1714
|
-
i.length > 0 ? t2.push(`${O}${i}`) : t2.push(o2 ? e : "");
|
|
1715
|
-
for (const p2 of y)
|
|
1716
|
-
p2.length > 0 ? t2.push(`${u3}${p2}`) : t2.push(o2 ? r2 : "");
|
|
1931
|
+
if (sample.networkEntries.length > 0) {
|
|
1932
|
+
lines.push("");
|
|
1933
|
+
lines.push(bold("Network"));
|
|
1934
|
+
for (const n2 of sample.networkEntries.slice(-5)) {
|
|
1935
|
+
const status = n2.status ? n2.status >= 400 ? red(String(n2.status)) : green(String(n2.status)) : dim("???");
|
|
1936
|
+
lines.push(` ${dim(n2.method)} ${truncate(n2.url, 50)} ${dim("\u2192")} ${status} ${dim(`(${n2.durationMs}ms)`)}`);
|
|
1717
1937
|
}
|
|
1718
|
-
m2.write(`${t2.join(`
|
|
1719
|
-
`)}
|
|
1720
|
-
`);
|
|
1721
|
-
},
|
|
1722
|
-
info: (s, e) => {
|
|
1723
|
-
log.message(s, { ...e, symbol: styleText2("blue", S_INFO) });
|
|
1724
|
-
},
|
|
1725
|
-
success: (s, e) => {
|
|
1726
|
-
log.message(s, { ...e, symbol: styleText2("green", S_SUCCESS) });
|
|
1727
|
-
},
|
|
1728
|
-
step: (s, e) => {
|
|
1729
|
-
log.message(s, { ...e, symbol: styleText2("green", S_STEP_SUBMIT) });
|
|
1730
|
-
},
|
|
1731
|
-
warn: (s, e) => {
|
|
1732
|
-
log.message(s, { ...e, symbol: styleText2("yellow", S_WARN) });
|
|
1733
|
-
},
|
|
1734
|
-
warning: (s, e) => {
|
|
1735
|
-
log.warn(s, e);
|
|
1736
|
-
},
|
|
1737
|
-
error: (s, e) => {
|
|
1738
|
-
log.message(s, { ...e, symbol: styleText2("red", S_ERROR) });
|
|
1739
1938
|
}
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
`);
|
|
1746
|
-
}
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
i.write(`${e}${o2}
|
|
1939
|
+
if (sample.timeline.length > 0) {
|
|
1940
|
+
lines.push("");
|
|
1941
|
+
lines.push(bold("Timeline"));
|
|
1942
|
+
for (const e of sample.timeline.slice(0, 10)) {
|
|
1943
|
+
const t2 = String(e.t).padStart(5);
|
|
1944
|
+
lines.push(` ${dim(t2 + "ms")} ${e.summary}`);
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
return lines.join(`
|
|
1750
1948
|
`);
|
|
1751
|
-
}
|
|
1752
|
-
var outro = (o2 = "", t2) => {
|
|
1753
|
-
const i = t2?.output ?? process.stdout, e = t2?.withGuide ?? settings.withGuide ? `${styleText2("gray", S_BAR)}
|
|
1754
|
-
${styleText2("gray", S_BAR_END)} ` : "";
|
|
1755
|
-
i.write(`${e}${o2}
|
|
1949
|
+
}
|
|
1756
1950
|
|
|
1951
|
+
// src/commands/incidents.ts
|
|
1952
|
+
var DEFAULT_LIMIT = 5;
|
|
1953
|
+
async function runIncidents(opts) {
|
|
1954
|
+
const session = requireSession();
|
|
1955
|
+
let projectKey;
|
|
1956
|
+
try {
|
|
1957
|
+
projectKey = await resolveProjectKey(opts.pk, !opts.json, session.token);
|
|
1958
|
+
} catch (err) {
|
|
1959
|
+
if (!opts.json) {
|
|
1960
|
+
console.error(err.message);
|
|
1961
|
+
console.error('Run "snapfail init" to set up a project.');
|
|
1962
|
+
} else {
|
|
1963
|
+
process.stdout.write(JSON.stringify({ error: err.message }) + `
|
|
1757
1964
|
`);
|
|
1758
|
-
}
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
} = {}) => {
|
|
1771
|
-
const u3 = isCI();
|
|
1772
|
-
let M2, T3, d = false, S = false, s = "", p2, w = performance.now();
|
|
1773
|
-
const x = getColumns(n2), k = I?.styleFrame ?? W, g2 = (e) => {
|
|
1774
|
-
const r2 = e > 1 ? O ?? settings.messages.error : G ?? settings.messages.cancel;
|
|
1775
|
-
S = e === 1, d && (a2(r2, e), S && typeof h2 == "function" && h2());
|
|
1776
|
-
}, f2 = () => g2(2), i = () => g2(1), A = () => {
|
|
1777
|
-
process.on("uncaughtExceptionMonitor", f2), process.on("unhandledRejection", f2), process.on("SIGINT", i), process.on("SIGTERM", i), process.on("exit", g2), m2 && m2.addEventListener("abort", i);
|
|
1778
|
-
}, H = () => {
|
|
1779
|
-
process.removeListener("uncaughtExceptionMonitor", f2), process.removeListener("unhandledRejection", f2), process.removeListener("SIGINT", i), process.removeListener("SIGTERM", i), process.removeListener("exit", g2), m2 && m2.removeEventListener("abort", i);
|
|
1780
|
-
}, y = () => {
|
|
1781
|
-
if (p2 === undefined)
|
|
1782
|
-
return;
|
|
1783
|
-
u3 && n2.write(`
|
|
1965
|
+
}
|
|
1966
|
+
process.exit(1);
|
|
1967
|
+
}
|
|
1968
|
+
const config = loadConfig(process.cwd(), projectKey);
|
|
1969
|
+
const limit = opts.limit ?? DEFAULT_LIMIT;
|
|
1970
|
+
const result = await fetchIncidents(config, {
|
|
1971
|
+
status: opts.status ?? "unresolved",
|
|
1972
|
+
limit,
|
|
1973
|
+
offset: opts.offset
|
|
1974
|
+
});
|
|
1975
|
+
if (opts.json) {
|
|
1976
|
+
process.stdout.write(JSON.stringify(result, null, 2) + `
|
|
1784
1977
|
`);
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1978
|
+
return;
|
|
1979
|
+
}
|
|
1980
|
+
const statusLabel = opts.status ?? "unresolved";
|
|
1981
|
+
const offset = opts.offset ?? 0;
|
|
1982
|
+
const showing = result.data.length;
|
|
1983
|
+
const total = result.total;
|
|
1984
|
+
console.log(`
|
|
1985
|
+
Incidents (${statusLabel}) \u2014 showing ${offset + 1}\u2013${offset + showing} of ${total} groups`);
|
|
1986
|
+
if (total > offset + showing) {
|
|
1987
|
+
console.log(` Use --limit ${limit * 2} or --offset ${offset + showing} to see more.
|
|
1789
1988
|
`);
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1989
|
+
} else {
|
|
1990
|
+
console.log("");
|
|
1991
|
+
}
|
|
1992
|
+
console.log(formatIncidentList(result.data, result.total, opts.status));
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1995
|
+
// src/commands/incident.ts
|
|
1996
|
+
async function runIncident(opts) {
|
|
1997
|
+
const session = requireSession();
|
|
1998
|
+
let projectKey;
|
|
1999
|
+
try {
|
|
2000
|
+
projectKey = await resolveProjectKey(opts.pk, !opts.json, session.token);
|
|
2001
|
+
} catch (err) {
|
|
2002
|
+
if (!opts.json) {
|
|
2003
|
+
console.error(err.message);
|
|
2004
|
+
} else {
|
|
2005
|
+
process.stdout.write(JSON.stringify({ error: err.message }) + `
|
|
1796
2006
|
`);
|
|
1797
|
-
let r2 = 0, t2 = 0;
|
|
1798
|
-
A(), T3 = setInterval(() => {
|
|
1799
|
-
if (u3 && s === p2)
|
|
1800
|
-
return;
|
|
1801
|
-
y(), p2 = s;
|
|
1802
|
-
const o2 = k(E[r2]);
|
|
1803
|
-
let v;
|
|
1804
|
-
if (u3)
|
|
1805
|
-
v = `${o2} ${s}...`;
|
|
1806
|
-
else if (l2 === "timer")
|
|
1807
|
-
v = `${o2} ${s} ${_2(w)}`;
|
|
1808
|
-
else {
|
|
1809
|
-
const B = ".".repeat(Math.floor(t2)).slice(0, 3);
|
|
1810
|
-
v = `${o2} ${s}${B}`;
|
|
1811
|
-
}
|
|
1812
|
-
const j = wrapAnsi(v, x, {
|
|
1813
|
-
hard: true,
|
|
1814
|
-
trim: false
|
|
1815
|
-
});
|
|
1816
|
-
n2.write(j), r2 = r2 + 1 < E.length ? r2 + 1 : 0, t2 = t2 < 4 ? t2 + 0.125 : 0;
|
|
1817
|
-
}, F);
|
|
1818
|
-
}, a2 = (e = "", r2 = 0, t2 = false) => {
|
|
1819
|
-
if (!d)
|
|
1820
|
-
return;
|
|
1821
|
-
d = false, clearInterval(T3), y();
|
|
1822
|
-
const o2 = r2 === 0 ? styleText2("green", S_STEP_SUBMIT) : r2 === 1 ? styleText2("red", S_STEP_CANCEL) : styleText2("red", S_STEP_ERROR);
|
|
1823
|
-
s = e ?? s, t2 || (l2 === "timer" ? n2.write(`${o2} ${s} ${_2(w)}
|
|
1824
|
-
`) : n2.write(`${o2} ${s}
|
|
1825
|
-
`)), H(), M2();
|
|
1826
|
-
};
|
|
1827
|
-
return {
|
|
1828
|
-
start: P2,
|
|
1829
|
-
stop: (e = "") => a2(e, 0),
|
|
1830
|
-
message: (e = "") => {
|
|
1831
|
-
s = C2(e ?? s);
|
|
1832
|
-
},
|
|
1833
|
-
cancel: (e = "") => a2(e, 1),
|
|
1834
|
-
error: (e = "") => a2(e, 2),
|
|
1835
|
-
clear: () => a2("", 0, true),
|
|
1836
|
-
get isCancelled() {
|
|
1837
|
-
return S;
|
|
1838
2007
|
}
|
|
1839
|
-
|
|
1840
|
-
}
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
};
|
|
1846
|
-
|
|
1847
|
-
`) ? e.split(`
|
|
1848
|
-
`).map((t2) => a2(t2)).join(`
|
|
1849
|
-
`) : a2(e);
|
|
1850
|
-
var select = (e) => {
|
|
1851
|
-
const a2 = (t2, d) => {
|
|
1852
|
-
const s = t2.label ?? String(t2.value);
|
|
1853
|
-
switch (d) {
|
|
1854
|
-
case "disabled":
|
|
1855
|
-
return `${styleText2("gray", S_RADIO_INACTIVE)} ${c2(s, (n2) => styleText2("gray", n2))}${t2.hint ? ` ${styleText2("dim", `(${t2.hint ?? "disabled"})`)}` : ""}`;
|
|
1856
|
-
case "selected":
|
|
1857
|
-
return `${c2(s, (n2) => styleText2("dim", n2))}`;
|
|
1858
|
-
case "active":
|
|
1859
|
-
return `${styleText2("green", S_RADIO_ACTIVE)} ${s}${t2.hint ? ` ${styleText2("dim", `(${t2.hint})`)}` : ""}`;
|
|
1860
|
-
case "cancelled":
|
|
1861
|
-
return `${c2(s, (n2) => styleText2(["strikethrough", "dim"], n2))}`;
|
|
1862
|
-
default:
|
|
1863
|
-
return `${styleText2("dim", S_RADIO_INACTIVE)} ${c2(s, (n2) => styleText2("dim", n2))}`;
|
|
2008
|
+
process.exit(1);
|
|
2009
|
+
}
|
|
2010
|
+
const config = loadConfig(process.cwd(), projectKey);
|
|
2011
|
+
if (opts.delete) {
|
|
2012
|
+
const ok = await deleteGroup(config, opts.id);
|
|
2013
|
+
if (!ok) {
|
|
2014
|
+
console.error(`Incident group ${opts.id} not found.`);
|
|
2015
|
+
process.exit(1);
|
|
1864
2016
|
}
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
output: e.output,
|
|
1871
|
-
initialValue: e.initialValue,
|
|
1872
|
-
render() {
|
|
1873
|
-
const t2 = e.withGuide ?? settings.withGuide, d = `${symbol(this.state)} `, s = `${symbolBar(this.state)} `, n2 = wrapTextWithPrefix(e.output, e.message, s, d), u4 = `${t2 ? `${styleText2("gray", S_BAR)}
|
|
1874
|
-
` : ""}${n2}
|
|
1875
|
-
`;
|
|
1876
|
-
switch (this.state) {
|
|
1877
|
-
case "submit": {
|
|
1878
|
-
const r2 = t2 ? `${styleText2("gray", S_BAR)} ` : "", l2 = wrapTextWithPrefix(e.output, a2(this.options[this.cursor], "selected"), r2);
|
|
1879
|
-
return `${u4}${l2}`;
|
|
1880
|
-
}
|
|
1881
|
-
case "cancel": {
|
|
1882
|
-
const r2 = t2 ? `${styleText2("gray", S_BAR)} ` : "", l2 = wrapTextWithPrefix(e.output, a2(this.options[this.cursor], "cancelled"), r2);
|
|
1883
|
-
return `${u4}${l2}${t2 ? `
|
|
1884
|
-
${styleText2("gray", S_BAR)}` : ""}`;
|
|
1885
|
-
}
|
|
1886
|
-
default: {
|
|
1887
|
-
const r2 = t2 ? `${styleText2("cyan", S_BAR)} ` : "", l2 = t2 ? styleText2("cyan", S_BAR_END) : "", g2 = u4.split(`
|
|
1888
|
-
`).length, h2 = t2 ? 2 : 1;
|
|
1889
|
-
return `${u4}${r2}${limitOptions({
|
|
1890
|
-
output: e.output,
|
|
1891
|
-
cursor: this.cursor,
|
|
1892
|
-
options: this.options,
|
|
1893
|
-
maxItems: e.maxItems,
|
|
1894
|
-
columnPadding: r2.length,
|
|
1895
|
-
rowPadding: g2 + h2,
|
|
1896
|
-
style: (p2, b2) => a2(p2, p2.disabled ? "disabled" : b2 ? "active" : "inactive")
|
|
1897
|
-
}).join(`
|
|
1898
|
-
${r2}`)}
|
|
1899
|
-
${l2}
|
|
1900
|
-
`;
|
|
1901
|
-
}
|
|
1902
|
-
}
|
|
2017
|
+
if (opts.json) {
|
|
2018
|
+
process.stdout.write(JSON.stringify({ ok: true, deleted: opts.id }) + `
|
|
2019
|
+
`);
|
|
2020
|
+
} else {
|
|
2021
|
+
console.log(`Deleted incident group ${opts.id} and all related data.`);
|
|
1903
2022
|
}
|
|
1904
|
-
|
|
1905
|
-
}
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
}
|
|
1936
|
-
|
|
1937
|
-
const n2 = i2 ? `${styleText2("cyan", S_BAR)} ` : "", r2 = i2 ? styleText2("cyan", S_BAR_END) : "";
|
|
1938
|
-
return `${s}${n2}${o2}
|
|
1939
|
-
${r2}
|
|
1940
|
-
`;
|
|
1941
|
-
}
|
|
2023
|
+
return;
|
|
2024
|
+
}
|
|
2025
|
+
const statusAction = opts.resolve ? "resolved" : opts.ignore ? "ignored" : opts.reopen ? "unresolved" : null;
|
|
2026
|
+
if (statusAction) {
|
|
2027
|
+
const ok = await updateIncidentStatus(config, opts.id, statusAction);
|
|
2028
|
+
if (!ok) {
|
|
2029
|
+
console.error(`Incident ${opts.id} not found.`);
|
|
2030
|
+
process.exit(1);
|
|
2031
|
+
}
|
|
2032
|
+
if (opts.json) {
|
|
2033
|
+
process.stdout.write(JSON.stringify({ ok: true, id: opts.id, status: statusAction }) + `
|
|
2034
|
+
`);
|
|
2035
|
+
} else {
|
|
2036
|
+
const label = statusAction === "resolved" ? "resolved" : statusAction === "ignored" ? "ignored" : "reopened";
|
|
2037
|
+
console.log(`Incident ${opts.id} marked as ${label}.`);
|
|
2038
|
+
}
|
|
2039
|
+
return;
|
|
2040
|
+
}
|
|
2041
|
+
if (opts.sample != null) {
|
|
2042
|
+
const s = await fetchSample(config, opts.id, opts.sample);
|
|
2043
|
+
if (!s) {
|
|
2044
|
+
console.error(`Sample ${opts.sample} not found for incident ${opts.id}`);
|
|
2045
|
+
process.exit(1);
|
|
2046
|
+
}
|
|
2047
|
+
if (opts.json) {
|
|
2048
|
+
process.stdout.write(JSON.stringify(s, null, 2) + `
|
|
2049
|
+
`);
|
|
2050
|
+
return;
|
|
2051
|
+
}
|
|
2052
|
+
const detail = await fetchIncident(config, opts.id);
|
|
2053
|
+
if (!detail) {
|
|
2054
|
+
console.error(`Incident ${opts.id} not found.`);
|
|
2055
|
+
process.exit(1);
|
|
1942
2056
|
}
|
|
2057
|
+
console.log(formatIncidentDetail(detail.group, s));
|
|
2058
|
+
return;
|
|
1943
2059
|
}
|
|
1944
|
-
|
|
2060
|
+
const result = await fetchIncident(config, opts.id);
|
|
2061
|
+
if (!result) {
|
|
2062
|
+
console.error(`Incident ${opts.id} not found.`);
|
|
2063
|
+
process.exit(1);
|
|
2064
|
+
}
|
|
2065
|
+
if (opts.json) {
|
|
2066
|
+
process.stdout.write(JSON.stringify(result, null, 2) + `
|
|
2067
|
+
`);
|
|
2068
|
+
return;
|
|
2069
|
+
}
|
|
2070
|
+
console.log(formatIncidentDetail(result.group, result.sample));
|
|
2071
|
+
}
|
|
2072
|
+
|
|
2073
|
+
// src/commands/init.ts
|
|
2074
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2 } from "fs";
|
|
2075
|
+
import { resolve as resolve2, join as join2 } from "path";
|
|
2076
|
+
import { execSync as execSync2 } from "child_process";
|
|
1945
2077
|
|
|
1946
2078
|
// src/skill.ts
|
|
1947
2079
|
var SKILL_BODY = `
|
|
@@ -2059,8 +2191,8 @@ async function waitForCliToken(endpoint) {
|
|
|
2059
2191
|
resolved = true;
|
|
2060
2192
|
const session = { token, email, name: name ?? email, endpoint };
|
|
2061
2193
|
writeSession(session);
|
|
2062
|
-
setTimeout(() => server.stop(), 200);
|
|
2063
2194
|
resolve2(session);
|
|
2195
|
+
setTimeout(() => server.stop(true), 50);
|
|
2064
2196
|
return new Response(`<!doctype html><html><head><meta charset="utf-8"><title>snapfail</title>
|
|
2065
2197
|
<style>
|
|
2066
2198
|
body{background:#0a0a0a;color:#f5f5f5;font-family:system-ui,sans-serif;
|
|
@@ -2390,8 +2522,20 @@ async function runInit(cwd = process.cwd()) {
|
|
|
2390
2522
|
|
|
2391
2523
|
// src/commands/explain.ts
|
|
2392
2524
|
async function runExplain(opts) {
|
|
2393
|
-
requireSession();
|
|
2394
|
-
|
|
2525
|
+
const session = requireSession();
|
|
2526
|
+
let projectKey;
|
|
2527
|
+
try {
|
|
2528
|
+
projectKey = await resolveProjectKey(opts.pk, !opts.json, session.token);
|
|
2529
|
+
} catch (err) {
|
|
2530
|
+
if (!opts.json) {
|
|
2531
|
+
console.error(err.message);
|
|
2532
|
+
} else {
|
|
2533
|
+
process.stdout.write(JSON.stringify({ error: err.message }) + `
|
|
2534
|
+
`);
|
|
2535
|
+
}
|
|
2536
|
+
process.exit(1);
|
|
2537
|
+
}
|
|
2538
|
+
const config = loadConfig(process.cwd(), projectKey);
|
|
2395
2539
|
const result = await fetchIncident(config, opts.id);
|
|
2396
2540
|
if (!result) {
|
|
2397
2541
|
console.error(`Incident ${opts.id} not found.`);
|
|
@@ -2436,10 +2580,386 @@ async function runLogin() {
|
|
|
2436
2580
|
console.log(`
|
|
2437
2581
|
Logged in as ${session.email}`);
|
|
2438
2582
|
}
|
|
2583
|
+
|
|
2584
|
+
// src/commands/suppress.ts
|
|
2585
|
+
var FIELD_LABELS = {
|
|
2586
|
+
message: "Error message contains",
|
|
2587
|
+
useragent: "User-agent contains",
|
|
2588
|
+
url: "URL contains",
|
|
2589
|
+
script_origin: "Script origin contains"
|
|
2590
|
+
};
|
|
2591
|
+
async function runSuppress(opts) {
|
|
2592
|
+
const session = requireSession();
|
|
2593
|
+
const projectKey = await resolveProjectKey(opts.pk, !opts.json, session.token);
|
|
2594
|
+
const config = loadConfig(process.cwd(), projectKey);
|
|
2595
|
+
const sub = opts.subcommand;
|
|
2596
|
+
if (!sub || sub === "list") {
|
|
2597
|
+
const rules = await listSuppressionRules(session.token, projectKey);
|
|
2598
|
+
if (opts.json) {
|
|
2599
|
+
process.stdout.write(JSON.stringify({ rules }, null, 2) + `
|
|
2600
|
+
`);
|
|
2601
|
+
return;
|
|
2602
|
+
}
|
|
2603
|
+
if (rules.length === 0) {
|
|
2604
|
+
console.log("No suppression rules configured.");
|
|
2605
|
+
console.log("Add one with: snapfail suppress add or snapfail suppress <incident-id>");
|
|
2606
|
+
return;
|
|
2607
|
+
}
|
|
2608
|
+
console.log(`
|
|
2609
|
+
Suppression rules for ${projectKey}
|
|
2610
|
+
`);
|
|
2611
|
+
for (const rule2 of rules) {
|
|
2612
|
+
const label = FIELD_LABELS[rule2.field] ?? rule2.field;
|
|
2613
|
+
console.log(` [${rule2.id.slice(0, 8)}] ${label}: "${rule2.pattern}"`);
|
|
2614
|
+
}
|
|
2615
|
+
console.log(`
|
|
2616
|
+
Remove a rule: snapfail suppress delete <id-prefix>`);
|
|
2617
|
+
return;
|
|
2618
|
+
}
|
|
2619
|
+
if (sub === "delete") {
|
|
2620
|
+
const id = opts.args[0];
|
|
2621
|
+
if (!id) {
|
|
2622
|
+
console.error("Usage: snapfail suppress delete <rule-id>");
|
|
2623
|
+
process.exit(1);
|
|
2624
|
+
}
|
|
2625
|
+
const rules = await listSuppressionRules(session.token, projectKey);
|
|
2626
|
+
const match = rules.find((r2) => r2.id.startsWith(id));
|
|
2627
|
+
if (!match) {
|
|
2628
|
+
console.error(`No rule found with id starting with "${id}"`);
|
|
2629
|
+
process.exit(1);
|
|
2630
|
+
}
|
|
2631
|
+
await removeSuppressionRule(session.token, projectKey, match.id);
|
|
2632
|
+
if (opts.json) {
|
|
2633
|
+
process.stdout.write(JSON.stringify({ ok: true, deleted: match.id }) + `
|
|
2634
|
+
`);
|
|
2635
|
+
} else {
|
|
2636
|
+
const label = FIELD_LABELS[match.field] ?? match.field;
|
|
2637
|
+
console.log(`Deleted rule: ${label} "${match.pattern}"`);
|
|
2638
|
+
}
|
|
2639
|
+
return;
|
|
2640
|
+
}
|
|
2641
|
+
if (sub === "add") {
|
|
2642
|
+
const field = await select({
|
|
2643
|
+
message: "What field should the rule match on?",
|
|
2644
|
+
options: Object.entries(FIELD_LABELS).map(([value, label]) => ({
|
|
2645
|
+
value,
|
|
2646
|
+
label
|
|
2647
|
+
}))
|
|
2648
|
+
});
|
|
2649
|
+
if (isCancel(field)) {
|
|
2650
|
+
cancel("Cancelled.");
|
|
2651
|
+
process.exit(0);
|
|
2652
|
+
}
|
|
2653
|
+
const pattern = await text({
|
|
2654
|
+
message: "Pattern (case-insensitive substring)",
|
|
2655
|
+
placeholder: "e.g. Instagram",
|
|
2656
|
+
validate: (v) => (v ?? "").trim() ? undefined : "Pattern is required."
|
|
2657
|
+
});
|
|
2658
|
+
if (isCancel(pattern)) {
|
|
2659
|
+
cancel("Cancelled.");
|
|
2660
|
+
process.exit(0);
|
|
2661
|
+
}
|
|
2662
|
+
const rule2 = await addSuppressionRule(session.token, projectKey, field, pattern.trim());
|
|
2663
|
+
console.log(`
|
|
2664
|
+
Rule added: ${FIELD_LABELS[rule2.field]} "${rule2.pattern}"`);
|
|
2665
|
+
console.log("Future incidents matching this pattern will be silently dropped at ingest.");
|
|
2666
|
+
return;
|
|
2667
|
+
}
|
|
2668
|
+
const incidentId = sub;
|
|
2669
|
+
const result = await fetchIncident(config, incidentId);
|
|
2670
|
+
if (!result) {
|
|
2671
|
+
console.error(`Incident ${incidentId} not found.`);
|
|
2672
|
+
process.exit(1);
|
|
2673
|
+
}
|
|
2674
|
+
const { group, sample } = result;
|
|
2675
|
+
const suggestions = [];
|
|
2676
|
+
const msgPattern = group.title.replace(/\[.*?\]/g, "").trim().slice(0, 60);
|
|
2677
|
+
if (msgPattern.length > 5) {
|
|
2678
|
+
suggestions.push({
|
|
2679
|
+
field: "message",
|
|
2680
|
+
pattern: msgPattern,
|
|
2681
|
+
reason: `matches "${group.title}"`
|
|
2682
|
+
});
|
|
2683
|
+
}
|
|
2684
|
+
const ua = sample.device?.userAgent ?? "";
|
|
2685
|
+
const uaMatch = ua.match(/\b(Instagram|FBAN|FBAV|Twitter|TikTok|LinkedInApp|Snapchat|Pinterest)\b/i);
|
|
2686
|
+
if (uaMatch) {
|
|
2687
|
+
suggestions.push({
|
|
2688
|
+
field: "useragent",
|
|
2689
|
+
pattern: uaMatch[1],
|
|
2690
|
+
reason: `this error only appears from the ${uaMatch[1]} in-app browser`
|
|
2691
|
+
});
|
|
2692
|
+
}
|
|
2693
|
+
const firstFrame = sample.stackFrames?.[0];
|
|
2694
|
+
const ownDomain = new URL(sample.url ?? "https://example.com").hostname;
|
|
2695
|
+
if (firstFrame?.source) {
|
|
2696
|
+
try {
|
|
2697
|
+
const frameDomain = new URL(firstFrame.source).hostname;
|
|
2698
|
+
if (frameDomain && frameDomain !== ownDomain) {
|
|
2699
|
+
suggestions.push({
|
|
2700
|
+
field: "script_origin",
|
|
2701
|
+
pattern: frameDomain,
|
|
2702
|
+
reason: `error originates from third-party script at ${frameDomain}`
|
|
2703
|
+
});
|
|
2704
|
+
}
|
|
2705
|
+
} catch {}
|
|
2706
|
+
}
|
|
2707
|
+
if (suggestions.length === 0) {
|
|
2708
|
+
console.log(`No automatic suppression suggestions for incident ${incidentId}.`);
|
|
2709
|
+
console.log("Use `snapfail suppress add` to create a rule manually.");
|
|
2710
|
+
return;
|
|
2711
|
+
}
|
|
2712
|
+
console.log(`
|
|
2713
|
+
Suppression suggestions for: ${group.title}
|
|
2714
|
+
`);
|
|
2715
|
+
for (const s of suggestions) {
|
|
2716
|
+
console.log(` ${FIELD_LABELS[s.field]}: "${s.pattern}"`);
|
|
2717
|
+
console.log(` Reason: ${s.reason}
|
|
2718
|
+
`);
|
|
2719
|
+
}
|
|
2720
|
+
const chosen = await select({
|
|
2721
|
+
message: "Create a suppression rule?",
|
|
2722
|
+
options: [
|
|
2723
|
+
...suggestions.map((s, i2) => ({
|
|
2724
|
+
value: String(i2),
|
|
2725
|
+
label: `${FIELD_LABELS[s.field]}: "${s.pattern}"`
|
|
2726
|
+
})),
|
|
2727
|
+
{ value: "none", label: "Don't create a rule" }
|
|
2728
|
+
]
|
|
2729
|
+
});
|
|
2730
|
+
if (isCancel(chosen) || chosen === "none") {
|
|
2731
|
+
console.log("No rule created.");
|
|
2732
|
+
return;
|
|
2733
|
+
}
|
|
2734
|
+
const selected = suggestions[Number(chosen)];
|
|
2735
|
+
const rule = await addSuppressionRule(session.token, projectKey, selected.field, selected.pattern);
|
|
2736
|
+
console.log(`
|
|
2737
|
+
Rule added: ${FIELD_LABELS[rule.field]} "${rule.pattern}"`);
|
|
2738
|
+
console.log("Future incidents matching this pattern will be silently dropped at ingest.");
|
|
2739
|
+
}
|
|
2740
|
+
|
|
2741
|
+
// src/help.ts
|
|
2742
|
+
var GLOBAL_HELP = `
|
|
2743
|
+
snapfail \u2014 browser error capture for AI-assisted debugging
|
|
2744
|
+
|
|
2745
|
+
USAGE
|
|
2746
|
+
snapfail <command> [options]
|
|
2747
|
+
|
|
2748
|
+
COMMANDS
|
|
2749
|
+
init Set up snapfail in a project (detects framework, authenticates, writes .env)
|
|
2750
|
+
login Authenticate with your snapfail account
|
|
2751
|
+
skill Install the snapfail skill globally for Claude Code (~/.claude/skills/)
|
|
2752
|
+
incidents List incident groups for your project
|
|
2753
|
+
incident View or update a single incident group
|
|
2754
|
+
explain Output raw evidence context for LLM analysis
|
|
2755
|
+
suppress Manage suppression rules to filter out noise
|
|
2756
|
+
help Show this help, or help for a specific command
|
|
2757
|
+
|
|
2758
|
+
OPTIONS
|
|
2759
|
+
--version Print the CLI version
|
|
2760
|
+
--help Show help for the current command
|
|
2761
|
+
|
|
2762
|
+
Run \`snapfail help <command>\` for detailed usage of any command.
|
|
2763
|
+
`.trimStart();
|
|
2764
|
+
var COMMAND_HELP = {
|
|
2765
|
+
init: `
|
|
2766
|
+
snapfail init \u2014 Set up snapfail in the current project
|
|
2767
|
+
|
|
2768
|
+
USAGE
|
|
2769
|
+
snapfail init
|
|
2770
|
+
|
|
2771
|
+
DESCRIPTION
|
|
2772
|
+
Authenticates your account, selects or creates a project, writes the
|
|
2773
|
+
project key to .env, injects the browser SDK into your framework config,
|
|
2774
|
+
and generates .claude/skills/snapfail/SKILL.md for AI agents.
|
|
2775
|
+
|
|
2776
|
+
Supported frameworks: astro, vite, next
|
|
2777
|
+
Run once per project. Idempotent \u2014 safe to run again.
|
|
2778
|
+
|
|
2779
|
+
EXIT CODES
|
|
2780
|
+
0 Success
|
|
2781
|
+
1 Cancelled or error
|
|
2782
|
+
`.trimStart(),
|
|
2783
|
+
login: `
|
|
2784
|
+
snapfail login \u2014 Authenticate with your snapfail account
|
|
2785
|
+
|
|
2786
|
+
USAGE
|
|
2787
|
+
snapfail login
|
|
2788
|
+
|
|
2789
|
+
DESCRIPTION
|
|
2790
|
+
Opens a browser window for OAuth authentication. Saves the session to
|
|
2791
|
+
~/.snapfail/auth.json. Clears any existing session for the default
|
|
2792
|
+
endpoint first.
|
|
2793
|
+
|
|
2794
|
+
EXIT CODES
|
|
2795
|
+
0 Authenticated successfully
|
|
2796
|
+
1 Authentication timed out or failed
|
|
2797
|
+
`.trimStart(),
|
|
2798
|
+
skill: `
|
|
2799
|
+
snapfail skill \u2014 Install the snapfail skill globally for Claude Code
|
|
2800
|
+
|
|
2801
|
+
USAGE
|
|
2802
|
+
snapfail skill
|
|
2803
|
+
|
|
2804
|
+
DESCRIPTION
|
|
2805
|
+
Writes the snapfail skill definition to ~/.claude/skills/snapfail/SKILL.md.
|
|
2806
|
+
Claude Code picks this up automatically \u2014 no project configuration needed.
|
|
2807
|
+
|
|
2808
|
+
Use this when you want snapfail available in all your Claude Code sessions,
|
|
2809
|
+
not just projects where you ran \`snapfail init\`.
|
|
2810
|
+
|
|
2811
|
+
EXIT CODES
|
|
2812
|
+
0 Skill installed or updated
|
|
2813
|
+
`.trimStart(),
|
|
2814
|
+
incidents: `
|
|
2815
|
+
snapfail incidents \u2014 List incident groups for your project
|
|
2816
|
+
|
|
2817
|
+
USAGE
|
|
2818
|
+
snapfail incidents [options]
|
|
2819
|
+
|
|
2820
|
+
OPTIONS
|
|
2821
|
+
--pk <key> Project key (overrides .env / SNAPFAIL_PROJECT_KEY)
|
|
2822
|
+
--status <s> Filter by status: unresolved (default), resolved, ignored, all
|
|
2823
|
+
--limit <n> Number of results to show (default: 5)
|
|
2824
|
+
--offset <n> Pagination offset (default: 0)
|
|
2825
|
+
--json Output raw JSON for LLM consumption
|
|
2826
|
+
|
|
2827
|
+
DESCRIPTION
|
|
2828
|
+
Lists grouped incident summaries. Each group aggregates all occurrences of
|
|
2829
|
+
the same error fingerprint. Use \`snapfail incident <id>\` to drill in.
|
|
2830
|
+
|
|
2831
|
+
When running interactively (no --json), prompts for project selection if
|
|
2832
|
+
no project key is configured.
|
|
2833
|
+
|
|
2834
|
+
EXAMPLES
|
|
2835
|
+
snapfail incidents
|
|
2836
|
+
snapfail incidents --limit 20 --status resolved
|
|
2837
|
+
snapfail incidents --json --pk proj_abc123
|
|
2838
|
+
|
|
2839
|
+
EXIT CODES
|
|
2840
|
+
0 Success
|
|
2841
|
+
1 Auth or API error
|
|
2842
|
+
`.trimStart(),
|
|
2843
|
+
incident: `
|
|
2844
|
+
snapfail incident \u2014 View or update a single incident group
|
|
2845
|
+
|
|
2846
|
+
USAGE
|
|
2847
|
+
snapfail incident <id> [options]
|
|
2848
|
+
|
|
2849
|
+
OPTIONS
|
|
2850
|
+
--pk <key> Project key (overrides .env / SNAPFAIL_PROJECT_KEY)
|
|
2851
|
+
--sample <n> Show the Nth raw sample (0-based index)
|
|
2852
|
+
--resolve Mark the incident as resolved
|
|
2853
|
+
--ignore Mark the incident as ignored
|
|
2854
|
+
--reopen Reopen a resolved or ignored incident (sets to unresolved)
|
|
2855
|
+
--delete Permanently delete the group and all its samples
|
|
2856
|
+
--json Output raw JSON for LLM consumption
|
|
2857
|
+
|
|
2858
|
+
DESCRIPTION
|
|
2859
|
+
Without status flags: displays the incident group summary and a
|
|
2860
|
+
representative timeline sample.
|
|
2861
|
+
|
|
2862
|
+
With --sample <n>: shows the full raw event data for that specific sample,
|
|
2863
|
+
useful for comparing across different occurrences.
|
|
2864
|
+
|
|
2865
|
+
Status flags (--resolve, --ignore, --reopen) update the group on the server
|
|
2866
|
+
and print confirmation. Use --json to get machine-readable output.
|
|
2867
|
+
|
|
2868
|
+
EXAMPLES
|
|
2869
|
+
snapfail incident abc123
|
|
2870
|
+
snapfail incident abc123 --sample 2
|
|
2871
|
+
snapfail incident abc123 --resolve
|
|
2872
|
+
snapfail incident abc123 --json --pk proj_abc123
|
|
2873
|
+
|
|
2874
|
+
EXIT CODES
|
|
2875
|
+
0 Success
|
|
2876
|
+
1 Not found or API error
|
|
2877
|
+
`.trimStart(),
|
|
2878
|
+
suppress: `
|
|
2879
|
+
snapfail suppress \u2014 Manage suppression rules to filter out noise
|
|
2880
|
+
|
|
2881
|
+
USAGE
|
|
2882
|
+
snapfail suppress [list] List active rules
|
|
2883
|
+
snapfail suppress add Create a rule interactively
|
|
2884
|
+
snapfail suppress delete <id-prefix> Remove a rule by ID prefix
|
|
2885
|
+
snapfail suppress <incident-id> Suggest rules based on an incident
|
|
2886
|
+
|
|
2887
|
+
OPTIONS
|
|
2888
|
+
--pk <key> Project key (overrides .env / SNAPFAIL_PROJECT_KEY)
|
|
2889
|
+
--json Output raw JSON (for list/delete)
|
|
2890
|
+
|
|
2891
|
+
DESCRIPTION
|
|
2892
|
+
Suppression rules filter out errors at ingest time \u2014 matching incidents are
|
|
2893
|
+
silently dropped before being grouped or stored.
|
|
2894
|
+
|
|
2895
|
+
Rules match by case-insensitive substring on one of four fields:
|
|
2896
|
+
message The error message text
|
|
2897
|
+
useragent The browser/app user-agent string
|
|
2898
|
+
url The page URL where the error occurred
|
|
2899
|
+
script_origin The URL of the script file that threw the error
|
|
2900
|
+
|
|
2901
|
+
Pass an incident ID instead of a subcommand to get automatic suggestions
|
|
2902
|
+
based on that incident's data (user-agent, error message, script origin).
|
|
2903
|
+
|
|
2904
|
+
EXAMPLES
|
|
2905
|
+
snapfail suppress
|
|
2906
|
+
snapfail suppress ba7b555c (auto-suggest from incident)
|
|
2907
|
+
snapfail suppress add
|
|
2908
|
+
snapfail suppress delete a1b2c3
|
|
2909
|
+
|
|
2910
|
+
EXIT CODES
|
|
2911
|
+
0 Success
|
|
2912
|
+
1 Not found or API error
|
|
2913
|
+
`.trimStart(),
|
|
2914
|
+
explain: `
|
|
2915
|
+
snapfail explain \u2014 Output raw evidence context for LLM analysis
|
|
2916
|
+
|
|
2917
|
+
USAGE
|
|
2918
|
+
snapfail explain <id> [options]
|
|
2919
|
+
|
|
2920
|
+
OPTIONS
|
|
2921
|
+
--pk <key> Project key (overrides .env / SNAPFAIL_PROJECT_KEY)
|
|
2922
|
+
--json Output raw JSON ({ group, samples }) instead of formatted markdown
|
|
2923
|
+
|
|
2924
|
+
DESCRIPTION
|
|
2925
|
+
Fetches the incident group and up to 3 representative samples, then outputs
|
|
2926
|
+
structured evidence (error, stack, console, network, timeline) formatted for
|
|
2927
|
+
an LLM to reason about root cause.
|
|
2928
|
+
|
|
2929
|
+
This command does NOT pre-diagnose. Pipe the output to a frontier model
|
|
2930
|
+
(Claude, GPT-4, etc.) and ask it to identify the root cause.
|
|
2931
|
+
|
|
2932
|
+
The --json flag outputs the raw data objects, useful when you want to
|
|
2933
|
+
process the evidence programmatically.
|
|
2934
|
+
|
|
2935
|
+
EXAMPLES
|
|
2936
|
+
snapfail explain abc123
|
|
2937
|
+
snapfail explain abc123 --json | claude --print "What caused this error?"
|
|
2938
|
+
|
|
2939
|
+
EXIT CODES
|
|
2940
|
+
0 Success
|
|
2941
|
+
1 Not found or API error
|
|
2942
|
+
`.trimStart()
|
|
2943
|
+
};
|
|
2944
|
+
function printHelp(command) {
|
|
2945
|
+
if (!command) {
|
|
2946
|
+
process.stdout.write(GLOBAL_HELP);
|
|
2947
|
+
return;
|
|
2948
|
+
}
|
|
2949
|
+
const text2 = COMMAND_HELP[command];
|
|
2950
|
+
if (text2) {
|
|
2951
|
+
process.stdout.write(text2);
|
|
2952
|
+
} else {
|
|
2953
|
+
process.stdout.write(`No help found for command: ${command}
|
|
2954
|
+
|
|
2955
|
+
`);
|
|
2956
|
+
process.stdout.write(GLOBAL_HELP);
|
|
2957
|
+
}
|
|
2958
|
+
}
|
|
2439
2959
|
// package.json
|
|
2440
2960
|
var package_default = {
|
|
2441
2961
|
name: "snapfail",
|
|
2442
|
-
version: "0.0.
|
|
2962
|
+
version: "0.0.27",
|
|
2443
2963
|
type: "module",
|
|
2444
2964
|
description: "CLI for snapfail \u2014 project setup, incident inspection and AI diagnostics",
|
|
2445
2965
|
license: "MIT",
|
|
@@ -2464,13 +2984,21 @@ function parseArgs(argv) {
|
|
|
2464
2984
|
const command = rawCommand;
|
|
2465
2985
|
const args = [];
|
|
2466
2986
|
const flags = {};
|
|
2467
|
-
for (
|
|
2987
|
+
for (let i2 = 0;i2 < rest.length; i2++) {
|
|
2988
|
+
const token = rest[i2];
|
|
2468
2989
|
if (token.startsWith("--")) {
|
|
2469
2990
|
const eq = token.indexOf("=");
|
|
2470
2991
|
if (eq !== -1) {
|
|
2471
2992
|
flags[token.slice(2, eq)] = token.slice(eq + 1);
|
|
2472
2993
|
} else {
|
|
2473
|
-
|
|
2994
|
+
const key = token.slice(2);
|
|
2995
|
+
const next = rest[i2 + 1];
|
|
2996
|
+
if (next !== undefined && !next.startsWith("--")) {
|
|
2997
|
+
flags[key] = next;
|
|
2998
|
+
i2++;
|
|
2999
|
+
} else {
|
|
3000
|
+
flags[key] = true;
|
|
3001
|
+
}
|
|
2474
3002
|
}
|
|
2475
3003
|
} else {
|
|
2476
3004
|
args.push(token);
|
|
@@ -2483,10 +3011,19 @@ async function main() {
|
|
|
2483
3011
|
const { command, args, flags } = parseArgs(process.argv);
|
|
2484
3012
|
const json = flags["json"] === true;
|
|
2485
3013
|
const pk = typeof flags["pk"] === "string" ? flags["pk"] : undefined;
|
|
3014
|
+
const helpFlag = flags["help"] === true || flags["h"] === true;
|
|
2486
3015
|
if (command === "--version" || command === "-v" || flags["version"] === true) {
|
|
2487
3016
|
console.log(VERSION);
|
|
2488
3017
|
return;
|
|
2489
3018
|
}
|
|
3019
|
+
if (command === "help") {
|
|
3020
|
+
printHelp(args[0]);
|
|
3021
|
+
return;
|
|
3022
|
+
}
|
|
3023
|
+
if (helpFlag) {
|
|
3024
|
+
printHelp(command);
|
|
3025
|
+
return;
|
|
3026
|
+
}
|
|
2490
3027
|
try {
|
|
2491
3028
|
if (command === "init") {
|
|
2492
3029
|
await runInit();
|
|
@@ -2500,6 +3037,11 @@ async function main() {
|
|
|
2500
3037
|
await runSkill();
|
|
2501
3038
|
return;
|
|
2502
3039
|
}
|
|
3040
|
+
if (command === "suppress") {
|
|
3041
|
+
const [subcommand, ...rest] = args;
|
|
3042
|
+
await runSuppress({ subcommand, args: rest, json, pk });
|
|
3043
|
+
return;
|
|
3044
|
+
}
|
|
2503
3045
|
if (command === "incidents") {
|
|
2504
3046
|
await runIncidents({
|
|
2505
3047
|
json,
|
|
@@ -2513,25 +3055,35 @@ async function main() {
|
|
|
2513
3055
|
if (command === "incident") {
|
|
2514
3056
|
const id = args[0];
|
|
2515
3057
|
if (!id) {
|
|
2516
|
-
|
|
3058
|
+
printHelp("incident");
|
|
2517
3059
|
process.exit(1);
|
|
2518
3060
|
}
|
|
2519
3061
|
const sampleFlag = flags["sample"];
|
|
2520
3062
|
const sample = typeof sampleFlag === "string" ? parseInt(sampleFlag) : undefined;
|
|
2521
|
-
await runIncident({
|
|
3063
|
+
await runIncident({
|
|
3064
|
+
id,
|
|
3065
|
+
sample,
|
|
3066
|
+
json,
|
|
3067
|
+
pk,
|
|
3068
|
+
delete: flags["delete"] === true,
|
|
3069
|
+
resolve: flags["resolve"] === true,
|
|
3070
|
+
ignore: flags["ignore"] === true,
|
|
3071
|
+
reopen: flags["reopen"] === true
|
|
3072
|
+
});
|
|
2522
3073
|
return;
|
|
2523
3074
|
}
|
|
2524
3075
|
if (command === "explain") {
|
|
2525
3076
|
const id = args[0];
|
|
2526
3077
|
if (!id) {
|
|
2527
|
-
|
|
3078
|
+
printHelp("explain");
|
|
2528
3079
|
process.exit(1);
|
|
2529
3080
|
}
|
|
2530
3081
|
await runExplain({ id, json, pk });
|
|
2531
3082
|
return;
|
|
2532
3083
|
}
|
|
2533
|
-
console.error(`Unknown command: ${command}
|
|
2534
|
-
|
|
3084
|
+
console.error(`Unknown command: ${command}
|
|
3085
|
+
`);
|
|
3086
|
+
printHelp();
|
|
2535
3087
|
process.exit(1);
|
|
2536
3088
|
} catch (err) {
|
|
2537
3089
|
const message = err instanceof Error ? err.message : String(err);
|