brakit 0.8.1 → 0.8.3
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/README.md +5 -5
- package/dist/api.d.ts +103 -44
- package/dist/api.js +153 -266
- package/dist/bin/brakit.js +130 -219
- package/dist/mcp/server.js +65 -23
- package/dist/runtime/index.js +1623 -1539
- package/package.json +1 -1
package/dist/bin/brakit.js
CHANGED
|
@@ -28,12 +28,10 @@ var init_routes = __esm({
|
|
|
28
28
|
});
|
|
29
29
|
|
|
30
30
|
// src/constants/limits.ts
|
|
31
|
-
var
|
|
31
|
+
var MAX_INGEST_BYTES;
|
|
32
32
|
var init_limits = __esm({
|
|
33
33
|
"src/constants/limits.ts"() {
|
|
34
34
|
"use strict";
|
|
35
|
-
MAX_REQUEST_ENTRIES = 1e3;
|
|
36
|
-
MAX_TELEMETRY_ENTRIES = 1e3;
|
|
37
35
|
MAX_INGEST_BYTES = 10 * 1024 * 1024;
|
|
38
36
|
}
|
|
39
37
|
});
|
|
@@ -79,23 +77,47 @@ var init_network = __esm({
|
|
|
79
77
|
});
|
|
80
78
|
|
|
81
79
|
// src/constants/mcp.ts
|
|
82
|
-
var MCP_SERVER_NAME, MCP_SERVER_VERSION, INITIAL_DISCOVERY_TIMEOUT_MS, LAZY_DISCOVERY_TIMEOUT_MS, CLIENT_FETCH_TIMEOUT_MS, HEALTH_CHECK_TIMEOUT_MS, DISCOVERY_POLL_INTERVAL_MS, MAX_TIMELINE_EVENTS, MAX_RESOLVED_DISPLAY, ENRICHMENT_SEVERITY_FILTER;
|
|
80
|
+
var MCP_SERVER_NAME, MCP_SERVER_VERSION, INITIAL_DISCOVERY_TIMEOUT_MS, LAZY_DISCOVERY_TIMEOUT_MS, CLIENT_FETCH_TIMEOUT_MS, HEALTH_CHECK_TIMEOUT_MS, DISCOVERY_POLL_INTERVAL_MS, MAX_DISCOVERY_DEPTH, MAX_TIMELINE_EVENTS, MAX_RESOLVED_DISPLAY, ENRICHMENT_SEVERITY_FILTER;
|
|
83
81
|
var init_mcp = __esm({
|
|
84
82
|
"src/constants/mcp.ts"() {
|
|
85
83
|
"use strict";
|
|
86
84
|
MCP_SERVER_NAME = "brakit";
|
|
87
|
-
MCP_SERVER_VERSION = "0.8.
|
|
85
|
+
MCP_SERVER_VERSION = "0.8.3";
|
|
88
86
|
INITIAL_DISCOVERY_TIMEOUT_MS = 5e3;
|
|
89
87
|
LAZY_DISCOVERY_TIMEOUT_MS = 2e3;
|
|
90
88
|
CLIENT_FETCH_TIMEOUT_MS = 1e4;
|
|
91
89
|
HEALTH_CHECK_TIMEOUT_MS = 3e3;
|
|
92
90
|
DISCOVERY_POLL_INTERVAL_MS = 500;
|
|
91
|
+
MAX_DISCOVERY_DEPTH = 5;
|
|
93
92
|
MAX_TIMELINE_EVENTS = 20;
|
|
94
93
|
MAX_RESOLVED_DISPLAY = 5;
|
|
95
94
|
ENRICHMENT_SEVERITY_FILTER = ["critical", "warning"];
|
|
96
95
|
}
|
|
97
96
|
});
|
|
98
97
|
|
|
98
|
+
// src/constants/encoding.ts
|
|
99
|
+
var init_encoding = __esm({
|
|
100
|
+
"src/constants/encoding.ts"() {
|
|
101
|
+
"use strict";
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// src/constants/severity.ts
|
|
106
|
+
var SEVERITY_CRITICAL, SEVERITY_WARNING, SEVERITY_INFO, SEVERITY_ICON_MAP;
|
|
107
|
+
var init_severity = __esm({
|
|
108
|
+
"src/constants/severity.ts"() {
|
|
109
|
+
"use strict";
|
|
110
|
+
SEVERITY_CRITICAL = "critical";
|
|
111
|
+
SEVERITY_WARNING = "warning";
|
|
112
|
+
SEVERITY_INFO = "info";
|
|
113
|
+
SEVERITY_ICON_MAP = {
|
|
114
|
+
[SEVERITY_CRITICAL]: { icon: "\u2717", cls: "critical" },
|
|
115
|
+
[SEVERITY_WARNING]: { icon: "\u26A0", cls: "warning" },
|
|
116
|
+
[SEVERITY_INFO]: { icon: "\u2139", cls: "info" }
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
99
121
|
// src/constants/index.ts
|
|
100
122
|
var init_constants = __esm({
|
|
101
123
|
"src/constants/index.ts"() {
|
|
@@ -108,6 +130,8 @@ var init_constants = __esm({
|
|
|
108
130
|
init_headers();
|
|
109
131
|
init_network();
|
|
110
132
|
init_mcp();
|
|
133
|
+
init_encoding();
|
|
134
|
+
init_severity();
|
|
111
135
|
}
|
|
112
136
|
});
|
|
113
137
|
|
|
@@ -220,22 +244,55 @@ var init_client = __esm({
|
|
|
220
244
|
});
|
|
221
245
|
|
|
222
246
|
// src/mcp/discovery.ts
|
|
223
|
-
import { readFileSync as
|
|
224
|
-
import { resolve as
|
|
247
|
+
import { readFileSync as readFileSync3, existsSync as existsSync4, readdirSync, statSync } from "fs";
|
|
248
|
+
import { resolve as resolve5, dirname } from "path";
|
|
249
|
+
function readPort(portPath) {
|
|
250
|
+
if (!existsSync4(portPath)) return null;
|
|
251
|
+
const raw = readFileSync3(portPath, "utf-8").trim();
|
|
252
|
+
const port = parseInt(raw, 10);
|
|
253
|
+
return isNaN(port) || port < 1 || port > 65535 ? null : port;
|
|
254
|
+
}
|
|
255
|
+
function portInDir(dir) {
|
|
256
|
+
return readPort(resolve5(dir, PORT_FILE));
|
|
257
|
+
}
|
|
258
|
+
function portInChildren(dir) {
|
|
259
|
+
try {
|
|
260
|
+
for (const entry of readdirSync(dir)) {
|
|
261
|
+
if (entry.startsWith(".") || entry === "node_modules") continue;
|
|
262
|
+
const child = resolve5(dir, entry);
|
|
263
|
+
try {
|
|
264
|
+
if (!statSync(child).isDirectory()) continue;
|
|
265
|
+
} catch {
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
const port = portInDir(child);
|
|
269
|
+
if (port) return port;
|
|
270
|
+
}
|
|
271
|
+
} catch {
|
|
272
|
+
}
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
function searchForPort(startDir) {
|
|
276
|
+
const start = resolve5(startDir);
|
|
277
|
+
const initial = portInDir(start) ?? portInChildren(start);
|
|
278
|
+
if (initial) return initial;
|
|
279
|
+
let dir = dirname(start);
|
|
280
|
+
for (let depth = 0; depth < MAX_DISCOVERY_DEPTH; depth++) {
|
|
281
|
+
const port = portInDir(dir);
|
|
282
|
+
if (port) return port;
|
|
283
|
+
const parent = dirname(dir);
|
|
284
|
+
if (parent === dir) break;
|
|
285
|
+
dir = parent;
|
|
286
|
+
}
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
225
289
|
function discoverBrakitPort(cwd) {
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
if (!existsSync4(portPath)) {
|
|
290
|
+
const port = searchForPort(cwd ?? process.cwd());
|
|
291
|
+
if (!port) {
|
|
229
292
|
throw new Error(
|
|
230
|
-
|
|
231
|
-
Start your app with brakit enabled first.`
|
|
293
|
+
"Brakit is not running. Start your app with brakit enabled first."
|
|
232
294
|
);
|
|
233
295
|
}
|
|
234
|
-
const raw = readFileSync4(portPath, "utf-8").trim();
|
|
235
|
-
const port = parseInt(raw, 10);
|
|
236
|
-
if (isNaN(port) || port < 1 || port > 65535) {
|
|
237
|
-
throw new Error(`Invalid port in ${portPath}: "${raw}"`);
|
|
238
|
-
}
|
|
239
296
|
return { port, baseUrl: `http://localhost:${port}` };
|
|
240
297
|
}
|
|
241
298
|
async function waitForBrakit(cwd, timeoutMs = 1e4, pollMs = DISCOVERY_POLL_INTERVAL_MS) {
|
|
@@ -338,27 +395,25 @@ async function enrichRequestDetail(client, opts) {
|
|
|
338
395
|
if (opts.requestId) {
|
|
339
396
|
const data = await client.getRequests({ search: opts.requestId, limit: 1 });
|
|
340
397
|
if (data.requests.length > 0) {
|
|
341
|
-
return buildRequestDetail(client, data.requests[0]
|
|
398
|
+
return buildRequestDetail(client, data.requests[0]);
|
|
342
399
|
}
|
|
343
400
|
} else if (opts.endpoint) {
|
|
344
401
|
const { method, path } = parseEndpointKey(opts.endpoint);
|
|
345
402
|
const data = await client.getRequests({ method, search: path, limit: 1 });
|
|
346
403
|
if (data.requests.length > 0) {
|
|
347
|
-
return buildRequestDetail(client, data.requests[0]
|
|
404
|
+
return buildRequestDetail(client, data.requests[0]);
|
|
348
405
|
}
|
|
349
406
|
}
|
|
350
407
|
return null;
|
|
351
408
|
}
|
|
352
|
-
async function buildRequestDetail(client,
|
|
353
|
-
const [
|
|
354
|
-
client.
|
|
355
|
-
client.
|
|
356
|
-
client.
|
|
357
|
-
client.getFetches(requestId)
|
|
409
|
+
async function buildRequestDetail(client, req) {
|
|
410
|
+
const [activity, queries, fetches] = await Promise.all([
|
|
411
|
+
client.getActivity(req.id),
|
|
412
|
+
client.getQueries(req.id),
|
|
413
|
+
client.getFetches(req.id)
|
|
358
414
|
]);
|
|
359
|
-
const req = reqData.requests[0];
|
|
360
415
|
return {
|
|
361
|
-
id:
|
|
416
|
+
id: req.id,
|
|
362
417
|
method: req.method,
|
|
363
418
|
url: req.url,
|
|
364
419
|
statusCode: req.statusCode,
|
|
@@ -927,22 +982,24 @@ import { runMain } from "citty";
|
|
|
927
982
|
|
|
928
983
|
// src/cli/commands/install.ts
|
|
929
984
|
import { defineCommand } from "citty";
|
|
930
|
-
import { resolve as
|
|
931
|
-
import { readFile as readFile3, writeFile as
|
|
985
|
+
import { resolve as resolve3, join as join2 } from "path";
|
|
986
|
+
import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
932
987
|
import { execSync } from "child_process";
|
|
933
988
|
import pc from "picocolors";
|
|
934
989
|
|
|
935
990
|
// src/store/finding-store.ts
|
|
936
991
|
init_constants();
|
|
992
|
+
import { readFileSync as readFileSync2, existsSync as existsSync3 } from "fs";
|
|
993
|
+
import { resolve as resolve2 } from "path";
|
|
994
|
+
|
|
995
|
+
// src/utils/atomic-writer.ts
|
|
937
996
|
import {
|
|
938
|
-
readFileSync as readFileSync2,
|
|
939
997
|
writeFileSync as writeFileSync2,
|
|
940
998
|
existsSync as existsSync2,
|
|
941
999
|
mkdirSync as mkdirSync2,
|
|
942
1000
|
renameSync
|
|
943
1001
|
} from "fs";
|
|
944
1002
|
import { writeFile as writeFile2, mkdir, rename } from "fs/promises";
|
|
945
|
-
import { resolve as resolve2 } from "path";
|
|
946
1003
|
|
|
947
1004
|
// src/utils/fs.ts
|
|
948
1005
|
import { access } from "fs/promises";
|
|
@@ -1476,166 +1533,9 @@ var responsePiiLeakRule = {
|
|
|
1476
1533
|
}
|
|
1477
1534
|
};
|
|
1478
1535
|
|
|
1479
|
-
// src/store/request-store.ts
|
|
1480
|
-
init_constants();
|
|
1481
|
-
|
|
1482
|
-
// src/utils/static-patterns.ts
|
|
1483
|
-
var STATIC_PATTERNS = [
|
|
1484
|
-
/^\/_next\//,
|
|
1485
|
-
/\.(?:js|css|map|ico|png|jpg|jpeg|gif|svg|webp|woff2?|ttf|eot)$/,
|
|
1486
|
-
/^\/favicon/,
|
|
1487
|
-
/^\/__nextjs/
|
|
1488
|
-
];
|
|
1489
|
-
function isStaticPath(urlPath) {
|
|
1490
|
-
return STATIC_PATTERNS.some((p) => p.test(urlPath));
|
|
1491
|
-
}
|
|
1492
|
-
|
|
1493
|
-
// src/store/request-store.ts
|
|
1494
|
-
function flattenHeaders(headers) {
|
|
1495
|
-
const flat = {};
|
|
1496
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
1497
|
-
if (value === void 0) continue;
|
|
1498
|
-
flat[key] = Array.isArray(value) ? value.join(", ") : value;
|
|
1499
|
-
}
|
|
1500
|
-
return flat;
|
|
1501
|
-
}
|
|
1502
|
-
var RequestStore = class {
|
|
1503
|
-
constructor(maxEntries = MAX_REQUEST_ENTRIES) {
|
|
1504
|
-
this.maxEntries = maxEntries;
|
|
1505
|
-
}
|
|
1506
|
-
requests = [];
|
|
1507
|
-
listeners = [];
|
|
1508
|
-
capture(input) {
|
|
1509
|
-
const url = input.url;
|
|
1510
|
-
const path = url.split("?")[0];
|
|
1511
|
-
let requestBodyStr = null;
|
|
1512
|
-
if (input.requestBody && input.requestBody.length > 0) {
|
|
1513
|
-
requestBodyStr = input.requestBody.subarray(0, input.config.maxBodyCapture).toString("utf-8");
|
|
1514
|
-
}
|
|
1515
|
-
let responseBodyStr = null;
|
|
1516
|
-
if (input.responseBody && input.responseBody.length > 0) {
|
|
1517
|
-
const ct = input.responseContentType;
|
|
1518
|
-
if (ct.includes("json") || ct.includes("text") || ct.includes("html")) {
|
|
1519
|
-
responseBodyStr = input.responseBody.subarray(0, input.config.maxBodyCapture).toString("utf-8");
|
|
1520
|
-
}
|
|
1521
|
-
}
|
|
1522
|
-
const entry = {
|
|
1523
|
-
id: input.requestId,
|
|
1524
|
-
method: input.method,
|
|
1525
|
-
url,
|
|
1526
|
-
path,
|
|
1527
|
-
headers: flattenHeaders(input.requestHeaders),
|
|
1528
|
-
requestBody: requestBodyStr,
|
|
1529
|
-
statusCode: input.statusCode,
|
|
1530
|
-
responseHeaders: flattenHeaders(input.responseHeaders),
|
|
1531
|
-
responseBody: responseBodyStr,
|
|
1532
|
-
startedAt: input.startTime,
|
|
1533
|
-
durationMs: Math.round((input.endTime ?? performance.now()) - input.startTime),
|
|
1534
|
-
responseSize: input.responseBody?.length ?? 0,
|
|
1535
|
-
isStatic: isStaticPath(path)
|
|
1536
|
-
};
|
|
1537
|
-
this.requests.push(entry);
|
|
1538
|
-
if (this.requests.length > this.maxEntries) {
|
|
1539
|
-
this.requests.shift();
|
|
1540
|
-
}
|
|
1541
|
-
for (const fn of this.listeners) {
|
|
1542
|
-
fn(entry);
|
|
1543
|
-
}
|
|
1544
|
-
return entry;
|
|
1545
|
-
}
|
|
1546
|
-
getAll() {
|
|
1547
|
-
return this.requests;
|
|
1548
|
-
}
|
|
1549
|
-
clear() {
|
|
1550
|
-
this.requests.length = 0;
|
|
1551
|
-
}
|
|
1552
|
-
onRequest(fn) {
|
|
1553
|
-
this.listeners.push(fn);
|
|
1554
|
-
}
|
|
1555
|
-
offRequest(fn) {
|
|
1556
|
-
const idx = this.listeners.indexOf(fn);
|
|
1557
|
-
if (idx !== -1) this.listeners.splice(idx, 1);
|
|
1558
|
-
}
|
|
1559
|
-
};
|
|
1560
|
-
|
|
1561
|
-
// src/store/request-log.ts
|
|
1562
|
-
var defaultStore = new RequestStore();
|
|
1563
|
-
|
|
1564
|
-
// src/store/telemetry-store.ts
|
|
1565
|
-
init_constants();
|
|
1566
|
-
import { randomUUID } from "crypto";
|
|
1567
|
-
var TelemetryStore = class {
|
|
1568
|
-
constructor(maxEntries = MAX_TELEMETRY_ENTRIES) {
|
|
1569
|
-
this.maxEntries = maxEntries;
|
|
1570
|
-
}
|
|
1571
|
-
entries = [];
|
|
1572
|
-
listeners = [];
|
|
1573
|
-
add(data) {
|
|
1574
|
-
const entry = { id: randomUUID(), ...data };
|
|
1575
|
-
this.entries.push(entry);
|
|
1576
|
-
if (this.entries.length > this.maxEntries) this.entries.shift();
|
|
1577
|
-
for (const fn of this.listeners) fn(entry);
|
|
1578
|
-
return entry;
|
|
1579
|
-
}
|
|
1580
|
-
getAll() {
|
|
1581
|
-
return this.entries;
|
|
1582
|
-
}
|
|
1583
|
-
getByRequest(requestId) {
|
|
1584
|
-
return this.entries.filter((e) => e.parentRequestId === requestId);
|
|
1585
|
-
}
|
|
1586
|
-
clear() {
|
|
1587
|
-
this.entries.length = 0;
|
|
1588
|
-
}
|
|
1589
|
-
onEntry(fn) {
|
|
1590
|
-
this.listeners.push(fn);
|
|
1591
|
-
}
|
|
1592
|
-
offEntry(fn) {
|
|
1593
|
-
const idx = this.listeners.indexOf(fn);
|
|
1594
|
-
if (idx !== -1) this.listeners.splice(idx, 1);
|
|
1595
|
-
}
|
|
1596
|
-
};
|
|
1597
|
-
|
|
1598
|
-
// src/store/fetch-store.ts
|
|
1599
|
-
var FetchStore = class extends TelemetryStore {
|
|
1600
|
-
};
|
|
1601
|
-
var defaultFetchStore = new FetchStore();
|
|
1602
|
-
|
|
1603
|
-
// src/store/log-store.ts
|
|
1604
|
-
var LogStore = class extends TelemetryStore {
|
|
1605
|
-
};
|
|
1606
|
-
var defaultLogStore = new LogStore();
|
|
1607
|
-
|
|
1608
|
-
// src/store/error-store.ts
|
|
1609
|
-
var ErrorStore = class extends TelemetryStore {
|
|
1610
|
-
};
|
|
1611
|
-
var defaultErrorStore = new ErrorStore();
|
|
1612
|
-
|
|
1613
|
-
// src/store/query-store.ts
|
|
1614
|
-
var QueryStore = class extends TelemetryStore {
|
|
1615
|
-
};
|
|
1616
|
-
var defaultQueryStore = new QueryStore();
|
|
1617
|
-
|
|
1618
|
-
// src/store/metrics/metrics-store.ts
|
|
1619
|
-
init_constants();
|
|
1620
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
1621
|
-
init_endpoint();
|
|
1622
|
-
|
|
1623
|
-
// src/store/metrics/persistence.ts
|
|
1624
|
-
init_constants();
|
|
1625
|
-
import {
|
|
1626
|
-
readFileSync as readFileSync3,
|
|
1627
|
-
writeFileSync as writeFileSync3,
|
|
1628
|
-
mkdirSync as mkdirSync3,
|
|
1629
|
-
existsSync as existsSync3,
|
|
1630
|
-
unlinkSync,
|
|
1631
|
-
renameSync as renameSync2
|
|
1632
|
-
} from "fs";
|
|
1633
|
-
import { writeFile as writeFile3, mkdir as mkdir2, rename as rename2 } from "fs/promises";
|
|
1634
|
-
import { resolve as resolve3 } from "path";
|
|
1635
|
-
|
|
1636
1536
|
// src/analysis/group.ts
|
|
1637
1537
|
init_constants();
|
|
1638
|
-
import { randomUUID
|
|
1538
|
+
import { randomUUID } from "crypto";
|
|
1639
1539
|
|
|
1640
1540
|
// src/analysis/label.ts
|
|
1641
1541
|
init_constants();
|
|
@@ -1650,50 +1550,50 @@ init_thresholds();
|
|
|
1650
1550
|
|
|
1651
1551
|
// src/analysis/insights/rules/n1.ts
|
|
1652
1552
|
init_endpoint();
|
|
1653
|
-
|
|
1553
|
+
init_constants();
|
|
1654
1554
|
|
|
1655
1555
|
// src/analysis/insights/rules/cross-endpoint.ts
|
|
1656
1556
|
init_endpoint();
|
|
1657
|
-
|
|
1557
|
+
init_constants();
|
|
1658
1558
|
|
|
1659
1559
|
// src/analysis/insights/rules/redundant-query.ts
|
|
1660
1560
|
init_endpoint();
|
|
1661
|
-
|
|
1561
|
+
init_constants();
|
|
1662
1562
|
|
|
1663
1563
|
// src/analysis/insights/rules/error-hotspot.ts
|
|
1664
|
-
|
|
1564
|
+
init_constants();
|
|
1665
1565
|
|
|
1666
1566
|
// src/analysis/insights/rules/duplicate.ts
|
|
1667
|
-
|
|
1567
|
+
init_constants();
|
|
1668
1568
|
|
|
1669
1569
|
// src/analysis/insights/rules/slow.ts
|
|
1670
|
-
|
|
1570
|
+
init_constants();
|
|
1671
1571
|
|
|
1672
1572
|
// src/analysis/insights/rules/query-heavy.ts
|
|
1673
|
-
|
|
1573
|
+
init_constants();
|
|
1674
1574
|
|
|
1675
1575
|
// src/analysis/insights/rules/select-star.ts
|
|
1676
|
-
|
|
1576
|
+
init_constants();
|
|
1677
1577
|
|
|
1678
1578
|
// src/analysis/insights/rules/high-rows.ts
|
|
1679
|
-
|
|
1579
|
+
init_constants();
|
|
1680
1580
|
|
|
1681
1581
|
// src/analysis/insights/rules/response-overfetch.ts
|
|
1682
1582
|
init_endpoint();
|
|
1683
|
-
|
|
1583
|
+
init_constants();
|
|
1684
1584
|
|
|
1685
1585
|
// src/analysis/insights/rules/large-response.ts
|
|
1686
|
-
|
|
1586
|
+
init_constants();
|
|
1687
1587
|
|
|
1688
1588
|
// src/analysis/insights/rules/regression.ts
|
|
1689
|
-
|
|
1589
|
+
init_constants();
|
|
1690
1590
|
|
|
1691
1591
|
// src/analysis/insight-tracker.ts
|
|
1692
1592
|
init_endpoint();
|
|
1693
1593
|
init_thresholds();
|
|
1694
1594
|
|
|
1695
1595
|
// src/index.ts
|
|
1696
|
-
var VERSION = "0.8.
|
|
1596
|
+
var VERSION = "0.8.3";
|
|
1697
1597
|
|
|
1698
1598
|
// src/cli/commands/install.ts
|
|
1699
1599
|
var IMPORT_LINE = `import "brakit";`;
|
|
@@ -1713,10 +1613,21 @@ var install_default = defineCommand({
|
|
|
1713
1613
|
}
|
|
1714
1614
|
},
|
|
1715
1615
|
async run({ args }) {
|
|
1716
|
-
const rootDir =
|
|
1616
|
+
const rootDir = resolve3(args.dir);
|
|
1717
1617
|
const pkgPath = join2(rootDir, "package.json");
|
|
1718
1618
|
if (!await fileExists(pkgPath)) {
|
|
1719
|
-
console.error(pc.red(" No
|
|
1619
|
+
console.error(pc.red(" No project found. Run this from your project directory."));
|
|
1620
|
+
process.exit(1);
|
|
1621
|
+
}
|
|
1622
|
+
let pkg;
|
|
1623
|
+
try {
|
|
1624
|
+
pkg = JSON.parse(await readFile3(pkgPath, "utf-8"));
|
|
1625
|
+
} catch {
|
|
1626
|
+
console.error(pc.red(" Failed to read package.json."));
|
|
1627
|
+
process.exit(1);
|
|
1628
|
+
}
|
|
1629
|
+
if (!pkg.name || typeof pkg.name !== "string") {
|
|
1630
|
+
console.error(pc.red(" No project found. Run this from your project directory."));
|
|
1720
1631
|
process.exit(1);
|
|
1721
1632
|
}
|
|
1722
1633
|
let project;
|
|
@@ -1813,7 +1724,7 @@ async function setupNextjs(rootDir) {
|
|
|
1813
1724
|
`}`,
|
|
1814
1725
|
``
|
|
1815
1726
|
].join("\n");
|
|
1816
|
-
await
|
|
1727
|
+
await writeFile3(absPath, content);
|
|
1817
1728
|
return { action: "created", file: relPath, content };
|
|
1818
1729
|
}
|
|
1819
1730
|
async function setupNuxt(rootDir) {
|
|
@@ -1829,9 +1740,9 @@ async function setupNuxt(rootDir) {
|
|
|
1829
1740
|
const content = `${IMPORT_LINE}
|
|
1830
1741
|
`;
|
|
1831
1742
|
const dir = join2(rootDir, "server/plugins");
|
|
1832
|
-
const { mkdirSync:
|
|
1833
|
-
|
|
1834
|
-
await
|
|
1743
|
+
const { mkdirSync: mkdirSync3 } = await import("fs");
|
|
1744
|
+
mkdirSync3(dir, { recursive: true });
|
|
1745
|
+
await writeFile3(absPath, content);
|
|
1835
1746
|
return { action: "created", file: relPath, content };
|
|
1836
1747
|
}
|
|
1837
1748
|
async function setupPrepend(rootDir, ...candidates) {
|
|
@@ -1842,7 +1753,7 @@ async function setupPrepend(rootDir, ...candidates) {
|
|
|
1842
1753
|
if (content.includes(IMPORT_MARKER)) {
|
|
1843
1754
|
return { action: "exists", file: relPath };
|
|
1844
1755
|
}
|
|
1845
|
-
await
|
|
1756
|
+
await writeFile3(absPath, `${IMPORT_LINE}
|
|
1846
1757
|
${content}`);
|
|
1847
1758
|
return { action: "prepended", file: relPath };
|
|
1848
1759
|
}
|
|
@@ -1892,13 +1803,13 @@ async function setupMcp(rootDir) {
|
|
|
1892
1803
|
const config = JSON.parse(raw);
|
|
1893
1804
|
if (config?.mcpServers?.brakit) return "exists";
|
|
1894
1805
|
config.mcpServers = { ...config.mcpServers, ...MCP_CONFIG.mcpServers };
|
|
1895
|
-
await
|
|
1806
|
+
await writeFile3(mcpPath, JSON.stringify(config, null, 2) + "\n");
|
|
1896
1807
|
await ensureGitignoreMcp(rootDir);
|
|
1897
1808
|
return "updated";
|
|
1898
1809
|
} catch {
|
|
1899
1810
|
}
|
|
1900
1811
|
}
|
|
1901
|
-
await
|
|
1812
|
+
await writeFile3(mcpPath, JSON.stringify(MCP_CONFIG, null, 2) + "\n");
|
|
1902
1813
|
await ensureGitignoreMcp(rootDir);
|
|
1903
1814
|
return "created";
|
|
1904
1815
|
}
|
|
@@ -1908,9 +1819,9 @@ async function ensureGitignoreMcp(rootDir) {
|
|
|
1908
1819
|
if (await fileExists(gitignorePath)) {
|
|
1909
1820
|
const content = await readFile3(gitignorePath, "utf-8");
|
|
1910
1821
|
if (content.split("\n").some((l) => l.trim() === ".mcp.json")) return;
|
|
1911
|
-
await
|
|
1822
|
+
await writeFile3(gitignorePath, content.trimEnd() + "\n.mcp.json\n");
|
|
1912
1823
|
} else {
|
|
1913
|
-
await
|
|
1824
|
+
await writeFile3(gitignorePath, ".mcp.json\n");
|
|
1914
1825
|
}
|
|
1915
1826
|
} catch {
|
|
1916
1827
|
}
|
|
@@ -1934,8 +1845,8 @@ function printManualInstructions(framework) {
|
|
|
1934
1845
|
|
|
1935
1846
|
// src/cli/commands/uninstall.ts
|
|
1936
1847
|
import { defineCommand as defineCommand2 } from "citty";
|
|
1937
|
-
import { resolve as
|
|
1938
|
-
import { readFile as readFile4, writeFile as
|
|
1848
|
+
import { resolve as resolve4, join as join3 } from "path";
|
|
1849
|
+
import { readFile as readFile4, writeFile as writeFile4, unlink, rm } from "fs/promises";
|
|
1939
1850
|
import { execSync as execSync2 } from "child_process";
|
|
1940
1851
|
import pc2 from "picocolors";
|
|
1941
1852
|
init_constants();
|
|
@@ -1976,7 +1887,7 @@ var uninstall_default = defineCommand2({
|
|
|
1976
1887
|
}
|
|
1977
1888
|
},
|
|
1978
1889
|
async run({ args }) {
|
|
1979
|
-
const rootDir =
|
|
1890
|
+
const rootDir = resolve4(args.dir);
|
|
1980
1891
|
let project = null;
|
|
1981
1892
|
try {
|
|
1982
1893
|
project = await detectProject(rootDir);
|
|
@@ -2014,7 +1925,7 @@ var uninstall_default = defineCommand2({
|
|
|
2014
1925
|
const content = await readFile4(absPath, "utf-8");
|
|
2015
1926
|
if (!content.includes(IMPORT_LINE2)) continue;
|
|
2016
1927
|
const updated = content.split("\n").filter((line) => line.trim() !== IMPORT_LINE2.trim()).join("\n");
|
|
2017
|
-
await
|
|
1928
|
+
await writeFile4(absPath, updated);
|
|
2018
1929
|
console.log(pc2.green(` \u2713 Removed brakit import from ${relPath}`));
|
|
2019
1930
|
removed = true;
|
|
2020
1931
|
break;
|
|
@@ -2054,7 +1965,7 @@ async function removeMcpConfig(rootDir) {
|
|
|
2054
1965
|
if (Object.keys(config.mcpServers).length === 0) {
|
|
2055
1966
|
await unlink(mcpPath);
|
|
2056
1967
|
} else {
|
|
2057
|
-
await
|
|
1968
|
+
await writeFile4(mcpPath, JSON.stringify(config, null, 2) + "\n");
|
|
2058
1969
|
}
|
|
2059
1970
|
return true;
|
|
2060
1971
|
} catch {
|
|
@@ -2101,7 +2012,7 @@ async function cleanGitignore(rootDir) {
|
|
|
2101
2012
|
const lines = content.split("\n");
|
|
2102
2013
|
const filtered = lines.filter((line) => line.trim() !== METRICS_DIR);
|
|
2103
2014
|
if (filtered.length === lines.length) return false;
|
|
2104
|
-
await
|
|
2015
|
+
await writeFile4(gitignorePath, filtered.join("\n"));
|
|
2105
2016
|
return true;
|
|
2106
2017
|
} catch {
|
|
2107
2018
|
return false;
|
package/dist/mcp/server.js
CHANGED
|
@@ -22,12 +22,13 @@ 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.
|
|
25
|
+
var MCP_SERVER_VERSION = "0.8.3";
|
|
26
26
|
var INITIAL_DISCOVERY_TIMEOUT_MS = 5e3;
|
|
27
27
|
var LAZY_DISCOVERY_TIMEOUT_MS = 2e3;
|
|
28
28
|
var CLIENT_FETCH_TIMEOUT_MS = 1e4;
|
|
29
29
|
var HEALTH_CHECK_TIMEOUT_MS = 3e3;
|
|
30
30
|
var DISCOVERY_POLL_INTERVAL_MS = 500;
|
|
31
|
+
var MAX_DISCOVERY_DEPTH = 5;
|
|
31
32
|
var MAX_TIMELINE_EVENTS = 20;
|
|
32
33
|
var MAX_RESOLVED_DISPLAY = 5;
|
|
33
34
|
var ENRICHMENT_SEVERITY_FILTER = ["critical", "warning"];
|
|
@@ -107,8 +108,8 @@ var BrakitClient = class {
|
|
|
107
108
|
};
|
|
108
109
|
|
|
109
110
|
// src/mcp/discovery.ts
|
|
110
|
-
import { readFileSync, existsSync } from "fs";
|
|
111
|
-
import { resolve } from "path";
|
|
111
|
+
import { readFileSync, existsSync, readdirSync, statSync } from "fs";
|
|
112
|
+
import { resolve, dirname } from "path";
|
|
112
113
|
|
|
113
114
|
// src/constants/limits.ts
|
|
114
115
|
var MAX_INGEST_BYTES = 10 * 1024 * 1024;
|
|
@@ -116,21 +117,64 @@ var MAX_INGEST_BYTES = 10 * 1024 * 1024;
|
|
|
116
117
|
// src/constants/metrics.ts
|
|
117
118
|
var PORT_FILE = ".brakit/port";
|
|
118
119
|
|
|
120
|
+
// src/constants/severity.ts
|
|
121
|
+
var SEVERITY_CRITICAL = "critical";
|
|
122
|
+
var SEVERITY_WARNING = "warning";
|
|
123
|
+
var SEVERITY_INFO = "info";
|
|
124
|
+
var SEVERITY_ICON_MAP = {
|
|
125
|
+
[SEVERITY_CRITICAL]: { icon: "\u2717", cls: "critical" },
|
|
126
|
+
[SEVERITY_WARNING]: { icon: "\u26A0", cls: "warning" },
|
|
127
|
+
[SEVERITY_INFO]: { icon: "\u2139", cls: "info" }
|
|
128
|
+
};
|
|
129
|
+
|
|
119
130
|
// src/mcp/discovery.ts
|
|
131
|
+
function readPort(portPath) {
|
|
132
|
+
if (!existsSync(portPath)) return null;
|
|
133
|
+
const raw = readFileSync(portPath, "utf-8").trim();
|
|
134
|
+
const port = parseInt(raw, 10);
|
|
135
|
+
return isNaN(port) || port < 1 || port > 65535 ? null : port;
|
|
136
|
+
}
|
|
137
|
+
function portInDir(dir) {
|
|
138
|
+
return readPort(resolve(dir, PORT_FILE));
|
|
139
|
+
}
|
|
140
|
+
function portInChildren(dir) {
|
|
141
|
+
try {
|
|
142
|
+
for (const entry of readdirSync(dir)) {
|
|
143
|
+
if (entry.startsWith(".") || entry === "node_modules") continue;
|
|
144
|
+
const child = resolve(dir, entry);
|
|
145
|
+
try {
|
|
146
|
+
if (!statSync(child).isDirectory()) continue;
|
|
147
|
+
} catch {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
const port = portInDir(child);
|
|
151
|
+
if (port) return port;
|
|
152
|
+
}
|
|
153
|
+
} catch {
|
|
154
|
+
}
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
function searchForPort(startDir) {
|
|
158
|
+
const start = resolve(startDir);
|
|
159
|
+
const initial = portInDir(start) ?? portInChildren(start);
|
|
160
|
+
if (initial) return initial;
|
|
161
|
+
let dir = dirname(start);
|
|
162
|
+
for (let depth = 0; depth < MAX_DISCOVERY_DEPTH; depth++) {
|
|
163
|
+
const port = portInDir(dir);
|
|
164
|
+
if (port) return port;
|
|
165
|
+
const parent = dirname(dir);
|
|
166
|
+
if (parent === dir) break;
|
|
167
|
+
dir = parent;
|
|
168
|
+
}
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
120
171
|
function discoverBrakitPort(cwd) {
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
if (!existsSync(portPath)) {
|
|
172
|
+
const port = searchForPort(cwd ?? process.cwd());
|
|
173
|
+
if (!port) {
|
|
124
174
|
throw new Error(
|
|
125
|
-
|
|
126
|
-
Start your app with brakit enabled first.`
|
|
175
|
+
"Brakit is not running. Start your app with brakit enabled first."
|
|
127
176
|
);
|
|
128
177
|
}
|
|
129
|
-
const raw = readFileSync(portPath, "utf-8").trim();
|
|
130
|
-
const port = parseInt(raw, 10);
|
|
131
|
-
if (isNaN(port) || port < 1 || port > 65535) {
|
|
132
|
-
throw new Error(`Invalid port in ${portPath}: "${raw}"`);
|
|
133
|
-
}
|
|
134
178
|
return { port, baseUrl: `http://localhost:${port}` };
|
|
135
179
|
}
|
|
136
180
|
async function waitForBrakit(cwd, timeoutMs = 1e4, pollMs = DISCOVERY_POLL_INTERVAL_MS) {
|
|
@@ -244,27 +288,25 @@ async function enrichRequestDetail(client, opts) {
|
|
|
244
288
|
if (opts.requestId) {
|
|
245
289
|
const data = await client.getRequests({ search: opts.requestId, limit: 1 });
|
|
246
290
|
if (data.requests.length > 0) {
|
|
247
|
-
return buildRequestDetail(client, data.requests[0]
|
|
291
|
+
return buildRequestDetail(client, data.requests[0]);
|
|
248
292
|
}
|
|
249
293
|
} else if (opts.endpoint) {
|
|
250
294
|
const { method, path } = parseEndpointKey(opts.endpoint);
|
|
251
295
|
const data = await client.getRequests({ method, search: path, limit: 1 });
|
|
252
296
|
if (data.requests.length > 0) {
|
|
253
|
-
return buildRequestDetail(client, data.requests[0]
|
|
297
|
+
return buildRequestDetail(client, data.requests[0]);
|
|
254
298
|
}
|
|
255
299
|
}
|
|
256
300
|
return null;
|
|
257
301
|
}
|
|
258
|
-
async function buildRequestDetail(client,
|
|
259
|
-
const [
|
|
260
|
-
client.
|
|
261
|
-
client.
|
|
262
|
-
client.
|
|
263
|
-
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)
|
|
264
307
|
]);
|
|
265
|
-
const req = reqData.requests[0];
|
|
266
308
|
return {
|
|
267
|
-
id:
|
|
309
|
+
id: req.id,
|
|
268
310
|
method: req.method,
|
|
269
311
|
url: req.url,
|
|
270
312
|
statusCode: req.statusCode,
|