@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.
@@ -33,6 +33,7 @@ var RUNTIME_PAGE_CONNECTED_EVENT = "vite-plugin-vue-mcp-next:page-connected";
33
33
  var RUNTIME_PAGE_DISCONNECTED_EVENT = "vite-plugin-vue-mcp-next:page-disconnected";
34
34
  var RUNTIME_PAGE_HEARTBEAT_EVENT = "vite-plugin-vue-mcp-next:heartbeat";
35
35
  var DEFAULT_RUNTIME_PAGE_HEARTBEAT_INTERVAL_MS = 15e3;
36
+ var DEFAULT_ELEMENT_PICKER_TOAST_DURATION_MS = 2200;
36
37
  var DEFAULT_OPTIONS = {
37
38
  mcpPath: DEFAULT_MCP_PATH,
38
39
  host: "localhost",
@@ -51,6 +52,16 @@ var DEFAULT_OPTIONS = {
51
52
  skill: {
52
53
  autoConfig: true
53
54
  },
55
+ elementPicker: {
56
+ enabled: true,
57
+ shortcut: {
58
+ altKey: true,
59
+ shiftKey: true,
60
+ metaKey: false,
61
+ ctrlKey: false
62
+ },
63
+ toastDurationMs: DEFAULT_ELEMENT_PICKER_TOAST_DURATION_MS
64
+ },
54
65
  runtime: {
55
66
  mode: "auto",
56
67
  evaluate: {
@@ -169,8 +180,227 @@ function installConsoleHook(options) {
169
180
  };
170
181
  }
171
182
 
172
- // src/runtime/networkHook.ts
183
+ // src/runtime/elementRegistry.ts
173
184
  import { nanoid as nanoid2 } from "nanoid";
185
+ var RUNTIME_ELEMENT_ID_PREFIX = "runtime:vmcp_";
186
+ var RUNTIME_ELEMENT_ID_SIZE = 8;
187
+ var runtimeElementRegistry = createRuntimeElementRegistry();
188
+ function createRuntimeElementId() {
189
+ return `${RUNTIME_ELEMENT_ID_PREFIX}${nanoid2(RUNTIME_ELEMENT_ID_SIZE)}`;
190
+ }
191
+ function createRuntimeElementRegistry() {
192
+ const records = /* @__PURE__ */ new Map();
193
+ return {
194
+ register(element) {
195
+ const elementId = createRuntimeElementId();
196
+ records.set(elementId, {
197
+ elementId,
198
+ element,
199
+ createdAt: Date.now()
200
+ });
201
+ return elementId;
202
+ },
203
+ get(elementId) {
204
+ return records.get(elementId);
205
+ },
206
+ clear() {
207
+ records.clear();
208
+ }
209
+ };
210
+ }
211
+
212
+ // src/runtime/elementPicker.ts
213
+ var MCP_ID_ATTR = "data-v-mcp-id";
214
+ var INTERNAL_ATTR = "data-v-mcp-internal";
215
+ var SUCCESS_MESSAGE = "\u5143\u7D20\u4F4D\u7F6E\u5DF2\u590D\u5236\uFF0C\u8BF7\u53D1\u9001\u7ED9 AI";
216
+ var COPY_FAILED_PREFIX = "\u590D\u5236\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u590D\u5236\u5143\u7D20 ID";
217
+ var OVERLAY_Z_INDEX = "2147483647";
218
+ var TOAST_Z_INDEX = "2147483647";
219
+ function installElementPicker(options) {
220
+ if (!options.enabled) {
221
+ return;
222
+ }
223
+ const registry = runtimeElementRegistry;
224
+ const overlay = createOverlay();
225
+ let active = false;
226
+ let currentElement;
227
+ window.addEventListener("keydown", (event) => {
228
+ active = matchesShortcut(event, options.shortcut);
229
+ });
230
+ window.addEventListener("keyup", () => {
231
+ active = false;
232
+ currentElement = void 0;
233
+ updateOverlay(overlay);
234
+ });
235
+ window.addEventListener("mousemove", (event) => {
236
+ if (!active) {
237
+ return;
238
+ }
239
+ currentElement = document.elementFromPoint(event.clientX, event.clientY) ?? void 0;
240
+ updateOverlay(overlay, currentElement);
241
+ });
242
+ window.addEventListener(
243
+ "click",
244
+ (event) => {
245
+ if (!active || !isElementLike(event.target)) {
246
+ return;
247
+ }
248
+ event.preventDefault();
249
+ event.stopPropagation();
250
+ void copyAndNotify(event.target, registry, options.toastDurationMs);
251
+ },
252
+ true
253
+ );
254
+ }
255
+ function matchesShortcut(event, shortcut) {
256
+ return event.altKey === shortcut.altKey && event.shiftKey === shortcut.shiftKey && event.metaKey === shortcut.metaKey && event.ctrlKey === shortcut.ctrlKey;
257
+ }
258
+ function resolveElementId(element, registry) {
259
+ return element.getAttribute(MCP_ID_ATTR) ?? registry.register(element);
260
+ }
261
+ async function copyAndNotify(element, registry, toastDurationMs) {
262
+ const elementId = resolveElementId(element, registry);
263
+ const copied = await copyElementId(elementId);
264
+ showToast(
265
+ copied ? SUCCESS_MESSAGE : `${COPY_FAILED_PREFIX}: ${elementId}`,
266
+ toastDurationMs
267
+ );
268
+ }
269
+ async function copyElementId(elementId) {
270
+ try {
271
+ await navigator.clipboard.writeText(elementId);
272
+ return true;
273
+ } catch {
274
+ return false;
275
+ }
276
+ }
277
+ function createOverlay() {
278
+ const overlay = document.createElement("div");
279
+ markInternalElement(overlay);
280
+ Object.assign(overlay.style, {
281
+ position: "fixed",
282
+ pointerEvents: "none",
283
+ border: "2px solid #1d4ed8",
284
+ background: "rgba(29, 78, 216, 0.08)",
285
+ zIndex: OVERLAY_Z_INDEX,
286
+ display: "none"
287
+ });
288
+ document.body.appendChild(overlay);
289
+ return overlay;
290
+ }
291
+ function updateOverlay(overlay, element) {
292
+ if (!element || getElementAttr(element, INTERNAL_ATTR) === "true") {
293
+ overlay.style.display = "none";
294
+ return;
295
+ }
296
+ const rect = element.getBoundingClientRect();
297
+ Object.assign(overlay.style, {
298
+ display: "block",
299
+ left: `${String(rect.x)}px`,
300
+ top: `${String(rect.y)}px`,
301
+ width: `${String(rect.width)}px`,
302
+ height: `${String(rect.height)}px`
303
+ });
304
+ }
305
+ function showToast(message, durationMs) {
306
+ const toast = document.createElement("div");
307
+ markInternalElement(toast);
308
+ toast.textContent = message;
309
+ Object.assign(toast.style, {
310
+ position: "fixed",
311
+ left: "50%",
312
+ bottom: "32px",
313
+ transform: "translateX(-50%)",
314
+ zIndex: TOAST_Z_INDEX,
315
+ padding: "8px 12px",
316
+ borderRadius: "6px",
317
+ background: "rgba(17, 24, 39, 0.92)",
318
+ color: "#fff",
319
+ fontSize: "13px",
320
+ pointerEvents: "none"
321
+ });
322
+ document.body.appendChild(toast);
323
+ globalThis.setTimeout(() => {
324
+ toast.remove();
325
+ }, durationMs);
326
+ }
327
+ function isElementLike(value) {
328
+ return Boolean(
329
+ value && typeof value === "object" && "getAttribute" in value && "getBoundingClientRect" in value
330
+ );
331
+ }
332
+ function markInternalElement(element) {
333
+ if (typeof element.setAttribute === "function") {
334
+ element.setAttribute(INTERNAL_ATTR, "true");
335
+ }
336
+ element.dataset.vMcpInternal = "true";
337
+ }
338
+ function getElementAttr(element, name) {
339
+ if (typeof element.getAttribute === "function") {
340
+ return element.getAttribute(name);
341
+ }
342
+ return null;
343
+ }
344
+
345
+ // src/shared/elementId.ts
346
+ var PROJECT_SOURCE_ID_PATTERN = /^(.+\.(?:vue|tsx|jsx|ts|js)):(\d+):(\d+)$/;
347
+ var RUNTIME_ID_PATTERN = /^runtime:([A-Za-z0-9_-]+)$/;
348
+ var PACKAGE_ID_PREFIX = "pkg:";
349
+ function parseElementId(elementId) {
350
+ const sourceMatch = PROJECT_SOURCE_ID_PATTERN.exec(elementId);
351
+ if (sourceMatch) {
352
+ return {
353
+ kind: "project-source",
354
+ elementId,
355
+ file: sourceMatch[1],
356
+ line: Number(sourceMatch[2]),
357
+ column: Number(sourceMatch[3])
358
+ };
359
+ }
360
+ if (elementId.startsWith(PACKAGE_ID_PREFIX)) {
361
+ return parsePackageElementId(elementId);
362
+ }
363
+ const runtimeMatch = RUNTIME_ID_PATTERN.exec(elementId);
364
+ if (runtimeMatch) {
365
+ return {
366
+ kind: "runtime",
367
+ elementId,
368
+ runtimeId: runtimeMatch[1]
369
+ };
370
+ }
371
+ return {
372
+ kind: "invalid",
373
+ elementId,
374
+ reason: "unsupported elementId format"
375
+ };
376
+ }
377
+ function parsePackageElementId(elementId) {
378
+ const value = elementId.slice(PACKAGE_ID_PREFIX.length);
379
+ const parts = value.split("/").filter(Boolean);
380
+ if (parts.length < 2) {
381
+ return {
382
+ kind: "invalid",
383
+ elementId,
384
+ reason: "package elementId must include packageName and entryFile"
385
+ };
386
+ }
387
+ const scoped = parts[0]?.startsWith("@");
388
+ const packageName = scoped ? parts.slice(0, 2).join("/") : parts[0];
389
+ const entryFile = parts.slice(scoped ? 2 : 1).join("/");
390
+ if (!entryFile) {
391
+ return {
392
+ kind: "invalid",
393
+ elementId,
394
+ reason: "package elementId must include entryFile"
395
+ };
396
+ }
397
+ return {
398
+ kind: "package",
399
+ elementId,
400
+ packageName,
401
+ entryFile
402
+ };
403
+ }
174
404
 
175
405
  // src/shared/sanitize.ts
176
406
  function truncateText(text, maxLength) {
@@ -195,6 +425,270 @@ function maskHeaders(headers = {}, maskNames = []) {
195
425
  );
196
426
  }
197
427
 
428
+ // src/runtime/domSnapshot.ts
429
+ function createDomSnapshot(root, options) {
430
+ let count = 0;
431
+ function visit(node, depth) {
432
+ if (count >= options.maxNodes || depth > options.maxDepth) {
433
+ return null;
434
+ }
435
+ if (node.nodeType === Node.TEXT_NODE) {
436
+ return createTextSnapshot(node, options, () => {
437
+ count += 1;
438
+ });
439
+ }
440
+ if (!(node instanceof Element)) {
441
+ return null;
442
+ }
443
+ const tag = node.tagName.toLowerCase();
444
+ if (["script", "style", "noscript"].includes(tag) || isInternalMcpElement(node)) {
445
+ return null;
446
+ }
447
+ count += 1;
448
+ return createElementSnapshot(node, tag, (child) => visit(child, depth + 1));
449
+ }
450
+ return visit(root, 0) ?? { tag: root.tagName.toLowerCase() };
451
+ }
452
+ function queryDomElements(selector, limit) {
453
+ return Array.from(document.querySelectorAll(selector)).filter((element) => !isInternalMcpElement(element)).slice(0, limit).map((element) => createDomElementSummary(element));
454
+ }
455
+ function createDomElementSummary(element) {
456
+ return {
457
+ tag: element.tagName.toLowerCase(),
458
+ text: element.textContent.trim(),
459
+ attrs: collectAttrs(element),
460
+ rect: serializeRect(element.getBoundingClientRect())
461
+ };
462
+ }
463
+ function createTextSnapshot(node, options, markVisited) {
464
+ const text = node.textContent?.trim();
465
+ if (!text) {
466
+ return null;
467
+ }
468
+ markVisited();
469
+ return { tag: "#text", text: truncateText(text, options.maxTextLength).text };
470
+ }
471
+ function createElementSnapshot(node, tag, visitChild) {
472
+ const attrs = collectAttrs(node);
473
+ const children = Array.from(node.childNodes).map((child) => visitChild(child)).filter((child) => Boolean(child));
474
+ return {
475
+ tag,
476
+ ...node.textContent.trim() ? { text: node.textContent.trim() } : {},
477
+ ...Object.keys(attrs).length ? { attrs } : {},
478
+ ...children.length ? { children } : {}
479
+ };
480
+ }
481
+ function collectAttrs(element) {
482
+ const attrs = {};
483
+ for (const attr of Array.from(element.attributes)) {
484
+ attrs[attr.name] = attr.value;
485
+ }
486
+ if (typeof HTMLInputElement !== "undefined" && element instanceof HTMLInputElement && element.type === "password") {
487
+ attrs.value = "[masked]";
488
+ }
489
+ return attrs;
490
+ }
491
+ function serializeRect(rect) {
492
+ return {
493
+ x: rect.x,
494
+ y: rect.y,
495
+ width: rect.width,
496
+ height: rect.height,
497
+ top: rect.top,
498
+ right: rect.right,
499
+ bottom: rect.bottom,
500
+ left: rect.left
501
+ };
502
+ }
503
+ function isInternalMcpElement(element) {
504
+ return element.getAttribute("data-v-mcp-internal") === "true";
505
+ }
506
+
507
+ // src/runtime/vueComponentLocator.ts
508
+ function locateVueComponentForElement(element, root) {
509
+ const component = findNearestComponent(element);
510
+ if (!component) {
511
+ return void 0;
512
+ }
513
+ const file = getComponentFile(component);
514
+ const name = getComponentName(component);
515
+ if (!file) {
516
+ return { name };
517
+ }
518
+ const packageLocation = parseNodeModulesFile(file);
519
+ if (packageLocation) {
520
+ return {
521
+ name,
522
+ source: void 0,
523
+ packageLocation
524
+ };
525
+ }
526
+ return {
527
+ name,
528
+ source: {
529
+ file: createProjectRelativePath(root, file)
530
+ },
531
+ packageLocation: void 0
532
+ };
533
+ }
534
+ function findNearestComponent(element) {
535
+ let current = element;
536
+ while (current) {
537
+ const component = current.__vueParentComponent;
538
+ if (isVueRuntimeComponent(component)) {
539
+ return component;
540
+ }
541
+ current = current.parentElement;
542
+ }
543
+ return void 0;
544
+ }
545
+ function getComponentName(component) {
546
+ return component.type?.name ?? component.type?.__name ?? component.type?.displayName;
547
+ }
548
+ function getComponentFile(component) {
549
+ return component.type?.__file;
550
+ }
551
+ function parseNodeModulesFile(file) {
552
+ const normalized = normalizePath(file);
553
+ const marker = "/node_modules/";
554
+ const index = normalized.lastIndexOf(marker);
555
+ if (index < 0) {
556
+ return void 0;
557
+ }
558
+ const parts = normalized.slice(index + marker.length).split("/").filter(Boolean);
559
+ const scoped = parts[0]?.startsWith("@");
560
+ const packageName = scoped ? parts.slice(0, 2).join("/") : parts[0];
561
+ const entryFile = parts.slice(scoped ? 2 : 1).join("/");
562
+ if (!packageName || !entryFile) {
563
+ return void 0;
564
+ }
565
+ return {
566
+ packageName,
567
+ entryFile
568
+ };
569
+ }
570
+ function normalizePath(path) {
571
+ return path.replace(/\\/g, "/");
572
+ }
573
+ function createProjectRelativePath(root, file) {
574
+ const normalizedRoot = normalizePath(root).replace(/\/$/, "");
575
+ const normalizedFile = normalizePath(file);
576
+ const prefix = `${normalizedRoot}/`;
577
+ if (normalizedFile.startsWith(prefix)) {
578
+ return normalizedFile.slice(prefix.length);
579
+ }
580
+ return normalizedFile.replace(/^\//, "");
581
+ }
582
+ function isVueRuntimeComponent(value) {
583
+ return Boolean(value && typeof value === "object" && "type" in value);
584
+ }
585
+
586
+ // src/runtime/elementContext.ts
587
+ var activeResolver;
588
+ function createElementContextResolver(options) {
589
+ return {
590
+ getElementContext(elementId) {
591
+ const parsed = parseElementId(elementId);
592
+ if (parsed.kind === "project-source") {
593
+ const element = options.querySelector(
594
+ `[data-v-mcp-id="${escapeSelector(elementId)}"]`
595
+ );
596
+ return createProjectSourceContext(parsed, element, options.root);
597
+ }
598
+ if (parsed.kind === "package") {
599
+ return createPackageContext(parsed);
600
+ }
601
+ if (parsed.kind === "runtime") {
602
+ const record = options.registry.get(elementId);
603
+ if (!record) {
604
+ return createMissingRuntimeElementError(elementId);
605
+ }
606
+ return createRuntimeContext(elementId, record.element, options.root);
607
+ }
608
+ return {
609
+ ok: false,
610
+ error: parsed.reason,
611
+ elementId,
612
+ limitations: ["please provide a copied elementId from the element picker"]
613
+ };
614
+ }
615
+ };
616
+ }
617
+ function setElementContextResolver(resolver) {
618
+ activeResolver = resolver;
619
+ }
620
+ function getElementContextResolver() {
621
+ activeResolver ??= createElementContextResolver({
622
+ root: "/",
623
+ registry: runtimeElementRegistry,
624
+ querySelector(selector) {
625
+ return document.querySelector(selector);
626
+ }
627
+ });
628
+ return activeResolver;
629
+ }
630
+ function createProjectSourceContext(parsed, element, root) {
631
+ return {
632
+ ok: true,
633
+ elementId: parsed.elementId,
634
+ editable: true,
635
+ codeLocation: {
636
+ file: parsed.file,
637
+ line: parsed.line,
638
+ column: parsed.column
639
+ },
640
+ ...element ? { component: locateVueComponentForElement(element, root) } : {},
641
+ ...element ? { dom: createDomElementSummary(element) } : {},
642
+ limitations: element ? [] : ["runtime DOM element was not found"]
643
+ };
644
+ }
645
+ function createPackageContext(parsed) {
646
+ return {
647
+ ok: true,
648
+ elementId: parsed.elementId,
649
+ editable: false,
650
+ packageLocation: {
651
+ packageName: parsed.packageName,
652
+ entryFile: parsed.entryFile
653
+ },
654
+ limitations: ["third-party package source is not editable from this project"]
655
+ };
656
+ }
657
+ function createRuntimeContext(elementId, element, root) {
658
+ const component = locateVueComponentForElement(element, root);
659
+ const sourceFile = component?.source?.file;
660
+ return {
661
+ ok: true,
662
+ elementId,
663
+ editable: Boolean(sourceFile),
664
+ ...sourceFile ? { codeLocation: { file: sourceFile, line: 1, column: 1 } } : {},
665
+ component,
666
+ dom: createDomElementSummary(element),
667
+ limitations: sourceFile ? ["runtime id maps to nearest component file, exact template node is unavailable"] : ["runtime id is only valid during the current page lifecycle"]
668
+ };
669
+ }
670
+ function createMissingRuntimeElementError(elementId) {
671
+ return {
672
+ ok: false,
673
+ error: "element not found",
674
+ elementId,
675
+ limitations: [
676
+ "element was removed or page refreshed",
677
+ "please ask the user to pick the element again"
678
+ ]
679
+ };
680
+ }
681
+ function escapeSelector(value) {
682
+ const css = globalThis.CSS;
683
+ if (css?.escape) {
684
+ return css.escape(value);
685
+ }
686
+ return value.replace(/["\\]/g, "\\$&");
687
+ }
688
+
689
+ // src/runtime/networkHook.ts
690
+ import { nanoid as nanoid3 } from "nanoid";
691
+
198
692
  // src/shared/url.ts
199
693
  function parseRequestQuery(url) {
200
694
  const parsed = new URL(url, "http://vite-plugin-vue-mcp-next.local");
@@ -224,7 +718,7 @@ function safeUrlPathname(url) {
224
718
  // src/runtime/networkHook.ts
225
719
  function createHookNetworkRecord(input) {
226
720
  return {
227
- id: nanoid2(),
721
+ id: nanoid3(),
228
722
  pageId: input.pageId,
229
723
  source: "hook",
230
724
  url: input.url,
@@ -379,12 +873,12 @@ function safeReadXhrResponseText(xhr) {
379
873
  }
380
874
 
381
875
  // src/runtime/pageIdentity.ts
382
- import { nanoid as nanoid3 } from "nanoid";
876
+ import { nanoid as nanoid4 } from "nanoid";
383
877
  var RUNTIME_CLIENT_ID_STORAGE_KEY = "vite-plugin-vue-mcp-next:runtime-client-id";
384
878
  var RUNTIME_CLIENT_ID_WINDOW_NAME_PREFIX = "vite-plugin-vue-mcp-next:runtime-client-id=";
385
879
  var RUNTIME_CLIENT_ID_WINDOW_NAME_SEPARATOR = "\n";
386
880
  function createRuntimeClientId() {
387
- return `runtime-client-${nanoid3()}`;
881
+ return `runtime-client-${nanoid4()}`;
388
882
  }
389
883
  function readRuntimeClientIdFromTabScope(tabScope) {
390
884
  if (!tabScope) {
@@ -433,7 +927,7 @@ function getRuntimeClientId(storage, tabScope) {
433
927
  }
434
928
  }
435
929
  function createRuntimePageId() {
436
- return `runtime-${nanoid3()}`;
930
+ return `runtime-${nanoid4()}`;
437
931
  }
438
932
  function getRuntimePageIdentity(input) {
439
933
  return {
@@ -453,7 +947,7 @@ function getRuntimePageIdentity(input) {
453
947
  }
454
948
 
455
949
  // src/runtime/performanceHook.ts
456
- import { nanoid as nanoid4 } from "nanoid";
950
+ import { nanoid as nanoid5 } from "nanoid";
457
951
 
458
952
  // src/performance/summary.ts
459
953
  function buildPerformanceSummary(input) {
@@ -627,7 +1121,7 @@ function createPerformanceCollector(deps) {
627
1121
  };
628
1122
  }
629
1123
  function createRecordingId() {
630
- return `performance-${nanoid4()}`;
1124
+ return `performance-${nanoid5()}`;
631
1125
  }
632
1126
  function startSession(state, deps, options) {
633
1127
  const recordingId = createRecordingId();
@@ -1002,78 +1496,6 @@ import {
1002
1496
  } from "@vue/devtools-kit";
1003
1497
  import { createRPCClient } from "vite-dev-rpc";
1004
1498
 
1005
- // src/runtime/domSnapshot.ts
1006
- function createDomSnapshot(root, options) {
1007
- let count = 0;
1008
- function visit(node, depth) {
1009
- if (count >= options.maxNodes || depth > options.maxDepth) {
1010
- return null;
1011
- }
1012
- if (node.nodeType === Node.TEXT_NODE) {
1013
- return createTextSnapshot(node, options, () => {
1014
- count += 1;
1015
- });
1016
- }
1017
- if (!(node instanceof Element)) {
1018
- return null;
1019
- }
1020
- const tag = node.tagName.toLowerCase();
1021
- if (["script", "style", "noscript"].includes(tag)) {
1022
- return null;
1023
- }
1024
- count += 1;
1025
- return createElementSnapshot(node, tag, (child) => visit(child, depth + 1));
1026
- }
1027
- return visit(root, 0) ?? { tag: root.tagName.toLowerCase() };
1028
- }
1029
- function queryDomElements(selector, limit) {
1030
- return Array.from(document.querySelectorAll(selector)).slice(0, limit).map((element) => ({
1031
- tag: element.tagName.toLowerCase(),
1032
- text: element.textContent.trim(),
1033
- attrs: collectAttrs(element),
1034
- rect: serializeRect(element.getBoundingClientRect())
1035
- }));
1036
- }
1037
- function createTextSnapshot(node, options, markVisited) {
1038
- const text = node.textContent?.trim();
1039
- if (!text) {
1040
- return null;
1041
- }
1042
- markVisited();
1043
- return { tag: "#text", text: truncateText(text, options.maxTextLength).text };
1044
- }
1045
- function createElementSnapshot(node, tag, visitChild) {
1046
- const attrs = collectAttrs(node);
1047
- const children = Array.from(node.childNodes).map((child) => visitChild(child)).filter((child) => Boolean(child));
1048
- return {
1049
- tag,
1050
- ...Object.keys(attrs).length ? { attrs } : {},
1051
- ...children.length ? { children } : {}
1052
- };
1053
- }
1054
- function collectAttrs(element) {
1055
- const attrs = {};
1056
- for (const attr of Array.from(element.attributes)) {
1057
- attrs[attr.name] = attr.value;
1058
- }
1059
- if (element instanceof HTMLInputElement && element.type === "password") {
1060
- attrs.value = "[masked]";
1061
- }
1062
- return attrs;
1063
- }
1064
- function serializeRect(rect) {
1065
- return {
1066
- x: rect.x,
1067
- y: rect.y,
1068
- width: rect.width,
1069
- height: rect.height,
1070
- top: rect.top,
1071
- right: rect.right,
1072
- bottom: rect.bottom,
1073
- left: rect.left
1074
- };
1075
- }
1076
-
1077
1499
  // src/runtime/evaluateExpression.ts
1078
1500
  async function evaluateExpression(request) {
1079
1501
  const value = runExpression(request.expression);
@@ -1096,6 +1518,390 @@ function createTimeout(timeoutMs) {
1096
1518
  });
1097
1519
  }
1098
1520
 
1521
+ // src/runtime/storageBridge.ts
1522
+ function createRuntimeStorageBridge(env) {
1523
+ return {
1524
+ async manageStorage(request) {
1525
+ try {
1526
+ return await manageRuntimeStorage(env, request);
1527
+ } catch (error) {
1528
+ return createRuntimeStorageError(
1529
+ request,
1530
+ error instanceof Error ? error.message : String(error)
1531
+ );
1532
+ }
1533
+ }
1534
+ };
1535
+ }
1536
+ async function manageRuntimeStorage(env, request) {
1537
+ if (request.origin !== env.origin) {
1538
+ return createRuntimeStorageError(
1539
+ request,
1540
+ "Runtime storage access is limited to the current page origin"
1541
+ );
1542
+ }
1543
+ if (request.scope === "cookie") {
1544
+ return manageRuntimeCookie(env, request);
1545
+ }
1546
+ if (request.scope === "indexedDB") {
1547
+ return manageRuntimeIndexedDb(env, request);
1548
+ }
1549
+ return manageRuntimeWebStorage(getWebStorage(env, request.scope), request);
1550
+ }
1551
+ function manageRuntimeCookie(env, request) {
1552
+ if (!env.cookie) {
1553
+ return createRuntimeStorageError(
1554
+ request,
1555
+ "Runtime cookie access is unavailable",
1556
+ ["document.cookie is not available in this runtime environment"]
1557
+ );
1558
+ }
1559
+ if (request.action === "list") {
1560
+ return createRuntimeStorageSuccess(request, {
1561
+ origin: request.origin,
1562
+ cookies: readRuntimeCookies(env.cookie)
1563
+ });
1564
+ }
1565
+ if (request.action === "get") {
1566
+ assertCookieName(request);
1567
+ return createRuntimeStorageSuccess(request, {
1568
+ origin: request.origin,
1569
+ cookies: readRuntimeCookies(env.cookie).filter(
1570
+ (cookie) => cookie.name === request.cookie.name
1571
+ )
1572
+ });
1573
+ }
1574
+ if (request.action === "set") {
1575
+ assertCookieName(request);
1576
+ env.cookie.set(createRuntimeCookieWrite(request));
1577
+ return createRuntimeStorageSuccess(request, { ok: true });
1578
+ }
1579
+ if (request.action === "delete") {
1580
+ assertCookieName(request);
1581
+ env.cookie.set(createRuntimeCookieDelete(request));
1582
+ return createRuntimeStorageSuccess(request, {
1583
+ deletedCount: 1,
1584
+ skippedHttpOnlyCount: 0,
1585
+ limitations: ["HttpOnly cookies are invisible to runtime cookie access"]
1586
+ });
1587
+ }
1588
+ const cookies = readRuntimeCookies(env.cookie);
1589
+ for (const cookie of cookies) {
1590
+ env.cookie.set(
1591
+ createRuntimeCookieDelete({
1592
+ ...request,
1593
+ cookie: {
1594
+ name: cookie.name,
1595
+ path: request.cookie?.path
1596
+ }
1597
+ })
1598
+ );
1599
+ }
1600
+ return createRuntimeStorageSuccess(request, {
1601
+ deletedCount: cookies.length,
1602
+ skippedHttpOnlyCount: 0,
1603
+ limitations: ["HttpOnly cookies are invisible to runtime cookie access"]
1604
+ });
1605
+ }
1606
+ function getWebStorage(env, scope) {
1607
+ return scope === "sessionStorage" ? env.sessionStorage : env.localStorage;
1608
+ }
1609
+ function manageRuntimeWebStorage(storage, request) {
1610
+ if (request.action === "list") {
1611
+ return createRuntimeStorageSuccess(request, {
1612
+ origin: request.origin,
1613
+ scope: request.scope,
1614
+ entries: readStorageEntries(storage)
1615
+ });
1616
+ }
1617
+ if (request.action === "get") {
1618
+ assertStorageKey(request);
1619
+ return createRuntimeStorageSuccess(request, {
1620
+ origin: request.origin,
1621
+ scope: request.scope,
1622
+ key: request.key,
1623
+ value: storage.getItem(request.key)
1624
+ });
1625
+ }
1626
+ if (request.action === "set") {
1627
+ assertStorageKey(request);
1628
+ storage.setItem(request.key, request.value ?? "");
1629
+ return createRuntimeStorageSuccess(request, { ok: true });
1630
+ }
1631
+ if (request.action === "delete") {
1632
+ assertStorageKey(request);
1633
+ storage.removeItem(request.key);
1634
+ return createRuntimeStorageSuccess(request, { ok: true });
1635
+ }
1636
+ storage.clear();
1637
+ return createRuntimeStorageSuccess(request, { ok: true });
1638
+ }
1639
+ async function manageRuntimeIndexedDb(env, request) {
1640
+ if (!env.indexedDB?.databases) {
1641
+ return createRuntimeStorageError(request, "IndexedDB metadata API is unavailable");
1642
+ }
1643
+ if (request.action === "list") {
1644
+ const databases = await env.indexedDB.databases();
1645
+ return createRuntimeStorageSuccess(request, {
1646
+ origin: request.origin,
1647
+ scope: request.scope,
1648
+ databases: databases.map((database) => ({
1649
+ name: database.name,
1650
+ version: database.version
1651
+ }))
1652
+ });
1653
+ }
1654
+ assertIndexedDbTarget(request);
1655
+ if ("stores" in env.indexedDB) {
1656
+ return manageMemoryIndexedDb(env.indexedDB, request);
1657
+ }
1658
+ return manageBrowserIndexedDb(env.indexedDB, request);
1659
+ }
1660
+ function manageMemoryIndexedDb(indexedDB, request) {
1661
+ const storeId = `${request.databaseName}:${request.objectStoreName}`;
1662
+ const store = indexedDB.stores.get(storeId) ?? /* @__PURE__ */ new Map();
1663
+ indexedDB.stores.set(storeId, store);
1664
+ if (request.action === "get") {
1665
+ assertStorageKey(request);
1666
+ return createRuntimeStorageSuccess(request, {
1667
+ key: request.key,
1668
+ value: store.get(request.key) ?? null
1669
+ });
1670
+ }
1671
+ if (request.action === "set") {
1672
+ assertStorageKey(request);
1673
+ store.set(request.key, parseIndexedDbValue(request.value));
1674
+ return createRuntimeStorageSuccess(request, { ok: true });
1675
+ }
1676
+ if (request.action === "delete") {
1677
+ assertStorageKey(request);
1678
+ store.delete(request.key);
1679
+ return createRuntimeStorageSuccess(request, { ok: true });
1680
+ }
1681
+ store.clear();
1682
+ return createRuntimeStorageSuccess(request, { ok: true });
1683
+ }
1684
+ async function manageBrowserIndexedDb(indexedDB, request) {
1685
+ const database = await openIndexedDbForRequest(indexedDB, request);
1686
+ try {
1687
+ return await executeIndexedDbTransaction(database, request);
1688
+ } finally {
1689
+ database.close();
1690
+ }
1691
+ }
1692
+ function openIndexedDb(indexedDB, databaseName) {
1693
+ return new Promise((resolve, reject) => {
1694
+ const request = indexedDB.open(databaseName);
1695
+ request.onerror = () => {
1696
+ reject(request.error ?? new Error("IndexedDB open failed"));
1697
+ };
1698
+ request.onsuccess = () => {
1699
+ resolve(request.result);
1700
+ };
1701
+ });
1702
+ }
1703
+ async function openIndexedDbForRequest(indexedDB, request) {
1704
+ const database = await openIndexedDb(indexedDB, request.databaseName);
1705
+ if (request.action !== "set" || database.objectStoreNames.contains(request.objectStoreName)) {
1706
+ return database;
1707
+ }
1708
+ const version = database.version + 1;
1709
+ database.close();
1710
+ return openIndexedDbWithStore(indexedDB, {
1711
+ databaseName: request.databaseName,
1712
+ objectStoreName: request.objectStoreName,
1713
+ version
1714
+ });
1715
+ }
1716
+ function openIndexedDbWithStore(indexedDB, options) {
1717
+ return new Promise((resolve, reject) => {
1718
+ const request = indexedDB.open(options.databaseName, options.version);
1719
+ request.onupgradeneeded = () => {
1720
+ const database = request.result;
1721
+ if (!database.objectStoreNames.contains(options.objectStoreName)) {
1722
+ database.createObjectStore(options.objectStoreName);
1723
+ }
1724
+ };
1725
+ request.onerror = () => {
1726
+ reject(request.error ?? new Error("IndexedDB upgrade failed"));
1727
+ };
1728
+ request.onsuccess = () => {
1729
+ resolve(request.result);
1730
+ };
1731
+ });
1732
+ }
1733
+ function executeIndexedDbTransaction(database, request) {
1734
+ return new Promise((resolve, reject) => {
1735
+ const mode = request.action === "get" ? "readonly" : "readwrite";
1736
+ const transaction = database.transaction(request.objectStoreName, mode);
1737
+ const store = transaction.objectStore(request.objectStoreName);
1738
+ transaction.onerror = () => {
1739
+ reject(transaction.error ?? new Error("IndexedDB transaction failed"));
1740
+ };
1741
+ if (request.action === "get") {
1742
+ assertStorageKey(request);
1743
+ const getRequest = store.get(request.key);
1744
+ getRequest.onerror = () => {
1745
+ reject(getRequest.error ?? new Error("IndexedDB get failed"));
1746
+ };
1747
+ getRequest.onsuccess = () => {
1748
+ const value = getRequest.result ?? null;
1749
+ resolve(
1750
+ createRuntimeStorageSuccess(request, {
1751
+ key: request.key,
1752
+ value
1753
+ })
1754
+ );
1755
+ };
1756
+ return;
1757
+ }
1758
+ if (request.action === "set") {
1759
+ assertStorageKey(request);
1760
+ store.put(parseIndexedDbValue(request.value), request.key);
1761
+ transaction.oncomplete = () => {
1762
+ resolve(createRuntimeStorageSuccess(request, { ok: true }));
1763
+ };
1764
+ return;
1765
+ }
1766
+ if (request.action === "delete") {
1767
+ assertStorageKey(request);
1768
+ store.delete(request.key);
1769
+ transaction.oncomplete = () => {
1770
+ resolve(createRuntimeStorageSuccess(request, { ok: true }));
1771
+ };
1772
+ return;
1773
+ }
1774
+ store.clear();
1775
+ transaction.oncomplete = () => {
1776
+ resolve(createRuntimeStorageSuccess(request, { ok: true }));
1777
+ };
1778
+ });
1779
+ }
1780
+ function readStorageEntries(storage) {
1781
+ const entries = [];
1782
+ for (let index = 0; index < storage.length; index += 1) {
1783
+ const key = storage.key(index);
1784
+ if (!key) {
1785
+ continue;
1786
+ }
1787
+ const value = storage.getItem(key);
1788
+ if (value === null) {
1789
+ continue;
1790
+ }
1791
+ entries.push({ key, value });
1792
+ }
1793
+ return entries;
1794
+ }
1795
+ function readRuntimeCookies(cookieAccess) {
1796
+ return cookieAccess.get().split(";").map((item) => item.trim()).filter(Boolean).map((item) => {
1797
+ const separatorIndex = item.indexOf("=");
1798
+ if (separatorIndex === -1) {
1799
+ return { name: decodeCookiePart(item), value: "" };
1800
+ }
1801
+ return {
1802
+ name: decodeCookiePart(item.slice(0, separatorIndex)),
1803
+ value: decodeCookiePart(item.slice(separatorIndex + 1))
1804
+ };
1805
+ });
1806
+ }
1807
+ function createRuntimeCookieWrite(request) {
1808
+ const value = request.cookie.value ?? request.value ?? "";
1809
+ const parts = [
1810
+ `${encodeURIComponent(request.cookie.name)}=${encodeURIComponent(value)}`
1811
+ ];
1812
+ appendRuntimeCookieAttributes(parts, request.cookie);
1813
+ return parts.join("; ");
1814
+ }
1815
+ function createRuntimeCookieDelete(request) {
1816
+ const parts = [
1817
+ `${encodeURIComponent(request.cookie.name)}=`,
1818
+ "Expires=Thu, 01 Jan 1970 00:00:00 GMT",
1819
+ "Max-Age=0"
1820
+ ];
1821
+ appendRuntimeCookieAttributes(parts, {
1822
+ path: request.cookie.path,
1823
+ domain: request.cookie.domain
1824
+ });
1825
+ return parts.join("; ");
1826
+ }
1827
+ function appendRuntimeCookieAttributes(parts, cookie) {
1828
+ if (cookie.path) {
1829
+ parts.push(`Path=${cookie.path}`);
1830
+ }
1831
+ if (cookie.domain) {
1832
+ parts.push(`Domain=${cookie.domain}`);
1833
+ }
1834
+ if (cookie.expires !== void 0) {
1835
+ parts.push(`Expires=${new Date(cookie.expires * 1e3).toUTCString()}`);
1836
+ }
1837
+ if (cookie.sameSite) {
1838
+ parts.push(`SameSite=${normalizeRuntimeSameSite(cookie.sameSite)}`);
1839
+ }
1840
+ if (cookie.secure) {
1841
+ parts.push("Secure");
1842
+ }
1843
+ }
1844
+ function normalizeRuntimeSameSite(sameSite) {
1845
+ if (sameSite === "strict") {
1846
+ return "Strict";
1847
+ }
1848
+ if (sameSite === "lax") {
1849
+ return "Lax";
1850
+ }
1851
+ return "None";
1852
+ }
1853
+ function decodeCookiePart(value) {
1854
+ try {
1855
+ return decodeURIComponent(value);
1856
+ } catch {
1857
+ return value;
1858
+ }
1859
+ }
1860
+ function assertStorageKey(request) {
1861
+ if (!request.key) {
1862
+ throw new Error("Storage key is required for this operation");
1863
+ }
1864
+ }
1865
+ function assertCookieName(request) {
1866
+ if (!request.cookie?.name) {
1867
+ throw new Error("Cookie name is required for this operation");
1868
+ }
1869
+ }
1870
+ function assertIndexedDbTarget(request) {
1871
+ if (!request.databaseName || !request.objectStoreName) {
1872
+ throw new Error("IndexedDB databaseName and objectStoreName are required");
1873
+ }
1874
+ }
1875
+ function parseIndexedDbValue(value) {
1876
+ if (value === void 0) {
1877
+ return null;
1878
+ }
1879
+ try {
1880
+ return JSON.parse(value);
1881
+ } catch {
1882
+ return value;
1883
+ }
1884
+ }
1885
+ function createRuntimeStorageSuccess(request, data) {
1886
+ return {
1887
+ ok: true,
1888
+ source: "hook",
1889
+ action: request.action,
1890
+ scope: request.scope,
1891
+ data
1892
+ };
1893
+ }
1894
+ function createRuntimeStorageError(request, error, limitations) {
1895
+ return {
1896
+ ok: false,
1897
+ source: "hook",
1898
+ action: request.action,
1899
+ scope: request.scope,
1900
+ error,
1901
+ limitations
1902
+ };
1903
+ }
1904
+
1099
1905
  // src/runtime/devtoolsBridge.ts
1100
1906
  function createRuntimeDevtoolsRpc(getRpc) {
1101
1907
  return {
@@ -1117,6 +1923,22 @@ function createRuntimeDevtoolsRpc(getRpc) {
1117
1923
  );
1118
1924
  },
1119
1925
  onDomQueryUpdated: () => void 0,
1926
+ getElementContext(options) {
1927
+ try {
1928
+ getRpc().onElementContextUpdated(
1929
+ options.event,
1930
+ getElementContextResolver().getElementContext(options.elementId)
1931
+ );
1932
+ } catch (error) {
1933
+ getRpc().onElementContextUpdated(options.event, {
1934
+ ok: false,
1935
+ elementId: options.elementId,
1936
+ error: error instanceof Error ? error.message : String(error),
1937
+ limitations: ["runtime element context lookup failed"]
1938
+ });
1939
+ }
1940
+ },
1941
+ onElementContextUpdated: () => void 0,
1120
1942
  reloadPage(options) {
1121
1943
  getRpc().onPageReloaded(options.event, { ok: true, source: "hook" });
1122
1944
  setTimeout(() => {
@@ -1151,7 +1973,26 @@ function createRuntimeDevtoolsRpc(getRpc) {
1151
1973
  });
1152
1974
  }
1153
1975
  },
1154
- onScreenshotTaken: () => void 0
1976
+ onScreenshotTaken: () => void 0,
1977
+ async manageStorage(options) {
1978
+ const bridge = createRuntimeStorageBridge({
1979
+ origin: window.location.origin,
1980
+ localStorage: window.localStorage,
1981
+ sessionStorage: window.sessionStorage,
1982
+ indexedDB: window.indexedDB,
1983
+ cookie: {
1984
+ get: () => window.document.cookie,
1985
+ set: (value) => {
1986
+ window.document.cookie = value;
1987
+ }
1988
+ }
1989
+ });
1990
+ getRpc().onStorageUpdated(
1991
+ options.event,
1992
+ await bridge.manageStorage(options)
1993
+ );
1994
+ },
1995
+ onStorageUpdated: () => void 0
1155
1996
  };
1156
1997
  }
1157
1998
 
@@ -1434,13 +2275,16 @@ function createMissingComponentError(componentName) {
1434
2275
  }
1435
2276
 
1436
2277
  // src/runtime/client.ts
1437
- async function startRuntimeClient() {
2278
+ async function startRuntimeClient(runtimeOptions = {
2279
+ elementPicker: DEFAULT_OPTIONS.elementPicker
2280
+ }) {
1438
2281
  initializeVueDevtoolsHook();
1439
2282
  const hot = await createHotContext("vite-plugin-vue-mcp-next", "/");
1440
2283
  if (!hot) {
1441
2284
  return;
1442
2285
  }
1443
2286
  installVueBridge(hot);
2287
+ installElementPicker(runtimeOptions.elementPicker);
1444
2288
  const identity = getRuntimePageIdentity({
1445
2289
  href: window.location.href,
1446
2290
  title: document.title,
@@ -1449,6 +2293,15 @@ async function startRuntimeClient() {
1449
2293
  innerHeight: window.innerHeight,
1450
2294
  readyState: document.readyState
1451
2295
  });
2296
+ setElementContextResolver(
2297
+ createElementContextResolver({
2298
+ root: runtimeOptions.projectRoot ?? "/",
2299
+ registry: runtimeElementRegistry,
2300
+ querySelector(selector) {
2301
+ return document.querySelector(selector);
2302
+ }
2303
+ })
2304
+ );
1452
2305
  hot.send(RUNTIME_PAGE_CONNECTED_EVENT, identity);
1453
2306
  installRuntimePageLifecycle({
1454
2307
  pageId: identity.pageId,