@sightmap/mcp 0.8.0 → 0.9.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/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/index.js
CHANGED
|
@@ -347,30 +347,388 @@ async function handleInitProject(input) {
|
|
|
347
347
|
}
|
|
348
348
|
|
|
349
349
|
// src/tools/runtime/snapshot.ts
|
|
350
|
-
async function handleRuntimeSnapshot(input) {
|
|
350
|
+
async function handleRuntimeSnapshot(input, deps = {}) {
|
|
351
351
|
if (input.source.kind === "endpoint") {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
352
|
+
return runEndpoint(input.source);
|
|
353
|
+
}
|
|
354
|
+
if (input.source.kind === "literal") {
|
|
355
|
+
return runLiteral(input.source);
|
|
356
|
+
}
|
|
357
|
+
return runBrowser(input.source, deps);
|
|
358
|
+
}
|
|
359
|
+
async function runEndpoint(source) {
|
|
360
|
+
const res = await fetch(source.url);
|
|
361
|
+
if (res.status === 404) {
|
|
362
|
+
let detail = "";
|
|
363
|
+
try {
|
|
364
|
+
const body = await res.json();
|
|
365
|
+
detail = body.error ?? "";
|
|
366
|
+
} catch {
|
|
367
|
+
}
|
|
368
|
+
throw new Error(
|
|
369
|
+
`no snapshot cached yet at ${source.url}${detail ? `: ${detail}` : ""}`
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
if (!res.ok) {
|
|
373
|
+
throw new Error(`snapshot fetch failed: HTTP ${res.status}`);
|
|
374
|
+
}
|
|
375
|
+
const snapshot = await res.json();
|
|
376
|
+
return { ok: true, snapshot, source };
|
|
377
|
+
}
|
|
378
|
+
var REQUIRED_SNAPSHOT_FIELDS = [
|
|
379
|
+
"capturedAt",
|
|
380
|
+
"route",
|
|
381
|
+
"components",
|
|
382
|
+
"markers",
|
|
383
|
+
"unmarkedCandidates"
|
|
384
|
+
];
|
|
385
|
+
function runLiteral(source) {
|
|
386
|
+
validateSnapshotShape(source.snapshot, "literal");
|
|
387
|
+
return { ok: true, snapshot: source.snapshot, source };
|
|
388
|
+
}
|
|
389
|
+
function validateSnapshotShape(value, label) {
|
|
390
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
391
|
+
throw new Error(
|
|
392
|
+
`${label} snapshot: expected an object with fields { capturedAt, route, components, markers, unmarkedCandidates }; got ${value === null ? "null" : Array.isArray(value) ? "array" : typeof value}`
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
const s = value;
|
|
396
|
+
for (const field of REQUIRED_SNAPSHOT_FIELDS) {
|
|
397
|
+
if (!(field in s)) {
|
|
398
|
+
throw new Error(`${label} snapshot: missing required field \`${field}\``);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
if (!Array.isArray(s["components"])) {
|
|
402
|
+
throw new Error(`${label} snapshot: \`components\` must be an array`);
|
|
403
|
+
}
|
|
404
|
+
if (!Array.isArray(s["markers"])) {
|
|
405
|
+
throw new Error(`${label} snapshot: \`markers\` must be an array`);
|
|
406
|
+
}
|
|
407
|
+
if (!Array.isArray(s["unmarkedCandidates"])) {
|
|
408
|
+
throw new Error(`${label} snapshot: \`unmarkedCandidates\` must be an array`);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
async function runBrowser(source, deps) {
|
|
412
|
+
if (deps.upstream === void 0) {
|
|
413
|
+
throw new Error(
|
|
414
|
+
"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."
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
const upstream = deps.upstream;
|
|
418
|
+
const wantInject = source.inject === true;
|
|
419
|
+
const allowAutoInject = source.inject === void 0;
|
|
420
|
+
const urls = source.routes !== void 0 && source.routes.length > 0 ? source.routes.map((r) => absolutize(r, source.url)) : [source.url];
|
|
421
|
+
const captures = [];
|
|
422
|
+
for (const targetUrl of urls) {
|
|
423
|
+
const captureOpts = {
|
|
424
|
+
forceInject: wantInject,
|
|
425
|
+
autoInject: allowAutoInject
|
|
426
|
+
};
|
|
427
|
+
if (deps.getInjectScript !== void 0) {
|
|
428
|
+
captureOpts.getInjectScript = deps.getInjectScript;
|
|
429
|
+
}
|
|
430
|
+
const cap = await captureOneUrl(upstream, targetUrl, captureOpts);
|
|
431
|
+
validateSnapshotShape(cap, "browser");
|
|
432
|
+
captures.push({ url: targetUrl, snapshot: cap });
|
|
433
|
+
}
|
|
434
|
+
if (source.routes === void 0 || source.routes.length === 0) {
|
|
435
|
+
return { ok: true, snapshot: captures[0].snapshot, source };
|
|
436
|
+
}
|
|
437
|
+
return { ok: true, snapshots: captures, source };
|
|
438
|
+
}
|
|
439
|
+
function absolutize(routeOrUrl, baseUrl) {
|
|
440
|
+
try {
|
|
441
|
+
return new URL(routeOrUrl).toString();
|
|
442
|
+
} catch {
|
|
443
|
+
const base = new URL(baseUrl);
|
|
444
|
+
return new URL(routeOrUrl, base).toString();
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
async function captureOneUrl(upstream, url, opts) {
|
|
448
|
+
const navResult = await upstream.callTool("browser_navigate", { url });
|
|
449
|
+
if (navResult.isError === true) {
|
|
450
|
+
throw new Error(
|
|
451
|
+
`browser_navigate to ${url} failed: ${navResult.content[0]?.text ?? "unknown error"}`
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
const probe = await evalAndDecode(
|
|
455
|
+
upstream,
|
|
456
|
+
`() => typeof (window.__sightmap__ && window.__sightmap__.snapshot)`
|
|
457
|
+
);
|
|
458
|
+
const hasHook = probe === "function";
|
|
459
|
+
if (!hasHook && (opts.forceInject || opts.autoInject)) {
|
|
460
|
+
if (opts.getInjectScript === void 0) {
|
|
360
461
|
throw new Error(
|
|
361
|
-
|
|
462
|
+
"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."
|
|
362
463
|
);
|
|
363
464
|
}
|
|
364
|
-
|
|
365
|
-
|
|
465
|
+
const script = await opts.getInjectScript();
|
|
466
|
+
const wrapped = `() => { ${script}
|
|
467
|
+
; return typeof (window.__sightmap__ && window.__sightmap__.snapshot); }`;
|
|
468
|
+
const injectResult = await evalAndDecode(upstream, wrapped);
|
|
469
|
+
if (injectResult !== "function") {
|
|
470
|
+
throw new Error(
|
|
471
|
+
`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.`
|
|
472
|
+
);
|
|
366
473
|
}
|
|
367
|
-
|
|
368
|
-
|
|
474
|
+
} else if (!hasHook && opts.forceInject === false && opts.autoInject === false) {
|
|
475
|
+
throw new Error(
|
|
476
|
+
"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`."
|
|
477
|
+
);
|
|
369
478
|
}
|
|
370
|
-
|
|
371
|
-
|
|
479
|
+
const snap = await evalAndDecode(
|
|
480
|
+
upstream,
|
|
481
|
+
`() => JSON.parse(JSON.stringify(window.__sightmap__.snapshot()))`
|
|
372
482
|
);
|
|
483
|
+
return snap;
|
|
484
|
+
}
|
|
485
|
+
async function evalAndDecode(upstream, fnSource) {
|
|
486
|
+
const r = await upstream.callTool("browser_evaluate", { function: fnSource });
|
|
487
|
+
if (r.isError === true) {
|
|
488
|
+
throw new Error(
|
|
489
|
+
`browser_evaluate failed: ${r.content[0]?.text ?? "unknown error"}`
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
const text = r.content.map((b) => b.text).join("\n");
|
|
493
|
+
return decodeEvalText(text);
|
|
373
494
|
}
|
|
495
|
+
function decodeEvalText(text) {
|
|
496
|
+
const afterResult = text.split(/^###\s*Result/im)[1] ?? text;
|
|
497
|
+
const trimmed = afterResult.trim();
|
|
498
|
+
if (trimmed.length === 0) return void 0;
|
|
499
|
+
for (let end = trimmed.length; end > 0; end--) {
|
|
500
|
+
try {
|
|
501
|
+
return JSON.parse(trimmed.slice(0, end));
|
|
502
|
+
} catch {
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
return void 0;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// src/runtime/inject-script.ts
|
|
509
|
+
import { createRequire } from "module";
|
|
510
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
511
|
+
import { dirname, resolve as resolve3 } from "path";
|
|
512
|
+
var cachedScript;
|
|
513
|
+
async function getInjectScript() {
|
|
514
|
+
if (cachedScript !== void 0) return cachedScript;
|
|
515
|
+
const bippyIife = await loadBippyIife();
|
|
516
|
+
cachedScript = `${bippyIife}
|
|
517
|
+
${BOOTSTRAP_SRC}`;
|
|
518
|
+
return cachedScript;
|
|
519
|
+
}
|
|
520
|
+
async function loadBippyIife() {
|
|
521
|
+
const require2 = createRequire(import.meta.url);
|
|
522
|
+
const bippyPkgJson = require2.resolve("bippy/package.json");
|
|
523
|
+
const iifePath = resolve3(dirname(bippyPkgJson), "dist/index.iife.js");
|
|
524
|
+
return readFile6(iifePath, "utf8");
|
|
525
|
+
}
|
|
526
|
+
var BOOTSTRAP_SRC = String.raw`
|
|
527
|
+
(function () {
|
|
528
|
+
if (typeof window === "undefined") return;
|
|
529
|
+
if (!window.Bippy || typeof window.Bippy.instrument !== "function") return;
|
|
530
|
+
if (window.__sightmap__ && typeof window.__sightmap__.snapshot === "function") {
|
|
531
|
+
// Already installed (e.g. @sightmap/react is present). Don't double-wire.
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
var trackedRoots = new Set();
|
|
536
|
+
window.Bippy.instrument({
|
|
537
|
+
onCommitFiberRoot: function (_id, root) {
|
|
538
|
+
trackedRoots.add(root);
|
|
539
|
+
},
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
var FRAMEWORK_NOISE = new Set([
|
|
543
|
+
"Routes", "Route", "RenderedRoute", "Outlet", "BrowserRouter",
|
|
544
|
+
"Router", "RouterProvider", "DataRouterProvider", "RouterProviderImpl",
|
|
545
|
+
"LinkWithRef", "StrictMode", "SightmapProvider", "Fragment", "Anonymous",
|
|
546
|
+
"Suspense", "ErrorBoundary", "Provider", "Consumer",
|
|
547
|
+
"ThemeProvider", "Hydration", "HydrateFallback",
|
|
548
|
+
"RemixErrorBoundary", "HydratedRouter", "DataRoutes",
|
|
549
|
+
"WithHydrateFallbackProps2", "RenderErrorBoundary", "Layout",
|
|
550
|
+
]);
|
|
551
|
+
|
|
552
|
+
var HOST_PORTAL_TAG = 4;
|
|
553
|
+
|
|
554
|
+
function cssEsc(v) { return String(v).replace(/"/g, '\\"'); }
|
|
555
|
+
function isHumanShapedId(id) {
|
|
556
|
+
if (/[0-9]{6,}/.test(id)) return false;
|
|
557
|
+
if (/^[a-z]+-[a-f0-9]{8,}/i.test(id)) return false;
|
|
558
|
+
if (/:r[0-9a-z]+:/i.test(id)) return false;
|
|
559
|
+
return true;
|
|
560
|
+
}
|
|
561
|
+
function rankSelectors(el) {
|
|
562
|
+
var out = [];
|
|
563
|
+
var dsm = el.getAttribute("data-sightmap");
|
|
564
|
+
if (dsm !== null) {
|
|
565
|
+
out.push({ selector: '[data-sightmap="' + cssEsc(dsm) + '"]', stability: "high", source: "data-sightmap" });
|
|
566
|
+
}
|
|
567
|
+
["data-testid", "data-cy", "data-test"].forEach(function (attr) {
|
|
568
|
+
var v = el.getAttribute(attr);
|
|
569
|
+
if (v !== null) {
|
|
570
|
+
out.push({ selector: "[" + attr + '="' + cssEsc(v) + '"]', stability: "high", source: attr });
|
|
571
|
+
}
|
|
572
|
+
});
|
|
573
|
+
var role = el.getAttribute("role");
|
|
574
|
+
var aria = el.getAttribute("aria-label");
|
|
575
|
+
if (role && aria) {
|
|
576
|
+
out.push({ selector: '[role="' + cssEsc(role) + '"][aria-label="' + cssEsc(aria) + '"]', stability: "medium", source: "aria" });
|
|
577
|
+
} else if (aria) {
|
|
578
|
+
out.push({ selector: '[aria-label="' + cssEsc(aria) + '"]', stability: "medium", source: "aria-label" });
|
|
579
|
+
}
|
|
580
|
+
var name = el.getAttribute("name");
|
|
581
|
+
if (name !== null && /^(form|input|button|select|textarea|nav)$/i.test(el.tagName)) {
|
|
582
|
+
out.push({ selector: el.tagName.toLowerCase() + '[name="' + cssEsc(name) + '"]', stability: "medium", source: "semantic-name" });
|
|
583
|
+
}
|
|
584
|
+
if (el.id && isHumanShapedId(el.id)) {
|
|
585
|
+
out.push({ selector: "#" + cssEsc(el.id), stability: "high", source: "id" });
|
|
586
|
+
}
|
|
587
|
+
return out;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function findHostElementForFiber(fiber) {
|
|
591
|
+
if (!fiber.child) return null;
|
|
592
|
+
var stack = [fiber.child];
|
|
593
|
+
var walks = 0;
|
|
594
|
+
while (stack.length > 0 && walks < 5000) {
|
|
595
|
+
walks++;
|
|
596
|
+
var f = stack.pop();
|
|
597
|
+
if (!f) continue;
|
|
598
|
+
if (f.sibling) stack.push(f.sibling);
|
|
599
|
+
if (f.tag === HOST_PORTAL_TAG) continue;
|
|
600
|
+
var sn = f.stateNode;
|
|
601
|
+
if (sn && typeof sn === "object" && "tagName" in sn && document.contains(sn)) {
|
|
602
|
+
return sn;
|
|
603
|
+
}
|
|
604
|
+
if (f.child) stack.push(f.child);
|
|
605
|
+
}
|
|
606
|
+
return null;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function bridgeDomToFiber(el) {
|
|
610
|
+
for (var i = 0, keys = Object.keys(el); i < keys.length; i++) {
|
|
611
|
+
if (keys[i].indexOf("__reactFiber$") === 0) return el[keys[i]] || null;
|
|
612
|
+
}
|
|
613
|
+
return null;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
function nearestCompositeName(fiber) {
|
|
617
|
+
var f = fiber, walks = 0;
|
|
618
|
+
while (f && walks < 50) {
|
|
619
|
+
if (window.Bippy.isCompositeFiber(f)) {
|
|
620
|
+
var n = window.Bippy.getDisplayName(f);
|
|
621
|
+
if (n) return n;
|
|
622
|
+
}
|
|
623
|
+
f = f.return;
|
|
624
|
+
walks++;
|
|
625
|
+
}
|
|
626
|
+
return undefined;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
function domDepth(el) {
|
|
630
|
+
var d = 0, cur = el.parentElement;
|
|
631
|
+
while (cur && cur !== document.body) { d++; cur = cur.parentElement; }
|
|
632
|
+
return d;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
function collectComponents(rootFiber) {
|
|
636
|
+
var out = [];
|
|
637
|
+
var depthByFiber = new Map();
|
|
638
|
+
depthByFiber.set(rootFiber, 0);
|
|
639
|
+
window.Bippy.traverseFiber(rootFiber, function (f) {
|
|
640
|
+
var depth = depthByFiber.get(f) || 0;
|
|
641
|
+
var c = f.child;
|
|
642
|
+
while (c) { depthByFiber.set(c, depth + 1); c = c.sibling; }
|
|
643
|
+
if (!window.Bippy.isCompositeFiber(f)) return false;
|
|
644
|
+
var name = window.Bippy.getDisplayName(f);
|
|
645
|
+
if (!name) return false;
|
|
646
|
+
var childNames = [];
|
|
647
|
+
var cc = f.child;
|
|
648
|
+
while (cc) {
|
|
649
|
+
if (window.Bippy.isCompositeFiber(cc)) {
|
|
650
|
+
var n = window.Bippy.getDisplayName(cc);
|
|
651
|
+
if (n) childNames.push(n);
|
|
652
|
+
}
|
|
653
|
+
cc = cc.sibling;
|
|
654
|
+
}
|
|
655
|
+
out.push({ displayName: name, depth: depth, children: childNames });
|
|
656
|
+
return false;
|
|
657
|
+
});
|
|
658
|
+
return out;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
function collectMarkers() {
|
|
662
|
+
var out = [];
|
|
663
|
+
if (typeof document === "undefined") return out;
|
|
664
|
+
var els = document.querySelectorAll("[data-sightmap]");
|
|
665
|
+
for (var i = 0; i < els.length; i++) {
|
|
666
|
+
var el = els[i];
|
|
667
|
+
var name = el.getAttribute("data-sightmap") || "";
|
|
668
|
+
var fiber = bridgeDomToFiber(el);
|
|
669
|
+
var nc = nearestCompositeName(fiber);
|
|
670
|
+
var marker = {
|
|
671
|
+
name: name,
|
|
672
|
+
hostTag: el.tagName.toLowerCase(),
|
|
673
|
+
domDepth: domDepth(el),
|
|
674
|
+
candidateSelectors: rankSelectors(el),
|
|
675
|
+
};
|
|
676
|
+
if (nc !== undefined) marker.nearestComponent = nc;
|
|
677
|
+
out.push(marker);
|
|
678
|
+
}
|
|
679
|
+
return out;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
function collectUnmarkedCandidates(rootFiber) {
|
|
683
|
+
var out = [];
|
|
684
|
+
var seen = new Set();
|
|
685
|
+
window.Bippy.traverseFiber(rootFiber, function (f) {
|
|
686
|
+
if (!window.Bippy.isCompositeFiber(f)) return false;
|
|
687
|
+
var name = window.Bippy.getDisplayName(f);
|
|
688
|
+
if (!name || seen.has(name) || FRAMEWORK_NOISE.has(name)) return false;
|
|
689
|
+
seen.add(name);
|
|
690
|
+
var hostEl = findHostElementForFiber(f);
|
|
691
|
+
if (!hostEl) return false;
|
|
692
|
+
var selectors = rankSelectors(hostEl);
|
|
693
|
+
if (selectors.length === 0) return false;
|
|
694
|
+
out.push({
|
|
695
|
+
displayName: name,
|
|
696
|
+
hostTag: hostEl.tagName.toLowerCase(),
|
|
697
|
+
selectors: selectors,
|
|
698
|
+
domDepth: domDepth(hostEl),
|
|
699
|
+
});
|
|
700
|
+
return false;
|
|
701
|
+
});
|
|
702
|
+
return out;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
function snapshot() {
|
|
706
|
+
var roots = Array.from(trackedRoots);
|
|
707
|
+
var now = new Date().toISOString();
|
|
708
|
+
var loc = (typeof window !== "undefined" && window.location)
|
|
709
|
+
? (window.location.pathname + (window.location.search || ""))
|
|
710
|
+
: "?";
|
|
711
|
+
if (roots.length === 0) {
|
|
712
|
+
return { capturedAt: now, route: loc === "?" ? "?" : loc, commits: 0, components: [], markers: [], unmarkedCandidates: [] };
|
|
713
|
+
}
|
|
714
|
+
var root = roots.find(function (r) { return r.current && r.current.child !== null; }) || roots[0];
|
|
715
|
+
if (!root) {
|
|
716
|
+
return { capturedAt: now, route: loc === "?" ? "?" : loc, commits: 0, components: [], markers: [], unmarkedCandidates: [] };
|
|
717
|
+
}
|
|
718
|
+
var rootFiber = root.current;
|
|
719
|
+
return {
|
|
720
|
+
capturedAt: now,
|
|
721
|
+
route: loc,
|
|
722
|
+
commits: roots.length,
|
|
723
|
+
components: collectComponents(rootFiber),
|
|
724
|
+
markers: collectMarkers(),
|
|
725
|
+
unmarkedCandidates: collectUnmarkedCandidates(rootFiber),
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
window.__sightmap__ = { snapshot: snapshot };
|
|
730
|
+
})();
|
|
731
|
+
`;
|
|
374
732
|
|
|
375
733
|
// src/tools/propose/store.ts
|
|
376
734
|
var ProposalStore = class {
|
|
@@ -796,19 +1154,52 @@ var SIGHTMAP_ADD_VIEW = {
|
|
|
796
1154
|
};
|
|
797
1155
|
var SIGHTMAP_RUNTIME_SNAPSHOT = {
|
|
798
1156
|
name: "sightmap_runtime_snapshot",
|
|
799
|
-
description: "Fetch
|
|
1157
|
+
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.",
|
|
800
1158
|
inputSchema: {
|
|
801
1159
|
type: "object",
|
|
802
1160
|
properties: {
|
|
803
1161
|
source: {
|
|
804
1162
|
type: "object",
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
1163
|
+
oneOf: [
|
|
1164
|
+
{
|
|
1165
|
+
type: "object",
|
|
1166
|
+
properties: {
|
|
1167
|
+
kind: { type: "string", const: "endpoint" },
|
|
1168
|
+
url: { type: "string", description: "Plugin endpoint URL, e.g. http://localhost:5173/__sightmap__/snapshot.json." }
|
|
1169
|
+
},
|
|
1170
|
+
required: ["kind", "url"],
|
|
1171
|
+
additionalProperties: false
|
|
1172
|
+
},
|
|
1173
|
+
{
|
|
1174
|
+
type: "object",
|
|
1175
|
+
properties: {
|
|
1176
|
+
kind: { type: "string", const: "browser" },
|
|
1177
|
+
url: { type: "string", description: "Target URL to navigate to." },
|
|
1178
|
+
routes: {
|
|
1179
|
+
type: "array",
|
|
1180
|
+
items: { type: "string" },
|
|
1181
|
+
description: "Optional list of routes (absolute or relative to `url`) to capture in sequence."
|
|
1182
|
+
},
|
|
1183
|
+
inject: {
|
|
1184
|
+
type: "boolean",
|
|
1185
|
+
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__."
|
|
1186
|
+
}
|
|
1187
|
+
},
|
|
1188
|
+
required: ["kind", "url"],
|
|
1189
|
+
additionalProperties: false
|
|
1190
|
+
},
|
|
1191
|
+
{
|
|
1192
|
+
type: "object",
|
|
1193
|
+
properties: {
|
|
1194
|
+
kind: { type: "string", const: "literal" },
|
|
1195
|
+
snapshot: {
|
|
1196
|
+
description: "A pre-captured SightmapSnapshot object. Must include capturedAt, route, components, markers, unmarkedCandidates."
|
|
1197
|
+
}
|
|
1198
|
+
},
|
|
1199
|
+
required: ["kind", "snapshot"],
|
|
1200
|
+
additionalProperties: false
|
|
1201
|
+
}
|
|
1202
|
+
]
|
|
812
1203
|
}
|
|
813
1204
|
},
|
|
814
1205
|
required: ["source"],
|
|
@@ -1318,25 +1709,53 @@ var SightmapMcpServer = class {
|
|
|
1318
1709
|
);
|
|
1319
1710
|
}
|
|
1320
1711
|
const s = source;
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
"sightmap_runtime_snapshot: `source.kind` must be 'endpoint' or 'browser'."
|
|
1324
|
-
);
|
|
1325
|
-
}
|
|
1326
|
-
if (typeof s["url"] !== "string" || s["url"].length === 0) {
|
|
1712
|
+
const kind = s["kind"];
|
|
1713
|
+
if (kind !== "endpoint" && kind !== "browser" && kind !== "literal") {
|
|
1327
1714
|
return errorResult(
|
|
1328
|
-
"sightmap_runtime_snapshot: `source.
|
|
1715
|
+
"sightmap_runtime_snapshot: `source.kind` must be 'endpoint', 'browser', or 'literal'."
|
|
1329
1716
|
);
|
|
1330
1717
|
}
|
|
1331
|
-
|
|
1332
|
-
|
|
1718
|
+
let input;
|
|
1719
|
+
if (kind === "endpoint") {
|
|
1720
|
+
if (typeof s["url"] !== "string" || s["url"].length === 0) {
|
|
1721
|
+
return errorResult(
|
|
1722
|
+
"sightmap_runtime_snapshot: endpoint mode requires `source.url` (string)."
|
|
1723
|
+
);
|
|
1724
|
+
}
|
|
1725
|
+
input = { source: { kind: "endpoint", url: s["url"] } };
|
|
1726
|
+
} else if (kind === "browser") {
|
|
1727
|
+
if (typeof s["url"] !== "string" || s["url"].length === 0) {
|
|
1728
|
+
return errorResult(
|
|
1729
|
+
"sightmap_runtime_snapshot: browser mode requires `source.url` (string)."
|
|
1730
|
+
);
|
|
1731
|
+
}
|
|
1732
|
+
const browserSrc = {
|
|
1333
1733
|
kind: "browser",
|
|
1334
|
-
url: s["url"]
|
|
1335
|
-
|
|
1734
|
+
url: s["url"]
|
|
1735
|
+
};
|
|
1736
|
+
if (Array.isArray(s["routes"])) {
|
|
1737
|
+
browserSrc.routes = s["routes"].filter(
|
|
1738
|
+
(r) => typeof r === "string"
|
|
1739
|
+
);
|
|
1336
1740
|
}
|
|
1337
|
-
|
|
1741
|
+
if (typeof s["inject"] === "boolean") {
|
|
1742
|
+
browserSrc.inject = s["inject"];
|
|
1743
|
+
}
|
|
1744
|
+
input = { source: browserSrc };
|
|
1745
|
+
} else {
|
|
1746
|
+
if (!("snapshot" in s)) {
|
|
1747
|
+
return errorResult(
|
|
1748
|
+
"sightmap_runtime_snapshot: literal mode requires `source.snapshot`."
|
|
1749
|
+
);
|
|
1750
|
+
}
|
|
1751
|
+
input = { source: { kind: "literal", snapshot: s["snapshot"] } };
|
|
1752
|
+
}
|
|
1338
1753
|
try {
|
|
1339
|
-
const
|
|
1754
|
+
const deps = {
|
|
1755
|
+
getInjectScript
|
|
1756
|
+
};
|
|
1757
|
+
if (this.upstream !== void 0) deps.upstream = this.upstream;
|
|
1758
|
+
const result = await handleRuntimeSnapshot(input, deps);
|
|
1340
1759
|
return {
|
|
1341
1760
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1342
1761
|
};
|