@sightmap/mcp 0.8.0 → 0.10.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 +25 -9
- package/dist/cli.d.ts +13 -1
- package/dist/cli.js +499 -45
- package/dist/cli.js.map +1 -1
- package/dist/index.js +456 -37
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
package/dist/cli.js
CHANGED
|
@@ -49,8 +49,10 @@ var init_loadMerged = __esm({
|
|
|
49
49
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
50
50
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
51
51
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
52
|
-
import { resolve as
|
|
53
|
-
import { readFileSync, realpathSync } from "fs";
|
|
52
|
+
import { resolve as resolve4, dirname as dirname2, join as join4 } from "path";
|
|
53
|
+
import { readFileSync, realpathSync, writeFileSync, mkdirSync, rmSync } from "fs";
|
|
54
|
+
import { tmpdir } from "os";
|
|
55
|
+
import { randomBytes } from "crypto";
|
|
54
56
|
import { fileURLToPath } from "url";
|
|
55
57
|
|
|
56
58
|
// src/server.ts
|
|
@@ -402,31 +404,389 @@ async function handleInitProject(input) {
|
|
|
402
404
|
}
|
|
403
405
|
|
|
404
406
|
// src/tools/runtime/snapshot.ts
|
|
405
|
-
async function handleRuntimeSnapshot(input) {
|
|
407
|
+
async function handleRuntimeSnapshot(input, deps = {}) {
|
|
406
408
|
if (input.source.kind === "endpoint") {
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
409
|
+
return runEndpoint(input.source);
|
|
410
|
+
}
|
|
411
|
+
if (input.source.kind === "literal") {
|
|
412
|
+
return runLiteral(input.source);
|
|
413
|
+
}
|
|
414
|
+
return runBrowser(input.source, deps);
|
|
415
|
+
}
|
|
416
|
+
async function runEndpoint(source) {
|
|
417
|
+
const res = await fetch(source.url);
|
|
418
|
+
if (res.status === 404) {
|
|
419
|
+
let detail = "";
|
|
420
|
+
try {
|
|
421
|
+
const body = await res.json();
|
|
422
|
+
detail = body.error ?? "";
|
|
423
|
+
} catch {
|
|
424
|
+
}
|
|
425
|
+
throw new Error(
|
|
426
|
+
`no snapshot cached yet at ${source.url}${detail ? `: ${detail}` : ""}`
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
if (!res.ok) {
|
|
430
|
+
throw new Error(`snapshot fetch failed: HTTP ${res.status}`);
|
|
431
|
+
}
|
|
432
|
+
const snapshot = await res.json();
|
|
433
|
+
return { ok: true, snapshot, source };
|
|
434
|
+
}
|
|
435
|
+
var REQUIRED_SNAPSHOT_FIELDS = [
|
|
436
|
+
"capturedAt",
|
|
437
|
+
"route",
|
|
438
|
+
"components",
|
|
439
|
+
"markers",
|
|
440
|
+
"unmarkedCandidates"
|
|
441
|
+
];
|
|
442
|
+
function runLiteral(source) {
|
|
443
|
+
validateSnapshotShape(source.snapshot, "literal");
|
|
444
|
+
return { ok: true, snapshot: source.snapshot, source };
|
|
445
|
+
}
|
|
446
|
+
function validateSnapshotShape(value, label) {
|
|
447
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
448
|
+
throw new Error(
|
|
449
|
+
`${label} snapshot: expected an object with fields { capturedAt, route, components, markers, unmarkedCandidates }; got ${value === null ? "null" : Array.isArray(value) ? "array" : typeof value}`
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
const s = value;
|
|
453
|
+
for (const field of REQUIRED_SNAPSHOT_FIELDS) {
|
|
454
|
+
if (!(field in s)) {
|
|
455
|
+
throw new Error(`${label} snapshot: missing required field \`${field}\``);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
if (!Array.isArray(s["components"])) {
|
|
459
|
+
throw new Error(`${label} snapshot: \`components\` must be an array`);
|
|
460
|
+
}
|
|
461
|
+
if (!Array.isArray(s["markers"])) {
|
|
462
|
+
throw new Error(`${label} snapshot: \`markers\` must be an array`);
|
|
463
|
+
}
|
|
464
|
+
if (!Array.isArray(s["unmarkedCandidates"])) {
|
|
465
|
+
throw new Error(`${label} snapshot: \`unmarkedCandidates\` must be an array`);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
async function runBrowser(source, deps) {
|
|
469
|
+
if (deps.upstream === void 0) {
|
|
470
|
+
throw new Error(
|
|
471
|
+
"browser-mode snapshot requires an upstream MCP server (e.g., @playwright/mcp). Run sightmap-mcp without `--curate-only` so the @playwright/mcp passthrough is wired up."
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
const upstream = deps.upstream;
|
|
475
|
+
const wantInject = source.inject === true;
|
|
476
|
+
const allowAutoInject = source.inject === void 0;
|
|
477
|
+
const urls = source.routes !== void 0 && source.routes.length > 0 ? source.routes.map((r) => absolutize(r, source.url)) : [source.url];
|
|
478
|
+
const captures = [];
|
|
479
|
+
for (const targetUrl of urls) {
|
|
480
|
+
const captureOpts = {
|
|
481
|
+
forceInject: wantInject,
|
|
482
|
+
autoInject: allowAutoInject
|
|
483
|
+
};
|
|
484
|
+
if (deps.getInjectScript !== void 0) {
|
|
485
|
+
captureOpts.getInjectScript = deps.getInjectScript;
|
|
486
|
+
}
|
|
487
|
+
const cap = await captureOneUrl(upstream, targetUrl, captureOpts);
|
|
488
|
+
validateSnapshotShape(cap, "browser");
|
|
489
|
+
captures.push({ url: targetUrl, snapshot: cap });
|
|
490
|
+
}
|
|
491
|
+
if (source.routes === void 0 || source.routes.length === 0) {
|
|
492
|
+
return { ok: true, snapshot: captures[0].snapshot, source };
|
|
493
|
+
}
|
|
494
|
+
return { ok: true, snapshots: captures, source };
|
|
495
|
+
}
|
|
496
|
+
function absolutize(routeOrUrl, baseUrl) {
|
|
497
|
+
try {
|
|
498
|
+
return new URL(routeOrUrl).toString();
|
|
499
|
+
} catch {
|
|
500
|
+
const base = new URL(baseUrl);
|
|
501
|
+
return new URL(routeOrUrl, base).toString();
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
async function captureOneUrl(upstream, url, opts) {
|
|
505
|
+
const navResult = await upstream.callTool("browser_navigate", { url });
|
|
506
|
+
if (navResult.isError === true) {
|
|
507
|
+
throw new Error(
|
|
508
|
+
`browser_navigate to ${url} failed: ${navResult.content[0]?.text ?? "unknown error"}`
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
const probe = await evalAndDecode(
|
|
512
|
+
upstream,
|
|
513
|
+
`() => typeof (window.__sightmap__ && window.__sightmap__.snapshot)`
|
|
514
|
+
);
|
|
515
|
+
const hasHook = probe === "function";
|
|
516
|
+
if (!hasHook && (opts.forceInject || opts.autoInject)) {
|
|
517
|
+
if (opts.getInjectScript === void 0) {
|
|
415
518
|
throw new Error(
|
|
416
|
-
|
|
519
|
+
"browser-mode snapshot: page has no window.__sightmap__ and no inject script provider was configured. Install @sightmap/react in the target app, or wire `getInjectScript` into the MCP server."
|
|
417
520
|
);
|
|
418
521
|
}
|
|
419
|
-
|
|
420
|
-
|
|
522
|
+
const script = await opts.getInjectScript();
|
|
523
|
+
const wrapped = `() => { ${script}
|
|
524
|
+
; return typeof (window.__sightmap__ && window.__sightmap__.snapshot); }`;
|
|
525
|
+
const injectResult = await evalAndDecode(upstream, wrapped);
|
|
526
|
+
if (injectResult !== "function") {
|
|
527
|
+
throw new Error(
|
|
528
|
+
`browser-mode snapshot: inject script did not install window.__sightmap__.snapshot (typeof after inject: ${String(injectResult)}). The target page may not be a React app.`
|
|
529
|
+
);
|
|
421
530
|
}
|
|
422
|
-
|
|
423
|
-
|
|
531
|
+
} else if (!hasHook && opts.forceInject === false && opts.autoInject === false) {
|
|
532
|
+
throw new Error(
|
|
533
|
+
"browser-mode snapshot: page has no window.__sightmap__.snapshot and `inject: false` was set. Either install @sightmap/react in the app or pass `inject: true`."
|
|
534
|
+
);
|
|
424
535
|
}
|
|
425
|
-
|
|
426
|
-
|
|
536
|
+
const snap = await evalAndDecode(
|
|
537
|
+
upstream,
|
|
538
|
+
`() => JSON.parse(JSON.stringify(window.__sightmap__.snapshot()))`
|
|
427
539
|
);
|
|
540
|
+
return snap;
|
|
541
|
+
}
|
|
542
|
+
async function evalAndDecode(upstream, fnSource) {
|
|
543
|
+
const r = await upstream.callTool("browser_evaluate", { function: fnSource });
|
|
544
|
+
if (r.isError === true) {
|
|
545
|
+
throw new Error(
|
|
546
|
+
`browser_evaluate failed: ${r.content[0]?.text ?? "unknown error"}`
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
const text = r.content.map((b) => b.text).join("\n");
|
|
550
|
+
return decodeEvalText(text);
|
|
551
|
+
}
|
|
552
|
+
function decodeEvalText(text) {
|
|
553
|
+
const afterResult = text.split(/^###\s*Result/im)[1] ?? text;
|
|
554
|
+
const trimmed = afterResult.trim();
|
|
555
|
+
if (trimmed.length === 0) return void 0;
|
|
556
|
+
for (let end = trimmed.length; end > 0; end--) {
|
|
557
|
+
try {
|
|
558
|
+
return JSON.parse(trimmed.slice(0, end));
|
|
559
|
+
} catch {
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
return void 0;
|
|
428
563
|
}
|
|
429
564
|
|
|
565
|
+
// src/runtime/inject-script.ts
|
|
566
|
+
import { createRequire } from "module";
|
|
567
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
568
|
+
import { dirname, resolve as resolve3 } from "path";
|
|
569
|
+
var cachedScript;
|
|
570
|
+
async function getInjectScript() {
|
|
571
|
+
if (cachedScript !== void 0) return cachedScript;
|
|
572
|
+
const bippyIife = await loadBippyIife();
|
|
573
|
+
cachedScript = `${bippyIife}
|
|
574
|
+
${BOOTSTRAP_SRC}`;
|
|
575
|
+
return cachedScript;
|
|
576
|
+
}
|
|
577
|
+
async function loadBippyIife() {
|
|
578
|
+
const require2 = createRequire(import.meta.url);
|
|
579
|
+
const bippyPkgJson = require2.resolve("bippy/package.json");
|
|
580
|
+
const iifePath = resolve3(dirname(bippyPkgJson), "dist/index.iife.js");
|
|
581
|
+
return readFile6(iifePath, "utf8");
|
|
582
|
+
}
|
|
583
|
+
var BOOTSTRAP_SRC = String.raw`
|
|
584
|
+
(function () {
|
|
585
|
+
if (typeof window === "undefined") return;
|
|
586
|
+
if (!window.Bippy || typeof window.Bippy.instrument !== "function") return;
|
|
587
|
+
if (window.__sightmap__ && typeof window.__sightmap__.snapshot === "function") {
|
|
588
|
+
// Already installed (e.g. @sightmap/react is present). Don't double-wire.
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
var trackedRoots = new Set();
|
|
593
|
+
window.Bippy.instrument({
|
|
594
|
+
onCommitFiberRoot: function (_id, root) {
|
|
595
|
+
trackedRoots.add(root);
|
|
596
|
+
},
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
var FRAMEWORK_NOISE = new Set([
|
|
600
|
+
"Routes", "Route", "RenderedRoute", "Outlet", "BrowserRouter",
|
|
601
|
+
"Router", "RouterProvider", "DataRouterProvider", "RouterProviderImpl",
|
|
602
|
+
"LinkWithRef", "StrictMode", "SightmapProvider", "Fragment", "Anonymous",
|
|
603
|
+
"Suspense", "ErrorBoundary", "Provider", "Consumer",
|
|
604
|
+
"ThemeProvider", "Hydration", "HydrateFallback",
|
|
605
|
+
"RemixErrorBoundary", "HydratedRouter", "DataRoutes",
|
|
606
|
+
"WithHydrateFallbackProps2", "RenderErrorBoundary", "Layout",
|
|
607
|
+
]);
|
|
608
|
+
|
|
609
|
+
var HOST_PORTAL_TAG = 4;
|
|
610
|
+
|
|
611
|
+
function cssEsc(v) { return String(v).replace(/"/g, '\\"'); }
|
|
612
|
+
function isHumanShapedId(id) {
|
|
613
|
+
if (/[0-9]{6,}/.test(id)) return false;
|
|
614
|
+
if (/^[a-z]+-[a-f0-9]{8,}/i.test(id)) return false;
|
|
615
|
+
if (/:r[0-9a-z]+:/i.test(id)) return false;
|
|
616
|
+
return true;
|
|
617
|
+
}
|
|
618
|
+
function rankSelectors(el) {
|
|
619
|
+
var out = [];
|
|
620
|
+
var dsm = el.getAttribute("data-sightmap");
|
|
621
|
+
if (dsm !== null) {
|
|
622
|
+
out.push({ selector: '[data-sightmap="' + cssEsc(dsm) + '"]', stability: "high", source: "data-sightmap" });
|
|
623
|
+
}
|
|
624
|
+
["data-testid", "data-cy", "data-test"].forEach(function (attr) {
|
|
625
|
+
var v = el.getAttribute(attr);
|
|
626
|
+
if (v !== null) {
|
|
627
|
+
out.push({ selector: "[" + attr + '="' + cssEsc(v) + '"]', stability: "high", source: attr });
|
|
628
|
+
}
|
|
629
|
+
});
|
|
630
|
+
var role = el.getAttribute("role");
|
|
631
|
+
var aria = el.getAttribute("aria-label");
|
|
632
|
+
if (role && aria) {
|
|
633
|
+
out.push({ selector: '[role="' + cssEsc(role) + '"][aria-label="' + cssEsc(aria) + '"]', stability: "medium", source: "aria" });
|
|
634
|
+
} else if (aria) {
|
|
635
|
+
out.push({ selector: '[aria-label="' + cssEsc(aria) + '"]', stability: "medium", source: "aria-label" });
|
|
636
|
+
}
|
|
637
|
+
var name = el.getAttribute("name");
|
|
638
|
+
if (name !== null && /^(form|input|button|select|textarea|nav)$/i.test(el.tagName)) {
|
|
639
|
+
out.push({ selector: el.tagName.toLowerCase() + '[name="' + cssEsc(name) + '"]', stability: "medium", source: "semantic-name" });
|
|
640
|
+
}
|
|
641
|
+
if (el.id && isHumanShapedId(el.id)) {
|
|
642
|
+
out.push({ selector: "#" + cssEsc(el.id), stability: "high", source: "id" });
|
|
643
|
+
}
|
|
644
|
+
return out;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
function findHostElementForFiber(fiber) {
|
|
648
|
+
if (!fiber.child) return null;
|
|
649
|
+
var stack = [fiber.child];
|
|
650
|
+
var walks = 0;
|
|
651
|
+
while (stack.length > 0 && walks < 5000) {
|
|
652
|
+
walks++;
|
|
653
|
+
var f = stack.pop();
|
|
654
|
+
if (!f) continue;
|
|
655
|
+
if (f.sibling) stack.push(f.sibling);
|
|
656
|
+
if (f.tag === HOST_PORTAL_TAG) continue;
|
|
657
|
+
var sn = f.stateNode;
|
|
658
|
+
if (sn && typeof sn === "object" && "tagName" in sn && document.contains(sn)) {
|
|
659
|
+
return sn;
|
|
660
|
+
}
|
|
661
|
+
if (f.child) stack.push(f.child);
|
|
662
|
+
}
|
|
663
|
+
return null;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
function bridgeDomToFiber(el) {
|
|
667
|
+
for (var i = 0, keys = Object.keys(el); i < keys.length; i++) {
|
|
668
|
+
if (keys[i].indexOf("__reactFiber$") === 0) return el[keys[i]] || null;
|
|
669
|
+
}
|
|
670
|
+
return null;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
function nearestCompositeName(fiber) {
|
|
674
|
+
var f = fiber, walks = 0;
|
|
675
|
+
while (f && walks < 50) {
|
|
676
|
+
if (window.Bippy.isCompositeFiber(f)) {
|
|
677
|
+
var n = window.Bippy.getDisplayName(f);
|
|
678
|
+
if (n) return n;
|
|
679
|
+
}
|
|
680
|
+
f = f.return;
|
|
681
|
+
walks++;
|
|
682
|
+
}
|
|
683
|
+
return undefined;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
function domDepth(el) {
|
|
687
|
+
var d = 0, cur = el.parentElement;
|
|
688
|
+
while (cur && cur !== document.body) { d++; cur = cur.parentElement; }
|
|
689
|
+
return d;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
function collectComponents(rootFiber) {
|
|
693
|
+
var out = [];
|
|
694
|
+
var depthByFiber = new Map();
|
|
695
|
+
depthByFiber.set(rootFiber, 0);
|
|
696
|
+
window.Bippy.traverseFiber(rootFiber, function (f) {
|
|
697
|
+
var depth = depthByFiber.get(f) || 0;
|
|
698
|
+
var c = f.child;
|
|
699
|
+
while (c) { depthByFiber.set(c, depth + 1); c = c.sibling; }
|
|
700
|
+
if (!window.Bippy.isCompositeFiber(f)) return false;
|
|
701
|
+
var name = window.Bippy.getDisplayName(f);
|
|
702
|
+
if (!name) return false;
|
|
703
|
+
var childNames = [];
|
|
704
|
+
var cc = f.child;
|
|
705
|
+
while (cc) {
|
|
706
|
+
if (window.Bippy.isCompositeFiber(cc)) {
|
|
707
|
+
var n = window.Bippy.getDisplayName(cc);
|
|
708
|
+
if (n) childNames.push(n);
|
|
709
|
+
}
|
|
710
|
+
cc = cc.sibling;
|
|
711
|
+
}
|
|
712
|
+
out.push({ displayName: name, depth: depth, children: childNames });
|
|
713
|
+
return false;
|
|
714
|
+
});
|
|
715
|
+
return out;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
function collectMarkers() {
|
|
719
|
+
var out = [];
|
|
720
|
+
if (typeof document === "undefined") return out;
|
|
721
|
+
var els = document.querySelectorAll("[data-sightmap]");
|
|
722
|
+
for (var i = 0; i < els.length; i++) {
|
|
723
|
+
var el = els[i];
|
|
724
|
+
var name = el.getAttribute("data-sightmap") || "";
|
|
725
|
+
var fiber = bridgeDomToFiber(el);
|
|
726
|
+
var nc = nearestCompositeName(fiber);
|
|
727
|
+
var marker = {
|
|
728
|
+
name: name,
|
|
729
|
+
hostTag: el.tagName.toLowerCase(),
|
|
730
|
+
domDepth: domDepth(el),
|
|
731
|
+
candidateSelectors: rankSelectors(el),
|
|
732
|
+
};
|
|
733
|
+
if (nc !== undefined) marker.nearestComponent = nc;
|
|
734
|
+
out.push(marker);
|
|
735
|
+
}
|
|
736
|
+
return out;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
function collectUnmarkedCandidates(rootFiber) {
|
|
740
|
+
var out = [];
|
|
741
|
+
var seen = new Set();
|
|
742
|
+
window.Bippy.traverseFiber(rootFiber, function (f) {
|
|
743
|
+
if (!window.Bippy.isCompositeFiber(f)) return false;
|
|
744
|
+
var name = window.Bippy.getDisplayName(f);
|
|
745
|
+
if (!name || seen.has(name) || FRAMEWORK_NOISE.has(name)) return false;
|
|
746
|
+
seen.add(name);
|
|
747
|
+
var hostEl = findHostElementForFiber(f);
|
|
748
|
+
if (!hostEl) return false;
|
|
749
|
+
var selectors = rankSelectors(hostEl);
|
|
750
|
+
if (selectors.length === 0) return false;
|
|
751
|
+
out.push({
|
|
752
|
+
displayName: name,
|
|
753
|
+
hostTag: hostEl.tagName.toLowerCase(),
|
|
754
|
+
selectors: selectors,
|
|
755
|
+
domDepth: domDepth(hostEl),
|
|
756
|
+
});
|
|
757
|
+
return false;
|
|
758
|
+
});
|
|
759
|
+
return out;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
function snapshot() {
|
|
763
|
+
var roots = Array.from(trackedRoots);
|
|
764
|
+
var now = new Date().toISOString();
|
|
765
|
+
var loc = (typeof window !== "undefined" && window.location)
|
|
766
|
+
? (window.location.pathname + (window.location.search || ""))
|
|
767
|
+
: "?";
|
|
768
|
+
if (roots.length === 0) {
|
|
769
|
+
return { capturedAt: now, route: loc === "?" ? "?" : loc, commits: 0, components: [], markers: [], unmarkedCandidates: [] };
|
|
770
|
+
}
|
|
771
|
+
var root = roots.find(function (r) { return r.current && r.current.child !== null; }) || roots[0];
|
|
772
|
+
if (!root) {
|
|
773
|
+
return { capturedAt: now, route: loc === "?" ? "?" : loc, commits: 0, components: [], markers: [], unmarkedCandidates: [] };
|
|
774
|
+
}
|
|
775
|
+
var rootFiber = root.current;
|
|
776
|
+
return {
|
|
777
|
+
capturedAt: now,
|
|
778
|
+
route: loc,
|
|
779
|
+
commits: roots.length,
|
|
780
|
+
components: collectComponents(rootFiber),
|
|
781
|
+
markers: collectMarkers(),
|
|
782
|
+
unmarkedCandidates: collectUnmarkedCandidates(rootFiber),
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
window.__sightmap__ = { snapshot: snapshot };
|
|
787
|
+
})();
|
|
788
|
+
`;
|
|
789
|
+
|
|
430
790
|
// src/tools/propose/store.ts
|
|
431
791
|
var ProposalStore = class {
|
|
432
792
|
views = [];
|
|
@@ -851,19 +1211,52 @@ var SIGHTMAP_ADD_VIEW = {
|
|
|
851
1211
|
};
|
|
852
1212
|
var SIGHTMAP_RUNTIME_SNAPSHOT = {
|
|
853
1213
|
name: "sightmap_runtime_snapshot",
|
|
854
|
-
description: "Fetch
|
|
1214
|
+
description: "Fetch a SightmapSnapshot (fiber-tree introspection from bippy). Three sources:\n - { kind: 'endpoint', url } \u2014 HTTP GET against the @sightmap/react Vite plugin endpoint (zero-config for Vite users).\n - { kind: 'browser', url, routes?, inject? } \u2014 drive the live browser via @playwright/mcp; works on any React app (Webpack, RR 5.x, etc.) and, with `inject: true`, on apps that haven't installed @sightmap/react.\n - { kind: 'literal', snapshot } \u2014 accept a pre-captured snapshot from any source (subtext live-eval-script, devtools console paste, custom Playwright scripts, CI capture). Validates the shape; returns it.",
|
|
855
1215
|
inputSchema: {
|
|
856
1216
|
type: "object",
|
|
857
1217
|
properties: {
|
|
858
1218
|
source: {
|
|
859
1219
|
type: "object",
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
1220
|
+
oneOf: [
|
|
1221
|
+
{
|
|
1222
|
+
type: "object",
|
|
1223
|
+
properties: {
|
|
1224
|
+
kind: { type: "string", const: "endpoint" },
|
|
1225
|
+
url: { type: "string", description: "Plugin endpoint URL, e.g. http://localhost:5173/__sightmap__/snapshot.json." }
|
|
1226
|
+
},
|
|
1227
|
+
required: ["kind", "url"],
|
|
1228
|
+
additionalProperties: false
|
|
1229
|
+
},
|
|
1230
|
+
{
|
|
1231
|
+
type: "object",
|
|
1232
|
+
properties: {
|
|
1233
|
+
kind: { type: "string", const: "browser" },
|
|
1234
|
+
url: { type: "string", description: "Target URL to navigate to." },
|
|
1235
|
+
routes: {
|
|
1236
|
+
type: "array",
|
|
1237
|
+
items: { type: "string" },
|
|
1238
|
+
description: "Optional list of routes (absolute or relative to `url`) to capture in sequence."
|
|
1239
|
+
},
|
|
1240
|
+
inject: {
|
|
1241
|
+
type: "boolean",
|
|
1242
|
+
description: "When true, inject bippy + the snapshot bootstrap before navigation so non-@sightmap/react apps work. Omit to auto-inject only when the page lacks window.__sightmap__."
|
|
1243
|
+
}
|
|
1244
|
+
},
|
|
1245
|
+
required: ["kind", "url"],
|
|
1246
|
+
additionalProperties: false
|
|
1247
|
+
},
|
|
1248
|
+
{
|
|
1249
|
+
type: "object",
|
|
1250
|
+
properties: {
|
|
1251
|
+
kind: { type: "string", const: "literal" },
|
|
1252
|
+
snapshot: {
|
|
1253
|
+
description: "A pre-captured SightmapSnapshot object. Must include capturedAt, route, components, markers, unmarkedCandidates."
|
|
1254
|
+
}
|
|
1255
|
+
},
|
|
1256
|
+
required: ["kind", "snapshot"],
|
|
1257
|
+
additionalProperties: false
|
|
1258
|
+
}
|
|
1259
|
+
]
|
|
867
1260
|
}
|
|
868
1261
|
},
|
|
869
1262
|
required: ["source"],
|
|
@@ -1373,25 +1766,53 @@ var SightmapMcpServer = class {
|
|
|
1373
1766
|
);
|
|
1374
1767
|
}
|
|
1375
1768
|
const s = source;
|
|
1376
|
-
|
|
1769
|
+
const kind = s["kind"];
|
|
1770
|
+
if (kind !== "endpoint" && kind !== "browser" && kind !== "literal") {
|
|
1377
1771
|
return errorResult(
|
|
1378
|
-
"sightmap_runtime_snapshot: `source.kind` must be 'endpoint' or '
|
|
1772
|
+
"sightmap_runtime_snapshot: `source.kind` must be 'endpoint', 'browser', or 'literal'."
|
|
1379
1773
|
);
|
|
1380
1774
|
}
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1775
|
+
let input;
|
|
1776
|
+
if (kind === "endpoint") {
|
|
1777
|
+
if (typeof s["url"] !== "string" || s["url"].length === 0) {
|
|
1778
|
+
return errorResult(
|
|
1779
|
+
"sightmap_runtime_snapshot: endpoint mode requires `source.url` (string)."
|
|
1780
|
+
);
|
|
1781
|
+
}
|
|
1782
|
+
input = { source: { kind: "endpoint", url: s["url"] } };
|
|
1783
|
+
} else if (kind === "browser") {
|
|
1784
|
+
if (typeof s["url"] !== "string" || s["url"].length === 0) {
|
|
1785
|
+
return errorResult(
|
|
1786
|
+
"sightmap_runtime_snapshot: browser mode requires `source.url` (string)."
|
|
1787
|
+
);
|
|
1788
|
+
}
|
|
1789
|
+
const browserSrc = {
|
|
1388
1790
|
kind: "browser",
|
|
1389
|
-
url: s["url"]
|
|
1390
|
-
|
|
1791
|
+
url: s["url"]
|
|
1792
|
+
};
|
|
1793
|
+
if (Array.isArray(s["routes"])) {
|
|
1794
|
+
browserSrc.routes = s["routes"].filter(
|
|
1795
|
+
(r) => typeof r === "string"
|
|
1796
|
+
);
|
|
1391
1797
|
}
|
|
1392
|
-
|
|
1798
|
+
if (typeof s["inject"] === "boolean") {
|
|
1799
|
+
browserSrc.inject = s["inject"];
|
|
1800
|
+
}
|
|
1801
|
+
input = { source: browserSrc };
|
|
1802
|
+
} else {
|
|
1803
|
+
if (!("snapshot" in s)) {
|
|
1804
|
+
return errorResult(
|
|
1805
|
+
"sightmap_runtime_snapshot: literal mode requires `source.snapshot`."
|
|
1806
|
+
);
|
|
1807
|
+
}
|
|
1808
|
+
input = { source: { kind: "literal", snapshot: s["snapshot"] } };
|
|
1809
|
+
}
|
|
1393
1810
|
try {
|
|
1394
|
-
const
|
|
1811
|
+
const deps = {
|
|
1812
|
+
getInjectScript
|
|
1813
|
+
};
|
|
1814
|
+
if (this.upstream !== void 0) deps.upstream = this.upstream;
|
|
1815
|
+
const result = await handleRuntimeSnapshot(input, deps);
|
|
1395
1816
|
return {
|
|
1396
1817
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1397
1818
|
};
|
|
@@ -1763,8 +2184,8 @@ function evaluateCompat(input) {
|
|
|
1763
2184
|
}
|
|
1764
2185
|
|
|
1765
2186
|
// src/cli.ts
|
|
1766
|
-
var __dirname =
|
|
1767
|
-
var pkgJson = JSON.parse(readFileSync(
|
|
2187
|
+
var __dirname = dirname2(fileURLToPath(import.meta.url));
|
|
2188
|
+
var pkgJson = JSON.parse(readFileSync(resolve4(__dirname, "../package.json"), "utf8"));
|
|
1768
2189
|
function parseArgv(argv) {
|
|
1769
2190
|
const collectedDirs = [];
|
|
1770
2191
|
let explicitCurateRoot;
|
|
@@ -1807,7 +2228,7 @@ function parseArgv(argv) {
|
|
|
1807
2228
|
i++;
|
|
1808
2229
|
}
|
|
1809
2230
|
out.sightmapDirs = collectedDirs.length > 0 ? collectedDirs : [".sightmap"];
|
|
1810
|
-
out.curateRoot = explicitCurateRoot !== void 0 ?
|
|
2231
|
+
out.curateRoot = explicitCurateRoot !== void 0 ? resolve4(explicitCurateRoot) : resolve4(out.sightmapDirs[0]);
|
|
1811
2232
|
return out;
|
|
1812
2233
|
}
|
|
1813
2234
|
function printHelp() {
|
|
@@ -1845,7 +2266,7 @@ Pin matching versions in your .mcp.json.`
|
|
|
1845
2266
|
console.error(`[sightmap-mcp] Untested plugin/MCP pair: plugin v${process.env.SIGHTMAP_PLUGIN_VERSION} \u2194 MCP v${pkgJson.version}. Proceeding.`);
|
|
1846
2267
|
}
|
|
1847
2268
|
const { mergeSightmaps: mergeSightmaps2 } = await Promise.resolve().then(() => (init_loadMerged(), loadMerged_exports));
|
|
1848
|
-
const sightmap = await mergeSightmaps2(args.sightmapDirs.map((d) =>
|
|
2269
|
+
const sightmap = await mergeSightmaps2(args.sightmapDirs.map((d) => resolve4(d)));
|
|
1849
2270
|
if (sightmap.diagnostics.some((d) => d.severity === "error")) {
|
|
1850
2271
|
process.stderr.write(
|
|
1851
2272
|
`sightmap-mcp: ${sightmap.diagnostics.length} diagnostic(s) loading sightmap; continuing
|
|
@@ -1867,9 +2288,18 @@ Pin matching versions in your .mcp.json.`
|
|
|
1867
2288
|
});
|
|
1868
2289
|
return;
|
|
1869
2290
|
}
|
|
2291
|
+
const initScriptTempDir = await writeInitScriptToTempDir();
|
|
2292
|
+
const childEnv = {};
|
|
2293
|
+
for (const [k, v] of Object.entries(process.env)) {
|
|
2294
|
+
if (typeof v === "string") childEnv[k] = v;
|
|
2295
|
+
}
|
|
2296
|
+
if (initScriptTempDir !== void 0) {
|
|
2297
|
+
childEnv["PLAYWRIGHT_MCP_INIT_SCRIPT"] = initScriptTempDir.scriptPath;
|
|
2298
|
+
}
|
|
1870
2299
|
const upstreamTransport = new StdioClientTransport({
|
|
1871
2300
|
command: args.upstreamCommand,
|
|
1872
|
-
args: args.upstreamArgs
|
|
2301
|
+
args: args.upstreamArgs,
|
|
2302
|
+
env: childEnv
|
|
1873
2303
|
});
|
|
1874
2304
|
const upstreamClient = new Client(
|
|
1875
2305
|
{ name: "sightmap-mcp-bridge", version: "0.0.0" },
|
|
@@ -1888,11 +2318,34 @@ Pin matching versions in your .mcp.json.`
|
|
|
1888
2318
|
await upstreamClient.close();
|
|
1889
2319
|
} catch {
|
|
1890
2320
|
}
|
|
2321
|
+
if (initScriptTempDir !== void 0) {
|
|
2322
|
+
try {
|
|
2323
|
+
rmSync(initScriptTempDir.dir, { recursive: true, force: true });
|
|
2324
|
+
} catch {
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
1891
2327
|
process.exit(0);
|
|
1892
2328
|
};
|
|
1893
2329
|
process.on("SIGINT", cleanup);
|
|
1894
2330
|
process.on("SIGTERM", cleanup);
|
|
1895
2331
|
}
|
|
2332
|
+
async function writeInitScriptToTempDir() {
|
|
2333
|
+
try {
|
|
2334
|
+
const script = await getInjectScript();
|
|
2335
|
+
const unique = `${process.pid}-${Date.now()}-${randomBytes(4).toString("hex")}`;
|
|
2336
|
+
const dir = join4(tmpdir(), `sightmap-mcp-${unique}`);
|
|
2337
|
+
mkdirSync(dir, { recursive: true });
|
|
2338
|
+
const scriptPath = join4(dir, "bippy-init.js");
|
|
2339
|
+
writeFileSync(scriptPath, script, "utf8");
|
|
2340
|
+
return { dir, scriptPath };
|
|
2341
|
+
} catch (err) {
|
|
2342
|
+
process.stderr.write(
|
|
2343
|
+
`sightmap-mcp: could not write bippy init script for pre-navigate injection (falling back to post-navigate inject): ${err instanceof Error ? err.message : String(err)}
|
|
2344
|
+
`
|
|
2345
|
+
);
|
|
2346
|
+
return void 0;
|
|
2347
|
+
}
|
|
2348
|
+
}
|
|
1896
2349
|
function isInvokedAsMain() {
|
|
1897
2350
|
if (process.argv[1] === void 0) return false;
|
|
1898
2351
|
try {
|
|
@@ -1910,6 +2363,7 @@ if (isInvokedAsMain()) {
|
|
|
1910
2363
|
});
|
|
1911
2364
|
}
|
|
1912
2365
|
export {
|
|
1913
|
-
parseArgv
|
|
2366
|
+
parseArgv,
|
|
2367
|
+
writeInitScriptToTempDir
|
|
1914
2368
|
};
|
|
1915
2369
|
//# sourceMappingURL=cli.js.map
|