@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.
- package/README.md +140 -92
- package/dist/index.cjs +279 -39
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +273 -33
- package/dist/index.js.map +1 -1
- package/dist/runtime/client.cjs +137 -115
- package/dist/runtime/client.cjs.map +1 -1
- package/dist/runtime/client.d.cts +37 -1
- package/dist/runtime/client.d.ts +37 -1
- package/dist/runtime/client.js +142 -112
- package/dist/runtime/client.js.map +1 -1
- package/package.json +8 -2
|
@@ -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 };
|
package/dist/runtime/client.d.ts
CHANGED
|
@@ -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 };
|
package/dist/runtime/client.js
CHANGED
|
@@ -300,119 +300,32 @@ function getRuntimePageIdentity(input) {
|
|
|
300
300
|
};
|
|
301
301
|
}
|
|
302
302
|
|
|
303
|
-
// src/runtime/
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
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
|
|
396
|
-
|
|
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
|
-
|
|
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
|
-
|
|
439
|
-
return
|
|
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
|
-
|
|
487
|
-
const
|
|
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
|