@dynamicu/chromedebug-mcp 2.7.6 → 2.7.7
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 +24 -8
- package/chrome-extension/background.js +220 -0
- package/chrome-extension/popup.html +35 -0
- package/chrome-extension/popup.js +259 -0
- package/dist/chromedebug-extension-free.zip +0 -0
- package/package.json +2 -9
- package/scripts/diagnose-windows.js +2 -2
- package/scripts/postinstall.js +1 -1
- package/src/cli.js +1 -1
- package/src/commands/install-pro.js +7 -7
- package/src/commands/install-pro.js.bak +0 -542
package/README.md
CHANGED
|
@@ -126,17 +126,35 @@ Add to `~/.claude/config.json`:
|
|
|
126
126
|
</details>
|
|
127
127
|
|
|
128
128
|
<details>
|
|
129
|
-
<summary><strong>
|
|
129
|
+
<summary><strong>Troubleshooting</strong></summary>
|
|
130
130
|
|
|
131
|
-
|
|
131
|
+
### Running the Server Manually
|
|
132
|
+
|
|
133
|
+
If the MCP server isn't starting automatically, you can run it directly:
|
|
132
134
|
|
|
133
135
|
```bash
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
136
|
+
# Start the MCP server
|
|
137
|
+
chromedebug-mcp
|
|
138
|
+
|
|
139
|
+
# With verbose logging (shows connection details)
|
|
140
|
+
chromedebug-mcp --verbose
|
|
141
|
+
|
|
142
|
+
# View all options
|
|
143
|
+
chromedebug-mcp --help
|
|
137
144
|
```
|
|
138
145
|
|
|
139
|
-
|
|
146
|
+
### Common Issues
|
|
147
|
+
|
|
148
|
+
**Server not connecting?**
|
|
149
|
+
1. Ensure Chrome is closed before launching
|
|
150
|
+
2. Run `chromedebug-mcp --verbose` to see detailed logs
|
|
151
|
+
3. Check that port 3001 (or configured port) is available
|
|
152
|
+
|
|
153
|
+
**Extension not communicating?**
|
|
154
|
+
1. Check the extension popup shows "Connected" status
|
|
155
|
+
2. Reload the extension in `chrome://extensions/`
|
|
156
|
+
3. Restart Chrome with a fresh profile
|
|
157
|
+
|
|
140
158
|
</details>
|
|
141
159
|
|
|
142
160
|
---
|
|
@@ -154,8 +172,6 @@ chromedebug-mcp install-pro \
|
|
|
154
172
|
- 📺 [Demo Video](https://youtu.be/2Y9nIsvEjks)
|
|
155
173
|
- 🌐 [Chrome Extension](https://chromewebstore.google.com/detail/chromedebug-mcp-assistant/lemgbmdnephoaniipapgeciebfeakffn)
|
|
156
174
|
- 💎 [Get PRO](https://chromedebug.com/checkout/buy/996773cb-682b-430f-b9e3-9ce2130bd967)
|
|
157
|
-
- 🐛 [Report Issues](https://github.com/dynamicupgrade/ChromeDebug/issues)
|
|
158
|
-
- 💬 [Discussions](https://github.com/dynamicupgrade/ChromeDebug/discussions)
|
|
159
175
|
|
|
160
176
|
---
|
|
161
177
|
|
|
@@ -10,6 +10,7 @@ importScripts('upload-manager.js');
|
|
|
10
10
|
importScripts('chrome-session-manager.js');
|
|
11
11
|
importScripts('console-interception-library.js'); // Shared console interception library
|
|
12
12
|
importScripts('browser-recording-manager.js');
|
|
13
|
+
importScripts('plugin-registry.js'); // Plugin system for extensible site-specific features
|
|
13
14
|
|
|
14
15
|
const CONFIG_PORTS = CHROMEDEBUG_CONFIG.ports;
|
|
15
16
|
const DISCOVERY_TIMEOUT = CHROMEDEBUG_CONFIG.discoveryTimeout;
|
|
@@ -769,6 +770,7 @@ let logStreamer = null;
|
|
|
769
770
|
let logBuffer = null;
|
|
770
771
|
let browserRecordingManager = null;
|
|
771
772
|
let serverMode = 'unknown'; // 'server', 'browser-only', or 'unknown'
|
|
773
|
+
let pluginRegistry = null; // Plugin system registry for site-specific features
|
|
772
774
|
|
|
773
775
|
// Error handling state and configuration
|
|
774
776
|
// IMPORTANT: These must be declared before initializeServices()
|
|
@@ -891,6 +893,16 @@ async function initializeServices() {
|
|
|
891
893
|
browserRecordingManager = null;
|
|
892
894
|
}
|
|
893
895
|
|
|
896
|
+
// Initialize plugin registry for site-specific features
|
|
897
|
+
try {
|
|
898
|
+
pluginRegistry = new PluginRegistry();
|
|
899
|
+
await pluginRegistry.initialize();
|
|
900
|
+
console.log('[Background] Plugin registry initialized with', pluginRegistry.getAllPlugins().length, 'plugins');
|
|
901
|
+
} catch (pluginErr) {
|
|
902
|
+
console.error('[Background] Failed to initialize plugin registry:', pluginErr);
|
|
903
|
+
pluginRegistry = null;
|
|
904
|
+
}
|
|
905
|
+
|
|
894
906
|
// Initialize session manager with recovery
|
|
895
907
|
try {
|
|
896
908
|
if (!sessionManager) {
|
|
@@ -1390,6 +1402,214 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
|
1390
1402
|
});
|
|
1391
1403
|
return true; // Async response
|
|
1392
1404
|
|
|
1405
|
+
// ============================================================
|
|
1406
|
+
// Plugin System Message Handlers
|
|
1407
|
+
// ============================================================
|
|
1408
|
+
|
|
1409
|
+
case 'GET_PLUGINS_FOR_URL':
|
|
1410
|
+
// Get plugins that match the current page URL
|
|
1411
|
+
if (pluginRegistry && message.url) {
|
|
1412
|
+
const matchingPlugins = pluginRegistry.getPluginsForUrl(message.url);
|
|
1413
|
+
sendResponse({
|
|
1414
|
+
success: true,
|
|
1415
|
+
plugins: matchingPlugins.map(p => ({
|
|
1416
|
+
pluginId: p.pluginId,
|
|
1417
|
+
name: p.manifest.name,
|
|
1418
|
+
version: p.manifest.version,
|
|
1419
|
+
menuItems: p.manifest.menuItems || [],
|
|
1420
|
+
site: p.site
|
|
1421
|
+
}))
|
|
1422
|
+
});
|
|
1423
|
+
} else {
|
|
1424
|
+
sendResponse({ success: false, error: 'Plugin registry not initialized or URL missing' });
|
|
1425
|
+
}
|
|
1426
|
+
break;
|
|
1427
|
+
|
|
1428
|
+
case 'REGISTER_PLUGIN':
|
|
1429
|
+
// Register a new plugin from its manifest
|
|
1430
|
+
if (pluginRegistry && message.manifest) {
|
|
1431
|
+
pluginRegistry.registerPlugin(message.manifest).then(result => {
|
|
1432
|
+
sendResponse(result);
|
|
1433
|
+
}).catch(error => {
|
|
1434
|
+
console.error('[Background] Failed to register plugin:', error);
|
|
1435
|
+
sendResponse({ success: false, error: error.message });
|
|
1436
|
+
});
|
|
1437
|
+
return true; // Async response
|
|
1438
|
+
} else {
|
|
1439
|
+
sendResponse({ success: false, error: 'Plugin registry not initialized or manifest missing' });
|
|
1440
|
+
}
|
|
1441
|
+
break;
|
|
1442
|
+
|
|
1443
|
+
case 'UNREGISTER_PLUGIN':
|
|
1444
|
+
// Unregister a plugin by ID
|
|
1445
|
+
if (pluginRegistry && message.pluginId) {
|
|
1446
|
+
pluginRegistry.unregisterPlugin(message.pluginId).then(result => {
|
|
1447
|
+
sendResponse(result);
|
|
1448
|
+
}).catch(error => {
|
|
1449
|
+
console.error('[Background] Failed to unregister plugin:', error);
|
|
1450
|
+
sendResponse({ success: false, error: error.message });
|
|
1451
|
+
});
|
|
1452
|
+
return true; // Async response
|
|
1453
|
+
} else {
|
|
1454
|
+
sendResponse({ success: false, error: 'Plugin registry not initialized or pluginId missing' });
|
|
1455
|
+
}
|
|
1456
|
+
break;
|
|
1457
|
+
|
|
1458
|
+
case 'GET_ALL_PLUGINS':
|
|
1459
|
+
case 'GET_REGISTERED_PLUGINS':
|
|
1460
|
+
// Get all registered plugins
|
|
1461
|
+
if (pluginRegistry) {
|
|
1462
|
+
const allPlugins = pluginRegistry.getAllPlugins();
|
|
1463
|
+
sendResponse({
|
|
1464
|
+
success: true,
|
|
1465
|
+
plugins: allPlugins.map(p => ({
|
|
1466
|
+
pluginId: p.pluginId,
|
|
1467
|
+
name: p.name,
|
|
1468
|
+
version: p.version,
|
|
1469
|
+
status: p.status || 'enabled',
|
|
1470
|
+
sitePatterns: p.sitePatterns,
|
|
1471
|
+
menuItems: p.menuItems,
|
|
1472
|
+
serverLoaded: p.serverLoaded,
|
|
1473
|
+
registeredAt: p.registeredAt
|
|
1474
|
+
})),
|
|
1475
|
+
registryState: pluginRegistry.exportState()
|
|
1476
|
+
});
|
|
1477
|
+
} else {
|
|
1478
|
+
sendResponse({ success: false, error: 'Plugin registry not initialized' });
|
|
1479
|
+
}
|
|
1480
|
+
break;
|
|
1481
|
+
|
|
1482
|
+
case 'ENABLE_PLUGIN':
|
|
1483
|
+
// Enable a plugin by ID
|
|
1484
|
+
if (pluginRegistry && message.pluginId) {
|
|
1485
|
+
pluginRegistry.enablePlugin(message.pluginId).then(result => {
|
|
1486
|
+
sendResponse(result);
|
|
1487
|
+
}).catch(error => {
|
|
1488
|
+
console.error('[Background] Failed to enable plugin:', error);
|
|
1489
|
+
sendResponse({ success: false, error: error.message });
|
|
1490
|
+
});
|
|
1491
|
+
return true; // Async response
|
|
1492
|
+
} else {
|
|
1493
|
+
sendResponse({ success: false, error: 'Plugin registry not initialized or pluginId missing' });
|
|
1494
|
+
}
|
|
1495
|
+
break;
|
|
1496
|
+
|
|
1497
|
+
case 'DISABLE_PLUGIN':
|
|
1498
|
+
// Disable a plugin by ID
|
|
1499
|
+
if (pluginRegistry && message.pluginId) {
|
|
1500
|
+
pluginRegistry.disablePlugin(message.pluginId).then(result => {
|
|
1501
|
+
sendResponse(result);
|
|
1502
|
+
}).catch(error => {
|
|
1503
|
+
console.error('[Background] Failed to disable plugin:', error);
|
|
1504
|
+
sendResponse({ success: false, error: error.message });
|
|
1505
|
+
});
|
|
1506
|
+
return true; // Async response
|
|
1507
|
+
} else {
|
|
1508
|
+
sendResponse({ success: false, error: 'Plugin registry not initialized or pluginId missing' });
|
|
1509
|
+
}
|
|
1510
|
+
break;
|
|
1511
|
+
|
|
1512
|
+
case 'SYNC_PLUGINS_WITH_SERVER':
|
|
1513
|
+
// Sync plugins with Chrome Debug MCP server
|
|
1514
|
+
if (pluginRegistry) {
|
|
1515
|
+
const port = message.port || CONFIG_PORTS[0];
|
|
1516
|
+
pluginRegistry.syncWithServer(port).then(result => {
|
|
1517
|
+
sendResponse(result);
|
|
1518
|
+
}).catch(error => {
|
|
1519
|
+
console.error('[Background] Failed to sync plugins with server:', error);
|
|
1520
|
+
sendResponse({ success: false, error: error.message });
|
|
1521
|
+
});
|
|
1522
|
+
return true; // Async response
|
|
1523
|
+
} else {
|
|
1524
|
+
sendResponse({ success: false, error: 'Plugin registry not initialized' });
|
|
1525
|
+
}
|
|
1526
|
+
break;
|
|
1527
|
+
|
|
1528
|
+
case 'EXECUTE_PLUGIN_ACTION':
|
|
1529
|
+
// Execute a plugin menu action (routes to server)
|
|
1530
|
+
if (message.pluginId && message.actionId) {
|
|
1531
|
+
// Find the active server port and route the action
|
|
1532
|
+
discoverActiveServerPort().then(async (serverPort) => {
|
|
1533
|
+
if (!serverPort) {
|
|
1534
|
+
sendResponse({ success: false, error: 'Chrome Debug server not available' });
|
|
1535
|
+
return;
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
try {
|
|
1539
|
+
const response = await fetch(`http://localhost:${serverPort}/chromedebug/plugins/${message.pluginId}/execute`, {
|
|
1540
|
+
method: 'POST',
|
|
1541
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1542
|
+
body: JSON.stringify({
|
|
1543
|
+
actionId: message.actionId,
|
|
1544
|
+
context: message.context || {},
|
|
1545
|
+
tabId: sender.tab?.id
|
|
1546
|
+
})
|
|
1547
|
+
});
|
|
1548
|
+
|
|
1549
|
+
const result = await response.json();
|
|
1550
|
+
sendResponse(result);
|
|
1551
|
+
} catch (error) {
|
|
1552
|
+
console.error('[Background] Failed to execute plugin action:', error);
|
|
1553
|
+
sendResponse({ success: false, error: error.message });
|
|
1554
|
+
}
|
|
1555
|
+
});
|
|
1556
|
+
return true; // Async response
|
|
1557
|
+
} else {
|
|
1558
|
+
sendResponse({ success: false, error: 'Missing pluginId or actionId' });
|
|
1559
|
+
}
|
|
1560
|
+
break;
|
|
1561
|
+
|
|
1562
|
+
case 'IMPORT_PLUGIN_FROM_FILE':
|
|
1563
|
+
// Import plugin manifest from a file (via popup)
|
|
1564
|
+
// Accepts either: manifest (object) or manifestJson (string)
|
|
1565
|
+
{
|
|
1566
|
+
let manifest = null;
|
|
1567
|
+
if (message.manifest && typeof message.manifest === 'object') {
|
|
1568
|
+
manifest = message.manifest;
|
|
1569
|
+
} else if (message.manifestJson) {
|
|
1570
|
+
try {
|
|
1571
|
+
manifest = JSON.parse(message.manifestJson);
|
|
1572
|
+
} catch (parseError) {
|
|
1573
|
+
sendResponse({ success: false, error: 'Invalid JSON: ' + parseError.message });
|
|
1574
|
+
break;
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
if (!manifest) {
|
|
1579
|
+
sendResponse({ success: false, error: 'No manifest provided' });
|
|
1580
|
+
break;
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
if (pluginRegistry) {
|
|
1584
|
+
pluginRegistry.registerPlugin(manifest).then(result => {
|
|
1585
|
+
if (result.success) {
|
|
1586
|
+
console.log('[Background] Plugin imported:', result.pluginId);
|
|
1587
|
+
}
|
|
1588
|
+
sendResponse(result);
|
|
1589
|
+
}).catch(error => {
|
|
1590
|
+
sendResponse({ success: false, error: error.message });
|
|
1591
|
+
});
|
|
1592
|
+
return true; // Async response
|
|
1593
|
+
} else {
|
|
1594
|
+
sendResponse({ success: false, error: 'Plugin registry not initialized' });
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
break;
|
|
1598
|
+
|
|
1599
|
+
case 'CLEAR_ALL_PLUGINS':
|
|
1600
|
+
// Clear all plugins (for testing/reset)
|
|
1601
|
+
if (pluginRegistry) {
|
|
1602
|
+
pluginRegistry.clearAll().then(() => {
|
|
1603
|
+
sendResponse({ success: true, message: 'All plugins cleared' });
|
|
1604
|
+
}).catch(error => {
|
|
1605
|
+
sendResponse({ success: false, error: error.message });
|
|
1606
|
+
});
|
|
1607
|
+
return true; // Async response
|
|
1608
|
+
} else {
|
|
1609
|
+
sendResponse({ success: false, error: 'Plugin registry not initialized' });
|
|
1610
|
+
}
|
|
1611
|
+
break;
|
|
1612
|
+
|
|
1393
1613
|
default:
|
|
1394
1614
|
// Don't warn or respond for messages that should be handled by other listener (v2.0.6)
|
|
1395
1615
|
// This prevents intercepting workflow recording messages
|
|
@@ -240,6 +240,41 @@
|
|
|
240
240
|
</div>
|
|
241
241
|
</div>
|
|
242
242
|
|
|
243
|
+
<!-- Plugins Section -->
|
|
244
|
+
<div id="plugins-section" style="padding: 8px; border: 1px solid #ccc; background: #f9f9f9; border-radius: 4px; margin-bottom: 15px;">
|
|
245
|
+
<details id="plugins-details">
|
|
246
|
+
<summary style="cursor: pointer; font-size: 12px; font-weight: bold; color: #333; display: flex; align-items: center; gap: 8px;">
|
|
247
|
+
<span>🔌 Plugins</span>
|
|
248
|
+
<span id="plugins-count-badge" style="background: #e0e0e0; color: #666; font-size: 10px; padding: 2px 6px; border-radius: 10px; font-weight: normal;">0</span>
|
|
249
|
+
<span style="flex: 1;"></span>
|
|
250
|
+
<button id="sync-plugins-btn" style="font-size: 9px; padding: 2px 6px; background: #2196F3; color: white; border: none; border-radius: 3px; cursor: pointer;" title="Sync with server">🔄 Sync</button>
|
|
251
|
+
</summary>
|
|
252
|
+
|
|
253
|
+
<div style="margin-top: 8px;">
|
|
254
|
+
<!-- Import Plugin Button -->
|
|
255
|
+
<div style="display: flex; gap: 6px; margin-bottom: 8px;">
|
|
256
|
+
<button id="import-plugin-btn" style="flex: 1; padding: 6px; font-size: 10px; background: linear-gradient(135deg, #4285f4 0%, #34a853 100%); color: white; border: none; border-radius: 4px; cursor: pointer;">
|
|
257
|
+
📥 Import Plugin
|
|
258
|
+
</button>
|
|
259
|
+
<input type="file" id="plugin-file-input" accept=".json" style="display: none;">
|
|
260
|
+
</div>
|
|
261
|
+
|
|
262
|
+
<!-- Plugins List -->
|
|
263
|
+
<div id="plugins-list" style="max-height: 200px; overflow-y: auto;">
|
|
264
|
+
<div id="no-plugins-message" style="text-align: center; padding: 12px; color: #888; font-size: 11px;">
|
|
265
|
+
No plugins installed. Import a plugin manifest to get started.
|
|
266
|
+
</div>
|
|
267
|
+
<div id="plugins-container"></div>
|
|
268
|
+
</div>
|
|
269
|
+
|
|
270
|
+
<!-- Sync Status -->
|
|
271
|
+
<div id="plugins-sync-status" style="font-size: 9px; color: #888; margin-top: 6px; text-align: center; display: none;">
|
|
272
|
+
Last synced: <span id="plugins-last-sync">Never</span>
|
|
273
|
+
</div>
|
|
274
|
+
</div>
|
|
275
|
+
</details>
|
|
276
|
+
</div>
|
|
277
|
+
|
|
243
278
|
<!-- 2-Column Layout for Recordings -->
|
|
244
279
|
<div style="display: grid; grid-template-columns: 50% 50%; gap: 6px; box-sizing: border-box; overflow: hidden; max-width: 100%; margin-bottom: 60px;">
|
|
245
280
|
|
|
@@ -2933,8 +2933,267 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
2933
2933
|
clearTimeout(screenshotQualityDebounceTimer);
|
|
2934
2934
|
}
|
|
2935
2935
|
});
|
|
2936
|
+
|
|
2937
|
+
// ========================================
|
|
2938
|
+
// Plugin Management UI Initialization
|
|
2939
|
+
// ========================================
|
|
2940
|
+
initializePluginUI();
|
|
2936
2941
|
});
|
|
2937
2942
|
|
|
2943
|
+
// ========================================
|
|
2944
|
+
// Plugin Management Functions
|
|
2945
|
+
// ========================================
|
|
2946
|
+
|
|
2947
|
+
/**
|
|
2948
|
+
* Initialize plugin UI event handlers and load plugins
|
|
2949
|
+
*/
|
|
2950
|
+
function initializePluginUI() {
|
|
2951
|
+
const importBtn = document.getElementById('import-plugin-btn');
|
|
2952
|
+
const fileInput = document.getElementById('plugin-file-input');
|
|
2953
|
+
const syncBtn = document.getElementById('sync-plugins-btn');
|
|
2954
|
+
|
|
2955
|
+
// Import button -> trigger file input
|
|
2956
|
+
if (importBtn && fileInput) {
|
|
2957
|
+
importBtn.addEventListener('click', () => {
|
|
2958
|
+
fileInput.click();
|
|
2959
|
+
});
|
|
2960
|
+
|
|
2961
|
+
// Handle file selection
|
|
2962
|
+
fileInput.addEventListener('change', async (e) => {
|
|
2963
|
+
const file = e.target.files[0];
|
|
2964
|
+
if (!file) return;
|
|
2965
|
+
|
|
2966
|
+
try {
|
|
2967
|
+
const text = await file.text();
|
|
2968
|
+
const manifest = JSON.parse(text);
|
|
2969
|
+
|
|
2970
|
+
// Validate required fields
|
|
2971
|
+
if (!manifest.pluginId || !manifest.name) {
|
|
2972
|
+
showToast('Invalid plugin manifest: missing pluginId or name', 3000, 'error');
|
|
2973
|
+
return;
|
|
2974
|
+
}
|
|
2975
|
+
|
|
2976
|
+
// Send to background for registration
|
|
2977
|
+
const response = await chrome.runtime.sendMessage({
|
|
2978
|
+
action: 'IMPORT_PLUGIN_FROM_FILE',
|
|
2979
|
+
manifest: manifest
|
|
2980
|
+
});
|
|
2981
|
+
|
|
2982
|
+
if (response && response.success) {
|
|
2983
|
+
showToast('Plugin "' + manifest.name + '" imported successfully!', 3000, 'success');
|
|
2984
|
+
loadPluginsList();
|
|
2985
|
+
} else {
|
|
2986
|
+
showToast(response?.error || 'Failed to import plugin', 3000, 'error');
|
|
2987
|
+
}
|
|
2988
|
+
} catch (err) {
|
|
2989
|
+
console.error('Error importing plugin:', err);
|
|
2990
|
+
showToast('Invalid JSON file', 3000, 'error');
|
|
2991
|
+
}
|
|
2992
|
+
|
|
2993
|
+
// Reset file input for next import
|
|
2994
|
+
fileInput.value = '';
|
|
2995
|
+
});
|
|
2996
|
+
}
|
|
2997
|
+
|
|
2998
|
+
// Sync button -> sync with server
|
|
2999
|
+
if (syncBtn) {
|
|
3000
|
+
syncBtn.addEventListener('click', async () => {
|
|
3001
|
+
syncBtn.disabled = true;
|
|
3002
|
+
syncBtn.textContent = '\u231B';
|
|
3003
|
+
|
|
3004
|
+
try {
|
|
3005
|
+
const response = await chrome.runtime.sendMessage({
|
|
3006
|
+
action: 'SYNC_PLUGINS_WITH_SERVER'
|
|
3007
|
+
});
|
|
3008
|
+
|
|
3009
|
+
if (response && response.success) {
|
|
3010
|
+
const syncStatus = document.getElementById('plugins-sync-status');
|
|
3011
|
+
const lastSync = document.getElementById('plugins-last-sync');
|
|
3012
|
+
if (syncStatus && lastSync) {
|
|
3013
|
+
syncStatus.style.display = 'block';
|
|
3014
|
+
lastSync.textContent = new Date().toLocaleTimeString();
|
|
3015
|
+
}
|
|
3016
|
+
showToast('Synced ' + (response.count || 0) + ' plugins', 2000, 'success');
|
|
3017
|
+
loadPluginsList();
|
|
3018
|
+
} else {
|
|
3019
|
+
showToast(response?.error || 'Sync failed', 3000, 'error');
|
|
3020
|
+
}
|
|
3021
|
+
} catch (err) {
|
|
3022
|
+
console.error('Error syncing plugins:', err);
|
|
3023
|
+
showToast('Failed to connect to server', 3000, 'error');
|
|
3024
|
+
}
|
|
3025
|
+
|
|
3026
|
+
syncBtn.disabled = false;
|
|
3027
|
+
syncBtn.textContent = '\uD83D\uDD04 Sync';
|
|
3028
|
+
});
|
|
3029
|
+
}
|
|
3030
|
+
|
|
3031
|
+
// Load plugins on startup
|
|
3032
|
+
loadPluginsList();
|
|
3033
|
+
}
|
|
3034
|
+
|
|
3035
|
+
/**
|
|
3036
|
+
* Load and display registered plugins
|
|
3037
|
+
*/
|
|
3038
|
+
async function loadPluginsList() {
|
|
3039
|
+
const pluginsContainer = document.getElementById('plugins-container');
|
|
3040
|
+
const noPluginsMessage = document.getElementById('no-plugins-message');
|
|
3041
|
+
const pluginsCountBadge = document.getElementById('plugins-count-badge');
|
|
3042
|
+
|
|
3043
|
+
if (!pluginsContainer) return;
|
|
3044
|
+
|
|
3045
|
+
try {
|
|
3046
|
+
const response = await chrome.runtime.sendMessage({
|
|
3047
|
+
action: 'GET_REGISTERED_PLUGINS'
|
|
3048
|
+
});
|
|
3049
|
+
|
|
3050
|
+
const plugins = response?.plugins || [];
|
|
3051
|
+
|
|
3052
|
+
// Update count badge
|
|
3053
|
+
if (pluginsCountBadge) {
|
|
3054
|
+
pluginsCountBadge.textContent = plugins.length.toString();
|
|
3055
|
+
pluginsCountBadge.style.background = plugins.length > 0 ? '#4CAF50' : '#e0e0e0';
|
|
3056
|
+
pluginsCountBadge.style.color = plugins.length > 0 ? 'white' : '#666';
|
|
3057
|
+
}
|
|
3058
|
+
|
|
3059
|
+
// Show/hide no plugins message
|
|
3060
|
+
if (noPluginsMessage) {
|
|
3061
|
+
noPluginsMessage.style.display = plugins.length === 0 ? 'block' : 'none';
|
|
3062
|
+
}
|
|
3063
|
+
|
|
3064
|
+
// Clear container safely
|
|
3065
|
+
while (pluginsContainer.firstChild) {
|
|
3066
|
+
pluginsContainer.removeChild(pluginsContainer.firstChild);
|
|
3067
|
+
}
|
|
3068
|
+
|
|
3069
|
+
plugins.forEach(plugin => {
|
|
3070
|
+
const pluginEl = createPluginElement(plugin);
|
|
3071
|
+
pluginsContainer.appendChild(pluginEl);
|
|
3072
|
+
});
|
|
3073
|
+
} catch (err) {
|
|
3074
|
+
console.error('Error loading plugins:', err);
|
|
3075
|
+
}
|
|
3076
|
+
}
|
|
3077
|
+
|
|
3078
|
+
/**
|
|
3079
|
+
* Create DOM element for a plugin using safe DOM methods
|
|
3080
|
+
*/
|
|
3081
|
+
function createPluginElement(plugin) {
|
|
3082
|
+
const el = document.createElement('div');
|
|
3083
|
+
el.style.cssText = 'padding: 8px; border: 1px solid #ddd; border-radius: 4px; margin-bottom: 6px; background: white;';
|
|
3084
|
+
|
|
3085
|
+
const isEnabled = plugin.status !== 'disabled';
|
|
3086
|
+
const statusColor = isEnabled ? '#4CAF50' : '#999';
|
|
3087
|
+
|
|
3088
|
+
// Main row container
|
|
3089
|
+
const mainRow = document.createElement('div');
|
|
3090
|
+
mainRow.style.cssText = 'display: flex; justify-content: space-between; align-items: center;';
|
|
3091
|
+
|
|
3092
|
+
// Left side: name and version
|
|
3093
|
+
const infoDiv = document.createElement('div');
|
|
3094
|
+
|
|
3095
|
+
const nameSpan = document.createElement('span');
|
|
3096
|
+
nameSpan.style.cssText = 'font-weight: bold; font-size: 11px;';
|
|
3097
|
+
nameSpan.textContent = plugin.name || plugin.pluginId;
|
|
3098
|
+
|
|
3099
|
+
const versionSpan = document.createElement('span');
|
|
3100
|
+
versionSpan.style.cssText = 'font-size: 9px; color: #888; margin-left: 4px;';
|
|
3101
|
+
versionSpan.textContent = 'v' + (plugin.version || '1.0.0');
|
|
3102
|
+
|
|
3103
|
+
infoDiv.appendChild(nameSpan);
|
|
3104
|
+
infoDiv.appendChild(versionSpan);
|
|
3105
|
+
|
|
3106
|
+
// Right side: status and buttons
|
|
3107
|
+
const buttonsDiv = document.createElement('div');
|
|
3108
|
+
buttonsDiv.style.cssText = 'display: flex; gap: 4px; align-items: center;';
|
|
3109
|
+
|
|
3110
|
+
// Status indicator
|
|
3111
|
+
const statusDot = document.createElement('span');
|
|
3112
|
+
statusDot.style.cssText = 'width: 8px; height: 8px; border-radius: 50%; background: ' + statusColor + ';';
|
|
3113
|
+
|
|
3114
|
+
// Toggle button
|
|
3115
|
+
const toggleBtn = document.createElement('button');
|
|
3116
|
+
toggleBtn.className = 'plugin-toggle-btn';
|
|
3117
|
+
toggleBtn.dataset.pluginId = plugin.pluginId;
|
|
3118
|
+
toggleBtn.dataset.enabled = isEnabled.toString();
|
|
3119
|
+
toggleBtn.style.cssText = 'font-size: 9px; padding: 2px 6px; background: ' + (isEnabled ? '#ff9800' : '#4CAF50') + '; color: white; border: none; border-radius: 3px; cursor: pointer;';
|
|
3120
|
+
toggleBtn.textContent = isEnabled ? 'Disable' : 'Enable';
|
|
3121
|
+
|
|
3122
|
+
// Remove button
|
|
3123
|
+
const removeBtn = document.createElement('button');
|
|
3124
|
+
removeBtn.className = 'plugin-remove-btn';
|
|
3125
|
+
removeBtn.dataset.pluginId = plugin.pluginId;
|
|
3126
|
+
removeBtn.style.cssText = 'font-size: 9px; padding: 2px 6px; background: #f44336; color: white; border: none; border-radius: 3px; cursor: pointer;';
|
|
3127
|
+
removeBtn.textContent = '\u2715';
|
|
3128
|
+
|
|
3129
|
+
buttonsDiv.appendChild(statusDot);
|
|
3130
|
+
buttonsDiv.appendChild(toggleBtn);
|
|
3131
|
+
buttonsDiv.appendChild(removeBtn);
|
|
3132
|
+
|
|
3133
|
+
mainRow.appendChild(infoDiv);
|
|
3134
|
+
mainRow.appendChild(buttonsDiv);
|
|
3135
|
+
el.appendChild(mainRow);
|
|
3136
|
+
|
|
3137
|
+
// Site patterns info (if any)
|
|
3138
|
+
if (plugin.sitePatterns && plugin.sitePatterns.length > 0) {
|
|
3139
|
+
const sitesDiv = document.createElement('div');
|
|
3140
|
+
sitesDiv.style.cssText = 'font-size: 9px; color: #666; margin-top: 4px;';
|
|
3141
|
+
const sitesText = plugin.sitePatterns.slice(0, 2).join(', ');
|
|
3142
|
+
sitesDiv.textContent = 'Sites: ' + sitesText + (plugin.sitePatterns.length > 2 ? '...' : '');
|
|
3143
|
+
el.appendChild(sitesDiv);
|
|
3144
|
+
}
|
|
3145
|
+
|
|
3146
|
+
// Toggle button handler
|
|
3147
|
+
toggleBtn.addEventListener('click', async () => {
|
|
3148
|
+
const pluginId = toggleBtn.dataset.pluginId;
|
|
3149
|
+
const currentlyEnabled = toggleBtn.dataset.enabled === 'true';
|
|
3150
|
+
|
|
3151
|
+
try {
|
|
3152
|
+
const response = await chrome.runtime.sendMessage({
|
|
3153
|
+
action: currentlyEnabled ? 'DISABLE_PLUGIN' : 'ENABLE_PLUGIN',
|
|
3154
|
+
pluginId: pluginId
|
|
3155
|
+
});
|
|
3156
|
+
|
|
3157
|
+
if (response && response.success) {
|
|
3158
|
+
loadPluginsList();
|
|
3159
|
+
showToast('Plugin ' + (currentlyEnabled ? 'disabled' : 'enabled'), 2000, 'success');
|
|
3160
|
+
} else {
|
|
3161
|
+
showToast(response?.error || 'Operation failed', 3000, 'error');
|
|
3162
|
+
}
|
|
3163
|
+
} catch (err) {
|
|
3164
|
+
console.error('Error toggling plugin:', err);
|
|
3165
|
+
}
|
|
3166
|
+
});
|
|
3167
|
+
|
|
3168
|
+
// Remove button handler
|
|
3169
|
+
removeBtn.addEventListener('click', async () => {
|
|
3170
|
+
const pluginId = removeBtn.dataset.pluginId;
|
|
3171
|
+
const pluginName = plugin.name || pluginId;
|
|
3172
|
+
|
|
3173
|
+
if (!confirm('Remove plugin "' + pluginName + '"?')) {
|
|
3174
|
+
return;
|
|
3175
|
+
}
|
|
3176
|
+
|
|
3177
|
+
try {
|
|
3178
|
+
const response = await chrome.runtime.sendMessage({
|
|
3179
|
+
action: 'UNREGISTER_PLUGIN',
|
|
3180
|
+
pluginId: pluginId
|
|
3181
|
+
});
|
|
3182
|
+
|
|
3183
|
+
if (response && response.success) {
|
|
3184
|
+
loadPluginsList();
|
|
3185
|
+
showToast('Plugin removed', 2000, 'success');
|
|
3186
|
+
} else {
|
|
3187
|
+
showToast(response?.error || 'Failed to remove plugin', 3000, 'error');
|
|
3188
|
+
}
|
|
3189
|
+
} catch (err) {
|
|
3190
|
+
console.error('Error removing plugin:', err);
|
|
3191
|
+
}
|
|
3192
|
+
});
|
|
3193
|
+
|
|
3194
|
+
return el;
|
|
3195
|
+
}
|
|
3196
|
+
|
|
2938
3197
|
// Function to load and display workflow recordings
|
|
2939
3198
|
async function loadWorkflowRecordings(autoShow = false) {
|
|
2940
3199
|
const workflowRecordingsList = document.getElementById('workflowRecordingsList');
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dynamicu/chromedebug-mcp",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.7",
|
|
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",
|
|
@@ -11,14 +11,7 @@
|
|
|
11
11
|
"publishConfig": {
|
|
12
12
|
"access": "public"
|
|
13
13
|
},
|
|
14
|
-
"
|
|
15
|
-
"type": "git",
|
|
16
|
-
"url": "git+https://github.com/dynamicupgrade/ChromeDebug.git"
|
|
17
|
-
},
|
|
18
|
-
"bugs": {
|
|
19
|
-
"url": "https://github.com/dynamicupgrade/ChromeDebug/issues"
|
|
20
|
-
},
|
|
21
|
-
"homepage": "https://github.com/dynamicupgrade/ChromeDebug#readme",
|
|
14
|
+
"homepage": "https://chromedebug.com",
|
|
22
15
|
"scripts": {
|
|
23
16
|
"cleanup": "node scripts/cleanup-processes.js",
|
|
24
17
|
"cleanup-force": "node scripts/cleanup-processes.js --force",
|
|
@@ -45,8 +45,8 @@ if (process.platform === 'win32' && !process.env.WSL_DISTRO_NAME) {
|
|
|
45
45
|
}
|
|
46
46
|
console.error('');
|
|
47
47
|
|
|
48
|
-
console.error('For more information:');
|
|
49
|
-
console.error(' https://
|
|
48
|
+
console.error('For more information, see the npm package documentation:');
|
|
49
|
+
console.error(' https://www.npmjs.com/package/@dynamicu/chromedebug-mcp');
|
|
50
50
|
console.error('');
|
|
51
51
|
|
|
52
52
|
// Exit successfully
|
package/scripts/postinstall.js
CHANGED
|
@@ -48,4 +48,4 @@ console.log(' Claude Code automatically starts both servers when it runs chrom
|
|
|
48
48
|
console.log('💎 Want unlimited recordings?');
|
|
49
49
|
console.log(' Upgrade to Pro: https://chromedebug.com/checkout/buy/996773cb-682b-430f-b9e3-9ce2130bd967\n');
|
|
50
50
|
|
|
51
|
-
console.log('📚 Full documentation: https://
|
|
51
|
+
console.log('📚 Full documentation: https://www.npmjs.com/package/@dynamicu/chromedebug-mcp\n');
|
package/src/cli.js
CHANGED
|
@@ -68,7 +68,7 @@ Windows users have two options:
|
|
|
68
68
|
• Then run: chromedebug-mcp
|
|
69
69
|
• Note: MCP stdio communication may fail due to cmd.exe limitations
|
|
70
70
|
|
|
71
|
-
For details: https://
|
|
71
|
+
For details, see the npm package documentation at: https://www.npmjs.com/package/@dynamicu/chromedebug-mcp
|
|
72
72
|
`);
|
|
73
73
|
process.exit(1);
|
|
74
74
|
}
|
|
@@ -101,7 +101,7 @@ Expected formats:
|
|
|
101
101
|
Your key: ${maskLicenseKey(key)}
|
|
102
102
|
|
|
103
103
|
Please check your purchase confirmation email for the correct license key.
|
|
104
|
-
|
|
104
|
+
For help, see: https://www.npmjs.com/package/@dynamicu/chromedebug-mcp
|
|
105
105
|
`);
|
|
106
106
|
return false;
|
|
107
107
|
}
|
|
@@ -134,7 +134,7 @@ Possible causes:
|
|
|
134
134
|
2. Disk full - check: df -h ~
|
|
135
135
|
3. Home directory not writable
|
|
136
136
|
|
|
137
|
-
|
|
137
|
+
For help, see: https://www.npmjs.com/package/@dynamicu/chromedebug-mcp
|
|
138
138
|
`);
|
|
139
139
|
return false;
|
|
140
140
|
}
|
|
@@ -197,7 +197,7 @@ Please check:
|
|
|
197
197
|
2. File exists at the specified location
|
|
198
198
|
3. You have read permission
|
|
199
199
|
|
|
200
|
-
|
|
200
|
+
For help, see: https://www.npmjs.com/package/@dynamicu/chromedebug-mcp
|
|
201
201
|
`);
|
|
202
202
|
throw err;
|
|
203
203
|
}
|
|
@@ -226,7 +226,7 @@ Possible causes:
|
|
|
226
226
|
|
|
227
227
|
Error details: ${err.message}
|
|
228
228
|
|
|
229
|
-
|
|
229
|
+
For help, see: https://www.npmjs.com/package/@dynamicu/chromedebug-mcp
|
|
230
230
|
`);
|
|
231
231
|
throw err;
|
|
232
232
|
}
|
|
@@ -254,7 +254,7 @@ Expected manifest.json with ChromeDebug name.
|
|
|
254
254
|
|
|
255
255
|
Please verify you downloaded the correct PRO extension ZIP.
|
|
256
256
|
|
|
257
|
-
|
|
257
|
+
For help, see: https://www.npmjs.com/package/@dynamicu/chromedebug-mcp
|
|
258
258
|
`);
|
|
259
259
|
throw err;
|
|
260
260
|
}
|
|
@@ -561,12 +561,12 @@ This will overwrite the existing installation.`;
|
|
|
561
561
|
console.error(` (should output: ${EXTENSION_DIR})\n`);
|
|
562
562
|
|
|
563
563
|
console.error('Installation log: ' + INSTALL_LOG);
|
|
564
|
-
console.error('\
|
|
564
|
+
console.error('\nFor help, see: https://www.npmjs.com/package/@dynamicu/chromedebug-mcp\n');
|
|
565
565
|
} catch (err) {
|
|
566
566
|
await logInstall(`Installation failed: ${err.message}`, true);
|
|
567
567
|
console.error('\n Installation failed\n');
|
|
568
568
|
console.error('Check the installation log for details: ' + INSTALL_LOG);
|
|
569
|
-
console.error('\
|
|
569
|
+
console.error('\nFor help, see: https://www.npmjs.com/package/@dynamicu/chromedebug-mcp\n');
|
|
570
570
|
process.exit(1);
|
|
571
571
|
}
|
|
572
572
|
}
|
|
@@ -1,542 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ChromeDebug PRO License Installer
|
|
3
|
-
*
|
|
4
|
-
* Handles installation of PRO extension after purchase:
|
|
5
|
-
* - Validates license key format
|
|
6
|
-
* - Extracts PRO extension ZIP to ~/.chromedebug-pro/extension/
|
|
7
|
-
* - Saves license information to ~/.chromedebug-pro/license.json
|
|
8
|
-
* - Configures shell environment for CHROMEDEBUG_EXTENSION_PATH
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import fs from 'fs/promises';
|
|
12
|
-
import fsSync from 'fs';
|
|
13
|
-
import path from 'path';
|
|
14
|
-
import os from 'os';
|
|
15
|
-
import { execSync } from 'child_process';
|
|
16
|
-
import AdmZip from 'adm-zip';
|
|
17
|
-
|
|
18
|
-
// Constants
|
|
19
|
-
const CHROMEDEBUG_DIR = path.join(os.homedir(), '.chromedebug-pro');
|
|
20
|
-
const EXTENSION_DIR = path.join(CHROMEDEBUG_DIR, 'extension');
|
|
21
|
-
const LICENSE_FILE = path.join(CHROMEDEBUG_DIR, 'license.json');
|
|
22
|
-
const INSTALL_LOG = path.join(CHROMEDEBUG_DIR, 'install.log');
|
|
23
|
-
const ENV_VAR_NAME = 'CHROMEDEBUG_EXTENSION_PATH';
|
|
24
|
-
|
|
25
|
-
// License key format: LICENSEID-HASH (e.g., ABC12345-0123456789ABCDEF)
|
|
26
|
-
const LICENSE_KEY_REGEX = /^[A-Z0-9]{8,}-[A-Z0-9]{16,}$/i;
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Log installation activity
|
|
30
|
-
*/
|
|
31
|
-
async function logInstall(message, isError = false) {
|
|
32
|
-
const timestamp = new Date().toISOString();
|
|
33
|
-
const logMessage = `[${timestamp}] ${isError ? 'ERROR: ' : ''}${message}\n`;
|
|
34
|
-
|
|
35
|
-
try {
|
|
36
|
-
await fs.appendFile(INSTALL_LOG, logMessage);
|
|
37
|
-
} catch (err) {
|
|
38
|
-
// Ignore log errors - don't let logging break installation
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (isError) {
|
|
42
|
-
console.error(message);
|
|
43
|
-
} else {
|
|
44
|
-
console.log(message);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Validate command-line arguments
|
|
50
|
-
*/
|
|
51
|
-
function validateArguments(args) {
|
|
52
|
-
const licenseIndex = args.indexOf('--license');
|
|
53
|
-
const pathIndex = args.indexOf('--extension-path');
|
|
54
|
-
|
|
55
|
-
if (licenseIndex === -1 || pathIndex === -1) {
|
|
56
|
-
console.error(`
|
|
57
|
-
ChromeDebug PRO License Installer
|
|
58
|
-
|
|
59
|
-
Usage:
|
|
60
|
-
chromedebug-mcp install-pro --license YOUR_LICENSE_KEY --extension-path /path/to/chromedebug-pro-extension.zip
|
|
61
|
-
|
|
62
|
-
Required Arguments:
|
|
63
|
-
--license Your PRO license key (received after purchase)
|
|
64
|
-
--extension-path Path to the PRO extension ZIP file
|
|
65
|
-
|
|
66
|
-
Example:
|
|
67
|
-
chromedebug-mcp install-pro \\
|
|
68
|
-
--license ABC12345-0123456789ABCDEF \\
|
|
69
|
-
--extension-path ~/Downloads/chromedebug-pro-extension.zip
|
|
70
|
-
|
|
71
|
-
Get PRO: https://chromedebug.com/pro
|
|
72
|
-
`);
|
|
73
|
-
return null;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const license = args[licenseIndex + 1];
|
|
77
|
-
const extensionPath = args[pathIndex + 1];
|
|
78
|
-
|
|
79
|
-
if (!license || !extensionPath) {
|
|
80
|
-
console.error('Error: Missing values for --license or --extension-path');
|
|
81
|
-
return null;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return { license, extensionPath };
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Validate license key format
|
|
89
|
-
*/
|
|
90
|
-
function validateLicenseKey(key) {
|
|
91
|
-
if (!LICENSE_KEY_REGEX.test(key)) {
|
|
92
|
-
console.error(`
|
|
93
|
-
Error: Invalid license key format
|
|
94
|
-
|
|
95
|
-
Expected format: LICENSEID-HASH
|
|
96
|
-
Example: ABC12345-0123456789ABCDEF
|
|
97
|
-
|
|
98
|
-
Your key: ${maskLicenseKey(key)}
|
|
99
|
-
|
|
100
|
-
Please check your purchase confirmation email for the correct license key.
|
|
101
|
-
Need help? https://github.com/dynamicupgrade/ChromeDebug/issues
|
|
102
|
-
`);
|
|
103
|
-
return false;
|
|
104
|
-
}
|
|
105
|
-
return true;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Mask license key for display (show first 6 and last 3 chars)
|
|
110
|
-
*/
|
|
111
|
-
function maskLicenseKey(key) {
|
|
112
|
-
if (key.length <= 9) return '***';
|
|
113
|
-
return `${key.substring(0, 6)}...${key.substring(key.length - 3)}`;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Check pre-flight conditions
|
|
118
|
-
*/
|
|
119
|
-
async function preflightChecks() {
|
|
120
|
-
// Check write permission to home directory
|
|
121
|
-
try {
|
|
122
|
-
const testFile = path.join(os.homedir(), '.chromedebug-test');
|
|
123
|
-
await fs.writeFile(testFile, 'test');
|
|
124
|
-
await fs.unlink(testFile);
|
|
125
|
-
} catch (err) {
|
|
126
|
-
console.error(`
|
|
127
|
-
Error: Cannot write to home directory
|
|
128
|
-
|
|
129
|
-
Possible causes:
|
|
130
|
-
1. Permission denied - check: ls -ld ~
|
|
131
|
-
2. Disk full - check: df -h ~
|
|
132
|
-
3. Home directory not writable
|
|
133
|
-
|
|
134
|
-
Need help? https://github.com/dynamicupgrade/ChromeDebug/issues
|
|
135
|
-
`);
|
|
136
|
-
return false;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
return true;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Check for existing installation
|
|
144
|
-
*/
|
|
145
|
-
async function checkExistingInstallation() {
|
|
146
|
-
try {
|
|
147
|
-
const stats = await fs.stat(LICENSE_FILE);
|
|
148
|
-
if (stats.isFile()) {
|
|
149
|
-
const licenseData = JSON.parse(await fs.readFile(LICENSE_FILE, 'utf-8'));
|
|
150
|
-
return {
|
|
151
|
-
exists: true,
|
|
152
|
-
installed_at: licenseData.installed_at,
|
|
153
|
-
extension_version: licenseData.extension_version,
|
|
154
|
-
license_key: licenseData.license_key
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
} catch (err) {
|
|
158
|
-
// No existing installation
|
|
159
|
-
}
|
|
160
|
-
return { exists: false };
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Prompt for confirmation (synchronous for CLI)
|
|
165
|
-
*/
|
|
166
|
-
function promptConfirmation(message) {
|
|
167
|
-
// For now, we'll auto-continue with a warning
|
|
168
|
-
// In future, could add readline for interactive prompts
|
|
169
|
-
console.log(`\n${message}`);
|
|
170
|
-
console.log('Continuing with installation...\n');
|
|
171
|
-
return true;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Extract and validate ZIP file
|
|
176
|
-
*/
|
|
177
|
-
async function extractExtension(zipPath, targetPath) {
|
|
178
|
-
// Resolve path (handle ~ and relative paths)
|
|
179
|
-
const resolvedZipPath = zipPath.startsWith('~')
|
|
180
|
-
? path.join(os.homedir(), zipPath.slice(1))
|
|
181
|
-
: path.resolve(zipPath);
|
|
182
|
-
|
|
183
|
-
// Check if ZIP exists
|
|
184
|
-
try {
|
|
185
|
-
await fs.access(resolvedZipPath);
|
|
186
|
-
} catch (err) {
|
|
187
|
-
console.error(`
|
|
188
|
-
Error: Extension ZIP file not found
|
|
189
|
-
|
|
190
|
-
Path: ${resolvedZipPath}
|
|
191
|
-
|
|
192
|
-
Please check:
|
|
193
|
-
1. File path is correct
|
|
194
|
-
2. File exists at the specified location
|
|
195
|
-
3. You have read permission
|
|
196
|
-
|
|
197
|
-
Need help? https://github.com/dynamicupgrade/ChromeDebug/issues
|
|
198
|
-
`);
|
|
199
|
-
throw err;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Create target directory (clean if exists)
|
|
203
|
-
try {
|
|
204
|
-
await fs.rm(targetPath, { recursive: true, force: true });
|
|
205
|
-
} catch (err) {
|
|
206
|
-
// Ignore - directory might not exist
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
await fs.mkdir(targetPath, { recursive: true });
|
|
210
|
-
|
|
211
|
-
// Extract ZIP
|
|
212
|
-
try {
|
|
213
|
-
const zip = new AdmZip(resolvedZipPath);
|
|
214
|
-
zip.extractAllTo(targetPath, true);
|
|
215
|
-
} catch (err) {
|
|
216
|
-
console.error(`
|
|
217
|
-
Error: Failed to extract ZIP file
|
|
218
|
-
|
|
219
|
-
Possible causes:
|
|
220
|
-
1. ZIP file is corrupted - try re-downloading
|
|
221
|
-
2. Not a valid ZIP file
|
|
222
|
-
3. Disk space full
|
|
223
|
-
|
|
224
|
-
Error details: ${err.message}
|
|
225
|
-
|
|
226
|
-
Need help? https://github.com/dynamicupgrade/ChromeDebug/issues
|
|
227
|
-
`);
|
|
228
|
-
throw err;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// Validate extracted contents
|
|
232
|
-
const manifestPath = path.join(targetPath, 'manifest.json');
|
|
233
|
-
try {
|
|
234
|
-
const manifestContent = await fs.readFile(manifestPath, 'utf-8');
|
|
235
|
-
const manifest = JSON.parse(manifestContent);
|
|
236
|
-
|
|
237
|
-
if (!manifest.name || !manifest.name.toLowerCase().includes('chromedebug')) {
|
|
238
|
-
throw new Error('Extension does not appear to be ChromeDebug');
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
return {
|
|
242
|
-
version: manifest.version,
|
|
243
|
-
name: manifest.name
|
|
244
|
-
};
|
|
245
|
-
} catch (err) {
|
|
246
|
-
console.error(`
|
|
247
|
-
Error: Invalid extension package
|
|
248
|
-
|
|
249
|
-
The extracted files do not appear to be a valid ChromeDebug extension.
|
|
250
|
-
Expected manifest.json with ChromeDebug name.
|
|
251
|
-
|
|
252
|
-
Please verify you downloaded the correct PRO extension ZIP.
|
|
253
|
-
|
|
254
|
-
Need help? https://github.com/dynamicupgrade/ChromeDebug/issues
|
|
255
|
-
`);
|
|
256
|
-
throw err;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Save license information
|
|
262
|
-
*/
|
|
263
|
-
async function saveLicenseInfo(license, extensionVersion) {
|
|
264
|
-
const licenseData = {
|
|
265
|
-
license_key: license,
|
|
266
|
-
installed_at: new Date().toISOString(),
|
|
267
|
-
extension_version: extensionVersion,
|
|
268
|
-
extension_path: EXTENSION_DIR
|
|
269
|
-
};
|
|
270
|
-
|
|
271
|
-
await fs.writeFile(LICENSE_FILE, JSON.stringify(licenseData, null, 2), 'utf-8');
|
|
272
|
-
|
|
273
|
-
// Set restrictive permissions (owner read/write only)
|
|
274
|
-
await fs.chmod(LICENSE_FILE, 0o600);
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* Detect user's shell
|
|
279
|
-
*/
|
|
280
|
-
function detectShell() {
|
|
281
|
-
try {
|
|
282
|
-
const shell = process.env.SHELL || '';
|
|
283
|
-
|
|
284
|
-
if (shell.includes('zsh')) return 'zsh';
|
|
285
|
-
if (shell.includes('bash')) return 'bash';
|
|
286
|
-
if (shell.includes('fish')) return 'fish';
|
|
287
|
-
|
|
288
|
-
// Default to bash
|
|
289
|
-
return 'bash';
|
|
290
|
-
} catch (err) {
|
|
291
|
-
return 'bash';
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
/**
|
|
296
|
-
* Get shell configuration file path
|
|
297
|
-
*/
|
|
298
|
-
function getShellConfigFile(shell) {
|
|
299
|
-
const home = os.homedir();
|
|
300
|
-
|
|
301
|
-
switch (shell) {
|
|
302
|
-
case 'zsh':
|
|
303
|
-
return path.join(home, '.zshrc');
|
|
304
|
-
case 'bash':
|
|
305
|
-
// Prefer .bashrc on Linux, .bash_profile on macOS
|
|
306
|
-
if (process.platform === 'darwin') {
|
|
307
|
-
return path.join(home, '.bash_profile');
|
|
308
|
-
}
|
|
309
|
-
return path.join(home, '.bashrc');
|
|
310
|
-
case 'fish':
|
|
311
|
-
return path.join(home, '.config', 'fish', 'config.fish');
|
|
312
|
-
default:
|
|
313
|
-
return path.join(home, '.bashrc');
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* Check if environment variable is already configured
|
|
319
|
-
*/
|
|
320
|
-
async function checkIfAlreadyConfigured(configFile) {
|
|
321
|
-
try {
|
|
322
|
-
const content = await fs.readFile(configFile, 'utf-8');
|
|
323
|
-
const envVarLine = `export ${ENV_VAR_NAME}=`;
|
|
324
|
-
|
|
325
|
-
if (content.includes(envVarLine)) {
|
|
326
|
-
// Check if it's the correct path
|
|
327
|
-
const regex = new RegExp(`export ${ENV_VAR_NAME}=["']?([^"'\\n]+)["']?`);
|
|
328
|
-
const match = content.match(regex);
|
|
329
|
-
|
|
330
|
-
if (match && match[1]) {
|
|
331
|
-
const currentPath = match[1].replace(/^~/, os.homedir());
|
|
332
|
-
const targetPath = EXTENSION_DIR;
|
|
333
|
-
|
|
334
|
-
if (path.resolve(currentPath) === path.resolve(targetPath)) {
|
|
335
|
-
return { configured: true, needsUpdate: false, currentPath };
|
|
336
|
-
} else {
|
|
337
|
-
return { configured: true, needsUpdate: true, currentPath };
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
} catch (err) {
|
|
342
|
-
// File doesn't exist or can't be read
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
return { configured: false, needsUpdate: false };
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
/**
|
|
349
|
-
* Update shell configuration
|
|
350
|
-
*/
|
|
351
|
-
async function updateShellConfig(configFile, envVarValue) {
|
|
352
|
-
// Create backup
|
|
353
|
-
const backupFile = `${configFile}.chromedebug-backup`;
|
|
354
|
-
try {
|
|
355
|
-
await fs.copyFile(configFile, backupFile);
|
|
356
|
-
} catch (err) {
|
|
357
|
-
// Config file might not exist yet
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
// Read existing content
|
|
361
|
-
let content = '';
|
|
362
|
-
try {
|
|
363
|
-
content = await fs.readFile(configFile, 'utf-8');
|
|
364
|
-
} catch (err) {
|
|
365
|
-
// File doesn't exist, create new
|
|
366
|
-
if (configFile.includes('.bash')) {
|
|
367
|
-
content = '#!/bin/bash\n\n';
|
|
368
|
-
} else if (configFile.includes('.zsh')) {
|
|
369
|
-
content = '#!/bin/zsh\n\n';
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
// Check if already configured
|
|
374
|
-
const status = await checkIfAlreadyConfigured(configFile);
|
|
375
|
-
|
|
376
|
-
if (status.configured && !status.needsUpdate) {
|
|
377
|
-
return { updated: false, reason: 'already_configured' };
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// Add or update environment variable
|
|
381
|
-
const envVarLine = `export ${ENV_VAR_NAME}="${envVarValue}"`;
|
|
382
|
-
const commentLine = '# ChromeDebug PRO extension path';
|
|
383
|
-
|
|
384
|
-
if (status.configured && status.needsUpdate) {
|
|
385
|
-
// Update existing line
|
|
386
|
-
const regex = new RegExp(`export ${ENV_VAR_NAME}=["']?[^"'\\n]+["']?`, 'g');
|
|
387
|
-
content = content.replace(regex, envVarLine);
|
|
388
|
-
} else {
|
|
389
|
-
// Add new lines
|
|
390
|
-
if (!content.endsWith('\n')) {
|
|
391
|
-
content += '\n';
|
|
392
|
-
}
|
|
393
|
-
content += `\n${commentLine}\n${envVarLine}\n`;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
// Write updated content
|
|
397
|
-
await fs.writeFile(configFile, content, 'utf-8');
|
|
398
|
-
|
|
399
|
-
// Ensure proper permissions
|
|
400
|
-
await fs.chmod(configFile, 0o644);
|
|
401
|
-
|
|
402
|
-
return { updated: true, reason: status.needsUpdate ? 'updated' : 'added' };
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
/**
|
|
406
|
-
* Configure shell environment
|
|
407
|
-
*/
|
|
408
|
-
async function configureShell() {
|
|
409
|
-
const shell = detectShell();
|
|
410
|
-
const configFile = getShellConfigFile(shell);
|
|
411
|
-
|
|
412
|
-
await logInstall(`Detected shell: ${shell}`);
|
|
413
|
-
await logInstall(`Config file: ${configFile}`);
|
|
414
|
-
|
|
415
|
-
// Ensure config directory exists (for fish)
|
|
416
|
-
const configDir = path.dirname(configFile);
|
|
417
|
-
await fs.mkdir(configDir, { recursive: true });
|
|
418
|
-
|
|
419
|
-
// Update configuration
|
|
420
|
-
const result = await updateShellConfig(configFile, EXTENSION_DIR);
|
|
421
|
-
|
|
422
|
-
return { shell, configFile, ...result };
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
/**
|
|
426
|
-
* Main installation flow
|
|
427
|
-
*/
|
|
428
|
-
export async function installPro(args) {
|
|
429
|
-
console.log('\nChromeDebug PRO License Installer\n');
|
|
430
|
-
|
|
431
|
-
try {
|
|
432
|
-
// Create ChromeDebug directory
|
|
433
|
-
await fs.mkdir(CHROMEDEBUG_DIR, { recursive: true });
|
|
434
|
-
|
|
435
|
-
// Start logging
|
|
436
|
-
await logInstall('=== Installation Started ===');
|
|
437
|
-
|
|
438
|
-
// 1. Validate arguments
|
|
439
|
-
const parsedArgs = validateArguments(args);
|
|
440
|
-
if (!parsedArgs) {
|
|
441
|
-
return process.exit(1);
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
const { license, extensionPath } = parsedArgs;
|
|
445
|
-
|
|
446
|
-
// 2. Validate license key
|
|
447
|
-
console.log('Validating license key...');
|
|
448
|
-
if (!validateLicenseKey(license)) {
|
|
449
|
-
await logInstall(`Invalid license key: ${maskLicenseKey(license)}`, true);
|
|
450
|
-
return process.exit(1);
|
|
451
|
-
}
|
|
452
|
-
await logInstall(`License key validated: ${maskLicenseKey(license)}`);
|
|
453
|
-
console.log(` License key validated: ${maskLicenseKey(license)}\n`);
|
|
454
|
-
|
|
455
|
-
// 3. Pre-flight checks
|
|
456
|
-
if (!(await preflightChecks())) {
|
|
457
|
-
await logInstall('Pre-flight checks failed', true);
|
|
458
|
-
return process.exit(1);
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
// 4. Check for existing installation
|
|
462
|
-
const existing = await checkExistingInstallation();
|
|
463
|
-
if (existing.exists) {
|
|
464
|
-
const message = `
|
|
465
|
-
Existing PRO installation found:
|
|
466
|
-
- Installed: ${new Date(existing.installed_at).toLocaleDateString()}
|
|
467
|
-
- Version: ${existing.extension_version}
|
|
468
|
-
- License: ${maskLicenseKey(existing.license_key)}
|
|
469
|
-
|
|
470
|
-
This will overwrite the existing installation.`;
|
|
471
|
-
|
|
472
|
-
promptConfirmation(message);
|
|
473
|
-
await logInstall('Overwriting existing installation');
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
// 5. Extract extension
|
|
477
|
-
console.log('Extracting PRO extension...');
|
|
478
|
-
const extensionInfo = await extractExtension(extensionPath, EXTENSION_DIR);
|
|
479
|
-
await logInstall(`Extension extracted: ${extensionInfo.name} v${extensionInfo.version}`);
|
|
480
|
-
console.log(` PRO extension extracted to ${EXTENSION_DIR}`);
|
|
481
|
-
console.log(` Version: ${extensionInfo.version}\n`);
|
|
482
|
-
|
|
483
|
-
// Set extension directory permissions
|
|
484
|
-
await fs.chmod(EXTENSION_DIR, 0o755);
|
|
485
|
-
|
|
486
|
-
// 6. Save license information
|
|
487
|
-
console.log('Saving license information...');
|
|
488
|
-
await saveLicenseInfo(license, extensionInfo.version);
|
|
489
|
-
await logInstall(`License saved: ${maskLicenseKey(license)}`);
|
|
490
|
-
console.log(` License saved to ${LICENSE_FILE}\n`);
|
|
491
|
-
|
|
492
|
-
// 7. Configure shell environment
|
|
493
|
-
console.log('Configuring shell environment...');
|
|
494
|
-
let shellResult;
|
|
495
|
-
let shellConfigFailed = false;
|
|
496
|
-
|
|
497
|
-
try {
|
|
498
|
-
shellResult = await configureShell();
|
|
499
|
-
await logInstall(`Shell configured: ${shellResult.shell} (${shellResult.configFile})`);
|
|
500
|
-
|
|
501
|
-
if (shellResult.updated) {
|
|
502
|
-
console.log(` Shell configured: export ${ENV_VAR_NAME}="${EXTENSION_DIR}"`);
|
|
503
|
-
console.log(` Config file: ${shellResult.configFile}`);
|
|
504
|
-
console.log(` Backup saved: ${shellResult.configFile}.chromedebug-backup\n`);
|
|
505
|
-
} else {
|
|
506
|
-
console.log(` Shell already configured correctly\n`);
|
|
507
|
-
}
|
|
508
|
-
} catch (shellErr) {
|
|
509
|
-
shellConfigFailed = true;
|
|
510
|
-
await logInstall(`Shell configuration failed: ${shellErr.message}`, false);
|
|
511
|
-
console.log(`⚠ Could not automatically configure shell environment`);
|
|
512
|
-
console.log(` Error: ${shellErr.message}\n`);
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
// 8. Success!
|
|
516
|
-
await logInstall('=== Installation Completed Successfully ===');
|
|
517
|
-
|
|
518
|
-
console.log('');
|
|
519
|
-
console.log('PRO features are now active!');
|
|
520
|
-
console.log('\n');
|
|
521
|
-
|
|
522
|
-
console.log('Next steps:');
|
|
523
|
-
console.log(`1. Restart your terminal or run: source ${shellResult.configFile}`);
|
|
524
|
-
console.log('2. Close any running Chrome instances');
|
|
525
|
-
console.log('3. Launch ChromeDebug MCP: chromedebug-mcp');
|
|
526
|
-
console.log('4. The PRO extension will be loaded automatically\n');
|
|
527
|
-
|
|
528
|
-
console.log('Verify installation:');
|
|
529
|
-
console.log(` echo $${ENV_VAR_NAME}`);
|
|
530
|
-
console.log(` (should output: ${EXTENSION_DIR})\n`);
|
|
531
|
-
|
|
532
|
-
console.log('Installation log: ' + INSTALL_LOG);
|
|
533
|
-
console.log('\nNeed help? https://github.com/dynamicupgrade/ChromeDebug/issues\n');
|
|
534
|
-
|
|
535
|
-
} catch (err) {
|
|
536
|
-
await logInstall(`Installation failed: ${err.message}`, true);
|
|
537
|
-
console.error('\n Installation failed\n');
|
|
538
|
-
console.error('Check the installation log for details: ' + INSTALL_LOG);
|
|
539
|
-
console.error('\nNeed help? https://github.com/dynamicupgrade/ChromeDebug/issues\n');
|
|
540
|
-
process.exit(1);
|
|
541
|
-
}
|
|
542
|
-
}
|