@xiaou66/vite-plugin-vue-mcp-next 0.0.5 → 0.0.7

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.
@@ -1,3 +1,39 @@
1
+ /**
2
+ * 浏览器端 snapdom 截图降级能力。
3
+ *
4
+ * 该文件只在页面 runtime 中执行,适用于没有 CDP 但仍希望让 MCP 客户端看到页面视觉结果的场景;
5
+ * 函数型扩展通过 Vite import 路径加载,避免 Node 侧配置直接跨运行时传递函数。
6
+ */
7
+
8
+ /**
9
+ * snapdom 函数边界。
10
+ *
11
+ * 只声明当前截图流程需要的最小能力,可以让本插件不绑定 snapdom 的完整内部类型。
12
+ */
13
+ type SnapdomFunction = (element: Element, options: Record<string, unknown>) => Promise<{
14
+ toBlob: (options?: Record<string, unknown>) => Promise<Blob>;
15
+ }>;
16
+ /**
17
+ * snapdom 加载器。
18
+ *
19
+ * 该函数由 Vite 虚拟模块注册,避免发布包 runtime 直接 import optional peer 后触发 optimizeDeps 失败。
20
+ */
21
+ type SnapdomLoader = () => Promise<{
22
+ snapdom: SnapdomFunction;
23
+ }>;
24
+ /**
25
+ * 注册截图扩展模块。
26
+ *
27
+ * Vite import 路径必须在宿主项目的 Vite 模块图里解析,适用于插件、filter、fallbackURL 需要使用 alias 或源码转换的场景。
28
+ */
29
+ declare function setScreenshotModuleRegistry(registry: Record<string, Record<string, unknown>>): void;
30
+ /**
31
+ * 注册 snapdom 加载器。
32
+ *
33
+ * 只有宿主 Vite 项目可以正确解析 optional peer,因此由虚拟 runtime 入口在浏览器侧注册。
34
+ */
35
+ declare function setSnapdomLoader(loader: SnapdomLoader): void;
36
+
1
37
  /**
2
38
  * 运行时脚本执行请求。
3
39
  *
@@ -27,4 +63,4 @@ declare function evaluateExpression(request: RuntimeEvaluateRequest): Promise<un
27
63
  */
28
64
  declare function startRuntimeClient(): Promise<void>;
29
65
 
30
- export { type RuntimeEvaluateRequest, evaluateExpression, startRuntimeClient };
66
+ export { type RuntimeEvaluateRequest, evaluateExpression, setScreenshotModuleRegistry, setSnapdomLoader, startRuntimeClient };
@@ -1,3 +1,39 @@
1
+ /**
2
+ * 浏览器端 snapdom 截图降级能力。
3
+ *
4
+ * 该文件只在页面 runtime 中执行,适用于没有 CDP 但仍希望让 MCP 客户端看到页面视觉结果的场景;
5
+ * 函数型扩展通过 Vite import 路径加载,避免 Node 侧配置直接跨运行时传递函数。
6
+ */
7
+
8
+ /**
9
+ * snapdom 函数边界。
10
+ *
11
+ * 只声明当前截图流程需要的最小能力,可以让本插件不绑定 snapdom 的完整内部类型。
12
+ */
13
+ type SnapdomFunction = (element: Element, options: Record<string, unknown>) => Promise<{
14
+ toBlob: (options?: Record<string, unknown>) => Promise<Blob>;
15
+ }>;
16
+ /**
17
+ * snapdom 加载器。
18
+ *
19
+ * 该函数由 Vite 虚拟模块注册,避免发布包 runtime 直接 import optional peer 后触发 optimizeDeps 失败。
20
+ */
21
+ type SnapdomLoader = () => Promise<{
22
+ snapdom: SnapdomFunction;
23
+ }>;
24
+ /**
25
+ * 注册截图扩展模块。
26
+ *
27
+ * Vite import 路径必须在宿主项目的 Vite 模块图里解析,适用于插件、filter、fallbackURL 需要使用 alias 或源码转换的场景。
28
+ */
29
+ declare function setScreenshotModuleRegistry(registry: Record<string, Record<string, unknown>>): void;
30
+ /**
31
+ * 注册 snapdom 加载器。
32
+ *
33
+ * 只有宿主 Vite 项目可以正确解析 optional peer,因此由虚拟 runtime 入口在浏览器侧注册。
34
+ */
35
+ declare function setSnapdomLoader(loader: SnapdomLoader): void;
36
+
1
37
  /**
2
38
  * 运行时脚本执行请求。
3
39
  *
@@ -27,4 +63,4 @@ declare function evaluateExpression(request: RuntimeEvaluateRequest): Promise<un
27
63
  */
28
64
  declare function startRuntimeClient(): Promise<void>;
29
65
 
30
- export { type RuntimeEvaluateRequest, evaluateExpression, startRuntimeClient };
66
+ export { type RuntimeEvaluateRequest, evaluateExpression, setScreenshotModuleRegistry, setSnapdomLoader, startRuntimeClient };
@@ -300,119 +300,32 @@ function getRuntimePageIdentity(input) {
300
300
  };
301
301
  }
302
302
 
303
- // src/runtime/vueBridge.ts
304
- import {
305
- devtools,
306
- devtoolsRouterInfo,
307
- devtoolsState,
308
- getInspector,
309
- stringify,
310
- toggleHighPerfMode
311
- } from "@vue/devtools-kit";
312
- import { createRPCClient } from "vite-dev-rpc";
313
-
314
- // src/runtime/domSnapshot.ts
315
- function createDomSnapshot(root, options) {
316
- let count = 0;
317
- function visit(node, depth) {
318
- if (count >= options.maxNodes || depth > options.maxDepth) {
319
- return null;
320
- }
321
- if (node.nodeType === Node.TEXT_NODE) {
322
- return createTextSnapshot(node, options, () => {
323
- count += 1;
324
- });
325
- }
326
- if (!(node instanceof Element)) {
327
- return null;
328
- }
329
- const tag = node.tagName.toLowerCase();
330
- if (["script", "style", "noscript"].includes(tag)) {
331
- return null;
332
- }
333
- count += 1;
334
- return createElementSnapshot(node, tag, (child) => visit(child, depth + 1));
335
- }
336
- return visit(root, 0) ?? { tag: root.tagName.toLowerCase() };
337
- }
338
- function queryDomElements(selector, limit) {
339
- return Array.from(document.querySelectorAll(selector)).slice(0, limit).map((element) => ({
340
- tag: element.tagName.toLowerCase(),
341
- text: element.textContent.trim(),
342
- attrs: collectAttrs(element),
343
- rect: serializeRect(element.getBoundingClientRect())
344
- }));
345
- }
346
- function createTextSnapshot(node, options, markVisited) {
347
- const text = node.textContent?.trim();
348
- if (!text) {
349
- return null;
350
- }
351
- markVisited();
352
- return { tag: "#text", text: truncateText(text, options.maxTextLength).text };
353
- }
354
- function createElementSnapshot(node, tag, visitChild) {
355
- const attrs = collectAttrs(node);
356
- const children = Array.from(node.childNodes).map((child) => visitChild(child)).filter((child) => Boolean(child));
357
- return {
358
- tag,
359
- ...Object.keys(attrs).length ? { attrs } : {},
360
- ...children.length ? { children } : {}
361
- };
362
- }
363
- function collectAttrs(element) {
364
- const attrs = {};
365
- for (const attr of Array.from(element.attributes)) {
366
- attrs[attr.name] = attr.value;
367
- }
368
- if (element instanceof HTMLInputElement && element.type === "password") {
369
- attrs.value = "[masked]";
370
- }
371
- return attrs;
372
- }
373
- function serializeRect(rect) {
374
- return {
375
- x: rect.x,
376
- y: rect.y,
377
- width: rect.width,
378
- height: rect.height,
379
- top: rect.top,
380
- right: rect.right,
381
- bottom: rect.bottom,
382
- left: rect.left
383
- };
384
- }
385
-
386
- // src/runtime/evaluateExpression.ts
387
- async function evaluateExpression(request) {
388
- const value = runExpression(request.expression);
389
- const result = request.awaitPromise === false ? value : await Promise.resolve(value);
390
- return Promise.race([
391
- Promise.resolve(result),
392
- createTimeout(request.timeoutMs)
393
- ]);
303
+ // src/runtime/screenshot.ts
304
+ var snapdomLoader = () => Promise.reject(createMissingSnapdomError());
305
+ var screenshotModuleRegistry = {};
306
+ function setScreenshotModuleRegistry(registry) {
307
+ screenshotModuleRegistry = registry;
394
308
  }
395
- function runExpression(expression) {
396
- return new Function(`return (${expression})`)();
309
+ function setSnapdomLoader(loader) {
310
+ snapdomLoader = loader;
397
311
  }
398
- function createTimeout(timeoutMs) {
399
- return new Promise((_, reject) => {
400
- window.setTimeout(() => {
401
- reject(
402
- new Error(`evaluate_script timed out after ${String(timeoutMs)}ms`)
403
- );
404
- }, timeoutMs);
405
- });
406
- }
407
-
408
- // src/runtime/screenshot.ts
409
312
  async function takeRuntimeScreenshot(options) {
410
313
  const target = resolveScreenshotTarget(options.target, options.selector);
411
314
  if (!target.ok) {
412
315
  return target;
413
316
  }
414
317
  const loadSnapdom = options.loadSnapdom ?? loadDefaultSnapdom;
415
- const { snapdom } = await loadSnapdom();
318
+ let loaded;
319
+ try {
320
+ loaded = await loadSnapdom();
321
+ } catch (error) {
322
+ return {
323
+ ok: false,
324
+ source: "snapdom",
325
+ error: error instanceof Error ? error.message : String(error)
326
+ };
327
+ }
328
+ const { snapdom } = loaded;
416
329
  const snapdomOptions = await createSnapdomOptions(options);
417
330
  const capture = await snapdom(target.element, snapdomOptions);
418
331
  const blob = await capture.toBlob({
@@ -435,8 +348,8 @@ async function takeRuntimeScreenshot(options) {
435
348
  ]
436
349
  };
437
350
  }
438
- async function loadDefaultSnapdom() {
439
- return import("@zumer/snapdom");
351
+ function loadDefaultSnapdom() {
352
+ return snapdomLoader();
440
353
  }
441
354
  function resolveScreenshotTarget(target, selector) {
442
355
  if (target === "element" && !selector) {
@@ -483,14 +396,12 @@ async function loadSnapdomPlugins(options) {
483
396
  function createModuleLoader(options) {
484
397
  return options.loadModule ?? loadConfiguredModule;
485
398
  }
486
- async function loadConfiguredModule(path) {
487
- const config = await import("virtual:vite-plugin-vue-mcp-next/screenshot-config");
488
- const registry = config.screenshotModuleRegistry;
489
- const mod = registry[path];
399
+ function loadConfiguredModule(path) {
400
+ const mod = screenshotModuleRegistry[path];
490
401
  if (!mod) {
491
402
  throw new Error(`screenshot module is not registered: ${path}`);
492
403
  }
493
- return mod;
404
+ return Promise.resolve(mod);
494
405
  }
495
406
  async function loadPluginImport(plugin, loadModule) {
496
407
  const normalized = typeof plugin === "string" ? { path: plugin, exportName: "default" } : { exportName: "default", ...plugin };
@@ -523,6 +434,116 @@ function removeUndefinedEntries(value) {
523
434
  Object.entries(value).filter(([, item]) => item !== void 0)
524
435
  );
525
436
  }
437
+ function createMissingSnapdomError() {
438
+ return new Error(
439
+ "\u7F3A\u5C11\u53EF\u9009\u4F9D\u8D56 @zumer/snapdom\u3002DOM \u622A\u56FE\u964D\u7EA7\u9700\u8981\u8BE5\u4F9D\u8D56\uFF0C\u8BF7\u6267\u884C\uFF1Apnpm add -D @zumer/snapdom"
440
+ );
441
+ }
442
+
443
+ // src/runtime/vueBridge.ts
444
+ import {
445
+ devtools,
446
+ devtoolsRouterInfo,
447
+ devtoolsState,
448
+ getInspector,
449
+ stringify,
450
+ toggleHighPerfMode
451
+ } from "@vue/devtools-kit";
452
+ import { createRPCClient } from "vite-dev-rpc";
453
+
454
+ // src/runtime/domSnapshot.ts
455
+ function createDomSnapshot(root, options) {
456
+ let count = 0;
457
+ function visit(node, depth) {
458
+ if (count >= options.maxNodes || depth > options.maxDepth) {
459
+ return null;
460
+ }
461
+ if (node.nodeType === Node.TEXT_NODE) {
462
+ return createTextSnapshot(node, options, () => {
463
+ count += 1;
464
+ });
465
+ }
466
+ if (!(node instanceof Element)) {
467
+ return null;
468
+ }
469
+ const tag = node.tagName.toLowerCase();
470
+ if (["script", "style", "noscript"].includes(tag)) {
471
+ return null;
472
+ }
473
+ count += 1;
474
+ return createElementSnapshot(node, tag, (child) => visit(child, depth + 1));
475
+ }
476
+ return visit(root, 0) ?? { tag: root.tagName.toLowerCase() };
477
+ }
478
+ function queryDomElements(selector, limit) {
479
+ return Array.from(document.querySelectorAll(selector)).slice(0, limit).map((element) => ({
480
+ tag: element.tagName.toLowerCase(),
481
+ text: element.textContent.trim(),
482
+ attrs: collectAttrs(element),
483
+ rect: serializeRect(element.getBoundingClientRect())
484
+ }));
485
+ }
486
+ function createTextSnapshot(node, options, markVisited) {
487
+ const text = node.textContent?.trim();
488
+ if (!text) {
489
+ return null;
490
+ }
491
+ markVisited();
492
+ return { tag: "#text", text: truncateText(text, options.maxTextLength).text };
493
+ }
494
+ function createElementSnapshot(node, tag, visitChild) {
495
+ const attrs = collectAttrs(node);
496
+ const children = Array.from(node.childNodes).map((child) => visitChild(child)).filter((child) => Boolean(child));
497
+ return {
498
+ tag,
499
+ ...Object.keys(attrs).length ? { attrs } : {},
500
+ ...children.length ? { children } : {}
501
+ };
502
+ }
503
+ function collectAttrs(element) {
504
+ const attrs = {};
505
+ for (const attr of Array.from(element.attributes)) {
506
+ attrs[attr.name] = attr.value;
507
+ }
508
+ if (element instanceof HTMLInputElement && element.type === "password") {
509
+ attrs.value = "[masked]";
510
+ }
511
+ return attrs;
512
+ }
513
+ function serializeRect(rect) {
514
+ return {
515
+ x: rect.x,
516
+ y: rect.y,
517
+ width: rect.width,
518
+ height: rect.height,
519
+ top: rect.top,
520
+ right: rect.right,
521
+ bottom: rect.bottom,
522
+ left: rect.left
523
+ };
524
+ }
525
+
526
+ // src/runtime/evaluateExpression.ts
527
+ async function evaluateExpression(request) {
528
+ const value = runExpression(request.expression);
529
+ const result = request.awaitPromise === false ? value : await Promise.resolve(value);
530
+ return Promise.race([
531
+ Promise.resolve(result),
532
+ createTimeout(request.timeoutMs)
533
+ ]);
534
+ }
535
+ function runExpression(expression) {
536
+ return new Function(`return (${expression})`)();
537
+ }
538
+ function createTimeout(timeoutMs) {
539
+ return new Promise((_, reject) => {
540
+ window.setTimeout(() => {
541
+ reject(
542
+ new Error(`evaluate_script timed out after ${String(timeoutMs)}ms`)
543
+ );
544
+ }, timeoutMs);
545
+ });
546
+ }
526
547
 
527
548
  // src/runtime/devtoolsBridge.ts
528
549
  function createRuntimeDevtoolsRpc(getRpc) {
@@ -545,6 +566,13 @@ function createRuntimeDevtoolsRpc(getRpc) {
545
566
  );
546
567
  },
547
568
  onDomQueryUpdated: () => void 0,
569
+ reloadPage(options) {
570
+ getRpc().onPageReloaded(options.event, { ok: true, source: "hook" });
571
+ setTimeout(() => {
572
+ window.location.reload();
573
+ }, 0);
574
+ },
575
+ onPageReloaded: () => void 0,
548
576
  async evaluateScript(options) {
549
577
  try {
550
578
  getRpc().onEvaluateScriptUpdated(options.event, {
@@ -800,6 +828,8 @@ async function startRuntimeClient() {
800
828
  }
801
829
  export {
802
830
  evaluateExpression,
831
+ setScreenshotModuleRegistry,
832
+ setSnapdomLoader,
803
833
  startRuntimeClient
804
834
  };
805
835
  //# sourceMappingURL=client.js.map