snapfail 0.0.21 → 0.0.22
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 +75 -55
- package/package.json +1 -1
- package/src/index.ts +1 -1
package/dist/index.js
CHANGED
|
@@ -267,6 +267,68 @@ function formatIncidentList(data, total, status) {
|
|
|
267
267
|
${bold(String(total))} ${status ?? "unresolved"} incident${total !== 1 ? "s" : ""}`;
|
|
268
268
|
return table + summary;
|
|
269
269
|
}
|
|
270
|
+
function formatLLMContext(group, samples) {
|
|
271
|
+
const lines = [];
|
|
272
|
+
lines.push(`# Incident: ${group.title}`);
|
|
273
|
+
lines.push(`Group ID: ${group.id}`);
|
|
274
|
+
lines.push(`Fingerprint: ${group.fingerprint}`);
|
|
275
|
+
lines.push(`Occurrences: ${group.count} | First: ${new Date(group.firstSeen).toISOString()} | Last: ${new Date(group.lastSeen).toISOString()}`);
|
|
276
|
+
lines.push(`Environments: ${group.environments.join(", ")}`);
|
|
277
|
+
lines.push(`Status: ${group.status}`);
|
|
278
|
+
lines.push("");
|
|
279
|
+
lines.push("## Error");
|
|
280
|
+
lines.push(`Type: ${group.errorType}`);
|
|
281
|
+
lines.push(`Message: ${samples[0]?.errorMessage ?? group.title}`);
|
|
282
|
+
lines.push(`Normalized: ${samples[0]?.normalizedMessage ?? group.title}`);
|
|
283
|
+
if (samples[0] && samples[0].stackFrames.length > 0) {
|
|
284
|
+
lines.push("");
|
|
285
|
+
lines.push("## Stack");
|
|
286
|
+
for (const f of samples[0].stackFrames) {
|
|
287
|
+
lines.push(` ${f.fn ?? "(anonymous)"} ${f.file}:${f.line}${f.col != null ? `:${f.col}` : ""}`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
for (let i = 0;i < samples.length; i++) {
|
|
291
|
+
const s = samples[i];
|
|
292
|
+
lines.push("");
|
|
293
|
+
lines.push(`## Sample ${i + 1} / ${group.sampleIds.length}`);
|
|
294
|
+
lines.push(`Device: ${s.device.userAgent}`);
|
|
295
|
+
if (s.device.viewport) {
|
|
296
|
+
lines.push(`Viewport: ${s.device.viewport.width}x${s.device.viewport.height} Language: ${s.device.language ?? "unknown"}`);
|
|
297
|
+
}
|
|
298
|
+
lines.push(`URL: ${s.url}`);
|
|
299
|
+
if (s.route)
|
|
300
|
+
lines.push(`Route: ${s.route}`);
|
|
301
|
+
lines.push(`Environment: ${s.environmentMode}`);
|
|
302
|
+
if (s.consoleEntries.length > 0) {
|
|
303
|
+
lines.push("");
|
|
304
|
+
lines.push("### Console");
|
|
305
|
+
for (const e of s.consoleEntries) {
|
|
306
|
+
const args = e.args.map((a) => String(a)).join(" ");
|
|
307
|
+
lines.push(` [${e.level}] ${args}`);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
if (s.networkEntries.length > 0) {
|
|
311
|
+
lines.push("");
|
|
312
|
+
lines.push("### Network");
|
|
313
|
+
for (const n of s.networkEntries) {
|
|
314
|
+
const status = n.status ? ` \u2192 ${n.status}` : n.error ? ` \u2192 ERROR` : "";
|
|
315
|
+
lines.push(` ${n.method} ${n.url}${status} (${n.durationMs}ms)`);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (s.timeline.length > 0) {
|
|
319
|
+
lines.push("");
|
|
320
|
+
lines.push("### Timeline");
|
|
321
|
+
for (const e of s.timeline) {
|
|
322
|
+
lines.push(` +${e.t}ms ${e.kind}: ${e.summary}`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
if (i < samples.length - 1)
|
|
326
|
+
lines.push(`
|
|
327
|
+
---`);
|
|
328
|
+
}
|
|
329
|
+
return lines.join(`
|
|
330
|
+
`);
|
|
331
|
+
}
|
|
270
332
|
function formatIncidentDetail(group, sample) {
|
|
271
333
|
const lines = [];
|
|
272
334
|
lines.push(bold(`${group.errorType}: ${truncate(group.title, 80)}`));
|
|
@@ -2306,70 +2368,28 @@ async function runInit(cwd = process.cwd()) {
|
|
|
2306
2368
|
}
|
|
2307
2369
|
|
|
2308
2370
|
// src/commands/explain.ts
|
|
2309
|
-
async function fetchDiagnosis(config, id, force) {
|
|
2310
|
-
const url = new URL(`${config.endpoint}/api/diagnose/${id}`);
|
|
2311
|
-
if (force)
|
|
2312
|
-
url.searchParams.set("force", "1");
|
|
2313
|
-
let res;
|
|
2314
|
-
try {
|
|
2315
|
-
res = await fetch(url.toString(), {
|
|
2316
|
-
method: "POST",
|
|
2317
|
-
headers: { "X-Project-Key": config.projectKey, "Content-Type": "application/json" },
|
|
2318
|
-
body: "{}"
|
|
2319
|
-
});
|
|
2320
|
-
} catch (err) {
|
|
2321
|
-
throw new Error(`Network error: ${err instanceof Error ? err.message : String(err)}`);
|
|
2322
|
-
}
|
|
2323
|
-
if (res.status === 401)
|
|
2324
|
-
throw new Error("Unauthorized: check your project key.");
|
|
2325
|
-
if (res.status === 404)
|
|
2326
|
-
return null;
|
|
2327
|
-
if (res.status === 422)
|
|
2328
|
-
throw new Error("No samples available to diagnose this incident.");
|
|
2329
|
-
if (!res.ok) {
|
|
2330
|
-
const body = await res.text().catch(() => "");
|
|
2331
|
-
throw new Error(`API error ${res.status}: ${body}`);
|
|
2332
|
-
}
|
|
2333
|
-
return res.json();
|
|
2334
|
-
}
|
|
2335
|
-
function confidenceColor(c3) {
|
|
2336
|
-
if (c3 === "high")
|
|
2337
|
-
return green(c3);
|
|
2338
|
-
if (c3 === "medium")
|
|
2339
|
-
return yellow(c3);
|
|
2340
|
-
return red(c3);
|
|
2341
|
-
}
|
|
2342
|
-
function formatDiagnosis(d) {
|
|
2343
|
-
const lines = [];
|
|
2344
|
-
lines.push(bold("Root cause"));
|
|
2345
|
-
lines.push(` ${d.rootCause}`);
|
|
2346
|
-
lines.push("");
|
|
2347
|
-
lines.push(bold("In plain language"));
|
|
2348
|
-
lines.push(` ${d.plainSummary}`);
|
|
2349
|
-
if (d.fixPrompt) {
|
|
2350
|
-
lines.push("");
|
|
2351
|
-
lines.push(bold("Fix prompt") + dim(" (paste into your AI assistant)"));
|
|
2352
|
-
lines.push(` ${d.fixPrompt}`);
|
|
2353
|
-
}
|
|
2354
|
-
lines.push("");
|
|
2355
|
-
lines.push(dim("Confidence:") + " " + confidenceColor(d.confidence) + dim(" \xB7 analyzed ") + d.analyzedSampleIds.length + dim(" samples \xB7 model: ") + dim(d.model));
|
|
2356
|
-
return lines.join(`
|
|
2357
|
-
`);
|
|
2358
|
-
}
|
|
2359
2371
|
async function runExplain(opts) {
|
|
2360
2372
|
requireSession();
|
|
2361
2373
|
const config = loadConfig(process.cwd(), opts.pk);
|
|
2362
|
-
const
|
|
2363
|
-
if (!
|
|
2374
|
+
const result = await fetchIncident(config, opts.id);
|
|
2375
|
+
if (!result) {
|
|
2364
2376
|
console.error(`Incident ${opts.id} not found.`);
|
|
2365
2377
|
process.exit(1);
|
|
2366
2378
|
}
|
|
2379
|
+
const { group, sample } = result;
|
|
2380
|
+
const samples = [sample];
|
|
2381
|
+
for (let i2 = 1;i2 < Math.min(group.sampleIds.length, 3); i2++) {
|
|
2382
|
+
const s = await fetchSample(config, opts.id, i2);
|
|
2383
|
+
if (s)
|
|
2384
|
+
samples.push(s);
|
|
2385
|
+
}
|
|
2367
2386
|
if (opts.json) {
|
|
2368
|
-
process.stdout.write(JSON.stringify(
|
|
2387
|
+
process.stdout.write(JSON.stringify({ group, samples }, null, 2) + `
|
|
2369
2388
|
`);
|
|
2370
2389
|
return;
|
|
2371
2390
|
}
|
|
2372
|
-
|
|
2391
|
+
process.stdout.write(formatLLMContext(group, samples) + `
|
|
2392
|
+
`);
|
|
2373
2393
|
}
|
|
2374
2394
|
|
|
2375
2395
|
// src/index.ts
|
|
@@ -2433,7 +2453,7 @@ async function main() {
|
|
|
2433
2453
|
console.error("Usage: snapfail explain <id> [--force] [--json]");
|
|
2434
2454
|
process.exit(1);
|
|
2435
2455
|
}
|
|
2436
|
-
await runExplain({ id,
|
|
2456
|
+
await runExplain({ id, json, pk });
|
|
2437
2457
|
return;
|
|
2438
2458
|
}
|
|
2439
2459
|
console.error(`Unknown command: ${command}`);
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -78,7 +78,7 @@ async function main(): Promise<void> {
|
|
|
78
78
|
console.error("Usage: snapfail explain <id> [--force] [--json]");
|
|
79
79
|
process.exit(1);
|
|
80
80
|
}
|
|
81
|
-
await runExplain({ id,
|
|
81
|
+
await runExplain({ id, json, pk });
|
|
82
82
|
return;
|
|
83
83
|
}
|
|
84
84
|
|