@sightmap/mcp 0.7.1 → 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 +1048 -23
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.js +1002 -12
- 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, 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
|
|
@@ -246,6 +248,22 @@ async function handleUpdateView(input) {
|
|
|
246
248
|
if (input.patch.memory_replace) {
|
|
247
249
|
after.memory = [...input.patch.memory_replace];
|
|
248
250
|
}
|
|
251
|
+
if (input.patch.name !== void 0) {
|
|
252
|
+
after.name = input.patch.name;
|
|
253
|
+
}
|
|
254
|
+
if (input.patch.route !== void 0) {
|
|
255
|
+
after.route = input.patch.route;
|
|
256
|
+
}
|
|
257
|
+
if (input.patch.components !== void 0) {
|
|
258
|
+
const seen = /* @__PURE__ */ new Set();
|
|
259
|
+
for (const c2 of input.patch.components) {
|
|
260
|
+
if (seen.has(c2.name)) {
|
|
261
|
+
throw new Error(`duplicate component name in patch: ${c2.name}`);
|
|
262
|
+
}
|
|
263
|
+
seen.add(c2.name);
|
|
264
|
+
}
|
|
265
|
+
after.components = input.patch.components;
|
|
266
|
+
}
|
|
249
267
|
const nextViews = [...views];
|
|
250
268
|
nextViews[idx] = after;
|
|
251
269
|
const nextDoc = stripFragmentBrand(parsed);
|
|
@@ -265,23 +283,108 @@ function stripFragmentBrand(input) {
|
|
|
265
283
|
return out;
|
|
266
284
|
}
|
|
267
285
|
|
|
286
|
+
// src/tools/curate/add.ts
|
|
287
|
+
import { writeFile as writeFile2, readFile as readFile4, readdir as readdir2 } from "fs/promises";
|
|
288
|
+
import { resolve, join as join2 } from "path";
|
|
289
|
+
import {
|
|
290
|
+
format as format2,
|
|
291
|
+
canonicalize as canonicalize2
|
|
292
|
+
} from "@sightmap/sightmap";
|
|
293
|
+
async function handleAddView(input) {
|
|
294
|
+
await ensureNotExists(input.sightmapDir, input.view.name);
|
|
295
|
+
const filename = input.file ?? kebab(input.view.name) + ".yaml";
|
|
296
|
+
const path = join2(input.sightmapDir, filename);
|
|
297
|
+
const doc = {
|
|
298
|
+
version: 1,
|
|
299
|
+
views: [input.view]
|
|
300
|
+
};
|
|
301
|
+
const out = format2(doc);
|
|
302
|
+
const c = canonicalize2(out, { file: path });
|
|
303
|
+
const text = c.kind === "canonical" ? c.text : out;
|
|
304
|
+
await writeFile2(path, text, "utf8");
|
|
305
|
+
return { ok: true, file: path, written: input.view };
|
|
306
|
+
}
|
|
307
|
+
async function ensureNotExists(dir, name) {
|
|
308
|
+
let files;
|
|
309
|
+
try {
|
|
310
|
+
files = await readdir2(dir);
|
|
311
|
+
} catch {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
for (const f of files) {
|
|
315
|
+
if (!f.endsWith(".yaml")) continue;
|
|
316
|
+
const text = await readFile4(resolve(dir, f), "utf8");
|
|
317
|
+
if (new RegExp(`(^|\\n)\\s*-\\s+name:\\s+${escapeRe(name)}(\\s|$)`).test(text)) {
|
|
318
|
+
throw new Error(`view "${name}" already exists in ${f}`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
function kebab(s) {
|
|
323
|
+
return s.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
|
|
324
|
+
}
|
|
325
|
+
function escapeRe(s) {
|
|
326
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// src/tools/curate/remove.ts
|
|
330
|
+
import { readFile as readFile5, writeFile as writeFile3, unlink, readdir as readdir3 } from "fs/promises";
|
|
331
|
+
import { resolve as resolve2 } from "path";
|
|
332
|
+
import {
|
|
333
|
+
parse as parse4,
|
|
334
|
+
format as format3,
|
|
335
|
+
canonicalize as canonicalize3
|
|
336
|
+
} from "@sightmap/sightmap";
|
|
337
|
+
async function handleDeleteView(input) {
|
|
338
|
+
const entries = await readdir3(input.sightmapDir);
|
|
339
|
+
for (const entry of entries) {
|
|
340
|
+
if (!entry.endsWith(".yaml")) continue;
|
|
341
|
+
const path = resolve2(input.sightmapDir, entry);
|
|
342
|
+
const text = await readFile5(path, "utf8");
|
|
343
|
+
const parsed = parse4(text, { sourceFile: path });
|
|
344
|
+
const views = parsed.views ?? [];
|
|
345
|
+
const idx = views.findIndex((v) => v.name === input.name);
|
|
346
|
+
if (idx < 0) continue;
|
|
347
|
+
const remaining = views.filter((_, i) => i !== idx);
|
|
348
|
+
if (remaining.length === 0) {
|
|
349
|
+
await unlink(path);
|
|
350
|
+
return { ok: true, file: path, fileDeleted: true };
|
|
351
|
+
}
|
|
352
|
+
const nextDoc = stripFragmentBrand2(parsed);
|
|
353
|
+
nextDoc.views = remaining;
|
|
354
|
+
const out = format3(nextDoc);
|
|
355
|
+
const c = canonicalize3(out, { file: path });
|
|
356
|
+
const finalText = c.kind === "canonical" ? c.text : out;
|
|
357
|
+
await writeFile3(path, finalText, "utf8");
|
|
358
|
+
return { ok: true, file: path, fileDeleted: false };
|
|
359
|
+
}
|
|
360
|
+
throw new Error(`view "${input.name}" not found in ${input.sightmapDir}`);
|
|
361
|
+
}
|
|
362
|
+
function stripFragmentBrand2(input) {
|
|
363
|
+
const out = {};
|
|
364
|
+
for (const k of Object.keys(input)) {
|
|
365
|
+
if (k === "__brand" || k === "__sourceFile") continue;
|
|
366
|
+
out[k] = input[k];
|
|
367
|
+
}
|
|
368
|
+
return out;
|
|
369
|
+
}
|
|
370
|
+
|
|
268
371
|
// src/tools/curate/init.ts
|
|
269
|
-
import { mkdir, writeFile as
|
|
270
|
-
import { join as
|
|
372
|
+
import { mkdir, writeFile as writeFile4, access } from "fs/promises";
|
|
373
|
+
import { join as join3 } from "path";
|
|
271
374
|
|
|
272
375
|
// src/curate/scaffolder.ts
|
|
273
|
-
import { format as
|
|
376
|
+
import { format as format4, canonicalize as canonicalize4 } from "@sightmap/sightmap";
|
|
274
377
|
function makeStarterSightmap() {
|
|
275
|
-
const text =
|
|
276
|
-
const c =
|
|
378
|
+
const text = format4({ version: 1 });
|
|
379
|
+
const c = canonicalize4(text, { file: "app.yaml" });
|
|
277
380
|
return c.kind === "canonical" ? c.text : text;
|
|
278
381
|
}
|
|
279
382
|
|
|
280
383
|
// src/tools/curate/init.ts
|
|
281
384
|
async function handleInitProject(input) {
|
|
282
385
|
const root = input.dir ?? process.cwd();
|
|
283
|
-
const sightmapDir =
|
|
284
|
-
const appFile =
|
|
386
|
+
const sightmapDir = join3(root, ".sightmap");
|
|
387
|
+
const appFile = join3(sightmapDir, "app.yaml");
|
|
285
388
|
if (input.force !== true) {
|
|
286
389
|
let exists = false;
|
|
287
390
|
try {
|
|
@@ -296,10 +399,485 @@ async function handleInitProject(input) {
|
|
|
296
399
|
}
|
|
297
400
|
}
|
|
298
401
|
await mkdir(sightmapDir, { recursive: true });
|
|
299
|
-
await
|
|
402
|
+
await writeFile4(appFile, makeStarterSightmap(), "utf8");
|
|
300
403
|
return { ok: true, files: [appFile] };
|
|
301
404
|
}
|
|
302
405
|
|
|
406
|
+
// src/tools/runtime/snapshot.ts
|
|
407
|
+
async function handleRuntimeSnapshot(input, deps = {}) {
|
|
408
|
+
if (input.source.kind === "endpoint") {
|
|
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) {
|
|
518
|
+
throw new Error(
|
|
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."
|
|
520
|
+
);
|
|
521
|
+
}
|
|
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
|
+
);
|
|
530
|
+
}
|
|
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
|
+
);
|
|
535
|
+
}
|
|
536
|
+
const snap = await evalAndDecode(
|
|
537
|
+
upstream,
|
|
538
|
+
`() => JSON.parse(JSON.stringify(window.__sightmap__.snapshot()))`
|
|
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;
|
|
563
|
+
}
|
|
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
|
+
|
|
790
|
+
// src/tools/propose/store.ts
|
|
791
|
+
var ProposalStore = class {
|
|
792
|
+
views = [];
|
|
793
|
+
components = [];
|
|
794
|
+
nextId = 1;
|
|
795
|
+
mintId(prefix) {
|
|
796
|
+
return `${prefix}_${this.nextId++}`;
|
|
797
|
+
}
|
|
798
|
+
appendView(p) {
|
|
799
|
+
const v = { id: this.mintId("v"), ...p };
|
|
800
|
+
this.views.push(v);
|
|
801
|
+
return v;
|
|
802
|
+
}
|
|
803
|
+
appendComponent(p) {
|
|
804
|
+
const c = { id: this.mintId("c"), ...p };
|
|
805
|
+
this.components.push(c);
|
|
806
|
+
return c;
|
|
807
|
+
}
|
|
808
|
+
review() {
|
|
809
|
+
return { views: [...this.views], components: [...this.components] };
|
|
810
|
+
}
|
|
811
|
+
commit(input = {}) {
|
|
812
|
+
if (!input.ids) {
|
|
813
|
+
const out = this.review();
|
|
814
|
+
this.views = [];
|
|
815
|
+
this.components = [];
|
|
816
|
+
return out;
|
|
817
|
+
}
|
|
818
|
+
const selected = new Set(input.ids);
|
|
819
|
+
const consumedViews = this.views.filter((v) => selected.has(v.id));
|
|
820
|
+
const consumedComponents = this.components.filter((c) => selected.has(c.id));
|
|
821
|
+
this.views = this.views.filter((v) => !selected.has(v.id));
|
|
822
|
+
this.components = this.components.filter((c) => !selected.has(c.id));
|
|
823
|
+
return { views: consumedViews, components: consumedComponents };
|
|
824
|
+
}
|
|
825
|
+
};
|
|
826
|
+
var proposalStore = new ProposalStore();
|
|
827
|
+
|
|
828
|
+
// src/tools/propose/view.ts
|
|
829
|
+
async function handleProposeView(input) {
|
|
830
|
+
const v = proposalStore.appendView(input);
|
|
831
|
+
return { ok: true, id: v.id };
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
// src/tools/propose/component.ts
|
|
835
|
+
async function handleProposeComponent(input) {
|
|
836
|
+
const c = proposalStore.appendComponent(input);
|
|
837
|
+
return { ok: true, id: c.id };
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// src/tools/propose/review.ts
|
|
841
|
+
async function handleReviewProposals() {
|
|
842
|
+
return proposalStore.review();
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// src/tools/propose/commit.ts
|
|
846
|
+
async function handleCommitProposals(input) {
|
|
847
|
+
if (input.dry_run === true) {
|
|
848
|
+
const peek = proposalStore.review();
|
|
849
|
+
const selectedIds = input.ids ? new Set(input.ids) : null;
|
|
850
|
+
const filtered = {
|
|
851
|
+
views: selectedIds ? peek.views.filter((v) => selectedIds.has(v.id)) : peek.views,
|
|
852
|
+
components: selectedIds ? peek.components.filter((c) => selectedIds.has(c.id)) : peek.components
|
|
853
|
+
};
|
|
854
|
+
return { ok: true, committed: filtered, writes: [] };
|
|
855
|
+
}
|
|
856
|
+
const consumed = proposalStore.commit(
|
|
857
|
+
input.ids ? { ids: input.ids } : {}
|
|
858
|
+
);
|
|
859
|
+
const writes = [];
|
|
860
|
+
for (const v of consumed.views) {
|
|
861
|
+
const r = await handleAddView({
|
|
862
|
+
sightmapDir: input.sightmapDir,
|
|
863
|
+
view: {
|
|
864
|
+
name: v.name ?? deriveName(v.route),
|
|
865
|
+
route: v.route,
|
|
866
|
+
components: [],
|
|
867
|
+
...v.intent !== void 0 ? { intent: v.intent } : {},
|
|
868
|
+
...v.memory_notes && v.memory_notes.length > 0 ? { memory: v.memory_notes } : {}
|
|
869
|
+
}
|
|
870
|
+
});
|
|
871
|
+
writes.push({ kind: "view", file: r.file });
|
|
872
|
+
}
|
|
873
|
+
return { ok: true, committed: consumed, writes };
|
|
874
|
+
}
|
|
875
|
+
function deriveName(route) {
|
|
876
|
+
const parts = route.replace(/^\/+|\/+$/g, "").split("/").filter((p) => p.length > 0);
|
|
877
|
+
const last = parts[parts.length - 1] ?? "Index";
|
|
878
|
+
return last.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()).replace(/\s+/g, "");
|
|
879
|
+
}
|
|
880
|
+
|
|
303
881
|
// src/prompts/sepTrack.ts
|
|
304
882
|
var VALID_PHASES = ["draft", "spec", "fixture", "verify"];
|
|
305
883
|
function buildDraftPhase(args) {
|
|
@@ -557,7 +1135,7 @@ var SIGHTMAP_INIT_PROJECT = {
|
|
|
557
1135
|
};
|
|
558
1136
|
var SIGHTMAP_UPDATE_VIEW = {
|
|
559
1137
|
name: "sightmap_update_view",
|
|
560
|
-
description: "Apply a
|
|
1138
|
+
description: "Apply a patch to a view's fields. Accepts both semantic edits (description, intent, memory_append/memory_replace) and structural edits (name, route, components). Memory edits use either `memory_append` (default) or `memory_replace` \u2014 pass exactly one. Components is a full array replacement; use sightmap_add_view/sightmap_delete_view for whole-view lifecycle.",
|
|
561
1139
|
inputSchema: {
|
|
562
1140
|
type: "object",
|
|
563
1141
|
properties: {
|
|
@@ -568,7 +1146,22 @@ var SIGHTMAP_UPDATE_VIEW = {
|
|
|
568
1146
|
description: { type: "string" },
|
|
569
1147
|
intent: { type: "string" },
|
|
570
1148
|
memory_append: { type: "array", items: { type: "string" } },
|
|
571
|
-
memory_replace: { type: "array", items: { type: "string" } }
|
|
1149
|
+
memory_replace: { type: "array", items: { type: "string" } },
|
|
1150
|
+
name: { type: "string" },
|
|
1151
|
+
route: { type: "string" },
|
|
1152
|
+
components: {
|
|
1153
|
+
type: "array",
|
|
1154
|
+
items: {
|
|
1155
|
+
type: "object",
|
|
1156
|
+
properties: {
|
|
1157
|
+
name: { type: "string" },
|
|
1158
|
+
selector: { type: "string" },
|
|
1159
|
+
memory: { type: "array", items: { type: "string" } }
|
|
1160
|
+
},
|
|
1161
|
+
required: ["name", "selector"],
|
|
1162
|
+
additionalProperties: true
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
572
1165
|
},
|
|
573
1166
|
additionalProperties: false
|
|
574
1167
|
}
|
|
@@ -577,6 +1170,164 @@ var SIGHTMAP_UPDATE_VIEW = {
|
|
|
577
1170
|
additionalProperties: false
|
|
578
1171
|
}
|
|
579
1172
|
};
|
|
1173
|
+
var SIGHTMAP_ADD_VIEW = {
|
|
1174
|
+
name: "sightmap_add_view",
|
|
1175
|
+
description: "Create a new view in a new .yaml file. Filename defaults to kebab(view.name) + '.yaml' unless overridden via `file`. Rejects names that collide with an existing view.",
|
|
1176
|
+
inputSchema: {
|
|
1177
|
+
type: "object",
|
|
1178
|
+
properties: {
|
|
1179
|
+
view: {
|
|
1180
|
+
type: "object",
|
|
1181
|
+
properties: {
|
|
1182
|
+
name: { type: "string" },
|
|
1183
|
+
route: { type: "string" },
|
|
1184
|
+
description: { type: "string" },
|
|
1185
|
+
memory: { type: "array", items: { type: "string" } },
|
|
1186
|
+
components: {
|
|
1187
|
+
type: "array",
|
|
1188
|
+
items: {
|
|
1189
|
+
type: "object",
|
|
1190
|
+
properties: {
|
|
1191
|
+
name: { type: "string" },
|
|
1192
|
+
selector: { type: "string" },
|
|
1193
|
+
memory: { type: "array", items: { type: "string" } }
|
|
1194
|
+
},
|
|
1195
|
+
required: ["name", "selector"],
|
|
1196
|
+
additionalProperties: true
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
},
|
|
1200
|
+
required: ["name", "route"],
|
|
1201
|
+
additionalProperties: true
|
|
1202
|
+
},
|
|
1203
|
+
file: {
|
|
1204
|
+
type: "string",
|
|
1205
|
+
description: "Optional filename hint (relative to the curate root). Defaults to kebab(view.name) + '.yaml'."
|
|
1206
|
+
}
|
|
1207
|
+
},
|
|
1208
|
+
required: ["view"],
|
|
1209
|
+
additionalProperties: false
|
|
1210
|
+
}
|
|
1211
|
+
};
|
|
1212
|
+
var SIGHTMAP_RUNTIME_SNAPSHOT = {
|
|
1213
|
+
name: "sightmap_runtime_snapshot",
|
|
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.",
|
|
1215
|
+
inputSchema: {
|
|
1216
|
+
type: "object",
|
|
1217
|
+
properties: {
|
|
1218
|
+
source: {
|
|
1219
|
+
type: "object",
|
|
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
|
+
]
|
|
1260
|
+
}
|
|
1261
|
+
},
|
|
1262
|
+
required: ["source"],
|
|
1263
|
+
additionalProperties: false
|
|
1264
|
+
}
|
|
1265
|
+
};
|
|
1266
|
+
var SIGHTMAP_PROPOSE_VIEW = {
|
|
1267
|
+
name: "sightmap_propose_view",
|
|
1268
|
+
description: "Stage a proposed view from runtime observation. Does NOT write disk. Use sightmap_commit_proposals to persist.",
|
|
1269
|
+
inputSchema: {
|
|
1270
|
+
type: "object",
|
|
1271
|
+
properties: {
|
|
1272
|
+
route: { type: "string", description: "URL path for the observed view (e.g., '/dashboard')." },
|
|
1273
|
+
name: { type: "string", description: "Optional display name. If omitted, derived from route at commit time." },
|
|
1274
|
+
intent: { type: "string", description: "Optional one-liner describing the view's purpose." },
|
|
1275
|
+
observed: { type: "string", description: "Free-form note about what was observed on this view." },
|
|
1276
|
+
observed_components: { type: "array", items: { type: "string" } },
|
|
1277
|
+
memory_notes: { type: "array", items: { type: "string" } }
|
|
1278
|
+
},
|
|
1279
|
+
required: ["route"],
|
|
1280
|
+
additionalProperties: false
|
|
1281
|
+
}
|
|
1282
|
+
};
|
|
1283
|
+
var SIGHTMAP_PROPOSE_COMPONENT = {
|
|
1284
|
+
name: "sightmap_propose_component",
|
|
1285
|
+
description: "Stage a proposed component observation. Does NOT write disk.",
|
|
1286
|
+
inputSchema: {
|
|
1287
|
+
type: "object",
|
|
1288
|
+
properties: {
|
|
1289
|
+
name: { type: "string", description: "Component name (e.g., 'KpiCard')." },
|
|
1290
|
+
selector_candidate: { type: "string", description: "Best-guess CSS selector for the component." },
|
|
1291
|
+
observed_routes: { type: "array", items: { type: "string" } },
|
|
1292
|
+
notes: { type: "array", items: { type: "string" } }
|
|
1293
|
+
},
|
|
1294
|
+
required: ["name"],
|
|
1295
|
+
additionalProperties: false
|
|
1296
|
+
}
|
|
1297
|
+
};
|
|
1298
|
+
var SIGHTMAP_REVIEW_PROPOSALS = {
|
|
1299
|
+
name: "sightmap_review_proposals",
|
|
1300
|
+
description: "Return the current staged proposals (views + components).",
|
|
1301
|
+
inputSchema: {
|
|
1302
|
+
type: "object",
|
|
1303
|
+
properties: {},
|
|
1304
|
+
additionalProperties: false
|
|
1305
|
+
}
|
|
1306
|
+
};
|
|
1307
|
+
var SIGHTMAP_COMMIT_PROPOSALS = {
|
|
1308
|
+
name: "sightmap_commit_proposals",
|
|
1309
|
+
description: "Promote staged proposals to disk via add_view. Pass dry_run:true to preview without consuming the queue. Pass ids:[...] to commit a selection only.",
|
|
1310
|
+
inputSchema: {
|
|
1311
|
+
type: "object",
|
|
1312
|
+
properties: {
|
|
1313
|
+
ids: { type: "array", items: { type: "string" }, description: "Optional subset of proposal ids to commit. Omit to commit everything." },
|
|
1314
|
+
dry_run: { type: "boolean", description: "If true, preview without consuming the queue and without writing." }
|
|
1315
|
+
},
|
|
1316
|
+
additionalProperties: false
|
|
1317
|
+
}
|
|
1318
|
+
};
|
|
1319
|
+
var SIGHTMAP_DELETE_VIEW = {
|
|
1320
|
+
name: "sightmap_delete_view",
|
|
1321
|
+
description: "Delete a view by name. If the containing .yaml file has only this view, the file is removed; otherwise the view is spliced out and the file rewritten.",
|
|
1322
|
+
inputSchema: {
|
|
1323
|
+
type: "object",
|
|
1324
|
+
properties: {
|
|
1325
|
+
name: { type: "string" }
|
|
1326
|
+
},
|
|
1327
|
+
required: ["name"],
|
|
1328
|
+
additionalProperties: false
|
|
1329
|
+
}
|
|
1330
|
+
};
|
|
580
1331
|
var SIGHTMAP_CHECK = {
|
|
581
1332
|
name: "sightmap_check",
|
|
582
1333
|
description: "Validate the loaded sightmap. level='schema' returns only schema-level diagnostics (parse errors, schema failures, merge collisions). level='quality' (default) also runs lint rules: duplicate routes, route shadowing, selector syntax, unknown source attributions. Returns an array of diagnostics; an empty array means clean.",
|
|
@@ -683,8 +1434,15 @@ var SightmapMcpServer = class {
|
|
|
683
1434
|
SIGHTMAP_LIST_VIEWS,
|
|
684
1435
|
SIGHTMAP_GET_VIEW,
|
|
685
1436
|
SIGHTMAP_CHECK,
|
|
1437
|
+
SIGHTMAP_ADD_VIEW,
|
|
686
1438
|
SIGHTMAP_UPDATE_VIEW,
|
|
687
|
-
|
|
1439
|
+
SIGHTMAP_DELETE_VIEW,
|
|
1440
|
+
SIGHTMAP_INIT_PROJECT,
|
|
1441
|
+
SIGHTMAP_RUNTIME_SNAPSHOT,
|
|
1442
|
+
SIGHTMAP_PROPOSE_VIEW,
|
|
1443
|
+
SIGHTMAP_PROPOSE_COMPONENT,
|
|
1444
|
+
SIGHTMAP_REVIEW_PROPOSALS,
|
|
1445
|
+
SIGHTMAP_COMMIT_PROPOSALS
|
|
688
1446
|
];
|
|
689
1447
|
}
|
|
690
1448
|
/** Full async list including upstream-proxied tools, if configured. */
|
|
@@ -694,8 +1452,15 @@ var SightmapMcpServer = class {
|
|
|
694
1452
|
SIGHTMAP_LIST_VIEWS,
|
|
695
1453
|
SIGHTMAP_GET_VIEW,
|
|
696
1454
|
SIGHTMAP_CHECK,
|
|
1455
|
+
SIGHTMAP_ADD_VIEW,
|
|
697
1456
|
SIGHTMAP_UPDATE_VIEW,
|
|
698
|
-
|
|
1457
|
+
SIGHTMAP_DELETE_VIEW,
|
|
1458
|
+
SIGHTMAP_INIT_PROJECT,
|
|
1459
|
+
SIGHTMAP_RUNTIME_SNAPSHOT,
|
|
1460
|
+
SIGHTMAP_PROPOSE_VIEW,
|
|
1461
|
+
SIGHTMAP_PROPOSE_COMPONENT,
|
|
1462
|
+
SIGHTMAP_REVIEW_PROPOSALS,
|
|
1463
|
+
SIGHTMAP_COMMIT_PROPOSALS
|
|
699
1464
|
];
|
|
700
1465
|
if (this.upstream !== void 0) {
|
|
701
1466
|
own.push(SIGHTMAP_SNAPSHOT, SIGHTMAP_ACT, SIGHTMAP_NETWORK_REQUESTS);
|
|
@@ -733,10 +1498,24 @@ var SightmapMcpServer = class {
|
|
|
733
1498
|
return this.callSightmapGetView(args);
|
|
734
1499
|
case "sightmap_check":
|
|
735
1500
|
return this.callSightmapCheck(args);
|
|
1501
|
+
case "sightmap_add_view":
|
|
1502
|
+
return this.callSightmapAddView(args);
|
|
736
1503
|
case "sightmap_update_view":
|
|
737
1504
|
return this.callSightmapUpdateView(args);
|
|
1505
|
+
case "sightmap_delete_view":
|
|
1506
|
+
return this.callSightmapDeleteView(args);
|
|
738
1507
|
case "sightmap_init_project":
|
|
739
1508
|
return this.callSightmapInitProject(args);
|
|
1509
|
+
case "sightmap_runtime_snapshot":
|
|
1510
|
+
return this.callSightmapRuntimeSnapshot(args);
|
|
1511
|
+
case "sightmap_propose_view":
|
|
1512
|
+
return this.callSightmapProposeView(args);
|
|
1513
|
+
case "sightmap_propose_component":
|
|
1514
|
+
return this.callSightmapProposeComponent(args);
|
|
1515
|
+
case "sightmap_review_proposals":
|
|
1516
|
+
return this.callSightmapReviewProposals();
|
|
1517
|
+
case "sightmap_commit_proposals":
|
|
1518
|
+
return this.callSightmapCommitProposals(args);
|
|
740
1519
|
case "sightmap_snapshot":
|
|
741
1520
|
return this.callSightmapSnapshot();
|
|
742
1521
|
case "sightmap_act":
|
|
@@ -979,6 +1758,196 @@ var SightmapMcpServer = class {
|
|
|
979
1758
|
return errorResult(err instanceof Error ? err.message : String(err));
|
|
980
1759
|
}
|
|
981
1760
|
}
|
|
1761
|
+
async callSightmapRuntimeSnapshot(args) {
|
|
1762
|
+
const source = args["source"];
|
|
1763
|
+
if (source === null || typeof source !== "object" || Array.isArray(source)) {
|
|
1764
|
+
return errorResult(
|
|
1765
|
+
"sightmap_runtime_snapshot: required argument `source` (object) is missing."
|
|
1766
|
+
);
|
|
1767
|
+
}
|
|
1768
|
+
const s = source;
|
|
1769
|
+
const kind = s["kind"];
|
|
1770
|
+
if (kind !== "endpoint" && kind !== "browser" && kind !== "literal") {
|
|
1771
|
+
return errorResult(
|
|
1772
|
+
"sightmap_runtime_snapshot: `source.kind` must be 'endpoint', 'browser', or 'literal'."
|
|
1773
|
+
);
|
|
1774
|
+
}
|
|
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 = {
|
|
1790
|
+
kind: "browser",
|
|
1791
|
+
url: s["url"]
|
|
1792
|
+
};
|
|
1793
|
+
if (Array.isArray(s["routes"])) {
|
|
1794
|
+
browserSrc.routes = s["routes"].filter(
|
|
1795
|
+
(r) => typeof r === "string"
|
|
1796
|
+
);
|
|
1797
|
+
}
|
|
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
|
+
}
|
|
1810
|
+
try {
|
|
1811
|
+
const deps = {
|
|
1812
|
+
getInjectScript
|
|
1813
|
+
};
|
|
1814
|
+
if (this.upstream !== void 0) deps.upstream = this.upstream;
|
|
1815
|
+
const result = await handleRuntimeSnapshot(input, deps);
|
|
1816
|
+
return {
|
|
1817
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1818
|
+
};
|
|
1819
|
+
} catch (err) {
|
|
1820
|
+
return errorResult(err instanceof Error ? err.message : String(err));
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
async callSightmapProposeView(args) {
|
|
1824
|
+
if (typeof args["route"] !== "string" || args["route"].length === 0) {
|
|
1825
|
+
return errorResult(
|
|
1826
|
+
"sightmap_propose_view: required argument `route` (string) is missing or empty."
|
|
1827
|
+
);
|
|
1828
|
+
}
|
|
1829
|
+
const input = { route: args["route"] };
|
|
1830
|
+
if (typeof args["name"] === "string") input.name = args["name"];
|
|
1831
|
+
if (typeof args["intent"] === "string") input.intent = args["intent"];
|
|
1832
|
+
if (typeof args["observed"] === "string") input.observed = args["observed"];
|
|
1833
|
+
if (Array.isArray(args["observed_components"])) {
|
|
1834
|
+
input.observed_components = args["observed_components"].filter(
|
|
1835
|
+
(s) => typeof s === "string"
|
|
1836
|
+
);
|
|
1837
|
+
}
|
|
1838
|
+
if (Array.isArray(args["memory_notes"])) {
|
|
1839
|
+
input.memory_notes = args["memory_notes"].filter(
|
|
1840
|
+
(s) => typeof s === "string"
|
|
1841
|
+
);
|
|
1842
|
+
}
|
|
1843
|
+
try {
|
|
1844
|
+
const result = await handleProposeView(input);
|
|
1845
|
+
return {
|
|
1846
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1847
|
+
};
|
|
1848
|
+
} catch (err) {
|
|
1849
|
+
return errorResult(err instanceof Error ? err.message : String(err));
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
async callSightmapProposeComponent(args) {
|
|
1853
|
+
if (typeof args["name"] !== "string" || args["name"].length === 0) {
|
|
1854
|
+
return errorResult(
|
|
1855
|
+
"sightmap_propose_component: required argument `name` (string) is missing or empty."
|
|
1856
|
+
);
|
|
1857
|
+
}
|
|
1858
|
+
const input = { name: args["name"] };
|
|
1859
|
+
if (typeof args["selector_candidate"] === "string") {
|
|
1860
|
+
input.selector_candidate = args["selector_candidate"];
|
|
1861
|
+
}
|
|
1862
|
+
if (Array.isArray(args["observed_routes"])) {
|
|
1863
|
+
input.observed_routes = args["observed_routes"].filter(
|
|
1864
|
+
(s) => typeof s === "string"
|
|
1865
|
+
);
|
|
1866
|
+
}
|
|
1867
|
+
if (Array.isArray(args["notes"])) {
|
|
1868
|
+
input.notes = args["notes"].filter(
|
|
1869
|
+
(s) => typeof s === "string"
|
|
1870
|
+
);
|
|
1871
|
+
}
|
|
1872
|
+
try {
|
|
1873
|
+
const result = await handleProposeComponent(input);
|
|
1874
|
+
return {
|
|
1875
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1876
|
+
};
|
|
1877
|
+
} catch (err) {
|
|
1878
|
+
return errorResult(err instanceof Error ? err.message : String(err));
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
async callSightmapReviewProposals() {
|
|
1882
|
+
try {
|
|
1883
|
+
const result = await handleReviewProposals();
|
|
1884
|
+
return {
|
|
1885
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1886
|
+
};
|
|
1887
|
+
} catch (err) {
|
|
1888
|
+
return errorResult(err instanceof Error ? err.message : String(err));
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
async callSightmapCommitProposals(args) {
|
|
1892
|
+
if (this.curateRoot === void 0) {
|
|
1893
|
+
return errorResult(
|
|
1894
|
+
"sightmap_commit_proposals: no writable sightmap dir configured (pass --curate-root or --sightmap-dir)."
|
|
1895
|
+
);
|
|
1896
|
+
}
|
|
1897
|
+
const input = { sightmapDir: this.curateRoot };
|
|
1898
|
+
if (Array.isArray(args["ids"])) {
|
|
1899
|
+
input.ids = args["ids"].filter(
|
|
1900
|
+
(s) => typeof s === "string"
|
|
1901
|
+
);
|
|
1902
|
+
}
|
|
1903
|
+
if (args["dry_run"] === true) input.dry_run = true;
|
|
1904
|
+
try {
|
|
1905
|
+
const result = await handleCommitProposals(input);
|
|
1906
|
+
return {
|
|
1907
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1908
|
+
};
|
|
1909
|
+
} catch (err) {
|
|
1910
|
+
return errorResult(err instanceof Error ? err.message : String(err));
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
async callSightmapAddView(args) {
|
|
1914
|
+
if (this.curateRoot === void 0) {
|
|
1915
|
+
return errorResult(
|
|
1916
|
+
"sightmap_add_view: no writable sightmap dir configured (pass --curate-root or --sightmap-dir)."
|
|
1917
|
+
);
|
|
1918
|
+
}
|
|
1919
|
+
if (args["view"] === null || typeof args["view"] !== "object" || Array.isArray(args["view"])) {
|
|
1920
|
+
return errorResult(
|
|
1921
|
+
"sightmap_add_view: required argument `view` (object) is missing."
|
|
1922
|
+
);
|
|
1923
|
+
}
|
|
1924
|
+
const view = args["view"];
|
|
1925
|
+
if (typeof view["name"] !== "string" || view["name"].length === 0) {
|
|
1926
|
+
return errorResult(
|
|
1927
|
+
"sightmap_add_view: `view.name` (string) is required."
|
|
1928
|
+
);
|
|
1929
|
+
}
|
|
1930
|
+
if (typeof view["route"] !== "string" || view["route"].length === 0) {
|
|
1931
|
+
return errorResult(
|
|
1932
|
+
"sightmap_add_view: `view.route` (string) is required."
|
|
1933
|
+
);
|
|
1934
|
+
}
|
|
1935
|
+
const input = {
|
|
1936
|
+
sightmapDir: this.curateRoot,
|
|
1937
|
+
view
|
|
1938
|
+
};
|
|
1939
|
+
if (typeof args["file"] === "string" && args["file"].length > 0) {
|
|
1940
|
+
input.file = args["file"];
|
|
1941
|
+
}
|
|
1942
|
+
try {
|
|
1943
|
+
const result = await handleAddView(input);
|
|
1944
|
+
return {
|
|
1945
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1946
|
+
};
|
|
1947
|
+
} catch (err) {
|
|
1948
|
+
return errorResult(err instanceof Error ? err.message : String(err));
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
982
1951
|
async callSightmapUpdateView(args) {
|
|
983
1952
|
if (this.curateRoot === void 0) {
|
|
984
1953
|
return errorResult(
|
|
@@ -1008,6 +1977,29 @@ var SightmapMcpServer = class {
|
|
|
1008
1977
|
return errorResult(err instanceof Error ? err.message : String(err));
|
|
1009
1978
|
}
|
|
1010
1979
|
}
|
|
1980
|
+
async callSightmapDeleteView(args) {
|
|
1981
|
+
if (this.curateRoot === void 0) {
|
|
1982
|
+
return errorResult(
|
|
1983
|
+
"sightmap_delete_view: no writable sightmap dir configured (pass --curate-root or --sightmap-dir)."
|
|
1984
|
+
);
|
|
1985
|
+
}
|
|
1986
|
+
if (typeof args["name"] !== "string" || args["name"].length === 0) {
|
|
1987
|
+
return errorResult(
|
|
1988
|
+
"sightmap_delete_view: required argument `name` (string) is missing or empty."
|
|
1989
|
+
);
|
|
1990
|
+
}
|
|
1991
|
+
try {
|
|
1992
|
+
const result = await handleDeleteView({
|
|
1993
|
+
sightmapDir: this.curateRoot,
|
|
1994
|
+
name: args["name"]
|
|
1995
|
+
});
|
|
1996
|
+
return {
|
|
1997
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1998
|
+
};
|
|
1999
|
+
} catch (err) {
|
|
2000
|
+
return errorResult(err instanceof Error ? err.message : String(err));
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
1011
2003
|
async callSightmapCheck(args) {
|
|
1012
2004
|
const input = {};
|
|
1013
2005
|
if (args["level"] === "schema" || args["level"] === "quality") {
|
|
@@ -1166,13 +2158,13 @@ var KNOWN_INCOMPATIBLE_RANGES = [
|
|
|
1166
2158
|
{ pluginLt: "0.2.0", mcpGte: "0.5.0" }
|
|
1167
2159
|
];
|
|
1168
2160
|
var KNOWN_GOOD_MCP_MAJOR_MINOR = ["0.3", "0.4"];
|
|
1169
|
-
function
|
|
2161
|
+
function parse5(v) {
|
|
1170
2162
|
const [a, b, c] = v.split(".").map((s) => parseInt(s, 10));
|
|
1171
2163
|
return [a ?? 0, b ?? 0, c ?? 0];
|
|
1172
2164
|
}
|
|
1173
2165
|
function lt(a, b) {
|
|
1174
|
-
const [a0, a1, a2] =
|
|
1175
|
-
const [b0, b1, b2] =
|
|
2166
|
+
const [a0, a1, a2] = parse5(a);
|
|
2167
|
+
const [b0, b1, b2] = parse5(b);
|
|
1176
2168
|
if (a0 !== b0) return a0 < b0;
|
|
1177
2169
|
if (a1 !== b1) return a1 < b1;
|
|
1178
2170
|
return a2 < b2;
|
|
@@ -1192,8 +2184,8 @@ function evaluateCompat(input) {
|
|
|
1192
2184
|
}
|
|
1193
2185
|
|
|
1194
2186
|
// src/cli.ts
|
|
1195
|
-
var __dirname =
|
|
1196
|
-
var pkgJson = JSON.parse(readFileSync(
|
|
2187
|
+
var __dirname = dirname2(fileURLToPath(import.meta.url));
|
|
2188
|
+
var pkgJson = JSON.parse(readFileSync(resolve4(__dirname, "../package.json"), "utf8"));
|
|
1197
2189
|
function parseArgv(argv) {
|
|
1198
2190
|
const collectedDirs = [];
|
|
1199
2191
|
let explicitCurateRoot;
|
|
@@ -1236,7 +2228,7 @@ function parseArgv(argv) {
|
|
|
1236
2228
|
i++;
|
|
1237
2229
|
}
|
|
1238
2230
|
out.sightmapDirs = collectedDirs.length > 0 ? collectedDirs : [".sightmap"];
|
|
1239
|
-
out.curateRoot = explicitCurateRoot !== void 0 ?
|
|
2231
|
+
out.curateRoot = explicitCurateRoot !== void 0 ? resolve4(explicitCurateRoot) : resolve4(out.sightmapDirs[0]);
|
|
1240
2232
|
return out;
|
|
1241
2233
|
}
|
|
1242
2234
|
function printHelp() {
|
|
@@ -1274,7 +2266,7 @@ Pin matching versions in your .mcp.json.`
|
|
|
1274
2266
|
console.error(`[sightmap-mcp] Untested plugin/MCP pair: plugin v${process.env.SIGHTMAP_PLUGIN_VERSION} \u2194 MCP v${pkgJson.version}. Proceeding.`);
|
|
1275
2267
|
}
|
|
1276
2268
|
const { mergeSightmaps: mergeSightmaps2 } = await Promise.resolve().then(() => (init_loadMerged(), loadMerged_exports));
|
|
1277
|
-
const sightmap = await mergeSightmaps2(args.sightmapDirs.map((d) =>
|
|
2269
|
+
const sightmap = await mergeSightmaps2(args.sightmapDirs.map((d) => resolve4(d)));
|
|
1278
2270
|
if (sightmap.diagnostics.some((d) => d.severity === "error")) {
|
|
1279
2271
|
process.stderr.write(
|
|
1280
2272
|
`sightmap-mcp: ${sightmap.diagnostics.length} diagnostic(s) loading sightmap; continuing
|
|
@@ -1296,9 +2288,18 @@ Pin matching versions in your .mcp.json.`
|
|
|
1296
2288
|
});
|
|
1297
2289
|
return;
|
|
1298
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
|
+
}
|
|
1299
2299
|
const upstreamTransport = new StdioClientTransport({
|
|
1300
2300
|
command: args.upstreamCommand,
|
|
1301
|
-
args: args.upstreamArgs
|
|
2301
|
+
args: args.upstreamArgs,
|
|
2302
|
+
env: childEnv
|
|
1302
2303
|
});
|
|
1303
2304
|
const upstreamClient = new Client(
|
|
1304
2305
|
{ name: "sightmap-mcp-bridge", version: "0.0.0" },
|
|
@@ -1317,11 +2318,34 @@ Pin matching versions in your .mcp.json.`
|
|
|
1317
2318
|
await upstreamClient.close();
|
|
1318
2319
|
} catch {
|
|
1319
2320
|
}
|
|
2321
|
+
if (initScriptTempDir !== void 0) {
|
|
2322
|
+
try {
|
|
2323
|
+
rmSync(initScriptTempDir.dir, { recursive: true, force: true });
|
|
2324
|
+
} catch {
|
|
2325
|
+
}
|
|
2326
|
+
}
|
|
1320
2327
|
process.exit(0);
|
|
1321
2328
|
};
|
|
1322
2329
|
process.on("SIGINT", cleanup);
|
|
1323
2330
|
process.on("SIGTERM", cleanup);
|
|
1324
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
|
+
}
|
|
1325
2349
|
function isInvokedAsMain() {
|
|
1326
2350
|
if (process.argv[1] === void 0) return false;
|
|
1327
2351
|
try {
|
|
@@ -1339,6 +2363,7 @@ if (isInvokedAsMain()) {
|
|
|
1339
2363
|
});
|
|
1340
2364
|
}
|
|
1341
2365
|
export {
|
|
1342
|
-
parseArgv
|
|
2366
|
+
parseArgv,
|
|
2367
|
+
writeInitScriptToTempDir
|
|
1343
2368
|
};
|
|
1344
2369
|
//# sourceMappingURL=cli.js.map
|