brakit 0.7.6 → 0.8.0
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 +10 -3
- package/dist/api.d.ts +47 -2
- package/dist/api.js +250 -47
- package/dist/bin/brakit.js +1119 -29
- package/dist/mcp/server.d.ts +3 -0
- package/dist/mcp/server.js +737 -0
- package/dist/runtime/index.js +270 -13
- package/package.json +5 -1
package/dist/runtime/index.js
CHANGED
|
@@ -9,7 +9,7 @@ var __export = (target, all) => {
|
|
|
9
9
|
};
|
|
10
10
|
|
|
11
11
|
// src/constants/routes.ts
|
|
12
|
-
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;
|
|
12
|
+
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;
|
|
13
13
|
var init_routes = __esm({
|
|
14
14
|
"src/constants/routes.ts"() {
|
|
15
15
|
"use strict";
|
|
@@ -29,6 +29,7 @@ var init_routes = __esm({
|
|
|
29
29
|
DASHBOARD_API_INSIGHTS = "/__brakit/api/insights";
|
|
30
30
|
DASHBOARD_API_SECURITY = "/__brakit/api/security";
|
|
31
31
|
DASHBOARD_API_TAB = "/__brakit/api/tab";
|
|
32
|
+
DASHBOARD_API_FINDINGS = "/__brakit/api/findings";
|
|
32
33
|
}
|
|
33
34
|
});
|
|
34
35
|
|
|
@@ -93,7 +94,7 @@ var init_transport = __esm({
|
|
|
93
94
|
});
|
|
94
95
|
|
|
95
96
|
// src/constants/metrics.ts
|
|
96
|
-
var METRICS_DIR, METRICS_FILE, METRICS_FLUSH_INTERVAL_MS, METRICS_MAX_SESSIONS, METRICS_MAX_DATA_POINTS;
|
|
97
|
+
var METRICS_DIR, METRICS_FILE, METRICS_FLUSH_INTERVAL_MS, METRICS_MAX_SESSIONS, METRICS_MAX_DATA_POINTS, PORT_FILE, FINDINGS_FILE, FINDINGS_FLUSH_INTERVAL_MS;
|
|
97
98
|
var init_metrics = __esm({
|
|
98
99
|
"src/constants/metrics.ts"() {
|
|
99
100
|
"use strict";
|
|
@@ -102,6 +103,9 @@ var init_metrics = __esm({
|
|
|
102
103
|
METRICS_FLUSH_INTERVAL_MS = 3e4;
|
|
103
104
|
METRICS_MAX_SESSIONS = 50;
|
|
104
105
|
METRICS_MAX_DATA_POINTS = 200;
|
|
106
|
+
PORT_FILE = ".brakit/port";
|
|
107
|
+
FINDINGS_FILE = ".brakit/findings.json";
|
|
108
|
+
FINDINGS_FLUSH_INTERVAL_MS = 1e4;
|
|
105
109
|
}
|
|
106
110
|
});
|
|
107
111
|
|
|
@@ -148,6 +152,13 @@ var init_network = __esm({
|
|
|
148
152
|
}
|
|
149
153
|
});
|
|
150
154
|
|
|
155
|
+
// src/constants/mcp.ts
|
|
156
|
+
var init_mcp = __esm({
|
|
157
|
+
"src/constants/mcp.ts"() {
|
|
158
|
+
"use strict";
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
151
162
|
// src/constants/index.ts
|
|
152
163
|
var init_constants = __esm({
|
|
153
164
|
"src/constants/index.ts"() {
|
|
@@ -159,6 +170,7 @@ var init_constants = __esm({
|
|
|
159
170
|
init_metrics();
|
|
160
171
|
init_headers();
|
|
161
172
|
init_network();
|
|
173
|
+
init_mcp();
|
|
162
174
|
}
|
|
163
175
|
});
|
|
164
176
|
|
|
@@ -2055,6 +2067,33 @@ var init_insights = __esm({
|
|
|
2055
2067
|
}
|
|
2056
2068
|
});
|
|
2057
2069
|
|
|
2070
|
+
// src/dashboard/api/findings.ts
|
|
2071
|
+
function createFindingsHandler(findingStore) {
|
|
2072
|
+
return (req, res) => {
|
|
2073
|
+
if (!requireGet(req, res)) return;
|
|
2074
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
2075
|
+
const stateParam = url.searchParams.get("state");
|
|
2076
|
+
let findings;
|
|
2077
|
+
if (stateParam && VALID_STATES.has(stateParam)) {
|
|
2078
|
+
findings = findingStore.getByState(stateParam);
|
|
2079
|
+
} else {
|
|
2080
|
+
findings = findingStore.getAll();
|
|
2081
|
+
}
|
|
2082
|
+
sendJson(req, res, 200, {
|
|
2083
|
+
total: findings.length,
|
|
2084
|
+
findings
|
|
2085
|
+
});
|
|
2086
|
+
};
|
|
2087
|
+
}
|
|
2088
|
+
var VALID_STATES;
|
|
2089
|
+
var init_findings = __esm({
|
|
2090
|
+
"src/dashboard/api/findings.ts"() {
|
|
2091
|
+
"use strict";
|
|
2092
|
+
init_shared2();
|
|
2093
|
+
VALID_STATES = /* @__PURE__ */ new Set(["open", "fixing", "resolved"]);
|
|
2094
|
+
}
|
|
2095
|
+
});
|
|
2096
|
+
|
|
2058
2097
|
// src/dashboard/sse.ts
|
|
2059
2098
|
function createSSEHandler(engine) {
|
|
2060
2099
|
return (req, res) => {
|
|
@@ -2687,6 +2726,197 @@ var init_styles = __esm({
|
|
|
2687
2726
|
}
|
|
2688
2727
|
});
|
|
2689
2728
|
|
|
2729
|
+
// src/store/finding-id.ts
|
|
2730
|
+
import { createHash } from "crypto";
|
|
2731
|
+
function computeFindingId(finding) {
|
|
2732
|
+
const key = `${finding.rule}:${finding.endpoint}:${finding.desc}`;
|
|
2733
|
+
return createHash("sha256").update(key).digest("hex").slice(0, 16);
|
|
2734
|
+
}
|
|
2735
|
+
var init_finding_id = __esm({
|
|
2736
|
+
"src/store/finding-id.ts"() {
|
|
2737
|
+
"use strict";
|
|
2738
|
+
}
|
|
2739
|
+
});
|
|
2740
|
+
|
|
2741
|
+
// src/store/finding-store.ts
|
|
2742
|
+
import {
|
|
2743
|
+
readFileSync as readFileSync3,
|
|
2744
|
+
writeFileSync as writeFileSync3,
|
|
2745
|
+
existsSync as existsSync3,
|
|
2746
|
+
mkdirSync as mkdirSync3,
|
|
2747
|
+
renameSync as renameSync2
|
|
2748
|
+
} from "fs";
|
|
2749
|
+
import { writeFile as writeFile3, mkdir as mkdir2, rename as rename2 } from "fs/promises";
|
|
2750
|
+
import { resolve as resolve3 } from "path";
|
|
2751
|
+
var FindingStore;
|
|
2752
|
+
var init_finding_store = __esm({
|
|
2753
|
+
"src/store/finding-store.ts"() {
|
|
2754
|
+
"use strict";
|
|
2755
|
+
init_constants();
|
|
2756
|
+
init_fs();
|
|
2757
|
+
init_finding_id();
|
|
2758
|
+
FindingStore = class {
|
|
2759
|
+
constructor(rootDir) {
|
|
2760
|
+
this.rootDir = rootDir;
|
|
2761
|
+
this.metricsDir = resolve3(rootDir, METRICS_DIR);
|
|
2762
|
+
this.findingsPath = resolve3(rootDir, FINDINGS_FILE);
|
|
2763
|
+
this.tmpPath = this.findingsPath + ".tmp";
|
|
2764
|
+
this.load();
|
|
2765
|
+
}
|
|
2766
|
+
findings = /* @__PURE__ */ new Map();
|
|
2767
|
+
flushTimer = null;
|
|
2768
|
+
dirty = false;
|
|
2769
|
+
writing = false;
|
|
2770
|
+
findingsPath;
|
|
2771
|
+
tmpPath;
|
|
2772
|
+
metricsDir;
|
|
2773
|
+
start() {
|
|
2774
|
+
this.flushTimer = setInterval(
|
|
2775
|
+
() => this.flush(),
|
|
2776
|
+
FINDINGS_FLUSH_INTERVAL_MS
|
|
2777
|
+
);
|
|
2778
|
+
this.flushTimer.unref();
|
|
2779
|
+
}
|
|
2780
|
+
stop() {
|
|
2781
|
+
if (this.flushTimer) {
|
|
2782
|
+
clearInterval(this.flushTimer);
|
|
2783
|
+
this.flushTimer = null;
|
|
2784
|
+
}
|
|
2785
|
+
this.flushSync();
|
|
2786
|
+
}
|
|
2787
|
+
upsert(finding, source) {
|
|
2788
|
+
const id = computeFindingId(finding);
|
|
2789
|
+
const existing = this.findings.get(id);
|
|
2790
|
+
const now = Date.now();
|
|
2791
|
+
if (existing) {
|
|
2792
|
+
existing.lastSeenAt = now;
|
|
2793
|
+
existing.occurrences++;
|
|
2794
|
+
existing.finding = finding;
|
|
2795
|
+
if (existing.state === "resolved") {
|
|
2796
|
+
existing.state = "open";
|
|
2797
|
+
existing.resolvedAt = null;
|
|
2798
|
+
}
|
|
2799
|
+
this.dirty = true;
|
|
2800
|
+
return existing;
|
|
2801
|
+
}
|
|
2802
|
+
const stateful = {
|
|
2803
|
+
findingId: id,
|
|
2804
|
+
state: "open",
|
|
2805
|
+
source,
|
|
2806
|
+
finding,
|
|
2807
|
+
firstSeenAt: now,
|
|
2808
|
+
lastSeenAt: now,
|
|
2809
|
+
resolvedAt: null,
|
|
2810
|
+
occurrences: 1
|
|
2811
|
+
};
|
|
2812
|
+
this.findings.set(id, stateful);
|
|
2813
|
+
this.dirty = true;
|
|
2814
|
+
return stateful;
|
|
2815
|
+
}
|
|
2816
|
+
transition(findingId, state) {
|
|
2817
|
+
const finding = this.findings.get(findingId);
|
|
2818
|
+
if (!finding) return false;
|
|
2819
|
+
finding.state = state;
|
|
2820
|
+
if (state === "resolved") {
|
|
2821
|
+
finding.resolvedAt = Date.now();
|
|
2822
|
+
}
|
|
2823
|
+
this.dirty = true;
|
|
2824
|
+
return true;
|
|
2825
|
+
}
|
|
2826
|
+
reconcilePassive(currentFindings) {
|
|
2827
|
+
const currentIds = new Set(currentFindings.map(computeFindingId));
|
|
2828
|
+
for (const [id, stateful] of this.findings) {
|
|
2829
|
+
if (stateful.source === "passive" && stateful.state === "open" && !currentIds.has(id)) {
|
|
2830
|
+
stateful.state = "resolved";
|
|
2831
|
+
stateful.resolvedAt = Date.now();
|
|
2832
|
+
this.dirty = true;
|
|
2833
|
+
}
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
getAll() {
|
|
2837
|
+
return [...this.findings.values()];
|
|
2838
|
+
}
|
|
2839
|
+
getByState(state) {
|
|
2840
|
+
return [...this.findings.values()].filter((f) => f.state === state);
|
|
2841
|
+
}
|
|
2842
|
+
get(findingId) {
|
|
2843
|
+
return this.findings.get(findingId);
|
|
2844
|
+
}
|
|
2845
|
+
clear() {
|
|
2846
|
+
this.findings.clear();
|
|
2847
|
+
this.dirty = true;
|
|
2848
|
+
}
|
|
2849
|
+
load() {
|
|
2850
|
+
try {
|
|
2851
|
+
if (existsSync3(this.findingsPath)) {
|
|
2852
|
+
const raw = readFileSync3(this.findingsPath, "utf-8");
|
|
2853
|
+
const parsed = JSON.parse(raw);
|
|
2854
|
+
if (parsed?.version === 1 && Array.isArray(parsed.findings)) {
|
|
2855
|
+
for (const f of parsed.findings) {
|
|
2856
|
+
this.findings.set(f.findingId, f);
|
|
2857
|
+
}
|
|
2858
|
+
}
|
|
2859
|
+
}
|
|
2860
|
+
} catch {
|
|
2861
|
+
}
|
|
2862
|
+
}
|
|
2863
|
+
flush() {
|
|
2864
|
+
if (!this.dirty) return;
|
|
2865
|
+
this.writeAsync();
|
|
2866
|
+
}
|
|
2867
|
+
flushSync() {
|
|
2868
|
+
if (!this.dirty) return;
|
|
2869
|
+
try {
|
|
2870
|
+
this.ensureDir();
|
|
2871
|
+
const data = {
|
|
2872
|
+
version: 1,
|
|
2873
|
+
findings: [...this.findings.values()]
|
|
2874
|
+
};
|
|
2875
|
+
writeFileSync3(this.tmpPath, JSON.stringify(data));
|
|
2876
|
+
renameSync2(this.tmpPath, this.findingsPath);
|
|
2877
|
+
this.dirty = false;
|
|
2878
|
+
} catch (err) {
|
|
2879
|
+
process.stderr.write(
|
|
2880
|
+
`[brakit] failed to save findings: ${err.message}
|
|
2881
|
+
`
|
|
2882
|
+
);
|
|
2883
|
+
}
|
|
2884
|
+
}
|
|
2885
|
+
async writeAsync() {
|
|
2886
|
+
if (this.writing) return;
|
|
2887
|
+
this.writing = true;
|
|
2888
|
+
try {
|
|
2889
|
+
if (!existsSync3(this.metricsDir)) {
|
|
2890
|
+
await mkdir2(this.metricsDir, { recursive: true });
|
|
2891
|
+
ensureGitignore(this.metricsDir, METRICS_DIR);
|
|
2892
|
+
}
|
|
2893
|
+
const data = {
|
|
2894
|
+
version: 1,
|
|
2895
|
+
findings: [...this.findings.values()]
|
|
2896
|
+
};
|
|
2897
|
+
await writeFile3(this.tmpPath, JSON.stringify(data));
|
|
2898
|
+
await rename2(this.tmpPath, this.findingsPath);
|
|
2899
|
+
this.dirty = false;
|
|
2900
|
+
} catch (err) {
|
|
2901
|
+
process.stderr.write(
|
|
2902
|
+
`[brakit] failed to save findings: ${err.message}
|
|
2903
|
+
`
|
|
2904
|
+
);
|
|
2905
|
+
} finally {
|
|
2906
|
+
this.writing = false;
|
|
2907
|
+
if (this.dirty) this.writeAsync();
|
|
2908
|
+
}
|
|
2909
|
+
}
|
|
2910
|
+
ensureDir() {
|
|
2911
|
+
if (!existsSync3(this.metricsDir)) {
|
|
2912
|
+
mkdirSync3(this.metricsDir, { recursive: true });
|
|
2913
|
+
ensureGitignore(this.metricsDir, METRICS_DIR);
|
|
2914
|
+
}
|
|
2915
|
+
}
|
|
2916
|
+
};
|
|
2917
|
+
}
|
|
2918
|
+
});
|
|
2919
|
+
|
|
2690
2920
|
// src/detect/project.ts
|
|
2691
2921
|
import { readFile as readFile2 } from "fs/promises";
|
|
2692
2922
|
import { join } from "path";
|
|
@@ -4112,8 +4342,9 @@ var init_engine = __esm({
|
|
|
4112
4342
|
init_rules();
|
|
4113
4343
|
init_insights3();
|
|
4114
4344
|
AnalysisEngine = class {
|
|
4115
|
-
constructor(metricsStore, debounceMs = 300) {
|
|
4345
|
+
constructor(metricsStore, findingStore, debounceMs = 300) {
|
|
4116
4346
|
this.metricsStore = metricsStore;
|
|
4347
|
+
this.findingStore = findingStore;
|
|
4117
4348
|
this.debounceMs = debounceMs;
|
|
4118
4349
|
this.scanner = createDefaultScanner();
|
|
4119
4350
|
this.boundRequestListener = () => this.scheduleRecompute();
|
|
@@ -4174,6 +4405,12 @@ var init_engine = __esm({
|
|
|
4174
4405
|
const fetches = defaultFetchStore.getAll();
|
|
4175
4406
|
const flows = groupRequestsIntoFlows(requests);
|
|
4176
4407
|
this.cachedFindings = this.scanner.scan({ requests, logs });
|
|
4408
|
+
if (this.findingStore) {
|
|
4409
|
+
for (const finding of this.cachedFindings) {
|
|
4410
|
+
this.findingStore.upsert(finding, "passive");
|
|
4411
|
+
}
|
|
4412
|
+
this.findingStore.reconcilePassive(this.cachedFindings);
|
|
4413
|
+
}
|
|
4177
4414
|
this.cachedInsights = computeInsights({
|
|
4178
4415
|
requests,
|
|
4179
4416
|
queries,
|
|
@@ -4199,13 +4436,14 @@ var VERSION;
|
|
|
4199
4436
|
var init_src = __esm({
|
|
4200
4437
|
"src/index.ts"() {
|
|
4201
4438
|
"use strict";
|
|
4439
|
+
init_finding_store();
|
|
4202
4440
|
init_project();
|
|
4203
4441
|
init_adapter_registry();
|
|
4204
4442
|
init_rules();
|
|
4205
4443
|
init_engine();
|
|
4206
4444
|
init_insights3();
|
|
4207
4445
|
init_insights2();
|
|
4208
|
-
VERSION = "0.
|
|
4446
|
+
VERSION = "0.8.0";
|
|
4209
4447
|
}
|
|
4210
4448
|
});
|
|
4211
4449
|
|
|
@@ -6639,12 +6877,12 @@ var init_page = __esm({
|
|
|
6639
6877
|
// src/telemetry/config.ts
|
|
6640
6878
|
import { homedir } from "os";
|
|
6641
6879
|
import { join as join2 } from "path";
|
|
6642
|
-
import { existsSync as
|
|
6880
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4 } from "fs";
|
|
6643
6881
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
6644
6882
|
function readConfig() {
|
|
6645
6883
|
try {
|
|
6646
|
-
if (!
|
|
6647
|
-
return JSON.parse(
|
|
6884
|
+
if (!existsSync4(CONFIG_PATH)) return null;
|
|
6885
|
+
return JSON.parse(readFileSync4(CONFIG_PATH, "utf-8"));
|
|
6648
6886
|
} catch {
|
|
6649
6887
|
return null;
|
|
6650
6888
|
}
|
|
@@ -6707,6 +6945,9 @@ function createDashboardHandler(deps) {
|
|
|
6707
6945
|
routes[DASHBOARD_API_INSIGHTS] = createInsightsHandler(deps.analysisEngine);
|
|
6708
6946
|
routes[DASHBOARD_API_SECURITY] = createSecurityHandler(deps.analysisEngine);
|
|
6709
6947
|
}
|
|
6948
|
+
if (deps.findingStore) {
|
|
6949
|
+
routes[DASHBOARD_API_FINDINGS] = createFindingsHandler(deps.findingStore);
|
|
6950
|
+
}
|
|
6710
6951
|
routes[DASHBOARD_API_TAB] = (req, res) => {
|
|
6711
6952
|
const raw = (req.url ?? "").split("tab=")[1];
|
|
6712
6953
|
if (raw) {
|
|
@@ -6739,6 +6980,7 @@ var init_router = __esm({
|
|
|
6739
6980
|
init_constants();
|
|
6740
6981
|
init_api();
|
|
6741
6982
|
init_insights();
|
|
6983
|
+
init_findings();
|
|
6742
6984
|
init_sse();
|
|
6743
6985
|
init_page();
|
|
6744
6986
|
init_telemetry();
|
|
@@ -6779,12 +7021,11 @@ function colorTitle(severity, text) {
|
|
|
6779
7021
|
function truncate(s, max = 80) {
|
|
6780
7022
|
return s.length <= max ? s : s.slice(0, max - 1) + "\u2026";
|
|
6781
7023
|
}
|
|
6782
|
-
function formatConsoleLine(insight,
|
|
7024
|
+
function formatConsoleLine(insight, suffix) {
|
|
6783
7025
|
const icon = severityIcon(insight.severity);
|
|
6784
7026
|
const title = colorTitle(insight.severity, insight.title);
|
|
6785
7027
|
const desc = pc.dim(truncate(insight.desc) + (suffix ?? ""));
|
|
6786
|
-
|
|
6787
|
-
let line = ` ${icon} ${title} \u2014 ${desc} ${link}`;
|
|
7028
|
+
let line = ` ${icon} ${title} \u2014 ${desc}`;
|
|
6788
7029
|
if (insight.detail) {
|
|
6789
7030
|
line += `
|
|
6790
7031
|
${pc.dim("\u2514 " + insight.detail)}`;
|
|
@@ -6810,11 +7051,13 @@ function createConsoleInsightListener(proxyPort, metricsStore) {
|
|
|
6810
7051
|
suffix = ` (\u2191 from ${prev.p95DurationMs < 1e3 ? prev.p95DurationMs + "ms" : (prev.p95DurationMs / 1e3).toFixed(1) + "s"})`;
|
|
6811
7052
|
}
|
|
6812
7053
|
}
|
|
6813
|
-
lines.push(formatConsoleLine(insight,
|
|
7054
|
+
lines.push(formatConsoleLine(insight, suffix));
|
|
6814
7055
|
}
|
|
6815
7056
|
if (lines.length > 0) {
|
|
6816
7057
|
print("");
|
|
6817
7058
|
for (const line of lines) print(line);
|
|
7059
|
+
print("");
|
|
7060
|
+
print(` ${pc.magenta(pc.bold("brakit"))} ${pc.dim("\u2192")} ${pc.dim("Dashboard:")} ${pc.underline(`http://${dashUrl}`)} ${pc.dim("or ask your AI:")} ${pc.bold('"Fix brakit findings"')}`);
|
|
6818
7061
|
}
|
|
6819
7062
|
};
|
|
6820
7063
|
}
|
|
@@ -7050,6 +7293,8 @@ var setup_exports = {};
|
|
|
7050
7293
|
__export(setup_exports, {
|
|
7051
7294
|
setup: () => setup
|
|
7052
7295
|
});
|
|
7296
|
+
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, existsSync as existsSync5, unlinkSync as unlinkSync2 } from "fs";
|
|
7297
|
+
import { resolve as resolve4 } from "path";
|
|
7053
7298
|
function setup() {
|
|
7054
7299
|
if (initialized) return;
|
|
7055
7300
|
initialized = true;
|
|
@@ -7062,7 +7307,9 @@ function setup() {
|
|
|
7062
7307
|
const cwd = process.cwd();
|
|
7063
7308
|
const metricsStore = new MetricsStore(new FileMetricsPersistence(cwd));
|
|
7064
7309
|
metricsStore.start();
|
|
7065
|
-
const
|
|
7310
|
+
const findingStore = new FindingStore(cwd);
|
|
7311
|
+
findingStore.start();
|
|
7312
|
+
const analysisEngine = new AnalysisEngine(metricsStore, findingStore);
|
|
7066
7313
|
analysisEngine.start();
|
|
7067
7314
|
const config = {
|
|
7068
7315
|
proxyPort: 0,
|
|
@@ -7070,7 +7317,7 @@ function setup() {
|
|
|
7070
7317
|
showStatic: false,
|
|
7071
7318
|
maxBodyCapture: DEFAULT_MAX_BODY_CAPTURE
|
|
7072
7319
|
};
|
|
7073
|
-
const handleDashboard = createDashboardHandler({ metricsStore, analysisEngine });
|
|
7320
|
+
const handleDashboard = createDashboardHandler({ metricsStore, analysisEngine, findingStore });
|
|
7074
7321
|
onRequest((req) => {
|
|
7075
7322
|
const queries = defaultQueryStore.getByRequest(req.id);
|
|
7076
7323
|
const fetches = defaultFetchStore.getByRequest(req.id);
|
|
@@ -7084,6 +7331,9 @@ function setup() {
|
|
|
7084
7331
|
handleDashboard,
|
|
7085
7332
|
config,
|
|
7086
7333
|
onFirstRequest(port) {
|
|
7334
|
+
const dir = resolve4(cwd, METRICS_DIR);
|
|
7335
|
+
if (!existsSync5(dir)) mkdirSync5(dir, { recursive: true });
|
|
7336
|
+
writeFileSync5(resolve4(cwd, PORT_FILE), String(port));
|
|
7087
7337
|
analysisEngine.onUpdate(createConsoleInsightListener(port, metricsStore));
|
|
7088
7338
|
process.stdout.write(` brakit v${VERSION} \u2014 http://localhost:${port}${DASHBOARD_PREFIX}
|
|
7089
7339
|
`);
|
|
@@ -7092,7 +7342,13 @@ function setup() {
|
|
|
7092
7342
|
health.setTeardown(() => {
|
|
7093
7343
|
uninstallInterceptor();
|
|
7094
7344
|
analysisEngine.stop();
|
|
7345
|
+
findingStore.stop();
|
|
7095
7346
|
metricsStore.stop();
|
|
7347
|
+
try {
|
|
7348
|
+
const portPath = resolve4(cwd, PORT_FILE);
|
|
7349
|
+
if (existsSync5(portPath)) unlinkSync2(portPath);
|
|
7350
|
+
} catch {
|
|
7351
|
+
}
|
|
7096
7352
|
});
|
|
7097
7353
|
}
|
|
7098
7354
|
function routeEvent2(event) {
|
|
@@ -7123,6 +7379,7 @@ var init_setup = __esm({
|
|
|
7123
7379
|
init_router();
|
|
7124
7380
|
init_request_log();
|
|
7125
7381
|
init_store();
|
|
7382
|
+
init_finding_store();
|
|
7126
7383
|
init_engine();
|
|
7127
7384
|
init_terminal();
|
|
7128
7385
|
init_src();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brakit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
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": {
|
|
@@ -28,6 +28,9 @@
|
|
|
28
28
|
"typecheck": "tsc --noEmit",
|
|
29
29
|
"test": "vitest run",
|
|
30
30
|
"test:watch": "vitest",
|
|
31
|
+
"test:contracts": "vitest run tests/contracts tests/adapters",
|
|
32
|
+
"test:integration": "vitest run tests/integration",
|
|
33
|
+
"ci": "npm run typecheck && npm test && npm run build",
|
|
31
34
|
"lint": "tsc --noEmit"
|
|
32
35
|
},
|
|
33
36
|
"repository": {
|
|
@@ -40,6 +43,7 @@
|
|
|
40
43
|
},
|
|
41
44
|
"author": "Brakit <dev@brakit.ai> (https://brakit.ai)",
|
|
42
45
|
"dependencies": {
|
|
46
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
43
47
|
"citty": "^0.1.6",
|
|
44
48
|
"picocolors": "^1.1.1"
|
|
45
49
|
},
|