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.
@@ -1,88 +1,161 @@
1
1
  // popup.js - Ghost Bridge 弹窗逻辑
2
2
 
3
- const statusDot = document.getElementById('statusDot')
3
+ const dotWrapper = document.getElementById('dotWrapper')
4
+ const statusCard = document.getElementById('statusCard')
4
5
  const statusText = document.getElementById('statusText')
5
- const statusDetail = document.getElementById('statusDetail')
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 // 状态变化需要持续 300ms 才生效
22
+ const STATUS_DEBOUNCE_MS = 300
15
23
 
16
- // 状态映射
17
24
  const STATUS_MAP = {
18
25
  connected: {
19
- dotClass: 'connected',
20
- text: ' 已连接',
26
+ statusClass: 'connected',
27
+ text: 'ON / Attached',
21
28
  },
22
29
  connecting: {
23
- dotClass: 'connecting',
24
- text: '🔍 正在扫描...',
30
+ statusClass: 'connecting',
31
+ text: 'Scanning...',
25
32
  },
26
33
  verifying: {
27
- dotClass: 'connecting',
28
- text: '🔐 验证身份...',
34
+ statusClass: 'connecting',
35
+ text: 'Verifying Auth...',
29
36
  },
30
37
  scanning: {
31
- dotClass: 'connecting',
32
- text: '📡 搜索服务...',
38
+ statusClass: 'connecting',
39
+ text: 'Searching...',
33
40
  },
34
41
  not_found: {
35
- dotClass: 'disconnected',
36
- text: '🔴 未找到服务',
42
+ statusClass: 'disconnected',
43
+ text: 'Not Found',
37
44
  },
38
45
  disconnected: {
39
- dotClass: 'disconnected',
40
- text: '未连接',
46
+ statusClass: 'disconnected',
47
+ text: 'Disconnected',
41
48
  },
42
49
  error: {
43
- dotClass: 'error',
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
- statusDot.className = `status-dot ${config.dotClass}`
54
- statusText.textContent = config.text
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
- statusDetail.textContent = `端口 ${port} · WebSocket 已建立`
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 ? `(第 ${scanRound + 1} 轮)` : ''
61
- statusDetail.textContent = `正在扫描 ${basePort}-${basePort + 9}${roundText}`
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
- statusDetail.textContent = '请确保 Claude Code 已启动'
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
- statusDetail.textContent = ''
135
+ detailContainer.classList.add('collapsed')
66
136
  }
67
137
 
68
- // 按钮状态
69
- connectBtn.textContent = enabled ? '重新连接' : '连接'
70
- connectBtn.disabled = false
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 = `已扫描 ${scanRound} 轮,请确保 Claude Code 已启动`
75
- scanInfo.style.color = '#ff9f0a'
149
+ scanInfo.textContent = `Round ${scanRound}: Is your MCP Server running?`
150
+ scanInfo.classList.remove('collapsed')
76
151
  } else {
77
- scanInfo.textContent = ''
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('获取状态失败:', e)
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, 100)
210
+ setTimeout(fetchStatus, 150)
142
211
  } catch (e) {
143
- console.error('连接失败:', e)
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, 100)
219
+ setTimeout(fetchStatus, 50)
152
220
  } catch (e) {
153
- console.error('断开失败:', e)
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.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
  },