brakit 0.8.3 → 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.3";
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.3";
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;
@@ -1019,6 +1027,7 @@ init_finding_id();
1019
1027
 
1020
1028
  // src/detect/project.ts
1021
1029
  import { readFile as readFile2 } from "fs/promises";
1030
+ import { existsSync as existsSync4 } from "fs";
1022
1031
  import { join } from "path";
1023
1032
  var FRAMEWORKS = [
1024
1033
  { name: "nextjs", dep: "next", devCmd: "next dev", bin: "next", defaultPort: 3e3, devArgs: ["dev", "--port"] },
@@ -1032,19 +1041,11 @@ async function detectProject(rootDir) {
1032
1041
  const raw = await readFile2(pkgPath, "utf-8");
1033
1042
  const pkg = JSON.parse(raw);
1034
1043
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
1035
- let framework = "unknown";
1036
- let devCommand = "";
1037
- let devBin = "";
1038
- let defaultPort = 3e3;
1039
- for (const f of FRAMEWORKS) {
1040
- if (allDeps[f.dep]) {
1041
- framework = f.name;
1042
- devCommand = f.devCmd;
1043
- devBin = join(rootDir, "node_modules", ".bin", f.bin);
1044
- defaultPort = f.defaultPort;
1045
- break;
1046
- }
1047
- }
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;
1048
1049
  const packageManager = await detectPackageManager(rootDir);
1049
1050
  return { framework, devCommand, devBin, defaultPort, packageManager };
1050
1051
  }
@@ -1056,6 +1057,12 @@ async function detectPackageManager(rootDir) {
1056
1057
  if (await fileExists(join(rootDir, "package-lock.json"))) return "npm";
1057
1058
  return "unknown";
1058
1059
  }
1060
+ function detectFrameworkFromDeps(allDeps) {
1061
+ for (const f of FRAMEWORKS) {
1062
+ if (allDeps[f.dep]) return f.name;
1063
+ }
1064
+ return "unknown";
1065
+ }
1059
1066
 
1060
1067
  // src/analysis/rules/patterns.ts
1061
1068
  var SECRET_KEYS = /^(password|passwd|secret|api_key|apiKey|api_secret|apiSecret|private_key|privateKey|client_secret|clientSecret)$/;
@@ -1593,11 +1600,58 @@ init_endpoint();
1593
1600
  init_thresholds();
1594
1601
 
1595
1602
  // src/index.ts
1596
- var VERSION = "0.8.3";
1603
+ var VERSION = "0.8.4";
1597
1604
 
1598
1605
  // src/cli/commands/install.ts
1606
+ init_constants();
1607
+
1608
+ // src/cli/templates.ts
1599
1609
  var IMPORT_LINE = `import "brakit";`;
1600
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
1601
1655
  var install_default = defineCommand({
1602
1656
  meta: {
1603
1657
  name: "brakit install",
@@ -1662,6 +1716,7 @@ var install_default = defineCommand({
1662
1716
  } else {
1663
1717
  printManualInstructions(project.framework);
1664
1718
  }
1719
+ await ensureGitignoreEntry(rootDir, METRICS_DIR);
1665
1720
  const mcpResult = await setupMcp(rootDir);
1666
1721
  if (mcpResult === "created" || mcpResult === "updated") {
1667
1722
  console.log(pc.green(" \u2713 Configured MCP for Claude Code / Cursor"));
@@ -1690,6 +1745,7 @@ async function installPackage(rootDir, pm) {
1690
1745
  execSync(cmd, { cwd: rootDir, stdio: "pipe" });
1691
1746
  } catch {
1692
1747
  console.warn(pc.yellow(` \u26A0 Failed to run "${cmd}". Install brakit manually.`));
1748
+ return false;
1693
1749
  }
1694
1750
  return true;
1695
1751
  }
@@ -1716,14 +1772,7 @@ async function setupNextjs(rootDir) {
1716
1772
  }
1717
1773
  return { action: "manual", file: relPath };
1718
1774
  }
1719
- const content = [
1720
- `export async function register() {`,
1721
- ` if (process.env.NODE_ENV !== "production") {`,
1722
- ` try { await import("brakit"); } catch {}`,
1723
- ` }`,
1724
- `}`,
1725
- ``
1726
- ].join("\n");
1775
+ const content = BRAKIT_TEMPLATES.nextjs + "\n";
1727
1776
  await writeFile3(absPath, content);
1728
1777
  return { action: "created", file: relPath, content };
1729
1778
  }
@@ -1737,8 +1786,7 @@ async function setupNuxt(rootDir) {
1737
1786
  }
1738
1787
  return { action: "manual", file: relPath };
1739
1788
  }
1740
- const content = `${IMPORT_LINE}
1741
- `;
1789
+ const content = BRAKIT_TEMPLATES.nuxt + "\n";
1742
1790
  const dir = join2(rootDir, "server/plugins");
1743
1791
  const { mkdirSync: mkdirSync3 } = await import("fs");
1744
1792
  mkdirSync3(dir, { recursive: true });
@@ -1759,20 +1807,6 @@ ${content}`);
1759
1807
  }
1760
1808
  return { action: "manual", file: null };
1761
1809
  }
1762
- var ENTRY_CANDIDATES = [
1763
- "src/index.ts",
1764
- "src/server.ts",
1765
- "src/app.ts",
1766
- "src/index.js",
1767
- "src/server.js",
1768
- "src/app.js",
1769
- "server.ts",
1770
- "app.ts",
1771
- "index.ts",
1772
- "server.js",
1773
- "app.js",
1774
- "index.js"
1775
- ];
1776
1810
  async function setupGeneric(rootDir) {
1777
1811
  try {
1778
1812
  const pkgRaw = await readFile3(join2(rootDir, "package.json"), "utf-8");
@@ -1804,24 +1838,24 @@ async function setupMcp(rootDir) {
1804
1838
  if (config?.mcpServers?.brakit) return "exists";
1805
1839
  config.mcpServers = { ...config.mcpServers, ...MCP_CONFIG.mcpServers };
1806
1840
  await writeFile3(mcpPath, JSON.stringify(config, null, 2) + "\n");
1807
- await ensureGitignoreMcp(rootDir);
1841
+ await ensureGitignoreEntry(rootDir, ".mcp.json");
1808
1842
  return "updated";
1809
1843
  } catch {
1810
1844
  }
1811
1845
  }
1812
1846
  await writeFile3(mcpPath, JSON.stringify(MCP_CONFIG, null, 2) + "\n");
1813
- await ensureGitignoreMcp(rootDir);
1847
+ await ensureGitignoreEntry(rootDir, ".mcp.json");
1814
1848
  return "created";
1815
1849
  }
1816
- async function ensureGitignoreMcp(rootDir) {
1850
+ async function ensureGitignoreEntry(rootDir, entry) {
1817
1851
  const gitignorePath = join2(rootDir, ".gitignore");
1818
1852
  try {
1819
1853
  if (await fileExists(gitignorePath)) {
1820
1854
  const content = await readFile3(gitignorePath, "utf-8");
1821
- if (content.split("\n").some((l) => l.trim() === ".mcp.json")) return;
1822
- 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");
1823
1857
  } else {
1824
- await writeFile3(gitignorePath, ".mcp.json\n");
1858
+ await writeFile3(gitignorePath, entry + "\n");
1825
1859
  }
1826
1860
  } catch {
1827
1861
  }
@@ -1850,27 +1884,10 @@ import { readFile as readFile4, writeFile as writeFile4, unlink, rm } from "fs/p
1850
1884
  import { execSync as execSync2 } from "child_process";
1851
1885
  import pc2 from "picocolors";
1852
1886
  init_constants();
1853
- var IMPORT_LINE2 = `import "brakit";`;
1854
- var CREATED_FILES = [
1855
- "src/instrumentation.ts",
1856
- "instrumentation.ts",
1857
- "server/plugins/brakit.ts"
1858
- ];
1859
1887
  var PREPENDED_FILES = [
1860
1888
  "app/entry.server.tsx",
1861
1889
  "app/entry.server.ts",
1862
- "src/index.ts",
1863
- "src/server.ts",
1864
- "src/app.ts",
1865
- "src/index.js",
1866
- "src/server.js",
1867
- "src/app.js",
1868
- "server.ts",
1869
- "app.ts",
1870
- "index.ts",
1871
- "server.js",
1872
- "app.js",
1873
- "index.js"
1890
+ ...ENTRY_CANDIDATES
1874
1891
  ];
1875
1892
  var uninstall_default = defineCommand2({
1876
1893
  meta: {
@@ -1902,14 +1919,22 @@ var uninstall_default = defineCommand2({
1902
1919
  if (!await fileExists(absPath)) continue;
1903
1920
  const content = await readFile4(absPath, "utf-8");
1904
1921
  if (!content.includes("brakit")) continue;
1905
- const lines = content.split("\n").filter((l) => l.trim().length > 0);
1906
- 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("}"));
1907
- if (allBrakit) {
1922
+ if (isExactBrakitTemplate(content)) {
1908
1923
  await unlink(absPath);
1909
1924
  console.log(pc2.green(` \u2713 Removed ${relPath}`));
1910
1925
  removed = true;
1911
1926
  break;
1912
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
+ }
1913
1938
  }
1914
1939
  if (!removed) {
1915
1940
  const candidates = [...PREPENDED_FILES];
@@ -1923,8 +1948,8 @@ var uninstall_default = defineCommand2({
1923
1948
  const absPath = join3(rootDir, relPath);
1924
1949
  if (!await fileExists(absPath)) continue;
1925
1950
  const content = await readFile4(absPath, "utf-8");
1926
- if (!content.includes(IMPORT_LINE2)) continue;
1927
- 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");
1928
1953
  await writeFile4(absPath, updated);
1929
1954
  console.log(pc2.green(` \u2713 Removed brakit import from ${relPath}`));
1930
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.3";
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;
@@ -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.3";
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.3",
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": {