@dynamicu/chromedebug-mcp 2.5.5 → 2.5.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.
@@ -0,0 +1,554 @@
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: Accepts UUID format (LemonSqueezy) or custom format
26
+ // UUID: 0CD6ACFD-40B1-4EAF-955C-C4D581A8984B
27
+ // Custom: ABC12345-0123456789ABCDEF
28
+ const LICENSE_KEY_REGEX = /^[A-Z0-9]{8,}(-[A-Z0-9]{4,})+$/i;
29
+
30
+ /**
31
+ * Log installation activity
32
+ */
33
+ async function logInstall(message, isError = false) {
34
+ const timestamp = new Date().toISOString();
35
+ const logMessage = `[${timestamp}] ${isError ? 'ERROR: ' : ''}${message}\n`;
36
+
37
+ try {
38
+ await fs.appendFile(INSTALL_LOG, logMessage);
39
+ } catch (err) {
40
+ // Ignore log errors - don't let logging break installation
41
+ }
42
+
43
+ if (isError) {
44
+ console.error(message);
45
+ } else {
46
+ console.log(message);
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Validate command-line arguments
52
+ */
53
+ function validateArguments(args) {
54
+ const licenseIndex = args.indexOf('--license');
55
+ const pathIndex = args.indexOf('--extension-path');
56
+
57
+ if (licenseIndex === -1 || pathIndex === -1) {
58
+ console.error(`
59
+ ChromeDebug PRO License Installer
60
+
61
+ Usage:
62
+ chromedebug-mcp install-pro --license YOUR_LICENSE_KEY --extension-path /path/to/chromedebug-pro-extension.zip
63
+
64
+ Required Arguments:
65
+ --license Your PRO license key (received after purchase)
66
+ --extension-path Path to the PRO extension ZIP file
67
+
68
+ Example:
69
+ chromedebug-mcp install-pro \\
70
+ --license ABC12345-0123456789ABCDEF \\
71
+ --extension-path ~/Downloads/chromedebug-pro-extension.zip
72
+
73
+ Get PRO: https://chromedebug.com/pro
74
+ `);
75
+ return null;
76
+ }
77
+
78
+ const license = args[licenseIndex + 1];
79
+ const extensionPath = args[pathIndex + 1];
80
+
81
+ if (!license || !extensionPath) {
82
+ console.error('Error: Missing values for --license or --extension-path');
83
+ return null;
84
+ }
85
+
86
+ return { license, extensionPath };
87
+ }
88
+
89
+ /**
90
+ * Validate license key format
91
+ */
92
+ function validateLicenseKey(key) {
93
+ if (!LICENSE_KEY_REGEX.test(key)) {
94
+ console.error(`
95
+ Error: Invalid license key format
96
+
97
+ Expected formats:
98
+ • UUID (LemonSqueezy): 0CD6ACFD-40B1-4EAF-955C-C4D581A8984B
99
+ • Custom: ABC12345-0123456789ABCDEF
100
+
101
+ Your key: ${maskLicenseKey(key)}
102
+
103
+ Please check your purchase confirmation email for the correct license key.
104
+ Need help? https://github.com/dynamicupgrade/ChromeDebug/issues
105
+ `);
106
+ return false;
107
+ }
108
+ return true;
109
+ }
110
+
111
+ /**
112
+ * Mask license key for display (show first 6 and last 3 chars)
113
+ */
114
+ function maskLicenseKey(key) {
115
+ if (key.length <= 9) return '***';
116
+ return `${key.substring(0, 6)}...${key.substring(key.length - 3)}`;
117
+ }
118
+
119
+ /**
120
+ * Check pre-flight conditions
121
+ */
122
+ async function preflightChecks() {
123
+ // Check write permission to home directory
124
+ try {
125
+ const testFile = path.join(os.homedir(), '.chromedebug-test');
126
+ await fs.writeFile(testFile, 'test');
127
+ await fs.unlink(testFile);
128
+ } catch (err) {
129
+ console.error(`
130
+ Error: Cannot write to home directory
131
+
132
+ Possible causes:
133
+ 1. Permission denied - check: ls -ld ~
134
+ 2. Disk full - check: df -h ~
135
+ 3. Home directory not writable
136
+
137
+ Need help? https://github.com/dynamicupgrade/ChromeDebug/issues
138
+ `);
139
+ return false;
140
+ }
141
+
142
+ return true;
143
+ }
144
+
145
+ /**
146
+ * Check for existing installation
147
+ */
148
+ async function checkExistingInstallation() {
149
+ try {
150
+ const stats = await fs.stat(LICENSE_FILE);
151
+ if (stats.isFile()) {
152
+ const licenseData = JSON.parse(await fs.readFile(LICENSE_FILE, 'utf-8'));
153
+ return {
154
+ exists: true,
155
+ installed_at: licenseData.installed_at,
156
+ extension_version: licenseData.extension_version,
157
+ license_key: licenseData.license_key
158
+ };
159
+ }
160
+ } catch (err) {
161
+ // No existing installation
162
+ }
163
+ return { exists: false };
164
+ }
165
+
166
+ /**
167
+ * Prompt for confirmation (synchronous for CLI)
168
+ */
169
+ function promptConfirmation(message) {
170
+ // For now, we'll auto-continue with a warning
171
+ // In future, could add readline for interactive prompts
172
+ console.log(`\n${message}`);
173
+ console.log('Continuing with installation...\n');
174
+ return true;
175
+ }
176
+
177
+ /**
178
+ * Extract and validate ZIP file
179
+ */
180
+ async function extractExtension(zipPath, targetPath) {
181
+ // Resolve path (handle ~ and relative paths)
182
+ const resolvedZipPath = zipPath.startsWith('~')
183
+ ? path.join(os.homedir(), zipPath.slice(1))
184
+ : path.resolve(zipPath);
185
+
186
+ // Check if ZIP exists
187
+ try {
188
+ await fs.access(resolvedZipPath);
189
+ } catch (err) {
190
+ console.error(`
191
+ Error: Extension ZIP file not found
192
+
193
+ Path: ${resolvedZipPath}
194
+
195
+ Please check:
196
+ 1. File path is correct
197
+ 2. File exists at the specified location
198
+ 3. You have read permission
199
+
200
+ Need help? https://github.com/dynamicupgrade/ChromeDebug/issues
201
+ `);
202
+ throw err;
203
+ }
204
+
205
+ // Create target directory (clean if exists)
206
+ try {
207
+ await fs.rm(targetPath, { recursive: true, force: true });
208
+ } catch (err) {
209
+ // Ignore - directory might not exist
210
+ }
211
+
212
+ await fs.mkdir(targetPath, { recursive: true });
213
+
214
+ // Extract ZIP
215
+ try {
216
+ const zip = new AdmZip(resolvedZipPath);
217
+ zip.extractAllTo(targetPath, true);
218
+ } catch (err) {
219
+ console.error(`
220
+ Error: Failed to extract ZIP file
221
+
222
+ Possible causes:
223
+ 1. ZIP file is corrupted - try re-downloading
224
+ 2. Not a valid ZIP file
225
+ 3. Disk space full
226
+
227
+ Error details: ${err.message}
228
+
229
+ Need help? https://github.com/dynamicupgrade/ChromeDebug/issues
230
+ `);
231
+ throw err;
232
+ }
233
+
234
+ // Validate extracted contents
235
+ const manifestPath = path.join(targetPath, 'manifest.json');
236
+ try {
237
+ const manifestContent = await fs.readFile(manifestPath, 'utf-8');
238
+ const manifest = JSON.parse(manifestContent);
239
+
240
+ if (!manifest.name || !manifest.name.toLowerCase().includes('chromedebug')) {
241
+ throw new Error('Extension does not appear to be ChromeDebug');
242
+ }
243
+
244
+ return {
245
+ version: manifest.version,
246
+ name: manifest.name
247
+ };
248
+ } catch (err) {
249
+ console.error(`
250
+ Error: Invalid extension package
251
+
252
+ The extracted files do not appear to be a valid ChromeDebug extension.
253
+ Expected manifest.json with ChromeDebug name.
254
+
255
+ Please verify you downloaded the correct PRO extension ZIP.
256
+
257
+ Need help? https://github.com/dynamicupgrade/ChromeDebug/issues
258
+ `);
259
+ throw err;
260
+ }
261
+ }
262
+
263
+ /**
264
+ * Save license information
265
+ */
266
+ async function saveLicenseInfo(license, extensionVersion) {
267
+ const licenseData = {
268
+ license_key: license,
269
+ installed_at: new Date().toISOString(),
270
+ extension_version: extensionVersion,
271
+ extension_path: EXTENSION_DIR
272
+ };
273
+
274
+ await fs.writeFile(LICENSE_FILE, JSON.stringify(licenseData, null, 2), 'utf-8');
275
+
276
+ // Set restrictive permissions (owner read/write only)
277
+ await fs.chmod(LICENSE_FILE, 0o600);
278
+ }
279
+
280
+ /**
281
+ * Detect user's shell
282
+ */
283
+ function detectShell() {
284
+ try {
285
+ const shell = process.env.SHELL || '';
286
+
287
+ if (shell.includes('zsh')) return 'zsh';
288
+ if (shell.includes('bash')) return 'bash';
289
+ if (shell.includes('fish')) return 'fish';
290
+
291
+ // Default to bash
292
+ return 'bash';
293
+ } catch (err) {
294
+ return 'bash';
295
+ }
296
+ }
297
+
298
+ /**
299
+ * Get shell configuration file path
300
+ */
301
+ function getShellConfigFile(shell) {
302
+ const home = os.homedir();
303
+
304
+ switch (shell) {
305
+ case 'zsh':
306
+ return path.join(home, '.zshrc');
307
+ case 'bash':
308
+ // Prefer .bashrc on Linux, .bash_profile on macOS
309
+ if (process.platform === 'darwin') {
310
+ return path.join(home, '.bash_profile');
311
+ }
312
+ return path.join(home, '.bashrc');
313
+ case 'fish':
314
+ return path.join(home, '.config', 'fish', 'config.fish');
315
+ default:
316
+ return path.join(home, '.bashrc');
317
+ }
318
+ }
319
+
320
+ /**
321
+ * Check if environment variable is already configured
322
+ */
323
+ async function checkIfAlreadyConfigured(configFile) {
324
+ try {
325
+ const content = await fs.readFile(configFile, 'utf-8');
326
+ const envVarLine = `export ${ENV_VAR_NAME}=`;
327
+
328
+ if (content.includes(envVarLine)) {
329
+ // Check if it's the correct path
330
+ const regex = new RegExp(`export ${ENV_VAR_NAME}=["']?([^"'\\n]+)["']?`);
331
+ const match = content.match(regex);
332
+
333
+ if (match && match[1]) {
334
+ const currentPath = match[1].replace(/^~/, os.homedir());
335
+ const targetPath = EXTENSION_DIR;
336
+
337
+ if (path.resolve(currentPath) === path.resolve(targetPath)) {
338
+ return { configured: true, needsUpdate: false, currentPath };
339
+ } else {
340
+ return { configured: true, needsUpdate: true, currentPath };
341
+ }
342
+ }
343
+ }
344
+ } catch (err) {
345
+ // File doesn't exist or can't be read
346
+ }
347
+
348
+ return { configured: false, needsUpdate: false };
349
+ }
350
+
351
+ /**
352
+ * Update shell configuration
353
+ */
354
+ async function updateShellConfig(configFile, envVarValue) {
355
+ // Create backup
356
+ const backupFile = `${configFile}.chromedebug-backup`;
357
+ try {
358
+ await fs.copyFile(configFile, backupFile);
359
+ } catch (err) {
360
+ // Config file might not exist yet
361
+ }
362
+
363
+ // Read existing content
364
+ let content = '';
365
+ try {
366
+ content = await fs.readFile(configFile, 'utf-8');
367
+ } catch (err) {
368
+ // File doesn't exist, create new
369
+ if (configFile.includes('.bash')) {
370
+ content = '#!/bin/bash\n\n';
371
+ } else if (configFile.includes('.zsh')) {
372
+ content = '#!/bin/zsh\n\n';
373
+ }
374
+ }
375
+
376
+ // Check if already configured
377
+ const status = await checkIfAlreadyConfigured(configFile);
378
+
379
+ if (status.configured && !status.needsUpdate) {
380
+ return { updated: false, reason: 'already_configured' };
381
+ }
382
+
383
+ // Add or update environment variable
384
+ const envVarLine = `export ${ENV_VAR_NAME}="${envVarValue}"`;
385
+ const commentLine = '# ChromeDebug PRO extension path';
386
+
387
+ if (status.configured && status.needsUpdate) {
388
+ // Update existing line
389
+ const regex = new RegExp(`export ${ENV_VAR_NAME}=["']?[^"'\\n]+["']?`, 'g');
390
+ content = content.replace(regex, envVarLine);
391
+ } else {
392
+ // Add new lines
393
+ if (!content.endsWith('\n')) {
394
+ content += '\n';
395
+ }
396
+ content += `\n${commentLine}\n${envVarLine}\n`;
397
+ }
398
+
399
+ // Write updated content
400
+ await fs.writeFile(configFile, content, 'utf-8');
401
+
402
+ // Ensure proper permissions
403
+ await fs.chmod(configFile, 0o644);
404
+
405
+ return { updated: true, reason: status.needsUpdate ? 'updated' : 'added' };
406
+ }
407
+
408
+ /**
409
+ * Configure shell environment
410
+ */
411
+ async function configureShell() {
412
+ const shell = detectShell();
413
+ const configFile = getShellConfigFile(shell);
414
+
415
+ await logInstall(`Detected shell: ${shell}`);
416
+ await logInstall(`Config file: ${configFile}`);
417
+
418
+ // Ensure config directory exists (for fish)
419
+ const configDir = path.dirname(configFile);
420
+ await fs.mkdir(configDir, { recursive: true });
421
+
422
+ // Update configuration
423
+ const result = await updateShellConfig(configFile, EXTENSION_DIR);
424
+
425
+ return { shell, configFile, ...result };
426
+ }
427
+
428
+ /**
429
+ * Main installation flow
430
+ */
431
+ export async function installPro(args) {
432
+ console.log('\nChromeDebug PRO License Installer\n');
433
+
434
+ try {
435
+ // Create ChromeDebug directory
436
+ await fs.mkdir(CHROMEDEBUG_DIR, { recursive: true });
437
+
438
+ // Start logging
439
+ await logInstall('=== Installation Started ===');
440
+
441
+ // 1. Validate arguments
442
+ const parsedArgs = validateArguments(args);
443
+ if (!parsedArgs) {
444
+ return process.exit(1);
445
+ }
446
+
447
+ const { license, extensionPath } = parsedArgs;
448
+
449
+ // 2. Validate license key
450
+ console.log('Validating license key...');
451
+ if (!validateLicenseKey(license)) {
452
+ await logInstall(`Invalid license key: ${maskLicenseKey(license)}`, true);
453
+ return process.exit(1);
454
+ }
455
+ await logInstall(`License key validated: ${maskLicenseKey(license)}`);
456
+ console.log(` License key validated: ${maskLicenseKey(license)}\n`);
457
+
458
+ // 3. Pre-flight checks
459
+ if (!(await preflightChecks())) {
460
+ await logInstall('Pre-flight checks failed', true);
461
+ return process.exit(1);
462
+ }
463
+
464
+ // 4. Check for existing installation
465
+ const existing = await checkExistingInstallation();
466
+ if (existing.exists) {
467
+ const message = `
468
+ Existing PRO installation found:
469
+ - Installed: ${new Date(existing.installed_at).toLocaleDateString()}
470
+ - Version: ${existing.extension_version}
471
+ - License: ${maskLicenseKey(existing.license_key)}
472
+
473
+ This will overwrite the existing installation.`;
474
+
475
+ promptConfirmation(message);
476
+ await logInstall('Overwriting existing installation');
477
+ }
478
+
479
+ // 5. Extract extension
480
+ console.log('Extracting PRO extension...');
481
+ const extensionInfo = await extractExtension(extensionPath, EXTENSION_DIR);
482
+ await logInstall(`Extension extracted: ${extensionInfo.name} v${extensionInfo.version}`);
483
+ console.log(` PRO extension extracted to ${EXTENSION_DIR}`);
484
+ console.log(` Version: ${extensionInfo.version}\n`);
485
+
486
+ // Set extension directory permissions
487
+ await fs.chmod(EXTENSION_DIR, 0o755);
488
+
489
+ // 6. Save license information
490
+ console.log('Saving license information...');
491
+ await saveLicenseInfo(license, extensionInfo.version);
492
+ await logInstall(`License saved: ${maskLicenseKey(license)}`);
493
+ console.log(` License saved to ${LICENSE_FILE}\n`);
494
+
495
+ // 7. Configure shell environment
496
+ console.log('Configuring shell environment...');
497
+ let shellResult;
498
+ let shellConfigFailed = false;
499
+
500
+ try {
501
+ shellResult = await configureShell();
502
+ await logInstall(`Shell configured: ${shellResult.shell} (${shellResult.configFile})`);
503
+
504
+ if (shellResult.updated) {
505
+ console.log(` Shell configured: export ${ENV_VAR_NAME}="${EXTENSION_DIR}"`);
506
+ console.log(` Config file: ${shellResult.configFile}`);
507
+ console.log(` Backup saved: ${shellResult.configFile}.chromedebug-backup\n`);
508
+ } else {
509
+ console.log(` Shell already configured correctly\n`);
510
+ }
511
+ } catch (shellErr) {
512
+ shellConfigFailed = true;
513
+ await logInstall(`Shell configuration failed: ${shellErr.message}`, false);
514
+ console.log(`⚠ Could not automatically configure shell environment`);
515
+ console.log(` Error: ${shellErr.message}\n`);
516
+ }
517
+
518
+ // 8. Success!
519
+ await logInstall('=== Installation Completed Successfully ===');
520
+
521
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
522
+ console.log('✓ PRO features are now active!');
523
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
524
+
525
+ if (shellConfigFailed) {
526
+ console.log('⚠ Manual Configuration Required:');
527
+ console.log(` Add this line to your shell configuration file:`);
528
+ console.log(` export ${ENV_VAR_NAME}="${EXTENSION_DIR}"\n`);
529
+ }
530
+
531
+ console.log('Next steps:');
532
+ if (shellResult && shellResult.configFile) {
533
+ console.log(`1. Restart your terminal or run: source ${shellResult.configFile}`);
534
+ } else {
535
+ console.log(`1. Add the environment variable to your shell config and restart terminal`);
536
+ }
537
+ console.log('2. Close any running Chrome instances');
538
+ console.log('3. Launch ChromeDebug MCP: chromedebug-mcp');
539
+ console.log('4. The PRO extension will be loaded automatically\n');
540
+
541
+ console.log('Verify installation:');
542
+ console.log(` echo $${ENV_VAR_NAME}`);
543
+ console.log(` (should output: ${EXTENSION_DIR})\n`);
544
+
545
+ console.log('Installation log: ' + INSTALL_LOG);
546
+ console.log('\nNeed help? https://github.com/dynamicupgrade/ChromeDebug/issues\n');
547
+ } catch (err) {
548
+ await logInstall(`Installation failed: ${err.message}`, true);
549
+ console.error('\n Installation failed\n');
550
+ console.error('Check the installation log for details: ' + INSTALL_LOG);
551
+ console.error('\nNeed help? https://github.com/dynamicupgrade/ChromeDebug/issues\n');
552
+ process.exit(1);
553
+ }
554
+ }