ghost-bridge 0.3.0 → 0.5.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/LICENSE +21 -0
- package/README.md +140 -66
- package/dist/cli.js +68 -37
- package/dist/server.js +71 -1
- package/extension/background.js +425 -39
- package/extension/icon-128.png +0 -0
- package/extension/icon-16.png +0 -0
- package/extension/icon-48.png +0 -0
- package/extension/manifest.json +1 -1
- package/extension/offscreen.js +1 -1
- package/extension/popup.html +416 -85
- package/extension/popup.js +128 -53
- package/package.json +24 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 <Author>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,85 +1,159 @@
|
|
|
1
|
-
# Ghost Bridge
|
|
1
|
+
# 👻 Ghost Bridge
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/ghost-bridge)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
- `server.js`:MCP server,通过 stdio 被 Claude CLI 拉起,与扩展用 WebSocket 通信
|
|
7
|
-
- `extension/`:Chrome MV3 扩展,使用 `chrome.debugger` 附加当前激活标签
|
|
6
|
+
> **Zero-restart Chrome AI Copilot** — Subvert your workflow. Allow AI to seamlessly take over the browser you're already using, enabling real-time debugging, visual observation, and interactive manipulation without launching a new Chrome instance.
|
|
8
7
|
|
|
9
|
-
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## ✨ Why Ghost Bridge?
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
- 🔌 **Zero-Config Attach** — Bypasses the need for `--remote-debugging-port`. Captures Chrome's native DevTools Protocol directly via an extension.
|
|
13
|
+
- 🔍 **No-Sourcemap Debugging** — Slice code fragments, perform string searches, and analyze coverage to pinpoint bugs straight in minified production code.
|
|
14
|
+
- 🌐 **Deep Network Analysis** — Comprehensive capture of requests/responses with multi-dimensional filtering and response body inspection.
|
|
15
|
+
- 📸 **Visual & Structural Perception** — Full-page or clipped high-fidelity screenshots paired with structural data extraction (titles, links, forms, buttons).
|
|
16
|
+
- 🎯 **DOM Physical Manipulation** — Empowers AI to click, type, and form-submit with CDP-level physical simulation. Fully compatible with complex SPAs (React/Vue/Angular/Svelte).
|
|
17
|
+
- 📊 **Performance Diagnostics** — Get granular engine metrics: JS Heap, Layout recalculations, Web Vitals (TTFB/FCP/LCP), and resource loading speeds.
|
|
18
|
+
- 🔄 **Multi-Client Mastery** — Built-in singleton manager automatically coordinates multiple MCP clients sharing a single Chrome transport.
|
|
12
19
|
|
|
13
|
-
|
|
20
|
+
---
|
|
14
21
|
|
|
15
|
-
|
|
22
|
+
## 🚀 Quick Start
|
|
23
|
+
|
|
24
|
+
### 1. Install & Initialize
|
|
16
25
|
|
|
17
26
|
```bash
|
|
18
|
-
#
|
|
27
|
+
# Install globally
|
|
19
28
|
npm install -g ghost-bridge
|
|
20
29
|
|
|
21
|
-
#
|
|
30
|
+
# Auto-configure MCP (Claude Code, Antigravity, Codex) and prepare the extension directory
|
|
22
31
|
ghost-bridge init
|
|
23
32
|
```
|
|
24
33
|
|
|
25
|
-
|
|
34
|
+
> **Note on other MCP clients (Cursor, Windsurf, Roo):**
|
|
35
|
+
> `ghost-bridge init` attempts to auto-configure supported clients. If your client isn't auto-detected, simply add the following to your client's MCP configuration file (e.g., `mcp.json`):
|
|
36
|
+
> ```json
|
|
37
|
+
> {
|
|
38
|
+
> "mcpServers": {
|
|
39
|
+
> "ghost-bridge": {
|
|
40
|
+
> "command": "node",
|
|
41
|
+
> "args": ["/absolute/path/to/global/node_modules/ghost-bridge/dist/server.js"]
|
|
42
|
+
> }
|
|
43
|
+
> }
|
|
44
|
+
> }
|
|
45
|
+
> ```
|
|
46
|
+
|
|
47
|
+
### 2. Load the Chrome Extension
|
|
48
|
+
|
|
49
|
+
1. Open Chrome and navigate to `chrome://extensions`
|
|
50
|
+
2. Toggle **Developer mode** in the top right corner.
|
|
51
|
+
3. Click **Load unpacked**
|
|
52
|
+
4. Select the directory: `~/.ghost-bridge/extension`
|
|
53
|
+
|
|
54
|
+
> 💡 *Tip: Run `ghost-bridge extension --open` to reveal the directory directly.*
|
|
55
|
+
|
|
56
|
+
### 3. Connect & Command
|
|
57
|
+
|
|
58
|
+
1. Click the **Ghost Bridge** ghost icon in your browser toolbar.
|
|
59
|
+
2. Click **Connect** and wait for the status to turn to ✅ **ON**.
|
|
60
|
+
3. Open Claude Desktop or your Claude CLI. All tools are now primed and ready!
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 🛠️ Tool Arsenal
|
|
65
|
+
|
|
66
|
+
### 🔍 Core Debugging
|
|
67
|
+
|
|
68
|
+
| Tool | Capability |
|
|
69
|
+
|------|------------|
|
|
70
|
+
| `get_server_info` | Retrieves server instance status, WebSocket ports, and client roles. |
|
|
71
|
+
| `get_last_error` | Aggregates recent exceptions, console errors, and failed network requests with mapped locators. |
|
|
72
|
+
| `get_script_source` | Pulls raw script definitions. Supports URL-fragment filtering, specific line targeting, and beautification. |
|
|
73
|
+
| `coverage_snapshot` | Triggers a quick coverage trace (1.5s default) to identify the most active scripts on the page. |
|
|
74
|
+
| `find_by_string` | Scans page script sources for keywords, returning a 200-character context window. |
|
|
75
|
+
| `symbolic_hints` | Gathers context clues: Resource lists, Global Variable keys, LocalStorage schema, and UA strings. |
|
|
76
|
+
| `eval_script` | Executes raw JavaScript expressions in the page context. *(Use with caution)* |
|
|
77
|
+
|
|
78
|
+
### 🌍 Network Intelligence
|
|
79
|
+
|
|
80
|
+
| Tool | Capability |
|
|
81
|
+
|------|------------|
|
|
82
|
+
| `list_network_requests` | Lists captured network traffic. Supports filtering by URL, Method, Status Code, or Resource Type. |
|
|
83
|
+
| `get_network_detail` | Dives deep into a specific request's Headers, Timing, and optional Response Body extraction. |
|
|
84
|
+
| `clear_network_requests` | Wipes the current network capture buffer. |
|
|
85
|
+
|
|
86
|
+
### 📸 Page Perception
|
|
87
|
+
|
|
88
|
+
| Tool | Capability |
|
|
89
|
+
|------|------------|
|
|
90
|
+
| `capture_screenshot` | Captures the viewport or emulates full-page scrolling screenshots. |
|
|
91
|
+
| `get_page_content` | Extracts raw text, sanitized HTML, or structured actionable data representations. |
|
|
92
|
+
|
|
93
|
+
### 🎯 Interactive Automation (DOM)
|
|
94
|
+
|
|
95
|
+
| Tool | Capability |
|
|
96
|
+
|------|------------|
|
|
97
|
+
| `get_interactive_snapshot` | Scans for visible interactive elements, returning a concise map `[e1, e2...]`. Pierces open Shadow DOMs. |
|
|
98
|
+
| `dispatch_action` | Dispatches physical UI actions (click, fill, press, hover) against targeted element references (e.g., `e1`). |
|
|
99
|
+
|
|
100
|
+
**Example Agent Workflow:**
|
|
101
|
+
1. AI: `get_interactive_snapshot` ➝ `[{ref:"e1", tag:"input", placeholder:"Search..."}, {ref:"e2", tag:"button", text:"Login"}]`
|
|
102
|
+
2. AI: `dispatch_action({ref: "e1", action: "fill", value: "hello"})`
|
|
103
|
+
3. AI: `dispatch_action({ref: "e2", action: "click"})`
|
|
104
|
+
4. AI: `capture_screenshot` to verify state changes.
|
|
105
|
+
|
|
106
|
+
### 📊 Performance Profiling
|
|
107
|
+
|
|
108
|
+
| Tool | Capability |
|
|
109
|
+
|------|------------|
|
|
110
|
+
| `perf_metrics` | Collects layered performance data (Engine Metrics, Web Vitals, and Resource Load Summaries). |
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## ⚙️ Configuration
|
|
115
|
+
|
|
116
|
+
| Setting | Default | Description |
|
|
117
|
+
|---------|---------|-------------|
|
|
118
|
+
| **Base Port** | `33333` | WS port. Auto-increments if occupied. |
|
|
119
|
+
| **Token** | *Monthly UUID* | Local WS auth token, auto-rotates on the 1st of every month. |
|
|
120
|
+
| **Auto Detach** | `false` | Keeps debugger attached to actively buffer invisible exceptions and network calls. |
|
|
121
|
+
|
|
122
|
+
**Environment Variables:**
|
|
123
|
+
- `GHOST_BRIDGE_PORT` — Override base port.
|
|
124
|
+
- `GHOST_BRIDGE_TOKEN` — Override connection token.
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## 🏗️ Architecture
|
|
129
|
+
|
|
130
|
+
```mermaid
|
|
131
|
+
graph LR
|
|
132
|
+
A[Claude CLI/Desktop] <-->|stdio| B(MCP Server\nserver.js)
|
|
133
|
+
B <-->|WebSocket| C(Chrome Extension\nbackground.js)
|
|
134
|
+
C <-->|CDP| D[Browser Tab\nTarget Context]
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
- **MCP Server**: Spawned by Claude via standard I/O streams. Orchestrates WS connections.
|
|
138
|
+
- **Chrome Extension (MV3)**: Taps into `chrome.debugger` API. Utilizes an Offscreen Document to prevent WS hibernation.
|
|
139
|
+
- **Singleton Design**: If multiple agents spawn servers, the first becomes the master bridge while subsequent instances chain transparently as clients.
|
|
26
140
|
|
|
27
|
-
|
|
28
|
-
2. 开启右上角的“开发者模式”
|
|
29
|
-
3. 点击“加载已解压的扩展程序”
|
|
30
|
-
4. 选择目录:`~/.ghost-bridge/extension`
|
|
31
|
-
> 提示:运行 `ghost-bridge extension --open` 可直接打开该目录
|
|
141
|
+
---
|
|
32
142
|
|
|
33
|
-
|
|
143
|
+
## ⚠️ Known Limitations
|
|
34
144
|
|
|
35
|
-
|
|
145
|
+
- **Service Workers Suspending**: MV3 background workers may suspend. We've built robust auto-reconnection logic, but prolonged inactivity might require re-toggling.
|
|
146
|
+
- **DevTools Conflict**: If you manually open Chrome DevTools (F12) on the target tab, `chrome.debugger.attach` may be rejected.
|
|
147
|
+
- **Beautify Overhead**: Beautifying massive single-line bundles is expensive; the server will auto-truncate overly large scripts.
|
|
148
|
+
- **Cross-Origin OOPIF**: Elements and errors deeply embedded in strict Cross-Origin Iframes might evade the primary debugger hook without further multi-target attach logic.
|
|
36
149
|
|
|
37
150
|
---
|
|
38
151
|
|
|
39
|
-
##
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
- 端口:`3301`(`server.js` / `extension/background.js`)
|
|
48
|
-
- token:`1`(仅用于本机 WS 校验,如需修改请保持两端一致)
|
|
49
|
-
|
|
50
|
-
## 工具说明
|
|
51
|
-
|
|
52
|
-
### 基础调试工具
|
|
53
|
-
- **get_last_error**:汇总最近异常/console/网络报错,附带行列与脚本标识
|
|
54
|
-
- **get_script_source**:支持 `scriptUrlContains`、`line`、`column`,返回源码片段(无 sourcemap 仍可用)
|
|
55
|
-
- **coverage_snapshot**:默认 1.5s,输出调用次数最高的脚本
|
|
56
|
-
- **find_by_string**:在脚本源码里按关键词搜索,返回上下文 200 字符窗口
|
|
57
|
-
- **symbolic_hints**:采集资源列表、全局变量 key、localStorage key、UA 与 URL
|
|
58
|
-
- **eval_script**:只读表达式执行;谨慎使用,避免改写页面状态
|
|
59
|
-
|
|
60
|
-
### 🆕 网络请求分析工具
|
|
61
|
-
- **list_network_requests**:列出捕获的网络请求
|
|
62
|
-
- 支持按 URL 关键词过滤(`filter`)
|
|
63
|
-
- 支持按请求方法过滤(`method`: GET/POST/PUT/DELETE)
|
|
64
|
-
- 支持按状态过滤(`status`: success/error/failed/pending)
|
|
65
|
-
- 支持按资源类型过滤(`resourceType`: XHR/Fetch/Script/Image)
|
|
66
|
-
- 返回:URL、方法、状态码、耗时、大小等摘要信息
|
|
67
|
-
|
|
68
|
-
- **get_network_detail**:获取单个请求的详细信息
|
|
69
|
-
- 请求头和响应头
|
|
70
|
-
- 请求方法、状态码、MIME 类型
|
|
71
|
-
- 耗时分析(timing)
|
|
72
|
-
- 可选获取响应体(`includeBody: true`)
|
|
73
|
-
|
|
74
|
-
- **clear_network_requests**:清空已捕获的网络请求记录
|
|
75
|
-
|
|
76
|
-
## 设计取舍
|
|
77
|
-
- 不依赖 `--remote-debugging-port`,完全通过扩展获取 CDP,满足"零重启"
|
|
78
|
-
- 默认 `autoDetach=false` 保持附加,便于持续捕获异常和网络请求;可通过图标 OFF 立即解除调试
|
|
79
|
-
- 无 sourcemap 时通过片段截取、字符串搜索与覆盖率提供线索
|
|
80
|
-
- 网络请求完整记录,支持查看请求/响应详情和响应体
|
|
81
|
-
|
|
82
|
-
## 已知限制
|
|
83
|
-
- 扩展 service worker 可能被挂起,已内置 1s 重连策略;若长时间无流量需重新唤醒
|
|
84
|
-
- 若目标页面自带 DevTools 打开,`chrome.debugger.attach` 可能失败,请关闭后重试
|
|
85
|
-
- 大体积单行 bundle beautify 可能耗时,server 端对超长源码会只截取片段
|
|
152
|
+
## 🤝 Contributing
|
|
153
|
+
|
|
154
|
+
Contributions, issues, and feature requests are welcome!
|
|
155
|
+
Check out our [Contributing Guide](CONTRIBUTING.md) to get started building tools or improving the bridge.
|
|
156
|
+
|
|
157
|
+
## 📄 License
|
|
158
|
+
|
|
159
|
+
This project is [MIT](LICENSE) licensed.
|
package/dist/cli.js
CHANGED
|
@@ -5454,9 +5454,15 @@ var require_lib = __commonJS({
|
|
|
5454
5454
|
import path from "path";
|
|
5455
5455
|
import os2 from "os";
|
|
5456
5456
|
import { fileURLToPath } from "url";
|
|
5457
|
-
function
|
|
5457
|
+
function getClientConfigPaths() {
|
|
5458
5458
|
const homeDir = os2.homedir();
|
|
5459
|
-
return
|
|
5459
|
+
return [
|
|
5460
|
+
{ name: "Claude Code", path: path.join(homeDir, ".claude.json") },
|
|
5461
|
+
{ name: "Antigravity", path: path.join(homeDir, ".gemini", "antigravity", "mcp.json") },
|
|
5462
|
+
// Codex config placeholder - exact path depends on how Codex stores user config.
|
|
5463
|
+
// Assuming a common pattern here like ~/.codex/mcp.json or similar.
|
|
5464
|
+
{ name: "Codex", path: path.join(homeDir, ".codex", "mcp.json") }
|
|
5465
|
+
];
|
|
5460
5466
|
}
|
|
5461
5467
|
function getExtensionPath() {
|
|
5462
5468
|
const __filename2 = fileURLToPath(import.meta.url);
|
|
@@ -5502,20 +5508,28 @@ function openFolder(folderPath) {
|
|
|
5502
5508
|
}
|
|
5503
5509
|
async function init(options) {
|
|
5504
5510
|
console.log(source_default.bold("\u{1F47B} Ghost Bridge Initialization"));
|
|
5505
|
-
const
|
|
5511
|
+
const clientConfigs = getClientConfigPaths();
|
|
5506
5512
|
const serverPath = getServerPath();
|
|
5507
5513
|
const isDryRun = options.dryRun;
|
|
5508
|
-
console.log(source_default.dim("Checking
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
|
|
5514
|
+
console.log(source_default.dim("Checking MCP Client configurations..."));
|
|
5515
|
+
let configuredCount = 0;
|
|
5516
|
+
for (const client of clientConfigs) {
|
|
5517
|
+
const configPath = client.path;
|
|
5518
|
+
if (isDryRun) {
|
|
5519
|
+
console.log(source_default.yellow(`[Dry Run] Would check ${client.name} config at: ${configPath}`));
|
|
5520
|
+
if (import_fs_extra.default.existsSync(configPath) || client.name === "Claude Code") {
|
|
5521
|
+
console.log(source_default.yellow(`[Dry Run] Would add MCP server logic for ${client.name}`));
|
|
5522
|
+
}
|
|
5523
|
+
continue;
|
|
5524
|
+
}
|
|
5525
|
+
const exists = import_fs_extra.default.existsSync(configPath);
|
|
5526
|
+
if (!exists && client.name !== "Claude Code") {
|
|
5527
|
+
continue;
|
|
5528
|
+
}
|
|
5529
|
+
if (!exists) {
|
|
5530
|
+
console.log(source_default.yellow(`Configuration file not found for ${client.name} at ${configPath}, creating...`));
|
|
5515
5531
|
await import_fs_extra.default.ensureDir(path2.dirname(configPath));
|
|
5516
|
-
|
|
5517
|
-
await import_fs_extra.default.writeJson(configPath, { mcpServers: {} }, { spaces: 2 });
|
|
5518
|
-
}
|
|
5532
|
+
await import_fs_extra.default.writeJson(configPath, { mcpServers: {} }, { spaces: 2 });
|
|
5519
5533
|
}
|
|
5520
5534
|
try {
|
|
5521
5535
|
const config = await import_fs_extra.default.readJson(configPath);
|
|
@@ -5525,11 +5539,15 @@ async function init(options) {
|
|
|
5525
5539
|
args: [serverPath]
|
|
5526
5540
|
};
|
|
5527
5541
|
await import_fs_extra.default.writeJson(configPath, config, { spaces: 2 });
|
|
5528
|
-
console.log(source_default.green(`\u2705 MCP Server
|
|
5542
|
+
console.log(source_default.green(`\u2705 MCP Server configured for ${source_default.bold(client.name)} in ${configPath}`));
|
|
5543
|
+
configuredCount++;
|
|
5529
5544
|
} catch (err) {
|
|
5530
|
-
console.error(source_default.red(`Failed to update config: ${err.message}`));
|
|
5545
|
+
console.error(source_default.red(`Failed to update config for ${client.name}: ${err.message}`));
|
|
5531
5546
|
}
|
|
5532
5547
|
}
|
|
5548
|
+
if (configuredCount === 0 && !isDryRun) {
|
|
5549
|
+
console.log(source_default.yellow("\u26A0\uFE0F No supported MCP clients found to configure automatically."));
|
|
5550
|
+
}
|
|
5533
5551
|
const sourceExt = getExtensionPath();
|
|
5534
5552
|
const targetExt = getUserExtensionDir();
|
|
5535
5553
|
console.log(source_default.dim(`Setting up extension in ${targetExt}...`));
|
|
@@ -5617,34 +5635,47 @@ __export(status_exports, {
|
|
|
5617
5635
|
});
|
|
5618
5636
|
async function status() {
|
|
5619
5637
|
console.log(source_default.bold("\u{1F47B} Ghost Bridge Status"));
|
|
5620
|
-
const
|
|
5638
|
+
const clientConfigs = getClientConfigPaths();
|
|
5621
5639
|
const extDir = getUserExtensionDir();
|
|
5622
5640
|
const serverPath = getServerPath();
|
|
5623
|
-
|
|
5624
|
-
let
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
const
|
|
5632
|
-
if (
|
|
5633
|
-
|
|
5634
|
-
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
|
|
5641
|
+
console.log(source_default.bold.blue("\nMCP Client Configurations:"));
|
|
5642
|
+
let configuredCount = 0;
|
|
5643
|
+
for (const client of clientConfigs) {
|
|
5644
|
+
let mcpStatus = source_default.gray("Not Configured");
|
|
5645
|
+
let mcpDetails = "";
|
|
5646
|
+
const configPath = client.path;
|
|
5647
|
+
if (import_fs_extra3.default.existsSync(configPath)) {
|
|
5648
|
+
try {
|
|
5649
|
+
const config = await import_fs_extra3.default.readJson(configPath);
|
|
5650
|
+
if (config.mcpServers && config.mcpServers["ghost-bridge"]) {
|
|
5651
|
+
mcpStatus = source_default.green("Configured");
|
|
5652
|
+
const cfg = config.mcpServers["ghost-bridge"];
|
|
5653
|
+
const configuredPath = cfg.args[0];
|
|
5654
|
+
if (configuredPath === serverPath) {
|
|
5655
|
+
mcpDetails = source_default.dim("(Paths match)");
|
|
5656
|
+
} else {
|
|
5657
|
+
mcpDetails = source_default.yellow(`(Path mismatch)
|
|
5658
|
+
Configured: ${configuredPath}
|
|
5659
|
+
Current: ${serverPath}`);
|
|
5660
|
+
}
|
|
5661
|
+
configuredCount++;
|
|
5638
5662
|
}
|
|
5663
|
+
} catch (e) {
|
|
5664
|
+
mcpStatus = source_default.red("Error reading config");
|
|
5665
|
+
}
|
|
5666
|
+
} else {
|
|
5667
|
+
if (client.name === "Claude Code") {
|
|
5668
|
+
mcpStatus = source_default.yellow("Config file not found");
|
|
5669
|
+
} else {
|
|
5670
|
+
mcpStatus = source_default.gray("Not Installed");
|
|
5639
5671
|
}
|
|
5640
|
-
} catch (e) {
|
|
5641
|
-
mcpStatus = source_default.red("Error reading config");
|
|
5642
5672
|
}
|
|
5643
|
-
}
|
|
5644
|
-
|
|
5673
|
+
console.log(` ${source_default.bold(client.name)}: ${mcpStatus} ${mcpDetails}`);
|
|
5674
|
+
console.log(` Config File: ${source_default.dim(configPath)}`);
|
|
5675
|
+
}
|
|
5676
|
+
if (configuredCount === 0) {
|
|
5677
|
+
console.log(source_default.yellow("\n No MCP clients currently have ghost-bridge configured. Run `ghost-bridge init`."));
|
|
5645
5678
|
}
|
|
5646
|
-
console.log(`MCP Configuration: ${mcpStatus} ${mcpDetails}`);
|
|
5647
|
-
console.log(` Config File: ${configPath}`);
|
|
5648
5679
|
let extStatus = source_default.red("Not Installed (Run init)");
|
|
5649
5680
|
if (import_fs_extra3.default.existsSync(extDir)) {
|
|
5650
5681
|
extStatus = source_default.green("Installed");
|
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.4.0" },
|
|
28900
28900
|
{ capabilities: { tools: {} } }
|
|
28901
28901
|
);
|
|
28902
28902
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
@@ -29067,6 +29067,66 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
29067
29067
|
}
|
|
29068
29068
|
}
|
|
29069
29069
|
}
|
|
29070
|
+
},
|
|
29071
|
+
{
|
|
29072
|
+
name: "get_interactive_snapshot",
|
|
29073
|
+
description: "\u3010\u64CD\u4F5C\u9875\u9762\u524D\u5FC5\u987B\u5148\u8C03\u7528\u3011\u626B\u63CF\u5F53\u524D\u9875\u9762\u6240\u6709\u53EF\u89C1\u7684\u53EF\u4EA4\u4E92\u5143\u7D20\uFF08\u6309\u94AE/\u94FE\u63A5/\u8F93\u5165\u6846/\u4E0B\u62C9\u6846\u7B49\uFF09\uFF0C\u8FD4\u56DE\u5E26\u6709 ref \u77ED\u6807\u8BC6\uFF08\u5982 e1, e2, e3\uFF09\u7684\u7CBE\u7B80\u5217\u8868\uFF0C\u5305\u542B\u5143\u7D20\u7C7B\u578B\u3001\u6587\u672C\u548C\u4F4D\u7F6E\u3002Token \u6781\u7701\uFF08\u901A\u5E38 < 1000 tokens\uFF09\uFF0C\u4E13\u4E3A AI \u64CD\u4F5C\u9875\u9762\u800C\u8BBE\u8BA1\u3002\u83B7\u53D6\u540E\u53EF\u901A\u8FC7 dispatch_action \u5DE5\u5177\u4F7F\u7528 ref \u6807\u8BC6\u6765\u70B9\u51FB\u3001\u586B\u5199\u3001\u6309\u952E\u7B49\u3002\u652F\u6301 Shadow DOM \u7A7F\u900F\u3002\n\u26A0\uFE0F \u4EC5\u7528\u4E8E\u4EA4\u4E92\u64CD\u4F5C\u524D\u7684\u5143\u7D20\u5B9A\u4F4D\u3002\u5982\u9700\u6392\u67E5 UI/CSS \u5E03\u5C40\u95EE\u9898\uFF0C\u8BF7\u4F7F\u7528 capture_screenshot \u6216 get_page_content\u3002",
|
|
29074
|
+
inputSchema: {
|
|
29075
|
+
type: "object",
|
|
29076
|
+
properties: {
|
|
29077
|
+
selector: {
|
|
29078
|
+
type: "string",
|
|
29079
|
+
description: "CSS \u9009\u62E9\u5668\uFF0C\u9650\u5B9A\u626B\u63CF\u8303\u56F4\u3002\u4E0D\u6307\u5B9A\u5219\u626B\u63CF\u6574\u4E2A\u9875\u9762"
|
|
29080
|
+
},
|
|
29081
|
+
includeText: {
|
|
29082
|
+
type: "boolean",
|
|
29083
|
+
description: "\u662F\u5426\u5305\u542B\u5143\u7D20\u7684\u6587\u672C/\u5360\u4F4D\u7B26\u7B49\u4FE1\u606F\uFF0C\u9ED8\u8BA4 true"
|
|
29084
|
+
},
|
|
29085
|
+
maxElements: {
|
|
29086
|
+
type: "number",
|
|
29087
|
+
description: "\u6700\u5927\u8FD4\u56DE\u5143\u7D20\u6570\u91CF\uFF0C\u9ED8\u8BA4 100"
|
|
29088
|
+
}
|
|
29089
|
+
}
|
|
29090
|
+
}
|
|
29091
|
+
},
|
|
29092
|
+
{
|
|
29093
|
+
name: "dispatch_action",
|
|
29094
|
+
description: "\u3010\u64CD\u4F5C\u9875\u9762\u5143\u7D20\u3011\u5BF9 get_interactive_snapshot \u8FD4\u56DE\u7684\u5143\u7D20\u6267\u884C\u52A8\u4F5C\u3002\u901A\u8FC7 ref \u6807\u8BC6\uFF08\u5982 e1, e5\uFF09\u7CBE\u51C6\u5B9A\u4F4D\u5143\u7D20\uFF0C\u4F7F\u7528 CDP \u7269\u7406\u7EA7\u6A21\u62DF\u6267\u884C\u64CD\u4F5C\uFF0C\u517C\u5BB9\u6240\u6709\u524D\u7AEF\u6846\u67B6\uFF08React/Vue/Angular\uFF09\uFF0C\u6210\u529F\u7387\u6781\u9AD8\u3002\n\u652F\u6301\u7684\u52A8\u4F5C\uFF1Aclick\uFF08\u70B9\u51FB\uFF09\u3001fill\uFF08\u586B\u5199\u8F93\u5165\u6846\uFF09\u3001press\uFF08\u6309\u952E\u5982 Enter\uFF09\u3001scroll\uFF08\u6EDA\u52A8\uFF09\u3001select\uFF08\u4E0B\u62C9\u9009\u62E9\uFF09\u3001hover\uFF08\u60AC\u505C\uFF09\u3001focus\uFF08\u805A\u7126\uFF09\u3002\n\u26A0\uFE0F \u4F7F\u7528\u524D\u5FC5\u987B\u5148\u8C03\u7528 get_interactive_snapshot \u83B7\u53D6\u5143\u7D20\u5217\u8868\u3002\u64CD\u4F5C\u540E\u5EFA\u8BAE\u7528 capture_screenshot \u6216\u518D\u6B21 get_interactive_snapshot \u9A8C\u8BC1\u7ED3\u679C\u3002",
|
|
29095
|
+
inputSchema: {
|
|
29096
|
+
type: "object",
|
|
29097
|
+
properties: {
|
|
29098
|
+
ref: {
|
|
29099
|
+
type: "string",
|
|
29100
|
+
description: "\u76EE\u6807\u5143\u7D20\u7684 ref \u6807\u8BC6\uFF0C\u5982 'e1'\u3001'e5'\uFF08\u4ECE get_interactive_snapshot \u83B7\u53D6\uFF09"
|
|
29101
|
+
},
|
|
29102
|
+
action: {
|
|
29103
|
+
type: "string",
|
|
29104
|
+
enum: ["click", "fill", "press", "scroll", "select", "hover", "focus"],
|
|
29105
|
+
description: "\u8981\u6267\u884C\u7684\u52A8\u4F5C\u7C7B\u578B"
|
|
29106
|
+
},
|
|
29107
|
+
value: {
|
|
29108
|
+
type: "string",
|
|
29109
|
+
description: "fill \u65F6\u4E3A\u8981\u8F93\u5165\u7684\u6587\u672C\uFF1Bselect \u65F6\u4E3A\u8981\u9009\u62E9\u7684 option value\uFF1Bpress \u65F6\u4E3A\u6309\u952E\u540D\uFF08\u53EF\u9009\uFF09"
|
|
29110
|
+
},
|
|
29111
|
+
key: {
|
|
29112
|
+
type: "string",
|
|
29113
|
+
description: "press \u52A8\u4F5C\u7684\u6309\u952E\u540D\uFF0C\u5982 'Enter'\u3001'Escape'\u3001'Tab'\u3001'Backspace'\u3002\u9ED8\u8BA4 'Enter'"
|
|
29114
|
+
},
|
|
29115
|
+
deltaX: {
|
|
29116
|
+
type: "number",
|
|
29117
|
+
description: "scroll \u52A8\u4F5C\u7684\u6C34\u5E73\u6EDA\u52A8\u91CF\uFF08\u50CF\u7D20\uFF09\uFF0C\u9ED8\u8BA4 0"
|
|
29118
|
+
},
|
|
29119
|
+
deltaY: {
|
|
29120
|
+
type: "number",
|
|
29121
|
+
description: "scroll \u52A8\u4F5C\u7684\u5782\u76F4\u6EDA\u52A8\u91CF\uFF08\u50CF\u7D20\uFF09\uFF0C\u9ED8\u8BA4 300\uFF08\u6B63\u6570\u5411\u4E0B\uFF0C\u8D1F\u6570\u5411\u4E0A\uFF09"
|
|
29122
|
+
},
|
|
29123
|
+
waitMs: {
|
|
29124
|
+
type: "number",
|
|
29125
|
+
description: "\u64CD\u4F5C\u540E\u7B49\u5F85\u9875\u9762\u54CD\u5E94\u7684\u65F6\u95F4\uFF08\u6BEB\u79D2\uFF09\uFF0C\u9ED8\u8BA4 500\uFF0C\u6700\u5927 3000"
|
|
29126
|
+
}
|
|
29127
|
+
},
|
|
29128
|
+
required: ["ref", "action"]
|
|
29129
|
+
}
|
|
29070
29130
|
}
|
|
29071
29131
|
]
|
|
29072
29132
|
}));
|
|
@@ -29223,6 +29283,16 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
29223
29283
|
const res = await askChrome("getPageContent", { mode, selector, maxLength, includeMetadata });
|
|
29224
29284
|
return { content: [{ type: "text", text: jsonText(res) }] };
|
|
29225
29285
|
}
|
|
29286
|
+
if (name === "get_interactive_snapshot") {
|
|
29287
|
+
const { selector, includeText, maxElements } = args;
|
|
29288
|
+
const res = await askChrome("getInteractiveSnapshot", { selector, includeText, maxElements });
|
|
29289
|
+
return { content: [{ type: "text", text: jsonText(res) }] };
|
|
29290
|
+
}
|
|
29291
|
+
if (name === "dispatch_action") {
|
|
29292
|
+
const { ref, action, value, key, deltaX, deltaY, waitMs } = args;
|
|
29293
|
+
const res = await askChrome("dispatchAction", { ref, action, value, key, deltaX, deltaY, waitMs }, { timeoutMs: 1e4 });
|
|
29294
|
+
return { content: [{ type: "text", text: jsonText(res) }] };
|
|
29295
|
+
}
|
|
29226
29296
|
return { content: [{ type: "text", text: `\u672A\u77E5\u5DE5\u5177\uFF1A${name}` }] };
|
|
29227
29297
|
} catch (e) {
|
|
29228
29298
|
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|