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 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 = new URL(import.meta.url).pathname;
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 = new URL(import.meta.url).pathname;
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 = fileURLToPath(import.meta.url);
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.1.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.1.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 });
@@ -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}`)
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Ghost Bridge",
4
- "version": "0.2.0",
4
+ "version": "0.3.0",
5
5
  "description": "Zero-restart Chrome debugger bridge for Claude MCP, optimized for no-sourcemap production debugging.",
6
6
  "permissions": [
7
7
  "debugger",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ghost-bridge",
3
- "version": "0.2.3",
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
+ }