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.
Files changed (2) hide show
  1. package/dist/index.js +1564 -1012
  2. 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 ESC3 = "\x1B";
38
- var CSI2 = `${ESC3}[`;
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: `${ESC3}7`,
68
- restore: `${ESC3}8`
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 ESC2 = "\x1B";
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) => `${ESC2}${ANSI_CSI}${code}${ANSI_SGR_TERMINATOR}`;
630
- var wrapAnsiHyperlink = (url) => `${ESC2}${ANSI_ESCAPE_LINK}${url}${ANSI_ESCAPE_BELL}`;
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 === ESC2 || character === CSI) {
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 === ESC2 || character === CSI) {
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
- get segments() {
1261
- return this.#i;
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
- get separator() {
1264
- return this.#o;
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
- get formattedValue() {
1267
- return this.#l(this.#t);
1171
+ }
1172
+ class a extends V {
1173
+ options;
1174
+ cursor = 0;
1175
+ get _selectedValue() {
1176
+ return this.options[this.cursor];
1268
1177
  }
1269
- #l(t2) {
1270
- return this.#i.map((i) => t2[i.type]).join(this.#o);
1178
+ changeValue() {
1179
+ this.value = this._selectedValue.value;
1271
1180
  }
1272
- #r() {
1273
- this._setUserInput(this.#l(this.#t)), this._setValue(C(this.#t) ?? undefined);
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
- 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 ? {
1277
- year: String(e.getUTCFullYear()).padStart(4, "0"),
1278
- month: String(e.getUTCMonth() + 1).padStart(2, "0"),
1279
- day: String(e.getUTCDate()).padStart(2, "0")
1280
- } : { year: "____", month: "__", day: "__" }, o = n.map((a) => m2[a.type]).join(s);
1281
- 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));
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
- #a() {
1284
- const t2 = Math.max(0, Math.min(this.#e.segmentIndex, this.#i.length - 1)), i = this.#i[t2];
1285
- if (i)
1286
- return this.#e.positionInSegment = Math.max(0, Math.min(this.#e.positionInSegment, i.len - 1)), { segment: i, index: t2 };
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
- #m(t2) {
1289
- this.inlineError = "", this.#s = null;
1290
- const i = this.#a();
1291
- i && (this.#e.segmentIndex = Math.max(0, Math.min(this.#i.length - 1, i.index + t2)), this.#e.positionInSegment = 0, this.#n = true);
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
- #d(t2) {
1294
- const i = this.#a();
1295
- if (!i)
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
- 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);
1298
- let a;
1299
- e ? a = t2 === 1 ? o.min : o.max : a = Math.max(Math.min(o.max, m2 + t2), o.min), this.#t = {
1300
- ...this.#t,
1301
- [s.type]: a.toString().padStart(s.len, "0")
1302
- }, this.#n = true, this.#s = null, this.#r();
1303
- }
1304
- #f(t2) {
1305
- if (t2)
1306
- switch (t2) {
1307
- case "right":
1308
- return this.#m(1);
1309
- case "left":
1310
- return this.#m(-1);
1311
- case "up":
1312
- return this.#d(1);
1313
- case "down":
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
- this.#t[n.segment.type] = "_".repeat(n.segment.len), this.#n = true, this.#e.positionInSegment = 0, this.#r();
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
- if (i?.name === "tab") {
1331
- this.inlineError = "";
1332
- const n = this.#a();
1333
- if (!n)
1334
- return;
1335
- const e = i.shift ? -1 : 1, m2 = n.index + e;
1336
- m2 >= 0 && m2 < this.#i.length && (this.#e.segmentIndex = m2, this.#e.positionInSegment = 0, this.#n = true);
1337
- return;
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
- if (t2 && /^[0-9]$/.test(t2)) {
1340
- const n = this.#a();
1341
- if (!n)
1342
- return;
1343
- const { segment: e } = n, m2 = !this.#t[e.type].replace(/_/g, "");
1344
- if (this.#n && this.#s !== null && !m2) {
1345
- const h = this.#s + t2, d = { ...this.#t, [e.type]: h }, g2 = this.#g(d, e);
1346
- if (g2) {
1347
- this.inlineError = g2, this.#s = null, this.#n = false;
1348
- return;
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
- this.#n && !m2 && (this.#t[e.type] = "_".repeat(e.len), this.#e.positionInSegment = 0), this.#n = false, this.#s = null;
1354
- const o = this.#t[e.type], a = o.indexOf("_"), u = a >= 0 ? a : Math.min(this.#e.positionInSegment, e.len - 1);
1355
- if (u < 0 || u >= e.len)
1356
- return;
1357
- let l2 = o.slice(0, u) + t2 + o.slice(u + 1), D = false;
1358
- if (u === 0 && o === "__" && (e.type === "month" || e.type === "day")) {
1359
- const h = Number.parseInt(t2, 10);
1360
- l2 = `0${t2}`, D = h <= (e.type === "month" ? 1 : 2);
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
- if (e.type === "year" && (l2 = (o.replace(/_/g, "") + t2).padStart(e.len, "_")), !l2.includes("_")) {
1363
- const h = { ...this.#t, [e.type]: l2 }, d = this.#g(h, e);
1364
- if (d) {
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
- this.inlineError = "", this.#t[e.type] = l2;
1370
- const y = l2.includes("_") ? undefined : b(this.#t);
1371
- if (y) {
1372
- const { year: h, month: d } = y, g2 = c(h, d);
1373
- this.#t = {
1374
- year: String(Math.max(0, Math.min(9999, h))).padStart(4, "0"),
1375
- month: String(Math.max(1, Math.min(12, d))).padStart(2, "0"),
1376
- day: String(Math.max(1, Math.min(g2, y.day))).padStart(2, "0")
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
- #g(t2, i) {
1385
- const { month: s, day: n } = f(t2);
1386
- if (i.type === "month" && (s < 0 || s > 12))
1387
- return settings.date.messages.invalidMonth;
1388
- if (i.type === "day" && (n < 0 || n > 31))
1389
- return settings.date.messages.invalidDay(31, "any month");
1390
- }
1391
- #p(t2) {
1392
- const { year: i, month: s, day: n } = f(this.#t);
1393
- if (i && s && n) {
1394
- const e = c(i, s);
1395
- this.#t = {
1396
- ...this.#t,
1397
- day: String(Math.min(n, e)).padStart(2, "0")
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
- var u$1 = class u extends V {
1404
- options;
1405
- cursor = 0;
1406
- #t;
1407
- getGroupItems(t2) {
1408
- return this.options.filter((r) => r.group === t2);
1409
- }
1410
- isGroupSelected(t2) {
1411
- const r = this.getGroupItems(t2), e = this.value;
1412
- return e === undefined ? false : r.every((s) => e.includes(s.value));
1413
- }
1414
- toggleValue() {
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
- var o$1 = /* @__PURE__ */ new Set(["up", "down", "left", "right"]);
1454
-
1455
- class h extends V {
1456
- #s = false;
1457
- #t;
1458
- focused = "editor";
1459
- get userInputWithCursor() {
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
- get cursor() {
1471
- return this._cursor;
1623
+ if (projects.length === 1) {
1624
+ console.log(`Using project: ${projects[0].name}`);
1625
+ return projects[0].key;
1472
1626
  }
1473
- #r(t2) {
1474
- if (this.userInput.length === 0) {
1475
- this._setUserInput(t2);
1476
- return;
1477
- }
1478
- this._setUserInput(this.userInput.slice(0, this.cursor) + t2 + this.userInput.slice(this.cursor));
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
- #i(t2) {
1481
- const s = this.value ?? "";
1482
- switch (t2) {
1483
- case "up":
1484
- this._cursor = findTextCursor(this._cursor, 0, -1, s);
1485
- return;
1486
- case "down":
1487
- this._cursor = findTextCursor(this._cursor, 0, 1, s);
1488
- return;
1489
- case "left":
1490
- this._cursor = findTextCursor(this._cursor, -1, 0, s);
1491
- return;
1492
- case "right":
1493
- this._cursor = findTextCursor(this._cursor, 1, 0, s);
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
- _shouldSubmit(t2, s) {
1498
- if (this.#t)
1499
- return this.focused === "submit" ? true : (this.#r(`
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
- class a extends V {
1535
- options;
1536
- cursor = 0;
1537
- get _selectedValue() {
1538
- return this.options[this.cursor];
1539
- }
1540
- changeValue() {
1541
- this.value = this._selectedValue.value;
1542
- }
1543
- constructor(t2) {
1544
- super(t2, false), this.options = t2.options;
1545
- const i = this.options.findIndex(({ value: s }) => s === t2.initialValue), e = i === -1 ? 0 : i;
1546
- this.cursor = this.options[e].disabled ? findCursor(e, 1, this.options) : e, this.changeValue(), this.on("cursor", (s) => {
1547
- switch (s) {
1548
- case "left":
1549
- case "up":
1550
- this.cursor = findCursor(this.cursor, -1, this.options);
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
- class n extends V {
1562
- get userInputWithCursor() {
1563
- if (this.state === "submit")
1564
- return this.userInput;
1565
- const t2 = this.userInput;
1566
- if (this.cursor >= t2.length)
1567
- return `${this.userInput}\u2588`;
1568
- const e = t2.slice(0, this.cursor), [s, ...r] = t2.slice(this.cursor);
1569
- return `${e}${styleText("inverse", s)}${r.join("")}`;
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
- get cursor() {
1572
- return this._cursor;
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
- constructor(t2) {
1575
- super({
1576
- ...t2,
1577
- initialUserInput: t2.initialUserInput ?? t2.initialValue
1578
- }), this.on("userInput", (e) => {
1579
- this._setValue(e);
1580
- }), this.on("finalize", () => {
1581
- this.value || (this.value = t2.defaultValue), this.value === undefined && (this.value = "");
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
- // ../../node_modules/.bun/@clack+prompts@1.5.1/node_modules/@clack/prompts/dist/index.mjs
1587
- import { styleText as styleText2, stripVTControlCharacters } from "util";
1588
- import process$1 from "process";
1589
- var import_sisteransi2 = __toESM(require_src(), 1);
1590
- function isUnicodeSupported() {
1591
- if (process$1.platform !== "win32") {
1592
- return process$1.env.TERM !== "linux";
1593
- }
1594
- 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";
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
- var unicode = isUnicodeSupported();
1597
- var isCI = () => process.env.CI === "true";
1598
- var unicodeOr = (e, o2) => unicode ? e : o2;
1599
- var S_STEP_ACTIVE = unicodeOr("\u25C6", "*");
1600
- var S_STEP_CANCEL = unicodeOr("\u25A0", "x");
1601
- var S_STEP_ERROR = unicodeOr("\u25B2", "x");
1602
- var S_STEP_SUBMIT = unicodeOr("\u25C7", "o");
1603
- var S_BAR_START = unicodeOr("\u250C", "T");
1604
- var S_BAR = unicodeOr("\u2502", "|");
1605
- var S_BAR_END = unicodeOr("\u2514", "\u2014");
1606
- var S_BAR_START_RIGHT = unicodeOr("\u2510", "T");
1607
- var S_BAR_END_RIGHT = unicodeOr("\u2518", "\u2014");
1608
- var S_RADIO_ACTIVE = unicodeOr("\u25CF", ">");
1609
- var S_RADIO_INACTIVE = unicodeOr("\u25CB", " ");
1610
- var S_CHECKBOX_ACTIVE = unicodeOr("\u25FB", "[\u2022]");
1611
- var S_CHECKBOX_SELECTED = unicodeOr("\u25FC", "[+]");
1612
- var S_CHECKBOX_INACTIVE = unicodeOr("\u25FB", "[ ]");
1613
- var S_PASSWORD_MASK = unicodeOr("\u25AA", "\u2022");
1614
- var S_BAR_H = unicodeOr("\u2500", "-");
1615
- var S_CORNER_TOP_RIGHT = unicodeOr("\u256E", "+");
1616
- var S_CONNECT_LEFT = unicodeOr("\u251C", "+");
1617
- var S_CORNER_BOTTOM_RIGHT = unicodeOr("\u256F", "+");
1618
- var S_CORNER_BOTTOM_LEFT = unicodeOr("\u2570", "+");
1619
- var S_CORNER_TOP_LEFT = unicodeOr("\u256D", "+");
1620
- var S_INFO = unicodeOr("\u25CF", "\u2022");
1621
- var S_SUCCESS = unicodeOr("\u25C6", "*");
1622
- var S_WARN = unicodeOr("\u25B2", "!");
1623
- var S_ERROR = unicodeOr("\u25A0", "x");
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
- var symbolBar = (e) => {
1638
- switch (e) {
1639
- case "initial":
1640
- case "active":
1641
- return styleText2("cyan", S_BAR);
1642
- case "cancel":
1643
- return styleText2("red", S_BAR);
1644
- case "error":
1645
- return styleText2("yellow", S_BAR);
1646
- case "submit":
1647
- return styleText2("green", S_BAR);
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
- var E$1 = (l2, o2, g2, c2, h2, O = false) => {
1651
- let r2 = o2, w = 0;
1652
- if (O)
1653
- for (let i = c2 - 1;i >= g2 && (r2 -= l2[i].length, w++, !(r2 <= h2)); i--)
1654
- ;
1655
- else
1656
- for (let i = g2;i < c2 && (r2 -= l2[i].length, w++, !(r2 <= h2)); i++)
1657
- ;
1658
- return { lineCount: r2, removals: w };
1659
- };
1660
- var limitOptions = ({
1661
- cursor: l2,
1662
- options: o2,
1663
- style: g2,
1664
- output: c2 = process.stdout,
1665
- maxItems: h2 = Number.POSITIVE_INFINITY,
1666
- columnPadding: O = 0,
1667
- rowPadding: r2 = 4
1668
- }) => {
1669
- 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);
1670
- let p2 = 0;
1671
- l2 >= m2 - 3 && (p2 = Math.max(Math.min(l2 - m2 + 3, o2.length - m2), 0));
1672
- let f2 = m2 < o2.length && p2 > 0, u3 = m2 < o2.length && p2 + m2 < o2.length;
1673
- const W = Math.min(p2 + m2, o2.length), e = [];
1674
- let d = 0;
1675
- f2 && d++, u3 && d++;
1676
- const v = p2 + (f2 ? 1 : 0), P2 = W - (u3 ? 1 : 0);
1677
- for (let t2 = v;t2 < P2; t2++) {
1678
- const n2 = wrapAnsi(g2(o2[t2], t2 === l2), i, {
1679
- hard: true,
1680
- trim: false
1681
- }).split(`
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
- e.push(n2), d += n2.length;
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 (d > x) {
1686
- let t2 = 0, n2 = 0, s = d;
1687
- const M2 = l2 - v;
1688
- let a2 = x;
1689
- const T3 = () => E$1(e, s, 0, M2, a2), L = () => E$1(e, s, M2 + 1, e.length, a2, true);
1690
- 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));
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
- const b2 = [];
1693
- f2 && b2.push(C2);
1694
- for (const t2 of e)
1695
- for (const n2 of t2)
1696
- b2.push(n2);
1697
- return u3 && b2.push(C2), b2;
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
- var cancel = (o2 = "", t2) => {
1742
- const i = t2?.output ?? process.stdout, e = t2?.withGuide ?? settings.withGuide ? `${styleText2("gray", S_BAR_END)} ` : "";
1743
- i.write(`${e}${styleText2("red", o2)}
1744
-
1745
- `);
1746
- };
1747
- var intro = (o2 = "", t2) => {
1748
- const i = t2?.output ?? process.stdout, e = t2?.withGuide ?? settings.withGuide ? `${styleText2("gray", S_BAR_START)} ` : "";
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
- var W = (l2) => styleText2("magenta", l2);
1760
- var spinner = ({
1761
- indicator: l2 = "dots",
1762
- onCancel: h2,
1763
- output: n2 = process.stdout,
1764
- cancelMessage: G,
1765
- errorMessage: O,
1766
- frames: E = unicode ? ["\u25D2", "\u25D0", "\u25D3", "\u25D1"] : ["\u2022", "o", "O", "0"],
1767
- delay: F = unicode ? 80 : 120,
1768
- signal: m2,
1769
- ...I
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
- const r2 = wrapAnsi(p2, x, {
1786
- hard: true,
1787
- trim: false
1788
- }).split(`
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
- 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());
1791
- }, C2 = (e) => e.replace(/\.+$/, ""), _2 = (e) => {
1792
- const r2 = (performance.now() - e) / 1000, t2 = Math.floor(r2 / 60), o2 = Math.floor(r2 % 60);
1793
- return t2 > 0 ? `[${t2}m ${o2}s]` : `[${o2}s]`;
1794
- }, N = I.withGuide ?? settings.withGuide, P2 = (e = "") => {
1795
- d = true, M2 = block({ output: n2 }), s = C2(e), w = performance.now(), N && n2.write(`${styleText2("gray", S_BAR)}
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
- var u3 = {
1842
- light: unicodeOr("\u2500", "-"),
1843
- heavy: unicodeOr("\u2501", "="),
1844
- block: unicodeOr("\u2588", "#")
1845
- };
1846
- var c2 = (e, a2) => e.includes(`
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
- return new a({
1867
- options: e.options,
1868
- signal: e.signal,
1869
- input: e.input,
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
- }).prompt();
1905
- };
1906
- var i = `${styleText2("gray", S_BAR)} `;
1907
- var text = (t2) => new n({
1908
- validate: t2.validate,
1909
- placeholder: t2.placeholder,
1910
- defaultValue: t2.defaultValue,
1911
- initialValue: t2.initialValue,
1912
- output: t2.output,
1913
- signal: t2.signal,
1914
- input: t2.input,
1915
- render() {
1916
- const i2 = t2?.withGuide ?? settings.withGuide, s = `${`${i2 ? `${styleText2("gray", S_BAR)}
1917
- ` : ""}${symbol(this.state)} `}${t2.message}
1918
- `, 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 ?? "";
1919
- switch (this.state) {
1920
- case "error": {
1921
- const n2 = this.error ? ` ${styleText2("yellow", this.error)}` : "", r2 = i2 ? `${styleText2("yellow", S_BAR)} ` : "", d = i2 ? styleText2("yellow", S_BAR_END) : "";
1922
- return `${s.trim()}
1923
- ${r2}${o2}
1924
- ${d}${n2}
1925
- `;
1926
- }
1927
- case "submit": {
1928
- const n2 = a2 ? ` ${styleText2("dim", a2)}` : "", r2 = i2 ? styleText2("gray", S_BAR) : "";
1929
- return `${s}${r2}${n2}`;
1930
- }
1931
- case "cancel": {
1932
- const n2 = a2 ? ` ${styleText2(["strikethrough", "dim"], a2)}` : "", r2 = i2 ? styleText2("gray", S_BAR) : "";
1933
- return `${s}${r2}${n2}${a2.trim() ? `
1934
- ${r2}` : ""}`;
1935
- }
1936
- default: {
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
- }).prompt();
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
- const config = loadConfig(process.cwd(), opts.pk);
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.25",
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 (const token of rest) {
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
- flags[token.slice(2)] = true;
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
- console.error("Usage: snapfail incident <id> [--sample <n>] [--json]");
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({ id, sample, json, pk, delete: flags["delete"] === true });
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
- console.error("Usage: snapfail explain <id> [--force] [--json]");
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
- console.error(`snapfail v${VERSION} \u2014 Usage: snapfail [init|login|incidents|incident|explain|skill] [--version]`);
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);