@dynamicu/chromedebug-mcp 2.3.1 → 2.4.1

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 CHANGED
@@ -102,10 +102,35 @@ Chrome Debug includes a Chrome extension that enables visual element selection f
102
102
 
103
103
  ### Installing the Extension
104
104
 
105
+ #### Option 1: Chrome Web Store (Recommended - FREE Version)
106
+
107
+ Install the FREE version directly from the Chrome Web Store:
108
+
109
+ 🌐 **[Install from Chrome Web Store](https://chromewebstore.google.com/detail/lemgbmdnephoaniipapgeciebfeakffn?utm_source=item-share-cb)**
110
+
111
+ - āœ… 5 recordings per day
112
+ - āœ… Full MCP integration
113
+ - āœ… Visual element selection
114
+ - āœ… Automatic updates
115
+ - āœ… No manual installation needed
116
+
117
+ #### Option 2: Manual Installation (Development)
118
+
105
119
  1. Open Chrome and navigate to `chrome://extensions/`
106
- 2. Enable "Developer mode"
120
+ 2. Enable "Developer mode"
107
121
  3. Click "Load unpacked" and select the `chrome-extension` directory
108
122
 
123
+ #### Option 3: PRO Version (Unlimited)
124
+
125
+ For unlimited recordings and advanced features, purchase the PRO version:
126
+
127
+ šŸ’Ž **[Get PRO Version](https://dynamicu.lemonsqueezy.com/buy/641143)**
128
+
129
+ - āœ… Unlimited recordings
130
+ - āœ… Advanced function tracing
131
+ - āœ… Enhanced capture features
132
+ - āœ… Priority support
133
+
109
134
  ### Using the Extension
110
135
 
111
136
  1. **Element Selection**
@@ -123,6 +148,56 @@ Chrome Debug includes a Chrome extension that enables visual element selection f
123
148
  - Console logs can be included in the recording
124
149
  - Workflows can be replayed later for debugging
125
150
 
151
+ ### Managing License Activations
152
+
153
+ Chrome Debug uses a license activation system to prevent activation limit exhaustion when reinstalling the extension.
154
+
155
+ #### The Problem
156
+
157
+ When you uninstall and reinstall the Chrome extension, it clears local storage and generates a new activation instance. With a typical 3-activation limit, frequent reinstalls can exhaust your available slots.
158
+
159
+ #### The Solution: Activation Manager
160
+
161
+ Chrome Debug now includes an **Activation Manager** that automatically handles this scenario:
162
+
163
+ **How It Works:**
164
+ 1. When activation limit is reached, the Activation Manager opens automatically
165
+ 2. Shows all your current activations with device info (e.g., "macOS 14.0 • Chrome 121")
166
+ 3. Identifies your current device (highlighted in green)
167
+ 4. You select which activation to deactivate
168
+ 5. Extension automatically retries activation on current device
169
+
170
+ **Example: Reinstalling on Same Machine**
171
+
172
+ ```
173
+ You reinstall the extension on your laptop:
174
+ → Try to activate license
175
+ → Activation Manager shows:
176
+ • Activation 1: "macOS 14.0 • Chrome 120" (Oct 10) ← OLD instance
177
+ • Activation 2: "Windows 11 • Chrome 121" (Oct 12)
178
+ • Activation 3: "Linux • Firefox 119" (Oct 14)
179
+ → You recognize #1 is this same laptop
180
+ → Click "Deactivate" on #1
181
+ → Extension automatically activates new instance
182
+ → Result: Still 3/3 slots, seamless experience
183
+ ```
184
+
185
+ **Best Practices:**
186
+ - Before uninstalling: No action needed - Activation Manager handles it
187
+ - Multiple devices: Activation Manager helps track which is which
188
+ - Unused devices: Periodically deactivate machines you no longer use
189
+
190
+ #### FAQ
191
+
192
+ **Q: What happens if I forget which activation is which?**
193
+ A: The Activation Manager shows device information (OS, browser version) and activation dates. Your current device is highlighted in green.
194
+
195
+ **Q: Can I deactivate my current device?**
196
+ A: No, the button is disabled for your current device to prevent accidental lockout.
197
+
198
+ **Q: Does reinstalling the browser consume an activation?**
199
+ A: Yes, because local storage is cleared. Use the Activation Manager to deactivate the old instance first or after reinstalling.
200
+
126
201
 
127
202
  ## Development
128
203
 
@@ -0,0 +1,208 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Manage License Activations</title>
6
+ <style>
7
+ body {
8
+ width: 450px;
9
+ padding: 20px;
10
+ font-family: Arial, sans-serif;
11
+ background: #f5f5f5;
12
+ }
13
+
14
+ h2 {
15
+ margin: 0 0 10px 0;
16
+ font-size: 20px;
17
+ color: #333;
18
+ }
19
+
20
+ .subtitle {
21
+ font-size: 14px;
22
+ color: #666;
23
+ margin-bottom: 20px;
24
+ }
25
+
26
+ .limit-info {
27
+ background: #fff3cd;
28
+ border: 1px solid #ffc107;
29
+ padding: 15px;
30
+ border-radius: 4px;
31
+ margin-bottom: 20px;
32
+ font-size: 14px;
33
+ color: #856404;
34
+ }
35
+
36
+ .limit-info strong {
37
+ color: #333;
38
+ }
39
+
40
+ .activations-list {
41
+ background: white;
42
+ border-radius: 4px;
43
+ border: 1px solid #e0e0e0;
44
+ max-height: 400px;
45
+ overflow-y: auto;
46
+ }
47
+
48
+ .activation-item {
49
+ padding: 15px;
50
+ border-bottom: 1px solid #e0e0e0;
51
+ transition: background 0.2s;
52
+ }
53
+
54
+ .activation-item:last-child {
55
+ border-bottom: none;
56
+ }
57
+
58
+ .activation-item:hover {
59
+ background: #f9f9f9;
60
+ }
61
+
62
+ .activation-item.current-device {
63
+ background: #e8f5e9;
64
+ border-left: 4px solid #4caf50;
65
+ }
66
+
67
+ .activation-header {
68
+ display: flex;
69
+ justify-content: space-between;
70
+ align-items: center;
71
+ margin-bottom: 10px;
72
+ }
73
+
74
+ .activation-number {
75
+ font-weight: bold;
76
+ font-size: 16px;
77
+ color: #333;
78
+ }
79
+
80
+ .current-badge {
81
+ background: #4caf50;
82
+ color: white;
83
+ padding: 2px 8px;
84
+ border-radius: 12px;
85
+ font-size: 11px;
86
+ font-weight: bold;
87
+ }
88
+
89
+ .device-info {
90
+ font-size: 14px;
91
+ color: #666;
92
+ margin-bottom: 8px;
93
+ }
94
+
95
+ .device-info strong {
96
+ color: #333;
97
+ }
98
+
99
+ .activation-date {
100
+ font-size: 12px;
101
+ color: #999;
102
+ margin-bottom: 10px;
103
+ }
104
+
105
+ .deactivate-btn {
106
+ padding: 8px 16px;
107
+ background: #f44336;
108
+ color: white;
109
+ border: none;
110
+ border-radius: 4px;
111
+ cursor: pointer;
112
+ font-size: 13px;
113
+ font-weight: 500;
114
+ transition: background 0.2s;
115
+ }
116
+
117
+ .deactivate-btn:hover {
118
+ background: #d32f2f;
119
+ }
120
+
121
+ .deactivate-btn:disabled {
122
+ background: #ccc;
123
+ cursor: not-allowed;
124
+ }
125
+
126
+ .actions {
127
+ margin-top: 20px;
128
+ display: flex;
129
+ gap: 10px;
130
+ }
131
+
132
+ .cancel-btn {
133
+ flex: 1;
134
+ padding: 10px;
135
+ background: #9e9e9e;
136
+ color: white;
137
+ border: none;
138
+ border-radius: 4px;
139
+ cursor: pointer;
140
+ font-size: 14px;
141
+ font-weight: 500;
142
+ }
143
+
144
+ .cancel-btn:hover {
145
+ background: #757575;
146
+ }
147
+
148
+ .loading {
149
+ text-align: center;
150
+ padding: 40px;
151
+ font-size: 14px;
152
+ color: #666;
153
+ }
154
+
155
+ .error {
156
+ background: #ffebee;
157
+ border: 1px solid #f44336;
158
+ padding: 15px;
159
+ border-radius: 4px;
160
+ color: #c62828;
161
+ font-size: 14px;
162
+ margin-bottom: 20px;
163
+ }
164
+
165
+ .spinner {
166
+ border: 3px solid #f3f3f3;
167
+ border-top: 3px solid #2196F3;
168
+ border-radius: 50%;
169
+ width: 30px;
170
+ height: 30px;
171
+ animation: spin 1s linear infinite;
172
+ margin: 20px auto;
173
+ }
174
+
175
+ @keyframes spin {
176
+ 0% { transform: rotate(0deg); }
177
+ 100% { transform: rotate(360deg); }
178
+ }
179
+ </style>
180
+ </head>
181
+ <body>
182
+ <h2>āš ļø Activation Limit Reached</h2>
183
+ <p class="subtitle">Please deactivate an existing activation to continue.</p>
184
+
185
+ <div id="error-message" class="error" style="display: none;"></div>
186
+
187
+ <div id="limit-info" class="limit-info" style="display: none;">
188
+ <strong>License Status:</strong> <span id="activation-status">Loading...</span>
189
+ </div>
190
+
191
+ <div id="loading" class="loading">
192
+ <div class="spinner"></div>
193
+ <p>Loading activations...</p>
194
+ </div>
195
+
196
+ <div id="activations-container" style="display: none;">
197
+ <div class="activations-list" id="activations-list">
198
+ <!-- Activations will be populated here -->
199
+ </div>
200
+
201
+ <div class="actions">
202
+ <button class="cancel-btn" id="cancel-btn">Cancel</button>
203
+ </div>
204
+ </div>
205
+
206
+ <script type="module" src="activation-manager.js"></script>
207
+ </body>
208
+ </html>
@@ -0,0 +1,187 @@
1
+ // Activation Manager for Chrome Debug Extension
2
+ import { FirebaseLicenseClient } from './firebase-client.js';
3
+
4
+ const licenseClient = new FirebaseLicenseClient();
5
+
6
+ let licenseKey = '';
7
+ let currentInstanceId = '';
8
+ let activations = [];
9
+
10
+ // Initialize on page load
11
+ document.addEventListener('DOMContentLoaded', async () => {
12
+ await loadActivations();
13
+
14
+ // Set up event listeners
15
+ document.getElementById('cancel-btn').addEventListener('click', () => {
16
+ window.close();
17
+ });
18
+ });
19
+
20
+ /**
21
+ * Load activations from storage and Firebase
22
+ */
23
+ async function loadActivations() {
24
+ try {
25
+ // Get license key and instance ID from storage
26
+ const stored = await chrome.storage.local.get(['ls_license_key', 'ls_instance_id', 'chromedebug_instance_id']);
27
+ licenseKey = stored.ls_license_key;
28
+ currentInstanceId = stored.chromedebug_instance_id;
29
+
30
+ if (!licenseKey) {
31
+ showError('License key not found. Please activate your license first.');
32
+ return;
33
+ }
34
+
35
+ // Fetch activations from Firebase
36
+ const data = await licenseClient.listActivations(licenseKey);
37
+ activations = data.activations || [];
38
+
39
+ // Show activation status
40
+ document.getElementById('activation-status').textContent =
41
+ `${data.activationUsage} of ${data.activationLimit} activations used`;
42
+ document.getElementById('limit-info').style.display = 'block';
43
+
44
+ // Hide loading, show activations
45
+ document.getElementById('loading').style.display = 'none';
46
+ document.getElementById('activations-container').style.display = 'block';
47
+
48
+ // Render activations list
49
+ renderActivations();
50
+
51
+ } catch (error) {
52
+ console.error('[Activation Manager] Error loading activations:', error);
53
+ showError(`Failed to load activations: ${error.message}`);
54
+ document.getElementById('loading').style.display = 'none';
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Render the list of activations
60
+ */
61
+ function renderActivations() {
62
+ const container = document.getElementById('activations-list');
63
+ container.innerHTML = '';
64
+
65
+ if (activations.length === 0) {
66
+ container.innerHTML = '<div style="padding: 20px; text-align: center; color: #666;">No activations found.</div>';
67
+ return;
68
+ }
69
+
70
+ activations.forEach((activation, index) => {
71
+ const item = document.createElement('div');
72
+ item.className = 'activation-item';
73
+
74
+ // Check if this is the current device
75
+ const isCurrentDevice = activation.name === currentInstanceId ||
76
+ activation.identifier === currentInstanceId;
77
+
78
+ if (isCurrentDevice) {
79
+ item.classList.add('current-device');
80
+ }
81
+
82
+ // Format date
83
+ const date = new Date(activation.createdAt);
84
+ const formattedDate = date.toLocaleString('en-US', {
85
+ month: 'short',
86
+ day: 'numeric',
87
+ year: 'numeric',
88
+ hour: '2-digit',
89
+ minute: '2-digit'
90
+ });
91
+
92
+ // Build device info
93
+ let deviceDisplay = 'Unknown Device';
94
+ if (activation.deviceInfo) {
95
+ deviceDisplay = activation.deviceInfo.deviceName ||
96
+ `${activation.deviceInfo.platform || 'Unknown'} • ${activation.deviceInfo.browser || 'Unknown'}`;
97
+ } else if (activation.name && activation.name !== currentInstanceId) {
98
+ deviceDisplay = activation.name;
99
+ }
100
+
101
+ item.innerHTML = `
102
+ <div class="activation-header">
103
+ <span class="activation-number">Activation ${index + 1}</span>
104
+ ${isCurrentDevice ? '<span class="current-badge">CURRENT DEVICE</span>' : ''}
105
+ </div>
106
+ <div class="device-info">
107
+ <strong>${deviceDisplay}</strong>
108
+ </div>
109
+ <div class="activation-date">
110
+ Activated on ${formattedDate}
111
+ </div>
112
+ <button
113
+ class="deactivate-btn"
114
+ data-instance-id="${activation.identifier}"
115
+ ${isCurrentDevice ? 'disabled title="Cannot deactivate current device"' : ''}
116
+ >
117
+ ${isCurrentDevice ? 'āœ“ Current Device' : 'Deactivate'}
118
+ </button>
119
+ `;
120
+
121
+ // Add click handler for deactivate button
122
+ if (!isCurrentDevice) {
123
+ const deactivateBtn = item.querySelector('.deactivate-btn');
124
+ deactivateBtn.addEventListener('click', () => {
125
+ handleDeactivate(activation.identifier, index + 1);
126
+ });
127
+ }
128
+
129
+ container.appendChild(item);
130
+ });
131
+ }
132
+
133
+ /**
134
+ * Handle deactivation of an instance
135
+ */
136
+ async function handleDeactivate(instanceId, activationNumber) {
137
+ const confirmed = confirm(
138
+ `Are you sure you want to deactivate Activation ${activationNumber}?\n\n` +
139
+ `This will free up an activation slot, allowing you to activate on this device.`
140
+ );
141
+
142
+ if (!confirmed) {
143
+ return;
144
+ }
145
+
146
+ try {
147
+ // Disable all deactivate buttons immediately
148
+ const buttons = document.querySelectorAll('.deactivate-btn');
149
+ buttons.forEach(btn => btn.disabled = true);
150
+
151
+ // Call deactivate API
152
+ const result = await licenseClient.deactivateInstance(licenseKey, instanceId);
153
+
154
+ if (result.deactivated) {
155
+ // Show success message
156
+ alert('Instance deactivated successfully! Click OK to retry activation.');
157
+
158
+ // Close this window and retry activation
159
+ // Send message to popup to retry activation
160
+ chrome.runtime.sendMessage({
161
+ type: 'RETRY_ACTIVATION',
162
+ licenseKey: licenseKey
163
+ });
164
+
165
+ window.close();
166
+ } else {
167
+ throw new Error('Deactivation failed');
168
+ }
169
+
170
+ } catch (error) {
171
+ console.error('[Activation Manager] Deactivation error:', error);
172
+ showError(`Failed to deactivate: ${error.message}`);
173
+
174
+ // Re-enable buttons
175
+ const buttons = document.querySelectorAll('.deactivate-btn:not([data-current])');
176
+ buttons.forEach(btn => btn.disabled = false);
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Show error message
182
+ */
183
+ function showError(message) {
184
+ const errorDiv = document.getElementById('error-message');
185
+ errorDiv.textContent = message;
186
+ errorDiv.style.display = 'block';
187
+ }
@@ -29,6 +29,68 @@ class FirebaseLicenseClient {
29
29
  return instanceId;
30
30
  }
31
31
 
32
+ /**
33
+ * Collect device context information for activation tracking
34
+ * @returns {Object} Device information
35
+ */
36
+ getDeviceInfo() {
37
+ const ua = navigator.userAgent;
38
+ let browser = "Unknown";
39
+ let browserVersion = "Unknown";
40
+ let platform = "Unknown";
41
+
42
+ // Detect browser
43
+ if (ua.indexOf("Chrome") > -1 && ua.indexOf("Edg") === -1) {
44
+ browser = "Chrome";
45
+ const match = ua.match(/Chrome\/(\d+)/);
46
+ browserVersion = match ? match[1] : "Unknown";
47
+ } else if (ua.indexOf("Edg") > -1) {
48
+ browser = "Edge";
49
+ const match = ua.match(/Edg\/(\d+)/);
50
+ browserVersion = match ? match[1] : "Unknown";
51
+ } else if (ua.indexOf("Firefox") > -1) {
52
+ browser = "Firefox";
53
+ const match = ua.match(/Firefox\/(\d+)/);
54
+ browserVersion = match ? match[1] : "Unknown";
55
+ } else if (ua.indexOf("Safari") > -1) {
56
+ browser = "Safari";
57
+ const match = ua.match(/Version\/(\d+)/);
58
+ browserVersion = match ? match[1] : "Unknown";
59
+ }
60
+
61
+ // Detect platform
62
+ if (ua.indexOf("Win") > -1) {
63
+ platform = "Windows";
64
+ // Try to extract Windows version
65
+ if (ua.indexOf("Windows NT 10.0") > -1) platform = "Windows 10/11";
66
+ else if (ua.indexOf("Windows NT 6.3") > -1) platform = "Windows 8.1";
67
+ else if (ua.indexOf("Windows NT 6.2") > -1) platform = "Windows 8";
68
+ else if (ua.indexOf("Windows NT 6.1") > -1) platform = "Windows 7";
69
+ } else if (ua.indexOf("Mac") > -1) {
70
+ platform = "macOS";
71
+ // Try to extract macOS version from user agent
72
+ const match = ua.match(/Mac OS X (\d+[._]\d+)/);
73
+ if (match) {
74
+ const version = match[1].replace('_', '.');
75
+ platform = `macOS ${version}`;
76
+ }
77
+ } else if (ua.indexOf("Linux") > -1) {
78
+ platform = "Linux";
79
+ } else if (ua.indexOf("CrOS") > -1) {
80
+ platform = "Chrome OS";
81
+ }
82
+
83
+ return {
84
+ platform,
85
+ browser,
86
+ browserVersion,
87
+ userAgent: ua,
88
+ screenResolution: `${screen.width}x${screen.height}`,
89
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
90
+ language: navigator.language
91
+ };
92
+ }
93
+
32
94
  /**
33
95
  * Activate license with LemonSqueezy (first-time activation)
34
96
  * @param {string} licenseKey - License key to activate
@@ -36,16 +98,24 @@ class FirebaseLicenseClient {
36
98
  */
37
99
  async activateLicense(licenseKey) {
38
100
  const deviceId = await this.generateInstanceId();
101
+ const deviceInfo = this.getDeviceInfo();
39
102
 
40
103
  try {
41
104
  console.log(`[License] Activating license ${licenseKey} with device ${deviceId}`);
105
+ console.log('[License] Device info:', deviceInfo);
42
106
 
43
107
  const response = await fetch(`${this.functionsUrl}/activateLicense`, {
44
108
  method: 'POST',
45
109
  headers: {'Content-Type': 'application/json'},
46
110
  body: JSON.stringify({
47
111
  licenseKey: licenseKey.trim(),
48
- instanceName: deviceId
112
+ instanceName: deviceId,
113
+ deviceInfo: {
114
+ platform: deviceInfo.platform,
115
+ browser: deviceInfo.browser,
116
+ browserVersion: deviceInfo.browserVersion,
117
+ deviceName: `${deviceInfo.platform} • ${deviceInfo.browser} ${deviceInfo.browserVersion}`
118
+ }
49
119
  })
50
120
  });
51
121
 
@@ -273,6 +343,70 @@ class FirebaseLicenseClient {
273
343
  async clearLicenseCache() {
274
344
  await chrome.storage.local.remove(this.cacheKey);
275
345
  }
346
+
347
+ /**
348
+ * List all activations for a license key
349
+ * @param {string} licenseKey - License key
350
+ * @returns {Promise<{activations: Array, activationLimit: number, activationUsage: number, availableSlots: number}>}
351
+ */
352
+ async listActivations(licenseKey) {
353
+ try {
354
+ console.log(`[License] Listing activations for ${licenseKey}`);
355
+
356
+ const response = await fetch(`${this.functionsUrl}/listActivations`, {
357
+ method: 'POST',
358
+ headers: {'Content-Type': 'application/json'},
359
+ body: JSON.stringify({ licenseKey: licenseKey.trim() })
360
+ });
361
+
362
+ if (!response.ok) {
363
+ const errorData = await response.json();
364
+ throw new Error(errorData.error || `HTTP ${response.status}`);
365
+ }
366
+
367
+ const data = await response.json();
368
+ console.log('[License] Activations:', data);
369
+
370
+ return data;
371
+ } catch (error) {
372
+ console.error('[License] Failed to list activations:', error);
373
+ throw error;
374
+ }
375
+ }
376
+
377
+ /**
378
+ * Deactivate a specific license instance
379
+ * @param {string} licenseKey - License key
380
+ * @param {string} instanceId - Instance ID to deactivate
381
+ * @returns {Promise<{deactivated: boolean, message?: string}>}
382
+ */
383
+ async deactivateInstance(licenseKey, instanceId) {
384
+ try {
385
+ console.log(`[License] Deactivating instance ${instanceId}`);
386
+
387
+ const response = await fetch(`${this.functionsUrl}/deactivateInstance`, {
388
+ method: 'POST',
389
+ headers: {'Content-Type': 'application/json'},
390
+ body: JSON.stringify({
391
+ licenseKey: licenseKey.trim(),
392
+ instanceId
393
+ })
394
+ });
395
+
396
+ if (!response.ok) {
397
+ const errorData = await response.json();
398
+ throw new Error(errorData.error || `HTTP ${response.status}`);
399
+ }
400
+
401
+ const data = await response.json();
402
+ console.log('[License] Deactivation result:', data);
403
+
404
+ return data;
405
+ } catch (error) {
406
+ console.error('[License] Failed to deactivate instance:', error);
407
+ throw error;
408
+ }
409
+ }
276
410
  }
277
411
 
278
412
  export { FirebaseLicenseClient };
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "manifest_version": 3,
3
- "name": "ChromeDebug MCP Assistant FREE v2.1.1",
4
- "version": "2.1.1",
5
- "description": "ChromeDebug MCP visual element selector [FREE Edition] [Build: 2025-01-20-v2.1.1]",
3
+ "name": "ChromeDebug MCP Assistant FREE v2.4.0",
4
+ "version": "2.4.0",
5
+ "description": "ChromeDebug MCP visual element selector [FREE Edition] [Build: 2025-01-20-v2.4.0]",
6
6
  "permissions": [
7
7
  "activeTab",
8
8
  "scripting",
@@ -880,6 +880,8 @@ async function handleLicenseActivation() {
880
880
 
881
881
  const result = await licenseClient.validateLicense(licenseKey);
882
882
  console.log('[License] Validation result:', result);
883
+ console.log('[License] Error value:', result.error);
884
+ console.log('[License] Error type:', typeof result.error);
883
885
 
884
886
  if (result.valid) {
885
887
  messageDiv.textContent = 'License activated successfully!';
@@ -887,8 +889,54 @@ async function handleLicenseActivation() {
887
889
  document.getElementById('license-key-input').value = '';
888
890
  await initializeLicenseUI(); // Refresh UI
889
891
  } else {
890
- messageDiv.textContent = result.error || 'Invalid license key';
891
- messageDiv.style.color = '#f44336';
892
+ // Check if activation limit reached
893
+ console.log('[License] Checking if activation limit reached...');
894
+ console.log('[License] result.error === "ACTIVATION_LIMIT_REACHED":', result.error === 'ACTIVATION_LIMIT_REACHED');
895
+ console.log('[License] result.error includes "activation limit":', result.error && result.error.toLowerCase().includes('activation limit'));
896
+
897
+ if (result.error === 'ACTIVATION_LIMIT_REACHED' ||
898
+ (result.error && result.error.toLowerCase().includes('activation limit'))) {
899
+ console.log('[License] Activation limit reached, opening activation manager');
900
+ messageDiv.textContent = 'Opening activation manager...';
901
+ messageDiv.style.color = '#ff9800';
902
+
903
+ // Store the license key temporarily so activation manager can access it
904
+ await chrome.storage.local.set({
905
+ 'ls_license_key': licenseKey.trim(),
906
+ 'activation_manager_open': true
907
+ });
908
+
909
+ // Open activation manager in a new window
910
+ const width = 500;
911
+ const height = 600;
912
+ const left = Math.round((screen.width - width) / 2);
913
+ const top = Math.round((screen.height - height) / 2);
914
+
915
+ const activationManagerUrl = chrome.runtime.getURL('activation-manager.html');
916
+ console.log('[License] Opening activation manager at URL:', activationManagerUrl);
917
+
918
+ chrome.windows.create({
919
+ url: activationManagerUrl,
920
+ type: 'popup',
921
+ width: width,
922
+ height: height,
923
+ left: left,
924
+ top: top
925
+ }, (window) => {
926
+ if (chrome.runtime.lastError) {
927
+ console.error('[License] Failed to open activation manager:', chrome.runtime.lastError);
928
+ messageDiv.textContent = 'Error: Could not open activation manager. See console.';
929
+ messageDiv.style.color = '#f44336';
930
+ } else {
931
+ console.log('[License] Activation manager window created:', window);
932
+ messageDiv.textContent = 'Please deactivate an existing activation to continue';
933
+ messageDiv.style.color = '#ff9800';
934
+ }
935
+ });
936
+ } else {
937
+ messageDiv.textContent = result.error || 'Invalid license key';
938
+ messageDiv.style.color = '#f44336';
939
+ }
892
940
  }
893
941
  }
894
942
 
@@ -898,6 +946,24 @@ function handleUpgradeClick() {
898
946
  chrome.tabs.create({url: LEMONSQUEEZY_CHECKOUT_URL});
899
947
  }
900
948
 
949
+ // Listen for messages from activation manager
950
+ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
951
+ if (message.type === 'RETRY_ACTIVATION' && message.licenseKey) {
952
+ console.log('[License] Retrying activation after deactivation');
953
+
954
+ // Set the license key in the input
955
+ const licenseKeyInput = document.getElementById('license-key-input');
956
+ if (licenseKeyInput) {
957
+ licenseKeyInput.value = message.licenseKey;
958
+ }
959
+
960
+ // Retry activation
961
+ handleLicenseActivation().then(() => {
962
+ console.log('[License] Retry activation complete');
963
+ });
964
+ }
965
+ });
966
+
901
967
  document.addEventListener('DOMContentLoaded', () => {
902
968
  console.log('DOM loaded, initializing popup');
903
969
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dynamicu/chromedebug-mcp",
3
- "version": "2.3.1",
3
+ "version": "2.4.1",
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",
@@ -56,7 +56,9 @@
56
56
  "tbv:microtest": "cd chrome-extension && npm run tbv:microtest",
57
57
  "build:free": "webpack --config scripts/webpack.config.free.cjs",
58
58
  "build:pro": "webpack --config scripts/webpack.config.pro.cjs",
59
- "build:both": "npm run build:free && npm run build:pro"
59
+ "build:both": "npm run build:free && npm run build:pro",
60
+ "package-pro": "node scripts/package-pro-extension.js",
61
+ "package-webstore": "node scripts/package-webstore-extension.js"
60
62
  },
61
63
  "keywords": [
62
64
  "mcp",
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Package PRO Chrome extension for LemonSqueezy distribution
5
+ * Creates a .zip file with the PRO version including activation manager
6
+ */
7
+
8
+ import { createWriteStream, promises as fs } from 'fs';
9
+ import { join, dirname } from 'path';
10
+ import { fileURLToPath } from 'url';
11
+ import { execSync } from 'child_process';
12
+ import archiver from 'archiver';
13
+
14
+ const __filename = fileURLToPath(import.meta.url);
15
+ const __dirname = dirname(__filename);
16
+ const rootDir = join(__dirname, '..');
17
+ const proExtensionDir = join(rootDir, 'scripts', 'dist', 'pro');
18
+ const outputDir = join(rootDir, 'dist');
19
+ const outputFile = join(outputDir, 'chromedebug-extension-pro.zip');
20
+
21
+ async function packageProExtension() {
22
+ console.log('šŸ“¦ Building and packaging PRO Chrome extension...');
23
+
24
+ // Step 1: Build the pro version
25
+ console.log('šŸ”Ø Building PRO version...');
26
+ try {
27
+ execSync('npm run build:pro', {
28
+ cwd: rootDir,
29
+ stdio: 'inherit'
30
+ });
31
+ } catch (error) {
32
+ console.error('āŒ Failed to build PRO version');
33
+ throw error;
34
+ }
35
+
36
+ // Step 2: Verify the build output exists
37
+ try {
38
+ await fs.access(proExtensionDir);
39
+ } catch {
40
+ throw new Error(`Build output not found at ${proExtensionDir}`);
41
+ }
42
+
43
+ // Step 3: Ensure output directory exists
44
+ try {
45
+ await fs.mkdir(outputDir, { recursive: true });
46
+ } catch (error) {
47
+ // Directory might already exist, that's fine
48
+ }
49
+
50
+ // Step 4: Create zip file
51
+ console.log('šŸ“¦ Creating PRO zip package...');
52
+
53
+ const output = createWriteStream(outputFile);
54
+ const archive = archiver('zip', { zlib: { level: 9 } });
55
+
56
+ return new Promise((resolve, reject) => {
57
+ output.on('close', () => {
58
+ const sizeKB = (archive.pointer() / 1024).toFixed(2);
59
+ console.log(`āœ… PRO extension packaged successfully!`);
60
+ console.log(` Size: ${sizeKB} KB`);
61
+ console.log(` Location: ${outputFile}`);
62
+ console.log(` šŸ“Œ This is the PRO version with activation manager`);
63
+ console.log(` šŸ”’ For LemonSqueezy distribution only`);
64
+ resolve();
65
+ });
66
+
67
+ archive.on('error', reject);
68
+
69
+ archive.pipe(output);
70
+
71
+ // Add all files from the pro build directory
72
+ archive.directory(proExtensionDir, false);
73
+
74
+ archive.finalize();
75
+ });
76
+ }
77
+
78
+ packageProExtension().catch(err => {
79
+ console.error('āŒ Failed to package PRO extension:', err);
80
+ process.exit(1);
81
+ });
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Package FREE Chrome extension for Chrome Web Store distribution
5
+ * Creates a .zip file optimized for Chrome Web Store submission
6
+ */
7
+
8
+ import { createWriteStream, promises as fs } from 'fs';
9
+ import { join, dirname } from 'path';
10
+ import { fileURLToPath } from 'url';
11
+ import { execSync } from 'child_process';
12
+ import archiver from 'archiver';
13
+
14
+ const __filename = fileURLToPath(import.meta.url);
15
+ const __dirname = dirname(__filename);
16
+ const rootDir = join(__dirname, '..');
17
+ const freeExtensionDir = join(rootDir, 'scripts', 'dist', 'free');
18
+ const outputDir = join(rootDir, 'dist');
19
+ const outputFile = join(outputDir, 'chromedebug-webstore-free.zip');
20
+
21
+ async function packageWebStoreExtension() {
22
+ console.log('šŸ“¦ Building and packaging FREE Chrome extension for Web Store...');
23
+
24
+ // Step 1: Build the free version
25
+ console.log('šŸ”Ø Building free version...');
26
+ try {
27
+ execSync('npm run build:free', {
28
+ cwd: rootDir,
29
+ stdio: 'inherit'
30
+ });
31
+ } catch (error) {
32
+ console.error('āŒ Failed to build free version');
33
+ throw error;
34
+ }
35
+
36
+ // Step 2: Verify the build output exists
37
+ try {
38
+ await fs.access(freeExtensionDir);
39
+ } catch {
40
+ throw new Error(`Build output not found at ${freeExtensionDir}`);
41
+ }
42
+
43
+ // Step 3: Ensure output directory exists
44
+ try {
45
+ await fs.mkdir(outputDir, { recursive: true });
46
+ } catch (error) {
47
+ // Directory might already exist, that's fine
48
+ }
49
+
50
+ // Step 4: Create zip file optimized for Chrome Web Store
51
+ console.log('šŸ“¦ Creating Chrome Web Store package...');
52
+
53
+ const output = createWriteStream(outputFile);
54
+ const archive = archiver('zip', {
55
+ zlib: { level: 9 }
56
+ });
57
+
58
+ return new Promise((resolve, reject) => {
59
+ output.on('close', () => {
60
+ const sizeKB = (archive.pointer() / 1024).toFixed(2);
61
+ console.log(`āœ… Chrome Web Store package created successfully!`);
62
+ console.log(` Size: ${sizeKB} KB`);
63
+ console.log(` Location: ${outputFile}`);
64
+ console.log(` šŸ“Œ This is the FREE version for Chrome Web Store`);
65
+ console.log(` 🌐 Ready to upload to: https://chrome.google.com/webstore/devconsole`);
66
+ console.log(`\nšŸ“‹ Next steps:`);
67
+ console.log(` 1. Go to Chrome Web Store Developer Dashboard`);
68
+ console.log(` 2. Upload this zip file`);
69
+ console.log(` 3. Fill in store listing details`);
70
+ console.log(` 4. Submit for review`);
71
+ resolve();
72
+ });
73
+
74
+ archive.on('error', reject);
75
+
76
+ archive.pipe(output);
77
+
78
+ // Add all files from the free build directory
79
+ // Chrome Web Store expects all files to be in the root of the zip
80
+ archive.directory(freeExtensionDir, false);
81
+
82
+ archive.finalize();
83
+ });
84
+ }
85
+
86
+ packageWebStoreExtension().catch(err => {
87
+ console.error('āŒ Failed to package Chrome Web Store extension:', err);
88
+ process.exit(1);
89
+ });
@@ -37,6 +37,7 @@ module.exports = {
37
37
  { from: 'chrome-extension/popup.html', to: 'popup.html' },
38
38
  { from: 'chrome-extension/options.html', to: 'options.html' },
39
39
  { from: 'chrome-extension/offscreen.html', to: 'offscreen.html' },
40
+ { from: 'chrome-extension/activation-manager.html', to: 'activation-manager.html' },
40
41
 
41
42
  // Copy icons
42
43
  { from: 'chrome-extension/*.png', to: '[name][ext]' },
@@ -60,6 +61,8 @@ module.exports = {
60
61
  { from: 'chrome-extension/license-helper.js', to: 'license-helper.js' },
61
62
  { from: 'chrome-extension/firebase-client.js', to: 'firebase-client.js' },
62
63
  { from: 'chrome-extension/firebase-config.js', to: 'firebase-config.js' },
64
+ { from: 'chrome-extension/firebase-config.module.js', to: 'firebase-config.module.js' },
65
+ { from: 'chrome-extension/activation-manager.js', to: 'activation-manager.js' },
63
66
  { from: 'chrome-extension/frame-capture.js', to: 'frame-capture.js' },
64
67
  { from: 'chrome-extension/chrome-session-manager.js', to: 'chrome-session-manager.js' },
65
68
 
@@ -38,6 +38,7 @@ module.exports = {
38
38
  { from: 'chrome-extension/popup.html', to: 'popup.html' },
39
39
  { from: 'chrome-extension/options.html', to: 'options.html' },
40
40
  { from: 'chrome-extension/offscreen.html', to: 'offscreen.html' },
41
+ { from: 'chrome-extension/activation-manager.html', to: 'activation-manager.html' },
41
42
  { from: 'chrome-extension/pro/frame-editor.html', to: 'pro/frame-editor.html' },
42
43
 
43
44
  // Copy icons
@@ -62,6 +63,8 @@ module.exports = {
62
63
  { from: 'chrome-extension/license-helper.js', to: 'license-helper.js' },
63
64
  { from: 'chrome-extension/firebase-client.js', to: 'firebase-client.js' },
64
65
  { from: 'chrome-extension/firebase-config.js', to: 'firebase-config.js' },
66
+ { from: 'chrome-extension/firebase-config.module.js', to: 'firebase-config.module.js' },
67
+ { from: 'chrome-extension/activation-manager.js', to: 'activation-manager.js' },
65
68
  { from: 'chrome-extension/frame-capture.js', to: 'frame-capture.js' },
66
69
  { from: 'chrome-extension/chrome-session-manager.js', to: 'chrome-session-manager.js' },
67
70