@xiaou66/vite-plugin-vue-mcp-next 1.1.1 → 1.3.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.
@@ -60,6 +60,7 @@ var RUNTIME_PAGE_CONNECTED_EVENT = "vite-plugin-vue-mcp-next:page-connected";
60
60
  var RUNTIME_PAGE_DISCONNECTED_EVENT = "vite-plugin-vue-mcp-next:page-disconnected";
61
61
  var RUNTIME_PAGE_HEARTBEAT_EVENT = "vite-plugin-vue-mcp-next:heartbeat";
62
62
  var DEFAULT_RUNTIME_PAGE_HEARTBEAT_INTERVAL_MS = 15e3;
63
+ var DEFAULT_ELEMENT_PICKER_TOAST_DURATION_MS = 2200;
63
64
  var DEFAULT_OPTIONS = {
64
65
  mcpPath: DEFAULT_MCP_PATH,
65
66
  host: "localhost",
@@ -78,6 +79,16 @@ var DEFAULT_OPTIONS = {
78
79
  skill: {
79
80
  autoConfig: true
80
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
+ },
81
92
  runtime: {
82
93
  mode: "auto",
83
94
  evaluate: {
@@ -196,8 +207,227 @@ function installConsoleHook(options) {
196
207
  };
197
208
  }
198
209
 
199
- // src/runtime/networkHook.ts
210
+ // src/runtime/elementRegistry.ts
200
211
  var import_nanoid2 = require("nanoid");
212
+ var RUNTIME_ELEMENT_ID_PREFIX = "runtime:vmcp_";
213
+ var RUNTIME_ELEMENT_ID_SIZE = 8;
214
+ var runtimeElementRegistry = createRuntimeElementRegistry();
215
+ function createRuntimeElementId() {
216
+ return `${RUNTIME_ELEMENT_ID_PREFIX}${(0, import_nanoid2.nanoid)(RUNTIME_ELEMENT_ID_SIZE)}`;
217
+ }
218
+ function createRuntimeElementRegistry() {
219
+ const records = /* @__PURE__ */ new Map();
220
+ return {
221
+ register(element) {
222
+ const elementId = createRuntimeElementId();
223
+ records.set(elementId, {
224
+ elementId,
225
+ element,
226
+ createdAt: Date.now()
227
+ });
228
+ return elementId;
229
+ },
230
+ get(elementId) {
231
+ return records.get(elementId);
232
+ },
233
+ clear() {
234
+ records.clear();
235
+ }
236
+ };
237
+ }
238
+
239
+ // src/runtime/elementPicker.ts
240
+ var MCP_ID_ATTR = "data-v-mcp-id";
241
+ var INTERNAL_ATTR = "data-v-mcp-internal";
242
+ var SUCCESS_MESSAGE = "\u5143\u7D20\u4F4D\u7F6E\u5DF2\u590D\u5236\uFF0C\u8BF7\u53D1\u9001\u7ED9 AI";
243
+ var COPY_FAILED_PREFIX = "\u590D\u5236\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u590D\u5236\u5143\u7D20 ID";
244
+ var OVERLAY_Z_INDEX = "2147483647";
245
+ var TOAST_Z_INDEX = "2147483647";
246
+ function installElementPicker(options) {
247
+ if (!options.enabled) {
248
+ return;
249
+ }
250
+ const registry = runtimeElementRegistry;
251
+ const overlay = createOverlay();
252
+ let active = false;
253
+ let currentElement;
254
+ window.addEventListener("keydown", (event) => {
255
+ active = matchesShortcut(event, options.shortcut);
256
+ });
257
+ window.addEventListener("keyup", () => {
258
+ active = false;
259
+ currentElement = void 0;
260
+ updateOverlay(overlay);
261
+ });
262
+ window.addEventListener("mousemove", (event) => {
263
+ if (!active) {
264
+ return;
265
+ }
266
+ currentElement = document.elementFromPoint(event.clientX, event.clientY) ?? void 0;
267
+ updateOverlay(overlay, currentElement);
268
+ });
269
+ window.addEventListener(
270
+ "click",
271
+ (event) => {
272
+ if (!active || !isElementLike(event.target)) {
273
+ return;
274
+ }
275
+ event.preventDefault();
276
+ event.stopPropagation();
277
+ void copyAndNotify(event.target, registry, options.toastDurationMs);
278
+ },
279
+ true
280
+ );
281
+ }
282
+ function matchesShortcut(event, shortcut) {
283
+ return event.altKey === shortcut.altKey && event.shiftKey === shortcut.shiftKey && event.metaKey === shortcut.metaKey && event.ctrlKey === shortcut.ctrlKey;
284
+ }
285
+ function resolveElementId(element, registry) {
286
+ return element.getAttribute(MCP_ID_ATTR) ?? registry.register(element);
287
+ }
288
+ async function copyAndNotify(element, registry, toastDurationMs) {
289
+ const elementId = resolveElementId(element, registry);
290
+ const copied = await copyElementId(elementId);
291
+ showToast(
292
+ copied ? SUCCESS_MESSAGE : `${COPY_FAILED_PREFIX}: ${elementId}`,
293
+ toastDurationMs
294
+ );
295
+ }
296
+ async function copyElementId(elementId) {
297
+ try {
298
+ await navigator.clipboard.writeText(elementId);
299
+ return true;
300
+ } catch {
301
+ return false;
302
+ }
303
+ }
304
+ function createOverlay() {
305
+ const overlay = document.createElement("div");
306
+ markInternalElement(overlay);
307
+ Object.assign(overlay.style, {
308
+ position: "fixed",
309
+ pointerEvents: "none",
310
+ border: "2px solid #1d4ed8",
311
+ background: "rgba(29, 78, 216, 0.08)",
312
+ zIndex: OVERLAY_Z_INDEX,
313
+ display: "none"
314
+ });
315
+ document.body.appendChild(overlay);
316
+ return overlay;
317
+ }
318
+ function updateOverlay(overlay, element) {
319
+ if (!element || getElementAttr(element, INTERNAL_ATTR) === "true") {
320
+ overlay.style.display = "none";
321
+ return;
322
+ }
323
+ const rect = element.getBoundingClientRect();
324
+ Object.assign(overlay.style, {
325
+ display: "block",
326
+ left: `${String(rect.x)}px`,
327
+ top: `${String(rect.y)}px`,
328
+ width: `${String(rect.width)}px`,
329
+ height: `${String(rect.height)}px`
330
+ });
331
+ }
332
+ function showToast(message, durationMs) {
333
+ const toast = document.createElement("div");
334
+ markInternalElement(toast);
335
+ toast.textContent = message;
336
+ Object.assign(toast.style, {
337
+ position: "fixed",
338
+ left: "50%",
339
+ bottom: "32px",
340
+ transform: "translateX(-50%)",
341
+ zIndex: TOAST_Z_INDEX,
342
+ padding: "8px 12px",
343
+ borderRadius: "6px",
344
+ background: "rgba(17, 24, 39, 0.92)",
345
+ color: "#fff",
346
+ fontSize: "13px",
347
+ pointerEvents: "none"
348
+ });
349
+ document.body.appendChild(toast);
350
+ globalThis.setTimeout(() => {
351
+ toast.remove();
352
+ }, durationMs);
353
+ }
354
+ function isElementLike(value) {
355
+ return Boolean(
356
+ value && typeof value === "object" && "getAttribute" in value && "getBoundingClientRect" in value
357
+ );
358
+ }
359
+ function markInternalElement(element) {
360
+ if (typeof element.setAttribute === "function") {
361
+ element.setAttribute(INTERNAL_ATTR, "true");
362
+ }
363
+ element.dataset.vMcpInternal = "true";
364
+ }
365
+ function getElementAttr(element, name) {
366
+ if (typeof element.getAttribute === "function") {
367
+ return element.getAttribute(name);
368
+ }
369
+ return null;
370
+ }
371
+
372
+ // src/shared/elementId.ts
373
+ var PROJECT_SOURCE_ID_PATTERN = /^(.+\.(?:vue|tsx|jsx|ts|js)):(\d+):(\d+)$/;
374
+ var RUNTIME_ID_PATTERN = /^runtime:([A-Za-z0-9_-]+)$/;
375
+ var PACKAGE_ID_PREFIX = "pkg:";
376
+ function parseElementId(elementId) {
377
+ const sourceMatch = PROJECT_SOURCE_ID_PATTERN.exec(elementId);
378
+ if (sourceMatch) {
379
+ return {
380
+ kind: "project-source",
381
+ elementId,
382
+ file: sourceMatch[1],
383
+ line: Number(sourceMatch[2]),
384
+ column: Number(sourceMatch[3])
385
+ };
386
+ }
387
+ if (elementId.startsWith(PACKAGE_ID_PREFIX)) {
388
+ return parsePackageElementId(elementId);
389
+ }
390
+ const runtimeMatch = RUNTIME_ID_PATTERN.exec(elementId);
391
+ if (runtimeMatch) {
392
+ return {
393
+ kind: "runtime",
394
+ elementId,
395
+ runtimeId: runtimeMatch[1]
396
+ };
397
+ }
398
+ return {
399
+ kind: "invalid",
400
+ elementId,
401
+ reason: "unsupported elementId format"
402
+ };
403
+ }
404
+ function parsePackageElementId(elementId) {
405
+ const value = elementId.slice(PACKAGE_ID_PREFIX.length);
406
+ const parts = value.split("/").filter(Boolean);
407
+ if (parts.length < 2) {
408
+ return {
409
+ kind: "invalid",
410
+ elementId,
411
+ reason: "package elementId must include packageName and entryFile"
412
+ };
413
+ }
414
+ const scoped = parts[0]?.startsWith("@");
415
+ const packageName = scoped ? parts.slice(0, 2).join("/") : parts[0];
416
+ const entryFile = parts.slice(scoped ? 2 : 1).join("/");
417
+ if (!entryFile) {
418
+ return {
419
+ kind: "invalid",
420
+ elementId,
421
+ reason: "package elementId must include entryFile"
422
+ };
423
+ }
424
+ return {
425
+ kind: "package",
426
+ elementId,
427
+ packageName,
428
+ entryFile
429
+ };
430
+ }
201
431
 
202
432
  // src/shared/sanitize.ts
203
433
  function truncateText(text, maxLength) {
@@ -222,6 +452,270 @@ function maskHeaders(headers = {}, maskNames = []) {
222
452
  );
223
453
  }
224
454
 
455
+ // src/runtime/domSnapshot.ts
456
+ function createDomSnapshot(root, options) {
457
+ let count = 0;
458
+ function visit(node, depth) {
459
+ if (count >= options.maxNodes || depth > options.maxDepth) {
460
+ return null;
461
+ }
462
+ if (node.nodeType === Node.TEXT_NODE) {
463
+ return createTextSnapshot(node, options, () => {
464
+ count += 1;
465
+ });
466
+ }
467
+ if (!(node instanceof Element)) {
468
+ return null;
469
+ }
470
+ const tag = node.tagName.toLowerCase();
471
+ if (["script", "style", "noscript"].includes(tag) || isInternalMcpElement(node)) {
472
+ return null;
473
+ }
474
+ count += 1;
475
+ return createElementSnapshot(node, tag, (child) => visit(child, depth + 1));
476
+ }
477
+ return visit(root, 0) ?? { tag: root.tagName.toLowerCase() };
478
+ }
479
+ function queryDomElements(selector, limit) {
480
+ return Array.from(document.querySelectorAll(selector)).filter((element) => !isInternalMcpElement(element)).slice(0, limit).map((element) => createDomElementSummary(element));
481
+ }
482
+ function createDomElementSummary(element) {
483
+ return {
484
+ tag: element.tagName.toLowerCase(),
485
+ text: element.textContent.trim(),
486
+ attrs: collectAttrs(element),
487
+ rect: serializeRect(element.getBoundingClientRect())
488
+ };
489
+ }
490
+ function createTextSnapshot(node, options, markVisited) {
491
+ const text = node.textContent?.trim();
492
+ if (!text) {
493
+ return null;
494
+ }
495
+ markVisited();
496
+ return { tag: "#text", text: truncateText(text, options.maxTextLength).text };
497
+ }
498
+ function createElementSnapshot(node, tag, visitChild) {
499
+ const attrs = collectAttrs(node);
500
+ const children = Array.from(node.childNodes).map((child) => visitChild(child)).filter((child) => Boolean(child));
501
+ return {
502
+ tag,
503
+ ...node.textContent.trim() ? { text: node.textContent.trim() } : {},
504
+ ...Object.keys(attrs).length ? { attrs } : {},
505
+ ...children.length ? { children } : {}
506
+ };
507
+ }
508
+ function collectAttrs(element) {
509
+ const attrs = {};
510
+ for (const attr of Array.from(element.attributes)) {
511
+ attrs[attr.name] = attr.value;
512
+ }
513
+ if (typeof HTMLInputElement !== "undefined" && element instanceof HTMLInputElement && element.type === "password") {
514
+ attrs.value = "[masked]";
515
+ }
516
+ return attrs;
517
+ }
518
+ function serializeRect(rect) {
519
+ return {
520
+ x: rect.x,
521
+ y: rect.y,
522
+ width: rect.width,
523
+ height: rect.height,
524
+ top: rect.top,
525
+ right: rect.right,
526
+ bottom: rect.bottom,
527
+ left: rect.left
528
+ };
529
+ }
530
+ function isInternalMcpElement(element) {
531
+ return element.getAttribute("data-v-mcp-internal") === "true";
532
+ }
533
+
534
+ // src/runtime/vueComponentLocator.ts
535
+ function locateVueComponentForElement(element, root) {
536
+ const component = findNearestComponent(element);
537
+ if (!component) {
538
+ return void 0;
539
+ }
540
+ const file = getComponentFile(component);
541
+ const name = getComponentName(component);
542
+ if (!file) {
543
+ return { name };
544
+ }
545
+ const packageLocation = parseNodeModulesFile(file);
546
+ if (packageLocation) {
547
+ return {
548
+ name,
549
+ source: void 0,
550
+ packageLocation
551
+ };
552
+ }
553
+ return {
554
+ name,
555
+ source: {
556
+ file: createProjectRelativePath(root, file)
557
+ },
558
+ packageLocation: void 0
559
+ };
560
+ }
561
+ function findNearestComponent(element) {
562
+ let current = element;
563
+ while (current) {
564
+ const component = current.__vueParentComponent;
565
+ if (isVueRuntimeComponent(component)) {
566
+ return component;
567
+ }
568
+ current = current.parentElement;
569
+ }
570
+ return void 0;
571
+ }
572
+ function getComponentName(component) {
573
+ return component.type?.name ?? component.type?.__name ?? component.type?.displayName;
574
+ }
575
+ function getComponentFile(component) {
576
+ return component.type?.__file;
577
+ }
578
+ function parseNodeModulesFile(file) {
579
+ const normalized = normalizePath(file);
580
+ const marker = "/node_modules/";
581
+ const index = normalized.lastIndexOf(marker);
582
+ if (index < 0) {
583
+ return void 0;
584
+ }
585
+ const parts = normalized.slice(index + marker.length).split("/").filter(Boolean);
586
+ const scoped = parts[0]?.startsWith("@");
587
+ const packageName = scoped ? parts.slice(0, 2).join("/") : parts[0];
588
+ const entryFile = parts.slice(scoped ? 2 : 1).join("/");
589
+ if (!packageName || !entryFile) {
590
+ return void 0;
591
+ }
592
+ return {
593
+ packageName,
594
+ entryFile
595
+ };
596
+ }
597
+ function normalizePath(path) {
598
+ return path.replace(/\\/g, "/");
599
+ }
600
+ function createProjectRelativePath(root, file) {
601
+ const normalizedRoot = normalizePath(root).replace(/\/$/, "");
602
+ const normalizedFile = normalizePath(file);
603
+ const prefix = `${normalizedRoot}/`;
604
+ if (normalizedFile.startsWith(prefix)) {
605
+ return normalizedFile.slice(prefix.length);
606
+ }
607
+ return normalizedFile.replace(/^\//, "");
608
+ }
609
+ function isVueRuntimeComponent(value) {
610
+ return Boolean(value && typeof value === "object" && "type" in value);
611
+ }
612
+
613
+ // src/runtime/elementContext.ts
614
+ var activeResolver;
615
+ function createElementContextResolver(options) {
616
+ return {
617
+ getElementContext(elementId) {
618
+ const parsed = parseElementId(elementId);
619
+ if (parsed.kind === "project-source") {
620
+ const element = options.querySelector(
621
+ `[data-v-mcp-id="${escapeSelector(elementId)}"]`
622
+ );
623
+ return createProjectSourceContext(parsed, element, options.root);
624
+ }
625
+ if (parsed.kind === "package") {
626
+ return createPackageContext(parsed);
627
+ }
628
+ if (parsed.kind === "runtime") {
629
+ const record = options.registry.get(elementId);
630
+ if (!record) {
631
+ return createMissingRuntimeElementError(elementId);
632
+ }
633
+ return createRuntimeContext(elementId, record.element, options.root);
634
+ }
635
+ return {
636
+ ok: false,
637
+ error: parsed.reason,
638
+ elementId,
639
+ limitations: ["please provide a copied elementId from the element picker"]
640
+ };
641
+ }
642
+ };
643
+ }
644
+ function setElementContextResolver(resolver) {
645
+ activeResolver = resolver;
646
+ }
647
+ function getElementContextResolver() {
648
+ activeResolver ??= createElementContextResolver({
649
+ root: "/",
650
+ registry: runtimeElementRegistry,
651
+ querySelector(selector) {
652
+ return document.querySelector(selector);
653
+ }
654
+ });
655
+ return activeResolver;
656
+ }
657
+ function createProjectSourceContext(parsed, element, root) {
658
+ return {
659
+ ok: true,
660
+ elementId: parsed.elementId,
661
+ editable: true,
662
+ codeLocation: {
663
+ file: parsed.file,
664
+ line: parsed.line,
665
+ column: parsed.column
666
+ },
667
+ ...element ? { component: locateVueComponentForElement(element, root) } : {},
668
+ ...element ? { dom: createDomElementSummary(element) } : {},
669
+ limitations: element ? [] : ["runtime DOM element was not found"]
670
+ };
671
+ }
672
+ function createPackageContext(parsed) {
673
+ return {
674
+ ok: true,
675
+ elementId: parsed.elementId,
676
+ editable: false,
677
+ packageLocation: {
678
+ packageName: parsed.packageName,
679
+ entryFile: parsed.entryFile
680
+ },
681
+ limitations: ["third-party package source is not editable from this project"]
682
+ };
683
+ }
684
+ function createRuntimeContext(elementId, element, root) {
685
+ const component = locateVueComponentForElement(element, root);
686
+ const sourceFile = component?.source?.file;
687
+ return {
688
+ ok: true,
689
+ elementId,
690
+ editable: Boolean(sourceFile),
691
+ ...sourceFile ? { codeLocation: { file: sourceFile, line: 1, column: 1 } } : {},
692
+ component,
693
+ dom: createDomElementSummary(element),
694
+ limitations: sourceFile ? ["runtime id maps to nearest component file, exact template node is unavailable"] : ["runtime id is only valid during the current page lifecycle"]
695
+ };
696
+ }
697
+ function createMissingRuntimeElementError(elementId) {
698
+ return {
699
+ ok: false,
700
+ error: "element not found",
701
+ elementId,
702
+ limitations: [
703
+ "element was removed or page refreshed",
704
+ "please ask the user to pick the element again"
705
+ ]
706
+ };
707
+ }
708
+ function escapeSelector(value) {
709
+ const css = globalThis.CSS;
710
+ if (css?.escape) {
711
+ return css.escape(value);
712
+ }
713
+ return value.replace(/["\\]/g, "\\$&");
714
+ }
715
+
716
+ // src/runtime/networkHook.ts
717
+ var import_nanoid3 = require("nanoid");
718
+
225
719
  // src/shared/url.ts
226
720
  function parseRequestQuery(url) {
227
721
  const parsed = new URL(url, "http://vite-plugin-vue-mcp-next.local");
@@ -251,7 +745,7 @@ function safeUrlPathname(url) {
251
745
  // src/runtime/networkHook.ts
252
746
  function createHookNetworkRecord(input) {
253
747
  return {
254
- id: (0, import_nanoid2.nanoid)(),
748
+ id: (0, import_nanoid3.nanoid)(),
255
749
  pageId: input.pageId,
256
750
  source: "hook",
257
751
  url: input.url,
@@ -406,12 +900,12 @@ function safeReadXhrResponseText(xhr) {
406
900
  }
407
901
 
408
902
  // src/runtime/pageIdentity.ts
409
- var import_nanoid3 = require("nanoid");
903
+ var import_nanoid4 = require("nanoid");
410
904
  var RUNTIME_CLIENT_ID_STORAGE_KEY = "vite-plugin-vue-mcp-next:runtime-client-id";
411
905
  var RUNTIME_CLIENT_ID_WINDOW_NAME_PREFIX = "vite-plugin-vue-mcp-next:runtime-client-id=";
412
906
  var RUNTIME_CLIENT_ID_WINDOW_NAME_SEPARATOR = "\n";
413
907
  function createRuntimeClientId() {
414
- return `runtime-client-${(0, import_nanoid3.nanoid)()}`;
908
+ return `runtime-client-${(0, import_nanoid4.nanoid)()}`;
415
909
  }
416
910
  function readRuntimeClientIdFromTabScope(tabScope) {
417
911
  if (!tabScope) {
@@ -460,7 +954,7 @@ function getRuntimeClientId(storage, tabScope) {
460
954
  }
461
955
  }
462
956
  function createRuntimePageId() {
463
- return `runtime-${(0, import_nanoid3.nanoid)()}`;
957
+ return `runtime-${(0, import_nanoid4.nanoid)()}`;
464
958
  }
465
959
  function getRuntimePageIdentity(input) {
466
960
  return {
@@ -480,7 +974,7 @@ function getRuntimePageIdentity(input) {
480
974
  }
481
975
 
482
976
  // src/runtime/performanceHook.ts
483
- var import_nanoid4 = require("nanoid");
977
+ var import_nanoid5 = require("nanoid");
484
978
 
485
979
  // src/performance/summary.ts
486
980
  function buildPerformanceSummary(input) {
@@ -654,7 +1148,7 @@ function createPerformanceCollector(deps) {
654
1148
  };
655
1149
  }
656
1150
  function createRecordingId() {
657
- return `performance-${(0, import_nanoid4.nanoid)()}`;
1151
+ return `performance-${(0, import_nanoid5.nanoid)()}`;
658
1152
  }
659
1153
  function startSession(state, deps, options) {
660
1154
  const recordingId = createRecordingId();
@@ -1022,78 +1516,6 @@ function createMissingSnapdomError() {
1022
1516
  var import_devtools_kit = require("@vue/devtools-kit");
1023
1517
  var import_vite_dev_rpc = require("vite-dev-rpc");
1024
1518
 
1025
- // src/runtime/domSnapshot.ts
1026
- function createDomSnapshot(root, options) {
1027
- let count = 0;
1028
- function visit(node, depth) {
1029
- if (count >= options.maxNodes || depth > options.maxDepth) {
1030
- return null;
1031
- }
1032
- if (node.nodeType === Node.TEXT_NODE) {
1033
- return createTextSnapshot(node, options, () => {
1034
- count += 1;
1035
- });
1036
- }
1037
- if (!(node instanceof Element)) {
1038
- return null;
1039
- }
1040
- const tag = node.tagName.toLowerCase();
1041
- if (["script", "style", "noscript"].includes(tag)) {
1042
- return null;
1043
- }
1044
- count += 1;
1045
- return createElementSnapshot(node, tag, (child) => visit(child, depth + 1));
1046
- }
1047
- return visit(root, 0) ?? { tag: root.tagName.toLowerCase() };
1048
- }
1049
- function queryDomElements(selector, limit) {
1050
- return Array.from(document.querySelectorAll(selector)).slice(0, limit).map((element) => ({
1051
- tag: element.tagName.toLowerCase(),
1052
- text: element.textContent.trim(),
1053
- attrs: collectAttrs(element),
1054
- rect: serializeRect(element.getBoundingClientRect())
1055
- }));
1056
- }
1057
- function createTextSnapshot(node, options, markVisited) {
1058
- const text = node.textContent?.trim();
1059
- if (!text) {
1060
- return null;
1061
- }
1062
- markVisited();
1063
- return { tag: "#text", text: truncateText(text, options.maxTextLength).text };
1064
- }
1065
- function createElementSnapshot(node, tag, visitChild) {
1066
- const attrs = collectAttrs(node);
1067
- const children = Array.from(node.childNodes).map((child) => visitChild(child)).filter((child) => Boolean(child));
1068
- return {
1069
- tag,
1070
- ...Object.keys(attrs).length ? { attrs } : {},
1071
- ...children.length ? { children } : {}
1072
- };
1073
- }
1074
- function collectAttrs(element) {
1075
- const attrs = {};
1076
- for (const attr of Array.from(element.attributes)) {
1077
- attrs[attr.name] = attr.value;
1078
- }
1079
- if (element instanceof HTMLInputElement && element.type === "password") {
1080
- attrs.value = "[masked]";
1081
- }
1082
- return attrs;
1083
- }
1084
- function serializeRect(rect) {
1085
- return {
1086
- x: rect.x,
1087
- y: rect.y,
1088
- width: rect.width,
1089
- height: rect.height,
1090
- top: rect.top,
1091
- right: rect.right,
1092
- bottom: rect.bottom,
1093
- left: rect.left
1094
- };
1095
- }
1096
-
1097
1519
  // src/runtime/evaluateExpression.ts
1098
1520
  async function evaluateExpression(request) {
1099
1521
  const value = runExpression(request.expression);
@@ -1116,6 +1538,390 @@ function createTimeout(timeoutMs) {
1116
1538
  });
1117
1539
  }
1118
1540
 
1541
+ // src/runtime/storageBridge.ts
1542
+ function createRuntimeStorageBridge(env) {
1543
+ return {
1544
+ async manageStorage(request) {
1545
+ try {
1546
+ return await manageRuntimeStorage(env, request);
1547
+ } catch (error) {
1548
+ return createRuntimeStorageError(
1549
+ request,
1550
+ error instanceof Error ? error.message : String(error)
1551
+ );
1552
+ }
1553
+ }
1554
+ };
1555
+ }
1556
+ async function manageRuntimeStorage(env, request) {
1557
+ if (request.origin !== env.origin) {
1558
+ return createRuntimeStorageError(
1559
+ request,
1560
+ "Runtime storage access is limited to the current page origin"
1561
+ );
1562
+ }
1563
+ if (request.scope === "cookie") {
1564
+ return manageRuntimeCookie(env, request);
1565
+ }
1566
+ if (request.scope === "indexedDB") {
1567
+ return manageRuntimeIndexedDb(env, request);
1568
+ }
1569
+ return manageRuntimeWebStorage(getWebStorage(env, request.scope), request);
1570
+ }
1571
+ function manageRuntimeCookie(env, request) {
1572
+ if (!env.cookie) {
1573
+ return createRuntimeStorageError(
1574
+ request,
1575
+ "Runtime cookie access is unavailable",
1576
+ ["document.cookie is not available in this runtime environment"]
1577
+ );
1578
+ }
1579
+ if (request.action === "list") {
1580
+ return createRuntimeStorageSuccess(request, {
1581
+ origin: request.origin,
1582
+ cookies: readRuntimeCookies(env.cookie)
1583
+ });
1584
+ }
1585
+ if (request.action === "get") {
1586
+ assertCookieName(request);
1587
+ return createRuntimeStorageSuccess(request, {
1588
+ origin: request.origin,
1589
+ cookies: readRuntimeCookies(env.cookie).filter(
1590
+ (cookie) => cookie.name === request.cookie.name
1591
+ )
1592
+ });
1593
+ }
1594
+ if (request.action === "set") {
1595
+ assertCookieName(request);
1596
+ env.cookie.set(createRuntimeCookieWrite(request));
1597
+ return createRuntimeStorageSuccess(request, { ok: true });
1598
+ }
1599
+ if (request.action === "delete") {
1600
+ assertCookieName(request);
1601
+ env.cookie.set(createRuntimeCookieDelete(request));
1602
+ return createRuntimeStorageSuccess(request, {
1603
+ deletedCount: 1,
1604
+ skippedHttpOnlyCount: 0,
1605
+ limitations: ["HttpOnly cookies are invisible to runtime cookie access"]
1606
+ });
1607
+ }
1608
+ const cookies = readRuntimeCookies(env.cookie);
1609
+ for (const cookie of cookies) {
1610
+ env.cookie.set(
1611
+ createRuntimeCookieDelete({
1612
+ ...request,
1613
+ cookie: {
1614
+ name: cookie.name,
1615
+ path: request.cookie?.path
1616
+ }
1617
+ })
1618
+ );
1619
+ }
1620
+ return createRuntimeStorageSuccess(request, {
1621
+ deletedCount: cookies.length,
1622
+ skippedHttpOnlyCount: 0,
1623
+ limitations: ["HttpOnly cookies are invisible to runtime cookie access"]
1624
+ });
1625
+ }
1626
+ function getWebStorage(env, scope) {
1627
+ return scope === "sessionStorage" ? env.sessionStorage : env.localStorage;
1628
+ }
1629
+ function manageRuntimeWebStorage(storage, request) {
1630
+ if (request.action === "list") {
1631
+ return createRuntimeStorageSuccess(request, {
1632
+ origin: request.origin,
1633
+ scope: request.scope,
1634
+ entries: readStorageEntries(storage)
1635
+ });
1636
+ }
1637
+ if (request.action === "get") {
1638
+ assertStorageKey(request);
1639
+ return createRuntimeStorageSuccess(request, {
1640
+ origin: request.origin,
1641
+ scope: request.scope,
1642
+ key: request.key,
1643
+ value: storage.getItem(request.key)
1644
+ });
1645
+ }
1646
+ if (request.action === "set") {
1647
+ assertStorageKey(request);
1648
+ storage.setItem(request.key, request.value ?? "");
1649
+ return createRuntimeStorageSuccess(request, { ok: true });
1650
+ }
1651
+ if (request.action === "delete") {
1652
+ assertStorageKey(request);
1653
+ storage.removeItem(request.key);
1654
+ return createRuntimeStorageSuccess(request, { ok: true });
1655
+ }
1656
+ storage.clear();
1657
+ return createRuntimeStorageSuccess(request, { ok: true });
1658
+ }
1659
+ async function manageRuntimeIndexedDb(env, request) {
1660
+ if (!env.indexedDB?.databases) {
1661
+ return createRuntimeStorageError(request, "IndexedDB metadata API is unavailable");
1662
+ }
1663
+ if (request.action === "list") {
1664
+ const databases = await env.indexedDB.databases();
1665
+ return createRuntimeStorageSuccess(request, {
1666
+ origin: request.origin,
1667
+ scope: request.scope,
1668
+ databases: databases.map((database) => ({
1669
+ name: database.name,
1670
+ version: database.version
1671
+ }))
1672
+ });
1673
+ }
1674
+ assertIndexedDbTarget(request);
1675
+ if ("stores" in env.indexedDB) {
1676
+ return manageMemoryIndexedDb(env.indexedDB, request);
1677
+ }
1678
+ return manageBrowserIndexedDb(env.indexedDB, request);
1679
+ }
1680
+ function manageMemoryIndexedDb(indexedDB, request) {
1681
+ const storeId = `${request.databaseName}:${request.objectStoreName}`;
1682
+ const store = indexedDB.stores.get(storeId) ?? /* @__PURE__ */ new Map();
1683
+ indexedDB.stores.set(storeId, store);
1684
+ if (request.action === "get") {
1685
+ assertStorageKey(request);
1686
+ return createRuntimeStorageSuccess(request, {
1687
+ key: request.key,
1688
+ value: store.get(request.key) ?? null
1689
+ });
1690
+ }
1691
+ if (request.action === "set") {
1692
+ assertStorageKey(request);
1693
+ store.set(request.key, parseIndexedDbValue(request.value));
1694
+ return createRuntimeStorageSuccess(request, { ok: true });
1695
+ }
1696
+ if (request.action === "delete") {
1697
+ assertStorageKey(request);
1698
+ store.delete(request.key);
1699
+ return createRuntimeStorageSuccess(request, { ok: true });
1700
+ }
1701
+ store.clear();
1702
+ return createRuntimeStorageSuccess(request, { ok: true });
1703
+ }
1704
+ async function manageBrowserIndexedDb(indexedDB, request) {
1705
+ const database = await openIndexedDbForRequest(indexedDB, request);
1706
+ try {
1707
+ return await executeIndexedDbTransaction(database, request);
1708
+ } finally {
1709
+ database.close();
1710
+ }
1711
+ }
1712
+ function openIndexedDb(indexedDB, databaseName) {
1713
+ return new Promise((resolve, reject) => {
1714
+ const request = indexedDB.open(databaseName);
1715
+ request.onerror = () => {
1716
+ reject(request.error ?? new Error("IndexedDB open failed"));
1717
+ };
1718
+ request.onsuccess = () => {
1719
+ resolve(request.result);
1720
+ };
1721
+ });
1722
+ }
1723
+ async function openIndexedDbForRequest(indexedDB, request) {
1724
+ const database = await openIndexedDb(indexedDB, request.databaseName);
1725
+ if (request.action !== "set" || database.objectStoreNames.contains(request.objectStoreName)) {
1726
+ return database;
1727
+ }
1728
+ const version = database.version + 1;
1729
+ database.close();
1730
+ return openIndexedDbWithStore(indexedDB, {
1731
+ databaseName: request.databaseName,
1732
+ objectStoreName: request.objectStoreName,
1733
+ version
1734
+ });
1735
+ }
1736
+ function openIndexedDbWithStore(indexedDB, options) {
1737
+ return new Promise((resolve, reject) => {
1738
+ const request = indexedDB.open(options.databaseName, options.version);
1739
+ request.onupgradeneeded = () => {
1740
+ const database = request.result;
1741
+ if (!database.objectStoreNames.contains(options.objectStoreName)) {
1742
+ database.createObjectStore(options.objectStoreName);
1743
+ }
1744
+ };
1745
+ request.onerror = () => {
1746
+ reject(request.error ?? new Error("IndexedDB upgrade failed"));
1747
+ };
1748
+ request.onsuccess = () => {
1749
+ resolve(request.result);
1750
+ };
1751
+ });
1752
+ }
1753
+ function executeIndexedDbTransaction(database, request) {
1754
+ return new Promise((resolve, reject) => {
1755
+ const mode = request.action === "get" ? "readonly" : "readwrite";
1756
+ const transaction = database.transaction(request.objectStoreName, mode);
1757
+ const store = transaction.objectStore(request.objectStoreName);
1758
+ transaction.onerror = () => {
1759
+ reject(transaction.error ?? new Error("IndexedDB transaction failed"));
1760
+ };
1761
+ if (request.action === "get") {
1762
+ assertStorageKey(request);
1763
+ const getRequest = store.get(request.key);
1764
+ getRequest.onerror = () => {
1765
+ reject(getRequest.error ?? new Error("IndexedDB get failed"));
1766
+ };
1767
+ getRequest.onsuccess = () => {
1768
+ const value = getRequest.result ?? null;
1769
+ resolve(
1770
+ createRuntimeStorageSuccess(request, {
1771
+ key: request.key,
1772
+ value
1773
+ })
1774
+ );
1775
+ };
1776
+ return;
1777
+ }
1778
+ if (request.action === "set") {
1779
+ assertStorageKey(request);
1780
+ store.put(parseIndexedDbValue(request.value), request.key);
1781
+ transaction.oncomplete = () => {
1782
+ resolve(createRuntimeStorageSuccess(request, { ok: true }));
1783
+ };
1784
+ return;
1785
+ }
1786
+ if (request.action === "delete") {
1787
+ assertStorageKey(request);
1788
+ store.delete(request.key);
1789
+ transaction.oncomplete = () => {
1790
+ resolve(createRuntimeStorageSuccess(request, { ok: true }));
1791
+ };
1792
+ return;
1793
+ }
1794
+ store.clear();
1795
+ transaction.oncomplete = () => {
1796
+ resolve(createRuntimeStorageSuccess(request, { ok: true }));
1797
+ };
1798
+ });
1799
+ }
1800
+ function readStorageEntries(storage) {
1801
+ const entries = [];
1802
+ for (let index = 0; index < storage.length; index += 1) {
1803
+ const key = storage.key(index);
1804
+ if (!key) {
1805
+ continue;
1806
+ }
1807
+ const value = storage.getItem(key);
1808
+ if (value === null) {
1809
+ continue;
1810
+ }
1811
+ entries.push({ key, value });
1812
+ }
1813
+ return entries;
1814
+ }
1815
+ function readRuntimeCookies(cookieAccess) {
1816
+ return cookieAccess.get().split(";").map((item) => item.trim()).filter(Boolean).map((item) => {
1817
+ const separatorIndex = item.indexOf("=");
1818
+ if (separatorIndex === -1) {
1819
+ return { name: decodeCookiePart(item), value: "" };
1820
+ }
1821
+ return {
1822
+ name: decodeCookiePart(item.slice(0, separatorIndex)),
1823
+ value: decodeCookiePart(item.slice(separatorIndex + 1))
1824
+ };
1825
+ });
1826
+ }
1827
+ function createRuntimeCookieWrite(request) {
1828
+ const value = request.cookie.value ?? request.value ?? "";
1829
+ const parts = [
1830
+ `${encodeURIComponent(request.cookie.name)}=${encodeURIComponent(value)}`
1831
+ ];
1832
+ appendRuntimeCookieAttributes(parts, request.cookie);
1833
+ return parts.join("; ");
1834
+ }
1835
+ function createRuntimeCookieDelete(request) {
1836
+ const parts = [
1837
+ `${encodeURIComponent(request.cookie.name)}=`,
1838
+ "Expires=Thu, 01 Jan 1970 00:00:00 GMT",
1839
+ "Max-Age=0"
1840
+ ];
1841
+ appendRuntimeCookieAttributes(parts, {
1842
+ path: request.cookie.path,
1843
+ domain: request.cookie.domain
1844
+ });
1845
+ return parts.join("; ");
1846
+ }
1847
+ function appendRuntimeCookieAttributes(parts, cookie) {
1848
+ if (cookie.path) {
1849
+ parts.push(`Path=${cookie.path}`);
1850
+ }
1851
+ if (cookie.domain) {
1852
+ parts.push(`Domain=${cookie.domain}`);
1853
+ }
1854
+ if (cookie.expires !== void 0) {
1855
+ parts.push(`Expires=${new Date(cookie.expires * 1e3).toUTCString()}`);
1856
+ }
1857
+ if (cookie.sameSite) {
1858
+ parts.push(`SameSite=${normalizeRuntimeSameSite(cookie.sameSite)}`);
1859
+ }
1860
+ if (cookie.secure) {
1861
+ parts.push("Secure");
1862
+ }
1863
+ }
1864
+ function normalizeRuntimeSameSite(sameSite) {
1865
+ if (sameSite === "strict") {
1866
+ return "Strict";
1867
+ }
1868
+ if (sameSite === "lax") {
1869
+ return "Lax";
1870
+ }
1871
+ return "None";
1872
+ }
1873
+ function decodeCookiePart(value) {
1874
+ try {
1875
+ return decodeURIComponent(value);
1876
+ } catch {
1877
+ return value;
1878
+ }
1879
+ }
1880
+ function assertStorageKey(request) {
1881
+ if (!request.key) {
1882
+ throw new Error("Storage key is required for this operation");
1883
+ }
1884
+ }
1885
+ function assertCookieName(request) {
1886
+ if (!request.cookie?.name) {
1887
+ throw new Error("Cookie name is required for this operation");
1888
+ }
1889
+ }
1890
+ function assertIndexedDbTarget(request) {
1891
+ if (!request.databaseName || !request.objectStoreName) {
1892
+ throw new Error("IndexedDB databaseName and objectStoreName are required");
1893
+ }
1894
+ }
1895
+ function parseIndexedDbValue(value) {
1896
+ if (value === void 0) {
1897
+ return null;
1898
+ }
1899
+ try {
1900
+ return JSON.parse(value);
1901
+ } catch {
1902
+ return value;
1903
+ }
1904
+ }
1905
+ function createRuntimeStorageSuccess(request, data) {
1906
+ return {
1907
+ ok: true,
1908
+ source: "hook",
1909
+ action: request.action,
1910
+ scope: request.scope,
1911
+ data
1912
+ };
1913
+ }
1914
+ function createRuntimeStorageError(request, error, limitations) {
1915
+ return {
1916
+ ok: false,
1917
+ source: "hook",
1918
+ action: request.action,
1919
+ scope: request.scope,
1920
+ error,
1921
+ limitations
1922
+ };
1923
+ }
1924
+
1119
1925
  // src/runtime/devtoolsBridge.ts
1120
1926
  function createRuntimeDevtoolsRpc(getRpc) {
1121
1927
  return {
@@ -1137,6 +1943,22 @@ function createRuntimeDevtoolsRpc(getRpc) {
1137
1943
  );
1138
1944
  },
1139
1945
  onDomQueryUpdated: () => void 0,
1946
+ getElementContext(options) {
1947
+ try {
1948
+ getRpc().onElementContextUpdated(
1949
+ options.event,
1950
+ getElementContextResolver().getElementContext(options.elementId)
1951
+ );
1952
+ } catch (error) {
1953
+ getRpc().onElementContextUpdated(options.event, {
1954
+ ok: false,
1955
+ elementId: options.elementId,
1956
+ error: error instanceof Error ? error.message : String(error),
1957
+ limitations: ["runtime element context lookup failed"]
1958
+ });
1959
+ }
1960
+ },
1961
+ onElementContextUpdated: () => void 0,
1140
1962
  reloadPage(options) {
1141
1963
  getRpc().onPageReloaded(options.event, { ok: true, source: "hook" });
1142
1964
  setTimeout(() => {
@@ -1171,7 +1993,26 @@ function createRuntimeDevtoolsRpc(getRpc) {
1171
1993
  });
1172
1994
  }
1173
1995
  },
1174
- onScreenshotTaken: () => void 0
1996
+ onScreenshotTaken: () => void 0,
1997
+ async manageStorage(options) {
1998
+ const bridge = createRuntimeStorageBridge({
1999
+ origin: window.location.origin,
2000
+ localStorage: window.localStorage,
2001
+ sessionStorage: window.sessionStorage,
2002
+ indexedDB: window.indexedDB,
2003
+ cookie: {
2004
+ get: () => window.document.cookie,
2005
+ set: (value) => {
2006
+ window.document.cookie = value;
2007
+ }
2008
+ }
2009
+ });
2010
+ getRpc().onStorageUpdated(
2011
+ options.event,
2012
+ await bridge.manageStorage(options)
2013
+ );
2014
+ },
2015
+ onStorageUpdated: () => void 0
1175
2016
  };
1176
2017
  }
1177
2018
 
@@ -1454,13 +2295,16 @@ function createMissingComponentError(componentName) {
1454
2295
  }
1455
2296
 
1456
2297
  // src/runtime/client.ts
1457
- async function startRuntimeClient() {
2298
+ async function startRuntimeClient(runtimeOptions = {
2299
+ elementPicker: DEFAULT_OPTIONS.elementPicker
2300
+ }) {
1458
2301
  initializeVueDevtoolsHook();
1459
2302
  const hot = await (0, import_vite_hot_client.createHotContext)("vite-plugin-vue-mcp-next", "/");
1460
2303
  if (!hot) {
1461
2304
  return;
1462
2305
  }
1463
2306
  installVueBridge(hot);
2307
+ installElementPicker(runtimeOptions.elementPicker);
1464
2308
  const identity = getRuntimePageIdentity({
1465
2309
  href: window.location.href,
1466
2310
  title: document.title,
@@ -1469,6 +2313,15 @@ async function startRuntimeClient() {
1469
2313
  innerHeight: window.innerHeight,
1470
2314
  readyState: document.readyState
1471
2315
  });
2316
+ setElementContextResolver(
2317
+ createElementContextResolver({
2318
+ root: runtimeOptions.projectRoot ?? "/",
2319
+ registry: runtimeElementRegistry,
2320
+ querySelector(selector) {
2321
+ return document.querySelector(selector);
2322
+ }
2323
+ })
2324
+ );
1472
2325
  hot.send(RUNTIME_PAGE_CONNECTED_EVENT, identity);
1473
2326
  installRuntimePageLifecycle({
1474
2327
  pageId: identity.pageId,