ghost-bridge 0.8.0 → 0.9.1

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 CHANGED
@@ -16,6 +16,7 @@ Most browser-capable AI tools start a separate browser. Ghost Bridge connects AI
16
16
  - Inspect page structure, text, screenshots, errors, and network traffic
17
17
  - Search and extract script sources, even in production bundles
18
18
  - Click, type, scroll, and submit forms on the current page
19
+ - Bind multiple Chrome tabs as named targets and operate them independently
19
20
  - Share one Chrome transport across multiple MCP clients
20
21
 
21
22
  ## Quick Start
@@ -94,6 +95,9 @@ Typical prompts:
94
95
  | `get_page_content` | Text, HTML, and structured DOM extraction |
95
96
  | `get_interactive_snapshot` | Find clickable and editable elements |
96
97
  | `dispatch_action` | Click, fill, press, scroll, hover, select |
98
+ | `bind_tab` | Bind a Chrome tab as a named target such as `cases` or `app` |
99
+ | `unbind_tab` | Remove a named target binding |
100
+ | `list_targets` | Show named targets and their per-tab session status |
97
101
  | `pin_current_tab` | Keep Ghost Bridge attached to the current tab while you browse elsewhere |
98
102
  | `pin_tab` | Pin a target tab by tab ID, URL fragment, or title fragment |
99
103
  | `unpin_tab` | Return to following the focused tab |
@@ -117,9 +121,19 @@ Recommended flow:
117
121
 
118
122
  Notes:
119
123
 
124
+ - Use `bind_tab` when a workflow spans multiple pages. For example, bind a checklist page as `cases` and a business page as `app`, then call tools with `target: "cases"` or `target: "app"`.
125
+ - All browser tools accept an optional `target` parameter. When named targets are bound, `dispatch_action` requires `target` so refs from one page are not accidentally used on another page.
120
126
  - Use `pin_current_tab` when you are debugging a page and need to switch to other tabs without changing the AI target. Use `unpin_tab` to restore the original follow-focused-tab behavior.
121
127
  - `list_network_requests` and `get_network_detail` automatically summarize `data:` URLs and very long URLs so inline images or oversized query strings do not overwhelm model context
122
128
 
129
+ Multi-page example:
130
+
131
+ ```text
132
+ Bind the current checklist tab as cases.
133
+ Bind the tab whose title contains "Orders" as app.
134
+ Read the next case from target cases, operate target app, then mark the case passed or failed back on target cases.
135
+ ```
136
+
123
137
  ## Configuration
124
138
 
125
139
  | Setting | Default | Notes |
@@ -133,18 +147,26 @@ Notes:
133
147
  ```mermaid
134
148
  flowchart LR
135
149
  A["AI Client<br/>Claude / Codex / Cursor"]
136
- B["Ghost Bridge MCP Server<br/>server.js"]
150
+ S["Session Process<br/>dist/server.js (stdio)"]
151
+ B["Ghost Bridge Daemon<br/>resident WebSocket service"]
137
152
  C["Chrome Extension<br/>background.js"]
138
- D["Browser Tab<br/>Target Context"]
153
+ D["Browser Tabs<br/>Target Sessions"]
139
154
 
140
- A <-->|"stdio"| B
141
- B <-->|"WebSocket"| C
155
+ A <-->|"stdio"| S
156
+ S <-->|"WebSocket (mcp-client)"| B
157
+ C <-->|"WebSocket"| B
142
158
  C <-->|"CDP"| D
143
159
  ```
144
160
 
161
+ The WebSocket service runs as a detached daemon, independent of any MCP session:
162
+
163
+ - The first session process spawns the daemon automatically; it keeps running after that session exits
164
+ - If the daemon crashes, any live session detects it and respawns it automatically
165
+ - Stop it manually with `ghost-bridge stop` (close live MCP sessions first, otherwise they will bring it back on their next reconnect)
166
+
145
167
  ## Troubleshooting
146
168
 
147
- If the popup shows `No Bridge` / `Not Found`, it means the Chrome extension could not find a Ghost Bridge WebSocket service on the configured port. It does not necessarily mean your AI client is closed.
169
+ If the popup shows `No Bridge` / `Not Found`, it means the Chrome extension could not find a Ghost Bridge WebSocket service on the configured port. With the resident daemon this normally only happens before the first MCP session of the day starts, or after `ghost-bridge stop`. Starting any MCP session (or reconnecting the extension) brings the service back within seconds.
148
170
 
149
171
  Run `ghost-bridge status` and check:
150
172
 
package/dist/cli.js CHANGED
@@ -6010,6 +6010,7 @@ init_source();
6010
6010
  import { fileURLToPath as fileURLToPath2 } from "url";
6011
6011
  import path5 from "path";
6012
6012
  import fs5 from "fs";
6013
+ import os7 from "os";
6013
6014
  var __filename = fileURLToPath2(import.meta.url);
6014
6015
  var __dirname = path5.dirname(__filename);
6015
6016
  var packageJsonParams = JSON.parse(
@@ -6062,4 +6063,62 @@ program2.command("status").description("Check Ghost Bridge configuration status"
6062
6063
  console.error(source_default.red("Error checking status:"), error);
6063
6064
  }
6064
6065
  });
6066
+ var sleepMs = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
6067
+ function isProcessAlive(pid) {
6068
+ try {
6069
+ process.kill(pid, 0);
6070
+ return true;
6071
+ } catch (e) {
6072
+ return e.code === "EPERM";
6073
+ }
6074
+ }
6075
+ function readPortInfoFile() {
6076
+ const portInfoFile = process.env.GHOST_BRIDGE_PORT_INFO || path5.join(os7.tmpdir(), "ghost-bridge-port.json");
6077
+ if (!fs5.existsSync(portInfoFile)) return null;
6078
+ try {
6079
+ const info = JSON.parse(fs5.readFileSync(portInfoFile, "utf-8"));
6080
+ return info && info.pid ? { file: portInfoFile, ...info } : null;
6081
+ } catch {
6082
+ return null;
6083
+ }
6084
+ }
6085
+ function removePortInfoFile(entry) {
6086
+ try {
6087
+ const current = JSON.parse(fs5.readFileSync(entry.file, "utf-8"));
6088
+ if (current.pid === entry.pid) fs5.unlinkSync(entry.file);
6089
+ } catch {
6090
+ }
6091
+ }
6092
+ program2.command("stop").description("Stop the resident ghost-bridge daemon").action(async () => {
6093
+ try {
6094
+ const entry = readPortInfoFile();
6095
+ if (!entry) {
6096
+ console.log(source_default.yellow("\u672A\u53D1\u73B0\u8FD0\u884C\u4E2D\u7684 ghost-bridge \u5E38\u9A7B\u670D\u52A1"));
6097
+ return;
6098
+ }
6099
+ if (!isProcessAlive(entry.pid)) {
6100
+ removePortInfoFile(entry);
6101
+ console.log(source_default.yellow(`\u7AEF\u53E3\u4FE1\u606F\u5DF2\u8FC7\u671F\uFF08PID ${entry.pid} \u4E0D\u5B58\u5728\uFF09\uFF0C\u5DF2\u6E05\u7406`));
6102
+ return;
6103
+ }
6104
+ console.log(source_default.blue(`\u6B63\u5728\u505C\u6B62 ghost-bridge \u5E38\u9A7B\u670D\u52A1 (PID: ${entry.pid})...`));
6105
+ process.kill(entry.pid, "SIGTERM");
6106
+ const deadline = Date.now() + 5e3;
6107
+ while (Date.now() < deadline && isProcessAlive(entry.pid)) {
6108
+ await sleepMs(200);
6109
+ }
6110
+ if (isProcessAlive(entry.pid)) {
6111
+ console.error(source_default.red("\u670D\u52A1\u672A\u80FD\u5728\u9884\u671F\u65F6\u95F4\u5185\u9000\u51FA\uFF0C\u8BF7\u624B\u52A8\u68C0\u67E5\u8BE5\u8FDB\u7A0B"));
6112
+ process.exit(1);
6113
+ }
6114
+ removePortInfoFile(entry);
6115
+ console.log(source_default.green("\u2705 ghost-bridge \u5E38\u9A7B\u670D\u52A1\u5DF2\u505C\u6B62"));
6116
+ console.log(
6117
+ source_default.dim("\u6CE8\u610F\uFF1A\u82E5\u4ECD\u6709\u6D3B\u8DC3\u7684 MCP \u4F1A\u8BDD\uFF0C\u4F1A\u8BDD\u4F1A\u5728\u4E0B\u6B21\u91CD\u8FDE\u65F6\u81EA\u52A8\u91CD\u65B0\u62C9\u8D77\u670D\u52A1\uFF1B\u5982\u9700\u5F7B\u5E95\u505C\u6B62\uFF0C\u8BF7\u5148\u5173\u95ED\u4F7F\u7528\u4E2D\u7684\u4F1A\u8BDD\u3002")
6118
+ );
6119
+ } catch (error) {
6120
+ console.error(source_default.red("Error stopping ghost-bridge:"), error);
6121
+ process.exit(1);
6122
+ }
6123
+ });
6065
6124
  program2.parse();