brakit 0.10.0 → 0.10.2
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.d.ts +47 -19
- package/dist/api.js +127 -31
- package/dist/bin/brakit.js +425 -60
- package/dist/dashboard-client.global.js +524 -465
- package/dist/dashboard.html +623 -513
- package/dist/mcp/server.js +175 -22
- package/dist/runtime/index.js +1758 -1458
- package/package.json +1 -1
package/dist/bin/brakit.js
CHANGED
|
@@ -10,7 +10,7 @@ var __export = (target, all) => {
|
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
// src/constants/config.ts
|
|
13
|
-
var PROJECT_HASH_LENGTH, SECRET_SCAN_ARRAY_LIMIT, PII_SCAN_ARRAY_LIMIT, MIN_SECRET_VALUE_LENGTH, FULL_RECORD_MIN_FIELDS, LIST_PII_MIN_ITEMS, MAX_OBJECT_SCAN_DEPTH, ISSUE_PRUNE_TTL_MS, OVERFETCH_UNWRAP_MIN_SIZE, STALE_ISSUE_TTL_MS, METRICS_DIR, PORT_FILE, VALID_ISSUE_STATES, VALID_AI_FIX_STATUSES, VALID_SECURITY_SEVERITIES,
|
|
13
|
+
var PROJECT_HASH_LENGTH, SECRET_SCAN_ARRAY_LIMIT, PII_SCAN_ARRAY_LIMIT, MIN_SECRET_VALUE_LENGTH, FULL_RECORD_MIN_FIELDS, LIST_PII_MIN_ITEMS, MAX_OBJECT_SCAN_DEPTH, ISSUE_PRUNE_TTL_MS, OVERFETCH_UNWRAP_MIN_SIZE, STALE_ISSUE_TTL_MS, METRICS_DIR, PORT_FILE, VALID_ISSUE_STATES, VALID_AI_FIX_STATUSES, VALID_SECURITY_SEVERITIES, DETAIL_PREVIEW_LENGTH;
|
|
14
14
|
var init_config = __esm({
|
|
15
15
|
"src/constants/config.ts"() {
|
|
16
16
|
"use strict";
|
|
@@ -29,8 +29,6 @@ var init_config = __esm({
|
|
|
29
29
|
VALID_ISSUE_STATES = /* @__PURE__ */ new Set(["open", "fixing", "resolved", "stale", "regressed"]);
|
|
30
30
|
VALID_AI_FIX_STATUSES = /* @__PURE__ */ new Set(["fixed", "wont_fix"]);
|
|
31
31
|
VALID_SECURITY_SEVERITIES = /* @__PURE__ */ new Set(["critical", "warning"]);
|
|
32
|
-
TELEMETRY_EVENT_CLI_INVOKED = "cli_invoked";
|
|
33
|
-
TELEMETRY_EVENT_CLI_UNINSTALL = "cli_uninstall";
|
|
34
32
|
DETAIL_PREVIEW_LENGTH = 120;
|
|
35
33
|
}
|
|
36
34
|
});
|
|
@@ -72,8 +70,91 @@ var init_type_guards = __esm({
|
|
|
72
70
|
}
|
|
73
71
|
});
|
|
74
72
|
|
|
73
|
+
// src/constants/detection.ts
|
|
74
|
+
var KNOWN_DEPENDENCY_NAMES, KNOWN_DEPENDENCY_SET;
|
|
75
|
+
var init_detection = __esm({
|
|
76
|
+
"src/constants/detection.ts"() {
|
|
77
|
+
"use strict";
|
|
78
|
+
KNOWN_DEPENDENCY_NAMES = [
|
|
79
|
+
// -- Frameworks (meta) --
|
|
80
|
+
"next",
|
|
81
|
+
"@remix-run/dev",
|
|
82
|
+
"nuxt",
|
|
83
|
+
"astro",
|
|
84
|
+
// -- Frameworks (backend) --
|
|
85
|
+
"@nestjs/core",
|
|
86
|
+
"@adonisjs/core",
|
|
87
|
+
"sails",
|
|
88
|
+
"express",
|
|
89
|
+
"fastify",
|
|
90
|
+
"hono",
|
|
91
|
+
"koa",
|
|
92
|
+
"@hapi/hapi",
|
|
93
|
+
"elysia",
|
|
94
|
+
"h3",
|
|
95
|
+
"nitro",
|
|
96
|
+
"@trpc/server",
|
|
97
|
+
// -- Bundlers --
|
|
98
|
+
"vite",
|
|
99
|
+
// -- ORM / query builders --
|
|
100
|
+
"prisma",
|
|
101
|
+
"@prisma/client",
|
|
102
|
+
"drizzle-orm",
|
|
103
|
+
"typeorm",
|
|
104
|
+
"sequelize",
|
|
105
|
+
"mongoose",
|
|
106
|
+
"kysely",
|
|
107
|
+
"knex",
|
|
108
|
+
"@mikro-orm/core",
|
|
109
|
+
"objection",
|
|
110
|
+
// -- DB drivers --
|
|
111
|
+
"pg",
|
|
112
|
+
"mysql2",
|
|
113
|
+
"mongodb",
|
|
114
|
+
"better-sqlite3",
|
|
115
|
+
"@libsql/client",
|
|
116
|
+
"@planetscale/database",
|
|
117
|
+
"ioredis",
|
|
118
|
+
"redis",
|
|
119
|
+
// -- Auth --
|
|
120
|
+
"lucia",
|
|
121
|
+
"next-auth",
|
|
122
|
+
"@auth/core",
|
|
123
|
+
"passport",
|
|
124
|
+
// -- Queues / messaging --
|
|
125
|
+
"bullmq",
|
|
126
|
+
"amqplib",
|
|
127
|
+
"kafkajs",
|
|
128
|
+
// -- Validation --
|
|
129
|
+
"zod",
|
|
130
|
+
"joi",
|
|
131
|
+
"yup",
|
|
132
|
+
"arktype",
|
|
133
|
+
"valibot",
|
|
134
|
+
// -- HTTP clients --
|
|
135
|
+
"axios",
|
|
136
|
+
"got",
|
|
137
|
+
"ky",
|
|
138
|
+
"undici",
|
|
139
|
+
// -- Realtime --
|
|
140
|
+
"socket.io",
|
|
141
|
+
"ws",
|
|
142
|
+
// -- CSS / styling --
|
|
143
|
+
"tailwindcss",
|
|
144
|
+
// -- Testing --
|
|
145
|
+
"vitest",
|
|
146
|
+
"jest",
|
|
147
|
+
"mocha",
|
|
148
|
+
// -- Runtime indicators --
|
|
149
|
+
"bun-types",
|
|
150
|
+
"@types/bun"
|
|
151
|
+
];
|
|
152
|
+
KNOWN_DEPENDENCY_SET = new Set(KNOWN_DEPENDENCY_NAMES);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
75
156
|
// src/constants/labels.ts
|
|
76
|
-
var DASHBOARD_PREFIX, DASHBOARD_API_REQUESTS, DASHBOARD_API_EVENTS, DASHBOARD_API_FLOWS, DASHBOARD_API_CLEAR, DASHBOARD_API_LOGS, DASHBOARD_API_FETCHES, DASHBOARD_API_ERRORS, DASHBOARD_API_QUERIES, DASHBOARD_API_INGEST, DASHBOARD_API_METRICS, DASHBOARD_API_ACTIVITY, DASHBOARD_API_METRICS_LIVE, DASHBOARD_API_INSIGHTS, DASHBOARD_API_SECURITY, DASHBOARD_API_TAB, DASHBOARD_API_FINDINGS, DASHBOARD_API_FINDINGS_REPORT, DASHBOARD_API_GRAPH, VALID_TABS_TUPLE, VALID_TABS
|
|
157
|
+
var DASHBOARD_PREFIX, DASHBOARD_API_REQUESTS, DASHBOARD_API_EVENTS, DASHBOARD_API_FLOWS, DASHBOARD_API_CLEAR, DASHBOARD_API_LOGS, DASHBOARD_API_FETCHES, DASHBOARD_API_ERRORS, DASHBOARD_API_QUERIES, DASHBOARD_API_INGEST, DASHBOARD_API_METRICS, DASHBOARD_API_ACTIVITY, DASHBOARD_API_METRICS_LIVE, DASHBOARD_API_INSIGHTS, DASHBOARD_API_SECURITY, DASHBOARD_API_TAB, DASHBOARD_API_FINDINGS, DASHBOARD_API_FINDINGS_REPORT, DASHBOARD_API_GRAPH, VALID_TABS_TUPLE, VALID_TABS;
|
|
77
158
|
var init_labels = __esm({
|
|
78
159
|
"src/constants/labels.ts"() {
|
|
79
160
|
"use strict";
|
|
@@ -99,19 +180,12 @@ var init_labels = __esm({
|
|
|
99
180
|
VALID_TABS_TUPLE = [
|
|
100
181
|
"overview",
|
|
101
182
|
"actions",
|
|
102
|
-
"
|
|
103
|
-
"fetches",
|
|
104
|
-
"queries",
|
|
105
|
-
"errors",
|
|
106
|
-
"logs",
|
|
183
|
+
"insights",
|
|
107
184
|
"performance",
|
|
108
|
-
"
|
|
109
|
-
"
|
|
185
|
+
"graph",
|
|
186
|
+
"explorer"
|
|
110
187
|
];
|
|
111
188
|
VALID_TABS = new Set(VALID_TABS_TUPLE);
|
|
112
|
-
POSTHOG_HOST = "https://us.i.posthog.com";
|
|
113
|
-
POSTHOG_CAPTURE_PATH = "/i/v0/e/";
|
|
114
|
-
POSTHOG_REQUEST_TIMEOUT_MS = 3e3;
|
|
115
189
|
}
|
|
116
190
|
});
|
|
117
191
|
|
|
@@ -140,7 +214,7 @@ var init_features = __esm({
|
|
|
140
214
|
MAX_TIMELINE_EVENTS = 20;
|
|
141
215
|
MAX_RESOLVED_DISPLAY = 5;
|
|
142
216
|
ENRICHMENT_SEVERITY_FILTER = ["critical", "warning"];
|
|
143
|
-
MCP_SERVER_VERSION = "0.10.
|
|
217
|
+
MCP_SERVER_VERSION = "0.10.2";
|
|
144
218
|
RECOVERY_WINDOW_MS = 5 * 60 * 1e3;
|
|
145
219
|
PORT_MIN = 1;
|
|
146
220
|
PORT_MAX = 65535;
|
|
@@ -149,13 +223,32 @@ var init_features = __esm({
|
|
|
149
223
|
}
|
|
150
224
|
});
|
|
151
225
|
|
|
226
|
+
// src/constants/telemetry.ts
|
|
227
|
+
var POSTHOG_HOST, POSTHOG_CAPTURE_PATH, POSTHOG_REQUEST_TIMEOUT_MS, TELEMETRY_EVENT_CLI_INVOKED, TELEMETRY_EVENT_CLI_UNINSTALL, TELEMETRY_EVENT_DASHBOARD_VIEWED, TELEMETRY_EVENT_SESSION, TELEMETRY_SDK_NAME, SPEED_BUCKET_THRESHOLDS;
|
|
228
|
+
var init_telemetry = __esm({
|
|
229
|
+
"src/constants/telemetry.ts"() {
|
|
230
|
+
"use strict";
|
|
231
|
+
POSTHOG_HOST = "https://us.i.posthog.com";
|
|
232
|
+
POSTHOG_CAPTURE_PATH = "/i/v0/e/";
|
|
233
|
+
POSTHOG_REQUEST_TIMEOUT_MS = 3e3;
|
|
234
|
+
TELEMETRY_EVENT_CLI_INVOKED = "cli_invoked";
|
|
235
|
+
TELEMETRY_EVENT_CLI_UNINSTALL = "cli_uninstall";
|
|
236
|
+
TELEMETRY_EVENT_DASHBOARD_VIEWED = "dashboard_viewed";
|
|
237
|
+
TELEMETRY_EVENT_SESSION = "session";
|
|
238
|
+
TELEMETRY_SDK_NAME = "node";
|
|
239
|
+
SPEED_BUCKET_THRESHOLDS = [200, 500, 1e3, 2e3, 5e3];
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
|
|
152
243
|
// src/constants/index.ts
|
|
153
244
|
var init_constants = __esm({
|
|
154
245
|
"src/constants/index.ts"() {
|
|
155
246
|
"use strict";
|
|
156
247
|
init_config();
|
|
248
|
+
init_detection();
|
|
157
249
|
init_labels();
|
|
158
250
|
init_features();
|
|
251
|
+
init_telemetry();
|
|
159
252
|
}
|
|
160
253
|
});
|
|
161
254
|
|
|
@@ -366,10 +459,27 @@ async function enrichFindings(client) {
|
|
|
366
459
|
if (reqData.requests.length > 0) {
|
|
367
460
|
const req = reqData.requests[0];
|
|
368
461
|
if (req.id) {
|
|
369
|
-
const activity = await
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
462
|
+
const [activity, queries, fetches] = await Promise.all([
|
|
463
|
+
client.getActivity(req.id),
|
|
464
|
+
client.getQueries(req.id),
|
|
465
|
+
client.getFetches(req.id)
|
|
466
|
+
]);
|
|
467
|
+
const lines = [`Request took ${req.durationMs}ms.`];
|
|
468
|
+
if (queries.entries.length > 0) {
|
|
469
|
+
lines.push(`DB Queries (${queries.entries.length}):`);
|
|
470
|
+
for (const q of queries.entries.slice(0, 5)) {
|
|
471
|
+
const sql = q.sql ?? `${q.operation ?? ""} ${q.table ?? q.model ?? ""}`;
|
|
472
|
+
lines.push(` [${q.durationMs}ms] ${sql}`);
|
|
473
|
+
}
|
|
474
|
+
if (queries.entries.length > 5) lines.push(` ... and ${queries.entries.length - 5} more`);
|
|
475
|
+
}
|
|
476
|
+
if (fetches.entries.length > 0) {
|
|
477
|
+
lines.push(`Fetches (${fetches.entries.length}):`);
|
|
478
|
+
for (const f of fetches.entries.slice(0, 3)) {
|
|
479
|
+
lines.push(` [${f.durationMs}ms] ${f.method} ${f.url} \u2192 ${f.statusCode}`);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
return lines.join("\n");
|
|
373
483
|
}
|
|
374
484
|
}
|
|
375
485
|
} catch {
|
|
@@ -488,6 +598,9 @@ var init_get_findings = __esm({
|
|
|
488
598
|
return { content: [{ type: "text", text: `Invalid state "${state}". Use: open, fixing, resolved, stale, regressed.` }], isError: true };
|
|
489
599
|
}
|
|
490
600
|
let findings = await enrichFindings(client);
|
|
601
|
+
if (!state) {
|
|
602
|
+
findings = findings.filter((f) => f.aiStatus !== "wont_fix");
|
|
603
|
+
}
|
|
491
604
|
if (severity) {
|
|
492
605
|
findings = findings.filter((f) => f.severity === severity);
|
|
493
606
|
}
|
|
@@ -499,21 +612,25 @@ var init_get_findings = __esm({
|
|
|
499
612
|
if (findings.length === 0) {
|
|
500
613
|
return { content: [{ type: "text", text: "No findings detected. The application looks healthy." }] };
|
|
501
614
|
}
|
|
502
|
-
const lines = [
|
|
503
|
-
`
|
|
615
|
+
const lines = [
|
|
616
|
+
`Found ${findings.length} issue(s). Full context included below \u2014 no need to call get_request_detail separately.`,
|
|
617
|
+
`After fixing, call report_fixes ONCE with all results.
|
|
618
|
+
`
|
|
619
|
+
];
|
|
504
620
|
for (const f of findings) {
|
|
505
621
|
lines.push(`[${f.severity.toUpperCase()}] ${f.title}`);
|
|
506
622
|
lines.push(` ID: ${f.findingId}`);
|
|
507
623
|
lines.push(` Endpoint: ${f.endpoint}`);
|
|
508
624
|
lines.push(` Issue: ${f.description}`);
|
|
509
|
-
if (f.context)
|
|
625
|
+
if (f.context) {
|
|
626
|
+
for (const ctxLine of f.context.split("\n")) {
|
|
627
|
+
lines.push(` ${ctxLine}`);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
510
630
|
lines.push(` Fix: ${f.hint}`);
|
|
511
631
|
if (f.aiStatus === "fixed") {
|
|
512
632
|
lines.push(` AI Status: fixed (awaiting verification)`);
|
|
513
633
|
if (f.aiNotes) lines.push(` AI Notes: ${f.aiNotes}`);
|
|
514
|
-
} else if (f.aiStatus === "wont_fix") {
|
|
515
|
-
lines.push(` AI Status: won't fix`);
|
|
516
|
-
if (f.aiNotes) lines.push(` AI Notes: ${f.aiNotes}`);
|
|
517
634
|
}
|
|
518
635
|
lines.push("");
|
|
519
636
|
}
|
|
@@ -901,6 +1018,69 @@ var init_report_fix = __esm({
|
|
|
901
1018
|
}
|
|
902
1019
|
});
|
|
903
1020
|
|
|
1021
|
+
// src/mcp/tools/report-fixes.ts
|
|
1022
|
+
var reportFixes;
|
|
1023
|
+
var init_report_fixes = __esm({
|
|
1024
|
+
"src/mcp/tools/report-fixes.ts"() {
|
|
1025
|
+
"use strict";
|
|
1026
|
+
init_type_guards();
|
|
1027
|
+
reportFixes = {
|
|
1028
|
+
name: "report_fixes",
|
|
1029
|
+
description: "Report results for multiple findings in a single call. Use this instead of calling report_fix repeatedly \u2014 it's faster and requires only one confirmation. Pass a JSON array string where each item has finding_id, status ('fixed' or 'wont_fix'), and summary.",
|
|
1030
|
+
inputSchema: {
|
|
1031
|
+
type: "object",
|
|
1032
|
+
properties: {
|
|
1033
|
+
fixes: {
|
|
1034
|
+
type: "string",
|
|
1035
|
+
description: 'JSON array of fix reports. Example: [{"finding_id":"abc123","status":"fixed","summary":"Added input validation"}]'
|
|
1036
|
+
}
|
|
1037
|
+
},
|
|
1038
|
+
required: ["fixes"]
|
|
1039
|
+
},
|
|
1040
|
+
async handler(client, args) {
|
|
1041
|
+
let fixes;
|
|
1042
|
+
try {
|
|
1043
|
+
const raw = typeof args.fixes === "string" ? JSON.parse(args.fixes) : args.fixes;
|
|
1044
|
+
if (!Array.isArray(raw) || raw.length === 0) {
|
|
1045
|
+
return { content: [{ type: "text", text: "fixes must be a non-empty JSON array." }], isError: true };
|
|
1046
|
+
}
|
|
1047
|
+
fixes = raw.filter(
|
|
1048
|
+
(item) => typeof item === "object" && item !== null && typeof item.finding_id === "string" && typeof item.status === "string" && typeof item.summary === "string"
|
|
1049
|
+
);
|
|
1050
|
+
if (fixes.length === 0) {
|
|
1051
|
+
return { content: [{ type: "text", text: "No valid fix entries found. Each entry needs finding_id, status, and summary." }], isError: true };
|
|
1052
|
+
}
|
|
1053
|
+
} catch {
|
|
1054
|
+
return { content: [{ type: "text", text: "fixes must be valid JSON." }], isError: true };
|
|
1055
|
+
}
|
|
1056
|
+
const results = [];
|
|
1057
|
+
let errors = 0;
|
|
1058
|
+
for (const fix of fixes) {
|
|
1059
|
+
if (!fix.finding_id || !isValidAiFixStatus(fix.status) || !fix.summary) {
|
|
1060
|
+
results.push(`\u2717 Invalid entry: ${fix.finding_id || "missing ID"}`);
|
|
1061
|
+
errors++;
|
|
1062
|
+
continue;
|
|
1063
|
+
}
|
|
1064
|
+
const ok = await client.reportFix(fix.finding_id, fix.status, fix.summary);
|
|
1065
|
+
if (ok) {
|
|
1066
|
+
const label = fix.status === "fixed" ? "fixed" : "won't fix";
|
|
1067
|
+
results.push(`\u2713 ${fix.finding_id} \u2192 ${label}`);
|
|
1068
|
+
} else {
|
|
1069
|
+
results.push(`\u2717 ${fix.finding_id} \u2192 not found`);
|
|
1070
|
+
errors++;
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
const errorSuffix = errors > 0 ? ` (${errors} error${errors !== 1 ? "s" : ""})` : "";
|
|
1074
|
+
const summary = `Processed ${fixes.length} finding(s)${errorSuffix}. Dashboard updated.`;
|
|
1075
|
+
return {
|
|
1076
|
+
content: [{ type: "text", text: summary + "\n\n" + results.join("\n") }],
|
|
1077
|
+
...errors > 0 ? { isError: true } : {}
|
|
1078
|
+
};
|
|
1079
|
+
}
|
|
1080
|
+
};
|
|
1081
|
+
}
|
|
1082
|
+
});
|
|
1083
|
+
|
|
904
1084
|
// src/mcp/tools/index.ts
|
|
905
1085
|
function getToolDefinitions() {
|
|
906
1086
|
return [...TOOL_MAP.values()].map((t) => ({
|
|
@@ -930,8 +1110,9 @@ var init_tools = __esm({
|
|
|
930
1110
|
init_get_report();
|
|
931
1111
|
init_clear_findings();
|
|
932
1112
|
init_report_fix();
|
|
1113
|
+
init_report_fixes();
|
|
933
1114
|
TOOL_MAP = new Map(
|
|
934
|
-
[getFindings, getEndpoints, getRequestDetail, verifyFix, getReport, clearFindings, reportFix].map((t) => [t.name, t])
|
|
1115
|
+
[getFindings, getEndpoints, getRequestDetail, verifyFix, getReport, clearFindings, reportFix, reportFixes].map((t) => [t.name, t])
|
|
935
1116
|
);
|
|
936
1117
|
}
|
|
937
1118
|
});
|
|
@@ -941,7 +1122,7 @@ var SERVER_INSTRUCTIONS, PROMPTS, PROMPT_MESSAGES;
|
|
|
941
1122
|
var init_prompts = __esm({
|
|
942
1123
|
"src/mcp/prompts.ts"() {
|
|
943
1124
|
"use strict";
|
|
944
|
-
SERVER_INSTRUCTIONS = "Brakit is a dev tool that monitors your running app for security vulnerabilities, performance issues, and query problems. IMPORTANT: After fixing
|
|
1125
|
+
SERVER_INSTRUCTIONS = "Brakit is a dev tool that monitors your running app for security vulnerabilities, performance issues, and query problems. IMPORTANT: After fixing brakit findings, call report_fixes (batch) with all results at once instead of calling report_fix individually. This is faster and requires only one confirmation.";
|
|
945
1126
|
PROMPTS = [
|
|
946
1127
|
{
|
|
947
1128
|
name: "check-app",
|
|
@@ -953,8 +1134,8 @@ var init_prompts = __esm({
|
|
|
953
1134
|
}
|
|
954
1135
|
];
|
|
955
1136
|
PROMPT_MESSAGES = {
|
|
956
|
-
"check-app": "Check my running app for security and performance issues using brakit. First get all findings, then get the endpoint summary. For any critical or warning findings, get the request detail to understand the root cause. Give me a clear report of what's wrong and offer to fix each issue. After fixing
|
|
957
|
-
"fix-findings": "Get all open brakit findings. For each finding:\n1. Get the request detail to understand the exact issue\n2. Find the source code responsible and fix it\n3.
|
|
1137
|
+
"check-app": "Check my running app for security and performance issues using brakit. First get all findings, then get the endpoint summary. For any critical or warning findings, get the request detail to understand the root cause. Give me a clear report of what's wrong and offer to fix each issue. After fixing issues, call report_fixes once with all results.",
|
|
1138
|
+
"fix-findings": "Get all open brakit findings. For each finding:\n1. Get the request detail to understand the exact issue\n2. Find the source code responsible and fix it\n3. Track the finding_id, status ('fixed' or 'wont_fix'), and a brief summary\n\nAfter processing ALL findings, call report_fixes ONCE with the full array of results. Do not call report_fix individually for each finding.\n\nAfter reporting, ask me to re-trigger the endpoints so brakit can verify the fixes."
|
|
958
1139
|
};
|
|
959
1140
|
}
|
|
960
1141
|
});
|
|
@@ -1122,12 +1303,24 @@ import { createHash as createHash2 } from "crypto";
|
|
|
1122
1303
|
import { readFile as readFile3, readdir } from "fs/promises";
|
|
1123
1304
|
import { existsSync as existsSync4 } from "fs";
|
|
1124
1305
|
import { join as join2, relative } from "path";
|
|
1306
|
+
init_detection();
|
|
1125
1307
|
var FRAMEWORKS = [
|
|
1308
|
+
// Meta-frameworks first (they bundle Express/Vite internally)
|
|
1126
1309
|
{ name: "nextjs", dep: "next", devCmd: "next dev", bin: "next", defaultPort: 3e3, devArgs: ["dev", "--port"] },
|
|
1127
1310
|
{ name: "remix", dep: "@remix-run/dev", devCmd: "remix dev", bin: "remix", defaultPort: 3e3, devArgs: ["dev"] },
|
|
1128
1311
|
{ name: "nuxt", dep: "nuxt", devCmd: "nuxt dev", bin: "nuxt", defaultPort: 3e3, devArgs: ["dev", "--port"] },
|
|
1129
|
-
{ name: "
|
|
1130
|
-
{ name: "
|
|
1312
|
+
{ name: "astro", dep: "astro", devCmd: "astro dev", bin: "astro", defaultPort: 4321, devArgs: ["dev", "--port"] },
|
|
1313
|
+
{ name: "nestjs", dep: "@nestjs/core", devCmd: "nest start", bin: "nest", defaultPort: 3e3, devArgs: ["--watch"] },
|
|
1314
|
+
{ name: "adonis", dep: "@adonisjs/core", devCmd: "node ace serve", bin: "ace", defaultPort: 3333, devArgs: ["serve", "--watch"] },
|
|
1315
|
+
{ name: "sails", dep: "sails", devCmd: "sails lift", bin: "sails", defaultPort: 1337, devArgs: ["lift"] },
|
|
1316
|
+
// Server frameworks
|
|
1317
|
+
{ name: "hono", dep: "hono", devCmd: "node", bin: "node", defaultPort: 3e3 },
|
|
1318
|
+
{ name: "fastify", dep: "fastify", devCmd: "node", bin: "node", defaultPort: 3e3 },
|
|
1319
|
+
{ name: "koa", dep: "koa", devCmd: "node", bin: "node", defaultPort: 3e3 },
|
|
1320
|
+
{ name: "hapi", dep: "@hapi/hapi", devCmd: "node", bin: "node", defaultPort: 3e3 },
|
|
1321
|
+
{ name: "express", dep: "express", devCmd: "node", bin: "node", defaultPort: 3e3 },
|
|
1322
|
+
// Bundlers (last — likely used alongside a framework above)
|
|
1323
|
+
{ name: "vite", dep: "vite", devCmd: "vite", bin: "vite", defaultPort: 5173, devArgs: ["--port"] }
|
|
1131
1324
|
];
|
|
1132
1325
|
async function detectProject(rootDir) {
|
|
1133
1326
|
const pkgPath = join2(rootDir, "package.json");
|
|
@@ -1139,23 +1332,22 @@ async function detectProject(rootDir) {
|
|
|
1139
1332
|
const devCommand = matched?.devCmd ?? "";
|
|
1140
1333
|
const devBin = matched ? join2(rootDir, "node_modules", ".bin", matched.bin) : "";
|
|
1141
1334
|
const defaultPort = matched?.defaultPort ?? 3e3;
|
|
1142
|
-
const packageManager =
|
|
1335
|
+
const packageManager = detectPackageManager(rootDir);
|
|
1143
1336
|
return { framework, devCommand, devBin, defaultPort, packageManager };
|
|
1144
1337
|
}
|
|
1145
|
-
async function detectPackageManager(rootDir) {
|
|
1146
|
-
if (await fileExists(join2(rootDir, "bun.lockb"))) return "bun";
|
|
1147
|
-
if (await fileExists(join2(rootDir, "bun.lock"))) return "bun";
|
|
1148
|
-
if (await fileExists(join2(rootDir, "pnpm-lock.yaml"))) return "pnpm";
|
|
1149
|
-
if (await fileExists(join2(rootDir, "yarn.lock"))) return "yarn";
|
|
1150
|
-
if (await fileExists(join2(rootDir, "package-lock.json"))) return "npm";
|
|
1151
|
-
return "unknown";
|
|
1152
|
-
}
|
|
1153
1338
|
function detectFrameworkFromDeps(allDeps) {
|
|
1154
1339
|
for (const f of FRAMEWORKS) {
|
|
1155
1340
|
if (allDeps[f.dep]) return f.name;
|
|
1156
1341
|
}
|
|
1157
1342
|
return "unknown";
|
|
1158
1343
|
}
|
|
1344
|
+
function detectPackageManager(rootDir) {
|
|
1345
|
+
if (existsSync4(join2(rootDir, "bun.lockb")) || existsSync4(join2(rootDir, "bun.lock"))) return "bun";
|
|
1346
|
+
if (existsSync4(join2(rootDir, "pnpm-lock.yaml"))) return "pnpm";
|
|
1347
|
+
if (existsSync4(join2(rootDir, "yarn.lock"))) return "yarn";
|
|
1348
|
+
if (existsSync4(join2(rootDir, "package-lock.json"))) return "npm";
|
|
1349
|
+
return "unknown";
|
|
1350
|
+
}
|
|
1159
1351
|
var PYTHON_ENTRY_CANDIDATES = [
|
|
1160
1352
|
"app.py",
|
|
1161
1353
|
"main.py",
|
|
@@ -1910,7 +2102,7 @@ init_constants();
|
|
|
1910
2102
|
init_endpoint();
|
|
1911
2103
|
|
|
1912
2104
|
// src/index.ts
|
|
1913
|
-
var VERSION = "0.10.
|
|
2105
|
+
var VERSION = "0.10.2";
|
|
1914
2106
|
|
|
1915
2107
|
// src/cli/commands/install.ts
|
|
1916
2108
|
init_constants();
|
|
@@ -2251,10 +2443,6 @@ init_constants();
|
|
|
2251
2443
|
init_log();
|
|
2252
2444
|
init_type_guards();
|
|
2253
2445
|
|
|
2254
|
-
// src/telemetry/index.ts
|
|
2255
|
-
import { platform as platform2, release, arch } from "os";
|
|
2256
|
-
import { spawn } from "child_process";
|
|
2257
|
-
|
|
2258
2446
|
// src/telemetry/config.ts
|
|
2259
2447
|
init_features();
|
|
2260
2448
|
import { homedir as homedir2, platform } from "os";
|
|
@@ -2264,10 +2452,14 @@ import { randomUUID as randomUUID2 } from "crypto";
|
|
|
2264
2452
|
var IS_WINDOWS = platform() === "win32";
|
|
2265
2453
|
var CONFIG_DIR = join4(homedir2(), ".brakit");
|
|
2266
2454
|
var CONFIG_PATH = join4(CONFIG_DIR, "config.json");
|
|
2455
|
+
function isValidTelemetryConfig(value) {
|
|
2456
|
+
return typeof value === "object" && value !== null && typeof value.telemetry === "boolean" && typeof value.anonymousId === "string" && value.anonymousId.length > 0;
|
|
2457
|
+
}
|
|
2267
2458
|
function readConfig() {
|
|
2268
2459
|
try {
|
|
2269
2460
|
if (!existsSync6(CONFIG_PATH)) return null;
|
|
2270
|
-
|
|
2461
|
+
const parsed = JSON.parse(readFileSync4(CONFIG_PATH, "utf-8"));
|
|
2462
|
+
return isValidTelemetryConfig(parsed) ? parsed : null;
|
|
2271
2463
|
} catch {
|
|
2272
2464
|
return null;
|
|
2273
2465
|
}
|
|
@@ -2290,9 +2482,7 @@ function writeConfig(config) {
|
|
|
2290
2482
|
}
|
|
2291
2483
|
function getOrCreateConfig() {
|
|
2292
2484
|
const existing = readConfig();
|
|
2293
|
-
if (existing
|
|
2294
|
-
return existing;
|
|
2295
|
-
}
|
|
2485
|
+
if (existing) return existing;
|
|
2296
2486
|
const config = { telemetry: true, anonymousId: randomUUID2() };
|
|
2297
2487
|
writeConfig(config);
|
|
2298
2488
|
return config;
|
|
@@ -2302,16 +2492,19 @@ function isTelemetryEnabled() {
|
|
|
2302
2492
|
if (cachedEnabled !== null) return cachedEnabled;
|
|
2303
2493
|
const env = process.env.BRAKIT_TELEMETRY;
|
|
2304
2494
|
if (env !== void 0) {
|
|
2305
|
-
|
|
2495
|
+
const normalized = env.toLowerCase().trim();
|
|
2496
|
+
cachedEnabled = normalized !== "false" && normalized !== "0" && normalized !== "off";
|
|
2306
2497
|
return cachedEnabled;
|
|
2307
2498
|
}
|
|
2308
2499
|
cachedEnabled = readConfig()?.telemetry ?? true;
|
|
2309
2500
|
return cachedEnabled;
|
|
2310
2501
|
}
|
|
2311
2502
|
|
|
2312
|
-
// src/telemetry/
|
|
2313
|
-
|
|
2314
|
-
|
|
2503
|
+
// src/telemetry/transport.ts
|
|
2504
|
+
import { platform as platform2, release, arch } from "os";
|
|
2505
|
+
import { spawn } from "child_process";
|
|
2506
|
+
init_telemetry();
|
|
2507
|
+
init_log();
|
|
2315
2508
|
var POSTHOG_KEY = "phc_E9TwydCGnSfPLIUhNxChpeg32TSowjk31KiPhnLPP0x";
|
|
2316
2509
|
function commonProperties() {
|
|
2317
2510
|
return {
|
|
@@ -2325,7 +2518,7 @@ function commonProperties() {
|
|
|
2325
2518
|
};
|
|
2326
2519
|
}
|
|
2327
2520
|
function sendToPosthog(event, properties) {
|
|
2328
|
-
if (!isTelemetryEnabled()) return;
|
|
2521
|
+
if (!isTelemetryEnabled() || !POSTHOG_KEY) return;
|
|
2329
2522
|
const config = getOrCreateConfig();
|
|
2330
2523
|
const payload = {
|
|
2331
2524
|
api_key: POSTHOG_KEY,
|
|
@@ -2335,26 +2528,198 @@ function sendToPosthog(event, properties) {
|
|
|
2335
2528
|
properties: { ...commonProperties(), ...properties }
|
|
2336
2529
|
};
|
|
2337
2530
|
try {
|
|
2338
|
-
const
|
|
2531
|
+
const serializedPayload = JSON.stringify(payload);
|
|
2339
2532
|
const url = `${POSTHOG_HOST}${POSTHOG_CAPTURE_PATH}`;
|
|
2340
2533
|
const child = spawn(
|
|
2341
2534
|
process.execPath,
|
|
2342
2535
|
[
|
|
2343
2536
|
"-e",
|
|
2344
|
-
`fetch(${JSON.stringify(url)},{method:"POST",headers:{"content-type":"application/json"},body:${JSON.stringify(
|
|
2537
|
+
`fetch(${JSON.stringify(url)},{method:"POST",headers:{"content-type":"application/json"},body:${JSON.stringify(serializedPayload)},signal:AbortSignal.timeout(${POSTHOG_REQUEST_TIMEOUT_MS})}).catch(()=>{})`
|
|
2345
2538
|
],
|
|
2346
2539
|
{ detached: true, stdio: "ignore" }
|
|
2347
2540
|
);
|
|
2348
2541
|
child.unref();
|
|
2349
|
-
} catch {
|
|
2542
|
+
} catch (err) {
|
|
2543
|
+
brakitDebug(`telemetry send failed: ${err}`);
|
|
2350
2544
|
}
|
|
2351
2545
|
}
|
|
2352
2546
|
function trackEvent(event, properties) {
|
|
2353
|
-
sendToPosthog(event, { sdk:
|
|
2547
|
+
sendToPosthog(event, { sdk: TELEMETRY_SDK_NAME, ...properties });
|
|
2548
|
+
}
|
|
2549
|
+
|
|
2550
|
+
// src/telemetry/session.ts
|
|
2551
|
+
init_telemetry();
|
|
2552
|
+
var defaultTransport = { send: sendToPosthog, track: trackEvent };
|
|
2553
|
+
var Session = class {
|
|
2554
|
+
constructor(transport = defaultTransport) {
|
|
2555
|
+
// ── Setup phase ──
|
|
2556
|
+
this.startTime = 0;
|
|
2557
|
+
this.framework = "unknown";
|
|
2558
|
+
this.packageManager = "";
|
|
2559
|
+
this.isCustomCommand = false;
|
|
2560
|
+
this.adapters = [];
|
|
2561
|
+
this.setupDurationMs = 0;
|
|
2562
|
+
this.setupSucceeded = false;
|
|
2563
|
+
// ── Detection ──
|
|
2564
|
+
this.detectedDependencies = [];
|
|
2565
|
+
this.adaptersFailed = [];
|
|
2566
|
+
this.configFilesDetected = [];
|
|
2567
|
+
this.jsRuntime = "node";
|
|
2568
|
+
this.loadedPackages = [];
|
|
2569
|
+
// ── Python SDK ──
|
|
2570
|
+
this.pythonConnected = false;
|
|
2571
|
+
this.pythonFramework = "unknown";
|
|
2572
|
+
this.pythonAdapters = [];
|
|
2573
|
+
this.pythonVersion = "";
|
|
2574
|
+
// ── Runtime accumulation ──
|
|
2575
|
+
this.requestCount = 0;
|
|
2576
|
+
this.insightTypes = /* @__PURE__ */ new Set();
|
|
2577
|
+
this.rulesTriggered = /* @__PURE__ */ new Set();
|
|
2578
|
+
this.tabsViewed = /* @__PURE__ */ new Set();
|
|
2579
|
+
this.dashboardOpened = false;
|
|
2580
|
+
this.explainUsed = false;
|
|
2581
|
+
this.firstRequestAt = 0;
|
|
2582
|
+
this.dashboardOpenedAt = 0;
|
|
2583
|
+
this.exitReason = "unknown";
|
|
2584
|
+
this.transport = transport;
|
|
2585
|
+
}
|
|
2586
|
+
// ── Setup phase ──
|
|
2587
|
+
init(framework, packageManager, isCustomCommand, adapters) {
|
|
2588
|
+
getOrCreateConfig();
|
|
2589
|
+
this.startTime = Date.now();
|
|
2590
|
+
this.framework = framework;
|
|
2591
|
+
this.packageManager = packageManager;
|
|
2592
|
+
this.isCustomCommand = isCustomCommand;
|
|
2593
|
+
this.adapters = adapters;
|
|
2594
|
+
}
|
|
2595
|
+
recordSetup(detection, durationMs) {
|
|
2596
|
+
this.detectedDependencies = detection.detectedDependencies;
|
|
2597
|
+
this.adaptersFailed = detection.adaptersFailed;
|
|
2598
|
+
this.configFilesDetected = detection.configFilesDetected;
|
|
2599
|
+
this.jsRuntime = detection.jsRuntime;
|
|
2600
|
+
this.setupDurationMs = durationMs;
|
|
2601
|
+
this.setupSucceeded = true;
|
|
2602
|
+
}
|
|
2603
|
+
// ── Runtime events ──
|
|
2604
|
+
recordFirstRequest(loadedPackages) {
|
|
2605
|
+
if (!this.firstRequestAt) this.firstRequestAt = Date.now();
|
|
2606
|
+
this.loadedPackages = loadedPackages;
|
|
2607
|
+
}
|
|
2608
|
+
recordPythonStack(info) {
|
|
2609
|
+
this.pythonConnected = true;
|
|
2610
|
+
this.pythonFramework = info.framework;
|
|
2611
|
+
this.pythonAdapters = info.adapters;
|
|
2612
|
+
this.pythonVersion = info.pythonVersion;
|
|
2613
|
+
}
|
|
2614
|
+
recordDashboardOpened() {
|
|
2615
|
+
if (this.dashboardOpened) return;
|
|
2616
|
+
this.dashboardOpened = true;
|
|
2617
|
+
this.dashboardOpenedAt = Date.now();
|
|
2618
|
+
this.transport.track(TELEMETRY_EVENT_DASHBOARD_VIEWED, {
|
|
2619
|
+
time_to_dashboard_ms: this.startTime > 0 ? Date.now() - this.startTime : null,
|
|
2620
|
+
request_count_at_open: this.requestCount
|
|
2621
|
+
});
|
|
2622
|
+
}
|
|
2623
|
+
recordTabViewed(tab) {
|
|
2624
|
+
this.tabsViewed.add(tab);
|
|
2625
|
+
}
|
|
2626
|
+
recordExplainUsed() {
|
|
2627
|
+
this.explainUsed = true;
|
|
2628
|
+
}
|
|
2629
|
+
recordExitReason(reason) {
|
|
2630
|
+
if (this.exitReason === "unknown") this.exitReason = reason;
|
|
2631
|
+
}
|
|
2632
|
+
// ── Pre-flush snapshot from services ──
|
|
2633
|
+
recordCounts(requestCount, insightTypes, rulesTriggered) {
|
|
2634
|
+
this.requestCount = requestCount;
|
|
2635
|
+
for (const t of insightTypes) this.insightTypes.add(t);
|
|
2636
|
+
for (const r of rulesTriggered) this.rulesTriggered.add(r);
|
|
2637
|
+
}
|
|
2638
|
+
// ── Serialization ──
|
|
2639
|
+
/** Build the full PostHog session payload. Single source of truth for all fields. */
|
|
2640
|
+
toPostHogPayload(services) {
|
|
2641
|
+
const metricsStore = services.metricsStore;
|
|
2642
|
+
const analysisEngine = services.analysisEngine;
|
|
2643
|
+
const live = metricsStore.getLiveEndpoints();
|
|
2644
|
+
const insights = analysisEngine.getInsights();
|
|
2645
|
+
const findings = analysisEngine.getFindings();
|
|
2646
|
+
let totalRequests = 0;
|
|
2647
|
+
let totalDuration = 0;
|
|
2648
|
+
let slowestP95 = 0;
|
|
2649
|
+
for (const ep of live) {
|
|
2650
|
+
totalRequests += ep.summary.totalRequests;
|
|
2651
|
+
totalDuration += ep.summary.p95Ms * ep.summary.totalRequests;
|
|
2652
|
+
if (ep.summary.p95Ms > slowestP95) slowestP95 = ep.summary.p95Ms;
|
|
2653
|
+
}
|
|
2654
|
+
const now = Date.now();
|
|
2655
|
+
return {
|
|
2656
|
+
sdk: TELEMETRY_SDK_NAME,
|
|
2657
|
+
// Stack detection
|
|
2658
|
+
framework: this.framework,
|
|
2659
|
+
package_manager: this.packageManager,
|
|
2660
|
+
js_runtime: this.jsRuntime,
|
|
2661
|
+
framework_detection_candidates: this.detectedDependencies,
|
|
2662
|
+
config_files_detected: this.configFilesDetected,
|
|
2663
|
+
loaded_packages: this.loadedPackages,
|
|
2664
|
+
loaded_package_count: this.loadedPackages.length,
|
|
2665
|
+
adapters_detected: this.adapters,
|
|
2666
|
+
adapters_failed: this.adaptersFailed,
|
|
2667
|
+
// Python SDK
|
|
2668
|
+
python_connected: this.pythonConnected,
|
|
2669
|
+
python_framework: this.pythonFramework,
|
|
2670
|
+
python_adapters: this.pythonAdapters,
|
|
2671
|
+
python_version: this.pythonVersion,
|
|
2672
|
+
// Session metadata
|
|
2673
|
+
is_custom_command: this.isCustomCommand,
|
|
2674
|
+
first_session: readConfig() === null,
|
|
2675
|
+
setup_succeeded: this.setupSucceeded,
|
|
2676
|
+
setup_duration_ms: this.setupDurationMs,
|
|
2677
|
+
session_duration_s: Math.ceil((now - this.startTime) / 1e3),
|
|
2678
|
+
session_duration_ms: now - this.startTime,
|
|
2679
|
+
exit_reason: this.exitReason,
|
|
2680
|
+
// Usage
|
|
2681
|
+
request_count: this.requestCount,
|
|
2682
|
+
error_count: services.errorStore.getAll().length,
|
|
2683
|
+
query_count: services.queryStore.getAll().length,
|
|
2684
|
+
fetch_count: services.fetchStore.getAll().length,
|
|
2685
|
+
endpoint_count: live.length,
|
|
2686
|
+
avg_duration_ms: totalRequests > 0 ? Math.round(totalDuration / totalRequests) : 0,
|
|
2687
|
+
slowest_endpoint_bucket: speedBucket(slowestP95),
|
|
2688
|
+
// Analysis
|
|
2689
|
+
insight_count: insights.length,
|
|
2690
|
+
finding_count: findings.length,
|
|
2691
|
+
insight_types: [...this.insightTypes],
|
|
2692
|
+
rules_triggered: [...this.rulesTriggered],
|
|
2693
|
+
// Dashboard engagement
|
|
2694
|
+
tabs_viewed: [...this.tabsViewed],
|
|
2695
|
+
dashboard_opened: this.dashboardOpened,
|
|
2696
|
+
explain_used: this.explainUsed,
|
|
2697
|
+
// Timing
|
|
2698
|
+
time_to_first_request_ms: this.firstRequestAt ? this.firstRequestAt - this.startTime : null,
|
|
2699
|
+
time_to_dashboard_ms: this.dashboardOpenedAt ? this.dashboardOpenedAt - this.startTime : null
|
|
2700
|
+
};
|
|
2701
|
+
}
|
|
2702
|
+
flush(services) {
|
|
2703
|
+
this.transport.send(TELEMETRY_EVENT_SESSION, this.toPostHogPayload(services));
|
|
2704
|
+
getOrCreateConfig();
|
|
2705
|
+
}
|
|
2706
|
+
};
|
|
2707
|
+
function speedBucket(ms) {
|
|
2708
|
+
if (ms === 0) return "none";
|
|
2709
|
+
const t = SPEED_BUCKET_THRESHOLDS;
|
|
2710
|
+
if (ms < t[0]) return `<${t[0]}ms`;
|
|
2711
|
+
for (let i = 1; i < t.length; i++) {
|
|
2712
|
+
if (ms < t[i]) return `${t[i - 1]}-${t[i]}ms`;
|
|
2713
|
+
}
|
|
2714
|
+
return `>${t[t.length - 1]}ms`;
|
|
2354
2715
|
}
|
|
2355
2716
|
|
|
2717
|
+
// src/telemetry/index.ts
|
|
2718
|
+
init_telemetry();
|
|
2719
|
+
var session = new Session();
|
|
2720
|
+
|
|
2356
2721
|
// src/cli/commands/uninstall.ts
|
|
2357
|
-
|
|
2722
|
+
init_telemetry();
|
|
2358
2723
|
var PREPENDED_FILES = [
|
|
2359
2724
|
"app/entry.server.tsx",
|
|
2360
2725
|
"app/entry.server.ts",
|
|
@@ -2613,7 +2978,7 @@ async function clearBuildCaches(rootDir) {
|
|
|
2613
2978
|
}
|
|
2614
2979
|
|
|
2615
2980
|
// bin/brakit.ts
|
|
2616
|
-
|
|
2981
|
+
init_telemetry();
|
|
2617
2982
|
var sub = process.argv[2];
|
|
2618
2983
|
var command = sub === "uninstall" ? "uninstall" : sub === "mcp" ? "mcp" : "install";
|
|
2619
2984
|
var cwd = process.cwd();
|