browsertrack 0.0.1 ā 0.1.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 +143 -0
- package/dist/chunk-6A7FFDIB.js +221 -0
- package/dist/chunk-6A7FFDIB.js.map +1 -0
- package/dist/chunk-ANMR5WBH.js +659 -0
- package/dist/chunk-ANMR5WBH.js.map +1 -0
- package/dist/chunk-BSKNG4V7.js +1199 -0
- package/dist/chunk-BSKNG4V7.js.map +1 -0
- package/dist/chunk-X7C5CHBO.js +2080 -0
- package/dist/chunk-X7C5CHBO.js.map +1 -0
- package/dist/chunk-ZLNGOOH2.js +554 -0
- package/dist/chunk-ZLNGOOH2.js.map +1 -0
- package/dist/cli/index.js +2829 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/client/index.cjs +2218 -0
- package/dist/client/index.d.ts +197 -0
- package/dist/client/index.js +20 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client.iife.js +309 -0
- package/dist/core/index.d.ts +78 -0
- package/dist/core/index.js +31 -0
- package/dist/core/index.js.map +1 -0
- package/dist/daemon/index.d.ts +58 -0
- package/dist/daemon/index.js +32 -0
- package/dist/daemon/index.js.map +1 -0
- package/dist/engine-DKloFjvs.d.ts +157 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +50 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/index.d.ts +11 -0
- package/dist/mcp/index.js +12 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/notes-CBvN91Wf.d.ts +260 -0
- package/dist/projects-CY8ungMt.d.ts +99 -0
- package/dist/server-CN-se8td.d.ts +55 -0
- package/examples/test-app/index.html +299 -0
- package/package.json +52 -7
- package/packages/cli/src/index.ts +413 -0
- package/packages/client/package.json +28 -0
- package/packages/client/src/breadcrumbs.ts +30 -0
- package/packages/client/src/client.ts +278 -0
- package/packages/client/src/commands/handler.ts +301 -0
- package/packages/client/src/config.ts +37 -0
- package/packages/client/src/index.ts +50 -0
- package/packages/client/src/interceptors/console.ts +68 -0
- package/packages/client/src/interceptors/interaction.ts +119 -0
- package/packages/client/src/interceptors/navigation.ts +93 -0
- package/packages/client/src/interceptors/network.ts +165 -0
- package/packages/client/src/interceptors/runtime.ts +65 -0
- package/packages/client/src/notes/inspector.ts +943 -0
- package/packages/client/src/screenshot/browser-script-driver.ts +192 -0
- package/packages/client/src/screenshot/driver.ts +14 -0
- package/packages/client/src/transport/websocket.ts +179 -0
- package/packages/client/tsconfig.json +8 -0
- package/packages/core/dist/index.d.ts +317 -0
- package/packages/core/dist/index.js +221 -0
- package/packages/core/package.json +23 -0
- package/packages/core/src/fingerprint.ts +120 -0
- package/packages/core/src/index.ts +9 -0
- package/packages/core/src/redaction.ts +139 -0
- package/packages/core/src/selector.ts +77 -0
- package/packages/core/src/types/commands.ts +101 -0
- package/packages/core/src/types/events.ts +108 -0
- package/packages/core/src/types/incidents.ts +54 -0
- package/packages/core/src/types/notes.ts +106 -0
- package/packages/core/src/types/probes.ts +44 -0
- package/packages/core/src/types/projects.ts +20 -0
- package/packages/core/tsconfig.json +8 -0
- package/packages/daemon/src/config.ts +29 -0
- package/packages/daemon/src/incidents/engine.ts +211 -0
- package/packages/daemon/src/index.ts +18 -0
- package/packages/daemon/src/notes/engine.ts +92 -0
- package/packages/daemon/src/notes/verification.ts +191 -0
- package/packages/daemon/src/server/daemon.ts +112 -0
- package/packages/daemon/src/server/http.ts +165 -0
- package/packages/daemon/src/server/ws.ts +175 -0
- package/packages/daemon/src/session/manager.ts +126 -0
- package/packages/daemon/src/storage/db.ts +733 -0
- package/packages/daemon/src/storage/screenshot-store.ts +49 -0
- package/packages/daemon/src/verification/engine.ts +264 -0
- package/packages/mcp/src/handlers.ts +398 -0
- package/packages/mcp/src/index.ts +3 -0
- package/packages/mcp/src/server.ts +86 -0
- package/packages/mcp/src/tools.ts +236 -0
- package/src/index.ts +4 -0
- package/test/client/interceptors.test.ts +69 -0
- package/test/core/fingerprint.test.ts +53 -0
- package/test/core/notes.test.ts +66 -0
- package/test/core/redaction.test.ts +48 -0
- package/test/daemon/incident-engine.test.ts +98 -0
- package/test/daemon/notes-storage.test.ts +130 -0
- package/test/daemon/notes-verification.test.ts +135 -0
- package/test/daemon/storage.test.ts +123 -0
- package/test/daemon/verification-engine.test.ts +88 -0
- package/test/e2e/daemon-mcp-e2e.test.ts +153 -0
- package/test/e2e/visual-notes-e2e.test.ts +205 -0
- package/test/mcp/handlers.test.ts +116 -0
- package/test/mcp/notes.test.ts +107 -0
- package/tsconfig.base.json +17 -0
- package/tsconfig.json +26 -0
- package/tsup.config.ts +54 -0
- package/vitest.config.ts +9 -0
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { getDaemonConfig } from '../../daemon/src/config.js';
|
|
5
|
+
import { createDaemon } from '../../daemon/src/index.js';
|
|
6
|
+
import { StorageDB } from '../../daemon/src/storage/db.js';
|
|
7
|
+
import { createMcpServer } from '../../mcp/src/server.js';
|
|
8
|
+
|
|
9
|
+
const program = new Command();
|
|
10
|
+
program.name('browsertrack').description('Local browser diagnostics + MCP bridge for coding agents').version('0.1.0');
|
|
11
|
+
|
|
12
|
+
function getPidFilePath(): string {
|
|
13
|
+
const config = getDaemonConfig();
|
|
14
|
+
return path.join(config.dataDir, 'daemon.pid');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function savePid(pid: number): void {
|
|
18
|
+
const pidFile = getPidFilePath();
|
|
19
|
+
fs.mkdirSync(path.dirname(pidFile), { recursive: true });
|
|
20
|
+
fs.writeFileSync(pidFile, String(pid), 'utf-8');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function readPid(): number | null {
|
|
24
|
+
try {
|
|
25
|
+
const pidFile = getPidFilePath();
|
|
26
|
+
if (fs.existsSync(pidFile)) {
|
|
27
|
+
const pidStr = fs.readFileSync(pidFile, 'utf-8').trim();
|
|
28
|
+
const pid = parseInt(pidStr, 10);
|
|
29
|
+
if (!isNaN(pid)) return pid;
|
|
30
|
+
}
|
|
31
|
+
} catch {}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function removePid(): void {
|
|
36
|
+
try {
|
|
37
|
+
const pidFile = getPidFilePath();
|
|
38
|
+
if (fs.existsSync(pidFile)) {
|
|
39
|
+
fs.unlinkSync(pidFile);
|
|
40
|
+
}
|
|
41
|
+
} catch {}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function isProcessRunning(pid: number): boolean {
|
|
45
|
+
try {
|
|
46
|
+
process.kill(pid, 0);
|
|
47
|
+
return true;
|
|
48
|
+
} catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 1. START
|
|
54
|
+
program
|
|
55
|
+
.command('start')
|
|
56
|
+
.description('Start the local BrowserTrack daemon and HTTP/WS server')
|
|
57
|
+
.option('-p, --port <number>', 'Server port', '7331')
|
|
58
|
+
.option('-h, --host <host>', 'Server host', '127.0.0.1')
|
|
59
|
+
.option('-v, --verbose', 'Enable verbose logging', false)
|
|
60
|
+
.action(async (options) => {
|
|
61
|
+
const existingPid = readPid();
|
|
62
|
+
if (existingPid && isProcessRunning(existingPid)) {
|
|
63
|
+
console.log(`[BrowserTrack] Daemon is already running (PID: ${existingPid})`);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const port = parseInt(options.port, 10);
|
|
68
|
+
const daemon = createDaemon({
|
|
69
|
+
port,
|
|
70
|
+
host: options.host,
|
|
71
|
+
verbose: options.verbose,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
await daemon.start();
|
|
76
|
+
savePid(process.pid);
|
|
77
|
+
|
|
78
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
79
|
+
console.log(' š BrowserTrack Daemon Active');
|
|
80
|
+
console.log(` š Server: http://${daemon.config.host}:${daemon.config.port}`);
|
|
81
|
+
console.log(` š WebSocket: ws://${daemon.config.host}:${daemon.config.port}`);
|
|
82
|
+
console.log(` š¦ Script: <script src="http://${daemon.config.host}:${daemon.config.port}/client.js"></script>`);
|
|
83
|
+
console.log(` š¾ Database: ${daemon.config.dbPath}`);
|
|
84
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
85
|
+
|
|
86
|
+
// Graceful shutdown handling
|
|
87
|
+
let isShuttingDown = false;
|
|
88
|
+
const shutdown = async () => {
|
|
89
|
+
if (isShuttingDown) {
|
|
90
|
+
process.exit(0);
|
|
91
|
+
}
|
|
92
|
+
isShuttingDown = true;
|
|
93
|
+
console.log('\n[BrowserTrack] Shutting down daemon...');
|
|
94
|
+
removePid();
|
|
95
|
+
|
|
96
|
+
// Safety fallback timer to prevent terminal freeze
|
|
97
|
+
const forceExitTimer = setTimeout(() => {
|
|
98
|
+
process.exit(0);
|
|
99
|
+
}, 800);
|
|
100
|
+
forceExitTimer.unref();
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
await daemon.stop();
|
|
104
|
+
} catch {}
|
|
105
|
+
process.exit(0);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
process.on('SIGINT', shutdown);
|
|
109
|
+
process.on('SIGTERM', shutdown);
|
|
110
|
+
} catch (err: any) {
|
|
111
|
+
console.error('[BrowserTrack] Failed to start daemon:', err.message);
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// 2. STOP
|
|
117
|
+
program
|
|
118
|
+
.command('stop')
|
|
119
|
+
.description('Stop the running BrowserTrack daemon')
|
|
120
|
+
.action(async () => {
|
|
121
|
+
const pid = readPid();
|
|
122
|
+
if (!pid || !isProcessRunning(pid)) {
|
|
123
|
+
console.log('[BrowserTrack] Daemon is not currently running.');
|
|
124
|
+
removePid();
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
process.kill(pid, 'SIGTERM');
|
|
130
|
+
removePid();
|
|
131
|
+
console.log(`[BrowserTrack] Stopped daemon (PID: ${pid}).`);
|
|
132
|
+
} catch (err: any) {
|
|
133
|
+
console.error(`[BrowserTrack] Failed to stop daemon:`, err.message);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// 3. STATUS
|
|
138
|
+
program
|
|
139
|
+
.command('status')
|
|
140
|
+
.description('Check if the BrowserTrack daemon is running and view active sessions')
|
|
141
|
+
.action(async () => {
|
|
142
|
+
const pid = readPid();
|
|
143
|
+
const isRunning = pid ? isProcessRunning(pid) : false;
|
|
144
|
+
const config = getDaemonConfig();
|
|
145
|
+
|
|
146
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
147
|
+
console.log(` Status: ${isRunning ? 'š¢ RUNNING' : 'āŖ STOPPED'}${isRunning ? ` (PID: ${pid})` : ''}`);
|
|
148
|
+
console.log(` Endpoint: http://${config.host}:${config.port}`);
|
|
149
|
+
console.log(` Database: ${config.dbPath}`);
|
|
150
|
+
|
|
151
|
+
if (fs.existsSync(config.dbPath)) {
|
|
152
|
+
try {
|
|
153
|
+
const db = new StorageDB(config.dbPath);
|
|
154
|
+
const projects = db.listProjects();
|
|
155
|
+
const sessions = db.listSessions(undefined, true);
|
|
156
|
+
const incidents = db.listIncidents({ limit: 100 });
|
|
157
|
+
const openIncidents = incidents.filter((i) => i.status === 'OPEN');
|
|
158
|
+
|
|
159
|
+
console.log(` Projects: ${projects.length}`);
|
|
160
|
+
console.log(` Live Sessions: ${sessions.length}`);
|
|
161
|
+
console.log(` Open Errors: ${openIncidents.length} (${incidents.length} total recorded)`);
|
|
162
|
+
db.close();
|
|
163
|
+
} catch {}
|
|
164
|
+
}
|
|
165
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// 4. PROJECTS
|
|
169
|
+
const projectCommand = program.command('project').description('Manage tracked project mappings');
|
|
170
|
+
|
|
171
|
+
projectCommand
|
|
172
|
+
.command('add <name>')
|
|
173
|
+
.description('Register a project with origin and filesystem path')
|
|
174
|
+
.requiredOption('-o, --origin <origin>', 'Project origin (e.g. http://localhost:5173)')
|
|
175
|
+
.option('-p, --path <path>', 'Filesystem path (e.g. /path/to/project)')
|
|
176
|
+
.action((name, options) => {
|
|
177
|
+
const config = getDaemonConfig();
|
|
178
|
+
const db = new StorageDB(config.dbPath);
|
|
179
|
+
const resolvedPath = options.path ? path.resolve(options.path) : undefined;
|
|
180
|
+
|
|
181
|
+
const proj = db.upsertProject({
|
|
182
|
+
id: `proj_${name}`,
|
|
183
|
+
name,
|
|
184
|
+
origin: options.origin,
|
|
185
|
+
path: resolvedPath,
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
console.log(`[BrowserTrack] Registered project '${proj.name}':`);
|
|
189
|
+
console.log(` ID: ${proj.id}`);
|
|
190
|
+
console.log(` Origin: ${proj.origin}`);
|
|
191
|
+
console.log(` Path: ${proj.path || '(none)'}`);
|
|
192
|
+
db.close();
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
program
|
|
196
|
+
.command('projects')
|
|
197
|
+
.description('List all tracked projects')
|
|
198
|
+
.action(() => {
|
|
199
|
+
const config = getDaemonConfig();
|
|
200
|
+
const db = new StorageDB(config.dbPath);
|
|
201
|
+
const projects = db.listProjects();
|
|
202
|
+
|
|
203
|
+
if (projects.length === 0) {
|
|
204
|
+
console.log('[BrowserTrack] No projects registered yet. Projects will be auto-detected upon browser connection.');
|
|
205
|
+
} else {
|
|
206
|
+
console.log('\nTracked Projects:');
|
|
207
|
+
for (const p of projects) {
|
|
208
|
+
console.log(` ⢠${p.name.padEnd(16)} | ${p.origin.padEnd(26)} | ${p.path || '(auto-detected)'}`);
|
|
209
|
+
}
|
|
210
|
+
console.log('');
|
|
211
|
+
}
|
|
212
|
+
db.close();
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// 5. ERRORS / INCIDENTS
|
|
216
|
+
program
|
|
217
|
+
.command('errors')
|
|
218
|
+
.description('List recorded runtime errors and incidents')
|
|
219
|
+
.option('-p, --project <project>', 'Filter by project name or ID')
|
|
220
|
+
.option('-s, --status <status>', 'Filter by status (OPEN, VERIFIED, FAILED, etc.)')
|
|
221
|
+
.option('-l, --limit <number>', 'Limit result count', '20')
|
|
222
|
+
.action((options) => {
|
|
223
|
+
const config = getDaemonConfig();
|
|
224
|
+
const db = new StorageDB(config.dbPath);
|
|
225
|
+
const limit = parseInt(options.limit, 10);
|
|
226
|
+
const incidents = db.listIncidents({
|
|
227
|
+
projectId: options.project,
|
|
228
|
+
status: options.status,
|
|
229
|
+
limit,
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
if (incidents.length === 0) {
|
|
233
|
+
console.log('[BrowserTrack] No incidents found matching the filter.');
|
|
234
|
+
} else {
|
|
235
|
+
console.log(`\nIncidents (${incidents.length}):`);
|
|
236
|
+
for (const inc of incidents) {
|
|
237
|
+
const statusBadge =
|
|
238
|
+
inc.status === 'OPEN'
|
|
239
|
+
? 'š“ OPEN'
|
|
240
|
+
: inc.status === 'VERIFIED'
|
|
241
|
+
? 'š¢ VERIFIED'
|
|
242
|
+
: inc.status === 'FAILED'
|
|
243
|
+
? 'ā FAILED'
|
|
244
|
+
: `āŖ ${inc.status}`;
|
|
245
|
+
console.log(` ${inc.id.padEnd(12)} [${statusBadge}] (${inc.occurrences}x) ${inc.type}: ${inc.message}`);
|
|
246
|
+
console.log(` Source: ${inc.source.file}:${inc.source.line} | Route: ${inc.route}`);
|
|
247
|
+
}
|
|
248
|
+
console.log('');
|
|
249
|
+
}
|
|
250
|
+
db.close();
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
// 6. VISUAL NOTES
|
|
254
|
+
const noteCmd = program.command('note').description('Inspect or manage visual development notes');
|
|
255
|
+
|
|
256
|
+
noteCmd
|
|
257
|
+
.command('show <noteId>')
|
|
258
|
+
.description('Show full context for a specific visual note')
|
|
259
|
+
.action((noteId) => {
|
|
260
|
+
const config = getDaemonConfig();
|
|
261
|
+
const db = new StorageDB(config.dbPath);
|
|
262
|
+
const note = db.getNote(noteId);
|
|
263
|
+
|
|
264
|
+
if (!note) {
|
|
265
|
+
console.log(`[BrowserTrack] Note '${noteId}' not found.`);
|
|
266
|
+
} else {
|
|
267
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
268
|
+
console.log(` š Visual Note: ${note.id} [${note.status}]`);
|
|
269
|
+
console.log(` š Route: ${note.route} (${note.url})`);
|
|
270
|
+
console.log(` š Viewport: ${note.viewport.width} Ć ${note.viewport.height} (dpr: ${note.viewport.devicePixelRatio})`);
|
|
271
|
+
if (note.target) {
|
|
272
|
+
console.log(` šÆ Target: ${note.target.selector}`);
|
|
273
|
+
console.log(` Bounds: x:${note.target.boundingRect.x}, y:${note.target.boundingRect.y}, ${note.target.boundingRect.width}Ć${note.target.boundingRect.height}`);
|
|
274
|
+
}
|
|
275
|
+
console.log(` š¬ Note: "${note.message}"`);
|
|
276
|
+
if (note.screenshots?.original) {
|
|
277
|
+
console.log(` š¼ļø Screenshot: ${note.screenshots.original}`);
|
|
278
|
+
}
|
|
279
|
+
console.log(` š Created: ${note.createdAt}`);
|
|
280
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
281
|
+
}
|
|
282
|
+
db.close();
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
noteCmd
|
|
286
|
+
.command('resolve <noteId>')
|
|
287
|
+
.description('Mark a visual note as resolved')
|
|
288
|
+
.action((noteId) => {
|
|
289
|
+
const config = getDaemonConfig();
|
|
290
|
+
const db = new StorageDB(config.dbPath);
|
|
291
|
+
const note = db.getNote(noteId);
|
|
292
|
+
|
|
293
|
+
if (!note) {
|
|
294
|
+
console.log(`[BrowserTrack] Note '${noteId}' not found.`);
|
|
295
|
+
} else {
|
|
296
|
+
db.updateNoteStatus(noteId, 'RESOLVED');
|
|
297
|
+
console.log(`[BrowserTrack] Marked note '${noteId}' as RESOLVED.`);
|
|
298
|
+
}
|
|
299
|
+
db.close();
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
noteCmd
|
|
303
|
+
.command('reopen <noteId>')
|
|
304
|
+
.description('Reopen a visual note')
|
|
305
|
+
.action((noteId) => {
|
|
306
|
+
const config = getDaemonConfig();
|
|
307
|
+
const db = new StorageDB(config.dbPath);
|
|
308
|
+
const note = db.getNote(noteId);
|
|
309
|
+
|
|
310
|
+
if (!note) {
|
|
311
|
+
console.log(`[BrowserTrack] Note '${noteId}' not found.`);
|
|
312
|
+
} else {
|
|
313
|
+
db.updateNoteStatus(noteId, 'OPEN');
|
|
314
|
+
console.log(`[BrowserTrack] Reopened note '${noteId}' (status: OPEN).`);
|
|
315
|
+
}
|
|
316
|
+
db.close();
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
program
|
|
320
|
+
.command('notes')
|
|
321
|
+
.description('List visual development notes')
|
|
322
|
+
.option('-p, --project <project>', 'Filter by project name or ID')
|
|
323
|
+
.option('-s, --status <status>', 'Filter by status (OPEN, RESOLVED, etc.)')
|
|
324
|
+
.option('-l, --limit <number>', 'Limit result count', '20')
|
|
325
|
+
.action((options) => {
|
|
326
|
+
const config = getDaemonConfig();
|
|
327
|
+
const db = new StorageDB(config.dbPath);
|
|
328
|
+
const limit = parseInt(options.limit, 10);
|
|
329
|
+
const notes = db.listNotes({
|
|
330
|
+
projectId: options.project,
|
|
331
|
+
status: options.status,
|
|
332
|
+
limit,
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
if (notes.length === 0) {
|
|
336
|
+
console.log('[BrowserTrack] No visual notes found.');
|
|
337
|
+
} else {
|
|
338
|
+
console.log(`\nVisual Notes (${notes.length}):`);
|
|
339
|
+
for (const n of notes) {
|
|
340
|
+
const badge = n.status === 'OPEN' ? 'š” OPEN' : n.status === 'RESOLVED' ? 'š¢ RESOLVED' : `āŖ ${n.status}`;
|
|
341
|
+
console.log(` ${n.id.padEnd(12)} [${badge}] Route: ${n.route.padEnd(16)} | Target: ${n.target?.selector || n.type}`);
|
|
342
|
+
console.log(` Note: "${n.message}" (${n.viewport.width}Ć${n.viewport.height})`);
|
|
343
|
+
}
|
|
344
|
+
console.log('');
|
|
345
|
+
}
|
|
346
|
+
db.close();
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
// 7. INBOX (Errors + Visual Notes)
|
|
350
|
+
program
|
|
351
|
+
.command('inbox')
|
|
352
|
+
.description('View combined developer inbox with active runtime errors and visual notes')
|
|
353
|
+
.option('-p, --project <project>', 'Filter by project name or ID')
|
|
354
|
+
.action((options) => {
|
|
355
|
+
const config = getDaemonConfig();
|
|
356
|
+
const db = new StorageDB(config.dbPath);
|
|
357
|
+
const incidents = db.listIncidents({ projectId: options.project, status: 'OPEN', limit: 20 });
|
|
358
|
+
const notes = db.listNotes({ projectId: options.project, status: 'OPEN', limit: 20 });
|
|
359
|
+
|
|
360
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
361
|
+
console.log(` š„ Browser Development Inbox ${options.project ? `(${options.project})` : ''}`);
|
|
362
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
363
|
+
|
|
364
|
+
if (incidents.length === 0 && notes.length === 0) {
|
|
365
|
+
console.log(' ⨠All clear! No open errors or visual notes.');
|
|
366
|
+
} else {
|
|
367
|
+
if (incidents.length > 0) {
|
|
368
|
+
console.log(`\n šØ Runtime Errors (${incidents.length}):`);
|
|
369
|
+
for (const inc of incidents) {
|
|
370
|
+
console.log(` ⢠${inc.id} (${inc.occurrences}x) ${inc.type}: ${inc.message}`);
|
|
371
|
+
console.log(` Route: ${inc.route} | Source: ${inc.source.file}:${inc.source.line}`);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (notes.length > 0) {
|
|
376
|
+
console.log(`\n š Visual Notes (${notes.length}):`);
|
|
377
|
+
for (const n of notes) {
|
|
378
|
+
console.log(` ⢠${n.id} on ${n.route} (${n.viewport.width}Ć${n.viewport.height})`);
|
|
379
|
+
console.log(` Target: ${n.target?.selector || n.type} | Note: "${n.message}"`);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
console.log('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
384
|
+
db.close();
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
// 6. CLEAR
|
|
388
|
+
program
|
|
389
|
+
.command('clear')
|
|
390
|
+
.description('Clear all stored incidents, events, and sessions from the database')
|
|
391
|
+
.action(() => {
|
|
392
|
+
const config = getDaemonConfig();
|
|
393
|
+
const db = new StorageDB(config.dbPath);
|
|
394
|
+
db.clearAll();
|
|
395
|
+
console.log('[BrowserTrack] Database cleared.');
|
|
396
|
+
db.close();
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
// 7. MCP SERVER
|
|
400
|
+
program
|
|
401
|
+
.command('mcp')
|
|
402
|
+
.description('Launch the Model Context Protocol (MCP) server over stdio')
|
|
403
|
+
.action(async () => {
|
|
404
|
+
try {
|
|
405
|
+
const server = createMcpServer();
|
|
406
|
+
await server.startStdio();
|
|
407
|
+
} catch (err: any) {
|
|
408
|
+
console.error('[BrowserTrack] MCP Server error:', err?.message);
|
|
409
|
+
process.exit(1);
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
program.parse(process.argv);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@browserdiag/client",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Lightweight browser diagnostics client for BrowserDiag",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"require": "./dist/index.cjs",
|
|
13
|
+
"types": "./dist/index.d.ts"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsup src/index.ts --format esm,cjs,iife --global-name BrowserDiag --dts --clean",
|
|
18
|
+
"dev": "tsup src/index.ts --format esm,cjs,iife --global-name BrowserDiag --dts --watch",
|
|
19
|
+
"clean": "rm -rf dist"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@browserdiag/core": "^0.1.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"tsup": "^8.3.6",
|
|
26
|
+
"typescript": "^5.7.3"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Breadcrumb } from '@browserdiag/core';
|
|
2
|
+
|
|
3
|
+
export class BreadcrumbBuffer {
|
|
4
|
+
private capacity: number;
|
|
5
|
+
private buffer: Breadcrumb[] = [];
|
|
6
|
+
|
|
7
|
+
constructor(capacity = 50) {
|
|
8
|
+
this.capacity = capacity;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
public add(breadcrumb: Omit<Breadcrumb, 'timestamp'> & { timestamp?: number }): void {
|
|
12
|
+
const item: Breadcrumb = {
|
|
13
|
+
...breadcrumb,
|
|
14
|
+
timestamp: breadcrumb.timestamp || Date.now(),
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
this.buffer.push(item);
|
|
18
|
+
if (this.buffer.length > this.capacity) {
|
|
19
|
+
this.buffer.shift();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public getRecent(): Breadcrumb[] {
|
|
24
|
+
return [...this.buffer];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public clear(): void {
|
|
28
|
+
this.buffer = [];
|
|
29
|
+
}
|
|
30
|
+
}
|