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 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 diagnosis = await fetchDiagnosis(config, opts.id, opts.force ?? false);
2363
- if (!diagnosis) {
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(diagnosis, null, 2) + `
2387
+ process.stdout.write(JSON.stringify({ group, samples }, null, 2) + `
2369
2388
  `);
2370
2389
  return;
2371
2390
  }
2372
- console.log(formatDiagnosis(diagnosis));
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, force: flags["force"] === true, json, pk });
2456
+ await runExplain({ id, json, pk });
2437
2457
  return;
2438
2458
  }
2439
2459
  console.error(`Unknown command: ${command}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "snapfail",
3
- "version": "0.0.21",
3
+ "version": "0.0.22",
4
4
  "type": "module",
5
5
  "description": "CLI for snapfail — project setup, incident inspection and AI diagnostics",
6
6
  "license": "MIT",
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, force: flags["force"] === true, json, pk });
81
+ await runExplain({ id, json, pk });
82
82
  return;
83
83
  }
84
84