@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 CHANGED
@@ -8,5 +8,17 @@ interface CliArgs {
8
8
  }
9
9
  /** Argv parser. Exposed for unit testing. */
10
10
  declare function parseArgv(argv: string[]): CliArgs;
11
+ /**
12
+ * Write the bippy inject script to a uniquely-named temp directory and return
13
+ * the path. Returns undefined if the script can't be materialized (e.g., bippy
14
+ * IIFE missing in dev contexts) — the caller treats that as "skip pre-nav
15
+ * injection; rely on the post-nav fallback".
16
+ *
17
+ * Visible for unit testing.
18
+ */
19
+ declare function writeInitScriptToTempDir(): Promise<{
20
+ dir: string;
21
+ scriptPath: string;
22
+ } | undefined>;
11
23
 
12
- export { parseArgv };
24
+ export { parseArgv, writeInitScriptToTempDir };
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 resolve3, dirname } from "path";
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
- const res = await fetch(input.source.url);
408
- if (res.status === 404) {
409
- let detail = "";
410
- try {
411
- const body = await res.json();
412
- detail = body.error ?? "";
413
- } catch {
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
- `no snapshot cached yet at ${input.source.url}${detail ? `: ${detail}` : ""}`
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
- if (!res.ok) {
420
- throw new Error(`snapshot fetch failed: HTTP ${res.status}`);
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
- const snapshot = await res.json();
423
- return { ok: true, snapshot, source: input.source };
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
- throw new Error(
426
- "browser-driven snapshot capture is not implemented in 1.0-rc; use { kind: 'endpoint' } with the Vite plugin endpoint instead"
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 the live SightmapSnapshot from a running Vite dev server's /__sightmap__/snapshot.json endpoint. Pass { source: { kind: 'endpoint', url: 'http://localhost:5173/__sightmap__/snapshot.json' } }. The browser-driven (kind: 'browser') mode is reserved for future Playwright bridge support.",
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
- properties: {
861
- kind: { type: "string", enum: ["endpoint", "browser"] },
862
- url: { type: "string" },
863
- routes: { type: "array", items: { type: "string" } }
864
- },
865
- required: ["kind", "url"],
866
- additionalProperties: false
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
- if (s["kind"] !== "endpoint" && s["kind"] !== "browser") {
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 'browser'."
1772
+ "sightmap_runtime_snapshot: `source.kind` must be 'endpoint', 'browser', or 'literal'."
1379
1773
  );
1380
1774
  }
1381
- if (typeof s["url"] !== "string" || s["url"].length === 0) {
1382
- return errorResult(
1383
- "sightmap_runtime_snapshot: `source.url` (string) is required."
1384
- );
1385
- }
1386
- const input = s["kind"] === "endpoint" ? { source: { kind: "endpoint", url: s["url"] } } : {
1387
- source: {
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
- ...Array.isArray(s["routes"]) ? { routes: s["routes"].filter((r) => typeof r === "string") } : {}
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 result = await handleRuntimeSnapshot(input);
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 = dirname(fileURLToPath(import.meta.url));
1767
- var pkgJson = JSON.parse(readFileSync(resolve3(__dirname, "../package.json"), "utf8"));
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 ? resolve3(explicitCurateRoot) : resolve3(out.sightmapDirs[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) => resolve3(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