ghost-bridge 0.9.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
@@ -147,18 +147,26 @@ Read the next case from target cases, operate target app, then mark the case pas
147
147
  ```mermaid
148
148
  flowchart LR
149
149
  A["AI Client<br/>Claude / Codex / Cursor"]
150
- 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"]
151
152
  C["Chrome Extension<br/>background.js"]
152
153
  D["Browser Tabs<br/>Target Sessions"]
153
154
 
154
- A <-->|"stdio"| B
155
- B <-->|"WebSocket"| C
155
+ A <-->|"stdio"| S
156
+ S <-->|"WebSocket (mcp-client)"| B
157
+ C <-->|"WebSocket"| B
156
158
  C <-->|"CDP"| D
157
159
  ```
158
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
+
159
167
  ## Troubleshooting
160
168
 
161
- 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.
162
170
 
163
171
  Run `ghost-bridge status` and check:
164
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();
package/dist/server.js CHANGED
@@ -27102,8 +27102,8 @@ var Protocol = class {
27102
27102
  *
27103
27103
  * The Protocol object assumes ownership of the Transport, replacing any callbacks that have already been set, and expects that it is the only user of the Transport instance going forward.
27104
27104
  */
27105
- async connect(transport2) {
27106
- this._transport = transport2;
27105
+ async connect(transport) {
27106
+ this._transport = transport;
27107
27107
  const _onclose = this.transport?.onclose;
27108
27108
  this._transport.onclose = () => {
27109
27109
  _onclose?.();
@@ -28523,6 +28523,7 @@ import net from "net";
28523
28523
  import fs2 from "fs";
28524
28524
  import os from "os";
28525
28525
  import path2 from "path";
28526
+ import { spawn } from "child_process";
28526
28527
  import { fileURLToPath as fileURLToPath2 } from "url";
28527
28528
 
28528
28529
  // lib/version.js
@@ -28540,9 +28541,10 @@ var BASE_PORT = Number(process.env.GHOST_BRIDGE_PORT || 33333);
28540
28541
  var DEFAULT_WS_TOKEN = "ghost-bridge-local";
28541
28542
  var WS_TOKEN = process.env.GHOST_BRIDGE_TOKEN || DEFAULT_WS_TOKEN;
28542
28543
  var RESPONSE_TIMEOUT = 8e3;
28543
- var PORT_INFO_FILE = path2.join(os.tmpdir(), "ghost-bridge-port.json");
28544
+ var PORT_INFO_FILE = process.env.GHOST_BRIDGE_PORT_INFO || path2.join(os.tmpdir(), "ghost-bridge-port.json");
28544
28545
  var SERVER_STARTED_AT = (/* @__PURE__ */ new Date()).toISOString();
28545
28546
  var SERVER_ENTRY_PATH = fileURLToPath2(import.meta.url);
28547
+ var IS_DAEMON = process.env.GHOST_BRIDGE_DAEMON === "1";
28546
28548
  var chromeConnection = null;
28547
28549
  var activeConnection = null;
28548
28550
  var actualPort = BASE_PORT;
@@ -28698,17 +28700,24 @@ function isPortAvailable(port) {
28698
28700
  server2.listen(port);
28699
28701
  });
28700
28702
  }
28701
- async function startWebSocketServer() {
28702
- const available = await isPortAvailable(BASE_PORT);
28703
- if (!available) {
28704
- throw new Error(`\u56FA\u5B9A\u7AEF\u53E3 ${BASE_PORT} \u4E0D\u53EF\u7528\uFF0C\u8BF7\u91CA\u653E\u8BE5\u7AEF\u53E3\u6216\u901A\u8FC7 GHOST_BRIDGE_PORT \u6307\u5B9A\u5176\u4ED6\u7AEF\u53E3`);
28705
- }
28706
- actualPort = BASE_PORT;
28707
- const wss2 = new import_websocket_server.default({ port: BASE_PORT });
28708
- log(`\u{1F680} WebSocket \u670D\u52A1\u5DF2\u542F\u52A8\uFF0C\u7AEF\u53E3 ${BASE_PORT}${WS_TOKEN ? "\uFF08\u542F\u7528 token \u6821\u9A8C\uFF09" : ""}`);
28709
- return wss2;
28703
+ function startWebSocketServer(port) {
28704
+ return new Promise((resolve, reject) => {
28705
+ let settled = false;
28706
+ const wss2 = new import_websocket_server.default({ port }, () => {
28707
+ if (settled) return;
28708
+ settled = true;
28709
+ wss2.removeListener("error", onError);
28710
+ resolve(wss2);
28711
+ });
28712
+ const onError = (err) => {
28713
+ if (settled) return;
28714
+ settled = true;
28715
+ reject(err);
28716
+ };
28717
+ wss2.on("error", onError);
28718
+ });
28710
28719
  }
28711
- async function initWebSocketService() {
28720
+ async function acquirePortForDaemon() {
28712
28721
  const existing = getExistingService();
28713
28722
  if (existing) {
28714
28723
  log(`\u68C0\u6D4B\u5230\u73B0\u6709\u670D\u52A1 (PID: ${existing.pid}, \u7AEF\u53E3: ${existing.port})\uFF0C\u9A8C\u8BC1\u4E2D...`);
@@ -28718,17 +28727,15 @@ async function initWebSocketService() {
28718
28727
  const mismatch = getServiceMismatch(probe);
28719
28728
  if (!mismatch) {
28720
28729
  actualPort = existing.port;
28721
- isMainInstance = false;
28722
- log(`\u2705 \u590D\u7528\u73B0\u6709\u670D\u52A1\uFF0C\u7AEF\u53E3 ${actualPort}`);
28723
- return null;
28730
+ log(`\u2705 \u5DF2\u6709\u5E38\u9A7B\u670D\u52A1\u5728\u8FD0\u884C\uFF0C\u65E0\u9700\u91CD\u590D\u542F\u52A8`);
28731
+ return { alreadyRunning: true };
28724
28732
  }
28725
28733
  await stopExistingService(Number(probe.pid) || existing.pid, existing.port, mismatch);
28726
28734
  } else {
28727
- const oldPid = Number(probe.pid) || existing.pid;
28728
- await stopExistingService(oldPid, existing.port, "token \u4E0D\u4E00\u81F4");
28735
+ await stopExistingService(Number(probe.pid) || existing.pid, existing.port, "token \u4E0D\u4E00\u81F4");
28729
28736
  }
28730
28737
  } else {
28731
- log(`\u274C \u73B0\u6709\u670D\u52A1\u9A8C\u8BC1\u5931\u8D25\uFF0C\u542F\u52A8\u65B0\u670D\u52A1...`);
28738
+ log(`\u274C \u73B0\u6709\u670D\u52A1\u9A8C\u8BC1\u5931\u8D25\uFF0C\u51C6\u5907\u63A5\u7BA1...`);
28732
28739
  try {
28733
28740
  fs2.unlinkSync(PORT_INFO_FILE);
28734
28741
  } catch {
@@ -28742,9 +28749,8 @@ async function initWebSocketService() {
28742
28749
  const mismatch = getServiceMismatch(probe);
28743
28750
  if (!mismatch) {
28744
28751
  actualPort = BASE_PORT;
28745
- isMainInstance = false;
28746
- log(`\u2705 \u590D\u7528\u56FA\u5B9A\u7AEF\u53E3\u4E0A\u7684\u73B0\u6709\u670D\u52A1\uFF0C\u7AEF\u53E3 ${actualPort}`);
28747
- return null;
28752
+ log(`\u2705 \u56FA\u5B9A\u7AEF\u53E3\u4E0A\u5DF2\u6709\u5E38\u9A7B\u670D\u52A1\u5728\u8FD0\u884C\uFF0C\u65E0\u9700\u91CD\u590D\u542F\u52A8`);
28753
+ return { alreadyRunning: true };
28748
28754
  }
28749
28755
  await stopExistingService(Number(probe.pid), BASE_PORT, mismatch);
28750
28756
  } else {
@@ -28755,8 +28761,34 @@ async function initWebSocketService() {
28755
28761
  throw new Error(`\u56FA\u5B9A\u7AEF\u53E3 ${BASE_PORT} \u5DF2\u88AB\u5176\u4ED6\u8FDB\u7A0B\u5360\u7528\uFF0C\u8BF7\u91CA\u653E\u8BE5\u7AEF\u53E3\u6216\u901A\u8FC7 GHOST_BRIDGE_PORT \u6307\u5B9A\u5176\u4ED6\u7AEF\u53E3`);
28756
28762
  }
28757
28763
  }
28758
- const wss2 = await startWebSocketServer();
28764
+ let wss2;
28765
+ try {
28766
+ wss2 = await startWebSocketServer(BASE_PORT);
28767
+ } catch (err) {
28768
+ const probe = await probeExistingService(BASE_PORT);
28769
+ if (probe?.service === "ghost-bridge" && probe.token === WS_TOKEN && !getServiceMismatch(probe)) {
28770
+ actualPort = BASE_PORT;
28771
+ log(`\u2705 \u53E6\u4E00\u4E2A\u5E38\u9A7B\u670D\u52A1\u5DF2\u62A2\u5148\u542F\u52A8\uFF0C\u672C\u8FDB\u7A0B\u9000\u51FA`);
28772
+ return { alreadyRunning: true };
28773
+ }
28774
+ throw err;
28775
+ }
28776
+ log(`\u{1F680} WebSocket \u670D\u52A1\u5DF2\u542F\u52A8\uFF0C\u7AEF\u53E3 ${BASE_PORT}${WS_TOKEN ? "\uFF08\u542F\u7528 token \u6821\u9A8C\uFF09" : ""}`);
28777
+ actualPort = BASE_PORT;
28759
28778
  isMainInstance = true;
28779
+ return { wss: wss2 };
28780
+ }
28781
+ async function runDaemon() {
28782
+ let acquired;
28783
+ try {
28784
+ acquired = await acquirePortForDaemon();
28785
+ } catch (e) {
28786
+ log(`\u274C daemon \u542F\u52A8\u5931\u8D25\uFF1A${e.message}`);
28787
+ process.exit(1);
28788
+ }
28789
+ if (acquired.alreadyRunning) {
28790
+ process.exit(0);
28791
+ }
28760
28792
  fs2.writeFileSync(
28761
28793
  PORT_INFO_FILE,
28762
28794
  JSON.stringify({
@@ -28769,9 +28801,47 @@ async function initWebSocketService() {
28769
28801
  }, null, 2)
28770
28802
  );
28771
28803
  log(`\u{1F4DD} \u7AEF\u53E3\u4FE1\u606F\u5DF2\u5199\u5165: ${PORT_INFO_FILE}`);
28772
- return wss2;
28804
+ return acquired.wss;
28805
+ }
28806
+ async function ensureDaemonRunning() {
28807
+ const existing = getExistingService();
28808
+ if (existing) {
28809
+ const probe = await probeExistingService(existing.port);
28810
+ if (probe?.service === "ghost-bridge" && probe.token === WS_TOKEN && !getServiceMismatch(probe)) {
28811
+ actualPort = Number(probe.port) || existing.port;
28812
+ return true;
28813
+ }
28814
+ } else {
28815
+ const probe = await probeExistingService(BASE_PORT);
28816
+ if (probe?.service === "ghost-bridge" && probe.token === WS_TOKEN && !getServiceMismatch(probe)) {
28817
+ actualPort = BASE_PORT;
28818
+ return true;
28819
+ }
28820
+ }
28821
+ return spawnDaemonAndWait();
28822
+ }
28823
+ async function spawnDaemonAndWait(timeoutMs = 15e3) {
28824
+ log("\u{1F6E0} \u5E38\u9A7B\u670D\u52A1\u672A\u8FD0\u884C\uFF0C\u6B63\u5728\u542F\u52A8 daemon...");
28825
+ const child = spawn(process.execPath, [SERVER_ENTRY_PATH], {
28826
+ detached: true,
28827
+ stdio: "ignore",
28828
+ env: { ...process.env, GHOST_BRIDGE_DAEMON: "1" }
28829
+ });
28830
+ child.unref();
28831
+ const deadline = Date.now() + timeoutMs;
28832
+ while (Date.now() < deadline) {
28833
+ const probe = await probeExistingService(BASE_PORT);
28834
+ if (probe?.service === "ghost-bridge" && probe.token === WS_TOKEN && !getServiceMismatch(probe)) {
28835
+ actualPort = Number(probe.port) || BASE_PORT;
28836
+ log(`\u2705 daemon \u5DF2\u5C31\u7EEA (PID: ${probe.pid}, \u7AEF\u53E3: ${actualPort})`);
28837
+ return true;
28838
+ }
28839
+ await sleep(250);
28840
+ }
28841
+ log(`\u274C daemon \u672A\u80FD\u5728 ${timeoutMs}ms \u5185\u5C31\u7EEA\uFF0C\u7A0D\u540E\u5C06\u91CD\u8BD5`);
28842
+ return false;
28773
28843
  }
28774
- var wss = await initWebSocketService();
28844
+ var wss = IS_DAEMON ? await runDaemon() : null;
28775
28845
  var wsPingInterval = null;
28776
28846
  var WS_PING_INTERVAL_MS = 3e4;
28777
28847
  if (wss) {
@@ -28913,22 +28983,22 @@ if (wss) {
28913
28983
  }
28914
28984
  }, WS_PING_INTERVAL_MS);
28915
28985
  } else {
28916
- log(`\u{1F4E1} \u4F5C\u4E3A\u5BA2\u6237\u7AEF\u8FDE\u63A5\u5230\u4E3B\u5B9E\u4F8B (\u7AEF\u53E3 ${actualPort})...`);
28986
+ const ready = await ensureDaemonRunning();
28987
+ if (!ready) log("\u26A0\uFE0F \u5E38\u9A7B\u670D\u52A1\u6682\u65F6\u4E0D\u53EF\u7528\uFF0C\u5C06\u4FDD\u6301\u540E\u53F0\u91CD\u8BD5\u5E76\u81EA\u52A8\u62C9\u8D77");
28988
+ log(`\u{1F4E1} \u4F5C\u4E3A\u5BA2\u6237\u7AEF\u8FDE\u63A5\u5230\u5E38\u9A7B\u670D\u52A1 (\u7AEF\u53E3: ${actualPort})...`);
28917
28989
  connectToMainInstance();
28918
28990
  }
28919
- var MAX_RECONNECT_ATTEMPTS = 10;
28920
28991
  var RECONNECT_INTERVAL = 3e3;
28921
- var reconnectAttempts = 0;
28922
- var wasEverConnected = false;
28992
+ var DAEMON_CHECK_EVERY = 5;
28993
+ var consecutiveFailures = 0;
28923
28994
  function connectToMainInstance() {
28924
28995
  const url2 = new URL(`ws://localhost:${actualPort}`);
28925
28996
  url2.searchParams.set("token", WS_TOKEN);
28926
28997
  url2.searchParams.set("role", "mcp-client");
28927
28998
  const ws = new import_websocket.default(url2.toString());
28928
28999
  ws.on("open", () => {
28929
- log(`\u2705 \u5DF2\u8FDE\u63A5\u5230\u4E3B\u5B9E\u4F8B (\u7AEF\u53E3 ${actualPort})`);
28930
- reconnectAttempts = 0;
28931
- wasEverConnected = true;
29000
+ log(`\u2705 \u5DF2\u8FDE\u63A5\u5230\u5E38\u9A7B\u670D\u52A1 (\u7AEF\u53E3: ${actualPort})`);
29001
+ consecutiveFailures = 0;
28932
29002
  });
28933
29003
  ws.on("message", (data) => {
28934
29004
  try {
@@ -28943,30 +29013,25 @@ function connectToMainInstance() {
28943
29013
  }
28944
29014
  });
28945
29015
  ws.on("close", () => {
28946
- log("\u26A0\uFE0F \u4E0E\u4E3B\u5B9E\u4F8B\u7684\u8FDE\u63A5\u5DF2\u65AD\u5F00");
29016
+ log("\u26A0\uFE0F \u4E0E\u5E38\u9A7B\u670D\u52A1\u7684\u8FDE\u63A5\u5DF2\u65AD\u5F00");
28947
29017
  activeConnection = null;
28948
- failAllPending("\u4E0E\u4E3B\u5B9E\u4F8B\u7684\u8FDE\u63A5\u5DF2\u65AD\u5F00");
28949
- reconnectAttempts++;
28950
- if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
28951
- log(`\u274C \u91CD\u8FDE\u5931\u8D25\u6B21\u6570\u8FC7\u591A (${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})\uFF0C\u5BA2\u6237\u7AEF\u8FDB\u7A0B\u9000\u51FA`);
28952
- process.exit(0);
28953
- }
28954
- setTimeout(() => {
28955
- if (!activeConnection) {
28956
- log(`\u{1F504} \u5C1D\u8BD5\u91CD\u65B0\u8FDE\u63A5\u5230\u4E3B\u5B9E\u4F8B... (${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})`);
28957
- connectToMainInstance();
28958
- }
29018
+ failAllPending("\u4E0E\u5E38\u9A7B\u670D\u52A1\u7684\u8FDE\u63A5\u5DF2\u65AD\u5F00");
29019
+ consecutiveFailures++;
29020
+ const attempt = consecutiveFailures;
29021
+ const needDaemonCheck = attempt % DAEMON_CHECK_EVERY === 0;
29022
+ setTimeout(async () => {
29023
+ if (activeConnection) return;
29024
+ if (needDaemonCheck) {
29025
+ log(`\u{1F504} \u8FDE\u7EED ${attempt} \u6B21\u672A\u8FDE\u4E0A\u5E38\u9A7B\u670D\u52A1\uFF0C\u68C0\u67E5\u5E76\u5C1D\u8BD5\u62C9\u8D77...`);
29026
+ await ensureDaemonRunning();
29027
+ }
29028
+ if (activeConnection) return;
29029
+ log(`\u{1F504} \u5C1D\u8BD5\u91CD\u65B0\u8FDE\u63A5\u5230\u5E38\u9A7B\u670D\u52A1 (\u7B2C ${attempt} \u6B21)...`);
29030
+ connectToMainInstance();
28959
29031
  }, RECONNECT_INTERVAL);
28960
29032
  });
28961
29033
  ws.on("error", (err) => {
28962
- log(`\u274C \u8FDE\u63A5\u4E3B\u5B9E\u4F8B\u5931\u8D25: ${err.message}`);
28963
- if (!wasEverConnected) {
28964
- reconnectAttempts++;
28965
- if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
28966
- log(`\u274C \u65E0\u6CD5\u8FDE\u63A5\u5230\u4E3B\u5B9E\u4F8B\uFF0C\u5BA2\u6237\u7AEF\u8FDB\u7A0B\u9000\u51FA`);
28967
- process.exit(0);
28968
- }
28969
- }
29034
+ log(`\u274C \u8FDE\u63A5\u5E38\u9A7B\u670D\u52A1\u5931\u8D25: ${err.message}`);
28970
29035
  });
28971
29036
  }
28972
29037
  function failAllPending(message) {
@@ -29001,7 +29066,7 @@ function handleIncoming(data) {
29001
29066
  else resolve(result);
29002
29067
  }
29003
29068
  async function askMainInstance(command, params = {}) {
29004
- if (!activeConnection) throw new Error("\u672A\u8FDE\u63A5\u5230\u4E3B\u5B9E\u4F8B");
29069
+ if (!activeConnection) throw new Error("\u672A\u8FDE\u63A5\u5230\u5E38\u9A7B\u670D\u52A1");
29005
29070
  const id = crypto.randomUUID();
29006
29071
  const payload = { id, command, params };
29007
29072
  return new Promise((resolve, reject) => {
@@ -29020,7 +29085,11 @@ async function askMainInstance(command, params = {}) {
29020
29085
  });
29021
29086
  }
29022
29087
  async function askChrome(command, params = {}, options = {}) {
29023
- if (!activeConnection) throw new Error("Chrome \u672A\u8FDE\u63A5\uFF0C\u8BF7\u786E\u8BA4\u6D4F\u89C8\u5668\u5F00\u542F\u4E14\u6269\u5C55\u5DF2\u542F\u7528");
29088
+ if (!activeConnection) {
29089
+ throw new Error(
29090
+ IS_DAEMON ? "Chrome \u672A\u8FDE\u63A5\uFF0C\u8BF7\u786E\u8BA4\u6D4F\u89C8\u5668\u5F00\u542F\u4E14\u6269\u5C55\u5DF2\u542F\u7528" : "ghost-bridge \u5E38\u9A7B\u670D\u52A1\u672A\u8FDE\u63A5\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\uFF08\u53EF\u7528 ghost-bridge status \u68C0\u67E5\uFF09"
29091
+ );
29092
+ }
29024
29093
  const id = crypto.randomUUID();
29025
29094
  const payload = { id, command, params, clientId: LOCAL_CLIENT_ID };
29026
29095
  if (WS_TOKEN) payload.token = WS_TOKEN;
@@ -29498,7 +29567,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
29498
29567
  text: jsonText({
29499
29568
  service: "ghost-bridge",
29500
29569
  version: GHOST_BRIDGE_VERSION,
29501
- role: isMainInstance ? "\u4E3B\u5B9E\u4F8B (WebSocket Server)" : "\u5BA2\u6237\u7AEF (\u8FDE\u63A5\u5230\u4E3B\u5B9E\u4F8B)",
29570
+ role: isMainInstance ? "daemon (\u5E38\u9A7B WebSocket \u670D\u52A1)" : "\u4F1A\u8BDD\u5BA2\u6237\u7AEF (\u8FDE\u63A5\u5E38\u9A7B\u670D\u52A1)",
29502
29571
  wsPort: actualPort,
29503
29572
  wsUrl: `ws://localhost:${actualPort}`,
29504
29573
  pid: process.pid,
@@ -29685,38 +29754,42 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
29685
29754
  return { content: [{ type: "text", text: `Error: ${e.message}` }] };
29686
29755
  }
29687
29756
  });
29688
- var transport = new StdioServerTransport();
29689
- await server.connect(transport);
29690
- var roleText = isMainInstance ? "\u4E3B\u5B9E\u4F8B" : "\u5BA2\u6237\u7AEF";
29691
- log(`\u2705 MCP server \u5DF2\u542F\u52A8 | \u89D2\u8272: ${roleText} | \u7AEF\u53E3: ${actualPort} | PID: ${process.pid} | PPID: ${process.ppid}`);
29692
- log(`\u{1F4C4} \u7AEF\u53E3\u4FE1\u606F\u6587\u4EF6: ${PORT_INFO_FILE}`);
29693
- log(`\u{1F4A1} \u4F7F\u7528 get_server_info \u5DE5\u5177\u67E5\u770B\u8BE6\u7EC6\u72B6\u6001`);
29694
- var PARENT_CHECK_INTERVAL = 5e3;
29695
- var parentPid = process.ppid;
29696
- process.stdin.on("end", () => {
29697
- log("\u26A0\uFE0F stdin \u5DF2\u5173\u95ED\uFF0C\u7236\u8FDB\u7A0B\u53EF\u80FD\u5DF2\u9000\u51FA\uFF0C\u6B63\u5728\u9000\u51FA...");
29698
- cleanup();
29699
- process.exit(0);
29700
- });
29701
- process.stdin.on("close", () => {
29702
- log("\u26A0\uFE0F stdin \u5DF2\u5173\u95ED\uFF0C\u6B63\u5728\u9000\u51FA...");
29703
- cleanup();
29704
- process.exit(0);
29705
- });
29706
- var parentCheckTimer = setInterval(() => {
29707
- try {
29708
- process.kill(parentPid, 0);
29709
- } catch (e) {
29710
- log(`\u26A0\uFE0F \u7236\u8FDB\u7A0B (PID: ${parentPid}) \u5DF2\u4E0D\u5B58\u5728\uFF0C\u6B63\u5728\u9000\u51FA...`);
29711
- clearInterval(parentCheckTimer);
29757
+ if (!IS_DAEMON) {
29758
+ const transport = new StdioServerTransport();
29759
+ await server.connect(transport);
29760
+ log(`\u2705 MCP server \u5DF2\u542F\u52A8 | \u89D2\u8272: \u4F1A\u8BDD\u5BA2\u6237\u7AEF | \u5E38\u9A7B\u670D\u52A1\u7AEF\u53E3: ${actualPort} | PID: ${process.pid} | PPID: ${process.ppid}`);
29761
+ log(`\u{1F4A1} \u4F7F\u7528 get_server_info \u5DE5\u5177\u67E5\u770B\u8BE6\u7EC6\u72B6\u6001`);
29762
+ const PARENT_CHECK_INTERVAL = 5e3;
29763
+ const parentPid = process.ppid;
29764
+ process.stdin.on("end", () => {
29765
+ log("\u26A0\uFE0F stdin \u5DF2\u5173\u95ED\uFF0C\u7236\u8FDB\u7A0B\u53EF\u80FD\u5DF2\u9000\u51FA\uFF0C\u6B63\u5728\u9000\u51FA...");
29712
29766
  cleanup();
29713
29767
  process.exit(0);
29714
- }
29715
- }, PARENT_CHECK_INTERVAL);
29716
- parentCheckTimer.unref();
29768
+ });
29769
+ process.stdin.on("close", () => {
29770
+ log("\u26A0\uFE0F stdin \u5DF2\u5173\u95ED\uFF0C\u6B63\u5728\u9000\u51FA...");
29771
+ cleanup();
29772
+ process.exit(0);
29773
+ });
29774
+ const parentCheckTimer = setInterval(() => {
29775
+ try {
29776
+ process.kill(parentPid, 0);
29777
+ } catch (e) {
29778
+ log(`\u26A0\uFE0F \u7236\u8FDB\u7A0B (PID: ${parentPid}) \u5DF2\u4E0D\u5B58\u5728\uFF0C\u6B63\u5728\u9000\u51FA...`);
29779
+ clearInterval(parentCheckTimer);
29780
+ cleanup();
29781
+ process.exit(0);
29782
+ }
29783
+ }, PARENT_CHECK_INTERVAL);
29784
+ parentCheckTimer.unref();
29785
+ } else {
29786
+ log(`\u2705 ghost-bridge daemon \u5DF2\u542F\u52A8 | \u7AEF\u53E3: ${actualPort} | PID: ${process.pid}`);
29787
+ log(`\u{1F4C4} \u7AEF\u53E3\u4FE1\u606F\u6587\u4EF6: ${PORT_INFO_FILE}`);
29788
+ log(`\u{1F4A1} \u505C\u6B62\u670D\u52A1: ghost-bridge stop`);
29789
+ }
29717
29790
  function cleanup() {
29718
29791
  log("\u{1F9F9} \u6B63\u5728\u6E05\u7406...");
29719
- if (isMainInstance) {
29792
+ if (IS_DAEMON) {
29720
29793
  try {
29721
29794
  if (fs2.existsSync(PORT_INFO_FILE)) {
29722
29795
  const info = JSON.parse(fs2.readFileSync(PORT_INFO_FILE, "utf-8"));
@@ -29753,7 +29826,7 @@ process.on("SIGTERM", () => {
29753
29826
  process.exit(0);
29754
29827
  });
29755
29828
  process.on("exit", () => {
29756
- if (isMainInstance) {
29829
+ if (IS_DAEMON) {
29757
29830
  try {
29758
29831
  if (fs2.existsSync(PORT_INFO_FILE)) {
29759
29832
  const info = JSON.parse(fs2.readFileSync(PORT_INFO_FILE, "utf-8"));
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Ghost Bridge",
4
- "version": "0.9.0",
4
+ "version": "0.9.1",
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.9.0",
3
+ "version": "0.9.1",
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.",