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 +6 -2
- package/dist/cli.js +4 -4
- package/dist/server.js +100 -30
- package/extension/background.js +3 -7
- package/extension/manifest.json +1 -1
- package/extension/offscreen.js +5 -7
- package/package.json +1 -1
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
|
|
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 |
|
|
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
|
|
5514
|
-
shouldCreate:
|
|
5515
|
-
isAvailable: () =>
|
|
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
|
-
|
|
28540
|
-
|
|
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
|
|
28580
|
+
function probeExistingService(port) {
|
|
28584
28581
|
return new Promise((resolve) => {
|
|
28585
28582
|
const url2 = new URL(`ws://localhost:${port}`);
|
|
28586
|
-
|
|
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
|
-
|
|
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
|
-
|
|
28597
|
-
ws.close();
|
|
28598
|
-
resolve(true);
|
|
28605
|
+
finish(msg);
|
|
28599
28606
|
}
|
|
28600
28607
|
} catch {
|
|
28601
28608
|
}
|
|
28602
28609
|
});
|
|
28603
28610
|
ws.on("error", () => {
|
|
28604
|
-
|
|
28605
|
-
resolve(false);
|
|
28611
|
+
finish(null);
|
|
28606
28612
|
});
|
|
28607
28613
|
ws.on("close", () => {
|
|
28608
|
-
|
|
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
|
|
28638
|
-
if (
|
|
28639
|
-
|
|
28640
|
-
|
|
28641
|
-
|
|
28642
|
-
|
|
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
|
|
28653
|
-
if (
|
|
28654
|
-
|
|
28655
|
-
|
|
28656
|
-
|
|
28657
|
-
|
|
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:
|
|
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");
|
package/extension/background.js
CHANGED
|
@@ -1,13 +1,8 @@
|
|
|
1
|
-
|
|
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:
|
|
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
|
package/extension/manifest.json
CHANGED
package/extension/offscreen.js
CHANGED
|
@@ -15,11 +15,7 @@ function log(msg) {
|
|
|
15
15
|
chrome.runtime.sendMessage({ type: 'log', msg }).catch(() => {})
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
|
|
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 =
|
|
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 ||
|
|
160
|
+
config.token = message.token || DEFAULT_TOKEN
|
|
163
161
|
disconnect()
|
|
164
162
|
manualDisconnect = false // 用户重新连接,清除断开标志
|
|
165
163
|
connect()
|