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/extension/popup.js
CHANGED
|
@@ -1,88 +1,161 @@
|
|
|
1
1
|
// popup.js - Ghost Bridge 弹窗逻辑
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const dotWrapper = document.getElementById('dotWrapper')
|
|
4
|
+
const statusCard = document.getElementById('statusCard')
|
|
4
5
|
const statusText = document.getElementById('statusText')
|
|
5
|
-
const
|
|
6
|
+
const detailContainer = document.getElementById('detailContainer')
|
|
7
|
+
const portVal = document.getElementById('portVal')
|
|
8
|
+
const tabRow = document.getElementById('tabRow')
|
|
9
|
+
const tabVal = document.getElementById('tabVal')
|
|
10
|
+
const errorRow = document.getElementById('errorRow')
|
|
11
|
+
const errorVal = document.getElementById('errorVal')
|
|
12
|
+
const errorList = document.getElementById('errorList')
|
|
13
|
+
const headerGhost = document.getElementById('headerGhost')
|
|
14
|
+
|
|
6
15
|
const connectBtn = document.getElementById('connectBtn')
|
|
7
16
|
const disconnectBtn = document.getElementById('disconnectBtn')
|
|
8
17
|
const scanInfo = document.getElementById('scanInfo')
|
|
9
18
|
|
|
10
|
-
// 状态稳定性控制:防止闪烁
|
|
11
19
|
let lastStableStatus = null
|
|
12
20
|
let pendingStatus = null
|
|
13
21
|
let statusChangeTimer = null
|
|
14
|
-
const STATUS_DEBOUNCE_MS = 300
|
|
22
|
+
const STATUS_DEBOUNCE_MS = 300
|
|
15
23
|
|
|
16
|
-
// 状态映射
|
|
17
24
|
const STATUS_MAP = {
|
|
18
25
|
connected: {
|
|
19
|
-
|
|
20
|
-
text: '
|
|
26
|
+
statusClass: 'connected',
|
|
27
|
+
text: 'ON / Attached',
|
|
21
28
|
},
|
|
22
29
|
connecting: {
|
|
23
|
-
|
|
24
|
-
text: '
|
|
30
|
+
statusClass: 'connecting',
|
|
31
|
+
text: 'Scanning...',
|
|
25
32
|
},
|
|
26
33
|
verifying: {
|
|
27
|
-
|
|
28
|
-
text: '
|
|
34
|
+
statusClass: 'connecting',
|
|
35
|
+
text: 'Verifying Auth...',
|
|
29
36
|
},
|
|
30
37
|
scanning: {
|
|
31
|
-
|
|
32
|
-
text: '
|
|
38
|
+
statusClass: 'connecting',
|
|
39
|
+
text: 'Searching...',
|
|
33
40
|
},
|
|
34
41
|
not_found: {
|
|
35
|
-
|
|
36
|
-
text: '
|
|
42
|
+
statusClass: 'disconnected',
|
|
43
|
+
text: 'Not Found',
|
|
37
44
|
},
|
|
38
45
|
disconnected: {
|
|
39
|
-
|
|
40
|
-
text: '
|
|
46
|
+
statusClass: 'disconnected',
|
|
47
|
+
text: 'Disconnected',
|
|
41
48
|
},
|
|
42
49
|
error: {
|
|
43
|
-
|
|
44
|
-
text: '
|
|
50
|
+
statusClass: 'error',
|
|
51
|
+
text: 'Connection Error',
|
|
45
52
|
},
|
|
46
53
|
}
|
|
47
54
|
|
|
48
|
-
// 实际执行 UI 更新
|
|
49
55
|
function renderUI(state) {
|
|
50
|
-
const { status, port, scanRound, enabled, currentPort, basePort } = state
|
|
56
|
+
const { status, port, scanRound, enabled, currentPort, basePort, errorCount, recentErrors, tabTitle } = state
|
|
51
57
|
const config = STATUS_MAP[status] || STATUS_MAP.disconnected
|
|
52
58
|
|
|
53
|
-
|
|
54
|
-
|
|
59
|
+
// Update classes for color & animations
|
|
60
|
+
dotWrapper.className = `status-dot-wrapper ${config.statusClass}`
|
|
61
|
+
|
|
62
|
+
// Update Ghost & Body Animation State
|
|
63
|
+
if (config.statusClass === 'connected') {
|
|
64
|
+
headerGhost.className = 'ghost-wrapper ghost-connected'
|
|
65
|
+
document.body.className = 'connected-state'
|
|
66
|
+
} else if (config.statusClass === 'connecting') {
|
|
67
|
+
headerGhost.className = 'ghost-wrapper ghost-connecting'
|
|
68
|
+
document.body.className = 'connecting-state'
|
|
69
|
+
} else {
|
|
70
|
+
headerGhost.className = 'ghost-wrapper ghost-disconnected'
|
|
71
|
+
document.body.className = 'disconnected-state'
|
|
72
|
+
}
|
|
55
73
|
|
|
56
|
-
//
|
|
74
|
+
// Animate text change
|
|
75
|
+
if (statusText.textContent !== config.text) {
|
|
76
|
+
statusText.style.opacity = '0'
|
|
77
|
+
setTimeout(() => {
|
|
78
|
+
statusText.textContent = config.text
|
|
79
|
+
statusText.style.opacity = '1'
|
|
80
|
+
}, 150)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Update Detail Container
|
|
57
84
|
if (status === 'connected' && port) {
|
|
58
|
-
|
|
85
|
+
portVal.textContent = port
|
|
86
|
+
portVal.className = 'detail-value highlight'
|
|
87
|
+
|
|
88
|
+
if (tabTitle) {
|
|
89
|
+
tabRow.classList.remove('hidden')
|
|
90
|
+
tabVal.textContent = tabTitle
|
|
91
|
+
tabVal.title = tabTitle
|
|
92
|
+
} else {
|
|
93
|
+
tabRow.classList.add('hidden')
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
errorRow.classList.remove('hidden')
|
|
97
|
+
errorVal.textContent = errorCount || 0
|
|
98
|
+
errorVal.className = errorCount > 0 ? 'detail-value warning' : 'detail-value'
|
|
99
|
+
|
|
100
|
+
// Render error list
|
|
101
|
+
if (recentErrors && recentErrors.length > 0) {
|
|
102
|
+
errorList.innerHTML = recentErrors.map(err => {
|
|
103
|
+
const text = err.text ? err.text.substring(0, 100) : 'Unknown Error'
|
|
104
|
+
const file = err.url ? err.url.split('/').pop() : 'inline'
|
|
105
|
+
const loc = err.line ? `${file}:${err.line}` : file
|
|
106
|
+
return `
|
|
107
|
+
<div class="error-item" title="${err.text || ''}">
|
|
108
|
+
<div class="err-msg">${text}${err.text && err.text.length > 100 ? '...' : ''}</div>
|
|
109
|
+
<div class="err-loc">${loc}</div>
|
|
110
|
+
</div>
|
|
111
|
+
`
|
|
112
|
+
}).join('')
|
|
113
|
+
} else {
|
|
114
|
+
errorList.innerHTML = '<div class="error-item" style="text-align:center;color:#64748b;border:none;">No recent errors recorded.</div>'
|
|
115
|
+
errorList.classList.add('collapsed')
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
detailContainer.classList.remove('collapsed')
|
|
59
119
|
} else if ((status === 'connecting' || status === 'verifying' || status === 'scanning') && currentPort) {
|
|
60
|
-
const roundText = scanRound > 0 ?
|
|
61
|
-
|
|
120
|
+
const roundText = scanRound > 0 ? ` [R${scanRound + 1}]` : ''
|
|
121
|
+
portVal.textContent = `Scanning: ${currentPort}${roundText}`
|
|
122
|
+
portVal.className = 'detail-value highlight'
|
|
123
|
+
tabRow.classList.add('hidden')
|
|
124
|
+
errorRow.classList.add('hidden')
|
|
125
|
+
detailContainer.classList.remove('collapsed')
|
|
126
|
+
} else if (status === 'disconnected') {
|
|
127
|
+
detailContainer.classList.add('collapsed')
|
|
62
128
|
} else if (status === 'not_found') {
|
|
63
|
-
|
|
129
|
+
portVal.textContent = 'Launch Claude Code'
|
|
130
|
+
portVal.className = 'detail-value warning'
|
|
131
|
+
tabRow.classList.add('hidden')
|
|
132
|
+
errorRow.classList.add('hidden')
|
|
133
|
+
detailContainer.classList.remove('collapsed')
|
|
64
134
|
} else {
|
|
65
|
-
|
|
135
|
+
detailContainer.classList.add('collapsed')
|
|
66
136
|
}
|
|
67
137
|
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
|
|
138
|
+
// Button States
|
|
139
|
+
if (status === 'connecting' || status === 'scanning' || status === 'verifying') {
|
|
140
|
+
connectBtn.textContent = 'Connecting...'
|
|
141
|
+
connectBtn.disabled = true
|
|
142
|
+
} else {
|
|
143
|
+
connectBtn.textContent = enabled ? 'Reconnect' : 'Connect'
|
|
144
|
+
connectBtn.disabled = false
|
|
145
|
+
}
|
|
71
146
|
|
|
72
|
-
//
|
|
147
|
+
// Scan info text
|
|
73
148
|
if ((status === 'connecting' || status === 'scanning') && scanRound > 2) {
|
|
74
|
-
scanInfo.textContent =
|
|
75
|
-
scanInfo.
|
|
149
|
+
scanInfo.textContent = `Round ${scanRound}: Is your MCP Server running?`
|
|
150
|
+
scanInfo.classList.remove('collapsed')
|
|
76
151
|
} else {
|
|
77
|
-
scanInfo.
|
|
152
|
+
scanInfo.classList.add('collapsed')
|
|
78
153
|
}
|
|
79
154
|
}
|
|
80
155
|
|
|
81
|
-
// 更新 UI 状态(带防抖,防止闪烁)
|
|
82
156
|
function updateUI(state) {
|
|
83
157
|
const newStatus = state.status
|
|
84
158
|
|
|
85
|
-
// 如果是首次加载或状态相同,直接更新
|
|
86
159
|
if (lastStableStatus === null || newStatus === lastStableStatus) {
|
|
87
160
|
lastStableStatus = newStatus
|
|
88
161
|
pendingStatus = null
|
|
@@ -94,10 +167,7 @@ function updateUI(state) {
|
|
|
94
167
|
return
|
|
95
168
|
}
|
|
96
169
|
|
|
97
|
-
// 状态变化:从 connected 变为其他状态时需要防抖
|
|
98
|
-
// 防止短暂的状态波动导致 UI 闪烁
|
|
99
170
|
if (lastStableStatus === 'connected' && newStatus !== 'connected') {
|
|
100
|
-
// 需要持续一段时间才确认断开
|
|
101
171
|
if (pendingStatus !== newStatus) {
|
|
102
172
|
pendingStatus = newStatus
|
|
103
173
|
if (statusChangeTimer) clearTimeout(statusChangeTimer)
|
|
@@ -108,11 +178,9 @@ function updateUI(state) {
|
|
|
108
178
|
renderUI(state)
|
|
109
179
|
}, STATUS_DEBOUNCE_MS)
|
|
110
180
|
}
|
|
111
|
-
// 暂不更新 UI,等待确认
|
|
112
181
|
return
|
|
113
182
|
}
|
|
114
183
|
|
|
115
|
-
// 其他状态变化(如从 scanning 到 connected)立即更新
|
|
116
184
|
lastStableStatus = newStatus
|
|
117
185
|
pendingStatus = null
|
|
118
186
|
if (statusChangeTimer) {
|
|
@@ -122,7 +190,6 @@ function updateUI(state) {
|
|
|
122
190
|
renderUI(state)
|
|
123
191
|
}
|
|
124
192
|
|
|
125
|
-
// 从 background 获取状态
|
|
126
193
|
async function fetchStatus() {
|
|
127
194
|
try {
|
|
128
195
|
const response = await chrome.runtime.sendMessage({ type: 'getStatus' })
|
|
@@ -130,36 +197,44 @@ async function fetchStatus() {
|
|
|
130
197
|
updateUI(response)
|
|
131
198
|
}
|
|
132
199
|
} catch (e) {
|
|
133
|
-
console.error('
|
|
200
|
+
console.error('Fetch status failed:', e)
|
|
134
201
|
}
|
|
135
202
|
}
|
|
136
203
|
|
|
137
|
-
// 启用连接(自动扫描端口)
|
|
138
204
|
connectBtn.addEventListener('click', async () => {
|
|
139
205
|
try {
|
|
206
|
+
// Add visual click feedback
|
|
207
|
+
connectBtn.textContent = 'Connecting...'
|
|
208
|
+
connectBtn.disabled = true
|
|
140
209
|
await chrome.runtime.sendMessage({ type: 'connect' })
|
|
141
|
-
setTimeout(fetchStatus,
|
|
210
|
+
setTimeout(fetchStatus, 150)
|
|
142
211
|
} catch (e) {
|
|
143
|
-
console.error('
|
|
212
|
+
console.error('Connect failed:', e)
|
|
144
213
|
}
|
|
145
214
|
})
|
|
146
215
|
|
|
147
|
-
// 断开连接
|
|
148
216
|
disconnectBtn.addEventListener('click', async () => {
|
|
149
217
|
try {
|
|
150
218
|
await chrome.runtime.sendMessage({ type: 'disconnect' })
|
|
151
|
-
setTimeout(fetchStatus,
|
|
219
|
+
setTimeout(fetchStatus, 50)
|
|
152
220
|
} catch (e) {
|
|
153
|
-
console.error('
|
|
221
|
+
console.error('Disconnect failed:', e)
|
|
154
222
|
}
|
|
155
223
|
})
|
|
156
224
|
|
|
157
|
-
// 初始加载
|
|
158
225
|
fetchStatus()
|
|
159
226
|
|
|
160
|
-
// 监听 background 主动推送的状态变化
|
|
161
227
|
chrome.runtime.onMessage.addListener((message) => {
|
|
162
228
|
if (message.type === 'statusUpdate') {
|
|
163
229
|
updateUI(message.state)
|
|
164
230
|
}
|
|
165
231
|
})
|
|
232
|
+
|
|
233
|
+
// Error List Toggle Logic
|
|
234
|
+
errorRow.addEventListener('click', () => {
|
|
235
|
+
if (errorList.classList.contains('collapsed')) {
|
|
236
|
+
errorList.classList.remove('collapsed')
|
|
237
|
+
} else {
|
|
238
|
+
errorList.classList.add('collapsed')
|
|
239
|
+
}
|
|
240
|
+
})
|
package/package.json
CHANGED
|
@@ -1,9 +1,32 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ghost-bridge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.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.",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"mcp",
|
|
9
|
+
"chrome",
|
|
10
|
+
"debugger",
|
|
11
|
+
"ai",
|
|
12
|
+
"claude",
|
|
13
|
+
"cdp",
|
|
14
|
+
"llm",
|
|
15
|
+
"copilot"
|
|
16
|
+
],
|
|
17
|
+
"author": "<Author>",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "https://github.com/<Username>/ghost-bridge.git"
|
|
21
|
+
},
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/<Username>/ghost-bridge/issues"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://github.com/<Username>/ghost-bridge#readme",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=18.0.0"
|
|
29
|
+
},
|
|
7
30
|
"bin": {
|
|
8
31
|
"ghost-bridge": "./dist/cli.js"
|
|
9
32
|
},
|