ghost-bridge 0.2.3 → 0.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.
- package/dist/cli.js +5 -4
- package/dist/server.js +24 -2
- package/extension/background.js +152 -0
- package/extension/manifest.json +1 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -5453,12 +5453,13 @@ var require_lib = __commonJS({
|
|
|
5453
5453
|
// lib/utils.js
|
|
5454
5454
|
import path from "path";
|
|
5455
5455
|
import os2 from "os";
|
|
5456
|
+
import { fileURLToPath } from "url";
|
|
5456
5457
|
function getClaudeConfigPath() {
|
|
5457
5458
|
const homeDir = os2.homedir();
|
|
5458
5459
|
return path.join(homeDir, ".claude.json");
|
|
5459
5460
|
}
|
|
5460
5461
|
function getExtensionPath() {
|
|
5461
|
-
const __filename2 =
|
|
5462
|
+
const __filename2 = fileURLToPath(import.meta.url);
|
|
5462
5463
|
const currentDir = path.dirname(__filename2);
|
|
5463
5464
|
if (currentDir.endsWith("/dist") || currentDir.endsWith("\\dist")) {
|
|
5464
5465
|
return path.resolve(currentDir, "../extension");
|
|
@@ -5466,7 +5467,7 @@ function getExtensionPath() {
|
|
|
5466
5467
|
return path.resolve(currentDir, "../extension");
|
|
5467
5468
|
}
|
|
5468
5469
|
function getServerPath() {
|
|
5469
|
-
const __filename2 =
|
|
5470
|
+
const __filename2 = fileURLToPath(import.meta.url);
|
|
5470
5471
|
const currentDir = path.dirname(__filename2);
|
|
5471
5472
|
if (currentDir.endsWith("/dist") || currentDir.endsWith("\\dist")) {
|
|
5472
5473
|
return path.resolve(currentDir, "server.js");
|
|
@@ -5679,10 +5680,10 @@ var {
|
|
|
5679
5680
|
|
|
5680
5681
|
// bin/ghost-bridge.js
|
|
5681
5682
|
init_source();
|
|
5682
|
-
import { fileURLToPath } from "url";
|
|
5683
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5683
5684
|
import path3 from "path";
|
|
5684
5685
|
import fs4 from "fs";
|
|
5685
|
-
var __filename =
|
|
5686
|
+
var __filename = fileURLToPath2(import.meta.url);
|
|
5686
5687
|
var __dirname = path3.dirname(__filename);
|
|
5687
5688
|
var packageJsonParams = JSON.parse(
|
|
5688
5689
|
fs4.readFileSync(path3.join(__dirname, "../package.json"), "utf-8")
|
package/dist/server.js
CHANGED
|
@@ -28896,7 +28896,7 @@ function buildSnippet(source, line, column, { beautifyEnabled = true, contextLin
|
|
|
28896
28896
|
return result;
|
|
28897
28897
|
}
|
|
28898
28898
|
var server = new Server(
|
|
28899
|
-
{ name: "ghost-bridge", version: "0.
|
|
28899
|
+
{ name: "ghost-bridge", version: "0.3.0" },
|
|
28900
28900
|
{ capabilities: { tools: {} } }
|
|
28901
28901
|
);
|
|
28902
28902
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
@@ -28993,6 +28993,23 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
28993
28993
|
description: "\u6E05\u7A7A\u5DF2\u6355\u83B7\u7684\u7F51\u7EDC\u8BF7\u6C42\u8BB0\u5F55",
|
|
28994
28994
|
inputSchema: { type: "object", properties: {} }
|
|
28995
28995
|
},
|
|
28996
|
+
{
|
|
28997
|
+
name: "perf_metrics",
|
|
28998
|
+
description: "\u83B7\u53D6\u9875\u9762\u6027\u80FD\u6307\u6807\uFF1A\u5305\u542B\u5F15\u64CE\u7EA7\u6307\u6807\uFF08JS\u5806\u5185\u5B58\u3001DOM\u8282\u70B9\u6570\u3001Layout\u6B21\u6570\u3001\u811A\u672C\u6267\u884C\u65F6\u95F4\uFF09\u3001Web Vitals\uFF08FCP\u3001TTFB\u3001DOMContentLoaded\u3001Long Tasks\uFF09\u548C\u8D44\u6E90\u52A0\u8F7D\u6458\u8981\u3002\u7528\u4E8E\u8BCA\u65AD\u9875\u9762\u5361\u987F\u3001\u5185\u5B58\u5360\u7528\u8FC7\u9AD8\u3001\u52A0\u8F7D\u7F13\u6162\u7B49\u6027\u80FD\u95EE\u9898\u3002",
|
|
28999
|
+
inputSchema: {
|
|
29000
|
+
type: "object",
|
|
29001
|
+
properties: {
|
|
29002
|
+
includeTimings: {
|
|
29003
|
+
type: "boolean",
|
|
29004
|
+
description: "\u662F\u5426\u5305\u542B Navigation Timing \u548C Web Vitals\uFF0C\u9ED8\u8BA4 true"
|
|
29005
|
+
},
|
|
29006
|
+
includeResources: {
|
|
29007
|
+
type: "boolean",
|
|
29008
|
+
description: "\u662F\u5426\u5305\u542B\u8D44\u6E90\u52A0\u8F7D\u6458\u8981\uFF08\u6309\u7C7B\u578B\u7EDF\u8BA1\u3001\u6700\u6162\u8D44\u6E90\uFF09\uFF0C\u9ED8\u8BA4 true"
|
|
29009
|
+
}
|
|
29010
|
+
}
|
|
29011
|
+
}
|
|
29012
|
+
},
|
|
28996
29013
|
{
|
|
28997
29014
|
name: "capture_screenshot",
|
|
28998
29015
|
description: "\u3010\u63A8\u8350\u7528\u4E8E\u89C6\u89C9\u5206\u6790\u3011\u622A\u53D6\u5F53\u524D\u9875\u9762\u7684\u622A\u56FE\uFF0C\u8FD4\u56DE base64 \u56FE\u7247\u3002\u9002\u7528\u4E8E\uFF1A1) \u67E5\u770B\u9875\u9762\u5B9E\u9645\u89C6\u89C9\u6548\u679C 2) \u6392\u67E5 UI/\u6837\u5F0F/\u5E03\u5C40/\u989C\u8272\u95EE\u9898 3) \u9A8C\u8BC1\u9875\u9762\u6E32\u67D3 4) \u5206\u6790\u5143\u7D20\u4F4D\u7F6E\u548C\u95F4\u8DDD 5) \u67E5\u770B\u56FE\u7247/\u56FE\u6807\u7B49\u89C6\u89C9\u5185\u5BB9\u3002\u5F53\u9700\u8981\u770B\u5230\u9875\u9762\u300C\u957F\u4EC0\u4E48\u6837\u300D\u65F6\u4F7F\u7528\u6B64\u5DE5\u5177\u3002\u5982\u4EC5\u9700\u6587\u672C/\u94FE\u63A5\u7B49\u4FE1\u606F\uFF0C\u5EFA\u8BAE\u4F7F\u7528\u66F4\u5FEB\u7684 get_page_content\u3002",
|
|
@@ -29078,7 +29095,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
29078
29095
|
type: "text",
|
|
29079
29096
|
text: jsonText({
|
|
29080
29097
|
service: "ghost-bridge",
|
|
29081
|
-
version: "0.
|
|
29098
|
+
version: "0.3.0",
|
|
29082
29099
|
role: isMainInstance ? "\u4E3B\u5B9E\u4F8B (WebSocket Server)" : "\u5BA2\u6237\u7AEF (\u8FDE\u63A5\u5230\u4E3B\u5B9E\u4F8B)",
|
|
29083
29100
|
wsPort: actualPort,
|
|
29084
29101
|
wsUrl: `ws://localhost:${actualPort}`,
|
|
@@ -29163,6 +29180,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
29163
29180
|
const res = await askChrome("clearNetworkRequests");
|
|
29164
29181
|
return { content: [{ type: "text", text: jsonText(res) }] };
|
|
29165
29182
|
}
|
|
29183
|
+
if (name === "perf_metrics") {
|
|
29184
|
+
const { includeTimings, includeResources } = args;
|
|
29185
|
+
const res = await askChrome("perfMetrics", { includeTimings, includeResources });
|
|
29186
|
+
return { content: [{ type: "text", text: jsonText(res) }] };
|
|
29187
|
+
}
|
|
29166
29188
|
if (name === "capture_screenshot") {
|
|
29167
29189
|
const { format, quality, fullPage, clip } = args;
|
|
29168
29190
|
const res = await askChrome("captureScreenshot", { format, quality, fullPage, clip }, { timeoutMs: 15e3 });
|
package/extension/background.js
CHANGED
|
@@ -590,6 +590,157 @@ async function handleClearNetworkRequests() {
|
|
|
590
590
|
return { cleared: count }
|
|
591
591
|
}
|
|
592
592
|
|
|
593
|
+
async function handlePerfMetrics(params = {}) {
|
|
594
|
+
const target = await ensureAttached()
|
|
595
|
+
const { includeResources = true, includeTimings = true } = params
|
|
596
|
+
|
|
597
|
+
// 1. CDP Performance.getMetrics — 底层引擎指标
|
|
598
|
+
await chrome.debugger.sendCommand(target, "Performance.enable")
|
|
599
|
+
const { metrics } = await chrome.debugger.sendCommand(target, "Performance.getMetrics")
|
|
600
|
+
await chrome.debugger.sendCommand(target, "Performance.disable")
|
|
601
|
+
|
|
602
|
+
// 整理为可读的分组
|
|
603
|
+
const metricsMap = {}
|
|
604
|
+
for (const m of metrics) {
|
|
605
|
+
metricsMap[m.name] = m.value
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
const engineMetrics = {
|
|
609
|
+
memory: {
|
|
610
|
+
jsHeapUsedSize: formatBytes(metricsMap.JSHeapUsedSize),
|
|
611
|
+
jsHeapTotalSize: formatBytes(metricsMap.JSHeapTotalSize),
|
|
612
|
+
usagePercent: metricsMap.JSHeapTotalSize
|
|
613
|
+
? Math.round((metricsMap.JSHeapUsedSize / metricsMap.JSHeapTotalSize) * 100) + "%"
|
|
614
|
+
: "N/A",
|
|
615
|
+
},
|
|
616
|
+
dom: {
|
|
617
|
+
nodes: metricsMap.Nodes,
|
|
618
|
+
documents: metricsMap.Documents,
|
|
619
|
+
frames: metricsMap.Frames,
|
|
620
|
+
jsEventListeners: metricsMap.JSEventListeners,
|
|
621
|
+
},
|
|
622
|
+
layout: {
|
|
623
|
+
layoutCount: metricsMap.LayoutCount,
|
|
624
|
+
recalcStyleCount: metricsMap.RecalcStyleCount,
|
|
625
|
+
layoutDuration: roundMs(metricsMap.LayoutDuration),
|
|
626
|
+
recalcStyleDuration: roundMs(metricsMap.RecalcStyleDuration),
|
|
627
|
+
},
|
|
628
|
+
tasks: {
|
|
629
|
+
scriptDuration: roundMs(metricsMap.ScriptDuration),
|
|
630
|
+
taskDuration: roundMs(metricsMap.TaskDuration),
|
|
631
|
+
taskOtherDuration: roundMs(metricsMap.TaskOtherDuration),
|
|
632
|
+
},
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
const result = { engineMetrics }
|
|
636
|
+
|
|
637
|
+
// 2. Web Vitals + Navigation Timing(通过 Runtime.evaluate)
|
|
638
|
+
if (includeTimings) {
|
|
639
|
+
const expression = `(function() {
|
|
640
|
+
try {
|
|
641
|
+
const result = {};
|
|
642
|
+
// Navigation Timing
|
|
643
|
+
const nav = performance.getEntriesByType('navigation')[0];
|
|
644
|
+
if (nav) {
|
|
645
|
+
result.navigation = {
|
|
646
|
+
type: nav.type,
|
|
647
|
+
redirectTime: Math.round(nav.redirectEnd - nav.redirectStart),
|
|
648
|
+
dnsTime: Math.round(nav.domainLookupEnd - nav.domainLookupStart),
|
|
649
|
+
connectTime: Math.round(nav.connectEnd - nav.connectStart),
|
|
650
|
+
ttfb: Math.round(nav.responseStart - nav.requestStart),
|
|
651
|
+
responseTime: Math.round(nav.responseEnd - nav.responseStart),
|
|
652
|
+
domInteractive: Math.round(nav.domInteractive),
|
|
653
|
+
domContentLoaded: Math.round(nav.domContentLoadedEventEnd),
|
|
654
|
+
loadComplete: Math.round(nav.loadEventEnd),
|
|
655
|
+
totalDuration: Math.round(nav.duration),
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
// Paint Timing (FP, FCP)
|
|
659
|
+
const paints = performance.getEntriesByType('paint');
|
|
660
|
+
result.paint = {};
|
|
661
|
+
for (const p of paints) {
|
|
662
|
+
if (p.name === 'first-paint') result.paint.firstPaint = Math.round(p.startTime);
|
|
663
|
+
if (p.name === 'first-contentful-paint') result.paint.firstContentfulPaint = Math.round(p.startTime);
|
|
664
|
+
}
|
|
665
|
+
// Long Tasks(如果有 PerformanceObserver 记录)
|
|
666
|
+
try {
|
|
667
|
+
const longTasks = performance.getEntriesByType('longtask');
|
|
668
|
+
if (longTasks && longTasks.length > 0) {
|
|
669
|
+
result.longTasks = {
|
|
670
|
+
count: longTasks.length,
|
|
671
|
+
totalDuration: Math.round(longTasks.reduce((s, t) => s + t.duration, 0)),
|
|
672
|
+
longest: Math.round(Math.max(...longTasks.map(t => t.duration))),
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
} catch(e) {}
|
|
676
|
+
// 基本信息
|
|
677
|
+
result.timing = {
|
|
678
|
+
now: Math.round(performance.now()),
|
|
679
|
+
timeOrigin: Math.round(performance.timeOrigin),
|
|
680
|
+
};
|
|
681
|
+
return result;
|
|
682
|
+
} catch (e) { return { error: e.message }; }
|
|
683
|
+
})()`
|
|
684
|
+
const { result: evalResult } = await chrome.debugger.sendCommand(target, "Runtime.evaluate", {
|
|
685
|
+
expression,
|
|
686
|
+
returnByValue: true,
|
|
687
|
+
})
|
|
688
|
+
if (evalResult?.value) {
|
|
689
|
+
result.webVitals = evalResult.value
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// 3. 资源加载摘要
|
|
694
|
+
if (includeResources) {
|
|
695
|
+
const resExpression = `(function() {
|
|
696
|
+
try {
|
|
697
|
+
const entries = performance.getEntriesByType('resource');
|
|
698
|
+
const byType = {};
|
|
699
|
+
let totalSize = 0, totalDuration = 0, slowest = null;
|
|
700
|
+
for (const e of entries) {
|
|
701
|
+
const type = e.initiatorType || 'other';
|
|
702
|
+
if (!byType[type]) byType[type] = { count: 0, totalSize: 0, totalDuration: 0 };
|
|
703
|
+
byType[type].count++;
|
|
704
|
+
byType[type].totalSize += e.transferSize || 0;
|
|
705
|
+
byType[type].totalDuration += e.duration || 0;
|
|
706
|
+
totalSize += e.transferSize || 0;
|
|
707
|
+
totalDuration += e.duration || 0;
|
|
708
|
+
if (!slowest || e.duration > slowest.duration) {
|
|
709
|
+
slowest = { name: e.name.split('/').pop().split('?')[0] || e.name.slice(0, 60), duration: Math.round(e.duration), size: e.transferSize || 0, type };
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
// 格式化 byType
|
|
713
|
+
const summary = {};
|
|
714
|
+
for (const [type, data] of Object.entries(byType)) {
|
|
715
|
+
summary[type] = { count: data.count, totalSize: data.totalSize, avgDuration: Math.round(data.totalDuration / data.count) };
|
|
716
|
+
}
|
|
717
|
+
return { totalResources: entries.length, totalTransferSize: totalSize, summary, slowest };
|
|
718
|
+
} catch (e) { return { error: e.message }; }
|
|
719
|
+
})()`
|
|
720
|
+
const { result: resResult } = await chrome.debugger.sendCommand(target, "Runtime.evaluate", {
|
|
721
|
+
expression: resExpression,
|
|
722
|
+
returnByValue: true,
|
|
723
|
+
})
|
|
724
|
+
if (resResult?.value) {
|
|
725
|
+
result.resources = resResult.value
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
return result
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
function formatBytes(bytes) {
|
|
733
|
+
if (bytes == null) return "N/A"
|
|
734
|
+
if (bytes < 1024) return bytes + " B"
|
|
735
|
+
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KB"
|
|
736
|
+
return (bytes / (1024 * 1024)).toFixed(1) + " MB"
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
function roundMs(seconds) {
|
|
740
|
+
if (seconds == null) return "N/A"
|
|
741
|
+
return Math.round(seconds * 1000) + "ms"
|
|
742
|
+
}
|
|
743
|
+
|
|
593
744
|
async function handleCaptureScreenshot(params = {}) {
|
|
594
745
|
const target = await ensureAttached()
|
|
595
746
|
const { format = 'png', quality, fullPage = false, clip } = params
|
|
@@ -784,6 +935,7 @@ async function handleCommand(message) {
|
|
|
784
935
|
else if (command === "listNetworkRequests") result = await handleListNetworkRequests(params)
|
|
785
936
|
else if (command === "getNetworkDetail") result = await handleGetNetworkDetail(params)
|
|
786
937
|
else if (command === "clearNetworkRequests") result = await handleClearNetworkRequests()
|
|
938
|
+
else if (command === "perfMetrics") result = await handlePerfMetrics(params)
|
|
787
939
|
else if (command === "captureScreenshot") result = await handleCaptureScreenshot(params)
|
|
788
940
|
else if (command === "getPageContent") result = await handleGetPageContent(params)
|
|
789
941
|
else throw new Error(`未知指令 ${command}`)
|
package/extension/manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ghost-bridge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Ghost Bridge: Zero-restart Chrome debugger bridge for Claude MCP. Includes CLI for easy setup.",
|
|
@@ -26,4 +26,4 @@
|
|
|
26
26
|
"js-beautify": "^1.14.11",
|
|
27
27
|
"ws": "^8.14.2"
|
|
28
28
|
}
|
|
29
|
-
}
|
|
29
|
+
}
|