tracebug 1.5.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 +37 -0
- package/bin.mjs +591 -0
- package/package.json +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# tracebug
|
|
2
|
+
|
|
3
|
+
The TraceBug CLI — a **local MCP server** that lets AI coding agents debug [TraceBug](https://github.com/prashantsinghmangat/tracebug-ai) bug-report exports, plus one-command framework setup.
|
|
4
|
+
|
|
5
|
+
Zero dependencies. ~24 KB. Node ≥ 18.
|
|
6
|
+
|
|
7
|
+
## MCP server
|
|
8
|
+
|
|
9
|
+
Connect Claude Code, Cursor, Windsurf, or VS Code to the `.html` bug reports the [TraceBug Chrome extension](https://chromewebstore.google.com/detail/fdemmibikigigkfjngclmdheeajhdgaj) exports:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
claude mcp add tracebug -- npx -y tracebug mcp --dir ./bug-reports
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The agent gets six read tools — `list_bug_reports`, `get_bug_report` (with a prioritized investigation guide), `get_console_errors`, `get_network_activity`, `get_repro_steps`, `get_screenshot` — and a `/tracebug:debug_bug_report` prompt.
|
|
16
|
+
|
|
17
|
+
**Everything stays on your machine.** The server binds to stdio only, opens zero network connections, and reads the report files from your disk. No account, no cloud, no upload.
|
|
18
|
+
|
|
19
|
+
Full guide: [tracebug.netlify.app/docs/mcp](https://tracebug.netlify.app/docs/mcp)
|
|
20
|
+
|
|
21
|
+
## Project setup
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx tracebug init
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Detects your framework (React, Next.js, Vue, Angular, Svelte, Nuxt, vanilla) and prints the setup snippet for the [`tracebug-sdk`](https://www.npmjs.com/package/tracebug-sdk) — the embeddable capture SDK.
|
|
28
|
+
|
|
29
|
+
## Related packages
|
|
30
|
+
|
|
31
|
+
| Package | What it is |
|
|
32
|
+
|---|---|
|
|
33
|
+
| [`tracebug`](https://www.npmjs.com/package/tracebug) (this one) | CLI: MCP server + `init`. For developers *receiving* bug reports. |
|
|
34
|
+
| [`tracebug-sdk`](https://www.npmjs.com/package/tracebug-sdk) | The full SDK for embedding capture in your app. Includes this CLI too. |
|
|
35
|
+
| [Chrome extension](https://chromewebstore.google.com/detail/fdemmibikigigkfjngclmdheeajhdgaj) | Zero-code capture for QA/testers on any website. |
|
|
36
|
+
|
|
37
|
+
MIT © Prashant Singh Mangat
|
package/bin.mjs
ADDED
|
@@ -0,0 +1,591 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __esm = (fn, res) => function __init() {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
};
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// cli/mcp-server.ts
|
|
13
|
+
var mcp_server_exports = {};
|
|
14
|
+
__export(mcp_server_exports, {
|
|
15
|
+
PROMPT_DEFINITIONS: () => PROMPT_DEFINITIONS,
|
|
16
|
+
TOOL_DEFINITIONS: () => TOOL_DEFINITIONS,
|
|
17
|
+
buildDebugPrompt: () => buildDebugPrompt,
|
|
18
|
+
buildInvestigationGuide: () => buildInvestigationGuide,
|
|
19
|
+
callTool: () => callTool,
|
|
20
|
+
handleMessage: () => handleMessage,
|
|
21
|
+
parseReportFile: () => parseReportFile,
|
|
22
|
+
runMcpServer: () => runMcpServer,
|
|
23
|
+
scanReportFiles: () => scanReportFiles,
|
|
24
|
+
toolGetBugReport: () => toolGetBugReport,
|
|
25
|
+
toolGetConsoleErrors: () => toolGetConsoleErrors,
|
|
26
|
+
toolGetNetworkActivity: () => toolGetNetworkActivity,
|
|
27
|
+
toolGetReproSteps: () => toolGetReproSteps,
|
|
28
|
+
toolGetScreenshot: () => toolGetScreenshot,
|
|
29
|
+
toolListBugReports: () => toolListBugReports
|
|
30
|
+
});
|
|
31
|
+
import * as fs from "fs";
|
|
32
|
+
import * as path from "path";
|
|
33
|
+
function parseReportFile(filePath) {
|
|
34
|
+
let raw;
|
|
35
|
+
try {
|
|
36
|
+
raw = fs.readFileSync(filePath, "utf8");
|
|
37
|
+
} catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
if (filePath.endsWith(".html")) {
|
|
42
|
+
const m = TB_DATA_RE.exec(raw);
|
|
43
|
+
if (!m) return null;
|
|
44
|
+
const payload = JSON.parse(m[1].replace(/<\\\/script>/gi, "</script>"));
|
|
45
|
+
return isReportPayload(payload) ? payload : null;
|
|
46
|
+
}
|
|
47
|
+
if (filePath.endsWith(".json")) {
|
|
48
|
+
const payload = JSON.parse(raw);
|
|
49
|
+
return isReportPayload(payload) ? payload : null;
|
|
50
|
+
}
|
|
51
|
+
} catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
function isReportPayload(p) {
|
|
57
|
+
return typeof p === "object" && p !== null && typeof p.meta === "object" && p.meta !== null && typeof p.meta.title === "string" && typeof p.meta.sessionId === "string";
|
|
58
|
+
}
|
|
59
|
+
function scanReportFiles(dir, depth = 0) {
|
|
60
|
+
if (depth > MAX_SCAN_DEPTH) return [];
|
|
61
|
+
let entries;
|
|
62
|
+
try {
|
|
63
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
64
|
+
} catch {
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
const found = [];
|
|
68
|
+
for (const entry of entries) {
|
|
69
|
+
const full = path.join(dir, entry.name);
|
|
70
|
+
if (entry.isDirectory()) {
|
|
71
|
+
if (!SKIP_DIRS.has(entry.name) && !entry.name.startsWith(".")) {
|
|
72
|
+
found.push(...scanReportFiles(full, depth + 1));
|
|
73
|
+
}
|
|
74
|
+
} else if (entry.name.endsWith(".html") || entry.name.endsWith(".json")) {
|
|
75
|
+
if (parseReportFile(full) !== null) found.push(full);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return found;
|
|
79
|
+
}
|
|
80
|
+
function requireReport(baseDir, file) {
|
|
81
|
+
const direct = path.isAbsolute(file) ? file : path.join(baseDir, file);
|
|
82
|
+
const directPayload = parseReportFile(direct);
|
|
83
|
+
if (directPayload) return { payload: directPayload, resolved: direct };
|
|
84
|
+
const candidates = scanReportFiles(baseDir);
|
|
85
|
+
const query = file.toLowerCase();
|
|
86
|
+
let match = candidates.find((c) => path.basename(c).toLowerCase() === query);
|
|
87
|
+
if (!match) {
|
|
88
|
+
match = candidates.find((c) => {
|
|
89
|
+
if (path.basename(c).toLowerCase().includes(query)) return true;
|
|
90
|
+
const p = parseReportFile(c);
|
|
91
|
+
return p !== null && p.meta.title.toLowerCase().includes(query);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
if (match) return { payload: parseReportFile(match), resolved: match };
|
|
95
|
+
const available = candidates.map((c) => path.relative(baseDir, c) || c);
|
|
96
|
+
throw new Error(
|
|
97
|
+
`Not a TraceBug export: ${file}. ` + (available.length ? `Available reports: ${available.join(", ")}` : `No reports found under ${baseDir} \u2014 check the server's --dir.`)
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
function buildInvestigationGuide(p) {
|
|
101
|
+
const steps = [];
|
|
102
|
+
const consoleErrors = p.consoleLogs?.filter((l) => l.level === "error").length ?? p.consoleErrors?.length ?? 0;
|
|
103
|
+
const netFailures = p.networkErrors?.length ?? 0;
|
|
104
|
+
const frustration = (p.actionChips ?? []).filter((c) => c.frustration).length;
|
|
105
|
+
const screenshots = p.screenshots?.length ?? 0;
|
|
106
|
+
const hintMentionsNetwork = /\b(request|response|http|4\d\d|5\d\d|status|api|fetch|xhr)\b/i.test(
|
|
107
|
+
p.rootCauseHint?.hint ?? ""
|
|
108
|
+
);
|
|
109
|
+
const networkStep = netFailures > 0 && `[HIGH] get_network_activity \u2014 ${netFailures} failed request${netFailures === 1 ? "" : "s"} captured, with response-body snippets that often name the server-side error.`;
|
|
110
|
+
const consoleStep = consoleErrors > 0 && `[HIGH] get_console_errors \u2014 ${consoleErrors} console error${consoleErrors === 1 ? "" : "s"} captured; stack traces point at the failing file and function.`;
|
|
111
|
+
for (const step of hintMentionsNetwork ? [networkStep, consoleStep] : [consoleStep, networkStep]) {
|
|
112
|
+
if (step) steps.push(step);
|
|
113
|
+
}
|
|
114
|
+
steps.push(
|
|
115
|
+
frustration > 0 ? `[HIGH] get_repro_steps \u2014 includes ${frustration} frustration signal${frustration === 1 ? "" : "s"} (rage/dead clicks) marking where the user got stuck, plus the action-to-error timeline.` : `[MEDIUM] get_repro_steps \u2014 the plain-English steps and session timeline show what the user did leading up to the bug.`
|
|
116
|
+
);
|
|
117
|
+
if (screenshots > 0) {
|
|
118
|
+
steps.push(
|
|
119
|
+
`[${consoleErrors || netFailures ? "MEDIUM" : "HIGH"}] get_screenshot \u2014 ${screenshots} screenshot${screenshots === 1 ? "" : "s"} showing the visual state when the bug was captured${consoleErrors || netFailures ? "" : " (no errors were captured, so visuals are a primary source)"}.`
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
if (p.video?.comments?.length) {
|
|
123
|
+
steps.push(
|
|
124
|
+
`[MEDIUM] The video has ${p.video.comments.length} timestamped tester comment${p.video.comments.length === 1 ? "" : "s"} (included in get_bug_report) \u2014 read them, they mark the exact moments the tester flagged.`
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
steps.push(
|
|
128
|
+
"Finally: cross-reference the findings with the codebase \u2014 search for the failing endpoint path, the symbols in the stack trace, or the UI text near the error \u2014 to locate the root cause and propose a fix."
|
|
129
|
+
);
|
|
130
|
+
return steps;
|
|
131
|
+
}
|
|
132
|
+
function toolListBugReports(baseDir, args2) {
|
|
133
|
+
const scanDir = args2.dir ? path.isAbsolute(args2.dir) ? args2.dir : path.join(baseDir, args2.dir) : baseDir;
|
|
134
|
+
const files = scanReportFiles(scanDir);
|
|
135
|
+
const reports = files.map((file) => {
|
|
136
|
+
const p = parseReportFile(file);
|
|
137
|
+
return {
|
|
138
|
+
file: path.relative(baseDir, file) || file,
|
|
139
|
+
title: p.meta.title,
|
|
140
|
+
summary: p.meta.summary,
|
|
141
|
+
severity: p.meta.severity,
|
|
142
|
+
priority: p.meta.priority ?? null,
|
|
143
|
+
page: p.meta.page,
|
|
144
|
+
generatedAt: new Date(p.meta.generatedAt).toISOString(),
|
|
145
|
+
rootCause: p.rootCauseHint ?? p.meta.rootCause ?? null,
|
|
146
|
+
counts: {
|
|
147
|
+
consoleErrors: p.consoleErrors?.length ?? 0,
|
|
148
|
+
networkFailures: p.networkErrors?.length ?? 0,
|
|
149
|
+
userActions: p.actionChips?.length ?? p.actions?.length ?? 0,
|
|
150
|
+
screenshots: p.screenshots?.length ?? 0,
|
|
151
|
+
hasVideo: Boolean(p.video)
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
});
|
|
155
|
+
return {
|
|
156
|
+
count: reports.length,
|
|
157
|
+
scannedDir: scanDir,
|
|
158
|
+
reports,
|
|
159
|
+
...reports.length ? { nextStep: "Call get_bug_report on a file for the full overview plus a prioritized investigation guide." } : {}
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function toolGetBugReport(baseDir, args2) {
|
|
163
|
+
const { payload: p } = requireReport(baseDir, args2.file);
|
|
164
|
+
return {
|
|
165
|
+
title: p.meta.title,
|
|
166
|
+
summary: p.meta.summary,
|
|
167
|
+
severity: p.meta.severity,
|
|
168
|
+
priority: p.meta.priority ?? null,
|
|
169
|
+
page: p.meta.page,
|
|
170
|
+
generatedAt: new Date(p.meta.generatedAt).toISOString(),
|
|
171
|
+
sessionDurationMs: p.meta.durationMs,
|
|
172
|
+
environment: p.meta.environment,
|
|
173
|
+
environmentDetails: p.info ?? [],
|
|
174
|
+
description: p.description || null,
|
|
175
|
+
rootCause: p.rootCauseHint ?? p.meta.rootCause ?? null,
|
|
176
|
+
annotations: p.annotations ?? [],
|
|
177
|
+
consoleErrorCount: p.consoleErrors?.length ?? 0,
|
|
178
|
+
networkFailureCount: p.networkErrors?.length ?? 0,
|
|
179
|
+
screenshotCount: p.screenshots?.length ?? 0,
|
|
180
|
+
video: p.video ? { durationMs: p.video.durationMs, comments: p.video.comments } : null,
|
|
181
|
+
investigationGuide: buildInvestigationGuide(p)
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function toolGetConsoleErrors(baseDir, args2) {
|
|
185
|
+
const { payload: p } = requireReport(baseDir, args2.file);
|
|
186
|
+
const logs = p.consoleLogs?.length ? p.consoleLogs : (p.consoleErrors ?? []).map((e) => ({ level: "error", ...e }));
|
|
187
|
+
return {
|
|
188
|
+
count: logs.length,
|
|
189
|
+
entries: logs.map((l) => ({
|
|
190
|
+
level: l.level,
|
|
191
|
+
message: l.message,
|
|
192
|
+
stack: l.stack ?? null,
|
|
193
|
+
at: new Date(l.timestamp).toISOString()
|
|
194
|
+
}))
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
function toolGetNetworkActivity(baseDir, args2) {
|
|
198
|
+
const { payload: p } = requireReport(baseDir, args2.file);
|
|
199
|
+
const failures = p.networkErrors ?? [];
|
|
200
|
+
const all = p.networkRequests ?? [];
|
|
201
|
+
const rows = args2.failuresOnly !== false ? failures : all.length ? all : failures;
|
|
202
|
+
return {
|
|
203
|
+
failureCount: failures.length,
|
|
204
|
+
totalCaptured: all.length || failures.length,
|
|
205
|
+
showing: args2.failuresOnly !== false ? "failures only (pass failuresOnly:false for all requests)" : "all requests",
|
|
206
|
+
requests: rows.map((r) => ({
|
|
207
|
+
method: r.method,
|
|
208
|
+
url: r.url,
|
|
209
|
+
status: r.status,
|
|
210
|
+
durationMs: r.duration,
|
|
211
|
+
at: new Date(r.timestamp).toISOString(),
|
|
212
|
+
responseSnippet: r.response ?? null
|
|
213
|
+
}))
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
function toolGetReproSteps(baseDir, args2) {
|
|
217
|
+
const { payload: p } = requireReport(baseDir, args2.file);
|
|
218
|
+
return {
|
|
219
|
+
title: p.meta.title,
|
|
220
|
+
rootCause: p.rootCauseHint ?? p.meta.rootCause ?? null,
|
|
221
|
+
steps: p.actions ?? [],
|
|
222
|
+
actions: (p.actionChips ?? []).map((c) => ({
|
|
223
|
+
action: [c.verb, c.nounLabel ?? c.target].filter(Boolean).join(" "),
|
|
224
|
+
kind: c.kind,
|
|
225
|
+
detail: c.detail ?? null,
|
|
226
|
+
frustrationSignal: c.frustration ?? null,
|
|
227
|
+
causedError: c.isError ?? false,
|
|
228
|
+
at: new Date(c.timestamp).toISOString()
|
|
229
|
+
})),
|
|
230
|
+
timeline: (p.events ?? []).map((e) => ({
|
|
231
|
+
elapsed: e.elapsed,
|
|
232
|
+
type: e.type,
|
|
233
|
+
description: e.description,
|
|
234
|
+
isError: e.isError
|
|
235
|
+
}))
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
function toolGetScreenshot(baseDir, args2) {
|
|
239
|
+
const { payload: p } = requireReport(baseDir, args2.file);
|
|
240
|
+
const shots = p.screenshots ?? [];
|
|
241
|
+
const index = args2.index ?? 0;
|
|
242
|
+
if (!shots.length) throw new Error("This report contains no screenshots.");
|
|
243
|
+
if (index < 0 || index >= shots.length) {
|
|
244
|
+
throw new Error(`Screenshot index ${index} out of range (0\u2013${shots.length - 1}).`);
|
|
245
|
+
}
|
|
246
|
+
const shot = shots[index];
|
|
247
|
+
const m = /^data:(image\/[a-z+]+);base64,(.+)$/s.exec(shot.dataUrl);
|
|
248
|
+
if (!m) throw new Error(`Screenshot ${index} has an unsupported data URL format.`);
|
|
249
|
+
return {
|
|
250
|
+
image: { mimeType: m[1], base64: m[2] },
|
|
251
|
+
filename: shot.filename,
|
|
252
|
+
takenAt: new Date(shot.timestamp).toISOString(),
|
|
253
|
+
totalScreenshots: shots.length
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
function buildDebugPrompt(file) {
|
|
257
|
+
const target = file?.trim();
|
|
258
|
+
return [
|
|
259
|
+
target ? `This is a TraceBug bug report: ${target}` : "Debug the most recent TraceBug bug report.",
|
|
260
|
+
"",
|
|
261
|
+
target ? `1. Call get_bug_report("${target}") to load the report overview and its investigation guide.` : "1. Call list_bug_reports to find the reports, then get_bug_report on the most recent one to load its overview and investigation guide.",
|
|
262
|
+
"2. Follow the investigation guide to gather the relevant data (console errors, network failures, repro steps, screenshots).",
|
|
263
|
+
"3. Cross-reference the findings with this codebase \u2014 search for the failing endpoint path, stack-trace symbols, or the UI text near the error \u2014 to identify the root cause.",
|
|
264
|
+
"4. Propose a concrete fix, citing the files and lines involved, and note edge cases worth testing once it lands."
|
|
265
|
+
].join("\n");
|
|
266
|
+
}
|
|
267
|
+
function callTool(baseDir, name, args2) {
|
|
268
|
+
try {
|
|
269
|
+
switch (name) {
|
|
270
|
+
case "list_bug_reports":
|
|
271
|
+
return textResult(toolListBugReports(baseDir, args2));
|
|
272
|
+
case "get_bug_report":
|
|
273
|
+
return textResult(toolGetBugReport(baseDir, args2));
|
|
274
|
+
case "get_console_errors":
|
|
275
|
+
return textResult(toolGetConsoleErrors(baseDir, args2));
|
|
276
|
+
case "get_network_activity":
|
|
277
|
+
return textResult(toolGetNetworkActivity(baseDir, args2));
|
|
278
|
+
case "get_repro_steps":
|
|
279
|
+
return textResult(toolGetReproSteps(baseDir, args2));
|
|
280
|
+
case "get_screenshot": {
|
|
281
|
+
const r = toolGetScreenshot(baseDir, args2);
|
|
282
|
+
return {
|
|
283
|
+
content: [
|
|
284
|
+
{ type: "image", data: r.image.base64, mimeType: r.image.mimeType },
|
|
285
|
+
{ type: "text", text: JSON.stringify({ filename: r.filename, takenAt: r.takenAt, totalScreenshots: r.totalScreenshots }) }
|
|
286
|
+
]
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
default:
|
|
290
|
+
return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
|
|
291
|
+
}
|
|
292
|
+
} catch (err) {
|
|
293
|
+
return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
function textResult(data) {
|
|
297
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
298
|
+
}
|
|
299
|
+
function handleMessage(baseDir, msg) {
|
|
300
|
+
const isNotification = msg.id === void 0 || msg.id === null;
|
|
301
|
+
switch (msg.method) {
|
|
302
|
+
case "initialize":
|
|
303
|
+
return reply(msg.id, {
|
|
304
|
+
protocolVersion: msg.params?.protocolVersion || "2025-06-18",
|
|
305
|
+
capabilities: { tools: {}, prompts: {} },
|
|
306
|
+
serverInfo: { name: "tracebug", version: SERVER_VERSION },
|
|
307
|
+
instructions: "TraceBug exposes locally-exported bug reports (self-contained .html files) to AI agents. Call list_bug_reports to discover reports, then get_bug_report \u2014 it returns an investigation guide telling you which get_* tools to call next. All data is read from local disk \u2014 nothing is uploaded."
|
|
308
|
+
});
|
|
309
|
+
case "ping":
|
|
310
|
+
return reply(msg.id, {});
|
|
311
|
+
case "tools/list":
|
|
312
|
+
return reply(msg.id, { tools: TOOL_DEFINITIONS });
|
|
313
|
+
case "tools/call": {
|
|
314
|
+
const name = msg.params?.name;
|
|
315
|
+
const args2 = msg.params?.arguments ?? {};
|
|
316
|
+
return reply(msg.id, callTool(baseDir, name, args2));
|
|
317
|
+
}
|
|
318
|
+
case "prompts/list":
|
|
319
|
+
return reply(msg.id, { prompts: PROMPT_DEFINITIONS });
|
|
320
|
+
case "prompts/get": {
|
|
321
|
+
const promptName = msg.params?.name;
|
|
322
|
+
if (promptName !== "debug_bug_report") {
|
|
323
|
+
return { jsonrpc: "2.0", id: msg.id ?? null, error: { code: -32602, message: `Unknown prompt: ${promptName}` } };
|
|
324
|
+
}
|
|
325
|
+
const promptArgs = msg.params?.arguments ?? {};
|
|
326
|
+
return reply(msg.id, {
|
|
327
|
+
description: PROMPT_DEFINITIONS[0].description,
|
|
328
|
+
messages: [
|
|
329
|
+
{ role: "user", content: { type: "text", text: buildDebugPrompt(promptArgs.file) } }
|
|
330
|
+
]
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
default:
|
|
334
|
+
if (isNotification) return null;
|
|
335
|
+
return { jsonrpc: "2.0", id: msg.id, error: { code: -32601, message: `Method not found: ${msg.method}` } };
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
function reply(id, result) {
|
|
339
|
+
return { jsonrpc: "2.0", id: id ?? null, result };
|
|
340
|
+
}
|
|
341
|
+
function runMcpServer(baseDir, version) {
|
|
342
|
+
SERVER_VERSION = version;
|
|
343
|
+
process.stderr.write(`TraceBug MCP server \u2014 reading bug reports from ${baseDir}
|
|
344
|
+
`);
|
|
345
|
+
return new Promise((resolve) => {
|
|
346
|
+
let buffer = "";
|
|
347
|
+
process.stdin.setEncoding("utf8");
|
|
348
|
+
process.stdin.on("data", (chunk) => {
|
|
349
|
+
buffer += chunk;
|
|
350
|
+
let nl;
|
|
351
|
+
while ((nl = buffer.indexOf("\n")) !== -1) {
|
|
352
|
+
const line = buffer.slice(0, nl).trim();
|
|
353
|
+
buffer = buffer.slice(nl + 1);
|
|
354
|
+
if (!line) continue;
|
|
355
|
+
let msg;
|
|
356
|
+
try {
|
|
357
|
+
msg = JSON.parse(line);
|
|
358
|
+
} catch {
|
|
359
|
+
process.stdout.write(
|
|
360
|
+
JSON.stringify({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "Parse error" } }) + "\n"
|
|
361
|
+
);
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
const response = handleMessage(baseDir, msg);
|
|
365
|
+
if (response) process.stdout.write(JSON.stringify(response) + "\n");
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
process.stdin.on("end", () => resolve());
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
var TB_DATA_RE, MAX_SCAN_DEPTH, SKIP_DIRS, FILE_PROP, TOOL_DEFINITIONS, PROMPT_DEFINITIONS, SERVER_VERSION;
|
|
372
|
+
var init_mcp_server = __esm({
|
|
373
|
+
"cli/mcp-server.ts"() {
|
|
374
|
+
"use strict";
|
|
375
|
+
TB_DATA_RE = /<script id="tb-data" type="application\/json">([\s\S]*?)<\/script>/;
|
|
376
|
+
MAX_SCAN_DEPTH = 3;
|
|
377
|
+
SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".next", "dist", "build", "coverage", "out"]);
|
|
378
|
+
FILE_PROP = {
|
|
379
|
+
file: {
|
|
380
|
+
type: "string",
|
|
381
|
+
description: "The report to read: a path to a TraceBug export file (.html), absolute or relative to the server's base directory \u2014 or just the filename, or a fragment of the filename or report title. Get exact paths from list_bug_reports."
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
TOOL_DEFINITIONS = [
|
|
385
|
+
{
|
|
386
|
+
name: "list_bug_reports",
|
|
387
|
+
description: "Scan a directory for TraceBug bug-report export files (.html/.json) and summarize each: title, summary, severity, root-cause hint, and captured-data counts. Start here.",
|
|
388
|
+
inputSchema: {
|
|
389
|
+
type: "object",
|
|
390
|
+
properties: {
|
|
391
|
+
dir: { type: "string", description: "Directory to scan (default: the server's base directory). Scans up to 3 levels deep, skipping node_modules etc." }
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
name: "get_bug_report",
|
|
397
|
+
description: "Get the overview of one TraceBug bug report: title, summary, severity, root-cause hint, environment, tester annotations, and a prioritized investigation guide telling you which tools to call next for this specific bug. Call this before the other get_* tools.",
|
|
398
|
+
inputSchema: { type: "object", properties: { ...FILE_PROP }, required: ["file"] }
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
name: "get_console_errors",
|
|
402
|
+
description: "Get the captured console output (errors, warnings, logs) with stack traces from a TraceBug bug report.",
|
|
403
|
+
inputSchema: { type: "object", properties: { ...FILE_PROP }, required: ["file"] }
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
name: "get_network_activity",
|
|
407
|
+
description: "Get captured network requests from a TraceBug bug report. Defaults to failed requests (status >= 400 or 0) with response snippets; pass failuresOnly:false for every captured request.",
|
|
408
|
+
inputSchema: {
|
|
409
|
+
type: "object",
|
|
410
|
+
properties: {
|
|
411
|
+
...FILE_PROP,
|
|
412
|
+
failuresOnly: { type: "boolean", description: "true (default) = failures only; false = all captured requests" }
|
|
413
|
+
},
|
|
414
|
+
required: ["file"]
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
name: "get_repro_steps",
|
|
419
|
+
description: "Get the reproduction context from a TraceBug bug report: plain-English steps, structured user actions (with rage/dead-click frustration signals), and the full session timeline.",
|
|
420
|
+
inputSchema: { type: "object", properties: { ...FILE_PROP }, required: ["file"] }
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
name: "get_screenshot",
|
|
424
|
+
description: "Get one screenshot from a TraceBug bug report as an image. Screenshots are auto-named from the action that triggered them (e.g. 01_click_submit.png).",
|
|
425
|
+
inputSchema: {
|
|
426
|
+
type: "object",
|
|
427
|
+
properties: {
|
|
428
|
+
...FILE_PROP,
|
|
429
|
+
index: { type: "number", description: "Zero-based screenshot index (default 0). list_bug_reports shows the count." }
|
|
430
|
+
},
|
|
431
|
+
required: ["file"]
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
];
|
|
435
|
+
PROMPT_DEFINITIONS = [
|
|
436
|
+
{
|
|
437
|
+
name: "debug_bug_report",
|
|
438
|
+
description: "Debug a TraceBug bug report: load it, follow its investigation guide, and cross-reference with the codebase to find the root cause and propose a fix.",
|
|
439
|
+
arguments: [
|
|
440
|
+
{
|
|
441
|
+
name: "file",
|
|
442
|
+
description: "Which report to debug \u2014 a path, filename, or title fragment. Omit to pick the most recent report.",
|
|
443
|
+
required: false
|
|
444
|
+
}
|
|
445
|
+
]
|
|
446
|
+
}
|
|
447
|
+
];
|
|
448
|
+
SERVER_VERSION = "0.0.0";
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
// cli/bin.ts
|
|
453
|
+
var args = process.argv.slice(2);
|
|
454
|
+
var command = args[0];
|
|
455
|
+
var BOLD = "\x1B[1m";
|
|
456
|
+
var CYAN = "\x1B[36m";
|
|
457
|
+
var GREEN = "\x1B[32m";
|
|
458
|
+
var DIM = "\x1B[2m";
|
|
459
|
+
var RESET = "\x1B[0m";
|
|
460
|
+
async function main() {
|
|
461
|
+
if (!command || command === "help") {
|
|
462
|
+
printHelp();
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
if (command === "init") {
|
|
466
|
+
await initProject();
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
if (command === "mcp") {
|
|
470
|
+
await startMcpServer();
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
console.log(`Unknown command: ${command}. Run ${CYAN}npx tracebug help${RESET}`);
|
|
474
|
+
}
|
|
475
|
+
async function startMcpServer() {
|
|
476
|
+
const { runMcpServer: runMcpServer2 } = await Promise.resolve().then(() => (init_mcp_server(), mcp_server_exports));
|
|
477
|
+
const fs2 = await import("fs");
|
|
478
|
+
const path2 = await import("path");
|
|
479
|
+
const dirFlag = args.indexOf("--dir");
|
|
480
|
+
const baseDir = dirFlag !== -1 && args[dirFlag + 1] ? path2.resolve(args[dirFlag + 1]) : process.cwd();
|
|
481
|
+
let version = "0.0.0";
|
|
482
|
+
for (const rel of ["./package.json", "../package.json"]) {
|
|
483
|
+
try {
|
|
484
|
+
const pkg = JSON.parse(fs2.readFileSync(new URL(rel, import.meta.url), "utf8"));
|
|
485
|
+
if (pkg.version) {
|
|
486
|
+
version = pkg.version;
|
|
487
|
+
break;
|
|
488
|
+
}
|
|
489
|
+
} catch {
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
await runMcpServer2(baseDir, version);
|
|
493
|
+
}
|
|
494
|
+
function printHelp() {
|
|
495
|
+
console.log(`
|
|
496
|
+
${BOLD}TraceBug CLI${RESET} \u2014 Debug bugs in seconds, not hours
|
|
497
|
+
|
|
498
|
+
${BOLD}Commands:${RESET}
|
|
499
|
+
${CYAN}init${RESET} Set up TraceBug in your project
|
|
500
|
+
${CYAN}mcp${RESET} Start the MCP server so AI agents (Claude Code, Cursor) can read
|
|
501
|
+
exported bug reports. Options: ${DIM}--dir <path>${RESET} (default: current dir)
|
|
502
|
+
${CYAN}help${RESET} Show this help message
|
|
503
|
+
|
|
504
|
+
${BOLD}Quick Start:${RESET}
|
|
505
|
+
${DIM}$${RESET} npx tracebug init
|
|
506
|
+
${DIM}$${RESET} npm run dev
|
|
507
|
+
|
|
508
|
+
${BOLD}Docs:${RESET} https://tracebug.dev/docs
|
|
509
|
+
`);
|
|
510
|
+
}
|
|
511
|
+
async function initProject() {
|
|
512
|
+
console.log(`
|
|
513
|
+
${BOLD}${CYAN}TraceBug${RESET} \u2014 Setting up bug reporting
|
|
514
|
+
`);
|
|
515
|
+
const fs2 = await import("fs");
|
|
516
|
+
const path2 = await import("path");
|
|
517
|
+
const cwd = process.cwd();
|
|
518
|
+
let framework = "vanilla";
|
|
519
|
+
const pkgPath = path2.join(cwd, "package.json");
|
|
520
|
+
if (fs2.existsSync(pkgPath)) {
|
|
521
|
+
try {
|
|
522
|
+
const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
|
|
523
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
524
|
+
if (deps["next"]) framework = "nextjs";
|
|
525
|
+
else if (deps["nuxt"]) framework = "nuxt";
|
|
526
|
+
else if (deps["vue"]) framework = "vue";
|
|
527
|
+
else if (deps["@angular/core"]) framework = "angular";
|
|
528
|
+
else if (deps["svelte"]) framework = "svelte";
|
|
529
|
+
else if (deps["react"]) framework = "react";
|
|
530
|
+
} catch {
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
console.log(` Detected framework: ${GREEN}${framework}${RESET}`);
|
|
534
|
+
const snippets = {
|
|
535
|
+
nextjs: `// app/tracebug.tsx (create this file)
|
|
536
|
+
"use client";
|
|
537
|
+
import { useEffect } from "react";
|
|
538
|
+
|
|
539
|
+
export default function TraceBugInit() {
|
|
540
|
+
useEffect(() => {
|
|
541
|
+
import("tracebug-sdk").then(({ default: TraceBug }) => {
|
|
542
|
+
TraceBug.init({ projectId: "${path2.basename(cwd)}" });
|
|
543
|
+
});
|
|
544
|
+
}, []);
|
|
545
|
+
return null;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// Then add <TraceBugInit /> to your root layout.tsx`,
|
|
549
|
+
react: `// src/main.tsx (add to your entry file)
|
|
550
|
+
import TraceBug from "tracebug-sdk";
|
|
551
|
+
TraceBug.init({ projectId: "${path2.basename(cwd)}" });`,
|
|
552
|
+
vue: `// main.ts (add to your entry file)
|
|
553
|
+
import TraceBug from "tracebug-sdk";
|
|
554
|
+
TraceBug.init({ projectId: "${path2.basename(cwd)}" });`,
|
|
555
|
+
svelte: `<!-- +layout.svelte (add to your root layout) -->
|
|
556
|
+
<script>
|
|
557
|
+
import { onMount } from 'svelte';
|
|
558
|
+
import TraceBug from 'tracebug-sdk';
|
|
559
|
+
onMount(() => TraceBug.init({ projectId: "${path2.basename(cwd)}" }));
|
|
560
|
+
</script>`,
|
|
561
|
+
angular: `// app.component.ts (add to constructor or ngOnInit)
|
|
562
|
+
import TraceBug from "tracebug-sdk";
|
|
563
|
+
TraceBug.init({ projectId: "${path2.basename(cwd)}" });`,
|
|
564
|
+
vanilla: `<!-- Add before </body> -->
|
|
565
|
+
<script type="module">
|
|
566
|
+
import TraceBug from "tracebug-sdk";
|
|
567
|
+
TraceBug.init({ projectId: "${path2.basename(cwd)}" });
|
|
568
|
+
</script>`,
|
|
569
|
+
nuxt: `// plugins/tracebug.client.ts (create this file)
|
|
570
|
+
import TraceBug from "tracebug-sdk";
|
|
571
|
+
export default defineNuxtPlugin(() => {
|
|
572
|
+
TraceBug.init({ projectId: "${path2.basename(cwd)}" });
|
|
573
|
+
});`
|
|
574
|
+
};
|
|
575
|
+
console.log(`
|
|
576
|
+
${BOLD}Add this to your project:${RESET}
|
|
577
|
+
`);
|
|
578
|
+
console.log(`${DIM}\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${RESET}`);
|
|
579
|
+
console.log(snippets[framework]);
|
|
580
|
+
console.log(`${DIM}\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${RESET}`);
|
|
581
|
+
console.log(`
|
|
582
|
+
${BOLD}Next steps:${RESET}
|
|
583
|
+
1. Install the SDK: ${CYAN}npm install tracebug-sdk${RESET}
|
|
584
|
+
2. Add the snippet above to your app
|
|
585
|
+
3. Run your dev server and interact with your app
|
|
586
|
+
4. Click the TraceBug toolbar to see captured sessions
|
|
587
|
+
|
|
588
|
+
${GREEN}Done!${RESET} TraceBug is ready. Happy debugging.
|
|
589
|
+
`);
|
|
590
|
+
}
|
|
591
|
+
main().catch(console.error);
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "tracebug",
|
|
3
|
+
"version": "1.5.0",
|
|
4
|
+
"description": "TraceBug CLI — local MCP server that lets AI coding agents (Claude Code, Cursor, VS Code) debug TraceBug bug-report exports, plus framework setup via `tracebug init`. Zero dependencies.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"tracebug": "./bin.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin.mjs",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=18"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"prepublishOnly": "node -e \"if(!require('fs').existsSync('bin.mjs')){console.error('bin.mjs missing - run: npm run build:cli (from the repo root)');process.exit(1)}\""
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"mcp",
|
|
21
|
+
"model-context-protocol",
|
|
22
|
+
"bug-reporting",
|
|
23
|
+
"debugging",
|
|
24
|
+
"claude-code",
|
|
25
|
+
"cursor",
|
|
26
|
+
"ai-agents",
|
|
27
|
+
"tracebug"
|
|
28
|
+
],
|
|
29
|
+
"author": {
|
|
30
|
+
"name": "Prashant Singh Mangat",
|
|
31
|
+
"url": "https://github.com/prashantsinghmangat"
|
|
32
|
+
},
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/prashantsinghmangat/tracebug-ai.git",
|
|
37
|
+
"directory": "packages/tracebug"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://tracebug.netlify.app/docs/mcp",
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/prashantsinghmangat/tracebug-ai/issues"
|
|
42
|
+
}
|
|
43
|
+
}
|