snapfail 0.0.14 → 0.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,451 +1,2374 @@
1
- import { createRequire } from "node:module";
2
- var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
-
4
- // src/index.ts
5
- import { join, resolve, basename } from "path";
6
- import { existsSync, readFileSync, writeFileSync } from "fs";
7
- import { createServer } from "http";
8
- import { spawn, execSync } from "child_process";
9
- var VERSION = "0.0.14";
10
- var APP_URL = process.env["SNAPFAIL_APP_URL"] ?? "https://app.snapfail.com";
11
- var AUTH_TIMEOUT_MS = 5 * 60 * 1000;
12
- var c = {
13
- reset: "\x1B[0m",
14
- bold: "\x1B[1m",
15
- dim: "\x1B[2m",
16
- green: "\x1B[32m",
17
- yellow: "\x1B[33m",
18
- red: "\x1B[31m",
19
- cyan: "\x1B[36m"
1
+ #!/usr/bin/env bun
2
+ // @bun
3
+ var __create = Object.create;
4
+ var __getProtoOf = Object.getPrototypeOf;
5
+ var __defProp = Object.defineProperty;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ function __accessProp(key) {
9
+ return this[key];
10
+ }
11
+ var __toESMCache_node;
12
+ var __toESMCache_esm;
13
+ var __toESM = (mod, isNodeMode, target) => {
14
+ var canCache = mod != null && typeof mod === "object";
15
+ if (canCache) {
16
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
17
+ var cached = cache.get(mod);
18
+ if (cached)
19
+ return cached;
20
+ }
21
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
22
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
23
+ for (let key of __getOwnPropNames(mod))
24
+ if (!__hasOwnProp.call(to, key))
25
+ __defProp(to, key, {
26
+ get: __accessProp.bind(mod, key),
27
+ enumerable: true
28
+ });
29
+ if (canCache)
30
+ cache.set(mod, to);
31
+ return to;
20
32
  };
21
- function fmt(style, msg) {
22
- return `${c[style]}${msg}${c.reset}`;
23
- }
24
- var log = (msg) => console.log(` ${fmt("green", "▶")} ${msg}`);
25
- var warn = (msg) => console.warn(` ${fmt("yellow", "⚠")} ${msg}`);
26
- var fail = (msg) => console.error(` ${fmt("red", "✗")} ${msg}`);
27
- var success = (msg) => console.log(` ${fmt("green", "✓")} ${msg}`);
28
- var dim = (msg) => fmt("dim", msg);
29
- var bold = (msg) => fmt("bold", msg);
30
- var green = (msg) => fmt("green", msg);
31
- async function askYesNo(question, defaultYes = true) {
32
- const readline = await import("readline");
33
- const rl = readline.createInterface({
34
- input: process.stdin,
35
- output: process.stdout
33
+ var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
34
+
35
+ // ../../node_modules/.bun/sisteransi@1.0.5/node_modules/sisteransi/src/index.js
36
+ var require_src = __commonJS((exports, module) => {
37
+ var ESC3 = "\x1B";
38
+ var CSI2 = `${ESC3}[`;
39
+ var beep = "\x07";
40
+ var cursor = {
41
+ to(x, y) {
42
+ if (!y)
43
+ return `${CSI2}${x + 1}G`;
44
+ return `${CSI2}${y + 1};${x + 1}H`;
45
+ },
46
+ move(x, y) {
47
+ let ret = "";
48
+ if (x < 0)
49
+ ret += `${CSI2}${-x}D`;
50
+ else if (x > 0)
51
+ ret += `${CSI2}${x}C`;
52
+ if (y < 0)
53
+ ret += `${CSI2}${-y}A`;
54
+ else if (y > 0)
55
+ ret += `${CSI2}${y}B`;
56
+ return ret;
57
+ },
58
+ up: (count = 1) => `${CSI2}${count}A`,
59
+ down: (count = 1) => `${CSI2}${count}B`,
60
+ forward: (count = 1) => `${CSI2}${count}C`,
61
+ backward: (count = 1) => `${CSI2}${count}D`,
62
+ nextLine: (count = 1) => `${CSI2}E`.repeat(count),
63
+ prevLine: (count = 1) => `${CSI2}F`.repeat(count),
64
+ left: `${CSI2}G`,
65
+ hide: `${CSI2}?25l`,
66
+ show: `${CSI2}?25h`,
67
+ save: `${ESC3}7`,
68
+ restore: `${ESC3}8`
69
+ };
70
+ var scroll = {
71
+ up: (count = 1) => `${CSI2}S`.repeat(count),
72
+ down: (count = 1) => `${CSI2}T`.repeat(count)
73
+ };
74
+ var erase = {
75
+ screen: `${CSI2}2J`,
76
+ up: (count = 1) => `${CSI2}1J`.repeat(count),
77
+ down: (count = 1) => `${CSI2}J`.repeat(count),
78
+ line: `${CSI2}2K`,
79
+ lineEnd: `${CSI2}K`,
80
+ lineStart: `${CSI2}1K`,
81
+ lines(count) {
82
+ let clear = "";
83
+ for (let i = 0;i < count; i++)
84
+ clear += this.line + (i < count - 1 ? cursor.up() : "");
85
+ if (count)
86
+ clear += cursor.left;
87
+ return clear;
88
+ }
89
+ };
90
+ module.exports = { cursor, scroll, erase, beep };
91
+ });
92
+
93
+ // src/config.ts
94
+ import { readFileSync, writeFileSync, existsSync } from "fs";
95
+ import { resolve } from "path";
96
+ var DEFAULT_ENDPOINT = "https://app.snapfail.com";
97
+ var ENV_FILE = ".env";
98
+ function loadConfig(cwd = process.cwd()) {
99
+ const fromEnv = process.env["SNAPFAIL_PROJECT_KEY"];
100
+ if (fromEnv)
101
+ return { projectKey: fromEnv, endpoint: DEFAULT_ENDPOINT };
102
+ const envPath = resolve(cwd, ENV_FILE);
103
+ if (existsSync(envPath)) {
104
+ const match = readFileSync(envPath, "utf-8").match(/^SNAPFAIL_PROJECT_KEY=(.+)$/m);
105
+ if (match)
106
+ return { projectKey: match[1].trim(), endpoint: DEFAULT_ENDPOINT };
107
+ }
108
+ throw new Error('No project key found. Run "snapfail init" or set SNAPFAIL_PROJECT_KEY.');
109
+ }
110
+ function writeProjectKey(projectKey, cwd = process.cwd(), keyName = "SNAPFAIL_PROJECT_KEY") {
111
+ const envPath = resolve(cwd, ENV_FILE);
112
+ const line = `${keyName}=${projectKey}`;
113
+ const pattern = new RegExp(`^${keyName}=.*`, "m");
114
+ if (existsSync(envPath)) {
115
+ let content = readFileSync(envPath, "utf-8");
116
+ if (pattern.test(content)) {
117
+ content = content.replace(pattern, line);
118
+ } else {
119
+ content = content.trimEnd() + `
120
+ ${line}
121
+ `;
122
+ }
123
+ writeFileSync(envPath, content, "utf-8");
124
+ } else {
125
+ writeFileSync(envPath, `${line}
126
+ `, "utf-8");
127
+ }
128
+ }
129
+
130
+ // src/api.ts
131
+ async function apiFetch(url, options) {
132
+ let res;
133
+ try {
134
+ res = await fetch(url, options);
135
+ } catch (err) {
136
+ throw new Error(`Network error: ${err instanceof Error ? err.message : String(err)}`);
137
+ }
138
+ if (res.status === 401)
139
+ throw new Error("Unauthorized: check your project key.");
140
+ if (res.status === 404)
141
+ return null;
142
+ if (!res.ok) {
143
+ const body = await res.text().catch(() => "");
144
+ throw new Error(`API error ${res.status}: ${body}`);
145
+ }
146
+ return res.json();
147
+ }
148
+ async function fetchIncidents(config, opts = {}) {
149
+ const url = new URL(`${config.endpoint}/api/incidents`);
150
+ url.searchParams.set("projectKey", config.projectKey);
151
+ if (opts.status)
152
+ url.searchParams.set("status", opts.status);
153
+ if (opts.limit != null)
154
+ url.searchParams.set("limit", String(opts.limit));
155
+ if (opts.offset != null)
156
+ url.searchParams.set("offset", String(opts.offset));
157
+ const data = await apiFetch(url.toString());
158
+ return data;
159
+ }
160
+ async function fetchIncident(config, id) {
161
+ const url = `${config.endpoint}/api/incidents/${id}`;
162
+ const data = await apiFetch(url, {
163
+ headers: { "X-Project-Key": config.projectKey }
36
164
  });
37
- return new Promise((resolve2) => {
38
- const promptChar = defaultYes ? "[Y/n]" : "[y/N]";
39
- rl.question(` ${fmt("cyan", "?")} ${question} ${dim(promptChar)} `, (answer) => {
40
- rl.close();
41
- const clean = answer.trim().toLowerCase();
42
- if (clean === "") {
43
- resolve2(defaultYes);
44
- } else if (clean === "y" || clean === "yes") {
45
- resolve2(true);
46
- } else if (clean === "n" || clean === "no") {
47
- resolve2(false);
48
- } else {
49
- resolve2(defaultYes);
50
- }
51
- });
165
+ return data;
166
+ }
167
+ async function fetchSample(config, groupId, sampleIndex) {
168
+ const url = `${config.endpoint}/api/incidents/${groupId}/samples/${sampleIndex}`;
169
+ const data = await apiFetch(url, {
170
+ headers: { "X-Project-Key": config.projectKey }
52
171
  });
172
+ return data;
53
173
  }
54
- function randomHex(bytes = 16) {
55
- const buf = new Uint8Array(bytes);
56
- crypto.getRandomValues(buf);
57
- return Array.from(buf, (b) => b.toString(16).padStart(2, "0")).join("");
174
+
175
+ // src/format.ts
176
+ var ESC = "\x1B[";
177
+ var bold = (s) => `${ESC}1m${s}${ESC}0m`;
178
+ var dim = (s) => `${ESC}2m${s}${ESC}0m`;
179
+ var red = (s) => `${ESC}31m${s}${ESC}0m`;
180
+ var green = (s) => `${ESC}32m${s}${ESC}0m`;
181
+ var yellow = (s) => `${ESC}33m${s}${ESC}0m`;
182
+ var cyan = (s) => `${ESC}36m${s}${ESC}0m`;
183
+ function truncate(s, n) {
184
+ if (s.length <= n)
185
+ return s;
186
+ return s.slice(0, n - 1) + "\u2026";
58
187
  }
59
- function openBrowser(url) {
60
- try {
61
- const p = process.platform;
62
- if (p === "darwin")
63
- spawn("open", [url], { stdio: "ignore", detached: true }).unref();
64
- else if (p === "win32")
65
- spawn("cmd", ["/c", "start", "", url], { stdio: "ignore", detached: true }).unref();
66
- else
67
- spawn("xdg-open", [url], { stdio: "ignore", detached: true }).unref();
68
- } catch {}
188
+ function relativeTime(ts) {
189
+ const diff = Date.now() - ts;
190
+ const s = Math.floor(diff / 1000);
191
+ if (s < 60)
192
+ return `${s}s ago`;
193
+ const m = Math.floor(s / 60);
194
+ if (m < 60)
195
+ return `${m}m ago`;
196
+ const h = Math.floor(m / 60);
197
+ if (h < 24)
198
+ return `${h}h ago`;
199
+ const d = Math.floor(h / 24);
200
+ return `${d}d ago`;
201
+ }
202
+ function formatTable(rows, headers) {
203
+ const allRows = [headers, ...rows];
204
+ const widths = headers.map((_, i) => Math.max(...allRows.map((r) => (r[i] ?? "").length)));
205
+ const pad = (s, w) => s + " ".repeat(Math.max(0, w - s.length));
206
+ const header = " " + headers.map((h, i) => bold(pad(h, widths[i]))).join(" ");
207
+ const divider = " " + widths.map((w) => dim("\u2500".repeat(w))).join(" ");
208
+ const body = rows.map((r) => " " + r.map((cell, i) => pad(cell, widths[i])).join(" ")).join(`
209
+ `);
210
+ return [header, divider, body].join(`
211
+ `);
212
+ }
213
+ function formatIncidentList(data, total, status) {
214
+ if (data.length === 0) {
215
+ return dim(`No ${status ?? "unresolved"} incidents found.`);
216
+ }
217
+ const rows = data.map((g) => [
218
+ cyan(truncate(g.id, 12)),
219
+ truncate(g.title, 38),
220
+ String(g.count),
221
+ g.environments.join(","),
222
+ relativeTime(g.lastSeen)
223
+ ]);
224
+ const table = formatTable(rows, ["ID", "TITLE", "COUNT", "ENV", "LAST SEEN"]);
225
+ const summary = `
226
+ ${bold(String(total))} ${status ?? "unresolved"} incident${total !== 1 ? "s" : ""}`;
227
+ return table + summary;
69
228
  }
70
- function detectProject(cwd) {
71
- for (const f of ["astro.config.mjs", "astro.config.ts", "astro.config.js", "astro.config.cjs"]) {
72
- if (existsSync(join(cwd, f)))
73
- return { kind: "astro", configFile: join(cwd, f), htmlFile: null };
229
+ function formatIncidentDetail(group, sample) {
230
+ const lines = [];
231
+ lines.push(bold(`${group.errorType}: ${truncate(group.title, 80)}`));
232
+ lines.push(`${dim("Status:")} ${group.status} ${dim("\xB7")} ${group.count} occurrences ${dim("\xB7")} ${group.environments.join(", ")}`);
233
+ lines.push(`${dim("First seen:")} ${new Date(group.firstSeen).toISOString().slice(0, 10)} ${dim("\xB7")} ${dim("Last seen:")} ${relativeTime(group.lastSeen)}`);
234
+ if (sample.stackFrames.length > 0) {
235
+ lines.push("");
236
+ lines.push(bold("Stack"));
237
+ for (const f of sample.stackFrames.slice(0, 6)) {
238
+ const fn = f.fn ? `${cyan(f.fn)} ` : "";
239
+ lines.push(` ${fn}${dim(f.file + ":" + f.line)}`);
240
+ }
74
241
  }
75
- for (const f of ["vite.config.ts", "vite.config.js", "vite.config.mts", "vite.config.mjs"]) {
76
- if (existsSync(join(cwd, f)))
77
- return { kind: "vite", configFile: join(cwd, f), htmlFile: null };
242
+ if (sample.consoleEntries.length > 0) {
243
+ lines.push("");
244
+ lines.push(bold("Console"));
245
+ for (const e of sample.consoleEntries.slice(-5)) {
246
+ const level = e.level === "error" ? red(`[${e.level}]`) : yellow(`[${e.level}]`);
247
+ const args = e.args.map((a) => truncate(String(a), 60)).join(" ");
248
+ lines.push(` ${level} ${args}`);
249
+ }
78
250
  }
79
- for (const f of ["index.html", "public/index.html", "src/index.html"]) {
80
- if (existsSync(join(cwd, f)))
81
- return { kind: "html", configFile: null, htmlFile: join(cwd, f) };
251
+ if (sample.networkEntries.length > 0) {
252
+ lines.push("");
253
+ lines.push(bold("Network"));
254
+ for (const n of sample.networkEntries.slice(-5)) {
255
+ const status = n.status ? n.status >= 400 ? red(String(n.status)) : green(String(n.status)) : dim("???");
256
+ lines.push(` ${dim(n.method)} ${truncate(n.url, 50)} ${dim("\u2192")} ${status} ${dim(`(${n.durationMs}ms)`)}`);
257
+ }
82
258
  }
83
- return { kind: "unknown", configFile: null, htmlFile: null };
259
+ if (sample.timeline.length > 0) {
260
+ lines.push("");
261
+ lines.push(bold("Timeline"));
262
+ for (const e of sample.timeline.slice(0, 10)) {
263
+ const t = String(e.t).padStart(5);
264
+ lines.push(` ${dim(t + "ms")} ${e.summary}`);
265
+ }
266
+ }
267
+ return lines.join(`
268
+ `);
84
269
  }
85
- function lastImportEnd(content) {
86
- const importRe = /^import\s[^;]+?['"][^\n]*['"];?\n/gm;
87
- let pos = 0;
88
- let m;
89
- while ((m = importRe.exec(content)) !== null) {
90
- pos = m.index + m[0].length;
270
+
271
+ // src/commands/incidents.ts
272
+ async function runIncidents(opts) {
273
+ const config = loadConfig();
274
+ const result = await fetchIncidents(config, {
275
+ status: opts.status ?? "unresolved",
276
+ limit: opts.limit,
277
+ offset: opts.offset
278
+ });
279
+ if (opts.json) {
280
+ process.stdout.write(JSON.stringify(result, null, 2) + `
281
+ `);
282
+ return;
91
283
  }
92
- return pos;
284
+ console.log(formatIncidentList(result.data, result.total, opts.status));
93
285
  }
94
- function insertImport(content, importLine) {
95
- const pos = lastImportEnd(content);
96
- return pos > 0 ? content.slice(0, pos) + importLine + `
97
- ` + content.slice(pos) : importLine + `
98
- ` + content;
286
+
287
+ // src/commands/incident.ts
288
+ async function runIncident(opts) {
289
+ const config = loadConfig();
290
+ if (opts.sample != null) {
291
+ const s = await fetchSample(config, opts.id, opts.sample);
292
+ if (!s) {
293
+ console.error(`Sample ${opts.sample} not found for incident ${opts.id}`);
294
+ process.exit(1);
295
+ }
296
+ if (opts.json) {
297
+ process.stdout.write(JSON.stringify(s, null, 2) + `
298
+ `);
299
+ return;
300
+ }
301
+ const detail = await fetchIncident(config, opts.id);
302
+ if (!detail) {
303
+ console.error(`Incident ${opts.id} not found.`);
304
+ process.exit(1);
305
+ }
306
+ console.log(formatIncidentDetail(detail.group, s));
307
+ return;
308
+ }
309
+ const result = await fetchIncident(config, opts.id);
310
+ if (!result) {
311
+ console.error(`Incident ${opts.id} not found.`);
312
+ process.exit(1);
313
+ }
314
+ if (opts.json) {
315
+ process.stdout.write(JSON.stringify(result, null, 2) + `
316
+ `);
317
+ return;
318
+ }
319
+ console.log(formatIncidentDetail(result.group, result.sample));
99
320
  }
100
- function injectAstroConfig(configPath, appName, enableReplay) {
101
- try {
102
- let src = readFileSync(configPath, "utf8");
103
- if (src.includes("@snapfail/sdk") || src.includes("snapfailAstro") || src.includes("snapfail(")) {
104
- warn(`SnapFail is already present in ${basename(configPath)}.`);
105
- return true;
321
+
322
+ // src/commands/init.ts
323
+ import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2 } from "fs";
324
+ import { resolve as resolve2, join as join2 } from "path";
325
+ import { execSync as execSync2 } from "child_process";
326
+
327
+ // ../../node_modules/.bun/@clack+core@1.4.1/node_modules/@clack/core/dist/index.mjs
328
+ import { styleText } from "util";
329
+ import { stdout, stdin } from "process";
330
+ import * as l from "readline";
331
+ import l__default from "readline";
332
+
333
+ // ../../node_modules/.bun/fast-string-truncated-width@3.0.3/node_modules/fast-string-truncated-width/dist/utils.js
334
+ var getCodePointsLength = (() => {
335
+ const SURROGATE_PAIR_RE = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g;
336
+ return (input) => {
337
+ let surrogatePairsNr = 0;
338
+ SURROGATE_PAIR_RE.lastIndex = 0;
339
+ while (SURROGATE_PAIR_RE.test(input)) {
340
+ surrogatePairsNr += 1;
341
+ }
342
+ return input.length - surrogatePairsNr;
343
+ };
344
+ })();
345
+ var isFullWidth = (x) => {
346
+ return x === 12288 || x >= 65281 && x <= 65376 || x >= 65504 && x <= 65510;
347
+ };
348
+ var isWideNotCJKTNotEmoji = (x) => {
349
+ return x === 8987 || x === 9001 || x >= 12272 && x <= 12287 || x >= 12289 && x <= 12350 || x >= 12441 && x <= 12543 || x >= 12549 && x <= 12591 || x >= 12593 && x <= 12686 || x >= 12688 && x <= 12771 || x >= 12783 && x <= 12830 || x >= 12832 && x <= 12871 || x >= 12880 && x <= 19903 || x >= 65040 && x <= 65049 || x >= 65072 && x <= 65106 || x >= 65108 && x <= 65126 || x >= 65128 && x <= 65131 || x >= 127488 && x <= 127490 || x >= 127504 && x <= 127547 || x >= 127552 && x <= 127560 || x >= 131072 && x <= 196605 || x >= 196608 && x <= 262141;
350
+ };
351
+
352
+ // ../../node_modules/.bun/fast-string-truncated-width@3.0.3/node_modules/fast-string-truncated-width/dist/index.js
353
+ var ANSI_RE = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]|\u001b\]8;[^;]*;.*?(?:\u0007|\u001b\u005c)/y;
354
+ var CONTROL_RE = /[\x00-\x08\x0A-\x1F\x7F-\x9F]{1,1000}/y;
355
+ var CJKT_WIDE_RE = /(?:(?![\uFF61-\uFF9F\uFF00-\uFFEF])[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}\p{Script=Tangut}]){1,1000}/yu;
356
+ var TAB_RE = /\t{1,1000}/y;
357
+ var EMOJI_RE = /[\u{1F1E6}-\u{1F1FF}]{2}|\u{1F3F4}[\u{E0061}-\u{E007A}]{2}[\u{E0030}-\u{E0039}\u{E0061}-\u{E007A}]{1,3}\u{E007F}|(?:\p{Emoji}\uFE0F\u20E3?|\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation})(?:\u200D(?:\p{Emoji_Modifier_Base}\p{Emoji_Modifier}?|\p{Emoji_Presentation}|\p{Emoji}\uFE0F\u20E3?))*/yu;
358
+ var LATIN_RE = /(?:[\x20-\x7E\xA0-\xFF](?!\uFE0F)){1,1000}/y;
359
+ var MODIFIER_RE = /\p{M}+/gu;
360
+ var NO_TRUNCATION = { limit: Infinity, ellipsis: "" };
361
+ var getStringTruncatedWidth = (input, truncationOptions = {}, widthOptions = {}) => {
362
+ const LIMIT = truncationOptions.limit ?? Infinity;
363
+ const ELLIPSIS = truncationOptions.ellipsis ?? "";
364
+ const ELLIPSIS_WIDTH = truncationOptions?.ellipsisWidth ?? (ELLIPSIS ? getStringTruncatedWidth(ELLIPSIS, NO_TRUNCATION, widthOptions).width : 0);
365
+ const ANSI_WIDTH = 0;
366
+ const CONTROL_WIDTH = widthOptions.controlWidth ?? 0;
367
+ const TAB_WIDTH = widthOptions.tabWidth ?? 8;
368
+ const EMOJI_WIDTH = widthOptions.emojiWidth ?? 2;
369
+ const FULL_WIDTH_WIDTH = 2;
370
+ const REGULAR_WIDTH = widthOptions.regularWidth ?? 1;
371
+ const WIDE_WIDTH = widthOptions.wideWidth ?? FULL_WIDTH_WIDTH;
372
+ const PARSE_BLOCKS = [
373
+ [LATIN_RE, REGULAR_WIDTH],
374
+ [ANSI_RE, ANSI_WIDTH],
375
+ [CONTROL_RE, CONTROL_WIDTH],
376
+ [TAB_RE, TAB_WIDTH],
377
+ [EMOJI_RE, EMOJI_WIDTH],
378
+ [CJKT_WIDE_RE, WIDE_WIDTH]
379
+ ];
380
+ let indexPrev = 0;
381
+ let index = 0;
382
+ let length = input.length;
383
+ let lengthExtra = 0;
384
+ let truncationEnabled = false;
385
+ let truncationIndex = length;
386
+ let truncationLimit = Math.max(0, LIMIT - ELLIPSIS_WIDTH);
387
+ let unmatchedStart = 0;
388
+ let unmatchedEnd = 0;
389
+ let width = 0;
390
+ let widthExtra = 0;
391
+ outer:
392
+ while (true) {
393
+ if (unmatchedEnd > unmatchedStart || index >= length && index > indexPrev) {
394
+ const unmatched = input.slice(unmatchedStart, unmatchedEnd) || input.slice(indexPrev, index);
395
+ lengthExtra = 0;
396
+ for (const char of unmatched.replaceAll(MODIFIER_RE, "")) {
397
+ const codePoint = char.codePointAt(0) || 0;
398
+ if (isFullWidth(codePoint)) {
399
+ widthExtra = FULL_WIDTH_WIDTH;
400
+ } else if (isWideNotCJKTNotEmoji(codePoint)) {
401
+ widthExtra = WIDE_WIDTH;
402
+ } else {
403
+ widthExtra = REGULAR_WIDTH;
404
+ }
405
+ if (width + widthExtra > truncationLimit) {
406
+ truncationIndex = Math.min(truncationIndex, Math.max(unmatchedStart, indexPrev) + lengthExtra);
407
+ }
408
+ if (width + widthExtra > LIMIT) {
409
+ truncationEnabled = true;
410
+ break outer;
411
+ }
412
+ lengthExtra += char.length;
413
+ width += widthExtra;
414
+ }
415
+ unmatchedStart = unmatchedEnd = 0;
416
+ }
417
+ if (index >= length) {
418
+ break outer;
419
+ }
420
+ for (let i = 0, l = PARSE_BLOCKS.length;i < l; i++) {
421
+ const [BLOCK_RE, BLOCK_WIDTH] = PARSE_BLOCKS[i];
422
+ BLOCK_RE.lastIndex = index;
423
+ if (BLOCK_RE.test(input)) {
424
+ lengthExtra = BLOCK_RE === CJKT_WIDE_RE ? getCodePointsLength(input.slice(index, BLOCK_RE.lastIndex)) : BLOCK_RE === EMOJI_RE ? 1 : BLOCK_RE.lastIndex - index;
425
+ widthExtra = lengthExtra * BLOCK_WIDTH;
426
+ if (width + widthExtra > truncationLimit) {
427
+ truncationIndex = Math.min(truncationIndex, index + Math.floor((truncationLimit - width) / BLOCK_WIDTH));
428
+ }
429
+ if (width + widthExtra > LIMIT) {
430
+ truncationEnabled = true;
431
+ break outer;
432
+ }
433
+ width += widthExtra;
434
+ unmatchedStart = indexPrev;
435
+ unmatchedEnd = index;
436
+ index = indexPrev = BLOCK_RE.lastIndex;
437
+ continue outer;
438
+ }
439
+ }
440
+ index += 1;
441
+ }
442
+ return {
443
+ width: truncationEnabled ? truncationLimit : width,
444
+ index: truncationEnabled ? truncationIndex : length,
445
+ truncated: truncationEnabled,
446
+ ellipsed: truncationEnabled && LIMIT >= ELLIPSIS_WIDTH
447
+ };
448
+ };
449
+ var dist_default = getStringTruncatedWidth;
450
+
451
+ // ../../node_modules/.bun/fast-string-width@3.0.2/node_modules/fast-string-width/dist/index.js
452
+ var NO_TRUNCATION2 = {
453
+ limit: Infinity,
454
+ ellipsis: "",
455
+ ellipsisWidth: 0
456
+ };
457
+ var fastStringWidth = (input, options = {}) => {
458
+ return dist_default(input, NO_TRUNCATION2, options).width;
459
+ };
460
+ var dist_default2 = fastStringWidth;
461
+
462
+ // ../../node_modules/.bun/fast-wrap-ansi@0.2.2/node_modules/fast-wrap-ansi/lib/main.js
463
+ var ESC2 = "\x1B";
464
+ var CSI = "\x9B";
465
+ var END_CODE = 39;
466
+ var ANSI_ESCAPE_BELL = "\x07";
467
+ var ANSI_CSI = "[";
468
+ var ANSI_OSC = "]";
469
+ var ANSI_SGR_TERMINATOR = "m";
470
+ var ANSI_ESCAPE_LINK = `${ANSI_OSC}8;;`;
471
+ var GROUP_REGEX = new RegExp(`(?:\\${ANSI_CSI}(?<code>\\d+)m|\\${ANSI_ESCAPE_LINK}(?<uri>.*)${ANSI_ESCAPE_BELL})`, "y");
472
+ var getClosingCode = (openingCode) => {
473
+ if (openingCode >= 30 && openingCode <= 37)
474
+ return 39;
475
+ if (openingCode >= 90 && openingCode <= 97)
476
+ return 39;
477
+ if (openingCode >= 40 && openingCode <= 47)
478
+ return 49;
479
+ if (openingCode >= 100 && openingCode <= 107)
480
+ return 49;
481
+ if (openingCode === 1 || openingCode === 2)
482
+ return 22;
483
+ if (openingCode === 3)
484
+ return 23;
485
+ if (openingCode === 4)
486
+ return 24;
487
+ if (openingCode === 7)
488
+ return 27;
489
+ if (openingCode === 8)
490
+ return 28;
491
+ if (openingCode === 9)
492
+ return 29;
493
+ if (openingCode === 0)
494
+ return 0;
495
+ return;
496
+ };
497
+ var wrapAnsiCode = (code) => `${ESC2}${ANSI_CSI}${code}${ANSI_SGR_TERMINATOR}`;
498
+ var wrapAnsiHyperlink = (url) => `${ESC2}${ANSI_ESCAPE_LINK}${url}${ANSI_ESCAPE_BELL}`;
499
+ var wrapWord = (rows, word, columns) => {
500
+ const characters = word[Symbol.iterator]();
501
+ let isInsideEscape = false;
502
+ let isInsideLinkEscape = false;
503
+ let lastRow = rows.at(-1);
504
+ let visible = lastRow === undefined ? 0 : dist_default2(lastRow);
505
+ let currentCharacter = characters.next();
506
+ let nextCharacter = characters.next();
507
+ let rawCharacterIndex = 0;
508
+ while (!currentCharacter.done) {
509
+ const character = currentCharacter.value;
510
+ const characterLength = dist_default2(character);
511
+ if (visible + characterLength <= columns) {
512
+ rows[rows.length - 1] += character;
513
+ } else {
514
+ rows.push(character);
515
+ visible = 0;
106
516
  }
107
- src = insertImport(src, `import { snapfail } from '@snapfail/sdk';`);
108
- const replayParam = enableReplay ? ", recordSession: true" : "";
109
- const pluginCall = `snapfail({ apiKey: process.env.SNAPFAIL_API_KEY ?? '', appName: '${appName}'${replayParam} })`;
110
- if (/integrations\s*:\s*\[/.test(src)) {
111
- src = src.replace(/integrations\s*:\s*\[/, `integrations: [
112
- ${pluginCall},`);
113
- } else if (/defineConfig\s*\(/.test(src)) {
114
- src = src.replace(/defineConfig\s*\(\s*\{/, `defineConfig({
115
- integrations: [${pluginCall}],`);
517
+ if (character === ESC2 || character === CSI) {
518
+ isInsideEscape = true;
519
+ isInsideLinkEscape = word.startsWith(ANSI_ESCAPE_LINK, rawCharacterIndex + 1);
520
+ }
521
+ if (isInsideEscape) {
522
+ if (isInsideLinkEscape) {
523
+ if (character === ANSI_ESCAPE_BELL) {
524
+ isInsideEscape = false;
525
+ isInsideLinkEscape = false;
526
+ }
527
+ } else if (character === ANSI_SGR_TERMINATOR) {
528
+ isInsideEscape = false;
529
+ }
116
530
  } else {
117
- warn("Could not find defineConfig in your Astro config — please add the integration manually.");
118
- return false;
531
+ visible += characterLength;
532
+ if (visible === columns && !nextCharacter.done) {
533
+ rows.push("");
534
+ visible = 0;
535
+ }
119
536
  }
120
- writeFileSync(configPath, src, "utf8");
121
- return true;
122
- } catch (err) {
123
- fail(`Could not modify ${basename(configPath)}: ${err.message}`);
124
- return false;
537
+ currentCharacter = nextCharacter;
538
+ nextCharacter = characters.next();
539
+ rawCharacterIndex += character.length;
125
540
  }
126
- }
127
- function injectViteConfig(configPath, appName, enableReplay) {
128
- try {
129
- let src = readFileSync(configPath, "utf8");
130
- if (src.includes("@snapfail/sdk") || src.includes("snapfailVite") || src.includes("snapfail(")) {
131
- warn(`SnapFail is already present in ${basename(configPath)}.`);
132
- return true;
541
+ lastRow = rows.at(-1);
542
+ if (!visible && lastRow !== undefined && lastRow.length && rows.length > 1) {
543
+ rows[rows.length - 2] += rows.pop();
544
+ }
545
+ };
546
+ var stringVisibleTrimSpacesRight = (string) => {
547
+ const words = string.split(" ");
548
+ let last = words.length;
549
+ while (last) {
550
+ if (dist_default2(words[last - 1])) {
551
+ break;
552
+ }
553
+ last--;
554
+ }
555
+ if (last === words.length) {
556
+ return string;
557
+ }
558
+ return words.slice(0, last).join(" ") + words.slice(last).join("");
559
+ };
560
+ var exec = (string, columns, options = {}) => {
561
+ if (options.trim !== false && string.trim() === "") {
562
+ return "";
563
+ }
564
+ let returnValue = "";
565
+ let escapeCode;
566
+ let escapeUrl;
567
+ const words = string.split(" ");
568
+ let rows = [""];
569
+ let rowLength = 0;
570
+ for (let index = 0;index < words.length; index++) {
571
+ const word = words[index];
572
+ if (options.trim !== false) {
573
+ const row = rows.at(-1) ?? "";
574
+ const trimmed = row.trimStart();
575
+ if (row.length !== trimmed.length) {
576
+ rows[rows.length - 1] = trimmed;
577
+ rowLength = dist_default2(trimmed);
578
+ }
579
+ }
580
+ if (index !== 0) {
581
+ if (rowLength >= columns && (options.wordWrap === false || options.trim === false)) {
582
+ rows.push("");
583
+ rowLength = 0;
584
+ }
585
+ if (rowLength || options.trim === false) {
586
+ rows[rows.length - 1] += " ";
587
+ rowLength++;
588
+ }
589
+ }
590
+ const wordLength = dist_default2(word);
591
+ if (options.hard && wordLength > columns) {
592
+ const remainingColumns = columns - rowLength;
593
+ const breaksStartingThisLine = 1 + Math.floor((wordLength - remainingColumns - 1) / columns);
594
+ const breaksStartingNextLine = Math.floor((wordLength - 1) / columns);
595
+ if (breaksStartingNextLine < breaksStartingThisLine) {
596
+ rows.push("");
597
+ }
598
+ wrapWord(rows, word, columns);
599
+ rowLength = dist_default2(rows.at(-1) ?? "");
600
+ continue;
601
+ }
602
+ if (rowLength + wordLength > columns && rowLength && wordLength) {
603
+ if (options.wordWrap === false && rowLength < columns) {
604
+ wrapWord(rows, word, columns);
605
+ rowLength = dist_default2(rows.at(-1) ?? "");
606
+ continue;
607
+ }
608
+ rows.push("");
609
+ rowLength = 0;
610
+ }
611
+ if (rowLength + wordLength > columns && options.wordWrap === false) {
612
+ wrapWord(rows, word, columns);
613
+ rowLength = dist_default2(rows.at(-1) ?? "");
614
+ continue;
133
615
  }
134
- src = insertImport(src, `import { snapfail } from '@snapfail/sdk';`);
135
- const replayParam = enableReplay ? ", recordSession: true" : "";
136
- const pluginCall = `snapfail({ apiKey: process.env.SNAPFAIL_API_KEY ?? '', appName: '${appName}'${replayParam} })`;
137
- if (/plugins\s*:\s*\[/.test(src)) {
138
- src = src.replace(/plugins\s*:\s*\[/, `plugins: [
139
- ${pluginCall},`);
140
- } else if (/defineConfig\s*\(/.test(src)) {
141
- src = src.replace(/defineConfig\s*\(\s*\{/, `defineConfig({
142
- plugins: [${pluginCall}],`);
616
+ rows[rows.length - 1] += word;
617
+ rowLength += wordLength;
618
+ }
619
+ if (options.trim !== false) {
620
+ rows = rows.map((row) => stringVisibleTrimSpacesRight(row));
621
+ }
622
+ const preString = rows.join(`
623
+ `);
624
+ let inSurrogate = false;
625
+ for (let i = 0;i < preString.length; i++) {
626
+ const character = preString[i];
627
+ returnValue += character;
628
+ if (!inSurrogate) {
629
+ inSurrogate = character >= "\uD800" && character <= "\uDBFF";
630
+ if (inSurrogate) {
631
+ continue;
632
+ }
143
633
  } else {
144
- warn("Could not find defineConfig in your Vite config — please add the plugin manually.");
145
- return false;
634
+ inSurrogate = false;
635
+ }
636
+ if (character === ESC2 || character === CSI) {
637
+ GROUP_REGEX.lastIndex = i + 1;
638
+ const groupsResult = GROUP_REGEX.exec(preString);
639
+ const groups = groupsResult?.groups;
640
+ if (groups?.code !== undefined) {
641
+ const code = Number.parseFloat(groups.code);
642
+ escapeCode = code === END_CODE ? undefined : code;
643
+ } else if (groups?.uri !== undefined) {
644
+ escapeUrl = groups.uri.length === 0 ? undefined : groups.uri;
645
+ }
646
+ }
647
+ if (preString[i + 1] === `
648
+ `) {
649
+ if (escapeUrl) {
650
+ returnValue += wrapAnsiHyperlink("");
651
+ }
652
+ const closingCode = escapeCode ? getClosingCode(escapeCode) : undefined;
653
+ if (escapeCode && closingCode) {
654
+ returnValue += wrapAnsiCode(closingCode);
655
+ }
656
+ } else if (character === `
657
+ `) {
658
+ if (escapeCode && getClosingCode(escapeCode)) {
659
+ returnValue += wrapAnsiCode(escapeCode);
660
+ }
661
+ if (escapeUrl) {
662
+ returnValue += wrapAnsiHyperlink(escapeUrl);
663
+ }
146
664
  }
147
- writeFileSync(configPath, src, "utf8");
148
- return true;
149
- } catch (err) {
150
- fail(`Could not modify ${basename(configPath)}: ${err.message}`);
151
- return false;
152
665
  }
666
+ return returnValue;
667
+ };
668
+ var CRLF_OR_LF = /\r?\n/;
669
+ function wrapAnsi(string, columns, options) {
670
+ return String(string).normalize().split(CRLF_OR_LF).map((line) => exec(line, columns, options)).join(`
671
+ `);
153
672
  }
154
- function injectHtmlFile(htmlPath, apiKey, enableReplay) {
155
- try {
156
- let src = readFileSync(htmlPath, "utf8");
157
- if (src.includes("__snapfailConfig") || src.includes("@snapfail/sdk") || src.includes("sdk.js")) {
158
- warn(`SnapFail is already present in ${basename(htmlPath)}.`);
159
- return true;
673
+
674
+ // ../../node_modules/.bun/@clack+core@1.4.1/node_modules/@clack/core/dist/index.mjs
675
+ var import_sisteransi = __toESM(require_src(), 1);
676
+ import { ReadStream } from "tty";
677
+ function findCursor(s, o, l2) {
678
+ if (!l2.some((r) => !r.disabled))
679
+ return s;
680
+ const t = s + o, n = Math.max(l2.length - 1, 0), e = t < 0 ? n : t > n ? 0 : t;
681
+ return l2[e].disabled ? findCursor(e, o < 0 ? -1 : 1, l2) : e;
682
+ }
683
+ function findTextCursor(s, o, l2, i) {
684
+ const t = i.split(`
685
+ `);
686
+ let n = 0, e = s;
687
+ for (const r of t) {
688
+ if (e <= r.length)
689
+ break;
690
+ e -= r.length + 1, n++;
691
+ }
692
+ for (n = Math.max(0, Math.min(t.length - 1, n + l2)), e = Math.min(e, t[n].length) + o;e < 0 && n > 0; )
693
+ n--, e += t[n].length + 1;
694
+ for (;e > t[n].length && n < t.length - 1; )
695
+ e -= t[n].length + 1, n++;
696
+ e = Math.max(0, Math.min(t[n].length, e));
697
+ let h = 0;
698
+ for (let r = 0;r < n; r++)
699
+ h += t[r].length + 1;
700
+ return h + e;
701
+ }
702
+ var a$2 = ["up", "down", "left", "right", "space", "enter", "cancel"];
703
+ var t = [
704
+ "January",
705
+ "February",
706
+ "March",
707
+ "April",
708
+ "May",
709
+ "June",
710
+ "July",
711
+ "August",
712
+ "September",
713
+ "October",
714
+ "November",
715
+ "December"
716
+ ];
717
+ var settings = {
718
+ actions: new Set(a$2),
719
+ aliases: /* @__PURE__ */ new Map([
720
+ ["k", "up"],
721
+ ["j", "down"],
722
+ ["h", "left"],
723
+ ["l", "right"],
724
+ ["\x03", "cancel"],
725
+ ["escape", "cancel"]
726
+ ]),
727
+ messages: {
728
+ cancel: "Canceled",
729
+ error: "Something went wrong"
730
+ },
731
+ withGuide: true,
732
+ date: {
733
+ monthNames: [...t],
734
+ messages: {
735
+ required: "Please enter a valid date",
736
+ invalidMonth: "There are only 12 months in a year",
737
+ invalidDay: (n, e) => `There are only ${n} days in ${e}`,
738
+ afterMin: (n) => `Date must be on or after ${n.toISOString().slice(0, 10)}`,
739
+ beforeMax: (n) => `Date must be on or before ${n.toISOString().slice(0, 10)}`
160
740
  }
161
- const config = { apiKey };
162
- if (enableReplay) {
163
- config.recordSession = true;
741
+ }
742
+ };
743
+ function isActionKey(n, e) {
744
+ if (typeof n == "string")
745
+ return settings.aliases.get(n) === e;
746
+ for (const s of n)
747
+ if (s !== undefined && isActionKey(s, e))
748
+ return true;
749
+ return false;
750
+ }
751
+ function diffLines(i, s) {
752
+ if (i === s)
753
+ return;
754
+ const e = i.split(`
755
+ `), t2 = s.split(`
756
+ `), r = Math.max(e.length, t2.length), f = [];
757
+ for (let n = 0;n < r; n++)
758
+ e[n] !== t2[n] && f.push(n);
759
+ return {
760
+ lines: f,
761
+ numLinesBefore: e.length,
762
+ numLinesAfter: t2.length,
763
+ numLines: r
764
+ };
765
+ }
766
+ var R = globalThis.process.platform.startsWith("win");
767
+ var CANCEL_SYMBOL = Symbol("clack:cancel");
768
+ function isCancel(e) {
769
+ return e === CANCEL_SYMBOL;
770
+ }
771
+ function setRawMode(e, r) {
772
+ const o = e;
773
+ o.isTTY && o.setRawMode(r);
774
+ }
775
+ function block({
776
+ input: e = stdin,
777
+ output: r = stdout,
778
+ overwrite: o = true,
779
+ hideCursor: t2 = true
780
+ } = {}) {
781
+ const s = l.createInterface({
782
+ input: e,
783
+ output: r,
784
+ prompt: "",
785
+ tabSize: 1
786
+ });
787
+ l.emitKeypressEvents(e, s), e instanceof ReadStream && e.isTTY && e.setRawMode(true);
788
+ const n = (f, { name: a, sequence: p }) => {
789
+ const c = String(f);
790
+ if (isActionKey([c, a, p], "cancel")) {
791
+ t2 && r.write(import_sisteransi.cursor.show), process.exit(0);
792
+ return;
164
793
  }
165
- const snippet = ` <!-- SnapFail SDK -->
166
- <script>window.__snapfailConfig=${JSON.stringify(config)};</script>
167
- <script type="module" src="https://cdn.jsdelivr.net/npm/@snapfail/sdk/dist/sdk.js"></script>
168
- `;
169
- src = src.replace("</head>", `${snippet}</head>`);
170
- writeFileSync(htmlPath, src, "utf8");
794
+ if (!o)
795
+ return;
796
+ const i = a === "return" ? 0 : -1, m = a === "return" ? -1 : 0;
797
+ l.moveCursor(r, i, m, () => {
798
+ l.clearLine(r, 1, () => {
799
+ e.once("keypress", n);
800
+ });
801
+ });
802
+ };
803
+ return t2 && r.write(import_sisteransi.cursor.hide), e.once("keypress", n), () => {
804
+ e.off("keypress", n), t2 && r.write(import_sisteransi.cursor.show), e instanceof ReadStream && e.isTTY && !R && e.setRawMode(false), s.terminal = false, s.close();
805
+ };
806
+ }
807
+ var getColumns = (e) => ("columns" in e) && typeof e.columns == "number" ? e.columns : 80;
808
+ var getRows = (e) => ("rows" in e) && typeof e.rows == "number" ? e.rows : 20;
809
+ function wrapTextWithPrefix(e, r, o, t2 = o, s = o, n) {
810
+ const f = getColumns(e ?? stdout);
811
+ return wrapAnsi(r, f - o.length, {
812
+ hard: true,
813
+ trim: false
814
+ }).split(`
815
+ `).map((c, i, m) => {
816
+ const d = n ? n(c, i) : c;
817
+ return i === 0 ? `${t2}${d}` : i === m.length - 1 ? `${s}${d}` : `${o}${d}`;
818
+ }).join(`
819
+ `);
820
+ }
821
+ function runValidation(e, n) {
822
+ if ("~standard" in e) {
823
+ const a = e["~standard"].validate(n);
824
+ if (a instanceof Promise)
825
+ throw new TypeError("Schema validation must be synchronous. Update `validate()` and remove any asynchronous logic.");
826
+ return a.issues?.at(0)?.message;
827
+ }
828
+ return e(n);
829
+ }
830
+
831
+ class V {
832
+ input;
833
+ output;
834
+ _abortSignal;
835
+ rl;
836
+ opts;
837
+ _render;
838
+ _track = false;
839
+ _prevFrame = "";
840
+ _subscribers = /* @__PURE__ */ new Map;
841
+ _cursor = 0;
842
+ state = "initial";
843
+ error = "";
844
+ value;
845
+ userInput = "";
846
+ constructor(t2, e = true) {
847
+ const { input: i = stdin, output: n = stdout, render: s, signal: r, ...o } = t2;
848
+ this.opts = o, this.onKeypress = this.onKeypress.bind(this), this.close = this.close.bind(this), this.render = this.render.bind(this), this._render = s.bind(this), this._track = e, this._abortSignal = r, this.input = i, this.output = n;
849
+ }
850
+ unsubscribe() {
851
+ this._subscribers.clear();
852
+ }
853
+ setSubscriber(t2, e) {
854
+ const i = this._subscribers.get(t2) ?? [];
855
+ i.push(e), this._subscribers.set(t2, i);
856
+ }
857
+ on(t2, e) {
858
+ this.setSubscriber(t2, { cb: e });
859
+ }
860
+ once(t2, e) {
861
+ this.setSubscriber(t2, { cb: e, once: true });
862
+ }
863
+ emit(t2, ...e) {
864
+ const i = this._subscribers.get(t2) ?? [], n = [];
865
+ for (const s of i)
866
+ s.cb(...e), s.once && n.push(() => i.splice(i.indexOf(s), 1));
867
+ for (const s of n)
868
+ s();
869
+ }
870
+ prompt() {
871
+ return new Promise((t2) => {
872
+ if (this._abortSignal) {
873
+ if (this._abortSignal.aborted)
874
+ return this.state = "cancel", this.close(), t2(CANCEL_SYMBOL);
875
+ this._abortSignal.addEventListener("abort", () => {
876
+ this.state = "cancel", this.close();
877
+ }, { once: true });
878
+ }
879
+ this.rl = l__default.createInterface({
880
+ input: this.input,
881
+ tabSize: 2,
882
+ prompt: "",
883
+ escapeCodeTimeout: 50,
884
+ terminal: true
885
+ }), this.rl.prompt(), this.opts.initialUserInput !== undefined && this._setUserInput(this.opts.initialUserInput, true), this.input.on("keypress", this.onKeypress), setRawMode(this.input, true), this.output.on("resize", this.render), this.render(), this.once("submit", () => {
886
+ this.output.write(import_sisteransi.cursor.show), this.output.off("resize", this.render), setRawMode(this.input, false), t2(this.value);
887
+ }), this.once("cancel", () => {
888
+ this.output.write(import_sisteransi.cursor.show), this.output.off("resize", this.render), setRawMode(this.input, false), t2(CANCEL_SYMBOL);
889
+ });
890
+ });
891
+ }
892
+ _isActionKey(t2, e) {
893
+ return t2 === "\t";
894
+ }
895
+ _shouldSubmit(t2, e) {
171
896
  return true;
172
- } catch (err) {
173
- fail(`Could not modify ${basename(htmlPath)}: ${err.message}`);
174
- return false;
897
+ }
898
+ _setValue(t2) {
899
+ this.value = t2, this.emit("value", this.value);
900
+ }
901
+ _setUserInput(t2, e) {
902
+ this.userInput = t2 ?? "", this.emit("userInput", this.userInput), e && this._track && this.rl && (this.rl.write(this.userInput), this._cursor = this.rl.cursor);
903
+ }
904
+ _clearUserInput() {
905
+ this.rl?.write(null, { ctrl: true, name: "u" }), this._setUserInput("");
906
+ }
907
+ onKeypress(t2, e) {
908
+ if (this._track && e.name !== "return" && (e.name && this._isActionKey(t2, e) && this.rl?.write(null, { ctrl: true, name: "h" }), this._cursor = this.rl?.cursor ?? 0, this._setUserInput(this.rl?.line)), this.state === "error" && (this.state = "active"), e?.name && (!this._track && settings.aliases.has(e.name) && this.emit("cursor", settings.aliases.get(e.name)), settings.actions.has(e.name) && this.emit("cursor", e.name)), t2 && (t2.toLowerCase() === "y" || t2.toLowerCase() === "n") && this.emit("confirm", t2.toLowerCase() === "y"), this.emit("key", t2, e), e?.name === "return" && this._shouldSubmit(t2, e)) {
909
+ if (this.opts.validate) {
910
+ const i = runValidation(this.opts.validate, this.value);
911
+ i && (this.error = i instanceof Error ? i.message : i, this.state = "error", this.rl?.write(this.userInput));
912
+ }
913
+ this.state !== "error" && (this.state = "submit");
914
+ }
915
+ isActionKey([t2, e?.name, e?.sequence], "cancel") && (this.state = "cancel"), (this.state === "submit" || this.state === "cancel") && this.emit("finalize"), this.render(), (this.state === "submit" || this.state === "cancel") && this.close();
916
+ }
917
+ close() {
918
+ this.input.unpipe(), this.input.removeListener("keypress", this.onKeypress), this.output.write(`
919
+ `), setRawMode(this.input, false), this.rl?.close(), this.rl = undefined, this.emit(`${this.state}`, this.value), this.unsubscribe();
920
+ }
921
+ restoreCursor() {
922
+ const t2 = wrapAnsi(this._prevFrame, process.stdout.columns, { hard: true, trim: false }).split(`
923
+ `).length - 1;
924
+ this.output.write(import_sisteransi.cursor.move(-999, t2 * -1));
925
+ }
926
+ render() {
927
+ const t2 = wrapAnsi(this._render(this) ?? "", process.stdout.columns, {
928
+ hard: true,
929
+ trim: false
930
+ });
931
+ if (t2 !== this._prevFrame) {
932
+ if (this.state === "initial")
933
+ this.output.write(import_sisteransi.cursor.hide);
934
+ else {
935
+ const e = diffLines(this._prevFrame, t2), i = getRows(this.output);
936
+ if (this.restoreCursor(), e) {
937
+ const n = Math.max(0, e.numLinesAfter - i), s = Math.max(0, e.numLinesBefore - i);
938
+ let r = e.lines.find((o) => o >= n);
939
+ if (r === undefined) {
940
+ this._prevFrame = t2;
941
+ return;
942
+ }
943
+ if (e.lines.length === 1) {
944
+ this.output.write(import_sisteransi.cursor.move(0, r - s)), this.output.write(import_sisteransi.erase.lines(1));
945
+ const o = t2.split(`
946
+ `);
947
+ this.output.write(o[r]), this._prevFrame = t2, this.output.write(import_sisteransi.cursor.move(0, o.length - r - 1));
948
+ return;
949
+ } else if (e.lines.length > 1) {
950
+ if (n < s)
951
+ r = n;
952
+ else {
953
+ const h = r - s;
954
+ h > 0 && this.output.write(import_sisteransi.cursor.move(0, h));
955
+ }
956
+ this.output.write(import_sisteransi.erase.down());
957
+ const f = t2.split(`
958
+ `).slice(r);
959
+ this.output.write(f.join(`
960
+ `)), this._prevFrame = t2;
961
+ return;
962
+ }
963
+ }
964
+ this.output.write(import_sisteransi.erase.down());
965
+ }
966
+ this.output.write(t2), this.state === "initial" && (this.state = "active"), this._prevFrame = t2;
967
+ }
175
968
  }
176
969
  }
177
- function writeEnvFile(cwd, apiKey) {
178
- const envPath = join(cwd, ".env");
179
- const KEY = "SNAPFAIL_API_KEY";
180
- const entry = `${KEY}=${apiKey}`;
181
- try {
182
- if (!existsSync(envPath)) {
183
- writeFileSync(envPath, `# SnapFail
184
- ${entry}
185
- `, "utf8");
186
- success(`Created .env → ${bold(KEY)}`);
187
- } else {
188
- let content = readFileSync(envPath, "utf8");
189
- if (new RegExp(`^${KEY}=`, "m").test(content)) {
190
- content = content.replace(new RegExp(`^${KEY}=.*$`, "m"), entry);
191
- success(`Updated ${bold(KEY)} in .env`);
192
- } else {
193
- const nl = content.endsWith(`
194
- `) ? "" : `
195
- `;
196
- content = content + `${nl}
197
- # SnapFail
198
- ${entry}
199
- `;
200
- success(`Added ${bold(KEY)} to .env`);
970
+ function p$1(l2, e) {
971
+ if (l2 === undefined || e.length === 0)
972
+ return 0;
973
+ const i = e.findIndex((s) => s.value === l2);
974
+ return i !== -1 ? i : 0;
975
+ }
976
+ function g(l2, e) {
977
+ return (e.label ?? String(e.value)).toLowerCase().includes(l2.toLowerCase());
978
+ }
979
+ function m(l2, e) {
980
+ if (e)
981
+ return l2 ? e : e[0];
982
+ }
983
+ var T$1 = class T extends V {
984
+ filteredOptions;
985
+ multiple;
986
+ isNavigating = false;
987
+ selectedValues = [];
988
+ focusedValue;
989
+ #e = 0;
990
+ #s = "";
991
+ #t;
992
+ #i;
993
+ #n;
994
+ get cursor() {
995
+ return this.#e;
996
+ }
997
+ get userInputWithCursor() {
998
+ if (!this.userInput)
999
+ return styleText(["inverse", "hidden"], "_");
1000
+ if (this._cursor >= this.userInput.length)
1001
+ return `${this.userInput}\u2588`;
1002
+ const e = this.userInput.slice(0, this._cursor), [t2, ...i] = this.userInput.slice(this._cursor);
1003
+ return `${e}${styleText("inverse", t2)}${i.join("")}`;
1004
+ }
1005
+ get options() {
1006
+ return typeof this.#i == "function" ? this.#i() : this.#i;
1007
+ }
1008
+ constructor(e) {
1009
+ super(e), this.#i = e.options, this.#n = e.placeholder;
1010
+ const t2 = this.options;
1011
+ this.filteredOptions = [...t2], this.multiple = e.multiple === true, this.#t = typeof e.options == "function" ? e.filter : e.filter ?? g;
1012
+ let i;
1013
+ if (e.initialValue && Array.isArray(e.initialValue) ? this.multiple ? i = e.initialValue : i = e.initialValue.slice(0, 1) : !this.multiple && this.options.length > 0 && (i = [this.options[0].value]), i)
1014
+ for (const s of i) {
1015
+ const n = t2.findIndex((o) => o.value === s);
1016
+ n !== -1 && (this.toggleSelected(s), this.#e = n);
201
1017
  }
202
- writeFileSync(envPath, content, "utf8");
1018
+ this.focusedValue = this.options[this.#e]?.value, this.on("key", (s, n) => this.#l(s, n)), this.on("userInput", (s) => this.#u(s));
1019
+ }
1020
+ _isActionKey(e, t2) {
1021
+ return e === "\t" || this.multiple && this.isNavigating && t2.name === "space" && e !== undefined && e !== "";
1022
+ }
1023
+ #l(e, t2) {
1024
+ const i = t2.name === "up", s = t2.name === "down", n = t2.name === "return", o = this.userInput === "" || this.userInput === "\t", u = this.#n, h = this.options, f = u !== undefined && u !== "" && h.some((r) => !r.disabled && (this.#t ? this.#t(u, r) : true));
1025
+ if (t2.name === "tab" && o && f) {
1026
+ this.userInput === "\t" && this._clearUserInput(), this._setUserInput(u, true), this.isNavigating = false;
1027
+ return;
203
1028
  }
204
- } catch (err) {
205
- warn(`Could not write .env: ${err.message}`);
206
- warn(`Add manually: ${bold(entry)}`);
207
- }
208
- }
209
- function printManualInstructions(kind, apiKey, appName, enableReplay) {
210
- console.log("");
211
- warn("Automatic injection was skipped. Add SnapFail manually:");
212
- console.log("");
213
- const replayParam = enableReplay ? ", recordSession: true" : "";
214
- if (kind === "astro") {
215
- console.log(` ${dim("# .env")}`);
216
- console.log(` SNAPFAIL_API_KEY=${apiKey}`);
217
- console.log("");
218
- console.log(` ${dim("// astro.config.mjs")}`);
219
- console.log(` import { snapfail } from '@snapfail/sdk';`);
220
- console.log(` export default defineConfig({`);
221
- console.log(` integrations: [snapfail({ apiKey: process.env.SNAPFAIL_API_KEY ?? '', appName: '${appName}'${replayParam} })],`);
222
- console.log(` });`);
223
- } else if (kind === "vite") {
224
- console.log(` ${dim("# .env")}`);
225
- console.log(` SNAPFAIL_API_KEY=${apiKey}`);
226
- console.log("");
227
- console.log(` ${dim("// vite.config.ts")}`);
228
- console.log(` import { snapfail } from '@snapfail/sdk';`);
229
- console.log(` export default defineConfig({`);
230
- console.log(` plugins: [snapfail({ apiKey: process.env.SNAPFAIL_API_KEY ?? '', appName: '${appName}'${replayParam} })],`);
231
- console.log(` });`);
232
- } else {
233
- const config = { apiKey };
234
- if (enableReplay) {
235
- config.recordSession = true;
236
- }
237
- console.log(` ${dim("<!-- index.html <head> CDN sdk (no build tool) -->")}`);
238
- console.log(` <script>window.__snapfailConfig=${JSON.stringify(config)};</script>`);
239
- console.log(` <script type="module" src="https://cdn.jsdelivr.net/npm/@snapfail/sdk/dist/sdk.js"></script>`);
240
- }
241
- console.log("");
242
- }
243
- async function runBrowserAuth() {
244
- const csrfToken = randomHex(16);
245
- return new Promise((resolve2) => {
246
- let done = false;
247
- let server = null;
248
- const timeout = setTimeout(() => {
249
- if (!done) {
250
- done = true;
251
- server?.close();
252
- fail("Authentication timed out (5 min). Run `snapfail init` again.");
253
- resolve2(null);
254
- }
255
- }, AUTH_TIMEOUT_MS);
256
- server = createServer((req, res) => {
257
- const corsHeaders = {
258
- "Access-Control-Allow-Origin": "*",
259
- "Access-Control-Allow-Methods": "GET, OPTIONS",
260
- "Access-Control-Allow-Headers": "Content-Type"
1029
+ i || s ? (this.#e = findCursor(this.#e, i ? -1 : 1, this.filteredOptions), this.focusedValue = this.filteredOptions[this.#e]?.value, this.multiple || (this.selectedValues = [this.focusedValue]), this.isNavigating = true) : n ? this.value = m(this.multiple, this.selectedValues) : this.multiple ? this.focusedValue !== undefined && (t2.name === "tab" || this.isNavigating && t2.name === "space") ? this.toggleSelected(this.focusedValue) : this.isNavigating = false : (this.focusedValue && (this.selectedValues = [this.focusedValue]), this.isNavigating = false);
1030
+ }
1031
+ deselectAll() {
1032
+ this.selectedValues = [];
1033
+ }
1034
+ toggleSelected(e) {
1035
+ this.filteredOptions.length !== 0 && (this.multiple ? this.selectedValues.includes(e) ? this.selectedValues = this.selectedValues.filter((t2) => t2 !== e) : this.selectedValues = [...this.selectedValues, e] : this.selectedValues = [e]);
1036
+ }
1037
+ #u(e) {
1038
+ if (e !== this.#s) {
1039
+ this.#s = e;
1040
+ const t2 = this.options;
1041
+ e && this.#t ? this.filteredOptions = t2.filter((n) => this.#t?.(e, n)) : this.filteredOptions = [...t2];
1042
+ const i = p$1(this.focusedValue, this.filteredOptions);
1043
+ this.#e = findCursor(i, 0, this.filteredOptions);
1044
+ const s = this.filteredOptions[this.#e];
1045
+ s && !s.disabled ? this.focusedValue = s.value : this.focusedValue = undefined, this.multiple || (this.focusedValue !== undefined ? this.toggleSelected(this.focusedValue) : this.deselectAll());
1046
+ }
1047
+ }
1048
+ };
1049
+ var _ = {
1050
+ Y: { type: "year", len: 4 },
1051
+ M: { type: "month", len: 2 },
1052
+ D: { type: "day", len: 2 }
1053
+ };
1054
+ function M(r) {
1055
+ return [...r].map((t2) => _[t2]);
1056
+ }
1057
+ function P(r) {
1058
+ const i = new Intl.DateTimeFormat(r, {
1059
+ year: "numeric",
1060
+ month: "2-digit",
1061
+ day: "2-digit"
1062
+ }).formatToParts(new Date(2000, 0, 15)), s = [];
1063
+ let n = "/";
1064
+ for (const e of i)
1065
+ e.type === "literal" ? n = e.value.trim() || e.value : (e.type === "year" || e.type === "month" || e.type === "day") && s.push({ type: e.type, len: e.type === "year" ? 4 : 2 });
1066
+ return { segments: s, separator: n };
1067
+ }
1068
+ function p(r) {
1069
+ return Number.parseInt((r || "0").replace(/_/g, "0"), 10) || 0;
1070
+ }
1071
+ function f(r) {
1072
+ return {
1073
+ year: p(r.year),
1074
+ month: p(r.month),
1075
+ day: p(r.day)
1076
+ };
1077
+ }
1078
+ function c(r, t2) {
1079
+ return new Date(r || 2001, t2 || 1, 0).getDate();
1080
+ }
1081
+ function b(r) {
1082
+ const { year: t2, month: i, day: s } = f(r);
1083
+ if (!t2 || t2 < 0 || t2 > 9999 || !i || i < 1 || i > 12 || !s || s < 1)
1084
+ return;
1085
+ const n = new Date(Date.UTC(t2, i - 1, s));
1086
+ if (!(n.getUTCFullYear() !== t2 || n.getUTCMonth() !== i - 1 || n.getUTCDate() !== s))
1087
+ return { year: t2, month: i, day: s };
1088
+ }
1089
+ function C(r) {
1090
+ const t2 = b(r);
1091
+ return t2 ? new Date(Date.UTC(t2.year, t2.month - 1, t2.day)) : undefined;
1092
+ }
1093
+ function T2(r, t2, i, s) {
1094
+ const n = i ? {
1095
+ year: i.getUTCFullYear(),
1096
+ month: i.getUTCMonth() + 1,
1097
+ day: i.getUTCDate()
1098
+ } : null, e = s ? {
1099
+ year: s.getUTCFullYear(),
1100
+ month: s.getUTCMonth() + 1,
1101
+ day: s.getUTCDate()
1102
+ } : null;
1103
+ return r === "year" ? { min: n?.year ?? 1, max: e?.year ?? 9999 } : r === "month" ? {
1104
+ min: n && t2.year === n.year ? n.month : 1,
1105
+ max: e && t2.year === e.year ? e.month : 12
1106
+ } : {
1107
+ min: n && t2.year === n.year && t2.month === n.month ? n.day : 1,
1108
+ max: e && t2.year === e.year && t2.month === e.month ? e.day : c(t2.year, t2.month)
1109
+ };
1110
+ }
1111
+
1112
+ class U extends V {
1113
+ #i;
1114
+ #o;
1115
+ #t;
1116
+ #h;
1117
+ #u;
1118
+ #e = { segmentIndex: 0, positionInSegment: 0 };
1119
+ #n = true;
1120
+ #s = null;
1121
+ inlineError = "";
1122
+ get segmentCursor() {
1123
+ return { ...this.#e };
1124
+ }
1125
+ get segmentValues() {
1126
+ return { ...this.#t };
1127
+ }
1128
+ get segments() {
1129
+ return this.#i;
1130
+ }
1131
+ get separator() {
1132
+ return this.#o;
1133
+ }
1134
+ get formattedValue() {
1135
+ return this.#l(this.#t);
1136
+ }
1137
+ #l(t2) {
1138
+ return this.#i.map((i) => t2[i.type]).join(this.#o);
1139
+ }
1140
+ #r() {
1141
+ this._setUserInput(this.#l(this.#t)), this._setValue(C(this.#t) ?? undefined);
1142
+ }
1143
+ constructor(t2) {
1144
+ 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 ? {
1145
+ year: String(e.getUTCFullYear()).padStart(4, "0"),
1146
+ month: String(e.getUTCMonth() + 1).padStart(2, "0"),
1147
+ day: String(e.getUTCDate()).padStart(2, "0")
1148
+ } : { year: "____", month: "__", day: "__" }, o = n.map((a) => m2[a.type]).join(s);
1149
+ 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));
1150
+ }
1151
+ #a() {
1152
+ const t2 = Math.max(0, Math.min(this.#e.segmentIndex, this.#i.length - 1)), i = this.#i[t2];
1153
+ if (i)
1154
+ return this.#e.positionInSegment = Math.max(0, Math.min(this.#e.positionInSegment, i.len - 1)), { segment: i, index: t2 };
1155
+ }
1156
+ #m(t2) {
1157
+ this.inlineError = "", this.#s = null;
1158
+ const i = this.#a();
1159
+ i && (this.#e.segmentIndex = Math.max(0, Math.min(this.#i.length - 1, i.index + t2)), this.#e.positionInSegment = 0, this.#n = true);
1160
+ }
1161
+ #d(t2) {
1162
+ const i = this.#a();
1163
+ if (!i)
1164
+ return;
1165
+ 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);
1166
+ let a;
1167
+ e ? a = t2 === 1 ? o.min : o.max : a = Math.max(Math.min(o.max, m2 + t2), o.min), this.#t = {
1168
+ ...this.#t,
1169
+ [s.type]: a.toString().padStart(s.len, "0")
1170
+ }, this.#n = true, this.#s = null, this.#r();
1171
+ }
1172
+ #f(t2) {
1173
+ if (t2)
1174
+ switch (t2) {
1175
+ case "right":
1176
+ return this.#m(1);
1177
+ case "left":
1178
+ return this.#m(-1);
1179
+ case "up":
1180
+ return this.#d(1);
1181
+ case "down":
1182
+ return this.#d(-1);
1183
+ }
1184
+ }
1185
+ #y(t2, i) {
1186
+ if (i?.name === "backspace" || i?.sequence === "\x7F" || i?.sequence === "\b" || t2 === "\x7F" || t2 === "\b") {
1187
+ this.inlineError = "";
1188
+ const n = this.#a();
1189
+ if (!n)
1190
+ return;
1191
+ if (!this.#t[n.segment.type].replace(/_/g, "")) {
1192
+ this.#m(-1);
1193
+ return;
1194
+ }
1195
+ this.#t[n.segment.type] = "_".repeat(n.segment.len), this.#n = true, this.#e.positionInSegment = 0, this.#r();
1196
+ return;
1197
+ }
1198
+ if (i?.name === "tab") {
1199
+ this.inlineError = "";
1200
+ const n = this.#a();
1201
+ if (!n)
1202
+ return;
1203
+ const e = i.shift ? -1 : 1, m2 = n.index + e;
1204
+ m2 >= 0 && m2 < this.#i.length && (this.#e.segmentIndex = m2, this.#e.positionInSegment = 0, this.#n = true);
1205
+ return;
1206
+ }
1207
+ if (t2 && /^[0-9]$/.test(t2)) {
1208
+ const n = this.#a();
1209
+ if (!n)
1210
+ return;
1211
+ const { segment: e } = n, m2 = !this.#t[e.type].replace(/_/g, "");
1212
+ if (this.#n && this.#s !== null && !m2) {
1213
+ const h = this.#s + t2, d = { ...this.#t, [e.type]: h }, g2 = this.#g(d, e);
1214
+ if (g2) {
1215
+ this.inlineError = g2, this.#s = null, this.#n = false;
1216
+ return;
1217
+ }
1218
+ 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);
1219
+ return;
1220
+ }
1221
+ this.#n && !m2 && (this.#t[e.type] = "_".repeat(e.len), this.#e.positionInSegment = 0), this.#n = false, this.#s = null;
1222
+ const o = this.#t[e.type], a = o.indexOf("_"), u = a >= 0 ? a : Math.min(this.#e.positionInSegment, e.len - 1);
1223
+ if (u < 0 || u >= e.len)
1224
+ return;
1225
+ let l2 = o.slice(0, u) + t2 + o.slice(u + 1), D = false;
1226
+ if (u === 0 && o === "__" && (e.type === "month" || e.type === "day")) {
1227
+ const h = Number.parseInt(t2, 10);
1228
+ l2 = `0${t2}`, D = h <= (e.type === "month" ? 1 : 2);
1229
+ }
1230
+ if (e.type === "year" && (l2 = (o.replace(/_/g, "") + t2).padStart(e.len, "_")), !l2.includes("_")) {
1231
+ const h = { ...this.#t, [e.type]: l2 }, d = this.#g(h, e);
1232
+ if (d) {
1233
+ this.inlineError = d;
1234
+ return;
1235
+ }
1236
+ }
1237
+ this.inlineError = "", this.#t[e.type] = l2;
1238
+ const y = l2.includes("_") ? undefined : b(this.#t);
1239
+ if (y) {
1240
+ const { year: h, month: d } = y, g2 = c(h, d);
1241
+ this.#t = {
1242
+ year: String(Math.max(0, Math.min(9999, h))).padStart(4, "0"),
1243
+ month: String(Math.max(1, Math.min(12, d))).padStart(2, "0"),
1244
+ day: String(Math.max(1, Math.min(g2, y.day))).padStart(2, "0")
1245
+ };
1246
+ }
1247
+ this.#r();
1248
+ const S = l2.indexOf("_");
1249
+ 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);
1250
+ }
1251
+ }
1252
+ #g(t2, i) {
1253
+ const { month: s, day: n } = f(t2);
1254
+ if (i.type === "month" && (s < 0 || s > 12))
1255
+ return settings.date.messages.invalidMonth;
1256
+ if (i.type === "day" && (n < 0 || n > 31))
1257
+ return settings.date.messages.invalidDay(31, "any month");
1258
+ }
1259
+ #p(t2) {
1260
+ const { year: i, month: s, day: n } = f(this.#t);
1261
+ if (i && s && n) {
1262
+ const e = c(i, s);
1263
+ this.#t = {
1264
+ ...this.#t,
1265
+ day: String(Math.min(n, e)).padStart(2, "0")
261
1266
  };
262
- if (req.method === "OPTIONS") {
263
- res.writeHead(204, corsHeaders);
264
- res.end();
1267
+ }
1268
+ this.value = C(this.#t) ?? t2.defaultValue ?? undefined;
1269
+ }
1270
+ }
1271
+ var u$1 = class u extends V {
1272
+ options;
1273
+ cursor = 0;
1274
+ #t;
1275
+ getGroupItems(t2) {
1276
+ return this.options.filter((r) => r.group === t2);
1277
+ }
1278
+ isGroupSelected(t2) {
1279
+ const r = this.getGroupItems(t2), e = this.value;
1280
+ return e === undefined ? false : r.every((s) => e.includes(s.value));
1281
+ }
1282
+ toggleValue() {
1283
+ const t2 = this.options[this.cursor];
1284
+ if (this.value === undefined && (this.value = []), t2.group === true) {
1285
+ const r = t2.value, e = this.getGroupItems(r);
1286
+ 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));
1287
+ } else {
1288
+ const r = this.value.includes(t2.value);
1289
+ this.value = r ? this.value.filter((e) => e !== t2.value) : [...this.value, t2.value];
1290
+ }
1291
+ }
1292
+ constructor(t2) {
1293
+ super(t2, false);
1294
+ const { options: r } = t2;
1295
+ this.#t = t2.selectableGroups !== false, this.options = Object.entries(r).flatMap(([e, s]) => [
1296
+ { value: e, group: true, label: e },
1297
+ ...s.map((i) => ({ ...i, group: e }))
1298
+ ]), this.value = [...t2.initialValues ?? []], this.cursor = Math.max(this.options.findIndex(({ value: e }) => e === t2.cursorAt), this.#t ? 0 : 1), this.on("cursor", (e) => {
1299
+ switch (e) {
1300
+ case "left":
1301
+ case "up": {
1302
+ this.cursor = this.cursor === 0 ? this.options.length - 1 : this.cursor - 1;
1303
+ const s = this.options[this.cursor]?.group === true;
1304
+ !this.#t && s && (this.cursor = this.cursor === 0 ? this.options.length - 1 : this.cursor - 1);
1305
+ break;
1306
+ }
1307
+ case "down":
1308
+ case "right": {
1309
+ this.cursor = this.cursor === this.options.length - 1 ? 0 : this.cursor + 1;
1310
+ const s = this.options[this.cursor]?.group === true;
1311
+ !this.#t && s && (this.cursor = this.cursor === this.options.length - 1 ? 0 : this.cursor + 1);
1312
+ break;
1313
+ }
1314
+ case "space":
1315
+ this.toggleValue();
1316
+ break;
1317
+ }
1318
+ });
1319
+ }
1320
+ };
1321
+ var o$1 = /* @__PURE__ */ new Set(["up", "down", "left", "right"]);
1322
+
1323
+ class h extends V {
1324
+ #s = false;
1325
+ #t;
1326
+ focused = "editor";
1327
+ get userInputWithCursor() {
1328
+ if (this.state === "submit")
1329
+ return this.userInput;
1330
+ const t2 = this.userInput;
1331
+ if (this.cursor >= t2.length)
1332
+ return `${t2}\u2588`;
1333
+ const s = t2.slice(0, this.cursor), r = t2[this.cursor], e = t2.slice(this.cursor + 1);
1334
+ return r === `
1335
+ ` ? `${s}\u2588
1336
+ ${e}` : `${s}${styleText("inverse", r)}${e}`;
1337
+ }
1338
+ get cursor() {
1339
+ return this._cursor;
1340
+ }
1341
+ #r(t2) {
1342
+ if (this.userInput.length === 0) {
1343
+ this._setUserInput(t2);
1344
+ return;
1345
+ }
1346
+ this._setUserInput(this.userInput.slice(0, this.cursor) + t2 + this.userInput.slice(this.cursor));
1347
+ }
1348
+ #i(t2) {
1349
+ const s = this.value ?? "";
1350
+ switch (t2) {
1351
+ case "up":
1352
+ this._cursor = findTextCursor(this._cursor, 0, -1, s);
1353
+ return;
1354
+ case "down":
1355
+ this._cursor = findTextCursor(this._cursor, 0, 1, s);
1356
+ return;
1357
+ case "left":
1358
+ this._cursor = findTextCursor(this._cursor, -1, 0, s);
1359
+ return;
1360
+ case "right":
1361
+ this._cursor = findTextCursor(this._cursor, 1, 0, s);
1362
+ return;
1363
+ }
1364
+ }
1365
+ _shouldSubmit(t2, s) {
1366
+ if (this.#t)
1367
+ return this.focused === "submit" ? true : (this.#r(`
1368
+ `), this._cursor++, false);
1369
+ const r = this.#s;
1370
+ return this.#s = true, r ? (this.userInput[this.cursor - 1] === `
1371
+ ` && (this._setUserInput(this.userInput.slice(0, this.cursor - 1) + this.userInput.slice(this.cursor)), this._cursor--), true) : (this.#r(`
1372
+ `), this._cursor++, false);
1373
+ }
1374
+ constructor(t2) {
1375
+ super(t2, false), this.#t = t2.showSubmit ?? false, this.on("key", (s, r) => {
1376
+ if (r?.name && o$1.has(r.name)) {
1377
+ this.#i(r.name);
265
1378
  return;
266
1379
  }
267
- const url = new URL(req.url ?? "/", `http://localhost`);
268
- if (url.pathname !== "/callback") {
269
- res.writeHead(404, corsHeaders);
270
- res.end("Not found");
1380
+ if (s === "\t" && this.#t) {
1381
+ this.focused = this.focused === "editor" ? "submit" : "editor";
271
1382
  return;
272
1383
  }
273
- const apiKey = url.searchParams.get("apiKey") ?? "";
274
- const projectName = url.searchParams.get("projectName") ?? "My App";
275
- const returnToken = url.searchParams.get("token") ?? "";
276
- if (returnToken !== csrfToken || !apiKey.startsWith("sf_")) {
277
- const body2 = htmlString("✗ Auth failed", `<p style="color:#ff4444">Invalid token or API key. Please run <code>snapfail init</code> again.</p>`, false);
278
- res.writeHead(400, { "Content-Type": "text/html; charset=utf-8", ...corsHeaders });
279
- res.end(body2);
1384
+ if (r?.name !== "return") {
1385
+ if (this.#s = false, r?.name === "backspace" && this.cursor > 0) {
1386
+ this._setUserInput(this.userInput.slice(0, this.cursor - 1) + this.userInput.slice(this.cursor)), this._cursor--;
1387
+ return;
1388
+ }
1389
+ if (r?.name === "delete" && this.cursor < this.userInput.length) {
1390
+ this._setUserInput(this.userInput.slice(0, this.cursor) + this.userInput.slice(this.cursor + 1));
1391
+ return;
1392
+ }
1393
+ s && (this.#t && this.focused === "submit" && (this.focused = "editor"), this.#r(s ?? ""), this._cursor++);
1394
+ }
1395
+ }), this.on("userInput", (s) => {
1396
+ this._setValue(s);
1397
+ }), this.on("finalize", () => {
1398
+ this.value || (this.value = t2.defaultValue), this.value === undefined && (this.value = "");
1399
+ });
1400
+ }
1401
+ }
1402
+ class a extends V {
1403
+ options;
1404
+ cursor = 0;
1405
+ get _selectedValue() {
1406
+ return this.options[this.cursor];
1407
+ }
1408
+ changeValue() {
1409
+ this.value = this._selectedValue.value;
1410
+ }
1411
+ constructor(t2) {
1412
+ super(t2, false), this.options = t2.options;
1413
+ const i = this.options.findIndex(({ value: s }) => s === t2.initialValue), e = i === -1 ? 0 : i;
1414
+ this.cursor = this.options[e].disabled ? findCursor(e, 1, this.options) : e, this.changeValue(), this.on("cursor", (s) => {
1415
+ switch (s) {
1416
+ case "left":
1417
+ case "up":
1418
+ this.cursor = findCursor(this.cursor, -1, this.options);
1419
+ break;
1420
+ case "down":
1421
+ case "right":
1422
+ this.cursor = findCursor(this.cursor, 1, this.options);
1423
+ break;
1424
+ }
1425
+ this.changeValue();
1426
+ });
1427
+ }
1428
+ }
1429
+ class n extends V {
1430
+ get userInputWithCursor() {
1431
+ if (this.state === "submit")
1432
+ return this.userInput;
1433
+ const t2 = this.userInput;
1434
+ if (this.cursor >= t2.length)
1435
+ return `${this.userInput}\u2588`;
1436
+ const e = t2.slice(0, this.cursor), [s, ...r] = t2.slice(this.cursor);
1437
+ return `${e}${styleText("inverse", s)}${r.join("")}`;
1438
+ }
1439
+ get cursor() {
1440
+ return this._cursor;
1441
+ }
1442
+ constructor(t2) {
1443
+ super({
1444
+ ...t2,
1445
+ initialUserInput: t2.initialUserInput ?? t2.initialValue
1446
+ }), this.on("userInput", (e) => {
1447
+ this._setValue(e);
1448
+ }), this.on("finalize", () => {
1449
+ this.value || (this.value = t2.defaultValue), this.value === undefined && (this.value = "");
1450
+ });
1451
+ }
1452
+ }
1453
+
1454
+ // ../../node_modules/.bun/@clack+prompts@1.5.1/node_modules/@clack/prompts/dist/index.mjs
1455
+ import { styleText as styleText2, stripVTControlCharacters } from "util";
1456
+ import process$1 from "process";
1457
+ var import_sisteransi2 = __toESM(require_src(), 1);
1458
+ function isUnicodeSupported() {
1459
+ if (process$1.platform !== "win32") {
1460
+ return process$1.env.TERM !== "linux";
1461
+ }
1462
+ 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";
1463
+ }
1464
+ var unicode = isUnicodeSupported();
1465
+ var isCI = () => process.env.CI === "true";
1466
+ var unicodeOr = (e, o2) => unicode ? e : o2;
1467
+ var S_STEP_ACTIVE = unicodeOr("\u25C6", "*");
1468
+ var S_STEP_CANCEL = unicodeOr("\u25A0", "x");
1469
+ var S_STEP_ERROR = unicodeOr("\u25B2", "x");
1470
+ var S_STEP_SUBMIT = unicodeOr("\u25C7", "o");
1471
+ var S_BAR_START = unicodeOr("\u250C", "T");
1472
+ var S_BAR = unicodeOr("\u2502", "|");
1473
+ var S_BAR_END = unicodeOr("\u2514", "\u2014");
1474
+ var S_BAR_START_RIGHT = unicodeOr("\u2510", "T");
1475
+ var S_BAR_END_RIGHT = unicodeOr("\u2518", "\u2014");
1476
+ var S_RADIO_ACTIVE = unicodeOr("\u25CF", ">");
1477
+ var S_RADIO_INACTIVE = unicodeOr("\u25CB", " ");
1478
+ var S_CHECKBOX_ACTIVE = unicodeOr("\u25FB", "[\u2022]");
1479
+ var S_CHECKBOX_SELECTED = unicodeOr("\u25FC", "[+]");
1480
+ var S_CHECKBOX_INACTIVE = unicodeOr("\u25FB", "[ ]");
1481
+ var S_PASSWORD_MASK = unicodeOr("\u25AA", "\u2022");
1482
+ var S_BAR_H = unicodeOr("\u2500", "-");
1483
+ var S_CORNER_TOP_RIGHT = unicodeOr("\u256E", "+");
1484
+ var S_CONNECT_LEFT = unicodeOr("\u251C", "+");
1485
+ var S_CORNER_BOTTOM_RIGHT = unicodeOr("\u256F", "+");
1486
+ var S_CORNER_BOTTOM_LEFT = unicodeOr("\u2570", "+");
1487
+ var S_CORNER_TOP_LEFT = unicodeOr("\u256D", "+");
1488
+ var S_INFO = unicodeOr("\u25CF", "\u2022");
1489
+ var S_SUCCESS = unicodeOr("\u25C6", "*");
1490
+ var S_WARN = unicodeOr("\u25B2", "!");
1491
+ var S_ERROR = unicodeOr("\u25A0", "x");
1492
+ var symbol = (e) => {
1493
+ switch (e) {
1494
+ case "initial":
1495
+ case "active":
1496
+ return styleText2("cyan", S_STEP_ACTIVE);
1497
+ case "cancel":
1498
+ return styleText2("red", S_STEP_CANCEL);
1499
+ case "error":
1500
+ return styleText2("yellow", S_STEP_ERROR);
1501
+ case "submit":
1502
+ return styleText2("green", S_STEP_SUBMIT);
1503
+ }
1504
+ };
1505
+ var symbolBar = (e) => {
1506
+ switch (e) {
1507
+ case "initial":
1508
+ case "active":
1509
+ return styleText2("cyan", S_BAR);
1510
+ case "cancel":
1511
+ return styleText2("red", S_BAR);
1512
+ case "error":
1513
+ return styleText2("yellow", S_BAR);
1514
+ case "submit":
1515
+ return styleText2("green", S_BAR);
1516
+ }
1517
+ };
1518
+ var E$1 = (l2, o2, g2, c2, h2, O = false) => {
1519
+ let r2 = o2, w = 0;
1520
+ if (O)
1521
+ for (let i = c2 - 1;i >= g2 && (r2 -= l2[i].length, w++, !(r2 <= h2)); i--)
1522
+ ;
1523
+ else
1524
+ for (let i = g2;i < c2 && (r2 -= l2[i].length, w++, !(r2 <= h2)); i++)
1525
+ ;
1526
+ return { lineCount: r2, removals: w };
1527
+ };
1528
+ var limitOptions = ({
1529
+ cursor: l2,
1530
+ options: o2,
1531
+ style: g2,
1532
+ output: c2 = process.stdout,
1533
+ maxItems: h2 = Number.POSITIVE_INFINITY,
1534
+ columnPadding: O = 0,
1535
+ rowPadding: r2 = 4
1536
+ }) => {
1537
+ 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);
1538
+ let p2 = 0;
1539
+ l2 >= m2 - 3 && (p2 = Math.max(Math.min(l2 - m2 + 3, o2.length - m2), 0));
1540
+ let f2 = m2 < o2.length && p2 > 0, u3 = m2 < o2.length && p2 + m2 < o2.length;
1541
+ const W = Math.min(p2 + m2, o2.length), e = [];
1542
+ let d = 0;
1543
+ f2 && d++, u3 && d++;
1544
+ const v = p2 + (f2 ? 1 : 0), P2 = W - (u3 ? 1 : 0);
1545
+ for (let t2 = v;t2 < P2; t2++) {
1546
+ const n2 = wrapAnsi(g2(o2[t2], t2 === l2), i, {
1547
+ hard: true,
1548
+ trim: false
1549
+ }).split(`
1550
+ `);
1551
+ e.push(n2), d += n2.length;
1552
+ }
1553
+ if (d > x) {
1554
+ let t2 = 0, n2 = 0, s = d;
1555
+ const M2 = l2 - v;
1556
+ let a2 = x;
1557
+ const T3 = () => E$1(e, s, 0, M2, a2), L = () => E$1(e, s, M2 + 1, e.length, a2, true);
1558
+ 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));
1559
+ }
1560
+ const b2 = [];
1561
+ f2 && b2.push(C2);
1562
+ for (const t2 of e)
1563
+ for (const n2 of t2)
1564
+ b2.push(n2);
1565
+ return u3 && b2.push(C2), b2;
1566
+ };
1567
+ var log = {
1568
+ message: (s = [], {
1569
+ symbol: e = styleText2("gray", S_BAR),
1570
+ secondarySymbol: r2 = styleText2("gray", S_BAR),
1571
+ output: m2 = process.stdout,
1572
+ spacing: l2 = 1,
1573
+ withGuide: c2
1574
+ } = {}) => {
1575
+ const t2 = [], o2 = c2 ?? settings.withGuide, f2 = o2 ? r2 : "", O = o2 ? `${e} ` : "", u3 = o2 ? `${r2} ` : "";
1576
+ for (let i = 0;i < l2; i++)
1577
+ t2.push(f2);
1578
+ const g2 = Array.isArray(s) ? s : s.split(`
1579
+ `);
1580
+ if (g2.length > 0) {
1581
+ const [i, ...y] = g2;
1582
+ i.length > 0 ? t2.push(`${O}${i}`) : t2.push(o2 ? e : "");
1583
+ for (const p2 of y)
1584
+ p2.length > 0 ? t2.push(`${u3}${p2}`) : t2.push(o2 ? r2 : "");
1585
+ }
1586
+ m2.write(`${t2.join(`
1587
+ `)}
1588
+ `);
1589
+ },
1590
+ info: (s, e) => {
1591
+ log.message(s, { ...e, symbol: styleText2("blue", S_INFO) });
1592
+ },
1593
+ success: (s, e) => {
1594
+ log.message(s, { ...e, symbol: styleText2("green", S_SUCCESS) });
1595
+ },
1596
+ step: (s, e) => {
1597
+ log.message(s, { ...e, symbol: styleText2("green", S_STEP_SUBMIT) });
1598
+ },
1599
+ warn: (s, e) => {
1600
+ log.message(s, { ...e, symbol: styleText2("yellow", S_WARN) });
1601
+ },
1602
+ warning: (s, e) => {
1603
+ log.warn(s, e);
1604
+ },
1605
+ error: (s, e) => {
1606
+ log.message(s, { ...e, symbol: styleText2("red", S_ERROR) });
1607
+ }
1608
+ };
1609
+ var cancel = (o2 = "", t2) => {
1610
+ const i = t2?.output ?? process.stdout, e = t2?.withGuide ?? settings.withGuide ? `${styleText2("gray", S_BAR_END)} ` : "";
1611
+ i.write(`${e}${styleText2("red", o2)}
1612
+
1613
+ `);
1614
+ };
1615
+ var intro = (o2 = "", t2) => {
1616
+ const i = t2?.output ?? process.stdout, e = t2?.withGuide ?? settings.withGuide ? `${styleText2("gray", S_BAR_START)} ` : "";
1617
+ i.write(`${e}${o2}
1618
+ `);
1619
+ };
1620
+ var outro = (o2 = "", t2) => {
1621
+ const i = t2?.output ?? process.stdout, e = t2?.withGuide ?? settings.withGuide ? `${styleText2("gray", S_BAR)}
1622
+ ${styleText2("gray", S_BAR_END)} ` : "";
1623
+ i.write(`${e}${o2}
1624
+
1625
+ `);
1626
+ };
1627
+ var W = (l2) => styleText2("magenta", l2);
1628
+ var spinner = ({
1629
+ indicator: l2 = "dots",
1630
+ onCancel: h2,
1631
+ output: n2 = process.stdout,
1632
+ cancelMessage: G,
1633
+ errorMessage: O,
1634
+ frames: E = unicode ? ["\u25D2", "\u25D0", "\u25D3", "\u25D1"] : ["\u2022", "o", "O", "0"],
1635
+ delay: F = unicode ? 80 : 120,
1636
+ signal: m2,
1637
+ ...I
1638
+ } = {}) => {
1639
+ const u3 = isCI();
1640
+ let M2, T3, d = false, S = false, s = "", p2, w = performance.now();
1641
+ const x = getColumns(n2), k = I?.styleFrame ?? W, g2 = (e) => {
1642
+ const r2 = e > 1 ? O ?? settings.messages.error : G ?? settings.messages.cancel;
1643
+ S = e === 1, d && (a2(r2, e), S && typeof h2 == "function" && h2());
1644
+ }, f2 = () => g2(2), i = () => g2(1), A = () => {
1645
+ 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);
1646
+ }, H = () => {
1647
+ 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);
1648
+ }, y = () => {
1649
+ if (p2 === undefined)
1650
+ return;
1651
+ u3 && n2.write(`
1652
+ `);
1653
+ const r2 = wrapAnsi(p2, x, {
1654
+ hard: true,
1655
+ trim: false
1656
+ }).split(`
1657
+ `);
1658
+ 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());
1659
+ }, C2 = (e) => e.replace(/\.+$/, ""), _2 = (e) => {
1660
+ const r2 = (performance.now() - e) / 1000, t2 = Math.floor(r2 / 60), o2 = Math.floor(r2 % 60);
1661
+ return t2 > 0 ? `[${t2}m ${o2}s]` : `[${o2}s]`;
1662
+ }, N = I.withGuide ?? settings.withGuide, P2 = (e = "") => {
1663
+ d = true, M2 = block({ output: n2 }), s = C2(e), w = performance.now(), N && n2.write(`${styleText2("gray", S_BAR)}
1664
+ `);
1665
+ let r2 = 0, t2 = 0;
1666
+ A(), T3 = setInterval(() => {
1667
+ if (u3 && s === p2)
280
1668
  return;
1669
+ y(), p2 = s;
1670
+ const o2 = k(E[r2]);
1671
+ let v;
1672
+ if (u3)
1673
+ v = `${o2} ${s}...`;
1674
+ else if (l2 === "timer")
1675
+ v = `${o2} ${s} ${_2(w)}`;
1676
+ else {
1677
+ const B = ".".repeat(Math.floor(t2)).slice(0, 3);
1678
+ v = `${o2} ${s}${B}`;
1679
+ }
1680
+ const j = wrapAnsi(v, x, {
1681
+ hard: true,
1682
+ trim: false
1683
+ });
1684
+ n2.write(j), r2 = r2 + 1 < E.length ? r2 + 1 : 0, t2 = t2 < 4 ? t2 + 0.125 : 0;
1685
+ }, F);
1686
+ }, a2 = (e = "", r2 = 0, t2 = false) => {
1687
+ if (!d)
1688
+ return;
1689
+ d = false, clearInterval(T3), y();
1690
+ const o2 = r2 === 0 ? styleText2("green", S_STEP_SUBMIT) : r2 === 1 ? styleText2("red", S_STEP_CANCEL) : styleText2("red", S_STEP_ERROR);
1691
+ s = e ?? s, t2 || (l2 === "timer" ? n2.write(`${o2} ${s} ${_2(w)}
1692
+ `) : n2.write(`${o2} ${s}
1693
+ `)), H(), M2();
1694
+ };
1695
+ return {
1696
+ start: P2,
1697
+ stop: (e = "") => a2(e, 0),
1698
+ message: (e = "") => {
1699
+ s = C2(e ?? s);
1700
+ },
1701
+ cancel: (e = "") => a2(e, 1),
1702
+ error: (e = "") => a2(e, 2),
1703
+ clear: () => a2("", 0, true),
1704
+ get isCancelled() {
1705
+ return S;
1706
+ }
1707
+ };
1708
+ };
1709
+ var u3 = {
1710
+ light: unicodeOr("\u2500", "-"),
1711
+ heavy: unicodeOr("\u2501", "="),
1712
+ block: unicodeOr("\u2588", "#")
1713
+ };
1714
+ var c2 = (e, a2) => e.includes(`
1715
+ `) ? e.split(`
1716
+ `).map((t2) => a2(t2)).join(`
1717
+ `) : a2(e);
1718
+ var select = (e) => {
1719
+ const a2 = (t2, d) => {
1720
+ const s = t2.label ?? String(t2.value);
1721
+ switch (d) {
1722
+ case "disabled":
1723
+ return `${styleText2("gray", S_RADIO_INACTIVE)} ${c2(s, (n2) => styleText2("gray", n2))}${t2.hint ? ` ${styleText2("dim", `(${t2.hint ?? "disabled"})`)}` : ""}`;
1724
+ case "selected":
1725
+ return `${c2(s, (n2) => styleText2("dim", n2))}`;
1726
+ case "active":
1727
+ return `${styleText2("green", S_RADIO_ACTIVE)} ${s}${t2.hint ? ` ${styleText2("dim", `(${t2.hint})`)}` : ""}`;
1728
+ case "cancelled":
1729
+ return `${c2(s, (n2) => styleText2(["strikethrough", "dim"], n2))}`;
1730
+ default:
1731
+ return `${styleText2("dim", S_RADIO_INACTIVE)} ${c2(s, (n2) => styleText2("dim", n2))}`;
1732
+ }
1733
+ };
1734
+ return new a({
1735
+ options: e.options,
1736
+ signal: e.signal,
1737
+ input: e.input,
1738
+ output: e.output,
1739
+ initialValue: e.initialValue,
1740
+ render() {
1741
+ 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)}
1742
+ ` : ""}${n2}
1743
+ `;
1744
+ switch (this.state) {
1745
+ case "submit": {
1746
+ const r2 = t2 ? `${styleText2("gray", S_BAR)} ` : "", l2 = wrapTextWithPrefix(e.output, a2(this.options[this.cursor], "selected"), r2);
1747
+ return `${u4}${l2}`;
1748
+ }
1749
+ case "cancel": {
1750
+ const r2 = t2 ? `${styleText2("gray", S_BAR)} ` : "", l2 = wrapTextWithPrefix(e.output, a2(this.options[this.cursor], "cancelled"), r2);
1751
+ return `${u4}${l2}${t2 ? `
1752
+ ${styleText2("gray", S_BAR)}` : ""}`;
1753
+ }
1754
+ default: {
1755
+ const r2 = t2 ? `${styleText2("cyan", S_BAR)} ` : "", l2 = t2 ? styleText2("cyan", S_BAR_END) : "", g2 = u4.split(`
1756
+ `).length, h2 = t2 ? 2 : 1;
1757
+ return `${u4}${r2}${limitOptions({
1758
+ output: e.output,
1759
+ cursor: this.cursor,
1760
+ options: this.options,
1761
+ maxItems: e.maxItems,
1762
+ columnPadding: r2.length,
1763
+ rowPadding: g2 + h2,
1764
+ style: (p2, b2) => a2(p2, p2.disabled ? "disabled" : b2 ? "active" : "inactive")
1765
+ }).join(`
1766
+ ${r2}`)}
1767
+ ${l2}
1768
+ `;
1769
+ }
1770
+ }
1771
+ }
1772
+ }).prompt();
1773
+ };
1774
+ var i = `${styleText2("gray", S_BAR)} `;
1775
+ var text = (t2) => new n({
1776
+ validate: t2.validate,
1777
+ placeholder: t2.placeholder,
1778
+ defaultValue: t2.defaultValue,
1779
+ initialValue: t2.initialValue,
1780
+ output: t2.output,
1781
+ signal: t2.signal,
1782
+ input: t2.input,
1783
+ render() {
1784
+ const i2 = t2?.withGuide ?? settings.withGuide, s = `${`${i2 ? `${styleText2("gray", S_BAR)}
1785
+ ` : ""}${symbol(this.state)} `}${t2.message}
1786
+ `, 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 ?? "";
1787
+ switch (this.state) {
1788
+ case "error": {
1789
+ const n2 = this.error ? ` ${styleText2("yellow", this.error)}` : "", r2 = i2 ? `${styleText2("yellow", S_BAR)} ` : "", d = i2 ? styleText2("yellow", S_BAR_END) : "";
1790
+ return `${s.trim()}
1791
+ ${r2}${o2}
1792
+ ${d}${n2}
1793
+ `;
281
1794
  }
282
- const body = htmlString("✓ Connected!", `<p>You can close this tab and return to your terminal.</p>
283
- <p style="color:#666;font-size:.85em;margin-top:1.5em">Project: <strong>${esc(projectName)}</strong></p>`, true);
284
- res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", ...corsHeaders });
285
- res.end(body);
286
- setTimeout(() => {
287
- clearTimeout(timeout);
288
- server?.close();
289
- }, 150);
290
- if (!done) {
291
- done = true;
292
- resolve2({ apiKey, projectName });
1795
+ case "submit": {
1796
+ const n2 = a2 ? ` ${styleText2("dim", a2)}` : "", r2 = i2 ? styleText2("gray", S_BAR) : "";
1797
+ return `${s}${r2}${n2}`;
1798
+ }
1799
+ case "cancel": {
1800
+ const n2 = a2 ? ` ${styleText2(["strikethrough", "dim"], a2)}` : "", r2 = i2 ? styleText2("gray", S_BAR) : "";
1801
+ return `${s}${r2}${n2}${a2.trim() ? `
1802
+ ${r2}` : ""}`;
1803
+ }
1804
+ default: {
1805
+ const n2 = i2 ? `${styleText2("cyan", S_BAR)} ` : "", r2 = i2 ? styleText2("cyan", S_BAR_END) : "";
1806
+ return `${s}${n2}${o2}
1807
+ ${r2}
1808
+ `;
1809
+ }
1810
+ }
1811
+ }
1812
+ }).prompt();
1813
+
1814
+ // src/skill.ts
1815
+ function generateSkillMd(config, framework) {
1816
+ return `# snapfail
1817
+
1818
+ snapfail captures browser error context (stack, console, network, replay timeline)
1819
+ and stores it so you can diagnose root causes. Evidence is structured for LLM reasoning.
1820
+
1821
+ ## When to use
1822
+
1823
+ When the user reports an error or unexpected behavior in their app, run:
1824
+
1825
+ \`\`\`
1826
+ snapfail incidents --json
1827
+ \`\`\`
1828
+
1829
+ Then drill into a specific incident:
1830
+
1831
+ \`\`\`
1832
+ snapfail incident <id> --json
1833
+ \`\`\`
1834
+
1835
+ **Always use \`--json\` when querying from the LLM** \u2014 it returns structured data
1836
+ without ANSI color codes.
1837
+
1838
+ ## Commands
1839
+
1840
+ \`\`\`
1841
+ snapfail incidents [--status=unresolved|resolved|ignored] [--json]
1842
+ List incident groups. Default status: unresolved.
1843
+
1844
+ snapfail incident <id> [--sample <n>] [--json]
1845
+ Show incident detail: group summary + most recent sample (stack, console, network, timeline).
1846
+ Use --sample <n> (0-indexed) to compare different occurrences of the same error.
1847
+
1848
+ snapfail explain <id>
1849
+ AI diagnosis: root cause, plain summary, fix prompt. (requires @snapfail/ai)
1850
+ \`\`\`
1851
+
1852
+ ## Interpreting an incident
1853
+
1854
+ Key fields from \`snapfail incident <id> --json\`:
1855
+
1856
+ - \`group.title\` \u2014 normalized error message (dynamic values replaced with [uuid], [id], etc.)
1857
+ - \`group.count\` \u2014 total occurrences
1858
+ - \`group.environments\` \u2014 where it happened: "dev" | "prod"
1859
+ - \`group.severity\` \u2014 "critical" | "error" | "warning"
1860
+ - \`sample.stackFrames\` \u2014 call stack at the time of the error
1861
+ - \`sample.consoleEntries\` \u2014 last ~15s of console output before the error
1862
+ - \`sample.networkEntries\` \u2014 last ~15s of network requests (scrubbed)
1863
+ - \`sample.timeline\` \u2014 derived user interaction sequence (clicks, navigation, mutations)
1864
+ - \`sample.url\` / \`sample.route\` \u2014 where the error occurred
1865
+
1866
+ ## Config
1867
+
1868
+ Project key: \`${config.projectKey}\`
1869
+ Endpoint: \`${config.endpoint}\`
1870
+ ${framework ? `Framework: ${framework}
1871
+ ` : ""}
1872
+ Config file: \`.snapfail.json\` (gitignored)
1873
+ `;
1874
+ }
1875
+
1876
+ // src/session.ts
1877
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync, existsSync as existsSync2, rmSync } from "fs";
1878
+ import { join } from "path";
1879
+ import { homedir } from "os";
1880
+ var SESSION_DIR = join(homedir(), ".snapfail");
1881
+ var SESSION_FILE = join(SESSION_DIR, "auth.json");
1882
+ function readSession() {
1883
+ try {
1884
+ if (!existsSync2(SESSION_FILE))
1885
+ return null;
1886
+ const raw = JSON.parse(readFileSync2(SESSION_FILE, "utf-8"));
1887
+ if (!raw.token || !raw.endpoint)
1888
+ return null;
1889
+ return raw;
1890
+ } catch {
1891
+ return null;
1892
+ }
1893
+ }
1894
+ function writeSession(session) {
1895
+ mkdirSync(SESSION_DIR, { recursive: true });
1896
+ writeFileSync2(SESSION_FILE, JSON.stringify(session, null, 2) + `
1897
+ `, "utf-8");
1898
+ }
1899
+
1900
+ // src/auth.ts
1901
+ import { execSync } from "child_process";
1902
+ function openBrowser(url) {
1903
+ try {
1904
+ const platform = process.platform;
1905
+ if (platform === "darwin")
1906
+ execSync(`open "${url}"`, { stdio: "ignore" });
1907
+ else if (platform === "win32")
1908
+ execSync(`cmd /c start "" "${url}"`, { stdio: "ignore" });
1909
+ else
1910
+ execSync(`xdg-open "${url}"`, { stdio: "ignore" });
1911
+ } catch {}
1912
+ }
1913
+ async function ensureSession(endpoint) {
1914
+ const existing = readSession();
1915
+ if (existing && existing.endpoint === endpoint)
1916
+ return existing;
1917
+ const token = await waitForCliToken(endpoint);
1918
+ return token;
1919
+ }
1920
+ async function waitForCliToken(endpoint) {
1921
+ return new Promise((resolve2, reject) => {
1922
+ let resolved = false;
1923
+ const server = Bun.serve({
1924
+ port: 0,
1925
+ async fetch(req) {
1926
+ const url = new URL(req.url);
1927
+ if (url.pathname !== "/callback") {
1928
+ return new Response("Not found", { status: 404 });
1929
+ }
1930
+ const token = url.searchParams.get("token");
1931
+ const email = url.searchParams.get("email");
1932
+ const name = url.searchParams.get("name");
1933
+ if (!token || !email) {
1934
+ return new Response("Missing token or email", { status: 400 });
1935
+ }
1936
+ resolved = true;
1937
+ const session = { token, email, name: name ?? email, endpoint };
1938
+ writeSession(session);
1939
+ setTimeout(() => server.stop(), 200);
1940
+ resolve2(session);
1941
+ return new Response(`<!doctype html><html><head><meta charset="utf-8"><title>snapfail</title>
1942
+ <style>
1943
+ body{background:#0a0a0a;color:#f5f5f5;font-family:system-ui,sans-serif;
1944
+ display:flex;align-items:center;justify-content:center;height:100vh;margin:0}
1945
+ .box{text-align:center;max-width:360px}
1946
+ .logo{font-size:18px;font-weight:600;letter-spacing:-0.5px;margin-bottom:24px;color:#f5f5f5}
1947
+ h1{font-size:20px;font-weight:500;margin:0 0 8px}
1948
+ p{color:#525252;font-size:14px;margin:0}
1949
+ </style></head><body>
1950
+ <div class="box">
1951
+ <div class="logo">snapfail</div>
1952
+ <h1>You're authenticated</h1>
1953
+ <p>You can close this tab and return to the terminal.</p>
1954
+ </div>
1955
+ </body></html>`, { headers: { "Content-Type": "text/html" } });
293
1956
  }
294
1957
  });
295
- server.listen(0, "127.0.0.1", () => {
296
- const addr = server.address();
297
- const callbackBase = `http://localhost:${addr.port}/callback`;
298
- const authUrl = `${APP_URL}/cli-auth?token=${csrfToken}&callback=${encodeURIComponent(callbackBase)}`;
299
- console.log("");
300
- log("Opening browser for authentication…");
301
- console.log(` ${dim(authUrl)}`);
302
- console.log("");
303
- log("Waiting for you to complete sign-in in the browser…");
304
- console.log(` ${dim("(press Ctrl+C to abort)")}`);
305
- openBrowser(authUrl);
1958
+ const port = server.port;
1959
+ const callbackUrl = `http://localhost:${port}/callback`;
1960
+ const authUrl = `${endpoint}/cli-auth?callback=${encodeURIComponent(callbackUrl)}`;
1961
+ console.log("");
1962
+ console.log("Opening your browser to authenticate\u2026");
1963
+ console.log("");
1964
+ console.log(" " + authUrl);
1965
+ console.log("");
1966
+ console.log("If the browser didn't open, copy the URL above and paste it manually.");
1967
+ console.log("");
1968
+ openBrowser(authUrl);
1969
+ setTimeout(() => {
1970
+ if (!resolved) {
1971
+ server.stop();
1972
+ reject(new Error("Authentication timed out after 5 minutes."));
1973
+ }
1974
+ }, 5 * 60 * 1000);
1975
+ });
1976
+ }
1977
+
1978
+ // src/commands/init.ts
1979
+ function detectFramework(cwd) {
1980
+ if (existsSync3(resolve2(cwd, "astro.config.ts")) || existsSync3(resolve2(cwd, "astro.config.mjs")) || existsSync3(resolve2(cwd, "astro.config.js")))
1981
+ return "astro";
1982
+ if (existsSync3(resolve2(cwd, "next.config.ts")) || existsSync3(resolve2(cwd, "next.config.mjs")) || existsSync3(resolve2(cwd, "next.config.js")))
1983
+ return "next";
1984
+ if (existsSync3(resolve2(cwd, "vite.config.ts")) || existsSync3(resolve2(cwd, "vite.config.mjs")) || existsSync3(resolve2(cwd, "vite.config.js")))
1985
+ return "vite";
1986
+ return;
1987
+ }
1988
+ function suggestProjectName(cwd) {
1989
+ try {
1990
+ const pkgPath = resolve2(cwd, "package.json");
1991
+ if (existsSync3(pkgPath)) {
1992
+ const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
1993
+ if (pkg.name)
1994
+ return pkg.name.replace(/^@[^/]+\//, "");
1995
+ }
1996
+ } catch {}
1997
+ return "";
1998
+ }
1999
+ function addToGitignore(cwd, entry) {
2000
+ const gitignorePath = resolve2(cwd, ".gitignore");
2001
+ if (existsSync3(gitignorePath)) {
2002
+ const content = readFileSync3(gitignorePath, "utf-8");
2003
+ if (content.includes(entry))
2004
+ return;
2005
+ writeFileSync3(gitignorePath, content.trimEnd() + `
2006
+ ` + entry + `
2007
+ `, "utf-8");
2008
+ } else {
2009
+ writeFileSync3(gitignorePath, entry + `
2010
+ `, "utf-8");
2011
+ }
2012
+ }
2013
+ function detectPackageManager(cwd) {
2014
+ if (existsSync3(resolve2(cwd, "bun.lockb")) || existsSync3(resolve2(cwd, "bun.lock")))
2015
+ return "bun";
2016
+ if (existsSync3(resolve2(cwd, "pnpm-lock.yaml")))
2017
+ return "pnpm";
2018
+ if (existsSync3(resolve2(cwd, "yarn.lock")))
2019
+ return "yarn";
2020
+ return "npm";
2021
+ }
2022
+ function installPackage(pm, pkg, cwd) {
2023
+ const cmd = pm === "bun" ? `bun add ${pkg}` : pm === "pnpm" ? `pnpm add ${pkg}` : pm === "yarn" ? `yarn add ${pkg}` : `npm install ${pkg}`;
2024
+ execSync2(cmd, { cwd, stdio: "pipe" });
2025
+ }
2026
+ function findConfigFile(cwd, names) {
2027
+ return names.find((n2) => existsSync3(resolve2(cwd, n2)));
2028
+ }
2029
+ var ENV_CALL = `process.env.SNAPFAIL_PROJECT_KEY ?? ""`;
2030
+ function injectViteAdapter(configPath) {
2031
+ let src = readFileSync3(configPath, "utf-8");
2032
+ if (src.includes("@snapfail/vite"))
2033
+ return;
2034
+ src = src.replace(/((?:^import [^\n]+\n)+)/m, `$1import snapfail from "@snapfail/vite";
2035
+ `);
2036
+ src = src.replace(/plugins:\s*\[\s*\]/, `plugins: [snapfail(${ENV_CALL})]`);
2037
+ if (!src.includes(`snapfail(${ENV_CALL})`)) {
2038
+ src = src.replace(/plugins:\s*\[(\s*)/, (_2, ws) => `plugins: [snapfail(${ENV_CALL}),${ws}`);
2039
+ }
2040
+ writeFileSync3(configPath, src, "utf-8");
2041
+ }
2042
+ function injectAstroAdapter(configPath) {
2043
+ let src = readFileSync3(configPath, "utf-8");
2044
+ if (src.includes("@snapfail/astro"))
2045
+ return;
2046
+ src = src.replace(/((?:^import [^\n]+\n)+)/m, `$1import snapfail from "@snapfail/astro";
2047
+ `);
2048
+ if (/integrations:\s*\[/.test(src)) {
2049
+ src = src.replace(/integrations:\s*\[\s*\]/, `integrations: [snapfail(${ENV_CALL})]`);
2050
+ if (!src.includes(`snapfail(${ENV_CALL})`)) {
2051
+ src = src.replace(/integrations:\s*\[(\s*)/, (_2, ws) => `integrations: [snapfail(${ENV_CALL}),${ws}`);
2052
+ }
2053
+ } else {
2054
+ src = src.replace(/defineConfig\(\{/, `defineConfig({
2055
+ integrations: [snapfail(${ENV_CALL})],`);
2056
+ }
2057
+ writeFileSync3(configPath, src, "utf-8");
2058
+ }
2059
+ function injectNextProvider(cwd) {
2060
+ const providerSrc = `"use client";
2061
+ import { useEffect } from "react";
2062
+ import { initSnapfail } from "@snapfail/browser";
2063
+
2064
+ export function SnapfailProvider() {
2065
+ useEffect(() => {
2066
+ initSnapfail({
2067
+ projectKey: process.env.NEXT_PUBLIC_SNAPFAIL_PROJECT_KEY ?? "",
2068
+ environment: process.env.NODE_ENV === "production" ? "prod" : "dev",
306
2069
  });
2070
+ }, []);
2071
+ return null;
2072
+ }
2073
+ `;
2074
+ const dirs = ["app", "src/app", "src"];
2075
+ const providerDir = dirs.find((d) => existsSync3(resolve2(cwd, d))) ?? "app";
2076
+ writeFileSync3(resolve2(cwd, providerDir, "snapfail-provider.tsx"), providerSrc, "utf-8");
2077
+ }
2078
+ async function fetchProjects(token) {
2079
+ const res = await fetch(`${DEFAULT_ENDPOINT}/api/cli/projects`, {
2080
+ headers: { Authorization: `Bearer ${token}` }
307
2081
  });
2082
+ if (!res.ok)
2083
+ throw new Error(`Failed to fetch projects (${res.status})`);
2084
+ return (await res.json()).projects;
308
2085
  }
309
- function htmlString(title, body, ok) {
310
- const accentColor = ok ? "#c4ffb8" : "#ff4444";
311
- return `<!DOCTYPE html>
312
- <html lang="en">
313
- <head>
314
- <meta charset="utf-8">
315
- <title>${esc(title)} — SnapFail</title>
316
- <style>
317
- *{box-sizing:border-box}
318
- body{margin:0;min-height:100vh;display:flex;align-items:center;justify-content:center;
319
- background:#0a0a0a;color:#e0e0e0;font-family:ui-monospace,monospace;text-align:center;padding:2rem}
320
- h1{font-size:2.5rem;margin-bottom:1rem;color:${accentColor}}
321
- p{color:#888;margin:.5rem 0}
322
- strong{color:${accentColor}}
323
- code{background:#1a1a1a;padding:.1em .4em;border-radius:4px}
324
- </style>
325
- </head>
326
- <body>
327
- <div>
328
- <h1>${esc(title)}</h1>
329
- ${body}
330
- </div>
331
- </body>
332
- </html>`;
333
- }
334
- function esc(s) {
335
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
336
- }
337
- function getInstallCommand(cwd, version) {
338
- if (existsSync(join(cwd, "bun.lock")) || existsSync(join(cwd, "bun.lockb"))) {
339
- return `bun add --exact @snapfail/sdk@${version}`;
340
- }
341
- if (existsSync(join(cwd, "pnpm-lock.yaml")) || existsSync(join(cwd, "pnpm-workspace.yaml"))) {
342
- return `pnpm add --save-exact @snapfail/sdk@${version}`;
343
- }
344
- if (existsSync(join(cwd, "yarn.lock"))) {
345
- return `yarn add --exact @snapfail/sdk@${version}`;
346
- }
347
- return `npm install --save-exact @snapfail/sdk@${version}`;
348
- }
349
- async function cmdInit(cwd) {
350
- console.log("");
351
- console.log(` ${bold("SnapFail CLI")} ${dim(`v${VERSION}`)}`);
352
- console.log(` ${dim("─".repeat(38))}`);
353
- console.log("");
354
- const project = detectProject(cwd);
355
- if (project.kind === "unknown") {
356
- fail("No astro.config, vite.config, or index.html found.");
357
- fail("Run `snapfail init` from your project's root directory.");
358
- process.exit(1);
2086
+ async function createRemoteProject(token, name) {
2087
+ const res = await fetch(`${DEFAULT_ENDPOINT}/api/cli/projects`, {
2088
+ method: "POST",
2089
+ headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
2090
+ body: JSON.stringify({ name })
2091
+ });
2092
+ if (!res.ok)
2093
+ throw new Error(`Failed to create project (${res.status})`);
2094
+ return (await res.json()).project;
2095
+ }
2096
+ async function runInit(cwd = process.cwd()) {
2097
+ intro("snapfail init");
2098
+ const framework = detectFramework(cwd);
2099
+ if (framework) {
2100
+ log.success(`Detected framework: ${framework}`);
2101
+ } else {
2102
+ log.warn("Could not detect framework automatically.");
359
2103
  }
360
- log(`Detected: ${bold(project.kind)} project`);
361
- if (project.configFile)
362
- log(`Config: ${dim(basename(project.configFile))}`);
363
- const auth = await runBrowserAuth();
364
- if (!auth)
2104
+ let session = readSession();
2105
+ if (session && session.endpoint !== DEFAULT_ENDPOINT)
2106
+ session = null;
2107
+ if (session) {
2108
+ log.success(`Logged in as ${session.email}`);
2109
+ } else {
2110
+ log.info("You need to sign in to snapfail.");
2111
+ session = await ensureSession(DEFAULT_ENDPOINT);
2112
+ log.success(`Logged in as ${session.email}`);
2113
+ }
2114
+ const spinner2 = spinner();
2115
+ spinner2.start("Fetching your projects\u2026");
2116
+ let projects;
2117
+ try {
2118
+ projects = await fetchProjects(session.token);
2119
+ spinner2.stop("Projects loaded.");
2120
+ } catch (err) {
2121
+ spinner2.stop("Failed to fetch projects.");
2122
+ log.error(err.message);
365
2123
  process.exit(1);
366
- const { apiKey, projectName } = auth;
367
- console.log("");
368
- success(`Authenticated! Project: ${bold(green(projectName))}`);
369
- success(`API Key: ${bold(green(apiKey))}`);
370
- console.log("");
371
- if (project.kind !== "html") {
372
- log("Writing API key to .env…");
373
- writeEnvFile(cwd, apiKey);
374
- console.log("");
375
2124
  }
376
- const enableReplay = await askYesNo("Do you want to enable session recording/replays? (captures full rrweb replay videos)", false);
377
- console.log("");
378
- log("Injecting SnapFail SDK into your project…");
379
- let ok = false;
380
- if (project.kind === "astro" && project.configFile) {
381
- ok = injectAstroConfig(project.configFile, projectName, enableReplay);
382
- } else if (project.kind === "vite" && project.configFile) {
383
- ok = injectViteConfig(project.configFile, projectName, enableReplay);
384
- } else if (project.htmlFile) {
385
- ok = injectHtmlFile(project.htmlFile, apiKey, enableReplay);
386
- }
387
- if (!ok) {
388
- printManualInstructions(project.kind, apiKey, projectName, enableReplay);
2125
+ let selectedKey = "";
2126
+ let selectedName = "";
2127
+ const CREATE_NEW = "__create_new__";
2128
+ if (projects.length === 0) {
2129
+ log.info("You don't have any projects yet.");
2130
+ const suggested = suggestProjectName(cwd);
2131
+ const name = await text({
2132
+ message: "Project name",
2133
+ placeholder: suggested || "my-app",
2134
+ defaultValue: suggested,
2135
+ validate: (v) => (v ?? "").trim() ? undefined : "Project name is required."
2136
+ });
2137
+ if (isCancel(name)) {
2138
+ cancel("Cancelled.");
2139
+ process.exit(0);
2140
+ }
2141
+ spinner2.start("Creating project\u2026");
2142
+ const created = await createRemoteProject(session.token, name.trim());
2143
+ spinner2.stop(`Project created.`);
2144
+ selectedKey = created.key;
2145
+ selectedName = created.name;
389
2146
  } else {
390
- console.log("");
391
- success("SnapFail is now integrated into your project!");
392
- console.log("");
393
- if (project.kind !== "html") {
394
- const installCmd = getInstallCommand(cwd, VERSION);
395
- const installNow = await askYesNo("Would you like to automatically install @snapfail/sdk dependency now?", true);
396
- console.log("");
397
- if (installNow) {
398
- log(`Running: ${bold(installCmd)}…`);
399
- try {
400
- execSync(installCmd, { stdio: "inherit", cwd });
401
- console.log("");
402
- success("SDK package installed successfully!");
403
- } catch (err) {
404
- console.log("");
405
- warn(`Could not run install command: ${err.message}`);
406
- warn(`Please run manually: ${installCmd}`);
407
- }
408
- } else {
409
- console.log(` ${dim("Make sure the SDK package is installed:")}`);
410
- console.log(` ${bold(" " + installCmd)}`);
2147
+ const choices = [
2148
+ ...projects.map((p2) => ({ value: p2.key, label: p2.name, hint: p2.key })),
2149
+ { value: CREATE_NEW, label: "Create a new project" }
2150
+ ];
2151
+ const pick = await select({
2152
+ message: "Select a project",
2153
+ options: choices
2154
+ });
2155
+ if (isCancel(pick)) {
2156
+ cancel("Cancelled.");
2157
+ process.exit(0);
2158
+ }
2159
+ if (pick === CREATE_NEW) {
2160
+ const suggested = suggestProjectName(cwd);
2161
+ const name = await text({
2162
+ message: "Project name",
2163
+ placeholder: suggested || "my-app",
2164
+ defaultValue: suggested,
2165
+ validate: (v) => (v ?? "").trim() ? undefined : "Project name is required."
2166
+ });
2167
+ if (isCancel(name)) {
2168
+ cancel("Cancelled.");
2169
+ process.exit(0);
411
2170
  }
412
- console.log("");
413
- console.log(` ${dim("Add .env to .gitignore to keep your API key private:")}`);
414
- console.log(` ${bold(' echo ".env" >> .gitignore')}`);
2171
+ spinner2.start("Creating project\u2026");
2172
+ const created = await createRemoteProject(session.token, name.trim());
2173
+ spinner2.stop("Project created.");
2174
+ selectedKey = created.key;
2175
+ selectedName = created.name;
2176
+ } else {
2177
+ const picked = projects.find((proj) => proj.key === pick);
2178
+ selectedKey = picked.key;
2179
+ selectedName = picked.name;
415
2180
  }
416
- console.log("");
417
2181
  }
2182
+ writeProjectKey(selectedKey, cwd);
2183
+ if (framework === "next")
2184
+ writeProjectKey(selectedKey, cwd, "NEXT_PUBLIC_SNAPFAIL_PROJECT_KEY");
2185
+ addToGitignore(cwd, ".env");
2186
+ log.success("SNAPFAIL_PROJECT_KEY written to .env");
2187
+ const skillDir = join2(cwd, ".agents", "skills", "snapfail");
2188
+ mkdirSync2(skillDir, { recursive: true });
2189
+ writeFileSync3(join2(skillDir, "SKILL.md"), generateSkillMd({ projectKey: selectedKey, endpoint: DEFAULT_ENDPOINT }, framework), "utf-8");
2190
+ log.success("SKILL.md created at .agents/skills/snapfail/SKILL.md");
2191
+ const pm = detectPackageManager(cwd);
2192
+ if (framework === "astro") {
2193
+ const cfg = findConfigFile(cwd, ["astro.config.ts", "astro.config.mjs", "astro.config.js"]);
2194
+ if (cfg) {
2195
+ spinner2.start(`Installing @snapfail/astro\u2026`);
2196
+ try {
2197
+ installPackage(pm, "@snapfail/astro", cwd);
2198
+ spinner2.stop("Adapter installed.");
2199
+ } catch {
2200
+ spinner2.stop("Skipping install (not yet on npm).");
2201
+ }
2202
+ injectAstroAdapter(resolve2(cwd, cfg));
2203
+ log.success(`Injected snapfail into ${cfg}`);
2204
+ }
2205
+ } else if (framework === "next") {
2206
+ spinner2.start("Installing @snapfail/next\u2026");
2207
+ try {
2208
+ installPackage(pm, "@snapfail/next @snapfail/browser", cwd);
2209
+ spinner2.stop("Adapter installed.");
2210
+ } catch {
2211
+ spinner2.stop("Skipping install (not yet on npm).");
2212
+ }
2213
+ injectNextProvider(cwd);
2214
+ log.success("Created snapfail-provider.tsx");
2215
+ log.warn("Add <SnapfailProvider /> to your root layout.");
2216
+ } else if (framework === "vite") {
2217
+ const cfg = findConfigFile(cwd, ["vite.config.ts", "vite.config.mjs", "vite.config.js"]);
2218
+ spinner2.start("Installing @snapfail/vite\u2026");
2219
+ try {
2220
+ installPackage(pm, "@snapfail/vite", cwd);
2221
+ spinner2.stop("Adapter installed.");
2222
+ } catch {
2223
+ spinner2.stop("Skipping install (not yet on npm).");
2224
+ }
2225
+ if (cfg) {
2226
+ injectViteAdapter(resolve2(cwd, cfg));
2227
+ log.success(`Injected snapfail into ${cfg}`);
2228
+ }
2229
+ } else {
2230
+ log.warn("No framework detected \u2014 install an adapter manually once published:");
2231
+ log.info(" @snapfail/vite | @snapfail/astro | @snapfail/next");
2232
+ }
2233
+ outro(`snapfail is ready \xB7 project: ${selectedName}`);
418
2234
  }
419
- function printHelp() {
420
- console.log(`
421
- ${bold("snapfail")} ${dim(`v${VERSION}`)} — Error-tracking CLI for SnapFail Protocol
422
-
423
- ${bold("Usage:")}
424
- snapfail init [path] Authenticate & inject the SDK into your project
425
- snapfail --version Print version
426
- snapfail --help Show this help
427
-
428
- ${bold("Examples:")}
429
- snapfail init ${dim("# run from your project root")}
430
- snapfail init ./my-app ${dim("# run against a specific directory")}
431
2235
 
432
- ${bold("Environment:")}
433
- SNAPFAIL_APP_URL ${dim("Override dashboard URL (default: https://app.snapfail.com)")}
2236
+ // src/commands/explain.ts
2237
+ async function fetchDiagnosis(config, id, force) {
2238
+ const url = new URL(`${config.endpoint}/api/diagnose/${id}`);
2239
+ if (force)
2240
+ url.searchParams.set("force", "1");
2241
+ let res;
2242
+ try {
2243
+ res = await fetch(url.toString(), {
2244
+ method: "POST",
2245
+ headers: { "X-Project-Key": config.projectKey }
2246
+ });
2247
+ } catch (err) {
2248
+ throw new Error(`Network error: ${err instanceof Error ? err.message : String(err)}`);
2249
+ }
2250
+ if (res.status === 401)
2251
+ throw new Error("Unauthorized: check your project key.");
2252
+ if (res.status === 404)
2253
+ return null;
2254
+ if (res.status === 422)
2255
+ throw new Error("No samples available to diagnose this incident.");
2256
+ if (!res.ok) {
2257
+ const body = await res.text().catch(() => "");
2258
+ throw new Error(`API error ${res.status}: ${body}`);
2259
+ }
2260
+ return res.json();
2261
+ }
2262
+ function confidenceColor(c3) {
2263
+ if (c3 === "high")
2264
+ return green(c3);
2265
+ if (c3 === "medium")
2266
+ return yellow(c3);
2267
+ return red(c3);
2268
+ }
2269
+ function formatDiagnosis(d) {
2270
+ const lines = [];
2271
+ lines.push(bold("Root cause"));
2272
+ lines.push(` ${d.rootCause}`);
2273
+ lines.push("");
2274
+ lines.push(bold("In plain language"));
2275
+ lines.push(` ${d.plainSummary}`);
2276
+ if (d.fixPrompt) {
2277
+ lines.push("");
2278
+ lines.push(bold("Fix prompt") + dim(" (paste into your AI assistant)"));
2279
+ lines.push(` ${d.fixPrompt}`);
2280
+ }
2281
+ lines.push("");
2282
+ lines.push(dim("Confidence:") + " " + confidenceColor(d.confidence) + dim(" \xB7 analyzed ") + d.analyzedSampleIds.length + dim(" samples \xB7 model: ") + dim(d.model));
2283
+ return lines.join(`
434
2284
  `);
435
2285
  }
436
- var [, , cmd, ...rest] = process.argv;
437
- if (!cmd || cmd === "--help" || cmd === "-h") {
438
- printHelp();
439
- } else if (cmd === "--version" || cmd === "-v") {
440
- console.log(VERSION);
441
- } else if (cmd === "init") {
442
- const target = rest[0] ? resolve(rest[0]) : process.cwd();
443
- cmdInit(target).catch((err) => {
444
- fail(err instanceof Error ? err.message : String(err));
2286
+ async function runExplain(opts) {
2287
+ const config = loadConfig();
2288
+ const diagnosis = await fetchDiagnosis(config, opts.id, opts.force ?? false);
2289
+ if (!diagnosis) {
2290
+ console.error(`Incident ${opts.id} not found.`);
445
2291
  process.exit(1);
446
- });
447
- } else {
448
- fail(`Unknown command: ${cmd}`);
449
- printHelp();
450
- process.exit(1);
2292
+ }
2293
+ if (opts.json) {
2294
+ process.stdout.write(JSON.stringify(diagnosis, null, 2) + `
2295
+ `);
2296
+ return;
2297
+ }
2298
+ console.log(formatDiagnosis(diagnosis));
2299
+ }
2300
+
2301
+ // src/index.ts
2302
+ function parseArgs(argv) {
2303
+ const [, , rawCommand = "incidents", ...rest] = argv;
2304
+ const command = rawCommand;
2305
+ const args = [];
2306
+ const flags = {};
2307
+ for (const token of rest) {
2308
+ if (token.startsWith("--")) {
2309
+ const eq = token.indexOf("=");
2310
+ if (eq !== -1) {
2311
+ flags[token.slice(2, eq)] = token.slice(eq + 1);
2312
+ } else {
2313
+ flags[token.slice(2)] = true;
2314
+ }
2315
+ } else {
2316
+ args.push(token);
2317
+ }
2318
+ }
2319
+ return { command, args, flags };
2320
+ }
2321
+ var VERSION = "0.0.16";
2322
+ async function main() {
2323
+ const { command, args, flags } = parseArgs(process.argv);
2324
+ const json = flags["json"] === true;
2325
+ if (command === "--version" || command === "-v" || flags["version"] === true) {
2326
+ console.log(VERSION);
2327
+ return;
2328
+ }
2329
+ try {
2330
+ if (command === "init") {
2331
+ await runInit();
2332
+ return;
2333
+ }
2334
+ if (command === "incidents") {
2335
+ await runIncidents({
2336
+ json,
2337
+ status: typeof flags["status"] === "string" ? flags["status"] : undefined,
2338
+ limit: typeof flags["limit"] === "string" ? parseInt(flags["limit"]) : undefined,
2339
+ offset: typeof flags["offset"] === "string" ? parseInt(flags["offset"]) : undefined
2340
+ });
2341
+ return;
2342
+ }
2343
+ if (command === "incident") {
2344
+ const id = args[0];
2345
+ if (!id) {
2346
+ console.error("Usage: snapfail incident <id> [--sample <n>] [--json]");
2347
+ process.exit(1);
2348
+ }
2349
+ const sampleFlag = flags["sample"];
2350
+ const sample = typeof sampleFlag === "string" ? parseInt(sampleFlag) : undefined;
2351
+ await runIncident({ id, sample, json });
2352
+ return;
2353
+ }
2354
+ if (command === "explain") {
2355
+ const id = args[0];
2356
+ if (!id) {
2357
+ console.error("Usage: snapfail explain <id> [--force] [--json]");
2358
+ process.exit(1);
2359
+ }
2360
+ await runExplain({ id, force: flags["force"] === true, json });
2361
+ return;
2362
+ }
2363
+ console.error(`Unknown command: ${command}`);
2364
+ console.error(`snapfail v${VERSION} \u2014 Usage: snapfail [incidents|incident|init|explain] [--version]`);
2365
+ process.exit(1);
2366
+ } catch (err) {
2367
+ const message = err instanceof Error ? err.message : String(err);
2368
+ console.error(`Error: ${message}`);
2369
+ process.exit(1);
2370
+ }
2371
+ }
2372
+ if (import.meta.main) {
2373
+ main();
451
2374
  }