cobro-mcp 0.9.3 → 0.9.4
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/CHANGELOG.md +4 -0
- package/dist/overlay.js +36 -21
- package/dist/server.js +45 -23
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,10 @@ All notable changes to this project are documented here. The format follows [Kee
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.9.4] — 2026-09-22
|
|
8
|
+
### Fixed
|
|
9
|
+
- Source locations are no longer dropped when the project directory itself sits under a node_modules path (library detection is now relative to the project root).
|
|
10
|
+
|
|
7
11
|
## [0.9.3] — 2026-09-22
|
|
8
12
|
### Changed
|
|
9
13
|
- MCP instructions reorganized into 8 short lines (payload reading and ref/region conventions on separate lines); same rules, about 17% shorter.
|
package/dist/overlay.js
CHANGED
|
@@ -976,37 +976,48 @@ ${Math.round(r.width)}\xD7${Math.round(r.height)}`;
|
|
|
976
976
|
const frame = frames.find((f) => !REACT_INTERNAL_FN.test(f.fn) && !NODE_MODULES_URL.test(f.url));
|
|
977
977
|
return frame ? { url: frame.url, line: frame.line, col: frame.col } : null;
|
|
978
978
|
}
|
|
979
|
+
function normalizeDrivePath(path) {
|
|
980
|
+
return path.replace(/\\/g, "/").replace(/^\/?([a-zA-Z]):\//, (_, drive) => `${drive.toLowerCase()}:/`);
|
|
981
|
+
}
|
|
982
|
+
function isLibraryPath(path, root) {
|
|
983
|
+
const norm = normalizeDrivePath(path);
|
|
984
|
+
let rel = norm;
|
|
985
|
+
if (root) {
|
|
986
|
+
const r = normalizeDrivePath(root).replace(/\/+$/, "");
|
|
987
|
+
if (norm.startsWith(`${r}/`)) rel = norm.slice(r.length + 1);
|
|
988
|
+
}
|
|
989
|
+
return /(^|\/)node_modules\//.test(rel);
|
|
990
|
+
}
|
|
979
991
|
|
|
980
992
|
// src/overlay/frameworks/react.ts
|
|
981
|
-
function userLoc(f) {
|
|
993
|
+
function userLoc(f, root) {
|
|
982
994
|
const src = f._debugSource;
|
|
983
|
-
const fileName = src?.fileName
|
|
984
|
-
if (fileName && !fileName
|
|
995
|
+
const fileName = src?.fileName;
|
|
996
|
+
if (fileName && !isLibraryPath(fileName, root)) return `${fileName}${src.lineNumber ? ":" + src.lineNumber : ""}`;
|
|
985
997
|
const stack = typeof f._debugStack === "string" ? f._debugStack : f._debugStack?.stack;
|
|
986
998
|
return stack ? pickUserFrame(stack) ?? void 0 : void 0;
|
|
987
999
|
}
|
|
988
|
-
function reactInfo(el) {
|
|
1000
|
+
function reactInfo(el, root) {
|
|
989
1001
|
const key = Object.keys(el).find((k) => k.startsWith("__reactFiber$"));
|
|
990
1002
|
if (!key) return void 0;
|
|
991
1003
|
let f = el[key] ?? null;
|
|
992
1004
|
let fallback;
|
|
993
1005
|
let loc;
|
|
994
1006
|
let component;
|
|
995
|
-
const
|
|
996
|
-
const callerFrames = [];
|
|
1007
|
+
const callerLocs = [];
|
|
997
1008
|
let pending;
|
|
998
1009
|
const commit = (p) => {
|
|
999
|
-
if (
|
|
1010
|
+
if (callerLocs.length >= 2) return;
|
|
1000
1011
|
if (typeof p === "string") {
|
|
1001
|
-
if (p !== loc && !
|
|
1002
|
-
} else
|
|
1012
|
+
if (p !== loc && !callerLocs.includes(p)) callerLocs.push(p);
|
|
1013
|
+
} else callerLocs.push(p);
|
|
1003
1014
|
};
|
|
1004
1015
|
let libBelow = false;
|
|
1005
1016
|
for (let i = 0; f && i < 30; i++, f = f.return ?? null) {
|
|
1006
1017
|
const t = f.type;
|
|
1007
1018
|
const name = t && typeof t !== "string" ? t.displayName || t.name : void 0;
|
|
1008
1019
|
if (loc === void 0) {
|
|
1009
|
-
const l2 = userLoc(f);
|
|
1020
|
+
const l2 = userLoc(f, root);
|
|
1010
1021
|
if (l2 !== void 0) {
|
|
1011
1022
|
loc = l2;
|
|
1012
1023
|
component = name;
|
|
@@ -1016,13 +1027,13 @@ ${Math.round(r.width)}\xD7${Math.round(r.height)}`;
|
|
|
1016
1027
|
continue;
|
|
1017
1028
|
}
|
|
1018
1029
|
if (!name) {
|
|
1019
|
-
if ((f._debugSource !== void 0 || f._debugStack !== void 0) && userLoc(f) === void 0) libBelow = true;
|
|
1030
|
+
if ((f._debugSource !== void 0 || f._debugStack !== void 0) && userLoc(f, root) === void 0) libBelow = true;
|
|
1020
1031
|
continue;
|
|
1021
1032
|
}
|
|
1022
1033
|
if (component === void 0) {
|
|
1023
|
-
if (libBelow && userLoc(f) === void 0) continue;
|
|
1034
|
+
if (libBelow && userLoc(f, root) === void 0) continue;
|
|
1024
1035
|
component = name;
|
|
1025
|
-
const l2 = userLoc(f);
|
|
1036
|
+
const l2 = userLoc(f, root);
|
|
1026
1037
|
if (l2 !== void 0) pending = l2;
|
|
1027
1038
|
continue;
|
|
1028
1039
|
}
|
|
@@ -1030,36 +1041,35 @@ ${Math.round(r.width)}\xD7${Math.round(r.height)}`;
|
|
|
1030
1041
|
commit(pending);
|
|
1031
1042
|
pending = void 0;
|
|
1032
1043
|
}
|
|
1033
|
-
const l = userLoc(f);
|
|
1044
|
+
const l = userLoc(f, root);
|
|
1034
1045
|
if (l !== void 0) pending = l;
|
|
1035
1046
|
}
|
|
1036
1047
|
if (loc === void 0) return fallback;
|
|
1037
1048
|
if (!component) return fallback;
|
|
1038
1049
|
const hit = typeof loc === "string" ? { component, source: loc } : { component, frame: loc };
|
|
1039
|
-
if (
|
|
1040
|
-
if (callerFrames.length) hit.callerFrames = callerFrames;
|
|
1050
|
+
if (callerLocs.length) hit.callerLocs = callerLocs;
|
|
1041
1051
|
return hit;
|
|
1042
1052
|
}
|
|
1043
1053
|
var react = { key: "react", detect: reactInfo };
|
|
1044
1054
|
|
|
1045
1055
|
// src/overlay/frameworks/vue.ts
|
|
1046
1056
|
var ABSOLUTE_RE = /^([a-zA-Z]:\/|\/)/;
|
|
1047
|
-
function normalizeVueSource(file) {
|
|
1057
|
+
function normalizeVueSource(file, root) {
|
|
1048
1058
|
const norm = file.replace(/\\/g, "/");
|
|
1049
|
-
if (norm
|
|
1059
|
+
if (isLibraryPath(norm, root)) return void 0;
|
|
1050
1060
|
if (!ABSOLUTE_RE.test(norm)) return norm;
|
|
1051
1061
|
const idx = norm.lastIndexOf("/src/");
|
|
1052
1062
|
if (idx !== -1) return norm.slice(idx + 1);
|
|
1053
1063
|
const segs = norm.split("/").filter(Boolean);
|
|
1054
1064
|
return segs.slice(-2).join("/");
|
|
1055
1065
|
}
|
|
1056
|
-
function vueInfo(el) {
|
|
1066
|
+
function vueInfo(el, root) {
|
|
1057
1067
|
let c = el["__vueParentComponent"] ?? null;
|
|
1058
1068
|
for (let i = 0; c && i < 10; i++, c = c.parent ?? null) {
|
|
1059
1069
|
const name = c.type?.name || c.type?.__name;
|
|
1060
1070
|
if (name) {
|
|
1061
1071
|
const file = c.type?.__file;
|
|
1062
|
-
const source = file ? normalizeVueSource(file) : void 0;
|
|
1072
|
+
const source = file ? normalizeVueSource(file, root) : void 0;
|
|
1063
1073
|
return source ? { component: name, source } : { component: name };
|
|
1064
1074
|
}
|
|
1065
1075
|
}
|
|
@@ -1069,9 +1079,13 @@ ${Math.round(r.width)}\xD7${Math.round(r.height)}`;
|
|
|
1069
1079
|
|
|
1070
1080
|
// src/overlay/frameworks/index.ts
|
|
1071
1081
|
var adapters = [react, vue];
|
|
1082
|
+
var projectRoot;
|
|
1083
|
+
function setProjectRoot(root) {
|
|
1084
|
+
projectRoot = root;
|
|
1085
|
+
}
|
|
1072
1086
|
function detectComponent(el) {
|
|
1073
1087
|
for (const adapter of adapters) {
|
|
1074
|
-
const info = adapter.detect(el);
|
|
1088
|
+
const info = adapter.detect(el, projectRoot);
|
|
1075
1089
|
if (info) return { key: adapter.key, info };
|
|
1076
1090
|
}
|
|
1077
1091
|
return void 0;
|
|
@@ -1173,6 +1187,7 @@ ${Math.round(r.width)}\xD7${Math.round(r.height)}`;
|
|
|
1173
1187
|
if (window.top !== window) return;
|
|
1174
1188
|
const PORT = __COBRO_PORT__;
|
|
1175
1189
|
const TOKEN = __COBRO_TOKEN__;
|
|
1190
|
+
setProjectRoot(__COBRO_ROOT__);
|
|
1176
1191
|
const boot = () => {
|
|
1177
1192
|
if (document.documentElement.hasAttribute("data-cobro")) return;
|
|
1178
1193
|
document.documentElement.setAttribute("data-cobro", "1");
|
package/dist/server.js
CHANGED
|
@@ -308,6 +308,22 @@ var ChannelServer = class {
|
|
|
308
308
|
|
|
309
309
|
// src/core/sourcemap.ts
|
|
310
310
|
import { FlattenMap, originalPositionFor } from "@jridgewell/trace-mapping";
|
|
311
|
+
|
|
312
|
+
// src/core/frame.ts
|
|
313
|
+
function normalizeDrivePath(path) {
|
|
314
|
+
return path.replace(/\\/g, "/").replace(/^\/?([a-zA-Z]):\//, (_, drive) => `${drive.toLowerCase()}:/`);
|
|
315
|
+
}
|
|
316
|
+
function isLibraryPath(path, root) {
|
|
317
|
+
const norm = normalizeDrivePath(path);
|
|
318
|
+
let rel = norm;
|
|
319
|
+
if (root) {
|
|
320
|
+
const r = normalizeDrivePath(root).replace(/\/+$/, "");
|
|
321
|
+
if (norm.startsWith(`${r}/`)) rel = norm.slice(r.length + 1);
|
|
322
|
+
}
|
|
323
|
+
return /(^|\/)node_modules\//.test(rel);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// src/core/sourcemap.ts
|
|
311
327
|
var FRAMEWORK_KEYS = ["react", "vue"];
|
|
312
328
|
function findSourceMapUrl(moduleText, moduleUrl) {
|
|
313
329
|
const re = /\/\/[#@]\s*sourceMappingURL=(\S+)/g;
|
|
@@ -322,11 +338,7 @@ function findSourceMapUrl(moduleText, moduleUrl) {
|
|
|
322
338
|
return void 0;
|
|
323
339
|
}
|
|
324
340
|
}
|
|
325
|
-
function
|
|
326
|
-
return path.replace(/\\/g, "/").replace(/^\/?([a-zA-Z]):\//, (_, drive) => `${drive.toLowerCase()}:/`);
|
|
327
|
-
}
|
|
328
|
-
function normalizeFileSource(pathname, root) {
|
|
329
|
-
const path = normalizeDrivePath(decodeURIComponent(pathname));
|
|
341
|
+
function normalizeFileSource(path, root) {
|
|
330
342
|
if (root) {
|
|
331
343
|
const r = normalizeDrivePath(root).replace(/\/+$/, "");
|
|
332
344
|
if (path === r) return "";
|
|
@@ -351,18 +363,30 @@ function resolveOriginal(mapJson, opts) {
|
|
|
351
363
|
} catch {
|
|
352
364
|
return void 0;
|
|
353
365
|
}
|
|
354
|
-
const
|
|
355
|
-
|
|
366
|
+
const raw = decodeURIComponent(url.pathname);
|
|
367
|
+
const abs = url.protocol === "file:" ? normalizeDrivePath(raw) : raw.replace(/^\/+/, "");
|
|
368
|
+
if (isLibraryPath(abs, opts.root)) return void 0;
|
|
369
|
+
const path = (url.protocol === "file:" ? normalizeFileSource(abs, opts.root) : abs).split("?")[0];
|
|
370
|
+
if (!path || path.split("/").includes("..") || /[\x00-\x1f]/.test(path)) return void 0;
|
|
356
371
|
return `${path}:${pos.line}`;
|
|
357
372
|
}
|
|
358
373
|
function stripFrame(e) {
|
|
359
374
|
let out = e;
|
|
360
375
|
for (const key of FRAMEWORK_KEYS) {
|
|
361
376
|
const info = out[key];
|
|
362
|
-
if (!info
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
377
|
+
if (!info) continue;
|
|
378
|
+
if (typeof info.component !== "string") {
|
|
379
|
+
const rest2 = { ...out };
|
|
380
|
+
delete rest2[key];
|
|
381
|
+
out = rest2;
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
const rest = { component: info.component.slice(0, 200) };
|
|
385
|
+
if (typeof info.source === "string") rest.source = info.source.slice(0, 200);
|
|
386
|
+
if (Array.isArray(info.callers)) {
|
|
387
|
+
const callers = info.callers.filter((c) => typeof c === "string").map((c) => c.slice(0, 200)).slice(0, 2);
|
|
388
|
+
if (callers.length) rest.callers = callers;
|
|
389
|
+
}
|
|
366
390
|
out = { ...out, [key]: rest };
|
|
367
391
|
}
|
|
368
392
|
return out;
|
|
@@ -409,17 +433,15 @@ async function resolveElementSources(batch, page, fetchText, opts) {
|
|
|
409
433
|
const source = await resolveFrame(react.frame, page, fetchText, root);
|
|
410
434
|
if (source) react.source = source;
|
|
411
435
|
}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
callers.push(source);
|
|
419
|
-
}
|
|
420
|
-
if (callers.length) react.callers = callers;
|
|
421
|
-
else delete react.callers;
|
|
436
|
+
const callers = [];
|
|
437
|
+
for (const loc of react.callerLocs ?? []) {
|
|
438
|
+
if (callers.length >= 2) break;
|
|
439
|
+
const s = typeof loc === "string" ? loc : await resolveFrame(loc, page, fetchText, root);
|
|
440
|
+
if (!s || s === react.source || callers.includes(s)) continue;
|
|
441
|
+
callers.push(s);
|
|
422
442
|
}
|
|
443
|
+
if (callers.length) react.callers = callers;
|
|
444
|
+
else delete react.callers;
|
|
423
445
|
}
|
|
424
446
|
}
|
|
425
447
|
|
|
@@ -623,7 +645,7 @@ var BrowserLauncher = class {
|
|
|
623
645
|
return this.launchedOnce;
|
|
624
646
|
}
|
|
625
647
|
injected() {
|
|
626
|
-
return this.opts.overlaySource.replace(/__COBRO_PORT__/g, String(this.opts.port)).replace(/__COBRO_TOKEN__/g, JSON.stringify(this.opts.token));
|
|
648
|
+
return this.opts.overlaySource.replace(/__COBRO_PORT__/g, () => String(this.opts.port)).replace(/__COBRO_TOKEN__/g, () => JSON.stringify(this.opts.token)).replace(/__COBRO_ROOT__/g, () => JSON.stringify(this.opts.root));
|
|
627
649
|
}
|
|
628
650
|
async launch() {
|
|
629
651
|
const engine2 = this.opts.engine ?? "chromium";
|
|
@@ -913,7 +935,7 @@ var bridge = await createBridge({
|
|
|
913
935
|
});
|
|
914
936
|
var fixed = configStrategy();
|
|
915
937
|
if (fixed) bridge.core.setStrategy(fixed);
|
|
916
|
-
launcher = new BrowserLauncher({ overlaySource, port: bridge.port, token, profileDir, headless: process.env.COBRO_HEADLESS === "1", engine });
|
|
938
|
+
launcher = new BrowserLauncher({ overlaySource, port: bridge.port, token, root: process.cwd(), profileDir, headless: process.env.COBRO_HEADLESS === "1", engine });
|
|
917
939
|
var mcp = createMcpServer({
|
|
918
940
|
core: bridge.core,
|
|
919
941
|
browser: launcher,
|
package/package.json
CHANGED