browsertrack 0.2.1 → 0.2.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/AGENTS.md +9 -6
- package/README.md +2 -0
- package/dist/{chunk-INXDWPJW.js → chunk-4HRLW6YF.js} +161 -106
- package/dist/chunk-4HRLW6YF.js.map +1 -0
- package/dist/{chunk-3HOXPTM2.js → chunk-AYSVE6NG.js} +808 -53
- package/dist/chunk-AYSVE6NG.js.map +1 -0
- package/dist/{chunk-6VA7GBAO.js → chunk-QRZ57ME3.js} +70 -2
- package/dist/chunk-QRZ57ME3.js.map +1 -0
- package/dist/{chunk-464D4U2U.js → chunk-TWEYRBDU.js} +279 -43
- package/dist/chunk-TWEYRBDU.js.map +1 -0
- package/dist/cli/index.js +1038 -545
- package/dist/cli/index.js.map +1 -1
- package/dist/client/index.cjs +184 -104
- package/dist/client/index.js +2 -2
- package/dist/client.iife.js +9 -9
- package/dist/core/index.d.ts +24 -1
- package/dist/core/index.js +9 -1
- package/dist/daemon/index.d.ts +2 -2
- package/dist/daemon/index.js +6 -8
- package/dist/index.d.ts +2 -2
- package/dist/index.js +14 -7
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.js +7 -4
- package/dist/{server-DiVmTrIR.d.ts → server-DjV7RWQM.d.ts} +9 -1
- package/docs/cli.md +4 -1
- package/docs/getting-started.md +60 -6
- package/docs/mcp-reference.md +42 -0
- package/package.json +1 -1
- package/packages/cli/src/index.ts +247 -151
- package/packages/client/src/interceptors/navigation.ts +38 -26
- package/packages/client/src/interceptors/network.ts +22 -17
- package/packages/client/src/notes/inspector.ts +81 -48
- package/packages/client/src/source/resolver.ts +9 -3
- package/packages/client/src/transport/websocket.ts +23 -18
- package/packages/core/src/index.ts +1 -0
- package/packages/core/src/safety.ts +86 -0
- package/packages/daemon/src/server/daemon.ts +7 -1
- package/packages/daemon/src/server/http.ts +125 -5
- package/packages/daemon/src/server/ws.ts +33 -29
- package/packages/daemon/src/storage/db.ts +57 -35
- package/packages/mcp/src/handlers.ts +114 -45
- package/packages/mcp/src/server.ts +202 -2
- package/test/core/safety.test.ts +106 -0
- package/test/daemon/storage.test.ts +36 -0
- package/test/e2e/daemon-mcp-e2e.test.ts +10 -0
- package/test/mcp/auto-start.test.ts +87 -0
- package/dist/chunk-3HOXPTM2.js.map +0 -1
- package/dist/chunk-464D4U2U.js.map +0 -1
- package/dist/chunk-6VA7GBAO.js.map +0 -1
- package/dist/chunk-7OCOQGDN.js +0 -635
- package/dist/chunk-7OCOQGDN.js.map +0 -1
- package/dist/chunk-INXDWPJW.js.map +0 -1
package/AGENTS.md
CHANGED
|
@@ -99,10 +99,9 @@ When an error or unexpected behavior occurs in the frontend:
|
|
|
99
99
|
|
|
100
100
|
If BrowserTrack is not yet active in your project:
|
|
101
101
|
|
|
102
|
-
### 1.
|
|
103
|
-
|
|
104
|
-
browsertrack start
|
|
105
|
-
```
|
|
102
|
+
### 1. Zero-Config Daemon Auto-Boot (Singleton Daemon)
|
|
103
|
+
You do **not** need to manually run `browsertrack start`! When your IDE connects to BrowserTrack via MCP (`browsertrack mcp`), the background HTTP/WebSocket server is launched **automatically** on `http://127.0.0.1:7331` as a **singleton daemon**. Multiple windows, workspaces, or sub-agent sessions seamlessly share this single daemon without spawning duplicates or conflicting on ports.
|
|
104
|
+
*(You can still manually run `browsertrack start` or `browsertrack stop` if you want a standalone terminal process).*
|
|
106
105
|
|
|
107
106
|
### 2. Connect the Browser Client
|
|
108
107
|
- **Via HTML script tag:**
|
|
@@ -116,17 +115,21 @@ browsertrack start
|
|
|
116
115
|
|
|
117
116
|
### 3. MCP Configuration
|
|
118
117
|
Ensure BrowserTrack MCP is configured in your editor:
|
|
118
|
+
|
|
119
119
|
```json
|
|
120
120
|
{
|
|
121
121
|
"mcpServers": {
|
|
122
122
|
"browsertrack": {
|
|
123
|
-
"command": "
|
|
124
|
-
"args": ["mcp"]
|
|
123
|
+
"command": "npx",
|
|
124
|
+
"args": ["-y", "browsertrack@latest", "mcp"]
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
```
|
|
129
129
|
|
|
130
|
+
> **Note on PATH errors (`executable file not found in $PATH`):**
|
|
131
|
+
> If your IDE cannot find `npx` or `browsertrack` in `$PATH` (common in GUI apps on macOS using NVM or Homebrew), provide the absolute path to `npx` (e.g. `~/.nvm/versions/node/v20.x.x/bin/npx`) along with `PATH` in the `env` object.
|
|
132
|
+
|
|
130
133
|
---
|
|
131
134
|
|
|
132
135
|
## 💡 Best Practices for AI Agents
|
package/README.md
CHANGED
|
@@ -17,6 +17,8 @@ browsertrack start
|
|
|
17
17
|
|
|
18
18
|
This starts the local diagnostics server at `http://127.0.0.1:7331` (WebSocket at `ws://127.0.0.1:7331`).
|
|
19
19
|
|
|
20
|
+
> **⚡ Zero-Config Auto-Start**: If you configure BrowserTrack via MCP in your AI editor (Cursor, Antigravity, Claude Code), you do **not** need to manually run `browsertrack start`! The MCP server automatically launches and manages a **single background daemon (singleton)** on port `7331`. Multiple windows or sessions share this single daemon without duplicate processes or port conflicts.
|
|
21
|
+
|
|
20
22
|
### 2. Connect Your Web Project
|
|
21
23
|
|
|
22
24
|
#### Option A: Zero-install script tag (Plain HTML / Vite / Next.js)
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getSemanticSelector,
|
|
3
3
|
redactUrl,
|
|
4
|
+
safeJsonStringify,
|
|
4
5
|
truncate
|
|
5
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-QRZ57ME3.js";
|
|
6
7
|
|
|
7
8
|
// packages/client/src/breadcrumbs.ts
|
|
8
9
|
var BreadcrumbBuffer = class {
|
|
@@ -78,7 +79,9 @@ function resolveReactComponent(el) {
|
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
81
|
let curr = hostFiber;
|
|
81
|
-
|
|
82
|
+
let depth = 0;
|
|
83
|
+
while (curr && depth < 50) {
|
|
84
|
+
depth++;
|
|
82
85
|
const type = curr.type;
|
|
83
86
|
const name = getReactComponentName(type);
|
|
84
87
|
if (name && !name.startsWith("html:") && name !== "Fragment") {
|
|
@@ -133,7 +136,9 @@ function resolveVueComponent(el) {
|
|
|
133
136
|
const sourceFile = type.__file;
|
|
134
137
|
const hierarchy = [];
|
|
135
138
|
let curr = vueParent;
|
|
136
|
-
|
|
139
|
+
let depth = 0;
|
|
140
|
+
while (curr && depth < 50) {
|
|
141
|
+
depth++;
|
|
137
142
|
const cType = curr.type || {};
|
|
138
143
|
const cName = cType.name || cType.__name || cType.displayName;
|
|
139
144
|
if (cName && !hierarchy.includes(cName)) {
|
|
@@ -170,7 +175,9 @@ function resolveVueComponent(el) {
|
|
|
170
175
|
function resolveSvelteComponent(el) {
|
|
171
176
|
try {
|
|
172
177
|
let curr = el;
|
|
173
|
-
|
|
178
|
+
let depth = 0;
|
|
179
|
+
while (curr && depth < 50) {
|
|
180
|
+
depth++;
|
|
174
181
|
const meta = curr.__svelte_meta;
|
|
175
182
|
if (meta && meta.loc) {
|
|
176
183
|
return {
|
|
@@ -709,12 +716,16 @@ function setupConsoleInterceptors(onConsole) {
|
|
|
709
716
|
function setupNavigationInterceptors(onNavigation) {
|
|
710
717
|
if (typeof window === "undefined" || typeof history === "undefined") return () => {
|
|
711
718
|
};
|
|
712
|
-
let currentUrl =
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
719
|
+
let currentUrl = "";
|
|
720
|
+
try {
|
|
721
|
+
currentUrl = redactUrl(window.location.href);
|
|
722
|
+
onNavigation({
|
|
723
|
+
to: currentUrl,
|
|
724
|
+
type: "initial",
|
|
725
|
+
timestamp: Date.now()
|
|
726
|
+
});
|
|
727
|
+
} catch {
|
|
728
|
+
}
|
|
718
729
|
const originalPushState = history.pushState;
|
|
719
730
|
const originalReplaceState = history.replaceState;
|
|
720
731
|
history.pushState = function(data, unused, url) {
|
|
@@ -750,26 +761,32 @@ function setupNavigationInterceptors(onNavigation) {
|
|
|
750
761
|
return result;
|
|
751
762
|
};
|
|
752
763
|
const onPopState = () => {
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
764
|
+
try {
|
|
765
|
+
const from = currentUrl;
|
|
766
|
+
const to = redactUrl(window.location.href);
|
|
767
|
+
currentUrl = to;
|
|
768
|
+
onNavigation({
|
|
769
|
+
from,
|
|
770
|
+
to,
|
|
771
|
+
type: "popstate",
|
|
772
|
+
timestamp: Date.now()
|
|
773
|
+
});
|
|
774
|
+
} catch {
|
|
775
|
+
}
|
|
762
776
|
};
|
|
763
777
|
const onHashChange = () => {
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
778
|
+
try {
|
|
779
|
+
const from = currentUrl;
|
|
780
|
+
const to = redactUrl(window.location.href);
|
|
781
|
+
currentUrl = to;
|
|
782
|
+
onNavigation({
|
|
783
|
+
from,
|
|
784
|
+
to,
|
|
785
|
+
type: "hashchange",
|
|
786
|
+
timestamp: Date.now()
|
|
787
|
+
});
|
|
788
|
+
} catch {
|
|
789
|
+
}
|
|
773
790
|
};
|
|
774
791
|
window.addEventListener("popstate", onPopState);
|
|
775
792
|
window.addEventListener("hashchange", onHashChange);
|
|
@@ -806,31 +823,38 @@ function setupNetworkInterceptors(onNetwork) {
|
|
|
806
823
|
urlStr = "unknown_url";
|
|
807
824
|
}
|
|
808
825
|
const safeUrl = redactUrl(urlStr);
|
|
826
|
+
let response;
|
|
809
827
|
try {
|
|
810
|
-
|
|
811
|
-
const durationMs = Date.now() - startTime;
|
|
812
|
-
onNetwork({
|
|
813
|
-
url: safeUrl,
|
|
814
|
-
method,
|
|
815
|
-
status: response.status,
|
|
816
|
-
statusText: response.statusText,
|
|
817
|
-
durationMs,
|
|
818
|
-
timestamp: startTime
|
|
819
|
-
});
|
|
820
|
-
return response;
|
|
828
|
+
response = await originalFetch.apply(window, [input, init2]);
|
|
821
829
|
} catch (err) {
|
|
822
|
-
const
|
|
830
|
+
const durationMs2 = Date.now() - startTime;
|
|
823
831
|
const isAbort = err?.name === "AbortError";
|
|
832
|
+
try {
|
|
833
|
+
onNetwork({
|
|
834
|
+
url: safeUrl,
|
|
835
|
+
method,
|
|
836
|
+
durationMs: durationMs2,
|
|
837
|
+
error: err?.message || "Network request failed",
|
|
838
|
+
aborted: isAbort,
|
|
839
|
+
timestamp: startTime
|
|
840
|
+
});
|
|
841
|
+
} catch {
|
|
842
|
+
}
|
|
843
|
+
throw err;
|
|
844
|
+
}
|
|
845
|
+
const durationMs = Date.now() - startTime;
|
|
846
|
+
try {
|
|
824
847
|
onNetwork({
|
|
825
848
|
url: safeUrl,
|
|
826
849
|
method,
|
|
850
|
+
status: response.status,
|
|
851
|
+
statusText: response.statusText,
|
|
827
852
|
durationMs,
|
|
828
|
-
error: err?.message || "Network request failed",
|
|
829
|
-
aborted: isAbort,
|
|
830
853
|
timestamp: startTime
|
|
831
854
|
});
|
|
832
|
-
|
|
855
|
+
} catch {
|
|
833
856
|
}
|
|
857
|
+
return response;
|
|
834
858
|
};
|
|
835
859
|
cleanups.push(() => {
|
|
836
860
|
window.fetch = originalFetch;
|
|
@@ -1216,15 +1240,18 @@ var WebSocketTransport = class {
|
|
|
1216
1240
|
}
|
|
1217
1241
|
}
|
|
1218
1242
|
send(payload) {
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1243
|
+
try {
|
|
1244
|
+
const raw = safeJsonStringify(payload);
|
|
1245
|
+
if (this.isConnected()) {
|
|
1246
|
+
try {
|
|
1247
|
+
this.ws.send(raw);
|
|
1248
|
+
} catch {
|
|
1249
|
+
this.enqueue(raw);
|
|
1250
|
+
}
|
|
1251
|
+
} else {
|
|
1224
1252
|
this.enqueue(raw);
|
|
1225
1253
|
}
|
|
1226
|
-
}
|
|
1227
|
-
this.enqueue(raw);
|
|
1254
|
+
} catch {
|
|
1228
1255
|
}
|
|
1229
1256
|
}
|
|
1230
1257
|
enqueue(raw) {
|
|
@@ -1249,17 +1276,17 @@ var WebSocketTransport = class {
|
|
|
1249
1276
|
}
|
|
1250
1277
|
sendHello() {
|
|
1251
1278
|
if (typeof window === "undefined") return;
|
|
1252
|
-
const hello = {
|
|
1253
|
-
type: "hello",
|
|
1254
|
-
origin: window.location.origin,
|
|
1255
|
-
url: window.location.href,
|
|
1256
|
-
title: document.title,
|
|
1257
|
-
userAgent: navigator.userAgent,
|
|
1258
|
-
timestamp: Date.now(),
|
|
1259
|
-
projectId: this.projectId
|
|
1260
|
-
};
|
|
1261
1279
|
try {
|
|
1262
|
-
|
|
1280
|
+
const hello = {
|
|
1281
|
+
type: "hello",
|
|
1282
|
+
origin: window.location.origin,
|
|
1283
|
+
url: window.location.href,
|
|
1284
|
+
title: document.title,
|
|
1285
|
+
userAgent: navigator.userAgent,
|
|
1286
|
+
timestamp: Date.now(),
|
|
1287
|
+
projectId: this.projectId
|
|
1288
|
+
};
|
|
1289
|
+
this.ws.send(safeJsonStringify(hello));
|
|
1263
1290
|
} catch {
|
|
1264
1291
|
}
|
|
1265
1292
|
}
|
|
@@ -2247,9 +2274,15 @@ var NoteInspector = class {
|
|
|
2247
2274
|
this.markersContainer.innerHTML = "";
|
|
2248
2275
|
if (!this.showMarkers || this.isHidden) return;
|
|
2249
2276
|
const currentPath = window.location.pathname;
|
|
2250
|
-
const activeNotes = this.savedNotes.filter(
|
|
2251
|
-
(n
|
|
2252
|
-
|
|
2277
|
+
const activeNotes = this.savedNotes.filter((n) => {
|
|
2278
|
+
if (n.status !== "OPEN") return false;
|
|
2279
|
+
if (!n.route) return true;
|
|
2280
|
+
if (n.route === currentPath) return true;
|
|
2281
|
+
if (n.route.includes("#") || n.route.includes("?")) {
|
|
2282
|
+
return window.location.href.includes(n.route);
|
|
2283
|
+
}
|
|
2284
|
+
return false;
|
|
2285
|
+
});
|
|
2253
2286
|
const pageNotes = [];
|
|
2254
2287
|
activeNotes.forEach((note, index) => {
|
|
2255
2288
|
if (note.type === "page") {
|
|
@@ -2474,65 +2507,83 @@ var NoteInspector = class {
|
|
|
2474
2507
|
}
|
|
2475
2508
|
setupListeners() {
|
|
2476
2509
|
const onMouseMove = (e) => {
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
this.
|
|
2481
|
-
|
|
2482
|
-
}
|
|
2483
|
-
if (e.altKey || this.activeMode === "element") {
|
|
2484
|
-
const target = document.elementFromPoint(e.clientX, e.clientY);
|
|
2485
|
-
if (target && target !== this.container && !this.container?.contains(target)) {
|
|
2486
|
-
this.hoveredElement = target;
|
|
2487
|
-
this.updateHighlight(target);
|
|
2510
|
+
try {
|
|
2511
|
+
if (this.isHidden || this.modalOverlay || this.cardOverlay || this.isDraggingRegion || this.activeMode === "region") return;
|
|
2512
|
+
const path = e.composedPath ? e.composedPath() : [];
|
|
2513
|
+
if (this.container && path.includes(this.container)) {
|
|
2514
|
+
this.hideHighlight();
|
|
2488
2515
|
return;
|
|
2489
2516
|
}
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2517
|
+
if (e.altKey || this.activeMode === "element") {
|
|
2518
|
+
const target = document.elementFromPoint(e.clientX, e.clientY);
|
|
2519
|
+
if (target && target !== this.container && !this.container?.contains(target)) {
|
|
2520
|
+
this.hoveredElement = target;
|
|
2521
|
+
this.updateHighlight(target);
|
|
2522
|
+
return;
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
if (!e.altKey && this.activeMode !== "element") {
|
|
2526
|
+
this.hideHighlight();
|
|
2527
|
+
}
|
|
2528
|
+
} catch {
|
|
2493
2529
|
}
|
|
2494
2530
|
};
|
|
2495
2531
|
const onClick = (e) => {
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
e.
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
this.
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
this.
|
|
2532
|
+
try {
|
|
2533
|
+
if (this.isHidden || this.modalOverlay || this.cardOverlay) return;
|
|
2534
|
+
const path = e.composedPath ? e.composedPath() : [];
|
|
2535
|
+
if (this.container && path.includes(this.container)) {
|
|
2536
|
+
return;
|
|
2537
|
+
}
|
|
2538
|
+
if (this.activeMode === "region") return;
|
|
2539
|
+
if (e.altKey || this.activeMode === "element") {
|
|
2540
|
+
e.preventDefault();
|
|
2541
|
+
e.stopPropagation();
|
|
2542
|
+
const target = this.hoveredElement || document.elementFromPoint(e.clientX, e.clientY);
|
|
2543
|
+
if (target && target !== this.container && !this.container?.contains(target)) {
|
|
2544
|
+
this.selectedElement = target;
|
|
2545
|
+
this.openNoteEditor(target, "element");
|
|
2546
|
+
if (this.activeMode === "element" && !this.activeScenario) {
|
|
2547
|
+
this.setMode("idle");
|
|
2548
|
+
}
|
|
2511
2549
|
}
|
|
2512
2550
|
}
|
|
2551
|
+
} catch {
|
|
2513
2552
|
}
|
|
2514
2553
|
};
|
|
2515
2554
|
const onKeyDown = (e) => {
|
|
2516
|
-
|
|
2517
|
-
if (
|
|
2518
|
-
this.
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
if (
|
|
2522
|
-
this.
|
|
2555
|
+
try {
|
|
2556
|
+
if (e.key === "Escape") {
|
|
2557
|
+
if (this.cardOverlay && this.cardOverlay.parentElement && this.shadowRoot) {
|
|
2558
|
+
this.shadowRoot.removeChild(this.cardOverlay);
|
|
2559
|
+
this.cardOverlay = null;
|
|
2560
|
+
} else if (this.activeMode === "region" || this.activeMode === "element") {
|
|
2561
|
+
if (!this.activeScenario) {
|
|
2562
|
+
this.setMode("idle");
|
|
2563
|
+
}
|
|
2523
2564
|
}
|
|
2524
2565
|
}
|
|
2566
|
+
} catch {
|
|
2525
2567
|
}
|
|
2526
2568
|
};
|
|
2527
2569
|
const onKeyUp = (e) => {
|
|
2528
|
-
|
|
2529
|
-
this.
|
|
2570
|
+
try {
|
|
2571
|
+
if (e.key === "Alt" && !this.modalOverlay && !this.cardOverlay && this.activeMode !== "element") {
|
|
2572
|
+
this.hideHighlight();
|
|
2573
|
+
}
|
|
2574
|
+
} catch {
|
|
2530
2575
|
}
|
|
2531
2576
|
};
|
|
2532
2577
|
const onScrollOrResize = () => {
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2578
|
+
try {
|
|
2579
|
+
requestAnimationFrame(() => {
|
|
2580
|
+
try {
|
|
2581
|
+
this.updateMarkerPositions();
|
|
2582
|
+
} catch {
|
|
2583
|
+
}
|
|
2584
|
+
});
|
|
2585
|
+
} catch {
|
|
2586
|
+
}
|
|
2536
2587
|
};
|
|
2537
2588
|
window.addEventListener("mousemove", onMouseMove, { capture: true, passive: true });
|
|
2538
2589
|
window.addEventListener("click", onClick, { capture: true });
|
|
@@ -2994,7 +3045,11 @@ var NoteInspector = class {
|
|
|
2994
3045
|
region.width,
|
|
2995
3046
|
region.height
|
|
2996
3047
|
);
|
|
2997
|
-
|
|
3048
|
+
try {
|
|
3049
|
+
resolve(canvas.toDataURL("image/png"));
|
|
3050
|
+
} catch {
|
|
3051
|
+
resolve(dataUrl);
|
|
3052
|
+
}
|
|
2998
3053
|
};
|
|
2999
3054
|
img.onerror = () => resolve(dataUrl);
|
|
3000
3055
|
img.src = dataUrl;
|
|
@@ -3284,4 +3339,4 @@ export {
|
|
|
3284
3339
|
init,
|
|
3285
3340
|
getClient
|
|
3286
3341
|
};
|
|
3287
|
-
//# sourceMappingURL=chunk-
|
|
3342
|
+
//# sourceMappingURL=chunk-4HRLW6YF.js.map
|