@xiaou66/vite-plugin-vue-mcp-next 1.2.0 → 1.3.1

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.
@@ -28,6 +28,116 @@ __export(client_exports, {
28
28
  module.exports = __toCommonJS(client_exports);
29
29
  var import_vite_hot_client = require("vite-hot-client");
30
30
 
31
+ // src/shared/limits.ts
32
+ var DEFAULT_DOM_MAX_DEPTH = 8;
33
+ var DEFAULT_DOM_MAX_NODES = 2e3;
34
+ var DEFAULT_DOM_MAX_TEXT_LENGTH = 300;
35
+ var DEFAULT_CONSOLE_MAX_RECORDS = 1e3;
36
+ var DEFAULT_NETWORK_MAX_RECORDS = 500;
37
+ var DEFAULT_NETWORK_MAX_BODY_SIZE = 1e5;
38
+ var DEFAULT_MASK_HEADERS = [
39
+ "authorization",
40
+ "cookie",
41
+ "set-cookie"
42
+ ];
43
+
44
+ // src/constants.ts
45
+ var DEFAULT_MCP_PATH = "/__mcp";
46
+ var DEFAULT_SCREENSHOT_MAX_BYTES = 5 * 1024 * 1024;
47
+ var DEFAULT_SCREENSHOT_SAVE_DIR = ".vite-mcp/screenshot";
48
+ var DEFAULT_PERFORMANCE_SAVE_DIR = ".vite-mcp/performance";
49
+ var DEFAULT_PERFORMANCE_MAX_DURATION_MS = 3e4;
50
+ var DEFAULT_PERFORMANCE_SAMPLE_INTERVAL_MS = 250;
51
+ var DEFAULT_PERFORMANCE_LONG_TASK_THRESHOLD_MS = 50;
52
+ var VIRTUAL_RUNTIME_ID = "virtual:vite-plugin-vue-mcp-next/runtime";
53
+ var RESOLVED_VIRTUAL_RUNTIME_ID = `\0${VIRTUAL_RUNTIME_ID}`;
54
+ var VIRTUAL_SCREENSHOT_CONFIG_ID = "virtual:vite-plugin-vue-mcp-next/screenshot-config";
55
+ var RESOLVED_VIRTUAL_SCREENSHOT_CONFIG_ID = `\0${VIRTUAL_SCREENSHOT_CONFIG_ID}`;
56
+ var VIRTUAL_SNAPDOM_LOADER_ID = "virtual:vite-plugin-vue-mcp-next/snapdom-loader";
57
+ var RESOLVED_VIRTUAL_SNAPDOM_LOADER_ID = `\0${VIRTUAL_SNAPDOM_LOADER_ID}`;
58
+ var DEFAULT_MCP_CLIENT_SERVER_NAME = "vite-mcp-next";
59
+ var RUNTIME_PAGE_CONNECTED_EVENT = "vite-plugin-vue-mcp-next:page-connected";
60
+ var RUNTIME_PAGE_DISCONNECTED_EVENT = "vite-plugin-vue-mcp-next:page-disconnected";
61
+ var RUNTIME_PAGE_HEARTBEAT_EVENT = "vite-plugin-vue-mcp-next:heartbeat";
62
+ var DEFAULT_RUNTIME_PAGE_HEARTBEAT_INTERVAL_MS = 15e3;
63
+ var DEFAULT_ELEMENT_PICKER_TOAST_DURATION_MS = 2200;
64
+ var DEFAULT_OPTIONS = {
65
+ mcpPath: DEFAULT_MCP_PATH,
66
+ host: "localhost",
67
+ printUrl: true,
68
+ updateCursorMcpJson: {
69
+ enabled: true,
70
+ serverName: DEFAULT_MCP_CLIENT_SERVER_NAME
71
+ },
72
+ mcpClients: {
73
+ cursor: true,
74
+ codex: true,
75
+ claudeCode: true,
76
+ trae: true,
77
+ serverName: DEFAULT_MCP_CLIENT_SERVER_NAME
78
+ },
79
+ skill: {
80
+ autoConfig: true
81
+ },
82
+ elementPicker: {
83
+ enabled: true,
84
+ shortcut: {
85
+ altKey: true,
86
+ shiftKey: true,
87
+ metaKey: false,
88
+ ctrlKey: false
89
+ },
90
+ toastDurationMs: DEFAULT_ELEMENT_PICKER_TOAST_DURATION_MS
91
+ },
92
+ runtime: {
93
+ mode: "auto",
94
+ evaluate: {
95
+ enabled: false,
96
+ timeoutMs: 3e3
97
+ }
98
+ },
99
+ cdp: {},
100
+ network: {
101
+ mode: "auto",
102
+ maxRecords: DEFAULT_NETWORK_MAX_RECORDS,
103
+ captureRequestBody: true,
104
+ captureResponseBody: true,
105
+ maxBodySize: DEFAULT_NETWORK_MAX_BODY_SIZE,
106
+ maskHeaders: [...DEFAULT_MASK_HEADERS]
107
+ },
108
+ dom: {
109
+ maxDepth: DEFAULT_DOM_MAX_DEPTH,
110
+ maxNodes: DEFAULT_DOM_MAX_NODES,
111
+ maxTextLength: DEFAULT_DOM_MAX_TEXT_LENGTH
112
+ },
113
+ console: {
114
+ maxRecords: DEFAULT_CONSOLE_MAX_RECORDS
115
+ },
116
+ screenshot: {
117
+ type: "path",
118
+ saveDir: DEFAULT_SCREENSHOT_SAVE_DIR,
119
+ prefer: "auto",
120
+ maxBytes: DEFAULT_SCREENSHOT_MAX_BYTES,
121
+ snapdom: {
122
+ options: {},
123
+ plugins: []
124
+ }
125
+ },
126
+ performance: {
127
+ mode: "auto",
128
+ maxDurationMs: DEFAULT_PERFORMANCE_MAX_DURATION_MS,
129
+ sampleIntervalMs: DEFAULT_PERFORMANCE_SAMPLE_INTERVAL_MS,
130
+ longTaskThresholdMs: DEFAULT_PERFORMANCE_LONG_TASK_THRESHOLD_MS,
131
+ saveDir: DEFAULT_PERFORMANCE_SAVE_DIR,
132
+ memory: {
133
+ enabled: true
134
+ },
135
+ stacks: {
136
+ enabled: true
137
+ }
138
+ }
139
+ };
140
+
31
141
  // src/runtime/consoleHook.ts
32
142
  var import_nanoid = require("nanoid");
33
143
 
@@ -63,13 +173,14 @@ function installConsoleHook(options) {
63
173
  debug: console.debug
64
174
  };
65
175
  const emit = (level, args) => {
176
+ const serializedArgs = serializeConsoleArgs(args);
66
177
  options.send({
67
178
  id: (0, import_nanoid.nanoid)(),
68
179
  pageId: options.pageId,
69
180
  source: "hook",
70
181
  level,
71
- message: args.map((arg) => safeStringify(arg)).join(" "),
72
- args,
182
+ message: serializedArgs.join(" "),
183
+ args: serializedArgs,
73
184
  timestamp: Date.now()
74
185
  });
75
186
  };
@@ -96,9 +207,231 @@ function installConsoleHook(options) {
96
207
  window.removeEventListener("error", onError);
97
208
  };
98
209
  }
210
+ function serializeConsoleArgs(args) {
211
+ return args.map((arg) => safeStringify(arg));
212
+ }
99
213
 
100
- // src/runtime/networkHook.ts
214
+ // src/runtime/elementRegistry.ts
101
215
  var import_nanoid2 = require("nanoid");
216
+ var RUNTIME_ELEMENT_ID_PREFIX = "runtime:vmcp_";
217
+ var RUNTIME_ELEMENT_ID_SIZE = 8;
218
+ var runtimeElementRegistry = createRuntimeElementRegistry();
219
+ function createRuntimeElementId() {
220
+ return `${RUNTIME_ELEMENT_ID_PREFIX}${(0, import_nanoid2.nanoid)(RUNTIME_ELEMENT_ID_SIZE)}`;
221
+ }
222
+ function createRuntimeElementRegistry() {
223
+ const records = /* @__PURE__ */ new Map();
224
+ return {
225
+ register(element) {
226
+ const elementId = createRuntimeElementId();
227
+ records.set(elementId, {
228
+ elementId,
229
+ element,
230
+ createdAt: Date.now()
231
+ });
232
+ return elementId;
233
+ },
234
+ get(elementId) {
235
+ return records.get(elementId);
236
+ },
237
+ clear() {
238
+ records.clear();
239
+ }
240
+ };
241
+ }
242
+
243
+ // src/runtime/elementPicker.ts
244
+ var MCP_ID_ATTR = "data-v-mcp-id";
245
+ var INTERNAL_ATTR = "data-v-mcp-internal";
246
+ var SUCCESS_MESSAGE = "\u5143\u7D20\u4F4D\u7F6E\u5DF2\u590D\u5236\uFF0C\u8BF7\u53D1\u9001\u7ED9 AI";
247
+ var COPY_FAILED_PREFIX = "\u590D\u5236\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u590D\u5236\u5143\u7D20 ID";
248
+ var OVERLAY_Z_INDEX = "2147483647";
249
+ var TOAST_Z_INDEX = "2147483647";
250
+ function installElementPicker(options) {
251
+ if (!options.enabled) {
252
+ return;
253
+ }
254
+ const registry = runtimeElementRegistry;
255
+ const overlay = createOverlay();
256
+ let active = false;
257
+ let currentElement;
258
+ window.addEventListener("keydown", (event) => {
259
+ active = matchesShortcut(event, options.shortcut);
260
+ });
261
+ window.addEventListener("keyup", () => {
262
+ active = false;
263
+ currentElement = void 0;
264
+ updateOverlay(overlay);
265
+ });
266
+ window.addEventListener("mousemove", (event) => {
267
+ if (!active) {
268
+ return;
269
+ }
270
+ currentElement = document.elementFromPoint(event.clientX, event.clientY) ?? void 0;
271
+ updateOverlay(overlay, currentElement);
272
+ });
273
+ window.addEventListener(
274
+ "click",
275
+ (event) => {
276
+ if (!active || !isElementLike(event.target)) {
277
+ return;
278
+ }
279
+ event.preventDefault();
280
+ event.stopPropagation();
281
+ void copyAndNotify(event.target, registry, options.toastDurationMs);
282
+ },
283
+ true
284
+ );
285
+ }
286
+ function matchesShortcut(event, shortcut) {
287
+ return event.altKey === shortcut.altKey && event.shiftKey === shortcut.shiftKey && event.metaKey === shortcut.metaKey && event.ctrlKey === shortcut.ctrlKey;
288
+ }
289
+ function resolveElementId(element, registry) {
290
+ return element.getAttribute(MCP_ID_ATTR) ?? registry.register(element);
291
+ }
292
+ async function copyAndNotify(element, registry, toastDurationMs) {
293
+ const elementId = resolveElementId(element, registry);
294
+ const copied = await copyElementId(elementId);
295
+ showToast(
296
+ copied ? SUCCESS_MESSAGE : `${COPY_FAILED_PREFIX}: ${elementId}`,
297
+ toastDurationMs
298
+ );
299
+ }
300
+ async function copyElementId(elementId) {
301
+ try {
302
+ await navigator.clipboard.writeText(elementId);
303
+ return true;
304
+ } catch {
305
+ return false;
306
+ }
307
+ }
308
+ function createOverlay() {
309
+ const overlay = document.createElement("div");
310
+ markInternalElement(overlay);
311
+ Object.assign(overlay.style, {
312
+ position: "fixed",
313
+ pointerEvents: "none",
314
+ border: "2px solid #1d4ed8",
315
+ background: "rgba(29, 78, 216, 0.08)",
316
+ zIndex: OVERLAY_Z_INDEX,
317
+ display: "none"
318
+ });
319
+ document.body.appendChild(overlay);
320
+ return overlay;
321
+ }
322
+ function updateOverlay(overlay, element) {
323
+ if (!element || getElementAttr(element, INTERNAL_ATTR) === "true") {
324
+ overlay.style.display = "none";
325
+ return;
326
+ }
327
+ const rect = element.getBoundingClientRect();
328
+ Object.assign(overlay.style, {
329
+ display: "block",
330
+ left: `${String(rect.x)}px`,
331
+ top: `${String(rect.y)}px`,
332
+ width: `${String(rect.width)}px`,
333
+ height: `${String(rect.height)}px`
334
+ });
335
+ }
336
+ function showToast(message, durationMs) {
337
+ const toast = document.createElement("div");
338
+ markInternalElement(toast);
339
+ toast.textContent = message;
340
+ Object.assign(toast.style, {
341
+ position: "fixed",
342
+ left: "50%",
343
+ bottom: "32px",
344
+ transform: "translateX(-50%)",
345
+ zIndex: TOAST_Z_INDEX,
346
+ padding: "8px 12px",
347
+ borderRadius: "6px",
348
+ background: "rgba(17, 24, 39, 0.92)",
349
+ color: "#fff",
350
+ fontSize: "13px",
351
+ pointerEvents: "none"
352
+ });
353
+ document.body.appendChild(toast);
354
+ globalThis.setTimeout(() => {
355
+ toast.remove();
356
+ }, durationMs);
357
+ }
358
+ function isElementLike(value) {
359
+ return Boolean(
360
+ value && typeof value === "object" && "getAttribute" in value && "getBoundingClientRect" in value
361
+ );
362
+ }
363
+ function markInternalElement(element) {
364
+ if (typeof element.setAttribute === "function") {
365
+ element.setAttribute(INTERNAL_ATTR, "true");
366
+ }
367
+ element.dataset.vMcpInternal = "true";
368
+ }
369
+ function getElementAttr(element, name) {
370
+ if (typeof element.getAttribute === "function") {
371
+ return element.getAttribute(name);
372
+ }
373
+ return null;
374
+ }
375
+
376
+ // src/shared/elementId.ts
377
+ var PROJECT_SOURCE_ID_PATTERN = /^(.+\.(?:vue|tsx|jsx|ts|js)):(\d+):(\d+)$/;
378
+ var RUNTIME_ID_PATTERN = /^runtime:([A-Za-z0-9_-]+)$/;
379
+ var PACKAGE_ID_PREFIX = "pkg:";
380
+ function parseElementId(elementId) {
381
+ const sourceMatch = PROJECT_SOURCE_ID_PATTERN.exec(elementId);
382
+ if (sourceMatch) {
383
+ return {
384
+ kind: "project-source",
385
+ elementId,
386
+ file: sourceMatch[1],
387
+ line: Number(sourceMatch[2]),
388
+ column: Number(sourceMatch[3])
389
+ };
390
+ }
391
+ if (elementId.startsWith(PACKAGE_ID_PREFIX)) {
392
+ return parsePackageElementId(elementId);
393
+ }
394
+ const runtimeMatch = RUNTIME_ID_PATTERN.exec(elementId);
395
+ if (runtimeMatch) {
396
+ return {
397
+ kind: "runtime",
398
+ elementId,
399
+ runtimeId: runtimeMatch[1]
400
+ };
401
+ }
402
+ return {
403
+ kind: "invalid",
404
+ elementId,
405
+ reason: "unsupported elementId format"
406
+ };
407
+ }
408
+ function parsePackageElementId(elementId) {
409
+ const value = elementId.slice(PACKAGE_ID_PREFIX.length);
410
+ const parts = value.split("/").filter(Boolean);
411
+ if (parts.length < 2) {
412
+ return {
413
+ kind: "invalid",
414
+ elementId,
415
+ reason: "package elementId must include packageName and entryFile"
416
+ };
417
+ }
418
+ const scoped = parts[0]?.startsWith("@");
419
+ const packageName = scoped ? parts.slice(0, 2).join("/") : parts[0];
420
+ const entryFile = parts.slice(scoped ? 2 : 1).join("/");
421
+ if (!entryFile) {
422
+ return {
423
+ kind: "invalid",
424
+ elementId,
425
+ reason: "package elementId must include entryFile"
426
+ };
427
+ }
428
+ return {
429
+ kind: "package",
430
+ elementId,
431
+ packageName,
432
+ entryFile
433
+ };
434
+ }
102
435
 
103
436
  // src/shared/sanitize.ts
104
437
  function truncateText(text, maxLength) {
@@ -123,6 +456,270 @@ function maskHeaders(headers = {}, maskNames = []) {
123
456
  );
124
457
  }
125
458
 
459
+ // src/runtime/domSnapshot.ts
460
+ function createDomSnapshot(root, options) {
461
+ let count = 0;
462
+ function visit(node, depth) {
463
+ if (count >= options.maxNodes || depth > options.maxDepth) {
464
+ return null;
465
+ }
466
+ if (node.nodeType === Node.TEXT_NODE) {
467
+ return createTextSnapshot(node, options, () => {
468
+ count += 1;
469
+ });
470
+ }
471
+ if (!(node instanceof Element)) {
472
+ return null;
473
+ }
474
+ const tag = node.tagName.toLowerCase();
475
+ if (["script", "style", "noscript"].includes(tag) || isInternalMcpElement(node)) {
476
+ return null;
477
+ }
478
+ count += 1;
479
+ return createElementSnapshot(node, tag, (child) => visit(child, depth + 1));
480
+ }
481
+ return visit(root, 0) ?? { tag: root.tagName.toLowerCase() };
482
+ }
483
+ function queryDomElements(selector, limit) {
484
+ return Array.from(document.querySelectorAll(selector)).filter((element) => !isInternalMcpElement(element)).slice(0, limit).map((element) => createDomElementSummary(element));
485
+ }
486
+ function createDomElementSummary(element) {
487
+ return {
488
+ tag: element.tagName.toLowerCase(),
489
+ text: element.textContent.trim(),
490
+ attrs: collectAttrs(element),
491
+ rect: serializeRect(element.getBoundingClientRect())
492
+ };
493
+ }
494
+ function createTextSnapshot(node, options, markVisited) {
495
+ const text = node.textContent?.trim();
496
+ if (!text) {
497
+ return null;
498
+ }
499
+ markVisited();
500
+ return { tag: "#text", text: truncateText(text, options.maxTextLength).text };
501
+ }
502
+ function createElementSnapshot(node, tag, visitChild) {
503
+ const attrs = collectAttrs(node);
504
+ const children = Array.from(node.childNodes).map((child) => visitChild(child)).filter((child) => Boolean(child));
505
+ return {
506
+ tag,
507
+ ...node.textContent.trim() ? { text: node.textContent.trim() } : {},
508
+ ...Object.keys(attrs).length ? { attrs } : {},
509
+ ...children.length ? { children } : {}
510
+ };
511
+ }
512
+ function collectAttrs(element) {
513
+ const attrs = {};
514
+ for (const attr of Array.from(element.attributes)) {
515
+ attrs[attr.name] = attr.value;
516
+ }
517
+ if (typeof HTMLInputElement !== "undefined" && element instanceof HTMLInputElement && element.type === "password") {
518
+ attrs.value = "[masked]";
519
+ }
520
+ return attrs;
521
+ }
522
+ function serializeRect(rect) {
523
+ return {
524
+ x: rect.x,
525
+ y: rect.y,
526
+ width: rect.width,
527
+ height: rect.height,
528
+ top: rect.top,
529
+ right: rect.right,
530
+ bottom: rect.bottom,
531
+ left: rect.left
532
+ };
533
+ }
534
+ function isInternalMcpElement(element) {
535
+ return element.getAttribute("data-v-mcp-internal") === "true";
536
+ }
537
+
538
+ // src/runtime/vueComponentLocator.ts
539
+ function locateVueComponentForElement(element, root) {
540
+ const component = findNearestComponent(element);
541
+ if (!component) {
542
+ return void 0;
543
+ }
544
+ const file = getComponentFile(component);
545
+ const name = getComponentName(component);
546
+ if (!file) {
547
+ return { name };
548
+ }
549
+ const packageLocation = parseNodeModulesFile(file);
550
+ if (packageLocation) {
551
+ return {
552
+ name,
553
+ source: void 0,
554
+ packageLocation
555
+ };
556
+ }
557
+ return {
558
+ name,
559
+ source: {
560
+ file: createProjectRelativePath(root, file)
561
+ },
562
+ packageLocation: void 0
563
+ };
564
+ }
565
+ function findNearestComponent(element) {
566
+ let current = element;
567
+ while (current) {
568
+ const component = current.__vueParentComponent;
569
+ if (isVueRuntimeComponent(component)) {
570
+ return component;
571
+ }
572
+ current = current.parentElement;
573
+ }
574
+ return void 0;
575
+ }
576
+ function getComponentName(component) {
577
+ return component.type?.name ?? component.type?.__name ?? component.type?.displayName;
578
+ }
579
+ function getComponentFile(component) {
580
+ return component.type?.__file;
581
+ }
582
+ function parseNodeModulesFile(file) {
583
+ const normalized = normalizePath(file);
584
+ const marker = "/node_modules/";
585
+ const index = normalized.lastIndexOf(marker);
586
+ if (index < 0) {
587
+ return void 0;
588
+ }
589
+ const parts = normalized.slice(index + marker.length).split("/").filter(Boolean);
590
+ const scoped = parts[0]?.startsWith("@");
591
+ const packageName = scoped ? parts.slice(0, 2).join("/") : parts[0];
592
+ const entryFile = parts.slice(scoped ? 2 : 1).join("/");
593
+ if (!packageName || !entryFile) {
594
+ return void 0;
595
+ }
596
+ return {
597
+ packageName,
598
+ entryFile
599
+ };
600
+ }
601
+ function normalizePath(path) {
602
+ return path.replace(/\\/g, "/");
603
+ }
604
+ function createProjectRelativePath(root, file) {
605
+ const normalizedRoot = normalizePath(root).replace(/\/$/, "");
606
+ const normalizedFile = normalizePath(file);
607
+ const prefix = `${normalizedRoot}/`;
608
+ if (normalizedFile.startsWith(prefix)) {
609
+ return normalizedFile.slice(prefix.length);
610
+ }
611
+ return normalizedFile.replace(/^\//, "");
612
+ }
613
+ function isVueRuntimeComponent(value) {
614
+ return Boolean(value && typeof value === "object" && "type" in value);
615
+ }
616
+
617
+ // src/runtime/elementContext.ts
618
+ var activeResolver;
619
+ function createElementContextResolver(options) {
620
+ return {
621
+ getElementContext(elementId) {
622
+ const parsed = parseElementId(elementId);
623
+ if (parsed.kind === "project-source") {
624
+ const element = options.querySelector(
625
+ `[data-v-mcp-id="${escapeSelector(elementId)}"]`
626
+ );
627
+ return createProjectSourceContext(parsed, element, options.root);
628
+ }
629
+ if (parsed.kind === "package") {
630
+ return createPackageContext(parsed);
631
+ }
632
+ if (parsed.kind === "runtime") {
633
+ const record = options.registry.get(elementId);
634
+ if (!record) {
635
+ return createMissingRuntimeElementError(elementId);
636
+ }
637
+ return createRuntimeContext(elementId, record.element, options.root);
638
+ }
639
+ return {
640
+ ok: false,
641
+ error: parsed.reason,
642
+ elementId,
643
+ limitations: ["please provide a copied elementId from the element picker"]
644
+ };
645
+ }
646
+ };
647
+ }
648
+ function setElementContextResolver(resolver) {
649
+ activeResolver = resolver;
650
+ }
651
+ function getElementContextResolver() {
652
+ activeResolver ??= createElementContextResolver({
653
+ root: "/",
654
+ registry: runtimeElementRegistry,
655
+ querySelector(selector) {
656
+ return document.querySelector(selector);
657
+ }
658
+ });
659
+ return activeResolver;
660
+ }
661
+ function createProjectSourceContext(parsed, element, root) {
662
+ return {
663
+ ok: true,
664
+ elementId: parsed.elementId,
665
+ editable: true,
666
+ codeLocation: {
667
+ file: parsed.file,
668
+ line: parsed.line,
669
+ column: parsed.column
670
+ },
671
+ ...element ? { component: locateVueComponentForElement(element, root) } : {},
672
+ ...element ? { dom: createDomElementSummary(element) } : {},
673
+ limitations: element ? [] : ["runtime DOM element was not found"]
674
+ };
675
+ }
676
+ function createPackageContext(parsed) {
677
+ return {
678
+ ok: true,
679
+ elementId: parsed.elementId,
680
+ editable: false,
681
+ packageLocation: {
682
+ packageName: parsed.packageName,
683
+ entryFile: parsed.entryFile
684
+ },
685
+ limitations: ["third-party package source is not editable from this project"]
686
+ };
687
+ }
688
+ function createRuntimeContext(elementId, element, root) {
689
+ const component = locateVueComponentForElement(element, root);
690
+ const sourceFile = component?.source?.file;
691
+ return {
692
+ ok: true,
693
+ elementId,
694
+ editable: Boolean(sourceFile),
695
+ ...sourceFile ? { codeLocation: { file: sourceFile, line: 1, column: 1 } } : {},
696
+ component,
697
+ dom: createDomElementSummary(element),
698
+ limitations: sourceFile ? ["runtime id maps to nearest component file, exact template node is unavailable"] : ["runtime id is only valid during the current page lifecycle"]
699
+ };
700
+ }
701
+ function createMissingRuntimeElementError(elementId) {
702
+ return {
703
+ ok: false,
704
+ error: "element not found",
705
+ elementId,
706
+ limitations: [
707
+ "element was removed or page refreshed",
708
+ "please ask the user to pick the element again"
709
+ ]
710
+ };
711
+ }
712
+ function escapeSelector(value) {
713
+ const css = globalThis.CSS;
714
+ if (css?.escape) {
715
+ return css.escape(value);
716
+ }
717
+ return value.replace(/["\\]/g, "\\$&");
718
+ }
719
+
720
+ // src/runtime/networkHook.ts
721
+ var import_nanoid3 = require("nanoid");
722
+
126
723
  // src/shared/url.ts
127
724
  function parseRequestQuery(url) {
128
725
  const parsed = new URL(url, "http://vite-plugin-vue-mcp-next.local");
@@ -152,7 +749,7 @@ function safeUrlPathname(url) {
152
749
  // src/runtime/networkHook.ts
153
750
  function createHookNetworkRecord(input) {
154
751
  return {
155
- id: (0, import_nanoid2.nanoid)(),
752
+ id: (0, import_nanoid3.nanoid)(),
156
753
  pageId: input.pageId,
157
754
  source: "hook",
158
755
  url: input.url,
@@ -307,12 +904,12 @@ function safeReadXhrResponseText(xhr) {
307
904
  }
308
905
 
309
906
  // src/runtime/pageIdentity.ts
310
- var import_nanoid3 = require("nanoid");
907
+ var import_nanoid4 = require("nanoid");
311
908
  var RUNTIME_CLIENT_ID_STORAGE_KEY = "vite-plugin-vue-mcp-next:runtime-client-id";
312
909
  var RUNTIME_CLIENT_ID_WINDOW_NAME_PREFIX = "vite-plugin-vue-mcp-next:runtime-client-id=";
313
910
  var RUNTIME_CLIENT_ID_WINDOW_NAME_SEPARATOR = "\n";
314
911
  function createRuntimeClientId() {
315
- return `runtime-client-${(0, import_nanoid3.nanoid)()}`;
912
+ return `runtime-client-${(0, import_nanoid4.nanoid)()}`;
316
913
  }
317
914
  function readRuntimeClientIdFromTabScope(tabScope) {
318
915
  if (!tabScope) {
@@ -361,7 +958,7 @@ function getRuntimeClientId(storage, tabScope) {
361
958
  }
362
959
  }
363
960
  function createRuntimePageId() {
364
- return `runtime-${(0, import_nanoid3.nanoid)()}`;
961
+ return `runtime-${(0, import_nanoid4.nanoid)()}`;
365
962
  }
366
963
  function getRuntimePageIdentity(input) {
367
964
  return {
@@ -381,102 +978,7 @@ function getRuntimePageIdentity(input) {
381
978
  }
382
979
 
383
980
  // src/runtime/performanceHook.ts
384
- var import_nanoid4 = require("nanoid");
385
-
386
- // src/shared/limits.ts
387
- var DEFAULT_DOM_MAX_DEPTH = 8;
388
- var DEFAULT_DOM_MAX_NODES = 2e3;
389
- var DEFAULT_DOM_MAX_TEXT_LENGTH = 300;
390
- var DEFAULT_CONSOLE_MAX_RECORDS = 1e3;
391
- var DEFAULT_NETWORK_MAX_RECORDS = 500;
392
- var DEFAULT_NETWORK_MAX_BODY_SIZE = 1e5;
393
- var DEFAULT_MASK_HEADERS = [
394
- "authorization",
395
- "cookie",
396
- "set-cookie"
397
- ];
398
-
399
- // src/constants.ts
400
- var DEFAULT_MCP_PATH = "/__mcp";
401
- var DEFAULT_SCREENSHOT_MAX_BYTES = 5 * 1024 * 1024;
402
- var DEFAULT_SCREENSHOT_SAVE_DIR = ".vite-mcp/screenshot";
403
- var DEFAULT_PERFORMANCE_SAVE_DIR = ".vite-mcp/performance";
404
- var DEFAULT_PERFORMANCE_MAX_DURATION_MS = 3e4;
405
- var DEFAULT_PERFORMANCE_SAMPLE_INTERVAL_MS = 250;
406
- var DEFAULT_PERFORMANCE_LONG_TASK_THRESHOLD_MS = 50;
407
- var VIRTUAL_RUNTIME_ID = "virtual:vite-plugin-vue-mcp-next/runtime";
408
- var RESOLVED_VIRTUAL_RUNTIME_ID = `\0${VIRTUAL_RUNTIME_ID}`;
409
- var VIRTUAL_SCREENSHOT_CONFIG_ID = "virtual:vite-plugin-vue-mcp-next/screenshot-config";
410
- var RESOLVED_VIRTUAL_SCREENSHOT_CONFIG_ID = `\0${VIRTUAL_SCREENSHOT_CONFIG_ID}`;
411
- var VIRTUAL_SNAPDOM_LOADER_ID = "virtual:vite-plugin-vue-mcp-next/snapdom-loader";
412
- var RESOLVED_VIRTUAL_SNAPDOM_LOADER_ID = `\0${VIRTUAL_SNAPDOM_LOADER_ID}`;
413
- var DEFAULT_MCP_CLIENT_SERVER_NAME = "vite-mcp-next";
414
- var DEFAULT_OPTIONS = {
415
- mcpPath: DEFAULT_MCP_PATH,
416
- host: "localhost",
417
- printUrl: true,
418
- updateCursorMcpJson: {
419
- enabled: true,
420
- serverName: DEFAULT_MCP_CLIENT_SERVER_NAME
421
- },
422
- mcpClients: {
423
- cursor: true,
424
- codex: true,
425
- claudeCode: true,
426
- trae: true,
427
- serverName: DEFAULT_MCP_CLIENT_SERVER_NAME
428
- },
429
- skill: {
430
- autoConfig: true
431
- },
432
- runtime: {
433
- mode: "auto",
434
- evaluate: {
435
- enabled: false,
436
- timeoutMs: 3e3
437
- }
438
- },
439
- cdp: {},
440
- network: {
441
- mode: "auto",
442
- maxRecords: DEFAULT_NETWORK_MAX_RECORDS,
443
- captureRequestBody: true,
444
- captureResponseBody: true,
445
- maxBodySize: DEFAULT_NETWORK_MAX_BODY_SIZE,
446
- maskHeaders: [...DEFAULT_MASK_HEADERS]
447
- },
448
- dom: {
449
- maxDepth: DEFAULT_DOM_MAX_DEPTH,
450
- maxNodes: DEFAULT_DOM_MAX_NODES,
451
- maxTextLength: DEFAULT_DOM_MAX_TEXT_LENGTH
452
- },
453
- console: {
454
- maxRecords: DEFAULT_CONSOLE_MAX_RECORDS
455
- },
456
- screenshot: {
457
- type: "path",
458
- saveDir: DEFAULT_SCREENSHOT_SAVE_DIR,
459
- prefer: "auto",
460
- maxBytes: DEFAULT_SCREENSHOT_MAX_BYTES,
461
- snapdom: {
462
- options: {},
463
- plugins: []
464
- }
465
- },
466
- performance: {
467
- mode: "auto",
468
- maxDurationMs: DEFAULT_PERFORMANCE_MAX_DURATION_MS,
469
- sampleIntervalMs: DEFAULT_PERFORMANCE_SAMPLE_INTERVAL_MS,
470
- longTaskThresholdMs: DEFAULT_PERFORMANCE_LONG_TASK_THRESHOLD_MS,
471
- saveDir: DEFAULT_PERFORMANCE_SAVE_DIR,
472
- memory: {
473
- enabled: true
474
- },
475
- stacks: {
476
- enabled: true
477
- }
478
- }
479
- };
981
+ var import_nanoid5 = require("nanoid");
480
982
 
481
983
  // src/performance/summary.ts
482
984
  function buildPerformanceSummary(input) {
@@ -650,7 +1152,7 @@ function createPerformanceCollector(deps) {
650
1152
  };
651
1153
  }
652
1154
  function createRecordingId() {
653
- return `performance-${(0, import_nanoid4.nanoid)()}`;
1155
+ return `performance-${(0, import_nanoid5.nanoid)()}`;
654
1156
  }
655
1157
  function startSession(state, deps, options) {
656
1158
  const recordingId = createRecordingId();
@@ -1018,78 +1520,6 @@ function createMissingSnapdomError() {
1018
1520
  var import_devtools_kit = require("@vue/devtools-kit");
1019
1521
  var import_vite_dev_rpc = require("vite-dev-rpc");
1020
1522
 
1021
- // src/runtime/domSnapshot.ts
1022
- function createDomSnapshot(root, options) {
1023
- let count = 0;
1024
- function visit(node, depth) {
1025
- if (count >= options.maxNodes || depth > options.maxDepth) {
1026
- return null;
1027
- }
1028
- if (node.nodeType === Node.TEXT_NODE) {
1029
- return createTextSnapshot(node, options, () => {
1030
- count += 1;
1031
- });
1032
- }
1033
- if (!(node instanceof Element)) {
1034
- return null;
1035
- }
1036
- const tag = node.tagName.toLowerCase();
1037
- if (["script", "style", "noscript"].includes(tag)) {
1038
- return null;
1039
- }
1040
- count += 1;
1041
- return createElementSnapshot(node, tag, (child) => visit(child, depth + 1));
1042
- }
1043
- return visit(root, 0) ?? { tag: root.tagName.toLowerCase() };
1044
- }
1045
- function queryDomElements(selector, limit) {
1046
- return Array.from(document.querySelectorAll(selector)).slice(0, limit).map((element) => ({
1047
- tag: element.tagName.toLowerCase(),
1048
- text: element.textContent.trim(),
1049
- attrs: collectAttrs(element),
1050
- rect: serializeRect(element.getBoundingClientRect())
1051
- }));
1052
- }
1053
- function createTextSnapshot(node, options, markVisited) {
1054
- const text = node.textContent?.trim();
1055
- if (!text) {
1056
- return null;
1057
- }
1058
- markVisited();
1059
- return { tag: "#text", text: truncateText(text, options.maxTextLength).text };
1060
- }
1061
- function createElementSnapshot(node, tag, visitChild) {
1062
- const attrs = collectAttrs(node);
1063
- const children = Array.from(node.childNodes).map((child) => visitChild(child)).filter((child) => Boolean(child));
1064
- return {
1065
- tag,
1066
- ...Object.keys(attrs).length ? { attrs } : {},
1067
- ...children.length ? { children } : {}
1068
- };
1069
- }
1070
- function collectAttrs(element) {
1071
- const attrs = {};
1072
- for (const attr of Array.from(element.attributes)) {
1073
- attrs[attr.name] = attr.value;
1074
- }
1075
- if (element instanceof HTMLInputElement && element.type === "password") {
1076
- attrs.value = "[masked]";
1077
- }
1078
- return attrs;
1079
- }
1080
- function serializeRect(rect) {
1081
- return {
1082
- x: rect.x,
1083
- y: rect.y,
1084
- width: rect.width,
1085
- height: rect.height,
1086
- top: rect.top,
1087
- right: rect.right,
1088
- bottom: rect.bottom,
1089
- left: rect.left
1090
- };
1091
- }
1092
-
1093
1523
  // src/runtime/evaluateExpression.ts
1094
1524
  async function evaluateExpression(request) {
1095
1525
  const value = runExpression(request.expression);
@@ -1517,6 +1947,22 @@ function createRuntimeDevtoolsRpc(getRpc) {
1517
1947
  );
1518
1948
  },
1519
1949
  onDomQueryUpdated: () => void 0,
1950
+ getElementContext(options) {
1951
+ try {
1952
+ getRpc().onElementContextUpdated(
1953
+ options.event,
1954
+ getElementContextResolver().getElementContext(options.elementId)
1955
+ );
1956
+ } catch (error) {
1957
+ getRpc().onElementContextUpdated(options.event, {
1958
+ ok: false,
1959
+ elementId: options.elementId,
1960
+ error: error instanceof Error ? error.message : String(error),
1961
+ limitations: ["runtime element context lookup failed"]
1962
+ });
1963
+ }
1964
+ },
1965
+ onElementContextUpdated: () => void 0,
1520
1966
  reloadPage(options) {
1521
1967
  getRpc().onPageReloaded(options.event, { ok: true, source: "hook" });
1522
1968
  setTimeout(() => {
@@ -1853,13 +2299,16 @@ function createMissingComponentError(componentName) {
1853
2299
  }
1854
2300
 
1855
2301
  // src/runtime/client.ts
1856
- async function startRuntimeClient() {
2302
+ async function startRuntimeClient(runtimeOptions = {
2303
+ elementPicker: DEFAULT_OPTIONS.elementPicker
2304
+ }) {
1857
2305
  initializeVueDevtoolsHook();
1858
2306
  const hot = await (0, import_vite_hot_client.createHotContext)("vite-plugin-vue-mcp-next", "/");
1859
2307
  if (!hot) {
1860
2308
  return;
1861
2309
  }
1862
2310
  installVueBridge(hot);
2311
+ installElementPicker(runtimeOptions.elementPicker);
1863
2312
  const identity = getRuntimePageIdentity({
1864
2313
  href: window.location.href,
1865
2314
  title: document.title,
@@ -1868,7 +2317,20 @@ async function startRuntimeClient() {
1868
2317
  innerHeight: window.innerHeight,
1869
2318
  readyState: document.readyState
1870
2319
  });
1871
- hot.send("vite-plugin-vue-mcp-next:page-connected", identity);
2320
+ setElementContextResolver(
2321
+ createElementContextResolver({
2322
+ root: runtimeOptions.projectRoot ?? "/",
2323
+ registry: runtimeElementRegistry,
2324
+ querySelector(selector) {
2325
+ return document.querySelector(selector);
2326
+ }
2327
+ })
2328
+ );
2329
+ hot.send(RUNTIME_PAGE_CONNECTED_EVENT, identity);
2330
+ installRuntimePageLifecycle({
2331
+ pageId: identity.pageId,
2332
+ send: hot.send.bind(hot)
2333
+ });
1872
2334
  installPerformanceHook({
1873
2335
  pageId: identity.pageId,
1874
2336
  send(report) {
@@ -1890,6 +2352,27 @@ async function startRuntimeClient() {
1890
2352
  }
1891
2353
  });
1892
2354
  }
2355
+ function installRuntimePageLifecycle(options) {
2356
+ let disconnected = false;
2357
+ const heartbeatTimer = setInterval(() => {
2358
+ options.send(RUNTIME_PAGE_HEARTBEAT_EVENT, {
2359
+ pageId: options.pageId,
2360
+ timestamp: Date.now()
2361
+ });
2362
+ }, DEFAULT_RUNTIME_PAGE_HEARTBEAT_INTERVAL_MS);
2363
+ const disconnect = () => {
2364
+ if (disconnected) {
2365
+ return;
2366
+ }
2367
+ disconnected = true;
2368
+ clearInterval(heartbeatTimer);
2369
+ options.send(RUNTIME_PAGE_DISCONNECTED_EVENT, {
2370
+ pageId: options.pageId
2371
+ });
2372
+ };
2373
+ window.addEventListener("pagehide", disconnect, { once: true });
2374
+ window.addEventListener("beforeunload", disconnect, { once: true });
2375
+ }
1893
2376
  // Annotate the CommonJS export names for ESM import in node:
1894
2377
  0 && (module.exports = {
1895
2378
  evaluateExpression,