@dynamicu/chromedebug-mcp 2.6.1 → 2.6.2

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.
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Disable React DevTools to prevent semver validation error
3
+ *
4
+ * This prevents the React DevTools browser extension from trying to inject
5
+ * into ChromeDebug's extension pages, which causes a console error:
6
+ * "Invalid argument not valid semver ('' received)"
7
+ *
8
+ * IMPORTANT: This only affects ChromeDebug extension pages (popup, options).
9
+ * It does NOT affect developers' React applications in other tabs.
10
+ * Each browser tab/window has its own isolated JavaScript context.
11
+ */
12
+
13
+ window.__REACT_DEVTOOLS_GLOBAL_HOOK__ = {
14
+ isDisabled: true,
15
+ supportsFiber: true,
16
+ inject: function() {},
17
+ onCommitFiberRoot: function() {},
18
+ onCommitFiberUnmount: function() {}
19
+ };
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "manifest_version": 3,
3
- "name": "ChromeDebug MCP Assistant FREE v2.5.0",
4
- "version": "2.5.0",
5
- "description": "ChromeDebug MCP visual element selector [FREE Edition] [Build: 2025-10-21-v2.5.0]",
3
+ "name": "ChromeDebug MCP Assistant FREE v2.6.0",
4
+ "version": "2.6.0",
5
+ "description": "ChromeDebug MCP visual element selector [FREE Edition] [Build: 2025-10-24-v2.6.0]",
6
6
  "permissions": [
7
7
  "activeTab",
8
8
  "scripting",
@@ -196,7 +196,8 @@
196
196
  <button id="reset" class="secondary">Reset to Default</button>
197
197
  </div>
198
198
  </div>
199
-
199
+
200
+ <script src="disable-react-devtools.js"></script>
200
201
  <script src="extension-config.js"></script>
201
202
  <script src="options.js"></script>
202
203
  </body>
@@ -164,6 +164,19 @@
164
164
  </div>
165
165
 
166
166
  <div id="license-message" style="margin-top: 5px; font-size: 12px; text-align: center;"></div>
167
+
168
+ <!-- Inline Activation Manager (shown when activation limit reached) -->
169
+ <div id="inline-activation-manager" style="display: none; margin-top: 15px; padding: 12px; background: #fff3cd; border: 1px solid #ffc107; border-radius: 4px; max-height: 0; overflow: hidden; transition: max-height 0.3s ease-out;">
170
+ <div style="font-size: 13px; font-weight: 500; color: #856404; margin-bottom: 10px;">
171
+ ⚠️ Activation Limit Reached
172
+ </div>
173
+ <div style="font-size: 12px; color: #856404; margin-bottom: 12px;">
174
+ Please deactivate an existing activation to continue
175
+ </div>
176
+ <div id="activations-list-inline" style="max-height: 250px; overflow-y: auto; background: white; border-radius: 4px; border: 1px solid #ddd;">
177
+ <!-- Activations will be loaded here dynamically -->
178
+ </div>
179
+ </div>
167
180
  </div>
168
181
 
169
182
  <div class="status">
@@ -526,7 +539,8 @@
526
539
  color: #c62828;
527
540
  }
528
541
  </style>
529
-
542
+
543
+ <script src="disable-react-devtools.js"></script>
530
544
  <script src="extension-config.js"></script>
531
545
  <script type="module" src="popup.js"></script>
532
546
  </body>
@@ -995,6 +995,193 @@ async function initializeLicenseUI() {
995
995
  }
996
996
  }
997
997
 
998
+ // Inline Activation Manager Functions
999
+ /**
1000
+ * Show the inline activation manager with slide-down animation
1001
+ */
1002
+ async function showInlineActivationManager(licenseKey) {
1003
+ const inlineManager = document.getElementById('inline-activation-manager');
1004
+ const activationsList = document.getElementById('activations-list-inline');
1005
+
1006
+ // Show the container
1007
+ inlineManager.style.display = 'block';
1008
+
1009
+ // Trigger animation
1010
+ setTimeout(() => {
1011
+ inlineManager.style.maxHeight = '400px';
1012
+ }, 10);
1013
+
1014
+ // Load activations
1015
+ try {
1016
+ activationsList.innerHTML = '<div style="padding: 15px; text-align: center; color: #666;">Loading activations...</div>';
1017
+
1018
+ // Get current instance ID
1019
+ const stored = await chrome.storage.local.get(['chromedebug_instance_id']);
1020
+ const currentInstanceId = stored.chromedebug_instance_id;
1021
+
1022
+ // Fetch activations from Firebase
1023
+ const data = await licenseClient.listActivations(licenseKey);
1024
+ const activations = data.activations || [];
1025
+
1026
+ // Render activations
1027
+ renderInlineActivations(activations, currentInstanceId, licenseKey);
1028
+
1029
+ } catch (error) {
1030
+ console.error('[License] Error loading activations:', error);
1031
+ activationsList.innerHTML = `<div style="padding: 15px; text-align: center; color: #f44336;">Failed to load activations: ${error.message}</div>`;
1032
+ }
1033
+ }
1034
+
1035
+ /**
1036
+ * Hide the inline activation manager with slide-up animation
1037
+ */
1038
+ function hideInlineActivationManager() {
1039
+ const inlineManager = document.getElementById('inline-activation-manager');
1040
+ inlineManager.style.maxHeight = '0';
1041
+
1042
+ setTimeout(() => {
1043
+ inlineManager.style.display = 'none';
1044
+ }, 300);
1045
+ }
1046
+
1047
+ /**
1048
+ * Render activations in the inline manager
1049
+ */
1050
+ function renderInlineActivations(activations, currentInstanceId, licenseKey) {
1051
+ const container = document.getElementById('activations-list-inline');
1052
+ container.innerHTML = '';
1053
+
1054
+ if (activations.length === 0) {
1055
+ container.innerHTML = '<div style="padding: 15px; text-align: center; color: #666;">No activations found.</div>';
1056
+ return;
1057
+ }
1058
+
1059
+ activations.forEach((activation, index) => {
1060
+ const item = document.createElement('div');
1061
+ item.style.cssText = 'padding: 12px; border-bottom: 1px solid #e0e0e0; transition: background 0.2s;';
1062
+ item.onmouseenter = () => item.style.background = '#f9f9f9';
1063
+ item.onmouseleave = () => item.style.background = isCurrentDevice ? '#e8f5e9' : 'white';
1064
+
1065
+ // Check if this is the current device
1066
+ const isCurrentDevice = activation.name === currentInstanceId || activation.identifier === currentInstanceId;
1067
+
1068
+ if (isCurrentDevice) {
1069
+ item.style.background = '#e8f5e9';
1070
+ item.style.borderLeft = '3px solid #4caf50';
1071
+ }
1072
+
1073
+ // Format date
1074
+ const date = new Date(activation.createdAt);
1075
+ const formattedDate = date.toLocaleString('en-US', {
1076
+ month: 'short',
1077
+ day: 'numeric',
1078
+ year: 'numeric',
1079
+ hour: '2-digit',
1080
+ minute: '2-digit'
1081
+ });
1082
+
1083
+ // Build device info
1084
+ let deviceDisplay = 'Unknown Device';
1085
+ if (activation.deviceInfo) {
1086
+ deviceDisplay = activation.deviceInfo.deviceName ||
1087
+ `${activation.deviceInfo.platform || 'Unknown'} • ${activation.deviceInfo.browser || 'Unknown'}`;
1088
+ } else if (activation.name && activation.name !== currentInstanceId) {
1089
+ deviceDisplay = activation.name;
1090
+ }
1091
+
1092
+ item.innerHTML = `
1093
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
1094
+ <span style="font-weight: 500; font-size: 13px; color: #333;">Activation ${index + 1}</span>
1095
+ ${isCurrentDevice ? '<span style="background: #4caf50; color: white; padding: 2px 6px; border-radius: 10px; font-size: 10px; font-weight: bold;">CURRENT</span>' : ''}
1096
+ </div>
1097
+ <div style="font-size: 12px; color: #666; margin-bottom: 4px;">
1098
+ <strong style="color: #333;">${deviceDisplay}</strong>
1099
+ </div>
1100
+ <div style="font-size: 11px; color: #999; margin-bottom: 8px;">
1101
+ ${formattedDate}
1102
+ </div>
1103
+ <button
1104
+ class="deactivate-btn-inline"
1105
+ data-instance-id="${activation.identifier}"
1106
+ data-activation-number="${index + 1}"
1107
+ data-license-key="${licenseKey}"
1108
+ style="padding: 5px 10px; background: ${isCurrentDevice ? '#ccc' : '#f44336'}; color: white; border: none; border-radius: 3px; cursor: ${isCurrentDevice ? 'not-allowed' : 'pointer'}; font-size: 11px; width: 100%;"
1109
+ ${isCurrentDevice ? 'disabled' : ''}
1110
+ >
1111
+ ${isCurrentDevice ? '✓ Current Device' : 'Deactivate'}
1112
+ </button>
1113
+ `;
1114
+
1115
+ // Add click handler for deactivate button
1116
+ if (!isCurrentDevice) {
1117
+ const deactivateBtn = item.querySelector('.deactivate-btn-inline');
1118
+ deactivateBtn.addEventListener('click', () => {
1119
+ handleInlineDeactivate(activation.identifier, index + 1, licenseKey);
1120
+ });
1121
+ }
1122
+
1123
+ container.appendChild(item);
1124
+ });
1125
+ }
1126
+
1127
+ /**
1128
+ * Handle deactivation of an instance (inline)
1129
+ */
1130
+ async function handleInlineDeactivate(instanceId, activationNumber, licenseKey) {
1131
+ const confirmed = confirm(
1132
+ `Are you sure you want to deactivate Activation ${activationNumber}?\n\n` +
1133
+ `This will free up an activation slot and automatically activate on this device.`
1134
+ );
1135
+
1136
+ if (!confirmed) {
1137
+ return;
1138
+ }
1139
+
1140
+ const messageDiv = document.getElementById('license-message');
1141
+
1142
+ try {
1143
+ // Disable all deactivate buttons
1144
+ const buttons = document.querySelectorAll('.deactivate-btn-inline');
1145
+ buttons.forEach(btn => btn.disabled = true);
1146
+
1147
+ messageDiv.textContent = 'Deactivating...';
1148
+ messageDiv.style.color = '#2196F3';
1149
+
1150
+ // Deactivate the instance
1151
+ const result = await licenseClient.deactivateInstance(licenseKey, instanceId);
1152
+ console.log('[License] Deactivation result:', result);
1153
+
1154
+ if (result.deactivated) {
1155
+ messageDiv.textContent = 'Deactivated! Activating on this device...';
1156
+ messageDiv.style.color = '#4caf50';
1157
+
1158
+ // Hide inline activation manager
1159
+ hideInlineActivationManager();
1160
+
1161
+ // Wait a moment, then retry activation
1162
+ setTimeout(async () => {
1163
+ // Set the license key in the input
1164
+ document.getElementById('license-key-input').value = licenseKey;
1165
+
1166
+ // Retry activation automatically
1167
+ await handleLicenseActivation();
1168
+ }, 1000);
1169
+
1170
+ } else {
1171
+ throw new Error(result.error || 'Deactivation failed');
1172
+ }
1173
+
1174
+ } catch (error) {
1175
+ console.error('[License] Deactivation error:', error);
1176
+ messageDiv.textContent = `Deactivation failed: ${error.message}`;
1177
+ messageDiv.style.color = '#f44336';
1178
+
1179
+ // Re-enable buttons
1180
+ const buttons = document.querySelectorAll('.deactivate-btn-inline:not([disabled])');
1181
+ buttons.forEach(btn => btn.disabled = false);
1182
+ }
1183
+ }
1184
+
998
1185
  // License activation handler
999
1186
  async function handleLicenseActivation() {
1000
1187
  const licenseKey = document.getElementById('license-key-input').value.trim();
@@ -1033,43 +1220,12 @@ async function handleLicenseActivation() {
1033
1220
 
1034
1221
  if (result.error === 'ACTIVATION_LIMIT_REACHED' ||
1035
1222
  (result.error && result.error.toLowerCase().includes('activation limit'))) {
1036
- console.log('[License] Activation limit reached, opening activation manager');
1037
- messageDiv.textContent = 'Opening activation manager...';
1223
+ console.log('[License] Activation limit reached, showing inline activation manager');
1224
+ messageDiv.textContent = 'Please deactivate an existing activation to continue';
1038
1225
  messageDiv.style.color = '#ff9800';
1039
1226
 
1040
- // Store the license key temporarily so activation manager can access it
1041
- await chrome.storage.local.set({
1042
- 'ls_license_key': licenseKey.trim(),
1043
- 'activation_manager_open': true
1044
- });
1045
-
1046
- // Open activation manager in a new window
1047
- const width = 500;
1048
- const height = 600;
1049
- const left = Math.round((screen.width - width) / 2);
1050
- const top = Math.round((screen.height - height) / 2);
1051
-
1052
- const activationManagerUrl = chrome.runtime.getURL('activation-manager.html');
1053
- console.log('[License] Opening activation manager at URL:', activationManagerUrl);
1054
-
1055
- chrome.windows.create({
1056
- url: activationManagerUrl,
1057
- type: 'popup',
1058
- width: width,
1059
- height: height,
1060
- left: left,
1061
- top: top
1062
- }, (window) => {
1063
- if (chrome.runtime.lastError) {
1064
- console.error('[License] Failed to open activation manager:', chrome.runtime.lastError);
1065
- messageDiv.textContent = 'Error: Could not open activation manager. See console.';
1066
- messageDiv.style.color = '#f44336';
1067
- } else {
1068
- console.log('[License] Activation manager window created:', window);
1069
- messageDiv.textContent = 'Please deactivate an existing activation to continue';
1070
- messageDiv.style.color = '#ff9800';
1071
- }
1072
- });
1227
+ // Show inline activation manager
1228
+ await showInlineActivationManager(licenseKey.trim());
1073
1229
  } else {
1074
1230
  messageDiv.textContent = result.error || 'Invalid license key';
1075
1231
  messageDiv.style.color = '#f44336';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dynamicu/chromedebug-mcp",
3
- "version": "2.6.1",
3
+ "version": "2.6.2",
4
4
  "description": "ChromeDebug MCP - MCP server that provides full control over a Chrome browser instance for debugging and automation with AI assistants like Claude Code",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -49,6 +49,7 @@ const filesToInclude = [
49
49
  'chrome-session-manager.js',
50
50
  'logger.js',
51
51
  'extension-config.js',
52
+ 'disable-react-devtools.js',
52
53
  'upload-manager.js',
53
54
  'firebase-client.js',
54
55
  { source: 'firebase-config.public.js', target: 'firebase-config.public.js' },
@@ -50,6 +50,7 @@ module.exports = {
50
50
  { from: 'chrome-extension/web-vitals.iife.js', to: 'web-vitals.iife.js' },
51
51
 
52
52
  // Copy supporting modules (these are imported by bundled scripts)
53
+ { from: 'chrome-extension/disable-react-devtools.js', to: 'disable-react-devtools.js' },
53
54
  { from: 'chrome-extension/pii-redactor.js', to: 'pii-redactor.js' },
54
55
  { from: 'chrome-extension/data-buffer.js', to: 'data-buffer.js' },
55
56
  { from: 'chrome-extension/performance-monitor.js', to: 'performance-monitor.js' },
@@ -52,6 +52,7 @@ module.exports = {
52
52
  { from: 'chrome-extension/web-vitals.iife.js', to: 'web-vitals.iife.js' },
53
53
 
54
54
  // Copy supporting modules (these are imported by bundled scripts)
55
+ { from: 'chrome-extension/disable-react-devtools.js', to: 'disable-react-devtools.js' },
55
56
  { from: 'chrome-extension/pii-redactor.js', to: 'pii-redactor.js' },
56
57
  { from: 'chrome-extension/data-buffer.js', to: 'data-buffer.js' },
57
58
  { from: 'chrome-extension/performance-monitor.js', to: 'performance-monitor.js' },
package/src/index.js CHANGED
@@ -260,5 +260,26 @@ For more information, see the documentation in CLAUDE.md
260
260
  }
261
261
 
262
262
  // Export for library usage and testing
263
- // This file is now safe to import without side effects
264
- export { ChromePilotApp };
263
+ export { ChromePilotApp };
264
+
265
+ // Auto-start when run directly (not imported as a module)
266
+ if (import.meta.url === `file://${process.argv[1]}`) {
267
+ const app = new ChromePilotApp();
268
+
269
+ // Handle graceful shutdown
270
+ process.on('SIGINT', async () => {
271
+ await app.shutdown();
272
+ process.exit(0);
273
+ });
274
+
275
+ process.on('SIGTERM', async () => {
276
+ await app.shutdown();
277
+ process.exit(0);
278
+ });
279
+
280
+ // Start the application
281
+ app.start().catch(error => {
282
+ logger.error('Failed to start ChromeDebug:', error);
283
+ process.exit(1);
284
+ });
285
+ }
package/src/logger.js CHANGED
@@ -77,15 +77,22 @@ export function logHttpRequest(logger, req, options = {}) {
77
77
  /**
78
78
  * Intercept stdout to prevent MCP JSON-RPC pollution
79
79
  * Call this at application startup for MCP servers
80
+ * Allows MCP JSON-RPC messages through while redirecting other output
80
81
  */
81
82
  export function interceptStdout() {
82
83
  const originalWrite = process.stdout.write;
83
84
 
84
85
  process.stdout.write = function(chunk, encoding, callback) {
85
- // Redirect all stdout to stderr with a warning prefix
86
86
  const message = chunk.toString();
87
- const redirectedMessage = `[STDOUT-REDIRECT] ${message}`;
88
87
 
88
+ // Allow MCP JSON-RPC messages through (they start with { and contain "jsonrpc")
89
+ if (message.trim().startsWith('{') && message.includes('"jsonrpc"')) {
90
+ // This is an MCP message - let it through to stdout unchanged
91
+ return originalWrite.call(process.stdout, chunk, encoding, callback);
92
+ }
93
+
94
+ // Redirect all other stdout to stderr with a warning prefix
95
+ const redirectedMessage = `[STDOUT-REDIRECT] ${message}`;
89
96
  return process.stderr.write(redirectedMessage, encoding, callback);
90
97
  };
91
98