brakit 0.8.2 → 0.8.4

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/api.js CHANGED
@@ -299,6 +299,7 @@ var FindingStore = class {
299
299
 
300
300
  // src/detect/project.ts
301
301
  import { readFile as readFile2 } from "fs/promises";
302
+ import { existsSync as existsSync4 } from "fs";
302
303
  import { join } from "path";
303
304
  var FRAMEWORKS = [
304
305
  { name: "nextjs", dep: "next", devCmd: "next dev", bin: "next", defaultPort: 3e3, devArgs: ["dev", "--port"] },
@@ -312,19 +313,11 @@ async function detectProject(rootDir) {
312
313
  const raw = await readFile2(pkgPath, "utf-8");
313
314
  const pkg = JSON.parse(raw);
314
315
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
315
- let framework = "unknown";
316
- let devCommand = "";
317
- let devBin = "";
318
- let defaultPort = 3e3;
319
- for (const f of FRAMEWORKS) {
320
- if (allDeps[f.dep]) {
321
- framework = f.name;
322
- devCommand = f.devCmd;
323
- devBin = join(rootDir, "node_modules", ".bin", f.bin);
324
- defaultPort = f.defaultPort;
325
- break;
326
- }
327
- }
316
+ const framework = detectFrameworkFromDeps(allDeps);
317
+ const matched = FRAMEWORKS.find((f) => f.name === framework);
318
+ const devCommand = matched?.devCmd ?? "";
319
+ const devBin = matched ? join(rootDir, "node_modules", ".bin", matched.bin) : "";
320
+ const defaultPort = matched?.defaultPort ?? 3e3;
328
321
  const packageManager = await detectPackageManager(rootDir);
329
322
  return { framework, devCommand, devBin, defaultPort, packageManager };
330
323
  }
@@ -336,6 +329,12 @@ async function detectPackageManager(rootDir) {
336
329
  if (await fileExists(join(rootDir, "package-lock.json"))) return "npm";
337
330
  return "unknown";
338
331
  }
332
+ function detectFrameworkFromDeps(allDeps) {
333
+ for (const f of FRAMEWORKS) {
334
+ if (allDeps[f.dep]) return f.name;
335
+ }
336
+ return "unknown";
337
+ }
339
338
 
340
339
  // src/instrument/adapter-registry.ts
341
340
  var AdapterRegistry = class {
@@ -2063,7 +2062,7 @@ var AnalysisEngine = class {
2063
2062
  };
2064
2063
 
2065
2064
  // src/index.ts
2066
- var VERSION = "0.8.2";
2065
+ var VERSION = "0.8.4";
2067
2066
  export {
2068
2067
  AdapterRegistry,
2069
2068
  AnalysisEngine,
@@ -82,7 +82,7 @@ var init_mcp = __esm({
82
82
  "src/constants/mcp.ts"() {
83
83
  "use strict";
84
84
  MCP_SERVER_NAME = "brakit";
85
- MCP_SERVER_VERSION = "0.8.2";
85
+ MCP_SERVER_VERSION = "0.8.4";
86
86
  INITIAL_DISCOVERY_TIMEOUT_MS = 5e3;
87
87
  LAZY_DISCOVERY_TIMEOUT_MS = 2e3;
88
88
  CLIENT_FETCH_TIMEOUT_MS = 1e4;
@@ -118,6 +118,13 @@ var init_severity = __esm({
118
118
  }
119
119
  });
120
120
 
121
+ // src/constants/telemetry.ts
122
+ var init_telemetry = __esm({
123
+ "src/constants/telemetry.ts"() {
124
+ "use strict";
125
+ }
126
+ });
127
+
121
128
  // src/constants/index.ts
122
129
  var init_constants = __esm({
123
130
  "src/constants/index.ts"() {
@@ -132,6 +139,7 @@ var init_constants = __esm({
132
139
  init_mcp();
133
140
  init_encoding();
134
141
  init_severity();
142
+ init_telemetry();
135
143
  }
136
144
  });
137
145
 
@@ -244,10 +252,10 @@ var init_client = __esm({
244
252
  });
245
253
 
246
254
  // src/mcp/discovery.ts
247
- import { readFileSync as readFileSync3, existsSync as existsSync4, readdirSync, statSync } from "fs";
255
+ import { readFileSync as readFileSync3, existsSync as existsSync5, readdirSync, statSync } from "fs";
248
256
  import { resolve as resolve5, dirname } from "path";
249
257
  function readPort(portPath) {
250
- if (!existsSync4(portPath)) return null;
258
+ if (!existsSync5(portPath)) return null;
251
259
  const raw = readFileSync3(portPath, "utf-8").trim();
252
260
  const port = parseInt(raw, 10);
253
261
  return isNaN(port) || port < 1 || port > 65535 ? null : port;
@@ -395,27 +403,25 @@ async function enrichRequestDetail(client, opts) {
395
403
  if (opts.requestId) {
396
404
  const data = await client.getRequests({ search: opts.requestId, limit: 1 });
397
405
  if (data.requests.length > 0) {
398
- return buildRequestDetail(client, data.requests[0].id);
406
+ return buildRequestDetail(client, data.requests[0]);
399
407
  }
400
408
  } else if (opts.endpoint) {
401
409
  const { method, path } = parseEndpointKey(opts.endpoint);
402
410
  const data = await client.getRequests({ method, search: path, limit: 1 });
403
411
  if (data.requests.length > 0) {
404
- return buildRequestDetail(client, data.requests[0].id);
412
+ return buildRequestDetail(client, data.requests[0]);
405
413
  }
406
414
  }
407
415
  return null;
408
416
  }
409
- async function buildRequestDetail(client, requestId) {
410
- const [reqData, activity, queries, fetches] = await Promise.all([
411
- client.getRequests({ search: requestId, limit: 1 }),
412
- client.getActivity(requestId),
413
- client.getQueries(requestId),
414
- client.getFetches(requestId)
417
+ async function buildRequestDetail(client, req) {
418
+ const [activity, queries, fetches] = await Promise.all([
419
+ client.getActivity(req.id),
420
+ client.getQueries(req.id),
421
+ client.getFetches(req.id)
415
422
  ]);
416
- const req = reqData.requests[0];
417
423
  return {
418
- id: requestId,
424
+ id: req.id,
419
425
  method: req.method,
420
426
  url: req.url,
421
427
  statusCode: req.statusCode,
@@ -1021,6 +1027,7 @@ init_finding_id();
1021
1027
 
1022
1028
  // src/detect/project.ts
1023
1029
  import { readFile as readFile2 } from "fs/promises";
1030
+ import { existsSync as existsSync4 } from "fs";
1024
1031
  import { join } from "path";
1025
1032
  var FRAMEWORKS = [
1026
1033
  { name: "nextjs", dep: "next", devCmd: "next dev", bin: "next", defaultPort: 3e3, devArgs: ["dev", "--port"] },
@@ -1034,19 +1041,11 @@ async function detectProject(rootDir) {
1034
1041
  const raw = await readFile2(pkgPath, "utf-8");
1035
1042
  const pkg = JSON.parse(raw);
1036
1043
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
1037
- let framework = "unknown";
1038
- let devCommand = "";
1039
- let devBin = "";
1040
- let defaultPort = 3e3;
1041
- for (const f of FRAMEWORKS) {
1042
- if (allDeps[f.dep]) {
1043
- framework = f.name;
1044
- devCommand = f.devCmd;
1045
- devBin = join(rootDir, "node_modules", ".bin", f.bin);
1046
- defaultPort = f.defaultPort;
1047
- break;
1048
- }
1049
- }
1044
+ const framework = detectFrameworkFromDeps(allDeps);
1045
+ const matched = FRAMEWORKS.find((f) => f.name === framework);
1046
+ const devCommand = matched?.devCmd ?? "";
1047
+ const devBin = matched ? join(rootDir, "node_modules", ".bin", matched.bin) : "";
1048
+ const defaultPort = matched?.defaultPort ?? 3e3;
1050
1049
  const packageManager = await detectPackageManager(rootDir);
1051
1050
  return { framework, devCommand, devBin, defaultPort, packageManager };
1052
1051
  }
@@ -1058,6 +1057,12 @@ async function detectPackageManager(rootDir) {
1058
1057
  if (await fileExists(join(rootDir, "package-lock.json"))) return "npm";
1059
1058
  return "unknown";
1060
1059
  }
1060
+ function detectFrameworkFromDeps(allDeps) {
1061
+ for (const f of FRAMEWORKS) {
1062
+ if (allDeps[f.dep]) return f.name;
1063
+ }
1064
+ return "unknown";
1065
+ }
1061
1066
 
1062
1067
  // src/analysis/rules/patterns.ts
1063
1068
  var SECRET_KEYS = /^(password|passwd|secret|api_key|apiKey|api_secret|apiSecret|private_key|privateKey|client_secret|clientSecret)$/;
@@ -1595,11 +1600,58 @@ init_endpoint();
1595
1600
  init_thresholds();
1596
1601
 
1597
1602
  // src/index.ts
1598
- var VERSION = "0.8.2";
1603
+ var VERSION = "0.8.4";
1599
1604
 
1600
1605
  // src/cli/commands/install.ts
1606
+ init_constants();
1607
+
1608
+ // src/cli/templates.ts
1601
1609
  var IMPORT_LINE = `import "brakit";`;
1602
1610
  var IMPORT_MARKER = "brakit";
1611
+ var CREATED_FILES = [
1612
+ "src/instrumentation.ts",
1613
+ "instrumentation.ts",
1614
+ "server/plugins/brakit.ts"
1615
+ ];
1616
+ var ENTRY_CANDIDATES = [
1617
+ "src/index.ts",
1618
+ "src/server.ts",
1619
+ "src/app.ts",
1620
+ "src/index.js",
1621
+ "src/server.js",
1622
+ "src/app.js",
1623
+ "server.ts",
1624
+ "app.ts",
1625
+ "index.ts",
1626
+ "server.js",
1627
+ "app.js",
1628
+ "index.js"
1629
+ ];
1630
+ var BRAKIT_TEMPLATES = {
1631
+ /** Next.js instrumentation.ts — standalone file created by install */
1632
+ nextjs: [
1633
+ `export async function register() {`,
1634
+ ` if (process.env.NODE_ENV !== "production") {`,
1635
+ ` try { await import("brakit"); } catch {}`,
1636
+ ` }`,
1637
+ `}`
1638
+ ].join("\n"),
1639
+ /** Nuxt server/plugins/brakit.ts — standalone file created by install */
1640
+ nuxt: `import "brakit";`
1641
+ };
1642
+ var ALL_TEMPLATES = Object.values(BRAKIT_TEMPLATES);
1643
+ function normalize(content) {
1644
+ return content.split("\n").map((line) => line.trim()).filter((line) => line.length > 0).join("\n");
1645
+ }
1646
+ function isExactBrakitTemplate(fileContent) {
1647
+ const normalizedFile = normalize(fileContent);
1648
+ if (!normalizedFile) return false;
1649
+ return ALL_TEMPLATES.some(
1650
+ (template) => normalize(template) === normalizedFile
1651
+ );
1652
+ }
1653
+
1654
+ // src/cli/commands/install.ts
1603
1655
  var install_default = defineCommand({
1604
1656
  meta: {
1605
1657
  name: "brakit install",
@@ -1664,6 +1716,7 @@ var install_default = defineCommand({
1664
1716
  } else {
1665
1717
  printManualInstructions(project.framework);
1666
1718
  }
1719
+ await ensureGitignoreEntry(rootDir, METRICS_DIR);
1667
1720
  const mcpResult = await setupMcp(rootDir);
1668
1721
  if (mcpResult === "created" || mcpResult === "updated") {
1669
1722
  console.log(pc.green(" \u2713 Configured MCP for Claude Code / Cursor"));
@@ -1692,6 +1745,7 @@ async function installPackage(rootDir, pm) {
1692
1745
  execSync(cmd, { cwd: rootDir, stdio: "pipe" });
1693
1746
  } catch {
1694
1747
  console.warn(pc.yellow(` \u26A0 Failed to run "${cmd}". Install brakit manually.`));
1748
+ return false;
1695
1749
  }
1696
1750
  return true;
1697
1751
  }
@@ -1718,14 +1772,7 @@ async function setupNextjs(rootDir) {
1718
1772
  }
1719
1773
  return { action: "manual", file: relPath };
1720
1774
  }
1721
- const content = [
1722
- `export async function register() {`,
1723
- ` if (process.env.NODE_ENV !== "production") {`,
1724
- ` try { await import("brakit"); } catch {}`,
1725
- ` }`,
1726
- `}`,
1727
- ``
1728
- ].join("\n");
1775
+ const content = BRAKIT_TEMPLATES.nextjs + "\n";
1729
1776
  await writeFile3(absPath, content);
1730
1777
  return { action: "created", file: relPath, content };
1731
1778
  }
@@ -1739,8 +1786,7 @@ async function setupNuxt(rootDir) {
1739
1786
  }
1740
1787
  return { action: "manual", file: relPath };
1741
1788
  }
1742
- const content = `${IMPORT_LINE}
1743
- `;
1789
+ const content = BRAKIT_TEMPLATES.nuxt + "\n";
1744
1790
  const dir = join2(rootDir, "server/plugins");
1745
1791
  const { mkdirSync: mkdirSync3 } = await import("fs");
1746
1792
  mkdirSync3(dir, { recursive: true });
@@ -1761,20 +1807,6 @@ ${content}`);
1761
1807
  }
1762
1808
  return { action: "manual", file: null };
1763
1809
  }
1764
- var ENTRY_CANDIDATES = [
1765
- "src/index.ts",
1766
- "src/server.ts",
1767
- "src/app.ts",
1768
- "src/index.js",
1769
- "src/server.js",
1770
- "src/app.js",
1771
- "server.ts",
1772
- "app.ts",
1773
- "index.ts",
1774
- "server.js",
1775
- "app.js",
1776
- "index.js"
1777
- ];
1778
1810
  async function setupGeneric(rootDir) {
1779
1811
  try {
1780
1812
  const pkgRaw = await readFile3(join2(rootDir, "package.json"), "utf-8");
@@ -1806,24 +1838,24 @@ async function setupMcp(rootDir) {
1806
1838
  if (config?.mcpServers?.brakit) return "exists";
1807
1839
  config.mcpServers = { ...config.mcpServers, ...MCP_CONFIG.mcpServers };
1808
1840
  await writeFile3(mcpPath, JSON.stringify(config, null, 2) + "\n");
1809
- await ensureGitignoreMcp(rootDir);
1841
+ await ensureGitignoreEntry(rootDir, ".mcp.json");
1810
1842
  return "updated";
1811
1843
  } catch {
1812
1844
  }
1813
1845
  }
1814
1846
  await writeFile3(mcpPath, JSON.stringify(MCP_CONFIG, null, 2) + "\n");
1815
- await ensureGitignoreMcp(rootDir);
1847
+ await ensureGitignoreEntry(rootDir, ".mcp.json");
1816
1848
  return "created";
1817
1849
  }
1818
- async function ensureGitignoreMcp(rootDir) {
1850
+ async function ensureGitignoreEntry(rootDir, entry) {
1819
1851
  const gitignorePath = join2(rootDir, ".gitignore");
1820
1852
  try {
1821
1853
  if (await fileExists(gitignorePath)) {
1822
1854
  const content = await readFile3(gitignorePath, "utf-8");
1823
- if (content.split("\n").some((l) => l.trim() === ".mcp.json")) return;
1824
- await writeFile3(gitignorePath, content.trimEnd() + "\n.mcp.json\n");
1855
+ if (content.split("\n").some((l) => l.trim() === entry)) return;
1856
+ await writeFile3(gitignorePath, content.trimEnd() + "\n" + entry + "\n");
1825
1857
  } else {
1826
- await writeFile3(gitignorePath, ".mcp.json\n");
1858
+ await writeFile3(gitignorePath, entry + "\n");
1827
1859
  }
1828
1860
  } catch {
1829
1861
  }
@@ -1852,27 +1884,10 @@ import { readFile as readFile4, writeFile as writeFile4, unlink, rm } from "fs/p
1852
1884
  import { execSync as execSync2 } from "child_process";
1853
1885
  import pc2 from "picocolors";
1854
1886
  init_constants();
1855
- var IMPORT_LINE2 = `import "brakit";`;
1856
- var CREATED_FILES = [
1857
- "src/instrumentation.ts",
1858
- "instrumentation.ts",
1859
- "server/plugins/brakit.ts"
1860
- ];
1861
1887
  var PREPENDED_FILES = [
1862
1888
  "app/entry.server.tsx",
1863
1889
  "app/entry.server.ts",
1864
- "src/index.ts",
1865
- "src/server.ts",
1866
- "src/app.ts",
1867
- "src/index.js",
1868
- "src/server.js",
1869
- "src/app.js",
1870
- "server.ts",
1871
- "app.ts",
1872
- "index.ts",
1873
- "server.js",
1874
- "app.js",
1875
- "index.js"
1890
+ ...ENTRY_CANDIDATES
1876
1891
  ];
1877
1892
  var uninstall_default = defineCommand2({
1878
1893
  meta: {
@@ -1904,14 +1919,22 @@ var uninstall_default = defineCommand2({
1904
1919
  if (!await fileExists(absPath)) continue;
1905
1920
  const content = await readFile4(absPath, "utf-8");
1906
1921
  if (!content.includes("brakit")) continue;
1907
- const lines = content.split("\n").filter((l) => l.trim().length > 0);
1908
- const allBrakit = lines.every((l) => l.includes("brakit") || l.includes("register") || l.includes("import") || l.includes("export") || l.includes("try") || l.includes("catch") || l.includes("process.env") || l.includes("{") || l.includes("}"));
1909
- if (allBrakit) {
1922
+ if (isExactBrakitTemplate(content)) {
1910
1923
  await unlink(absPath);
1911
1924
  console.log(pc2.green(` \u2713 Removed ${relPath}`));
1912
1925
  removed = true;
1913
1926
  break;
1914
1927
  }
1928
+ const lines = content.split("\n");
1929
+ const cleaned = lines.filter(
1930
+ (line) => !line.includes('import("brakit")') && !line.includes('import "brakit"')
1931
+ );
1932
+ if (cleaned.length < lines.length) {
1933
+ await writeFile4(absPath, cleaned.join("\n"));
1934
+ console.log(pc2.green(` \u2713 Removed brakit lines from ${relPath}`));
1935
+ removed = true;
1936
+ break;
1937
+ }
1915
1938
  }
1916
1939
  if (!removed) {
1917
1940
  const candidates = [...PREPENDED_FILES];
@@ -1925,8 +1948,8 @@ var uninstall_default = defineCommand2({
1925
1948
  const absPath = join3(rootDir, relPath);
1926
1949
  if (!await fileExists(absPath)) continue;
1927
1950
  const content = await readFile4(absPath, "utf-8");
1928
- if (!content.includes(IMPORT_LINE2)) continue;
1929
- const updated = content.split("\n").filter((line) => line.trim() !== IMPORT_LINE2.trim()).join("\n");
1951
+ if (!content.includes(IMPORT_LINE)) continue;
1952
+ const updated = content.split("\n").filter((line) => line.trim() !== IMPORT_LINE.trim()).join("\n");
1930
1953
  await writeFile4(absPath, updated);
1931
1954
  console.log(pc2.green(` \u2713 Removed brakit import from ${relPath}`));
1932
1955
  removed = true;
@@ -22,7 +22,7 @@ var DASHBOARD_API_FINDINGS = "/__brakit/api/findings";
22
22
 
23
23
  // src/constants/mcp.ts
24
24
  var MCP_SERVER_NAME = "brakit";
25
- var MCP_SERVER_VERSION = "0.8.2";
25
+ var MCP_SERVER_VERSION = "0.8.4";
26
26
  var INITIAL_DISCOVERY_TIMEOUT_MS = 5e3;
27
27
  var LAZY_DISCOVERY_TIMEOUT_MS = 2e3;
28
28
  var CLIENT_FETCH_TIMEOUT_MS = 1e4;
@@ -288,27 +288,25 @@ async function enrichRequestDetail(client, opts) {
288
288
  if (opts.requestId) {
289
289
  const data = await client.getRequests({ search: opts.requestId, limit: 1 });
290
290
  if (data.requests.length > 0) {
291
- return buildRequestDetail(client, data.requests[0].id);
291
+ return buildRequestDetail(client, data.requests[0]);
292
292
  }
293
293
  } else if (opts.endpoint) {
294
294
  const { method, path } = parseEndpointKey(opts.endpoint);
295
295
  const data = await client.getRequests({ method, search: path, limit: 1 });
296
296
  if (data.requests.length > 0) {
297
- return buildRequestDetail(client, data.requests[0].id);
297
+ return buildRequestDetail(client, data.requests[0]);
298
298
  }
299
299
  }
300
300
  return null;
301
301
  }
302
- async function buildRequestDetail(client, requestId) {
303
- const [reqData, activity, queries, fetches] = await Promise.all([
304
- client.getRequests({ search: requestId, limit: 1 }),
305
- client.getActivity(requestId),
306
- client.getQueries(requestId),
307
- client.getFetches(requestId)
302
+ async function buildRequestDetail(client, req) {
303
+ const [activity, queries, fetches] = await Promise.all([
304
+ client.getActivity(req.id),
305
+ client.getQueries(req.id),
306
+ client.getFetches(req.id)
308
307
  ]);
309
- const req = reqData.requests[0];
310
308
  return {
311
- id: requestId,
309
+ id: req.id,
312
310
  method: req.method,
313
311
  url: req.url,
314
312
  statusCode: req.statusCode,
@@ -224,6 +224,20 @@ var init_severity = __esm({
224
224
  }
225
225
  });
226
226
 
227
+ // src/constants/telemetry.ts
228
+ var POSTHOG_HOST, POSTHOG_CAPTURE_PATH, POSTHOG_REQUEST_TIMEOUT_MS, POSTHOG_SPAWN_TIMEOUT_MS, SIGNAL_EXIT_SIGINT, SIGNAL_EXIT_SIGTERM;
229
+ var init_telemetry = __esm({
230
+ "src/constants/telemetry.ts"() {
231
+ "use strict";
232
+ POSTHOG_HOST = "https://us.i.posthog.com";
233
+ POSTHOG_CAPTURE_PATH = "/i/v0/e/";
234
+ POSTHOG_REQUEST_TIMEOUT_MS = 3e3;
235
+ POSTHOG_SPAWN_TIMEOUT_MS = 5e3;
236
+ SIGNAL_EXIT_SIGINT = 130;
237
+ SIGNAL_EXIT_SIGTERM = 143;
238
+ }
239
+ });
240
+
227
241
  // src/constants/index.ts
228
242
  var init_constants = __esm({
229
243
  "src/constants/index.ts"() {
@@ -238,6 +252,7 @@ var init_constants = __esm({
238
252
  init_mcp();
239
253
  init_encoding();
240
254
  init_severity();
255
+ init_telemetry();
241
256
  }
242
257
  });
243
258
 
@@ -2526,11 +2541,33 @@ var init_finding_store = __esm({
2526
2541
 
2527
2542
  // src/detect/project.ts
2528
2543
  import { readFile as readFile2 } from "fs/promises";
2544
+ import { existsSync as existsSync4 } from "fs";
2529
2545
  import { join } from "path";
2546
+ function detectFrameworkFromDeps(allDeps) {
2547
+ for (const f of FRAMEWORKS) {
2548
+ if (allDeps[f.dep]) return f.name;
2549
+ }
2550
+ return "unknown";
2551
+ }
2552
+ function detectPackageManagerSync(rootDir) {
2553
+ if (existsSync4(join(rootDir, "bun.lockb")) || existsSync4(join(rootDir, "bun.lock"))) return "bun";
2554
+ if (existsSync4(join(rootDir, "pnpm-lock.yaml"))) return "pnpm";
2555
+ if (existsSync4(join(rootDir, "yarn.lock"))) return "yarn";
2556
+ if (existsSync4(join(rootDir, "package-lock.json"))) return "npm";
2557
+ return "unknown";
2558
+ }
2559
+ var FRAMEWORKS;
2530
2560
  var init_project = __esm({
2531
2561
  "src/detect/project.ts"() {
2532
2562
  "use strict";
2533
2563
  init_fs();
2564
+ FRAMEWORKS = [
2565
+ { name: "nextjs", dep: "next", devCmd: "next dev", bin: "next", defaultPort: 3e3, devArgs: ["dev", "--port"] },
2566
+ { name: "remix", dep: "@remix-run/dev", devCmd: "remix dev", bin: "remix", defaultPort: 3e3, devArgs: ["dev"] },
2567
+ { name: "nuxt", dep: "nuxt", devCmd: "nuxt dev", bin: "nuxt", defaultPort: 3e3, devArgs: ["dev", "--port"] },
2568
+ { name: "vite", dep: "vite", devCmd: "vite", bin: "vite", defaultPort: 5173, devArgs: ["--port"] },
2569
+ { name: "astro", dep: "astro", devCmd: "astro dev", bin: "astro", defaultPort: 4321, devArgs: ["dev", "--port"] }
2570
+ ];
2534
2571
  }
2535
2572
  });
2536
2573
 
@@ -4147,7 +4184,7 @@ var init_src = __esm({
4147
4184
  init_engine();
4148
4185
  init_insights3();
4149
4186
  init_insights2();
4150
- VERSION = "0.8.2";
4187
+ VERSION = "0.8.4";
4151
4188
  }
4152
4189
  });
4153
4190
 
@@ -6646,16 +6683,35 @@ var init_page = __esm({
6646
6683
  // src/telemetry/config.ts
6647
6684
  import { homedir } from "os";
6648
6685
  import { join as join2 } from "path";
6649
- import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
6686
+ import { existsSync as existsSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
6650
6687
  import { randomUUID as randomUUID3 } from "crypto";
6651
6688
  function readConfig() {
6652
6689
  try {
6653
- if (!existsSync4(CONFIG_PATH)) return null;
6690
+ if (!existsSync5(CONFIG_PATH)) return null;
6654
6691
  return JSON.parse(readFileSync3(CONFIG_PATH, "utf-8"));
6655
6692
  } catch {
6656
6693
  return null;
6657
6694
  }
6658
6695
  }
6696
+ function writeConfig(config) {
6697
+ try {
6698
+ if (!existsSync5(CONFIG_DIR))
6699
+ mkdirSync3(CONFIG_DIR, { recursive: true, mode: 448 });
6700
+ writeFileSync3(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", {
6701
+ mode: 384
6702
+ });
6703
+ } catch {
6704
+ }
6705
+ }
6706
+ function getOrCreateConfig() {
6707
+ const existing = readConfig();
6708
+ if (existing && typeof existing.telemetry === "boolean" && existing.anonymousId) {
6709
+ return existing;
6710
+ }
6711
+ const config = { telemetry: true, anonymousId: randomUUID3() };
6712
+ writeConfig(config);
6713
+ return config;
6714
+ }
6659
6715
  function isTelemetryEnabled() {
6660
6716
  const env = process.env.BRAKIT_TELEMETRY;
6661
6717
  if (env !== void 0) return env !== "false" && env !== "0" && env !== "off";
@@ -6672,21 +6728,126 @@ var init_config = __esm({
6672
6728
 
6673
6729
  // src/telemetry/index.ts
6674
6730
  import { platform, release, arch } from "os";
6731
+ import { spawnSync } from "child_process";
6732
+ function initSession(framework, packageManager, isCustomCommand, adapters) {
6733
+ session.startTime = Date.now();
6734
+ session.framework = framework;
6735
+ session.packageManager = packageManager;
6736
+ session.isCustomCommand = isCustomCommand;
6737
+ session.adapters = adapters;
6738
+ }
6739
+ function recordRequestCount(count) {
6740
+ session.requestCount = count;
6741
+ }
6742
+ function recordInsightTypes(types) {
6743
+ for (const t of types) session.insightTypes.add(t);
6744
+ }
6745
+ function recordRulesTriggered(rules) {
6746
+ for (const r of rules) session.rulesTriggered.add(r);
6747
+ }
6675
6748
  function recordTabViewed(tab) {
6676
- tabsViewed.add(tab);
6749
+ session.tabsViewed.add(tab);
6677
6750
  }
6678
6751
  function recordDashboardOpened() {
6679
- dashboardOpened = true;
6752
+ session.dashboardOpened = true;
6753
+ }
6754
+ function speedBucket(ms) {
6755
+ if (ms === 0) return "none";
6756
+ if (ms < 200) return "<200ms";
6757
+ if (ms < 500) return "200-500ms";
6758
+ if (ms < 1e3) return "500-1000ms";
6759
+ if (ms < 2e3) return "1000-2000ms";
6760
+ if (ms < 5e3) return "2000-5000ms";
6761
+ return ">5000ms";
6762
+ }
6763
+ function trackSession(registry) {
6764
+ if (!isTelemetryEnabled()) return;
6765
+ const isFirstSession = readConfig() === null;
6766
+ const config = getOrCreateConfig();
6767
+ const metricsStore = registry.get("metrics-store");
6768
+ const analysisEngine = registry.get("analysis-engine");
6769
+ const live = metricsStore.getLiveEndpoints();
6770
+ const insights = analysisEngine.getInsights();
6771
+ const findings = analysisEngine.getFindings();
6772
+ let totalRequests = 0;
6773
+ let totalDuration = 0;
6774
+ let slowestP95 = 0;
6775
+ for (const ep of live) {
6776
+ totalRequests += ep.summary.totalRequests;
6777
+ totalDuration += ep.summary.p95Ms * ep.summary.totalRequests;
6778
+ if (ep.summary.p95Ms > slowestP95) slowestP95 = ep.summary.p95Ms;
6779
+ }
6780
+ const payload = {
6781
+ api_key: POSTHOG_KEY,
6782
+ event: "session",
6783
+ distinct_id: config.anonymousId,
6784
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
6785
+ properties: {
6786
+ brakit_version: VERSION,
6787
+ node_version: process.version,
6788
+ os: `${platform()}-${release()}`,
6789
+ arch: arch(),
6790
+ framework: session.framework,
6791
+ package_manager: session.packageManager,
6792
+ is_custom_command: session.isCustomCommand,
6793
+ first_session: isFirstSession,
6794
+ adapters_detected: session.adapters,
6795
+ request_count: session.requestCount,
6796
+ error_count: registry.get("error-store").getAll().length,
6797
+ query_count: registry.get("query-store").getAll().length,
6798
+ fetch_count: registry.get("fetch-store").getAll().length,
6799
+ insight_count: insights.length,
6800
+ finding_count: findings.length,
6801
+ insight_types: [...session.insightTypes],
6802
+ rules_triggered: [...session.rulesTriggered],
6803
+ endpoint_count: live.length,
6804
+ avg_duration_ms: totalRequests > 0 ? Math.round(totalDuration / totalRequests) : 0,
6805
+ slowest_endpoint_bucket: speedBucket(slowestP95),
6806
+ tabs_viewed: [...session.tabsViewed],
6807
+ dashboard_opened: session.dashboardOpened,
6808
+ explain_used: session.explainUsed,
6809
+ session_duration_s: Math.round((Date.now() - session.startTime) / 1e3),
6810
+ $lib: "brakit",
6811
+ $process_person_profile: false,
6812
+ $geoip_disable: true
6813
+ }
6814
+ };
6815
+ try {
6816
+ const body = JSON.stringify(payload);
6817
+ const url = `${POSTHOG_HOST}${POSTHOG_CAPTURE_PATH}`;
6818
+ spawnSync(
6819
+ process.execPath,
6820
+ [
6821
+ "-e",
6822
+ `fetch(${JSON.stringify(url)},{method:"POST",headers:{"content-type":"application/json"},body:${JSON.stringify(body)},signal:AbortSignal.timeout(${POSTHOG_REQUEST_TIMEOUT_MS})}).catch(()=>{})`
6823
+ ],
6824
+ { timeout: POSTHOG_SPAWN_TIMEOUT_MS, stdio: "ignore" }
6825
+ );
6826
+ } catch {
6827
+ }
6680
6828
  }
6681
- var tabsViewed, dashboardOpened;
6682
- var init_telemetry = __esm({
6829
+ var POSTHOG_KEY, session;
6830
+ var init_telemetry2 = __esm({
6683
6831
  "src/telemetry/index.ts"() {
6684
6832
  "use strict";
6685
6833
  init_src();
6686
6834
  init_config();
6835
+ init_telemetry();
6687
6836
  init_config();
6688
- tabsViewed = /* @__PURE__ */ new Set();
6689
- dashboardOpened = false;
6837
+ POSTHOG_KEY = "phc_E9TwydCGnSfPLIUhNxChpeg32TSowjk31KiPhnLPP0x";
6838
+ session = {
6839
+ startTime: 0,
6840
+ framework: "",
6841
+ packageManager: "",
6842
+ isCustomCommand: false,
6843
+ adapters: [],
6844
+ requestCount: 0,
6845
+ insightTypes: /* @__PURE__ */ new Set(),
6846
+ rulesTriggered: /* @__PURE__ */ new Set(),
6847
+ tabsViewed: /* @__PURE__ */ new Set(),
6848
+ dashboardOpened: false,
6849
+ explainUsed: false
6850
+ };
6690
6851
  }
6691
6852
  });
6692
6853
 
@@ -6753,7 +6914,7 @@ var init_router = __esm({
6753
6914
  init_findings();
6754
6915
  init_sse();
6755
6916
  init_page();
6756
- init_telemetry();
6917
+ init_telemetry2();
6757
6918
  SECURITY_HEADERS = {
6758
6919
  "x-content-type-options": "nosniff",
6759
6920
  "x-frame-options": "DENY",
@@ -7155,7 +7316,7 @@ var init_metrics_store = __esm({
7155
7316
  for (const [endpoint, acc] of this.accumulators) {
7156
7317
  if (acc.durations.length === 0) continue;
7157
7318
  const n = acc.totalRequestCount;
7158
- const session = {
7319
+ const session2 = {
7159
7320
  sessionId: this.sessionId,
7160
7321
  startedAt: this.sessionStart,
7161
7322
  avgDurationMs: Math.round(acc.totalDurationSum / n),
@@ -7171,9 +7332,9 @@ var init_metrics_store = __esm({
7171
7332
  (s) => s.sessionId === this.sessionId
7172
7333
  );
7173
7334
  if (existingIdx !== -1) {
7174
- epMetrics.sessions[existingIdx] = session;
7335
+ epMetrics.sessions[existingIdx] = session2;
7175
7336
  } else {
7176
- epMetrics.sessions.push(session);
7337
+ epMetrics.sessions.push(session2);
7177
7338
  }
7178
7339
  if (epMetrics.sessions.length > METRICS_MAX_SESSIONS) {
7179
7340
  epMetrics.sessions = epMetrics.sessions.slice(-METRICS_MAX_SESSIONS);
@@ -7209,7 +7370,7 @@ var init_metrics_store = __esm({
7209
7370
  });
7210
7371
 
7211
7372
  // src/store/metrics/persistence.ts
7212
- import { readFileSync as readFileSync4, existsSync as existsSync5, unlinkSync } from "fs";
7373
+ import { readFileSync as readFileSync4, existsSync as existsSync6, unlinkSync } from "fs";
7213
7374
  import { resolve as resolve3 } from "path";
7214
7375
  var FileMetricsPersistence;
7215
7376
  var init_persistence = __esm({
@@ -7232,7 +7393,7 @@ var init_persistence = __esm({
7232
7393
  }
7233
7394
  load() {
7234
7395
  try {
7235
- if (existsSync5(this.metricsPath)) {
7396
+ if (existsSync6(this.metricsPath)) {
7236
7397
  const raw = readFileSync4(this.metricsPath, "utf-8");
7237
7398
  const parsed = JSON.parse(raw);
7238
7399
  if (parsed?.version === 1 && Array.isArray(parsed.endpoints)) {
@@ -7252,7 +7413,7 @@ var init_persistence = __esm({
7252
7413
  }
7253
7414
  remove() {
7254
7415
  try {
7255
- if (existsSync5(this.metricsPath)) {
7416
+ if (existsSync6(this.metricsPath)) {
7256
7417
  unlinkSync(this.metricsPath);
7257
7418
  }
7258
7419
  } catch {
@@ -7602,7 +7763,7 @@ var setup_exports = {};
7602
7763
  __export(setup_exports, {
7603
7764
  setup: () => setup
7604
7765
  });
7605
- import { writeFileSync as writeFileSync4, readFileSync as readFileSync5, mkdirSync as mkdirSync4, existsSync as existsSync6, unlinkSync as unlinkSync2 } from "fs";
7766
+ import { writeFileSync as writeFileSync4, readFileSync as readFileSync5, mkdirSync as mkdirSync4, existsSync as existsSync7, unlinkSync as unlinkSync2 } from "fs";
7606
7767
  import { resolve as resolve4 } from "path";
7607
7768
  function setup() {
7608
7769
  if (initialized) return;
@@ -7635,6 +7796,19 @@ function setup() {
7635
7796
  const adapterRegistry = createDefaultRegistry();
7636
7797
  adapterRegistry.patchAll(telemetryEmit);
7637
7798
  const cwd = process.cwd();
7799
+ let framework = "unknown";
7800
+ try {
7801
+ const pkg = JSON.parse(readFileSync5(resolve4(cwd, "package.json"), "utf-8"));
7802
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
7803
+ framework = detectFrameworkFromDeps(allDeps);
7804
+ } catch {
7805
+ }
7806
+ initSession(
7807
+ framework,
7808
+ detectPackageManagerSync(cwd),
7809
+ false,
7810
+ adapterRegistry.getActive().map((a) => a.name)
7811
+ );
7638
7812
  const metricsStore = new MetricsStore(new FileMetricsPersistence(cwd));
7639
7813
  metricsStore.start();
7640
7814
  registry.register("metrics-store", metricsStore);
@@ -7668,9 +7842,9 @@ function setup() {
7668
7842
  onFirstRequest(port) {
7669
7843
  setBrakitPort(port);
7670
7844
  const dir = resolve4(cwd, METRICS_DIR);
7671
- if (!existsSync6(dir)) mkdirSync4(dir, { recursive: true });
7845
+ if (!existsSync7(dir)) mkdirSync4(dir, { recursive: true });
7672
7846
  const portPath = resolve4(cwd, PORT_FILE);
7673
- if (existsSync6(portPath)) {
7847
+ if (existsSync7(portPath)) {
7674
7848
  const old = readFileSync5(portPath, "utf-8").trim();
7675
7849
  if (old && old !== String(port)) {
7676
7850
  brakitDebug(`Overwriting stale port file (was ${old}, now ${port})`);
@@ -7682,7 +7856,14 @@ function setup() {
7682
7856
  `);
7683
7857
  }
7684
7858
  });
7685
- health.setTeardown(() => {
7859
+ let teardownCalled = false;
7860
+ const runTeardown = () => {
7861
+ if (teardownCalled) return;
7862
+ teardownCalled = true;
7863
+ recordRequestCount(requestStore.getAll().length);
7864
+ recordInsightTypes(analysisEngine.getInsights().map((i) => i.type));
7865
+ recordRulesTriggered(analysisEngine.getFindings().map((f) => f.rule));
7866
+ trackSession(registry);
7686
7867
  uninstallInterceptor();
7687
7868
  terminalDispose?.();
7688
7869
  analysisEngine.stop();
@@ -7690,9 +7871,18 @@ function setup() {
7690
7871
  metricsStore.stop();
7691
7872
  try {
7692
7873
  const portPath = resolve4(cwd, PORT_FILE);
7693
- if (existsSync6(portPath)) unlinkSync2(portPath);
7874
+ if (existsSync7(portPath)) unlinkSync2(portPath);
7694
7875
  } catch {
7695
7876
  }
7877
+ };
7878
+ health.setTeardown(runTeardown);
7879
+ process.once("SIGINT", () => {
7880
+ runTeardown();
7881
+ process.exit(SIGNAL_EXIT_SIGINT);
7882
+ });
7883
+ process.once("SIGTERM", () => {
7884
+ runTeardown();
7885
+ process.exit(SIGNAL_EXIT_SIGTERM);
7696
7886
  });
7697
7887
  }
7698
7888
  var initialized;
@@ -7717,9 +7907,12 @@ var init_setup = __esm({
7717
7907
  init_terminal();
7718
7908
  init_src();
7719
7909
  init_constants();
7910
+ init_telemetry();
7720
7911
  init_health2();
7721
7912
  init_interceptor();
7722
7913
  init_log();
7914
+ init_project();
7915
+ init_telemetry2();
7723
7916
  initialized = false;
7724
7917
  }
7725
7918
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brakit",
3
- "version": "0.8.2",
3
+ "version": "0.8.4",
4
4
  "description": "See what your API is really doing. Security scanning, N+1 detection, duplicate calls, DB queries — one command, zero config.",
5
5
  "type": "module",
6
6
  "bin": {