@dynamicu/chromedebug-mcp 2.6.6 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/CLAUDE.md +1 -1
  2. package/README.md +1 -1
  3. package/chrome-extension/activation-manager.js +18 -4
  4. package/chrome-extension/background.js +1044 -552
  5. package/chrome-extension/browser-recording-manager.js +256 -0
  6. package/chrome-extension/chrome-debug-logger.js +168 -0
  7. package/chrome-extension/console-interception-library.js +430 -0
  8. package/chrome-extension/content.css +16 -16
  9. package/chrome-extension/content.js +617 -215
  10. package/chrome-extension/data-buffer.js +206 -17
  11. package/chrome-extension/extension-config.js +1 -1
  12. package/chrome-extension/frame-capture.js +52 -15
  13. package/chrome-extension/license-helper.js +26 -0
  14. package/chrome-extension/manifest.free.json +3 -6
  15. package/chrome-extension/options.js +1 -1
  16. package/chrome-extension/popup.html +315 -181
  17. package/chrome-extension/popup.js +673 -526
  18. package/chrome-extension/pro/enhanced-capture.js +406 -0
  19. package/chrome-extension/pro/frame-editor.html +410 -0
  20. package/chrome-extension/pro/frame-editor.js +1496 -0
  21. package/chrome-extension/pro/function-tracker.js +843 -0
  22. package/chrome-extension/pro/jszip.min.js +13 -0
  23. package/config/chromedebug-config.json +101 -0
  24. package/dist/chromedebug-extension-free.zip +0 -0
  25. package/package.json +3 -1
  26. package/scripts/package-pro-extension.js +1 -1
  27. package/scripts/webpack.config.free.cjs +11 -8
  28. package/scripts/webpack.config.pro.cjs +5 -0
  29. package/src/chrome-controller.js +7 -7
  30. package/src/cli.js +2 -2
  31. package/src/database.js +61 -9
  32. package/src/http-server.js +3 -2
  33. package/src/index.js +9 -6
  34. package/src/mcp/server.js +2 -2
  35. package/src/services/process-manager.js +10 -6
  36. package/src/services/process-tracker.js +10 -5
  37. package/src/services/profile-manager.js +17 -2
  38. package/src/validation/schemas.js +36 -6
  39. package/src/index-direct.js +0 -157
  40. package/src/index-modular.js +0 -219
  41. package/src/index-monolithic-backup.js +0 -2230
  42. package/src/legacy/chrome-controller-old.js +0 -1406
  43. package/src/legacy/index-express.js +0 -625
  44. package/src/legacy/index-old.js +0 -977
  45. package/src/legacy/routes.js +0 -260
  46. package/src/legacy/shared-storage.js +0 -101
@@ -0,0 +1,430 @@
1
+ /**
2
+ * Console Interception Library
3
+ * Shared code for both Screen Recording and Workflow Recording
4
+ *
5
+ * This library provides console log interception and cleanup functionality
6
+ * that is used identically by both recording systems.
7
+ *
8
+ * DRY Principle: Don't Repeat Yourself
9
+ * - Reduces code duplication from 600+ lines to reusable functions
10
+ * - Single source of truth for console interception logic
11
+ * - Easier to maintain and update
12
+ *
13
+ * Usage Example:
14
+ * ```javascript
15
+ * // Screen recording config
16
+ * const screenConfig = {
17
+ * overrideFlagName: '__chromePilotConsoleOverridden',
18
+ * originalConsoleName: '__chromePilotOriginalConsole',
19
+ * relayFlagName: '__chromePilotConsoleRelay',
20
+ * messageType: 'chrome-debug-console-log',
21
+ * backgroundAction: 'consoleLog'
22
+ * };
23
+ *
24
+ * // Start interception
25
+ * await startConsoleInterception(tabId, screenConfig);
26
+ *
27
+ * // Stop interception
28
+ * await stopConsoleInterception(tabId, screenConfig);
29
+ * ```
30
+ */
31
+
32
+ // Console Interception Library - Direct function definitions for importScripts compatibility
33
+
34
+ /**
35
+ * Injectable function for MAIN world console interception
36
+ * This function will be serialized and injected into the page's MAIN execution context
37
+ *
38
+ * @param {string} overrideFlagName - Window property to track if already overridden
39
+ * @param {string} originalConsoleName - Window property to store original console methods
40
+ * @param {string} messageType - The postMessage type for log relay
41
+ * @returns {string} Status message
42
+ */
43
+ function injectConsoleInterceptor(overrideFlagName, originalConsoleName, messageType) {
44
+ // Check if we've already overridden console methods
45
+ if (window[overrideFlagName]) {
46
+ console.log('[Chrome Debug] Console already overridden, skipping');
47
+ return 'already_installed';
48
+ }
49
+ window[overrideFlagName] = true;
50
+ console.log('[Chrome Debug] Installing console interceptor');
51
+
52
+ // CRITICAL: Store original console methods in window for later restoration
53
+ window[originalConsoleName] = {
54
+ log: console.log,
55
+ error: console.error,
56
+ warn: console.warn,
57
+ info: console.info,
58
+ debug: console.debug,
59
+ trace: console.trace,
60
+ table: console.table,
61
+ dir: console.dir,
62
+ group: console.group,
63
+ groupEnd: console.groupEnd,
64
+ time: console.time,
65
+ timeEnd: console.timeEnd,
66
+ count: console.count
67
+ };
68
+
69
+ // Duck-type check for Error-like objects (handles cross-realm errors)
70
+ const isErrorLike = (obj) => {
71
+ if (obj instanceof Error) return true;
72
+ // Duck-type check for cross-realm errors (e.g., from iframes, web workers)
73
+ return obj !== null &&
74
+ typeof obj === 'object' &&
75
+ typeof obj.message === 'string' &&
76
+ (typeof obj.stack === 'string' || typeof obj.name === 'string');
77
+ };
78
+
79
+ // Serialize Error objects properly (message, stack, name are non-enumerable)
80
+ const serializeError = (err, depth = 0) => {
81
+ if (depth > 3) return '[Error: max depth reached]';
82
+
83
+ const result = {
84
+ name: err.name || 'Error',
85
+ message: err.message || '',
86
+ };
87
+
88
+ // Capture stack trace with intelligent truncation
89
+ if (err.stack) {
90
+ const stackLines = err.stack.split('\n');
91
+ let truncatedStack = [];
92
+ for (let i = 0; i < stackLines.length && i < 10; i++) {
93
+ truncatedStack.push(stackLines[i]);
94
+ // Stop at node_modules to reduce noise
95
+ if (stackLines[i].includes('node_modules')) {
96
+ truncatedStack.push(' ... (truncated at node_modules)');
97
+ break;
98
+ }
99
+ }
100
+ result.stack = truncatedStack.join('\n');
101
+ }
102
+
103
+ // Capture custom enumerable properties (e.g., Firebase error.code)
104
+ for (const key of Object.keys(err)) {
105
+ if (!['name', 'message', 'stack'].includes(key)) {
106
+ try {
107
+ if (err[key] instanceof Error || isErrorLike(err[key])) {
108
+ result[key] = serializeError(err[key], depth + 1);
109
+ } else {
110
+ result[key] = JSON.parse(JSON.stringify(err[key]));
111
+ }
112
+ } catch (e) {
113
+ result[key] = '[Could not serialize]';
114
+ }
115
+ }
116
+ }
117
+
118
+ // ES2022 error.cause support
119
+ if (err.cause) {
120
+ if (err.cause instanceof Error || isErrorLike(err.cause)) {
121
+ result.cause = serializeError(err.cause, depth + 1);
122
+ } else {
123
+ try {
124
+ result.cause = JSON.parse(JSON.stringify(err.cause));
125
+ } catch (e) {
126
+ result.cause = '[Could not serialize cause]';
127
+ }
128
+ }
129
+ }
130
+
131
+ return result;
132
+ };
133
+
134
+ // Create sendLog function with truncation logic
135
+ const sendLog = (level, args) => {
136
+ try {
137
+ // Pre-serialize arguments to strings to avoid structured clone errors
138
+ const serializedArgs = args.map(arg => {
139
+ try {
140
+ if (arg === null) return 'null';
141
+ if (arg === undefined) return 'undefined';
142
+ if (typeof arg === 'function') return '[Function: ' + (arg.name || 'anonymous') + ']';
143
+
144
+ // IMPORTANT: Check for Error objects BEFORE generic object handler
145
+ // Error properties (message, stack, name) are non-enumerable and JSON.stringify returns "{}"
146
+ if (arg instanceof Error || isErrorLike(arg)) {
147
+ const errorObj = serializeError(arg);
148
+ return JSON.stringify(errorObj, null, 2);
149
+ }
150
+
151
+ // Note: Element check removed - service workers have no DOM access, so this will never trigger
152
+ if (typeof arg === 'object') {
153
+ // Custom replacer to handle nested errors in objects
154
+ const errorReplacer = (key, value) => {
155
+ if (value instanceof Error || (value && isErrorLike(value))) {
156
+ return serializeError(value);
157
+ }
158
+ return value;
159
+ };
160
+
161
+ // Try to stringify, but limit depth to avoid circular references
162
+ let stringified = JSON.stringify(arg, errorReplacer, 2);
163
+
164
+ // Check if this looks like a base64 image and truncate it
165
+ if (stringified.includes('data:image/') && stringified.length > 1000) {
166
+ const match = stringified.match(/data:image\/([^;]+);base64,(.{0,100})/);
167
+ if (match) {
168
+ return `[Base64 Image: ${match[1]}, ${stringified.length} bytes total, truncated...]`;
169
+ }
170
+ }
171
+
172
+ // Truncate any extremely large strings
173
+ const maxLength = 5000;
174
+ if (stringified.length > maxLength) {
175
+ return stringified.substring(0, maxLength) + `... [TRUNCATED: ${stringified.length} total bytes]`;
176
+ }
177
+
178
+ return stringified;
179
+ }
180
+
181
+ // Also check for base64 strings directly
182
+ const strValue = String(arg);
183
+ if (strValue.includes('data:image/') && strValue.length > 1000) {
184
+ const match = strValue.match(/data:image\/([^;]+);base64,(.{0,100})/);
185
+ if (match) {
186
+ return `[Base64 Image: ${match[1]}, ${strValue.length} bytes total, truncated...]`;
187
+ }
188
+ }
189
+
190
+ // Truncate any extremely large strings
191
+ if (strValue.length > 5000) {
192
+ return strValue.substring(0, 5000) + `... [TRUNCATED: ${strValue.length} total bytes]`;
193
+ }
194
+
195
+ return strValue;
196
+ } catch (e) {
197
+ return '[Object: could not serialize]';
198
+ }
199
+ });
200
+
201
+ // Build the message string
202
+ const messageString = serializedArgs.join(' ');
203
+
204
+ // Filter out internal Chrome Debug/ChromePilot logs
205
+ // These are internal debugging messages that should not be captured in recordings
206
+ if (messageString.startsWith('[Chrome Debug]') ||
207
+ messageString.startsWith('[ChromePilot]') ||
208
+ messageString.startsWith('[Console Injection]') ||
209
+ messageString.startsWith('[Console Cleanup]')) {
210
+ return; // Skip internal logs
211
+ }
212
+
213
+ // Post message to content script
214
+ window.postMessage({
215
+ type: messageType,
216
+ log: {
217
+ level,
218
+ message: messageString,
219
+ timestamp: Date.now()
220
+ }
221
+ }, '*');
222
+ } catch (e) {
223
+ // Ignore errors when sending logs
224
+ }
225
+ };
226
+
227
+ // Override console methods
228
+ const methods = ['log', 'error', 'warn', 'info', 'debug', 'trace', 'table', 'dir', 'group', 'groupEnd', 'time', 'timeEnd', 'count'];
229
+ methods.forEach(method => {
230
+ const original = window[originalConsoleName][method];
231
+ console[method] = (...args) => {
232
+ sendLog(method, args);
233
+ original.apply(console, args);
234
+ };
235
+ });
236
+
237
+ return 'console_installed';
238
+ }
239
+
240
+ /**
241
+ * Injectable function for content script console relay
242
+ * Forwards console logs from MAIN world to background script
243
+ *
244
+ * @param {string} relayFlagName - Window property to track if relay is active
245
+ * @param {string} messageType - The postMessage type to listen for
246
+ * @param {string} backgroundAction - The action name to send to background script
247
+ */
248
+ function injectConsoleRelay(relayFlagName, messageType, backgroundAction) {
249
+ // Check if relay is already active
250
+ if (window[relayFlagName]) {
251
+ console.log('[Chrome Debug] Console relay already active, skipping');
252
+ return;
253
+ }
254
+
255
+ // Create and store the message handler
256
+ const messageHandler = (event) => {
257
+ if (event.data && event.data.type === messageType) {
258
+ // Forward to background script
259
+ chrome.runtime.sendMessage({
260
+ action: backgroundAction,
261
+ log: event.data.log
262
+ }).catch(() => {
263
+ // Background might not be available - that's OK
264
+ });
265
+ }
266
+ };
267
+
268
+ // Store reference for later removal
269
+ window[relayFlagName] = messageHandler;
270
+
271
+ // Listen for messages from main world
272
+ window.addEventListener('message', messageHandler);
273
+ console.log('[Chrome Debug] Console relay installed');
274
+ }
275
+
276
+ /**
277
+ * Injectable function for MAIN world console cleanup
278
+ * Restores original console methods
279
+ *
280
+ * @param {string} overrideFlagName - Window property that tracks override status
281
+ * @param {string} originalConsoleName - Window property with stored original methods
282
+ * @returns {object} Status object
283
+ */
284
+ function cleanupConsoleInterceptor(overrideFlagName, originalConsoleName) {
285
+ console.log('[Chrome Debug] ===== CLEANUP SCRIPT EXECUTING IN MAIN WORLD =====');
286
+ console.log('[Chrome Debug] ' + originalConsoleName + ' exists:', !!window[originalConsoleName]);
287
+ console.log('[Chrome Debug] ' + overrideFlagName + ':', window[overrideFlagName]);
288
+
289
+ if (window[originalConsoleName] && window[overrideFlagName]) {
290
+ console.log('[Chrome Debug] RESTORING original console methods');
291
+
292
+ // Restore all original console methods
293
+ Object.assign(console, window[originalConsoleName]);
294
+
295
+ // Clean up stored references
296
+ delete window[originalConsoleName];
297
+ delete window[overrideFlagName];
298
+
299
+ console.log('[Chrome Debug] ✅ Console methods restored successfully');
300
+ return { success: true, message: 'Console restored' };
301
+ } else {
302
+ console.warn('[Chrome Debug] ⚠️ No console override to restore');
303
+ return { success: false, message: 'No override found' };
304
+ }
305
+ }
306
+
307
+ /**
308
+ * Injectable function for content script relay cleanup
309
+ * Removes the message event listener
310
+ *
311
+ * @param {string} relayFlagName - Window property with stored message handler
312
+ * @returns {object} Status object
313
+ */
314
+ function cleanupConsoleRelay(relayFlagName) {
315
+ console.log('[Chrome Debug] ===== CLEANUP SCRIPT EXECUTING IN CONTENT SCRIPT =====');
316
+ console.log('[Chrome Debug] ' + relayFlagName + ' exists:', !!window[relayFlagName]);
317
+
318
+ if (window[relayFlagName]) {
319
+ console.log('[Chrome Debug] REMOVING console relay listener');
320
+ window.removeEventListener('message', window[relayFlagName]);
321
+ delete window[relayFlagName];
322
+ console.log('[Chrome Debug] ✅ Console relay removed successfully');
323
+ return { success: true, message: 'Relay removed' };
324
+ } else {
325
+ console.warn('[Chrome Debug] ⚠️ No relay to remove');
326
+ return { success: false, message: 'No relay found' };
327
+ }
328
+ }
329
+
330
+ /**
331
+ * High-level API for starting console interception
332
+ *
333
+ * @param {number} tabId - Chrome tab ID
334
+ * @param {object} config - Configuration object
335
+ * @param {string} config.overrideFlagName - Window property for override tracking
336
+ * @param {string} config.originalConsoleName - Window property for original methods
337
+ * @param {string} config.relayFlagName - Window property for relay handler
338
+ * @param {string} config.messageType - PostMessage type for log relay
339
+ * @param {string} config.backgroundAction - Background script action name
340
+ * @returns {Promise<boolean>} True if successful, false otherwise
341
+ */
342
+ async function startConsoleInterception(tabId, config) {
343
+ try {
344
+ // Check if this tab allows content script injection
345
+ const tab = await chrome.tabs.get(tabId);
346
+ if (!tab.url || tab.url.startsWith('chrome://') || tab.url.startsWith('chrome-extension://') || tab.url.startsWith('moz-extension://')) {
347
+ console.log('Cannot inject console logger into restricted URL:', tab.url);
348
+ console.warn('WARNING: Console logs cannot be captured on restricted pages');
349
+ return false;
350
+ }
351
+
352
+ // Inject MAIN world console interceptor
353
+ const results = await chrome.scripting.executeScript({
354
+ target: { tabId: tabId },
355
+ world: 'MAIN',
356
+ func: injectConsoleInterceptor,
357
+ args: [config.overrideFlagName, config.originalConsoleName, config.messageType]
358
+ });
359
+
360
+ console.log('[Console Injection] MAIN world script injected:', results);
361
+
362
+ // Inject content script relay
363
+ await chrome.scripting.executeScript({
364
+ target: { tabId: tabId },
365
+ func: injectConsoleRelay,
366
+ args: [config.relayFlagName, config.messageType, config.backgroundAction]
367
+ });
368
+
369
+ console.log('[Console Injection] Content script relay injected successfully');
370
+ return true;
371
+ } catch (error) {
372
+ console.error('[Console Injection] Failed:', error);
373
+ return false;
374
+ }
375
+ }
376
+
377
+ /**
378
+ * High-level API for stopping console interception and cleanup
379
+ *
380
+ * @param {number} tabId - Chrome tab ID
381
+ * @param {object} config - Configuration object (same as startConsoleInterception)
382
+ * @returns {Promise<boolean>} True if successful, false otherwise
383
+ */
384
+ async function stopConsoleInterception(tabId, config) {
385
+ try {
386
+ console.log('[Console Cleanup] Starting cleanup for tab:', tabId);
387
+
388
+ // Check if this tab allows content script injection
389
+ const tab = await chrome.tabs.get(tabId);
390
+ if (!tab.url || tab.url.startsWith('chrome://') || tab.url.startsWith('chrome-extension://')) {
391
+ console.warn('[Console Cleanup] Cannot inject into restricted URL:', tab.url);
392
+ return false;
393
+ }
394
+
395
+ // Restore console methods in MAIN world
396
+ const mainWorldResult = await chrome.scripting.executeScript({
397
+ target: { tabId: tabId },
398
+ world: 'MAIN',
399
+ func: cleanupConsoleInterceptor,
400
+ args: [config.overrideFlagName, config.originalConsoleName]
401
+ });
402
+ console.log('[Console Cleanup] MAIN world result:', mainWorldResult[0]?.result);
403
+
404
+ // Remove relay in content script
405
+ const contentScriptResult = await chrome.scripting.executeScript({
406
+ target: { tabId: tabId },
407
+ func: cleanupConsoleRelay,
408
+ args: [config.relayFlagName]
409
+ });
410
+ console.log('[Console Cleanup] Content script result:', contentScriptResult[0]?.result);
411
+
412
+ console.log('[Console Cleanup] Cleanup complete');
413
+ return true;
414
+ } catch (error) {
415
+ console.error('[Console Cleanup] Failed:', error);
416
+ return false;
417
+ }
418
+ }
419
+
420
+ // Export for use in Chrome extension background script (service worker context)
421
+ // Direct assignment without wrapper to avoid importScripts NetworkError
422
+ self.ConsoleInterceptionLibrary = {
423
+ injectConsoleInterceptor,
424
+ injectConsoleRelay,
425
+ cleanupConsoleInterceptor,
426
+ cleanupConsoleRelay,
427
+ startConsoleInterception,
428
+ stopConsoleInterception
429
+ };
430
+
@@ -1,15 +1,15 @@
1
- .chrome-pilot-hover {
1
+ .chrome-debug-hover {
2
2
  outline: 2px solid #4A90E2 !important;
3
3
  outline-offset: 2px !important;
4
4
  cursor: pointer !important;
5
5
  }
6
6
 
7
- .chrome-pilot-selected {
7
+ .chrome-debug-selected {
8
8
  outline: 3px solid #E91E63 !important;
9
9
  outline-offset: 2px !important;
10
10
  }
11
11
 
12
- .chrome-pilot-overlay {
12
+ .chrome-debug-overlay {
13
13
  position: fixed;
14
14
  bottom: 20px;
15
15
  right: 20px;
@@ -24,13 +24,13 @@
24
24
  color: #e0e0e0;
25
25
  }
26
26
 
27
- .chrome-pilot-overlay h3 {
27
+ .chrome-debug-overlay h3 {
28
28
  margin: 0 0 10px 0;
29
29
  font-size: 16px;
30
30
  color: #ffffff;
31
31
  }
32
32
 
33
- .chrome-pilot-overlay .element-info {
33
+ .chrome-debug-overlay .element-info {
34
34
  background: #2a2a2a;
35
35
  padding: 10px;
36
36
  border-radius: 4px;
@@ -41,7 +41,7 @@
41
41
  border: 1px solid #3a3a3a;
42
42
  }
43
43
 
44
- .chrome-pilot-overlay .react-info {
44
+ .chrome-debug-overlay .react-info {
45
45
  background: #1a2332;
46
46
  padding: 10px;
47
47
  border-radius: 4px;
@@ -51,13 +51,13 @@
51
51
  color: #e0e0e0;
52
52
  }
53
53
 
54
- .chrome-pilot-overlay .react-info h4 {
54
+ .chrome-debug-overlay .react-info h4 {
55
55
  margin: 0 0 8px 0;
56
56
  font-size: 14px;
57
57
  font-weight: 600;
58
58
  }
59
59
 
60
- .chrome-pilot-overlay textarea {
60
+ .chrome-debug-overlay textarea {
61
61
  width: 100%;
62
62
  padding: 10px;
63
63
  border: 1px solid #ddd;
@@ -68,13 +68,13 @@
68
68
  font-size: 14px;
69
69
  }
70
70
 
71
- .chrome-pilot-overlay .button-group {
71
+ .chrome-debug-overlay .button-group {
72
72
  display: flex;
73
73
  gap: 10px;
74
74
  margin-top: 15px;
75
75
  }
76
76
 
77
- .chrome-pilot-overlay button {
77
+ .chrome-debug-overlay button {
78
78
  flex: 1;
79
79
  padding: 10px 20px;
80
80
  border: none;
@@ -85,31 +85,31 @@
85
85
  transition: background-color 0.2s;
86
86
  }
87
87
 
88
- .chrome-pilot-overlay .submit-btn {
88
+ .chrome-debug-overlay .submit-btn {
89
89
  background: #4A90E2;
90
90
  color: white;
91
91
  }
92
92
 
93
- .chrome-pilot-overlay .submit-btn:hover {
93
+ .chrome-debug-overlay .submit-btn:hover {
94
94
  background: #357ABD;
95
95
  }
96
96
 
97
- .chrome-pilot-overlay .cancel-btn {
97
+ .chrome-debug-overlay .cancel-btn {
98
98
  background: #f0f0f0;
99
99
  color: #333;
100
100
  }
101
101
 
102
- .chrome-pilot-overlay .cancel-btn:hover {
102
+ .chrome-debug-overlay .cancel-btn:hover {
103
103
  background: #e0e0e0;
104
104
  }
105
105
 
106
- .chrome-pilot-overlay .loading {
106
+ .chrome-debug-overlay .loading {
107
107
  text-align: center;
108
108
  color: #a0a0a0;
109
109
  padding: 20px;
110
110
  }
111
111
 
112
- .chrome-pilot-status {
112
+ .chrome-debug-status {
113
113
  position: fixed;
114
114
  top: 20px;
115
115
  right: 20px;