ghost-bridge 0.6.2 → 0.7.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/README.md CHANGED
@@ -22,6 +22,10 @@ Most browser-capable AI tools start a separate browser. Ghost Bridge connects AI
22
22
 
23
23
  - `list_network_requests` and `get_network_detail` now summarize `data:` URLs and oversized URLs so inline images and long query strings do not overwhelm model context
24
24
 
25
+ ## What's New in 0.7.0
26
+
27
+ - The default bridge token is now the fixed local value `ghost-bridge-local` instead of a monthly rotating token, avoiding extension/server mismatches across month boundaries while keeping `GHOST_BRIDGE_TOKEN` available for manual override
28
+
25
29
  ## What's New in 0.6.0
26
30
 
27
31
  - `inspect_page` now collects structured page data and interactive elements in one browser-side snapshot, reducing duplicate DOM scans and cutting one round-trip from the hot path
@@ -40,7 +44,7 @@ ghost-bridge init
40
44
 
41
45
  `ghost-bridge init` currently writes config for:
42
46
 
43
- - Claude Code: `~/.claude/settings.json` or `~/.claude.json`
47
+ - Claude Code: `~/.claude.json`
44
48
  - Codex: `~/.codex/config.toml`
45
49
  - Cursor: `~/.cursor/mcp.json`
46
50
  - Antigravity: `~/.gemini/antigravity/mcp.json`
@@ -130,7 +134,7 @@ Notes:
130
134
  | Setting | Default | Notes |
131
135
  |---------|---------|-------|
132
136
  | Port | `33333` | Set `GHOST_BRIDGE_PORT` to override |
133
- | Token | Monthly UUID | Set `GHOST_BRIDGE_TOKEN` to override |
137
+ | Token | `ghost-bridge-local` | Set `GHOST_BRIDGE_TOKEN` to override |
134
138
  | Auto detach | `false` | Keeps debugger attached for ongoing capture |
135
139
 
136
140
  ## Architecture
package/dist/cli.js CHANGED
@@ -5505,14 +5505,14 @@ function getJsonClientDefinition(name, configPath, options = {}) {
5505
5505
  function getClientDefinitions() {
5506
5506
  const homeDir = getHomeDir();
5507
5507
  const claudeDir = path2.join(homeDir, ".claude");
5508
- const claudeSettingsPath = path2.join(claudeDir, "settings.json");
5509
5508
  const claudeLegacyPath = path2.join(homeDir, ".claude.json");
5510
5509
  const cursorDir = path2.join(homeDir, ".cursor");
5511
5510
  const antigravityDir = path2.join(homeDir, ".gemini", "antigravity");
5511
+ const hasClaudeLegacy = import_fs_extra.default.existsSync(claudeLegacyPath);
5512
5512
  return [
5513
- getJsonClientDefinition("Claude Code", import_fs_extra.default.existsSync(claudeSettingsPath) ? claudeSettingsPath : claudeLegacyPath, {
5514
- shouldCreate: true,
5515
- isAvailable: () => import_fs_extra.default.existsSync(claudeDir) || import_fs_extra.default.existsSync(claudeLegacyPath)
5513
+ getJsonClientDefinition("Claude Code (~/.claude.json)", claudeLegacyPath, {
5514
+ shouldCreate: hasClaudeLegacy || import_fs_extra.default.existsSync(claudeDir),
5515
+ isAvailable: () => hasClaudeLegacy || import_fs_extra.default.existsSync(claudeDir)
5516
5516
  }),
5517
5517
  {
5518
5518
  name: "Codex",
package/dist/server.js CHANGED
@@ -28536,14 +28536,11 @@ var GHOST_BRIDGE_VERSION = packageJson.version;
28536
28536
 
28537
28537
  // src/server.js
28538
28538
  var BASE_PORT = Number(process.env.GHOST_BRIDGE_PORT || 33333);
28539
- function getMonthlyToken() {
28540
- const now = /* @__PURE__ */ new Date();
28541
- const firstDayOfMonth = new Date(now.getFullYear(), now.getMonth(), 1, 0, 0, 0, 0);
28542
- return String(firstDayOfMonth.getTime());
28543
- }
28544
- var WS_TOKEN = process.env.GHOST_BRIDGE_TOKEN || getMonthlyToken();
28539
+ var DEFAULT_WS_TOKEN = "ghost-bridge-local";
28540
+ var WS_TOKEN = process.env.GHOST_BRIDGE_TOKEN || DEFAULT_WS_TOKEN;
28545
28541
  var RESPONSE_TIMEOUT = 8e3;
28546
28542
  var PORT_INFO_FILE = path2.join(os.tmpdir(), "ghost-bridge-port.json");
28543
+ var SERVER_STARTED_AT = (/* @__PURE__ */ new Date()).toISOString();
28547
28544
  var chromeConnection = null;
28548
28545
  var activeConnection = null;
28549
28546
  var actualPort = BASE_PORT;
@@ -28580,35 +28577,87 @@ function getExistingService() {
28580
28577
  return null;
28581
28578
  }
28582
28579
  }
28583
- function verifyExistingService(port) {
28580
+ function probeExistingService(port) {
28584
28581
  return new Promise((resolve) => {
28585
28582
  const url2 = new URL(`ws://localhost:${port}`);
28586
- if (WS_TOKEN) url2.searchParams.set("token", WS_TOKEN);
28583
+ url2.searchParams.set("role", "probe");
28587
28584
  const ws = new import_websocket.default(url2.toString());
28585
+ let settled = false;
28586
+ const finish = (result) => {
28587
+ if (settled) return;
28588
+ settled = true;
28589
+ clearTimeout(timeout);
28590
+ try {
28591
+ if (ws.readyState === import_websocket.default.CONNECTING || ws.readyState === import_websocket.default.OPEN) {
28592
+ ws.close();
28593
+ }
28594
+ } catch {
28595
+ }
28596
+ resolve(result);
28597
+ };
28588
28598
  const timeout = setTimeout(() => {
28589
- ws.close();
28590
- resolve(false);
28599
+ finish(null);
28591
28600
  }, 2e3);
28592
28601
  ws.on("message", (data) => {
28593
28602
  try {
28594
28603
  const msg = JSON.parse(data.toString());
28595
28604
  if (msg.type === "identity" && msg.service === "ghost-bridge") {
28596
- clearTimeout(timeout);
28597
- ws.close();
28598
- resolve(true);
28605
+ finish(msg);
28599
28606
  }
28600
28607
  } catch {
28601
28608
  }
28602
28609
  });
28603
28610
  ws.on("error", () => {
28604
- clearTimeout(timeout);
28605
- resolve(false);
28611
+ finish(null);
28606
28612
  });
28607
28613
  ws.on("close", () => {
28608
- clearTimeout(timeout);
28614
+ finish(null);
28609
28615
  });
28610
28616
  });
28611
28617
  }
28618
+ function sleep(ms) {
28619
+ return new Promise((resolve) => setTimeout(resolve, ms));
28620
+ }
28621
+ async function waitForPortAvailable(port, timeoutMs = 5e3) {
28622
+ const deadline = Date.now() + timeoutMs;
28623
+ while (Date.now() < deadline) {
28624
+ if (await isPortAvailable(port)) {
28625
+ return true;
28626
+ }
28627
+ await sleep(100);
28628
+ }
28629
+ return isPortAvailable(port);
28630
+ }
28631
+ async function stopExistingService(pid, port) {
28632
+ if (!pid || pid === process.pid) {
28633
+ return false;
28634
+ }
28635
+ log(`\u68C0\u6D4B\u5230\u65E7\u5B9E\u4F8B token \u4E0D\u4E00\u81F4\uFF0C\u51C6\u5907\u505C\u6B62\u65E7\u5B9E\u4F8B (PID: ${pid})`);
28636
+ try {
28637
+ process.kill(pid, "SIGTERM");
28638
+ } catch (e) {
28639
+ if (e.code === "ESRCH") {
28640
+ log(`\u65E7\u5B9E\u4F8B PID ${pid} \u5DF2\u4E0D\u5B58\u5728`);
28641
+ return true;
28642
+ }
28643
+ throw e;
28644
+ }
28645
+ const released = await waitForPortAvailable(port, 5e3);
28646
+ if (!released) {
28647
+ throw new Error(`\u65E7\u5B9E\u4F8B (PID: ${pid}) \u672A\u5728\u9884\u671F\u65F6\u95F4\u5185\u91CA\u653E\u7AEF\u53E3 ${port}`);
28648
+ }
28649
+ if (fs2.existsSync(PORT_INFO_FILE)) {
28650
+ try {
28651
+ const info = JSON.parse(fs2.readFileSync(PORT_INFO_FILE, "utf-8"));
28652
+ if (info.pid === pid) {
28653
+ fs2.unlinkSync(PORT_INFO_FILE);
28654
+ }
28655
+ } catch {
28656
+ }
28657
+ }
28658
+ log(`\u2705 \u65E7\u5B9E\u4F8B\u5DF2\u9000\u51FA\uFF0C\u7AEF\u53E3 ${port} \u53EF\u7531\u65B0\u5B9E\u4F8B\u63A5\u7BA1`);
28659
+ return true;
28660
+ }
28612
28661
  function isPortAvailable(port) {
28613
28662
  return new Promise((resolve) => {
28614
28663
  const server2 = net.createServer();
@@ -28634,12 +28683,16 @@ async function initWebSocketService() {
28634
28683
  const existing = getExistingService();
28635
28684
  if (existing) {
28636
28685
  log(`\u68C0\u6D4B\u5230\u73B0\u6709\u670D\u52A1 (PID: ${existing.pid}, \u7AEF\u53E3: ${existing.port})\uFF0C\u9A8C\u8BC1\u4E2D...`);
28637
- const valid = await verifyExistingService(existing.port);
28638
- if (valid) {
28639
- actualPort = existing.port;
28640
- isMainInstance = false;
28641
- log(`\u2705 \u590D\u7528\u73B0\u6709\u670D\u52A1\uFF0C\u7AEF\u53E3 ${actualPort}`);
28642
- return null;
28686
+ const probe = await probeExistingService(existing.port);
28687
+ if (probe?.service === "ghost-bridge") {
28688
+ if (probe.token === WS_TOKEN) {
28689
+ actualPort = existing.port;
28690
+ isMainInstance = false;
28691
+ log(`\u2705 \u590D\u7528\u73B0\u6709\u670D\u52A1\uFF0C\u7AEF\u53E3 ${actualPort}`);
28692
+ return null;
28693
+ }
28694
+ const oldPid = Number(probe.pid) || existing.pid;
28695
+ await stopExistingService(oldPid, existing.port);
28643
28696
  } else {
28644
28697
  log(`\u274C \u73B0\u6709\u670D\u52A1\u9A8C\u8BC1\u5931\u8D25\uFF0C\u542F\u52A8\u65B0\u670D\u52A1...`);
28645
28698
  try {
@@ -28649,14 +28702,19 @@ async function initWebSocketService() {
28649
28702
  }
28650
28703
  }
28651
28704
  if (!await isPortAvailable(BASE_PORT)) {
28652
- const valid = await verifyExistingService(BASE_PORT);
28653
- if (valid) {
28654
- actualPort = BASE_PORT;
28655
- isMainInstance = false;
28656
- log(`\u2705 \u590D\u7528\u56FA\u5B9A\u7AEF\u53E3\u4E0A\u7684\u73B0\u6709\u670D\u52A1\uFF0C\u7AEF\u53E3 ${actualPort}`);
28657
- return null;
28705
+ const probe = await probeExistingService(BASE_PORT);
28706
+ if (probe?.service === "ghost-bridge") {
28707
+ if (probe.token === WS_TOKEN) {
28708
+ actualPort = BASE_PORT;
28709
+ isMainInstance = false;
28710
+ log(`\u2705 \u590D\u7528\u56FA\u5B9A\u7AEF\u53E3\u4E0A\u7684\u73B0\u6709\u670D\u52A1\uFF0C\u7AEF\u53E3 ${actualPort}`);
28711
+ return null;
28712
+ }
28713
+ await stopExistingService(Number(probe.pid), BASE_PORT);
28714
+ }
28715
+ if (!await isPortAvailable(BASE_PORT)) {
28716
+ 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`);
28658
28717
  }
28659
- 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`);
28660
28718
  }
28661
28719
  const wss2 = await startWebSocketServer();
28662
28720
  isMainInstance = true;
@@ -28666,7 +28724,7 @@ async function initWebSocketService() {
28666
28724
  port: actualPort,
28667
28725
  wsUrl: `ws://localhost:${actualPort}`,
28668
28726
  pid: process.pid,
28669
- startedAt: (/* @__PURE__ */ new Date()).toISOString()
28727
+ startedAt: SERVER_STARTED_AT
28670
28728
  }, null, 2)
28671
28729
  );
28672
28730
  log(`\u{1F4DD} \u7AEF\u53E3\u4FE1\u606F\u5DF2\u5199\u5165: ${PORT_INFO_FILE}`);
@@ -28678,6 +28736,18 @@ if (wss) {
28678
28736
  const url2 = new URL(req.url || "/", "http://localhost");
28679
28737
  const token = url2.searchParams.get("token") || "";
28680
28738
  const role = url2.searchParams.get("role") || "";
28739
+ if (role === "probe") {
28740
+ ws.send(JSON.stringify({
28741
+ type: "identity",
28742
+ service: "ghost-bridge",
28743
+ token: WS_TOKEN,
28744
+ pid: process.pid,
28745
+ port: actualPort,
28746
+ startedAt: SERVER_STARTED_AT
28747
+ }));
28748
+ ws.close(1e3, "Probe complete");
28749
+ return;
28750
+ }
28681
28751
  if (WS_TOKEN && token !== WS_TOKEN) {
28682
28752
  log(`\u62D2\u7EDD\u8FDE\u63A5\uFF1Atoken \u4E0D\u5339\u914D (\u6536\u5230: ${token}, \u671F\u671B: ${WS_TOKEN})`);
28683
28753
  ws.close(1008, "Bad token");
@@ -1,13 +1,8 @@
1
- // 使用当月1号0点的时间戳作为 token,确保同月内的服务器和插件自动匹配
2
- function getMonthlyToken() {
3
- const now = new Date()
4
- const firstDayOfMonth = new Date(now.getFullYear(), now.getMonth(), 1, 0, 0, 0, 0)
5
- return String(firstDayOfMonth.getTime())
6
- }
1
+ const DEFAULT_TOKEN = 'ghost-bridge-local'
7
2
 
8
3
  const CONFIG = {
9
4
  basePort: 33333,
10
- token: getMonthlyToken(),
5
+ token: DEFAULT_TOKEN,
11
6
  autoDetach: false,
12
7
  maxErrors: 100,
13
8
  maxStackFrames: 20,
@@ -1846,6 +1841,7 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
1846
1841
  CONFIG.basePort = message.port
1847
1842
  chrome.storage.local.set({ basePort: message.port })
1848
1843
  }
1844
+ CONFIG.token = DEFAULT_TOKEN
1849
1845
  state.enabled = true
1850
1846
  state.connected = false
1851
1847
  state.port = null
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Ghost Bridge",
4
- "version": "0.6.2",
4
+ "version": "0.7.0",
5
5
  "description": "Zero-restart Chrome debugger bridge for Claude MCP, optimized for no-sourcemap production debugging.",
6
6
  "permissions": [
7
7
  "debugger",
@@ -15,11 +15,7 @@ function log(msg) {
15
15
  chrome.runtime.sendMessage({ type: 'log', msg }).catch(() => {})
16
16
  }
17
17
 
18
- function getMonthlyToken() {
19
- const now = new Date()
20
- const firstDayOfMonth = new Date(now.getFullYear(), now.getMonth(), 1, 0, 0, 0, 0)
21
- return String(firstDayOfMonth.getTime())
22
- }
18
+ const DEFAULT_TOKEN = 'ghost-bridge-local'
23
19
 
24
20
  // 连接到服务器
25
21
  function connect() {
@@ -74,7 +70,9 @@ function connect() {
74
70
  port: port,
75
71
  }).catch(() => {})
76
72
  } else {
77
- terminalErrorMessage = `Port ${port} is occupied by a non-matching service, or the token does not match.`
73
+ terminalErrorMessage = msg.service === 'ghost-bridge'
74
+ ? `Port ${port} is running ghost-bridge, but the token does not match.`
75
+ : `Port ${port} is occupied by a non-matching service.`
78
76
  log('身份验证失败,将在固定端口上重试...')
79
77
  chrome.runtime.sendMessage({
80
78
  type: 'status',
@@ -159,7 +157,7 @@ function disconnect() {
159
157
  chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
160
158
  if (message.type === 'connect') {
161
159
  config.basePort = message.basePort || 33333
162
- config.token = message.token || getMonthlyToken()
160
+ config.token = message.token || DEFAULT_TOKEN
163
161
  disconnect()
164
162
  manualDisconnect = false // 用户重新连接,清除断开标志
165
163
  connect()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ghost-bridge",
3
- "version": "0.6.2",
3
+ "version": "0.7.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.",