ghost-bridge 0.7.1 → 0.9.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.
@@ -7,11 +7,13 @@ const detailContainer = document.getElementById('detailContainer')
7
7
  const portVal = document.getElementById('portVal')
8
8
  const tabRow = document.getElementById('tabRow')
9
9
  const tabVal = document.getElementById('tabVal')
10
- const errorRow = document.getElementById('errorRow')
11
- const errorVal = document.getElementById('errorVal')
12
- const errorList = document.getElementById('errorList')
10
+ const viewingRow = document.getElementById('viewingRow')
11
+ const viewingVal = document.getElementById('viewingVal')
13
12
  const headerGhost = document.getElementById('headerGhost')
14
13
 
14
+ const targetBtn = document.getElementById('targetBtn')
15
+ const targetBtnText = document.getElementById('targetBtnText')
16
+ const targetIcon = document.getElementById('targetIcon')
15
17
  const connectBtn = document.getElementById('connectBtn')
16
18
  const disconnectBtn = document.getElementById('disconnectBtn')
17
19
  const scanInfo = document.getElementById('scanInfo')
@@ -19,6 +21,7 @@ const scanInfo = document.getElementById('scanInfo')
19
21
  let lastStableStatus = null
20
22
  let pendingStatus = null
21
23
  let statusChangeTimer = null
24
+ let latestState = null
22
25
  const STATUS_DEBOUNCE_MS = 300
23
26
 
24
27
  const STATUS_MAP = {
@@ -53,8 +56,24 @@ const STATUS_MAP = {
53
56
  }
54
57
 
55
58
  function renderUI(state) {
56
- const { status, port, enabled, currentPort, basePort, connectionError, errorCount, recentErrors, tabTitle } = state
59
+ latestState = state
60
+ const {
61
+ status,
62
+ port,
63
+ enabled,
64
+ currentPort,
65
+ basePort,
66
+ connectionError,
67
+ targetMode = 'focused',
68
+ targetTab,
69
+ viewingTab,
70
+ tabTitle,
71
+ } = state
72
+ const isPinned = targetMode === 'pinned'
57
73
  const config = STATUS_MAP[status] || STATUS_MAP.disconnected
74
+ const statusLabel = status === 'connected'
75
+ ? isPinned ? 'ON / Pinned' : 'ON / Following'
76
+ : config.text
58
77
 
59
78
  // Update classes for color & animations
60
79
  dotWrapper.className = `status-dot-wrapper ${config.statusClass}`
@@ -75,10 +94,10 @@ function renderUI(state) {
75
94
  }
76
95
 
77
96
  // Animate text change
78
- if (statusText.textContent !== config.text) {
97
+ if (statusText.textContent !== statusLabel) {
79
98
  statusText.style.opacity = '0'
80
99
  setTimeout(() => {
81
- statusText.textContent = config.text
100
+ statusText.textContent = statusLabel
82
101
  statusText.style.opacity = '1'
83
102
  }, 150)
84
103
  }
@@ -88,34 +107,21 @@ function renderUI(state) {
88
107
  portVal.textContent = port
89
108
  portVal.className = 'detail-value highlight'
90
109
 
91
- if (tabTitle) {
110
+ const targetTitle = targetTab?.title || tabTitle
111
+ if (targetTitle) {
92
112
  tabRow.classList.remove('hidden')
93
- tabVal.textContent = tabTitle
94
- tabVal.title = tabTitle
113
+ tabVal.textContent = targetTitle
114
+ tabVal.title = targetTab?.url || targetTitle
95
115
  } else {
96
116
  tabRow.classList.add('hidden')
97
117
  }
98
118
 
99
- errorRow.classList.remove('hidden')
100
- errorVal.textContent = errorCount || 0
101
- errorVal.className = errorCount > 0 ? 'detail-value warning' : 'detail-value'
102
-
103
- // Render error list
104
- if (recentErrors && recentErrors.length > 0) {
105
- errorList.innerHTML = recentErrors.map(err => {
106
- const text = err.text ? err.text.substring(0, 100) : 'Unknown Error'
107
- const file = err.url ? err.url.split('/').pop() : 'inline'
108
- const loc = err.line ? `${file}:${err.line}` : file
109
- return `
110
- <div class="error-item" title="${err.text || ''}">
111
- <div class="err-msg">${text}${err.text && err.text.length > 100 ? '...' : ''}</div>
112
- <div class="err-loc">${loc}</div>
113
- </div>
114
- `
115
- }).join('')
119
+ if (isPinned && viewingTab && targetTab && viewingTab.id !== targetTab.id) {
120
+ viewingRow.classList.remove('hidden')
121
+ viewingVal.textContent = viewingTab.title || 'Untitled'
122
+ viewingVal.title = viewingTab.url || viewingTab.title || ''
116
123
  } else {
117
- errorList.innerHTML = '<div class="error-item" style="text-align:center;color:#64748b;border:none;">No recent errors recorded.</div>'
118
- errorList.classList.add('collapsed')
124
+ viewingRow.classList.add('hidden')
119
125
  }
120
126
 
121
127
  detailContainer.classList.remove('collapsed')
@@ -123,21 +129,21 @@ function renderUI(state) {
123
129
  portVal.textContent = `Connecting: ${currentPort}`
124
130
  portVal.className = 'detail-value highlight'
125
131
  tabRow.classList.add('hidden')
126
- errorRow.classList.add('hidden')
132
+ viewingRow.classList.add('hidden')
127
133
  detailContainer.classList.remove('collapsed')
128
134
  } else if (status === 'disconnected') {
129
135
  detailContainer.classList.add('collapsed')
130
136
  } else if (status === 'not_found') {
131
- portVal.textContent = 'Launch Claude Code'
137
+ portVal.textContent = 'No Bridge'
132
138
  portVal.className = 'detail-value warning'
133
139
  tabRow.classList.add('hidden')
134
- errorRow.classList.add('hidden')
140
+ viewingRow.classList.add('hidden')
135
141
  detailContainer.classList.remove('collapsed')
136
142
  } else if (status === 'error') {
137
143
  portVal.textContent = `Port ${currentPort || basePort || '-'} blocked`
138
144
  portVal.className = 'detail-value warning'
139
145
  tabRow.classList.add('hidden')
140
- errorRow.classList.add('hidden')
146
+ viewingRow.classList.add('hidden')
141
147
  detailContainer.classList.remove('collapsed')
142
148
  } else {
143
149
  detailContainer.classList.add('collapsed')
@@ -152,12 +158,24 @@ function renderUI(state) {
152
158
  connectBtn.disabled = false
153
159
  }
154
160
 
161
+ targetBtn.classList.toggle('pinned', isPinned)
162
+ targetBtnText.textContent = isPinned ? 'Locked' : 'Pin'
163
+ targetIcon.textContent = isPinned ? '●' : '○'
164
+ targetBtn.title = isPinned
165
+ ? 'Unpin target tab and follow the focused tab'
166
+ : 'Pin current tab as the Ghost Bridge target'
167
+ targetBtn.setAttribute('aria-label', targetBtn.title)
168
+ targetBtn.disabled = status !== 'connected' || !port
169
+
155
170
  // Scan info text
156
171
  if (status === 'error' && connectionError) {
157
172
  scanInfo.textContent = connectionError
158
173
  scanInfo.classList.remove('collapsed')
174
+ } else if (status === 'connected' && state.targetError) {
175
+ scanInfo.textContent = state.targetError
176
+ scanInfo.classList.remove('collapsed')
159
177
  } else if (status === 'not_found' && basePort) {
160
- scanInfo.textContent = `Port ${basePort} unavailable. Is your MCP server running on this port?`
178
+ scanInfo.textContent = connectionError || `No ghost-bridge WebSocket service was found on port ${basePort}. Start a configured MCP client, then reconnect.`
161
179
  scanInfo.classList.remove('collapsed')
162
180
  } else {
163
181
  scanInfo.classList.add('collapsed')
@@ -212,6 +230,27 @@ async function fetchStatus() {
212
230
  }
213
231
  }
214
232
 
233
+ targetBtn.addEventListener('click', async () => {
234
+ try {
235
+ const isPinned = latestState?.targetMode === 'pinned'
236
+ targetBtnText.textContent = isPinned ? 'Unlocking' : 'Pinning'
237
+ targetBtn.disabled = true
238
+ const response = await chrome.runtime.sendMessage({
239
+ type: isPinned ? 'unpinTab' : 'pinCurrentTab',
240
+ })
241
+ if (response && !response.ok) {
242
+ scanInfo.textContent = response.error || 'Target action failed'
243
+ scanInfo.classList.remove('collapsed')
244
+ }
245
+ } catch (e) {
246
+ console.error('Target action failed:', e)
247
+ scanInfo.textContent = e.message
248
+ scanInfo.classList.remove('collapsed')
249
+ } finally {
250
+ setTimeout(fetchStatus, 100)
251
+ }
252
+ })
253
+
215
254
  connectBtn.addEventListener('click', async () => {
216
255
  try {
217
256
  // Add visual click feedback
@@ -240,12 +279,3 @@ chrome.runtime.onMessage.addListener((message) => {
240
279
  updateUI(message.state)
241
280
  }
242
281
  })
243
-
244
- // Error List Toggle Logic
245
- errorRow.addEventListener('click', () => {
246
- if (errorList.classList.contains('collapsed')) {
247
- errorList.classList.remove('collapsed')
248
- } else {
249
- errorList.classList.add('collapsed')
250
- }
251
- })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ghost-bridge",
3
- "version": "0.7.1",
3
+ "version": "0.9.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.",
@@ -17,12 +17,12 @@
17
17
  "author": "<Author>",
18
18
  "repository": {
19
19
  "type": "git",
20
- "url": "https://github.com/<Username>/ghost-bridge.git"
20
+ "url": "https://github.com/learnStop163/ghost-bridge.git"
21
21
  },
22
22
  "bugs": {
23
- "url": "https://github.com/<Username>/ghost-bridge/issues"
23
+ "url": "https://github.com/learnStop163/ghost-bridge/issues"
24
24
  },
25
- "homepage": "https://github.com/<Username>/ghost-bridge#readme",
25
+ "homepage": "https://github.com/learnStop163/ghost-bridge#readme",
26
26
  "license": "MIT",
27
27
  "engines": {
28
28
  "node": ">=18.0.0"