@dynamicu/chromedebug-mcp 2.2.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 (95) hide show
  1. package/CLAUDE.md +344 -0
  2. package/LICENSE +21 -0
  3. package/README.md +250 -0
  4. package/chrome-extension/README.md +41 -0
  5. package/chrome-extension/background.js +3917 -0
  6. package/chrome-extension/chrome-session-manager.js +706 -0
  7. package/chrome-extension/content.css +181 -0
  8. package/chrome-extension/content.js +3022 -0
  9. package/chrome-extension/data-buffer.js +435 -0
  10. package/chrome-extension/dom-tracker.js +411 -0
  11. package/chrome-extension/extension-config.js +78 -0
  12. package/chrome-extension/firebase-client.js +278 -0
  13. package/chrome-extension/firebase-config.js +32 -0
  14. package/chrome-extension/firebase-config.module.js +22 -0
  15. package/chrome-extension/firebase-config.module.template.js +27 -0
  16. package/chrome-extension/firebase-config.template.js +36 -0
  17. package/chrome-extension/frame-capture.js +407 -0
  18. package/chrome-extension/icon128.png +1 -0
  19. package/chrome-extension/icon16.png +1 -0
  20. package/chrome-extension/icon48.png +1 -0
  21. package/chrome-extension/license-helper.js +181 -0
  22. package/chrome-extension/logger.js +23 -0
  23. package/chrome-extension/manifest.json +73 -0
  24. package/chrome-extension/network-tracker.js +510 -0
  25. package/chrome-extension/offscreen.html +10 -0
  26. package/chrome-extension/options.html +203 -0
  27. package/chrome-extension/options.js +282 -0
  28. package/chrome-extension/pako.min.js +2 -0
  29. package/chrome-extension/performance-monitor.js +533 -0
  30. package/chrome-extension/pii-redactor.js +405 -0
  31. package/chrome-extension/popup.html +532 -0
  32. package/chrome-extension/popup.js +2446 -0
  33. package/chrome-extension/upload-manager.js +323 -0
  34. package/chrome-extension/web-vitals.iife.js +1 -0
  35. package/config/api-keys.json +11 -0
  36. package/config/chrome-pilot-config.json +45 -0
  37. package/package.json +126 -0
  38. package/scripts/cleanup-processes.js +109 -0
  39. package/scripts/config-manager.js +280 -0
  40. package/scripts/generate-extension-config.js +53 -0
  41. package/scripts/setup-security.js +64 -0
  42. package/src/capture/architecture.js +426 -0
  43. package/src/capture/error-handling-tests.md +38 -0
  44. package/src/capture/error-handling-types.ts +360 -0
  45. package/src/capture/index.js +508 -0
  46. package/src/capture/interfaces.js +625 -0
  47. package/src/capture/memory-manager.js +713 -0
  48. package/src/capture/types.js +342 -0
  49. package/src/chrome-controller.js +2658 -0
  50. package/src/cli.js +19 -0
  51. package/src/config-loader.js +303 -0
  52. package/src/database.js +2178 -0
  53. package/src/firebase-license-manager.js +462 -0
  54. package/src/firebase-privacy-guard.js +397 -0
  55. package/src/http-server.js +1516 -0
  56. package/src/index-direct.js +157 -0
  57. package/src/index-modular.js +219 -0
  58. package/src/index-monolithic-backup.js +2230 -0
  59. package/src/index.js +305 -0
  60. package/src/legacy/chrome-controller-old.js +1406 -0
  61. package/src/legacy/index-express.js +625 -0
  62. package/src/legacy/index-old.js +977 -0
  63. package/src/legacy/routes.js +260 -0
  64. package/src/legacy/shared-storage.js +101 -0
  65. package/src/logger.js +10 -0
  66. package/src/mcp/handlers/chrome-tool-handler.js +306 -0
  67. package/src/mcp/handlers/element-tool-handler.js +51 -0
  68. package/src/mcp/handlers/frame-tool-handler.js +957 -0
  69. package/src/mcp/handlers/request-handler.js +104 -0
  70. package/src/mcp/handlers/workflow-tool-handler.js +636 -0
  71. package/src/mcp/server.js +68 -0
  72. package/src/mcp/tools/index.js +701 -0
  73. package/src/middleware/auth.js +371 -0
  74. package/src/middleware/security.js +267 -0
  75. package/src/port-discovery.js +258 -0
  76. package/src/routes/admin.js +182 -0
  77. package/src/services/browser-daemon.js +494 -0
  78. package/src/services/chrome-service.js +375 -0
  79. package/src/services/failover-manager.js +412 -0
  80. package/src/services/git-safety-service.js +675 -0
  81. package/src/services/heartbeat-manager.js +200 -0
  82. package/src/services/http-client.js +195 -0
  83. package/src/services/process-manager.js +318 -0
  84. package/src/services/process-tracker.js +574 -0
  85. package/src/services/profile-manager.js +449 -0
  86. package/src/services/project-manager.js +415 -0
  87. package/src/services/session-manager.js +497 -0
  88. package/src/services/session-registry.js +491 -0
  89. package/src/services/unified-session-manager.js +678 -0
  90. package/src/shared-storage-old.js +267 -0
  91. package/src/standalone-server.js +53 -0
  92. package/src/utils/extension-path.js +145 -0
  93. package/src/utils.js +187 -0
  94. package/src/validation/log-transformer.js +125 -0
  95. package/src/validation/schemas.js +391 -0
@@ -0,0 +1,2446 @@
1
+ // Chrome Debug Extension Popup Script v2.0
2
+ const EXTENSION_VERSION = '2.0.4-BUILD-20250119';
3
+ console.log(`[popup.js] Loaded version: ${EXTENSION_VERSION}`);
4
+
5
+ // Import Firebase license client
6
+ import { FirebaseLicenseClient } from './firebase-client.js';
7
+ import { LEMONSQUEEZY_CHECKOUT_URL } from './firebase-config.module.js';
8
+
9
+ // Initialize license client
10
+ const licenseClient = new FirebaseLicenseClient();
11
+ let currentUserId = null;
12
+
13
+ // Global variables for recording functionality
14
+ let isRecording = false;
15
+ let recordingTimer = null;
16
+ let recordingStartTime = null;
17
+ let isStoppingRecording = false;
18
+
19
+ // Countdown timer variables
20
+ let countdownTimer = null;
21
+ let countdownInterval = null;
22
+ let nextSnapshotTime = null;
23
+ let currentFrameRate = 1;
24
+
25
+ // Workflow recording variables
26
+ let isWorkflowRecording = false;
27
+ let workflowRecordingTimer = null;
28
+ let workflowStartTime = null;
29
+
30
+ // Debounce timer for screenshot quality slider
31
+ let screenshotQualityDebounceTimer = null;
32
+
33
+ // Workflow recording functions
34
+ function updateWorkflowRecordingUI() {
35
+ const workflowBtn = document.getElementById('workflowRecordBtn');
36
+ const workflowStatus = document.getElementById('workflowRecordingStatus');
37
+ const saveRestorePointBtn = document.getElementById('saveRestorePointBtn');
38
+
39
+ console.log('[Popup v2.0.6] updateWorkflowRecordingUI called, isWorkflowRecording:', isWorkflowRecording);
40
+ console.log('[Popup v2.0.6] workflowBtn element exists?', !!workflowBtn);
41
+
42
+ if (workflowBtn) {
43
+ if (isWorkflowRecording) {
44
+ console.log('[Popup v2.0.6] Updating button to STOP state');
45
+ console.log('[Popup v2.0.6] Current button text:', workflowBtn.textContent);
46
+ workflowBtn.textContent = 'Stop Workflow Recording';
47
+ workflowBtn.style.background = '#e91e63';
48
+ workflowBtn.classList.add('recording');
49
+ console.log('[Popup v2.0.6] Button updated - new text:', workflowBtn.textContent);
50
+ console.log('[Popup v2.0.6] Button classes:', workflowBtn.className);
51
+ startWorkflowTimer();
52
+ // Show restore point button during recording
53
+ if (saveRestorePointBtn) {
54
+ saveRestorePointBtn.style.display = 'block';
55
+ }
56
+ } else {
57
+ workflowBtn.textContent = 'Start Workflow Recording';
58
+ workflowBtn.style.background = '#9c27b0';
59
+ workflowBtn.classList.remove('recording');
60
+ stopWorkflowTimer();
61
+ if (workflowStatus) {
62
+ workflowStatus.textContent = '';
63
+ }
64
+ // Hide restore point button when not recording
65
+ if (saveRestorePointBtn) {
66
+ saveRestorePointBtn.style.display = 'none';
67
+ }
68
+ }
69
+ }
70
+ }
71
+
72
+ function startWorkflowTimer() {
73
+ // Get stored start time or use current time
74
+ chrome.storage.local.get(['workflowStartTime'], (result) => {
75
+ workflowStartTime = result.workflowStartTime || Date.now();
76
+
77
+ workflowRecordingTimer = setInterval(() => {
78
+ const elapsed = Math.floor((Date.now() - workflowStartTime) / 1000);
79
+ const minutes = Math.floor(elapsed / 60);
80
+ const seconds = elapsed % 60;
81
+ const workflowStatus = document.getElementById('workflowRecordingStatus');
82
+ if (workflowStatus) {
83
+ workflowStatus.innerHTML = `Recording workflow: <span class="recording-timer">${minutes}:${seconds.toString().padStart(2, '0')}</span>`;
84
+ }
85
+ }, 1000);
86
+
87
+ // Update timer immediately
88
+ const elapsed = Math.floor((Date.now() - workflowStartTime) / 1000);
89
+ const minutes = Math.floor(elapsed / 60);
90
+ const seconds = elapsed % 60;
91
+ const workflowStatus = document.getElementById('workflowRecordingStatus');
92
+ if (workflowStatus) {
93
+ workflowStatus.innerHTML = `Recording workflow: <span class="recording-timer">${minutes}:${seconds.toString().padStart(2, '0')}</span>`;
94
+ }
95
+ });
96
+ }
97
+
98
+ function stopWorkflowTimer() {
99
+ if (workflowRecordingTimer) {
100
+ clearInterval(workflowRecordingTimer);
101
+ workflowRecordingTimer = null;
102
+ }
103
+ }
104
+
105
+ // Import configuration - this will be available globally
106
+ const CONFIG_PORTS = CHROMEDEBUG_CONFIG?.ports || [3001, 3000, 3002, 3028]; // Fallback to defaults
107
+
108
+ // Site management functions
109
+ async function initializeSiteManagement() {
110
+ const siteStatusIcon = document.getElementById('siteStatusIcon');
111
+ const siteStatusText = document.getElementById('siteStatusText');
112
+ const toggleSiteBtn = document.getElementById('toggleSiteBtn');
113
+ const addSiteBtn = document.getElementById('addSiteBtn');
114
+
115
+ try {
116
+ // Get current tab
117
+ const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
118
+ if (!tab || !tab.url) {
119
+ updateSiteStatus('❓', 'Cannot access current tab', true);
120
+ return;
121
+ }
122
+
123
+ const url = new URL(tab.url);
124
+ const hostname = url.hostname;
125
+
126
+ // Get site restriction settings
127
+ const settings = await chrome.storage.sync.get(['chromePilotMode', 'chromePilotAllowedSites', 'chromePilotRestrictedSites']);
128
+ const mode = settings.chromePilotMode || 'whitelist';
129
+ const allowedSites = settings.chromePilotAllowedSites || ['localhost:*', '127.0.0.1:*', '*.local', '*.test', '*.dev'];
130
+ const restrictedSites = settings.chromePilotRestrictedSites || ['youtube.com', '*.youtube.com', 'google.com', '*.google.com'];
131
+
132
+ // Test if site is allowed
133
+ const isAllowed = testSiteAccess(hostname, mode, allowedSites, restrictedSites);
134
+
135
+ if (isAllowed) {
136
+ updateSiteStatus('✅', `Active on ${hostname}`, false);
137
+ toggleSiteBtn.textContent = mode === 'whitelist' ? 'Remove from Whitelist' : 'Add to Blacklist';
138
+ toggleSiteBtn.className = 'site-toggle-btn block';
139
+ addSiteBtn.textContent = 'Manage Sites';
140
+ } else {
141
+ updateSiteStatus('❌', `Blocked on ${hostname}`, false);
142
+ toggleSiteBtn.textContent = mode === 'whitelist' ? 'Add to Whitelist' : 'Remove from Blacklist';
143
+ toggleSiteBtn.className = 'site-toggle-btn allow';
144
+ addSiteBtn.textContent = 'Manage Sites';
145
+ }
146
+
147
+ // Add event listeners
148
+ toggleSiteBtn.addEventListener('click', () => toggleCurrentSite(hostname, mode, allowedSites, restrictedSites, isAllowed));
149
+ addSiteBtn.addEventListener('click', () => addCurrentSite(hostname, mode));
150
+
151
+ } catch (error) {
152
+ console.error('Site management initialization error:', error);
153
+ updateSiteStatus('⚠️', 'Error checking site status', true);
154
+ }
155
+ }
156
+
157
+ function updateSiteStatus(icon, text, disabled) {
158
+ const siteStatusIcon = document.getElementById('siteStatusIcon');
159
+ const siteStatusText = document.getElementById('siteStatusText');
160
+ const toggleSiteBtn = document.getElementById('toggleSiteBtn');
161
+ const addSiteBtn = document.getElementById('addSiteBtn');
162
+
163
+ if (siteStatusIcon) siteStatusIcon.textContent = icon;
164
+ if (siteStatusText) siteStatusText.textContent = text;
165
+ if (toggleSiteBtn) toggleSiteBtn.disabled = disabled;
166
+ if (addSiteBtn) addSiteBtn.disabled = disabled;
167
+ }
168
+
169
+ async function toggleCurrentSite(hostname, mode, allowedSites, restrictedSites, currentlyAllowed) {
170
+ try {
171
+ let newAllowedSites = [...allowedSites];
172
+ let newRestrictedSites = [...restrictedSites];
173
+
174
+ if (mode === 'whitelist') {
175
+ if (currentlyAllowed) {
176
+ // Remove from whitelist
177
+ newAllowedSites = newAllowedSites.filter(site => site !== hostname && site !== `*.${hostname.split('.').slice(1).join('.')}`);
178
+ } else {
179
+ // Add to whitelist
180
+ if (!newAllowedSites.includes(hostname)) {
181
+ newAllowedSites.push(hostname);
182
+ }
183
+ }
184
+ } else {
185
+ if (currentlyAllowed) {
186
+ // Add to blacklist
187
+ if (!newRestrictedSites.includes(hostname)) {
188
+ newRestrictedSites.push(hostname);
189
+ }
190
+ } else {
191
+ // Remove from blacklist
192
+ newRestrictedSites = newRestrictedSites.filter(site => site !== hostname && site !== `*.${hostname.split('.').slice(1).join('.')}`);
193
+ }
194
+ }
195
+
196
+ await chrome.storage.sync.set({
197
+ chromePilotAllowedSites: newAllowedSites,
198
+ chromePilotRestrictedSites: newRestrictedSites
199
+ });
200
+
201
+ // Refresh site status
202
+ initializeSiteManagement();
203
+
204
+ updateSiteStatus('✅', 'Settings updated. Reload tab to apply changes.', false);
205
+
206
+ } catch (error) {
207
+ console.error('Error toggling site:', error);
208
+ updateSiteStatus('⚠️', 'Error updating settings', false);
209
+ }
210
+ }
211
+
212
+ async function addCurrentSite(hostname, mode) {
213
+ try {
214
+ // Open options page
215
+ chrome.tabs.create({ url: chrome.runtime.getURL('options.html') });
216
+ } catch (error) {
217
+ console.error('Error opening options:', error);
218
+ }
219
+ }
220
+
221
+ function testSiteAccess(hostname, mode, allowedSites, restrictedSites) {
222
+ hostname = hostname.toLowerCase();
223
+
224
+ if (mode === 'blacklist') {
225
+ // Block if hostname matches any restricted site pattern
226
+ return !restrictedSites.some(pattern => {
227
+ // Special handling for patterns with port wildcards like "localhost:*"
228
+ if (pattern.includes(':*')) {
229
+ const basePattern = pattern.replace(':*', '').toLowerCase();
230
+ if (hostname === basePattern) {
231
+ return true; // Match hostname regardless of port
232
+ }
233
+ }
234
+
235
+ if (pattern.includes('*')) {
236
+ const regex = new RegExp(pattern.replace(/\*/g, '.*').replace(/\./g, '\\.'));
237
+ return regex.test(hostname);
238
+ }
239
+ return hostname.includes(pattern.toLowerCase());
240
+ });
241
+ } else {
242
+ // Whitelist mode: only allow if hostname matches allowed sites
243
+ if (allowedSites.length === 0) return true; // Empty whitelist allows all
244
+
245
+ return allowedSites.some(pattern => {
246
+ // Special handling for patterns with port wildcards like "localhost:*"
247
+ if (pattern.includes(':*')) {
248
+ const basePattern = pattern.replace(':*', '').toLowerCase();
249
+ if (hostname === basePattern) {
250
+ return true; // Match hostname regardless of port
251
+ }
252
+ }
253
+
254
+ if (pattern.includes('*')) {
255
+ const regex = new RegExp(pattern.replace(/\*/g, '.*').replace(/\./g, '\\.'));
256
+ return regex.test(hostname);
257
+ }
258
+ return hostname.includes(pattern.toLowerCase());
259
+ });
260
+ }
261
+ }
262
+
263
+ // Function definitions (outside DOMContentLoaded for proper scope)
264
+ async function checkServerStatus() {
265
+ const statusEl = document.getElementById('serverStatus');
266
+ const statusTextEl = document.getElementById('statusText');
267
+
268
+ const ports = CONFIG_PORTS;
269
+ let connected = false;
270
+ let connectedPort = null;
271
+
272
+ for (const port of ports) {
273
+ try {
274
+ const response = await fetch(`http://localhost:${port}/chromedebug/status`, {
275
+ method: 'GET',
276
+ mode: 'cors'
277
+ });
278
+
279
+ if (response.ok) {
280
+ connected = true;
281
+ connectedPort = port;
282
+ break;
283
+ }
284
+ } catch (error) {
285
+ // Try next port
286
+ }
287
+ }
288
+
289
+ if (connected) {
290
+ statusEl.className = 'server-status connected';
291
+ statusTextEl.textContent = `Server connected (port ${connectedPort})`;
292
+ } else {
293
+ statusEl.className = 'server-status disconnected';
294
+ statusTextEl.textContent = 'Server not running';
295
+ }
296
+
297
+ // Return connection status and port for other functions to use
298
+ return { connected, connectedPort };
299
+ }
300
+
301
+
302
+ function updateRecordingUI() {
303
+ const recordBtn = document.getElementById('recordBtn');
304
+ const recordingStatus = document.getElementById('recordingStatus');
305
+ const manualSnapshotBtn = document.getElementById('manualSnapshotBtn');
306
+ const countdownDisplay = document.getElementById('countdownDisplay');
307
+
308
+ if (recordBtn) {
309
+ if (isRecording) {
310
+ recordBtn.textContent = 'Stop Recording';
311
+ recordBtn.classList.add('recording');
312
+ startRecordingTimer();
313
+
314
+ // Show manual snapshot button
315
+ if (manualSnapshotBtn) {
316
+ manualSnapshotBtn.style.display = 'block';
317
+ }
318
+
319
+ // Show countdown if frame rate is 3+ seconds
320
+ if (currentFrameRate >= 3 && countdownDisplay) {
321
+ countdownDisplay.style.display = 'block';
322
+ startCountdownTimer();
323
+ }
324
+ } else {
325
+ recordBtn.textContent = 'Start Recording';
326
+ recordBtn.classList.remove('recording');
327
+ stopRecordingTimer();
328
+ stopCountdownTimer();
329
+
330
+ // Hide manual snapshot button and countdown
331
+ if (manualSnapshotBtn) {
332
+ manualSnapshotBtn.style.display = 'none';
333
+ }
334
+ if (countdownDisplay) {
335
+ countdownDisplay.style.display = 'none';
336
+ }
337
+
338
+ if (recordingStatus) {
339
+ recordingStatus.textContent = '';
340
+ }
341
+ }
342
+ }
343
+ }
344
+
345
+ function startRecordingTimer() {
346
+ // Use stored recordingStartTime if available, otherwise use current time
347
+ if (!recordingStartTime) {
348
+ chrome.storage.local.get(['recordingStartTime'], (result) => {
349
+ recordingStartTime = result.recordingStartTime || Date.now();
350
+ });
351
+ }
352
+
353
+ recordingTimer = setInterval(() => {
354
+ const elapsed = Math.floor((Date.now() - recordingStartTime) / 1000);
355
+ const minutes = Math.floor(elapsed / 60);
356
+ const seconds = elapsed % 60;
357
+ const recordingStatus = document.getElementById('recordingStatus');
358
+ if (recordingStatus) {
359
+ recordingStatus.innerHTML = `Recording: <span class="recording-timer">${minutes}:${seconds.toString().padStart(2, '0')}</span>`;
360
+ }
361
+ }, 1000);
362
+ }
363
+
364
+ function stopRecordingTimer() {
365
+ if (recordingTimer) {
366
+ clearInterval(recordingTimer);
367
+ recordingTimer = null;
368
+ }
369
+ }
370
+
371
+ function startCountdownTimer() {
372
+ // Initialize next snapshot time if not already set
373
+ if (!nextSnapshotTime) {
374
+ nextSnapshotTime = Date.now() + (currentFrameRate * 1000);
375
+ }
376
+
377
+ countdownInterval = setInterval(() => {
378
+ const now = Date.now();
379
+ const timeRemaining = Math.max(0, nextSnapshotTime - now);
380
+ const secondsRemaining = Math.ceil(timeRemaining / 1000);
381
+
382
+ const countdownTimer = document.getElementById('countdownTimer');
383
+ if (countdownTimer) {
384
+ countdownTimer.textContent = secondsRemaining;
385
+
386
+ // Add animation class for the last 3 seconds
387
+ if (secondsRemaining <= 3 && secondsRemaining > 0) {
388
+ countdownTimer.style.color = '#ff5252';
389
+ countdownTimer.classList.add('countdown-timer');
390
+ } else {
391
+ countdownTimer.style.color = '#f44336';
392
+ countdownTimer.classList.remove('countdown-timer');
393
+ }
394
+ }
395
+
396
+ // Reset timer for next snapshot
397
+ if (timeRemaining <= 0) {
398
+ nextSnapshotTime = now + (currentFrameRate * 1000);
399
+ }
400
+ }, 100); // Update every 100ms for smooth countdown
401
+ }
402
+
403
+ function stopCountdownTimer() {
404
+ if (countdownInterval) {
405
+ clearInterval(countdownInterval);
406
+ countdownInterval = null;
407
+ }
408
+ nextSnapshotTime = null;
409
+ }
410
+
411
+ function takeManualSnapshot() {
412
+ // Send message to background script to trigger a manual snapshot
413
+ chrome.runtime.sendMessage({
414
+ action: 'takeManualSnapshot'
415
+ }, (response) => {
416
+ if (chrome.runtime.lastError) {
417
+ console.error('Error taking manual snapshot:', chrome.runtime.lastError);
418
+ return;
419
+ }
420
+
421
+ if (response && response.success) {
422
+ console.log('Manual snapshot taken successfully');
423
+ // Show brief feedback
424
+ const manualSnapshotBtn = document.getElementById('manualSnapshotBtn');
425
+ if (manualSnapshotBtn) {
426
+ const originalText = manualSnapshotBtn.textContent;
427
+ manualSnapshotBtn.textContent = 'Snapshot Taken!';
428
+ manualSnapshotBtn.style.background = '#45a049';
429
+ setTimeout(() => {
430
+ manualSnapshotBtn.textContent = originalText;
431
+ manualSnapshotBtn.style.background = '#4CAF50';
432
+ }, 1000);
433
+ }
434
+ } else {
435
+ console.error('Failed to take manual snapshot:', response?.error);
436
+ }
437
+ });
438
+ }
439
+
440
+ /*
441
+ * SNAPSHOT FEATURE DISABLED (2025-10-01)
442
+ *
443
+ * This function provided "quick snapshots" (screenshot + console logs) for debugging.
444
+ *
445
+ * WHY DISABLED:
446
+ * - Without console log history, snapshots are just screenshots (users can do natively)
447
+ * - Console log capture requires always-on monitoring (privacy concern)
448
+ * - Cannot retrieve historical logs without constant interception
449
+ * - Core value proposition cannot be delivered cleanly
450
+ *
451
+ * ARCHITECTURAL CHALLENGE:
452
+ * To capture console logs, we need one of:
453
+ * 1. Always-on monitoring (privacy/performance concern)
454
+ * 2. Live-only capture (no historical context, defeats debugging purpose)
455
+ * 3. Historical API (doesn't exist in Chrome)
456
+ *
457
+ * TO RE-ENABLE:
458
+ * 1. Design privacy-conscious always-on log monitoring with:
459
+ * - User consent and clear privacy controls
460
+ * - Configurable retention periods
461
+ * - Memory-efficient log buffering
462
+ * - Visual indicator when monitoring active
463
+ * 2. Implement persistent log storage layer
464
+ * 3. Add user settings for log capture
465
+ * 4. Privacy impact assessment
466
+ * 5. Uncomment this function and UI elements
467
+ * 6. Uncomment related functions: loadSnapshots(), event handlers
468
+ * 7. Re-enable backend endpoints and methods
469
+ *
470
+ * RECOMMENDED ALTERNATIVE:
471
+ * Use "Start Recording" which properly captures screenshots + logs + user actions
472
+ *
473
+ * See: SNAPSHOT_FEATURE_DISABLED.md for detailed explanation
474
+ */
475
+ /*
476
+ async function takeSnapshot() {
477
+ console.log('Taking standalone snapshot...');
478
+
479
+ try {
480
+ const serverStatus = await checkServerStatus();
481
+ if (!serverStatus.connected) {
482
+ console.error('Server not running');
483
+ const recordingStatus = document.getElementById('recordingStatus');
484
+ if (recordingStatus) {
485
+ recordingStatus.innerHTML = '<strong style="color: #f44336;">Error: Server not running</strong><br>' +
486
+ '<small>Please start the Chrome Debug server first</small>';
487
+ }
488
+ setTimeout(() => {
489
+ if (recordingStatus) recordingStatus.textContent = '';
490
+ }, 5000);
491
+ return;
492
+ }
493
+
494
+ const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
495
+
496
+ if (!tab || !tab.id) {
497
+ console.error('No active tab found');
498
+ const recordingStatus = document.getElementById('recordingStatus');
499
+ if (recordingStatus) {
500
+ recordingStatus.textContent = 'Error: No active tab';
501
+ }
502
+ return;
503
+ }
504
+
505
+ const noteInput = document.getElementById('snapshotNoteInput');
506
+ const note = noteInput ? noteInput.value.trim() : '';
507
+
508
+ const takeSnapshotBtn = document.getElementById('takeSnapshotBtn');
509
+ if (takeSnapshotBtn) {
510
+ takeSnapshotBtn.textContent = 'Taking Snapshot...';
511
+ takeSnapshotBtn.disabled = true;
512
+ }
513
+
514
+ chrome.runtime.sendMessage({
515
+ action: 'takeStandaloneSnapshot',
516
+ tabId: tab.id,
517
+ note: note
518
+ }, (response) => {
519
+ if (takeSnapshotBtn) {
520
+ takeSnapshotBtn.disabled = false;
521
+ takeSnapshotBtn.textContent = '📸 Take Snapshot';
522
+ }
523
+
524
+ if (chrome.runtime.lastError) {
525
+ console.error('Error taking snapshot:', chrome.runtime.lastError);
526
+ const recordingStatus = document.getElementById('recordingStatus');
527
+ if (recordingStatus) {
528
+ recordingStatus.textContent = 'Error: ' + chrome.runtime.lastError.message;
529
+ }
530
+ return;
531
+ }
532
+
533
+ if (response && response.success) {
534
+ console.log('Snapshot taken successfully:', response.sessionId);
535
+ const recordingStatus = document.getElementById('recordingStatus');
536
+ if (recordingStatus) {
537
+ recordingStatus.innerHTML = `<strong style="color: #4CAF50;">Snapshot saved!</strong>`;
538
+ }
539
+
540
+ if (noteInput) {
541
+ noteInput.value = '';
542
+ }
543
+
544
+ if (response.sessionId) {
545
+ addRecording(response.sessionId, false, true, serverStatus.connectedPort, 'snapshot');
546
+ }
547
+
548
+ setTimeout(() => {
549
+ if (recordingStatus) recordingStatus.textContent = '';
550
+ }, 3000);
551
+
552
+ if (takeSnapshotBtn) {
553
+ const originalText = takeSnapshotBtn.textContent;
554
+ const originalColor = takeSnapshotBtn.style.background;
555
+ takeSnapshotBtn.textContent = '✅ Snapshot Saved!';
556
+ takeSnapshotBtn.style.background = '#4CAF50';
557
+ setTimeout(() => {
558
+ takeSnapshotBtn.textContent = originalText;
559
+ takeSnapshotBtn.style.background = originalColor;
560
+ }, 2000);
561
+ }
562
+
563
+ const snapshotsList = document.getElementById('snapshotsList');
564
+ if (snapshotsList && snapshotsList.style.display !== 'none') {
565
+ loadSnapshots();
566
+ }
567
+ } else {
568
+ const recordingStatus = document.getElementById('recordingStatus');
569
+ if (recordingStatus) {
570
+ recordingStatus.textContent = 'Error: ' + (response?.error || 'Failed to take snapshot');
571
+ }
572
+ }
573
+ });
574
+ } catch (error) {
575
+ console.error('Error in takeSnapshot:', error);
576
+ const recordingStatus = document.getElementById('recordingStatus');
577
+ if (recordingStatus) {
578
+ recordingStatus.textContent = 'Error: ' + error.message;
579
+ }
580
+
581
+ const takeSnapshotBtn = document.getElementById('takeSnapshotBtn');
582
+ if (takeSnapshotBtn) {
583
+ takeSnapshotBtn.disabled = false;
584
+ takeSnapshotBtn.textContent = '📸 Take Snapshot';
585
+ }
586
+ }
587
+ }
588
+ */
589
+
590
+ function loadRecordings() {
591
+ chrome.storage.local.get(['recordings'], (result) => {
592
+ const recordings = result.recordings || [];
593
+ updateRecordingsDisplay(recordings);
594
+ });
595
+ }
596
+
597
+ function addRecording(recordingId, isSession = false, isFrameCapture = false, serverPort = null, type = null, sessionName = null) {
598
+ chrome.storage.local.get(['recordings'], (result) => {
599
+ const recordings = result.recordings || [];
600
+ recordings.unshift({
601
+ id: recordingId,
602
+ timestamp: Date.now(),
603
+ isSession: isSession,
604
+ isFrameCapture: isFrameCapture,
605
+ serverPort: serverPort,
606
+ type: type || (isSession ? 'session' : isFrameCapture ? 'recording' : 'recording'),
607
+ name: sessionName
608
+ }); // Add to beginning
609
+ chrome.storage.local.set({ recordings }, () => {
610
+ updateRecordingsDisplay(recordings);
611
+ });
612
+ });
613
+ }
614
+
615
+ function deleteRecording(recordingId) {
616
+ // First, tell the background script to delete from server
617
+ chrome.runtime.sendMessage({
618
+ action: 'deleteRecording',
619
+ recordingId: recordingId
620
+ }, (response) => {
621
+ // Then remove from local storage
622
+ chrome.storage.local.get(['recordings'], (result) => {
623
+ const recordings = result.recordings || [];
624
+ const filtered = recordings.filter(r => r.id !== recordingId);
625
+ chrome.storage.local.set({ recordings: filtered }, () => {
626
+ updateRecordingsDisplay(filtered);
627
+ });
628
+ });
629
+ });
630
+ }
631
+
632
+ function updateRecordingsDisplay(recordings) {
633
+ const container = document.getElementById('recordingsContainer');
634
+ const listDiv = document.getElementById('recordingsList');
635
+
636
+ if (!container || !listDiv) return;
637
+
638
+ if (recordings.length === 0) {
639
+ listDiv.style.display = 'none';
640
+ return;
641
+ }
642
+
643
+ listDiv.style.display = 'block';
644
+ container.innerHTML = recordings.map(recording => {
645
+ const portInfo = recording.serverPort ? ` - Port: ${recording.serverPort}` : '';
646
+ let typeInfo = '';
647
+ let typeIcon = '';
648
+
649
+ if (recording.type === 'snapshot') {
650
+ typeInfo = ' (snapshot)';
651
+ typeIcon = '📸 ';
652
+ } else if (recording.isFrameCapture) {
653
+ typeInfo = ' (frames)';
654
+ typeIcon = '🎥 ';
655
+ } else if (recording.isSession) {
656
+ typeInfo = ' (chunked)';
657
+ typeIcon = '📊 ';
658
+ }
659
+
660
+ const displayName = recording.name || recording.id;
661
+
662
+ return `
663
+ <div class="recording-item">
664
+ <div class="recording-info">
665
+ <div class="recording-id" title="${recording.id}">
666
+ <span class="recording-id-text">${typeIcon}${displayName}</span>
667
+ ${typeInfo}${portInfo}
668
+ </div>
669
+ </div>
670
+ <div class="recording-buttons">
671
+ <button class="copy-id-btn" data-id="${recording.id}" title="Copy ID">Copy ID</button>
672
+ <button class="copy-prompt-btn" data-id="${recording.id}" data-port="${recording.serverPort || ''}" data-type="${recording.type || ''}" title="Copy Claude Prompt">Copy Prompt</button>
673
+ ${recording.isFrameCapture || recording.type === 'snapshot' ? `<button class="view-btn" data-id="${recording.id}" data-type="${recording.type || ''}" title="View Screenshots">View</button>` : ''}
674
+ <button class="delete-btn" data-id="${recording.id}" title="Delete">Delete</button>
675
+ </div>
676
+ </div>
677
+ `;
678
+ }).join('');
679
+
680
+ // Add click handlers for copy ID buttons
681
+ container.querySelectorAll('.copy-id-btn').forEach(btn => {
682
+ btn.addEventListener('click', (e) => {
683
+ const recordingId = e.target.getAttribute('data-id');
684
+ navigator.clipboard.writeText(recordingId).then(() => {
685
+ // Visual feedback
686
+ const originalText = e.target.textContent;
687
+ e.target.textContent = 'Copied!';
688
+ e.target.style.background = '#4CAF50';
689
+ e.target.style.color = 'white';
690
+ setTimeout(() => {
691
+ e.target.textContent = originalText;
692
+ e.target.style.background = '';
693
+ e.target.style.color = '';
694
+ }, 1000);
695
+ }).catch(err => {
696
+ console.error('Failed to copy:', err);
697
+ alert('Failed to copy recording ID');
698
+ });
699
+ });
700
+ });
701
+
702
+ // Add click handlers for copy prompt buttons
703
+ container.querySelectorAll('.copy-prompt-btn').forEach(btn => {
704
+ btn.addEventListener('click', (e) => {
705
+ const recordingId = e.target.getAttribute('data-id');
706
+ const port = e.target.getAttribute('data-port');
707
+ const type = e.target.getAttribute('data-type');
708
+ const portText = port ? ` that was recorded on port ${port}` : '';
709
+
710
+ let prompt;
711
+ if (type === 'snapshot') {
712
+ prompt = `Please use the get_frame_screenshot function in Chrome Debug to view the snapshot "${recordingId}"${portText}. This is a single-frame screenshot with console logs captured for debugging.`;
713
+ } else {
714
+ prompt = `Please use the chrome_pilot_show_frames function in Chrome Debug to load the recording "${recordingId}"${portText}.`;
715
+ }
716
+
717
+ navigator.clipboard.writeText(prompt).then(() => {
718
+ // Visual feedback
719
+ const originalText = e.target.textContent;
720
+ e.target.textContent = 'Copied!';
721
+ e.target.style.background = '#4CAF50';
722
+ e.target.style.color = 'white';
723
+ setTimeout(() => {
724
+ e.target.textContent = originalText;
725
+ e.target.style.background = '';
726
+ e.target.style.color = '';
727
+ }, 1000);
728
+ }).catch(err => {
729
+ console.error('Failed to copy:', err);
730
+ alert('Failed to copy prompt');
731
+ });
732
+ });
733
+ });
734
+
735
+ // Add click handlers for delete buttons
736
+ container.querySelectorAll('.delete-btn').forEach(btn => {
737
+ btn.addEventListener('click', (e) => {
738
+ const recordingId = e.target.getAttribute('data-id');
739
+ if (confirm('Delete this recording?')) {
740
+ deleteRecording(recordingId);
741
+ }
742
+ });
743
+ });
744
+
745
+ // Add click handlers for view buttons
746
+ container.querySelectorAll('.view-btn').forEach(btn => {
747
+ btn.addEventListener('click', async (e) => {
748
+ const recordingId = e.target.getAttribute('data-id');
749
+ const type = e.target.getAttribute('data-type');
750
+
751
+ // Check if pro version (frame editor) is available
752
+ try {
753
+ const frameEditorUrl = type === 'snapshot'
754
+ ? chrome.runtime.getURL(`pro/frame-editor.html?sessionId=${recordingId}&type=snapshot`)
755
+ : chrome.runtime.getURL(`pro/frame-editor.html?sessionId=${recordingId}`);
756
+
757
+ // Try to fetch the frame editor to see if it exists
758
+ const response = await fetch(frameEditorUrl, { method: 'HEAD' });
759
+
760
+ if (response.ok) {
761
+ // Pro version available - open frame editor
762
+ chrome.tabs.create({ url: frameEditorUrl });
763
+ } else {
764
+ throw new Error('Frame editor not available');
765
+ }
766
+ } catch (error) {
767
+ // Free version - show upgrade prompt
768
+ console.log('[ChromePilot Free] Frame editor not available - showing upgrade prompt');
769
+ alert('Frame Editor is a Pro feature. Upgrade to ChromePilot Pro to view detailed frame recordings with console logs and advanced debugging tools.');
770
+ }
771
+ });
772
+ });
773
+ }
774
+
775
+
776
+
777
+
778
+ // Initialize when DOM is ready
779
+ // License UI initialization function
780
+ async function initializeLicenseUI() {
781
+ console.log('[License] Initializing license UI');
782
+
783
+ // Get or create userId
784
+ const stored = await chrome.storage.local.get('chromedebug_user_id');
785
+ currentUserId = stored.chromedebug_user_id || crypto.randomUUID();
786
+ if (!stored.chromedebug_user_id) {
787
+ await chrome.storage.local.set({chromedebug_user_id: currentUserId});
788
+ console.log('[License] Created new user ID:', currentUserId);
789
+ }
790
+
791
+ // Check license status
792
+ const licenseStatus = await licenseClient.getCachedLicenseStatus();
793
+ console.log('[License] License status:', licenseStatus);
794
+
795
+ // Update workflow recording PRO badge visibility
796
+ const workflowProBadge = document.getElementById('workflowProBadge');
797
+
798
+ if (licenseStatus.valid && licenseStatus.tier === 'pro') {
799
+ // Show pro status
800
+ document.getElementById('pro-tier-status').style.display = 'block';
801
+ document.getElementById('free-tier-status').style.display = 'none';
802
+ // Hide license activation input/button when Pro license is active
803
+ document.getElementById('license-activation').style.display = 'none';
804
+ // Hide PRO badge for Pro users
805
+ if (workflowProBadge) workflowProBadge.style.display = 'none';
806
+ console.log('[License] Displaying Pro tier status');
807
+ } else {
808
+ // Show free tier + usage
809
+ const usage = await licenseClient.checkUsageLimit(currentUserId);
810
+ console.log('[License] Usage check result:', usage);
811
+
812
+ // Handle both API response format (currentUsage, dailyLimit) and offline format (count, limit)
813
+ document.getElementById('usage-count').textContent = usage.currentUsage ?? usage.count ?? 0;
814
+ document.getElementById('usage-limit').textContent = usage.dailyLimit ?? usage.limit ?? 50;
815
+ document.getElementById('free-tier-status').style.display = 'block';
816
+ document.getElementById('pro-tier-status').style.display = 'none';
817
+ // Show license activation input/button for free tier
818
+ document.getElementById('license-activation').style.display = 'block';
819
+ // Show PRO badge for free users
820
+ if (workflowProBadge) workflowProBadge.style.display = 'block';
821
+ console.log('[License] Displaying Free tier status');
822
+ }
823
+ }
824
+
825
+ // License activation handler
826
+ async function handleLicenseActivation() {
827
+ const licenseKey = document.getElementById('license-key-input').value.trim();
828
+ const messageDiv = document.getElementById('license-message');
829
+
830
+ if (!licenseKey) {
831
+ messageDiv.textContent = 'Please enter a license key';
832
+ messageDiv.style.color = '#f44336';
833
+ return;
834
+ }
835
+
836
+ messageDiv.textContent = 'Validating...';
837
+ messageDiv.style.color = '#2196F3';
838
+ console.log('[License] Validating license key...');
839
+
840
+ const result = await licenseClient.validateLicense(licenseKey);
841
+ console.log('[License] Validation result:', result);
842
+
843
+ if (result.valid) {
844
+ messageDiv.textContent = 'License activated successfully!';
845
+ messageDiv.style.color = '#4caf50';
846
+ document.getElementById('license-key-input').value = '';
847
+ await initializeLicenseUI(); // Refresh UI
848
+ } else {
849
+ messageDiv.textContent = result.error || 'Invalid license key';
850
+ messageDiv.style.color = '#f44336';
851
+ }
852
+ }
853
+
854
+ // Upgrade button handler
855
+ function handleUpgradeClick() {
856
+ console.log('[License] Opening upgrade page:', LEMONSQUEEZY_CHECKOUT_URL);
857
+ chrome.tabs.create({url: LEMONSQUEEZY_CHECKOUT_URL});
858
+ }
859
+
860
+ document.addEventListener('DOMContentLoaded', () => {
861
+ console.log('DOM loaded, initializing popup');
862
+
863
+ // Initialize license UI first
864
+ initializeLicenseUI().catch(error => {
865
+ console.error('[License] Failed to initialize license UI:', error);
866
+ });
867
+
868
+ // License event handlers
869
+ const activateLicenseBtn = document.getElementById('activate-license-btn');
870
+ const upgradeBtn = document.getElementById('upgrade-btn');
871
+
872
+ if (activateLicenseBtn) {
873
+ activateLicenseBtn.addEventListener('click', handleLicenseActivation);
874
+ }
875
+
876
+ if (upgradeBtn) {
877
+ upgradeBtn.addEventListener('click', handleUpgradeClick);
878
+ }
879
+
880
+ // Function to update button states based on server status
881
+ async function updateButtonStatesForServerStatus() {
882
+ const serverStatus = await checkServerStatus();
883
+ const recordBtn = document.getElementById('recordBtn');
884
+ const workflowRecordBtn = document.getElementById('workflowRecordBtn');
885
+
886
+ if (!serverStatus.connected) {
887
+ // Disable recording buttons when server is not running
888
+ if (recordBtn && !isRecording) {
889
+ recordBtn.style.opacity = '0.6';
890
+ recordBtn.title = 'Server not running - please start Chrome Debug server';
891
+ }
892
+ if (workflowRecordBtn && !isWorkflowRecording) {
893
+ workflowRecordBtn.style.opacity = '0.6';
894
+ workflowRecordBtn.title = 'Server not running - please start Chrome Debug server';
895
+ }
896
+ } else {
897
+ // Enable recording buttons when server is running
898
+ if (recordBtn) {
899
+ recordBtn.style.opacity = '1';
900
+ recordBtn.title = '';
901
+ }
902
+ if (workflowRecordBtn) {
903
+ workflowRecordBtn.style.opacity = '1';
904
+ workflowRecordBtn.title = '';
905
+ }
906
+ }
907
+ }
908
+
909
+ // Initialize server status check
910
+ updateButtonStatesForServerStatus();
911
+ setInterval(updateButtonStatesForServerStatus, 3000);
912
+
913
+ // Initialize site management functionality
914
+ initializeSiteManagement();
915
+
916
+ // Initialize enhanced capture toggle
917
+ const enhancedCaptureCheckbox = document.getElementById('enhancedCaptureCheckbox');
918
+ if (enhancedCaptureCheckbox) {
919
+ // Load setting from storage (default to false for performance)
920
+ chrome.storage.local.get(['enhancedClickCapture'], (result) => {
921
+ enhancedCaptureCheckbox.checked = result.enhancedClickCapture === true;
922
+ });
923
+
924
+ // Save changes when checkbox is clicked
925
+ enhancedCaptureCheckbox.addEventListener('change', () => {
926
+ chrome.storage.local.set({ enhancedClickCapture: enhancedCaptureCheckbox.checked });
927
+ console.log('Enhanced click capture setting changed to:', enhancedCaptureCheckbox.checked);
928
+ });
929
+ }
930
+
931
+ // Initialize state from storage
932
+ chrome.storage.local.get([
933
+ 'frameRate',
934
+ 'imageQuality',
935
+ 'frameFlash',
936
+ 'workflowRecording',
937
+ 'includeLogsInExport',
938
+ 'workflowScreenshotsEnabled',
939
+ 'workflowScreenshotFormat',
940
+ 'workflowScreenshotQuality',
941
+ 'workflowScreenshotMaxWidth'
942
+ ], (result) => {
943
+
944
+ // Restore workflow recording state
945
+ if (result.workflowRecording === true) {
946
+ isWorkflowRecording = true;
947
+ updateWorkflowRecordingUI();
948
+ }
949
+
950
+ // Restore include logs checkbox state (default to true)
951
+ if (includeLogsCheckbox) {
952
+ includeLogsCheckbox.checked = result.includeLogsInExport !== false;
953
+ }
954
+
955
+ // Load recording settings with defaults
956
+ const frameRateSelect = document.getElementById('frameRate');
957
+ const imageQualitySelect = document.getElementById('imageQuality');
958
+ const frameFlashCheckbox = document.getElementById('frameFlash');
959
+
960
+ // Set defaults if not stored
961
+ const frameRate = result.frameRate || '1';
962
+ const imageQuality = result.imageQuality || '30';
963
+ const frameFlash = result.frameFlash !== false; // Default to true
964
+
965
+ if (frameRateSelect) {
966
+ frameRateSelect.value = frameRate;
967
+ }
968
+ if (imageQualitySelect) {
969
+ imageQualitySelect.value = imageQuality;
970
+ }
971
+ if (frameFlashCheckbox) {
972
+ frameFlashCheckbox.checked = frameFlash;
973
+ }
974
+
975
+ // Save defaults if they weren't stored
976
+ if (!result.frameRate || !result.imageQuality || result.frameFlash === undefined) {
977
+ chrome.storage.local.set({
978
+ frameRate: frameRate,
979
+ imageQuality: imageQuality,
980
+ frameFlash: frameFlash
981
+ });
982
+ }
983
+
984
+ // Restore workflow screenshot settings
985
+ const enableScreenshotsCheckbox = document.getElementById('enableScreenshotsCheckbox');
986
+ const screenshotFormat = document.getElementById('screenshotFormat');
987
+ const screenshotQuality = document.getElementById('screenshotQuality');
988
+ const screenshotMaxWidth = document.getElementById('screenshotMaxWidth');
989
+ const screenshotSettingsDiv = document.getElementById('screenshotSettings');
990
+ const qualityValue = document.getElementById('qualityValue');
991
+
992
+ if (enableScreenshotsCheckbox) {
993
+ // Default to true if not set
994
+ const screenshotsEnabled = result.workflowScreenshotsEnabled !== false;
995
+ enableScreenshotsCheckbox.checked = screenshotsEnabled;
996
+
997
+ if (screenshotSettingsDiv) {
998
+ screenshotSettingsDiv.style.display = screenshotsEnabled ? 'block' : 'none';
999
+ }
1000
+ }
1001
+
1002
+ if (screenshotFormat && result.workflowScreenshotFormat) {
1003
+ screenshotFormat.value = result.workflowScreenshotFormat;
1004
+ }
1005
+
1006
+ if (screenshotQuality) {
1007
+ const quality = result.workflowScreenshotQuality || 30;
1008
+ screenshotQuality.value = quality;
1009
+ if (qualityValue) qualityValue.textContent = quality;
1010
+ }
1011
+
1012
+ if (screenshotMaxWidth && result.workflowScreenshotMaxWidth) {
1013
+ screenshotMaxWidth.value = result.workflowScreenshotMaxWidth;
1014
+ }
1015
+
1016
+ });
1017
+
1018
+ // Load saved recordings
1019
+ loadRecordings();
1020
+
1021
+ // Setup recording settings event listeners
1022
+ const frameRateSelect = document.getElementById('frameRate');
1023
+ const imageQualitySelect = document.getElementById('imageQuality');
1024
+ const frameFlashCheckbox = document.getElementById('frameFlash');
1025
+
1026
+ if (frameRateSelect) {
1027
+ frameRateSelect.addEventListener('change', () => {
1028
+ chrome.storage.local.set({ frameRate: frameRateSelect.value });
1029
+ });
1030
+ }
1031
+
1032
+ if (imageQualitySelect) {
1033
+ imageQualitySelect.addEventListener('change', () => {
1034
+ chrome.storage.local.set({ imageQuality: imageQualitySelect.value });
1035
+ });
1036
+ }
1037
+
1038
+ if (frameFlashCheckbox) {
1039
+ frameFlashCheckbox.addEventListener('change', () => {
1040
+ chrome.storage.local.set({ frameFlash: frameFlashCheckbox.checked });
1041
+ });
1042
+ }
1043
+
1044
+ // Check initial recording state from storage
1045
+ chrome.storage.local.get(['recordingActive', 'recordingStartTime', 'pendingRecording', 'pendingTabId'], (result) => {
1046
+ if (result.recordingActive) {
1047
+ isRecording = true;
1048
+ recordingStartTime = result.recordingStartTime || Date.now();
1049
+ updateRecordingUI();
1050
+ console.log('Restored recording state from storage');
1051
+ } else if (result.pendingRecording && result.pendingTabId) {
1052
+ // Auto-start recording for the pending tab
1053
+ console.log('Found pending recording for tab:', result.pendingTabId);
1054
+ const targetTabId = result.pendingTabId;
1055
+ chrome.storage.local.remove(['pendingRecording', 'pendingTabId']);
1056
+
1057
+ // Start recording
1058
+ chrome.runtime.sendMessage({
1059
+ action: 'startRecording',
1060
+ tabId: targetTabId
1061
+ }, (response) => {
1062
+ if (chrome.runtime.lastError) {
1063
+ console.error('Error starting recording:', chrome.runtime.lastError);
1064
+ return;
1065
+ }
1066
+
1067
+ if (response && response.success) {
1068
+ isRecording = true;
1069
+ recordingStartTime = Date.now();
1070
+ updateRecordingUI();
1071
+ }
1072
+ });
1073
+ }
1074
+ });
1075
+
1076
+
1077
+ // Setup recording button handler
1078
+ const recordBtn = document.getElementById('recordBtn');
1079
+ const recordingStatus = document.getElementById('recordingStatus');
1080
+
1081
+ if (recordBtn) {
1082
+ console.log('Setting up record button handler');
1083
+ recordBtn.addEventListener('click', async (e) => {
1084
+ e.preventDefault();
1085
+ console.log('Record button clicked, isRecording:', isRecording);
1086
+
1087
+ if (!isRecording && !isStoppingRecording) {
1088
+ try {
1089
+ // Check if server is running before starting recording
1090
+ const serverStatus = await checkServerStatus();
1091
+ if (!serverStatus.connected) {
1092
+ console.error('Server not running');
1093
+ if (recordingStatus) {
1094
+ recordingStatus.innerHTML = '<strong style="color: #f44336;">Error: Server not running</strong><br>' +
1095
+ '<small>Please start the Chrome Debug server first</small>';
1096
+ }
1097
+ // Clear error message after 5 seconds
1098
+ setTimeout(() => {
1099
+ if (recordingStatus) recordingStatus.textContent = '';
1100
+ }, 5000);
1101
+ return;
1102
+ }
1103
+
1104
+ // Get the current active tab
1105
+ const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
1106
+ console.log('Current tab:', tab);
1107
+
1108
+ if (!tab || !tab.id) {
1109
+ console.error('No active tab found');
1110
+ if (recordingStatus) {
1111
+ recordingStatus.textContent = 'Error: No active tab';
1112
+ }
1113
+ return;
1114
+ }
1115
+
1116
+ // Disable button while starting
1117
+ recordBtn.textContent = 'Starting...';
1118
+ recordBtn.disabled = true;
1119
+
1120
+ // Get recording settings
1121
+ const frameRateSelect = document.getElementById('frameRate');
1122
+ const imageQualitySelect = document.getElementById('imageQuality');
1123
+ const frameFlashCheckbox = document.getElementById('frameFlash');
1124
+ const screenRecordingSessionNameInput = document.getElementById('screenRecordingSessionNameInput');
1125
+
1126
+ const settings = {
1127
+ frameRate: frameRateSelect ? parseFloat(frameRateSelect.value) : 1,
1128
+ imageQuality: imageQualitySelect ? parseInt(imageQualitySelect.value) : 30,
1129
+ frameFlash: frameFlashCheckbox ? frameFlashCheckbox.checked : true,
1130
+ sessionName: screenRecordingSessionNameInput?.value.trim() || null
1131
+ };
1132
+
1133
+ // Send message to background to start recording
1134
+ chrome.runtime.sendMessage({
1135
+ action: 'startRecording',
1136
+ tabId: tab.id,
1137
+ settings: settings
1138
+ }, (response) => {
1139
+ recordBtn.disabled = false;
1140
+
1141
+ if (chrome.runtime.lastError) {
1142
+ console.error('Error starting recording:', chrome.runtime.lastError);
1143
+ recordingStatus.textContent = 'Error: ' + chrome.runtime.lastError.message;
1144
+ recordBtn.textContent = 'Start Recording';
1145
+ return;
1146
+ }
1147
+
1148
+ if (response && response.success) {
1149
+ console.log('Recording started successfully');
1150
+ isRecording = true;
1151
+ recordingStartTime = Date.now();
1152
+
1153
+ // Capture current frame rate for countdown timer
1154
+ currentFrameRate = settings.frameRate;
1155
+
1156
+ updateRecordingUI();
1157
+ } else {
1158
+ recordingStatus.textContent = 'Error: ' + (response?.error || 'Failed to start recording');
1159
+ recordBtn.textContent = 'Start Recording';
1160
+ }
1161
+ });
1162
+ } catch (error) {
1163
+ console.error('Error in record button handler:', error);
1164
+ if (recordingStatus) {
1165
+ recordingStatus.textContent = 'Error: ' + error.message;
1166
+ }
1167
+ recordBtn.disabled = false;
1168
+ recordBtn.textContent = 'Start Recording';
1169
+ }
1170
+ } else if (isRecording && !isStoppingRecording) {
1171
+ // Stop recording
1172
+ console.log('Stopping recording...');
1173
+ isStoppingRecording = true;
1174
+ recordBtn.textContent = 'Stopping...';
1175
+ recordBtn.disabled = true;
1176
+
1177
+ chrome.runtime.sendMessage({
1178
+ action: 'stopRecording'
1179
+ }, (response) => {
1180
+ isStoppingRecording = false;
1181
+ recordBtn.disabled = false;
1182
+
1183
+ if (chrome.runtime.lastError) {
1184
+ console.error('Error stopping recording:', chrome.runtime.lastError);
1185
+ recordingStatus.textContent = 'Error: ' + chrome.runtime.lastError.message;
1186
+ recordBtn.textContent = 'Stop Recording';
1187
+ return;
1188
+ }
1189
+
1190
+ if (response && response.success) {
1191
+ console.log('Recording stopped successfully');
1192
+ isRecording = false;
1193
+ updateRecordingUI();
1194
+ } else {
1195
+ recordingStatus.textContent = 'Error: ' + (response?.error || 'Failed to stop recording');
1196
+ recordBtn.textContent = 'Stop Recording';
1197
+ }
1198
+ });
1199
+ }
1200
+ });
1201
+ } else {
1202
+ console.error('Record button not found!');
1203
+ }
1204
+
1205
+ // Setup manual snapshot button handler
1206
+ const manualSnapshotBtn = document.getElementById('manualSnapshotBtn');
1207
+ if (manualSnapshotBtn) {
1208
+ manualSnapshotBtn.addEventListener('click', (e) => {
1209
+ e.preventDefault();
1210
+ if (isRecording) {
1211
+ takeManualSnapshot();
1212
+ }
1213
+ });
1214
+ }
1215
+
1216
+ /*
1217
+ * SNAPSHOT FEATURE DISABLED (2025-10-01)
1218
+ * Snapshot button click handler disabled - see SNAPSHOT_FEATURE_DISABLED.md
1219
+ */
1220
+ /*
1221
+ const takeSnapshotBtn = document.getElementById('takeSnapshotBtn');
1222
+ if (takeSnapshotBtn) {
1223
+ takeSnapshotBtn.addEventListener('click', async (e) => {
1224
+ e.preventDefault();
1225
+ await takeSnapshot();
1226
+ });
1227
+ }
1228
+ */
1229
+
1230
+ // Setup workflow recording button handler
1231
+ const workflowRecordBtn = document.getElementById('workflowRecordBtn');
1232
+ const workflowRecordingsList = document.getElementById('workflowRecordingsList');
1233
+ const workflowRecordingsContainer = document.getElementById('workflowRecordingsContainer');
1234
+ const includeLogsCheckbox = document.getElementById('includeLogsCheckbox');
1235
+
1236
+ if (workflowRecordBtn) {
1237
+ workflowRecordBtn.addEventListener('click', async () => {
1238
+ if (!isWorkflowRecording) {
1239
+ // Start workflow recording
1240
+ try {
1241
+ // Check if server is running before starting workflow recording
1242
+ const serverStatus = await checkServerStatus();
1243
+ if (!serverStatus.connected) {
1244
+ console.error('Server not running');
1245
+ const workflowStatus = document.getElementById('workflowRecordingStatus');
1246
+ if (workflowStatus) {
1247
+ workflowStatus.innerHTML = '<strong style="color: #f44336;">Error: Server not running</strong><br>' +
1248
+ '<small>Please start the Chrome Debug server first</small>';
1249
+ }
1250
+ // Clear error message after 5 seconds
1251
+ setTimeout(() => {
1252
+ if (workflowStatus) workflowStatus.textContent = '';
1253
+ }, 5000);
1254
+ return;
1255
+ }
1256
+
1257
+ // Check license before allowing workflow recording (same pattern as strategic plan)
1258
+ console.log('[License] Checking license for workflow recording...');
1259
+ const licenseCheck = await chrome.runtime.sendMessage({
1260
+ action: 'checkLicenseForWorkflow'
1261
+ });
1262
+ console.log('[License] License check result:', licenseCheck);
1263
+
1264
+ // If not Pro or limit reached, show upgrade modal
1265
+ if (!licenseCheck || !licenseCheck.allowed || (licenseCheck.tier && licenseCheck.tier !== 'pro')) {
1266
+ const message = licenseCheck?.message || 'Workflow Recording is a Pro feature. Upgrade to Pro for unlimited workflow recordings with function tracing.';
1267
+
1268
+ // Show modal
1269
+ if (confirm(`${message}\n\nWould you like to upgrade now?`)) {
1270
+ // Open upgrade page
1271
+ chrome.tabs.create({
1272
+ url: LEMONSQUEEZY_CHECKOUT_URL
1273
+ });
1274
+ }
1275
+
1276
+ return; // Stop execution
1277
+ }
1278
+
1279
+ const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
1280
+
1281
+ if (!tab || !tab.id) {
1282
+ console.error('No active tab found');
1283
+ return;
1284
+ }
1285
+
1286
+ // Get settings
1287
+ const includeLogsInExport = includeLogsCheckbox ? includeLogsCheckbox.checked : true;
1288
+ const sessionName = document.getElementById('sessionNameInput')?.value || null;
1289
+ const enableScreenshots = document.getElementById('enableScreenshotsCheckbox')?.checked || false;
1290
+
1291
+ let screenshotSettings = null;
1292
+ if (enableScreenshots) {
1293
+ screenshotSettings = {
1294
+ enabled: true,
1295
+ format: document.getElementById('screenshotFormat')?.value || 'jpeg',
1296
+ quality: parseInt(document.getElementById('screenshotQuality')?.value) || 30,
1297
+ maxWidth: parseInt(document.getElementById('screenshotMaxWidth')?.value) || null
1298
+ };
1299
+ }
1300
+
1301
+ // Send message to background script to start recording
1302
+ console.log('[Popup v2.0.6] Sending startWorkflowRecording message to background');
1303
+ chrome.runtime.sendMessage({
1304
+ action: 'startWorkflowRecording',
1305
+ tabId: tab.id,
1306
+ includeLogsInExport: includeLogsInExport,
1307
+ sessionName: sessionName,
1308
+ screenshotSettings: screenshotSettings
1309
+ }, (response) => {
1310
+ console.log('[Popup v2.0.6] Received response from background:', response);
1311
+ console.log('[Popup v2.0.6] chrome.runtime.lastError:', chrome.runtime.lastError);
1312
+ console.log('[Popup v2.0.6] Checking which branch will execute...');
1313
+
1314
+ if (chrome.runtime.lastError) {
1315
+ console.error('[Popup v2.0.6] RUNTIME ERROR BRANCH:', chrome.runtime.lastError);
1316
+ // Check if this tab allows content script injection
1317
+ if (!tab.url || tab.url.startsWith('chrome://') || tab.url.startsWith('chrome-extension://') || tab.url.startsWith('moz-extension://')) {
1318
+ console.log('Cannot inject content script into restricted URL:', tab.url);
1319
+ return;
1320
+ }
1321
+ // Inject content script if needed
1322
+ chrome.scripting.executeScript({
1323
+ target: { tabId: tab.id },
1324
+ files: ['content.js']
1325
+ }, () => {
1326
+ if (chrome.runtime.lastError) {
1327
+ console.error('Failed to inject content script:', chrome.runtime.lastError);
1328
+ return;
1329
+ }
1330
+ chrome.scripting.insertCSS({
1331
+ target: { tabId: tab.id },
1332
+ files: ['content.css']
1333
+ }, () => {
1334
+ if (chrome.runtime.lastError) {
1335
+ console.error('Failed to inject CSS:', chrome.runtime.lastError);
1336
+ return;
1337
+ }
1338
+ // Try again after injection
1339
+ setTimeout(() => {
1340
+ chrome.runtime.sendMessage({
1341
+ action: 'startWorkflowRecording',
1342
+ tabId: tab.id,
1343
+ includeLogsInExport: includeLogsInExport,
1344
+ sessionName: sessionName,
1345
+ screenshotSettings: screenshotSettings
1346
+ }, (retryResponse) => {
1347
+ if (retryResponse && retryResponse.success) {
1348
+ isWorkflowRecording = true;
1349
+ // Save state and start time to storage
1350
+ const startTime = Date.now();
1351
+ chrome.storage.local.set({
1352
+ workflowRecording: true,
1353
+ workflowStartTime: startTime
1354
+ });
1355
+ workflowStartTime = startTime;
1356
+ updateWorkflowRecordingUI();
1357
+ if (workflowRecordingsList) workflowRecordingsList.style.display = 'none';
1358
+ }
1359
+ });
1360
+ }, 100);
1361
+ });
1362
+ });
1363
+ } else if (response && response.success) {
1364
+ console.log('[Popup v2.0.6] *** SUCCESS BRANCH REACHED! ***');
1365
+ console.log('[Popup v2.0.6] Workflow recording started successfully, updating UI now');
1366
+ console.log('[Popup v2.0.6] Setting isWorkflowRecording = true');
1367
+ isWorkflowRecording = true;
1368
+ // Save state and start time to storage
1369
+ const startTime = Date.now();
1370
+ chrome.storage.local.set({
1371
+ workflowRecording: true,
1372
+ workflowStartTime: startTime
1373
+ });
1374
+ workflowStartTime = startTime;
1375
+ console.log('[Popup v2.0.6] About to call updateWorkflowRecordingUI()');
1376
+ updateWorkflowRecordingUI();
1377
+ console.log('[Popup v2.0.6] updateWorkflowRecordingUI() call completed');
1378
+ // Hide any previous results
1379
+ if (workflowRecordingsList) workflowRecordingsList.style.display = 'none';
1380
+ } else {
1381
+ console.error('[Popup v2.0.6] *** ELSE BRANCH - Unexpected response ***');
1382
+ console.error('[Popup v2.0.6] Response details:', {
1383
+ exists: !!response,
1384
+ success: response?.success,
1385
+ type: typeof response,
1386
+ keys: response ? Object.keys(response) : 'no response',
1387
+ fullResponse: response
1388
+ });
1389
+ }
1390
+ });
1391
+ } catch (error) {
1392
+ console.error('Error starting workflow recording:', error);
1393
+ }
1394
+ } else {
1395
+ // Stop workflow recording
1396
+ try {
1397
+ const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
1398
+
1399
+ if (!tab || !tab.id) {
1400
+ console.error('No active tab found');
1401
+ return;
1402
+ }
1403
+
1404
+ // Send message to background script to stop recording
1405
+ chrome.runtime.sendMessage({
1406
+ action: 'stopWorkflowRecording',
1407
+ tabId: tab.id
1408
+ }, async (response) => {
1409
+ if (chrome.runtime.lastError) {
1410
+ console.error('Error stopping workflow:', chrome.runtime.lastError);
1411
+ // Still stop recording in UI even if there was an error
1412
+ isWorkflowRecording = false;
1413
+ chrome.storage.local.set({
1414
+ workflowRecording: false,
1415
+ workflowStartTime: null
1416
+ });
1417
+ updateWorkflowRecordingUI();
1418
+ alert('Error stopping workflow recording. The recording may have been lost.');
1419
+ return;
1420
+ }
1421
+
1422
+ if (response && response.success && response.workflow) {
1423
+ isWorkflowRecording = false;
1424
+ // Clear state from storage
1425
+ chrome.storage.local.set({
1426
+ workflowRecording: false,
1427
+ workflowStartTime: null
1428
+ });
1429
+ updateWorkflowRecordingUI();
1430
+
1431
+ // Display the workflow results
1432
+ if (workflowRecordingsContainer) {
1433
+ let workflowItem;
1434
+
1435
+ // Get current server port for prompt generation
1436
+ const serverConnection = await checkServerStatus();
1437
+ const currentServerPort = serverConnection.connectedPort;
1438
+
1439
+ // The response.workflow contains the full response from stopWorkflowRecording
1440
+ const workflowData = response.workflow;
1441
+
1442
+ if (workflowData.savedToServer) {
1443
+ // Create a saved recording entry like screen recordings
1444
+ workflowItem = document.createElement('div');
1445
+ workflowItem.className = 'recording-item';
1446
+ workflowItem.innerHTML = `
1447
+ <div class="recording-info">
1448
+ <div style="font-weight: bold; margin-bottom: 4px;">
1449
+ ${workflowData.workflow.sessionId} (workflow)
1450
+ </div>
1451
+ <div style="color: #666; font-size: 11px;">
1452
+ ${workflowData.workflow.actions.length} actions • ${workflowData.workflow.logs.length} logs
1453
+ </div>
1454
+ </div>
1455
+ <div style="display: flex; gap: 4px; margin-top: 8px;">
1456
+ <button class="copy-id-btn" style="padding: 4px 8px; background: #2196F3; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px;">
1457
+ Copy ID
1458
+ </button>
1459
+ <button class="copy-prompt-btn" data-id="${workflowData.workflow.sessionId}" data-port="${currentServerPort || ''}" style="padding: 4px 8px; background: #4CAF50; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px;">
1460
+ Copy Prompt
1461
+ </button>
1462
+ </div>
1463
+ `;
1464
+
1465
+ // Add event listeners for the buttons
1466
+ const copyIdBtn = workflowItem.querySelector('.copy-id-btn');
1467
+ const copyPromptBtn = workflowItem.querySelector('.copy-prompt-btn');
1468
+
1469
+ copyIdBtn.addEventListener('click', () => {
1470
+ navigator.clipboard.writeText(workflowData.workflow.sessionId).then(() => {
1471
+ copyIdBtn.textContent = 'Copied!';
1472
+ copyIdBtn.style.background = '#4CAF50';
1473
+ setTimeout(() => {
1474
+ copyIdBtn.textContent = 'Copy ID';
1475
+ copyIdBtn.style.background = '#2196F3';
1476
+ }, 2000);
1477
+ });
1478
+ });
1479
+
1480
+ // copyPromptBtn is now handled by the shared event listener system with data-id and data-port attributes
1481
+ } else {
1482
+ // Fallback when server not available
1483
+ workflowItem = document.createElement('div');
1484
+ workflowItem.className = 'recording-item';
1485
+ workflowItem.innerHTML = `
1486
+ <div class="recording-info">
1487
+ <div style="font-weight: bold; margin-bottom: 4px; color: #ff6b6b;">
1488
+ Server not available - JSON downloaded
1489
+ </div>
1490
+ <div style="color: #666; font-size: 11px;">
1491
+ ${workflowData.workflow.length} actions recorded
1492
+ </div>
1493
+ </div>
1494
+ `;
1495
+ }
1496
+
1497
+ // Add the item to the container
1498
+ workflowRecordingsContainer.appendChild(workflowItem);
1499
+ workflowRecordingsList.style.display = 'block';
1500
+
1501
+ // Reload the list to show all recordings
1502
+ setTimeout(() => {
1503
+ loadWorkflowRecordings();
1504
+ }, 500);
1505
+ }
1506
+ }
1507
+ });
1508
+ } catch (error) {
1509
+ console.error('Error stopping workflow recording:', error);
1510
+ }
1511
+ }
1512
+ });
1513
+ }
1514
+
1515
+ // Setup include logs checkbox
1516
+ if (includeLogsCheckbox) {
1517
+ includeLogsCheckbox.addEventListener('change', () => {
1518
+ chrome.storage.local.set({ includeLogsInExport: includeLogsCheckbox.checked });
1519
+ });
1520
+ }
1521
+
1522
+ // Setup save restore point button
1523
+ const saveRestorePointBtn = document.getElementById('saveRestorePointBtn');
1524
+ const restorePointsList = document.getElementById('restorePointsList');
1525
+ const restorePointsContainer = document.getElementById('restorePointsContainer');
1526
+
1527
+ if (saveRestorePointBtn) {
1528
+ saveRestorePointBtn.addEventListener('click', async () => {
1529
+ try {
1530
+ const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
1531
+
1532
+ if (!tab || !tab.id) {
1533
+ console.error('No active tab found');
1534
+ return;
1535
+ }
1536
+
1537
+ // Send message to content script to create restore point
1538
+ chrome.tabs.sendMessage(tab.id, {
1539
+ action: 'createRestorePoint'
1540
+ }, (response) => {
1541
+ if (chrome.runtime.lastError) {
1542
+ console.error('Error creating restore point:', chrome.runtime.lastError);
1543
+ return;
1544
+ }
1545
+
1546
+ if (response && response.success) {
1547
+ console.log('Restore point created:', response.restorePointId);
1548
+ // Refresh restore points list
1549
+ loadRestorePoints();
1550
+ } else {
1551
+ console.error('Failed to create restore point:', response);
1552
+ }
1553
+ });
1554
+ } catch (error) {
1555
+ console.error('Error creating restore point:', error);
1556
+ }
1557
+ });
1558
+ }
1559
+
1560
+ // Function to load and display restore points
1561
+ async function loadRestorePoints() {
1562
+ // Get current workflow ID from storage
1563
+ chrome.storage.local.get(['currentWorkflowId'], async (result) => {
1564
+ if (!result.currentWorkflowId) {
1565
+ return;
1566
+ }
1567
+
1568
+ // Check server for restore points
1569
+ const ports = CONFIG_PORTS;
1570
+ for (const port of ports) {
1571
+ try {
1572
+ const response = await fetch(`http://localhost:${port}/chromedebug/restore-points/${result.currentWorkflowId}`, {
1573
+ method: 'GET',
1574
+ mode: 'cors'
1575
+ });
1576
+
1577
+ if (response.ok) {
1578
+ const restorePoints = await response.json();
1579
+
1580
+ if (restorePoints.length > 0) {
1581
+ restorePointsList.style.display = 'block';
1582
+ restorePointsContainer.innerHTML = '';
1583
+
1584
+ restorePoints.forEach(rp => {
1585
+ const item = document.createElement('div');
1586
+ item.className = 'restore-point-item';
1587
+ item.innerHTML = `
1588
+ <div class="restore-point-info">
1589
+ <div style="font-weight: bold;">Action ${rp.actionIndex}</div>
1590
+ <div style="font-size: 11px; color: #666;">
1591
+ ${new Date(rp.timestamp).toLocaleString()}
1592
+ </div>
1593
+ <div style="font-size: 11px; color: #666;">
1594
+ ${rp.title}
1595
+ </div>
1596
+ </div>
1597
+ <div>
1598
+ <button class="restore-btn" data-restore-id="${rp.id}">Restore</button>
1599
+ <button class="delete-btn" data-restore-id="${rp.id}" style="padding: 4px 8px; font-size: 10px;">Delete</button>
1600
+ </div>
1601
+ `;
1602
+ restorePointsContainer.appendChild(item);
1603
+ });
1604
+
1605
+ // Add event listeners to restore buttons
1606
+ document.querySelectorAll('.restore-btn').forEach(btn => {
1607
+ btn.addEventListener('click', async (e) => {
1608
+ const restoreId = e.target.getAttribute('data-restore-id');
1609
+ await restoreFromPoint(restoreId);
1610
+ });
1611
+ });
1612
+
1613
+ // Add event listeners to delete buttons
1614
+ document.querySelectorAll('.restore-point-item .delete-btn').forEach(btn => {
1615
+ btn.addEventListener('click', async (e) => {
1616
+ const restoreId = e.target.getAttribute('data-restore-id');
1617
+ if (confirm('Delete this restore point?')) {
1618
+ await deleteRestorePoint(restoreId);
1619
+ loadRestorePoints(); // Refresh list
1620
+ }
1621
+ });
1622
+ });
1623
+ } else {
1624
+ restorePointsList.style.display = 'none';
1625
+ }
1626
+ break;
1627
+ }
1628
+ } catch (error) {
1629
+ // Try next port
1630
+ }
1631
+ }
1632
+ });
1633
+ }
1634
+
1635
+ // Function to restore from a restore point
1636
+ async function restoreFromPoint(restorePointId) {
1637
+ try {
1638
+ // First get the restore point data from server
1639
+ const ports = CONFIG_PORTS;
1640
+ let restorePointData = null;
1641
+
1642
+ for (const port of ports) {
1643
+ try {
1644
+ const response = await fetch(`http://localhost:${port}/chromedebug/restore-point/${restorePointId}`, {
1645
+ method: 'GET',
1646
+ mode: 'cors'
1647
+ });
1648
+
1649
+ if (response.ok) {
1650
+ restorePointData = await response.json();
1651
+ break;
1652
+ }
1653
+ } catch (error) {
1654
+ // Try next port
1655
+ }
1656
+ }
1657
+
1658
+ if (!restorePointData) {
1659
+ console.error('Could not retrieve restore point data');
1660
+ return;
1661
+ }
1662
+
1663
+ const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
1664
+
1665
+ if (!tab || !tab.id) {
1666
+ console.error('No active tab found');
1667
+ return;
1668
+ }
1669
+
1670
+ // Send message to content script to restore
1671
+ chrome.tabs.sendMessage(tab.id, {
1672
+ action: 'restoreFromPoint',
1673
+ restorePointData: restorePointData
1674
+ }, (response) => {
1675
+ if (chrome.runtime.lastError) {
1676
+ console.error('Error restoring from point:', chrome.runtime.lastError);
1677
+ return;
1678
+ }
1679
+
1680
+ if (response && response.success) {
1681
+ console.log('Successfully restored from point');
1682
+ } else {
1683
+ console.error('Failed to restore from point:', response);
1684
+ }
1685
+ });
1686
+ } catch (error) {
1687
+ console.error('Error restoring from point:', error);
1688
+ }
1689
+ }
1690
+
1691
+ // Function to delete a restore point
1692
+ async function deleteRestorePoint(restorePointId) {
1693
+ const ports = CONFIG_PORTS;
1694
+
1695
+ for (const port of ports) {
1696
+ try {
1697
+ const response = await fetch(`http://localhost:${port}/chromedebug/restore-point/${restorePointId}`, {
1698
+ method: 'DELETE',
1699
+ mode: 'cors'
1700
+ });
1701
+
1702
+ if (response.ok) {
1703
+ console.log('Restore point deleted');
1704
+ break;
1705
+ }
1706
+ } catch (error) {
1707
+ // Try next port
1708
+ }
1709
+ }
1710
+ }
1711
+
1712
+ // Setup screenshot settings handlers
1713
+ const enableScreenshotsCheckbox = document.getElementById('enableScreenshotsCheckbox');
1714
+ const screenshotSettingsDiv = document.getElementById('screenshotSettings');
1715
+ const qualitySlider = document.getElementById('screenshotQuality');
1716
+ const qualityValue = document.getElementById('qualityValue');
1717
+
1718
+ if (enableScreenshotsCheckbox) {
1719
+ enableScreenshotsCheckbox.addEventListener('change', () => {
1720
+ if (screenshotSettingsDiv) {
1721
+ screenshotSettingsDiv.style.display = enableScreenshotsCheckbox.checked ? 'block' : 'none';
1722
+ }
1723
+ // Save checkbox state with error handling
1724
+ chrome.storage.local.set({ workflowScreenshotsEnabled: enableScreenshotsCheckbox.checked }, () => {
1725
+ if (chrome.runtime.lastError) {
1726
+ console.error('Failed to save screenshot enabled setting:', chrome.runtime.lastError);
1727
+ }
1728
+ });
1729
+ });
1730
+ }
1731
+
1732
+ // Screenshot format dropdown
1733
+ const screenshotFormat = document.getElementById('screenshotFormat');
1734
+ if (screenshotFormat) {
1735
+ screenshotFormat.addEventListener('change', () => {
1736
+ chrome.storage.local.set({ workflowScreenshotFormat: screenshotFormat.value }, () => {
1737
+ if (chrome.runtime.lastError) {
1738
+ console.error('Failed to save screenshot format:', chrome.runtime.lastError);
1739
+ }
1740
+ });
1741
+ });
1742
+ }
1743
+
1744
+ // Screenshot quality slider with debouncing
1745
+ if (qualitySlider && qualityValue) {
1746
+ qualitySlider.addEventListener('input', () => {
1747
+ // Update display immediately
1748
+ qualityValue.textContent = qualitySlider.value;
1749
+
1750
+ // Debounce the save operation
1751
+ clearTimeout(screenshotQualityDebounceTimer);
1752
+ screenshotQualityDebounceTimer = setTimeout(() => {
1753
+ const quality = parseInt(qualitySlider.value);
1754
+ chrome.storage.local.set({ workflowScreenshotQuality: quality }, () => {
1755
+ if (chrome.runtime.lastError) {
1756
+ console.error('Failed to save screenshot quality:', chrome.runtime.lastError);
1757
+ }
1758
+ });
1759
+ }, 500);
1760
+ });
1761
+ }
1762
+
1763
+ // Screenshot max width with validation
1764
+ const screenshotMaxWidth = document.getElementById('screenshotMaxWidth');
1765
+ if (screenshotMaxWidth) {
1766
+ screenshotMaxWidth.addEventListener('blur', () => {
1767
+ let value = screenshotMaxWidth.value.trim();
1768
+
1769
+ if (value === '') {
1770
+ value = null;
1771
+ } else {
1772
+ value = parseInt(value);
1773
+ if (isNaN(value) || value < 100 || value > 10000) {
1774
+ console.warn('Invalid max width value, must be between 100-10000');
1775
+ screenshotMaxWidth.value = '';
1776
+ return;
1777
+ }
1778
+ }
1779
+
1780
+ chrome.storage.local.set({ workflowScreenshotMaxWidth: value }, () => {
1781
+ if (chrome.runtime.lastError) {
1782
+ console.error('Failed to save screenshot max width:', chrome.runtime.lastError);
1783
+ }
1784
+ });
1785
+ });
1786
+ }
1787
+
1788
+
1789
+ // Copy button handler is now inline in the workflow item creation
1790
+
1791
+ // Set up "View Past Recordings" links
1792
+ const viewWorkflowRecordingsLink = document.getElementById('viewWorkflowRecordings');
1793
+ const viewScreenRecordingsLink = document.getElementById('viewScreenRecordings');
1794
+
1795
+ if (viewWorkflowRecordingsLink) {
1796
+ viewWorkflowRecordingsLink.addEventListener('click', (e) => {
1797
+ e.preventDefault();
1798
+ const workflowRecordingsList = document.getElementById('workflowRecordingsList');
1799
+ if (workflowRecordingsList) {
1800
+ if (workflowRecordingsList.style.display === 'none' || workflowRecordingsList.style.display === '') {
1801
+ workflowRecordingsList.style.display = 'block';
1802
+ loadWorkflowRecordings();
1803
+ viewWorkflowRecordingsLink.textContent = 'Hide Recordings';
1804
+ } else {
1805
+ workflowRecordingsList.style.display = 'none';
1806
+ viewWorkflowRecordingsLink.textContent = 'View Past Recordings';
1807
+ }
1808
+ }
1809
+ });
1810
+ }
1811
+
1812
+ if (viewScreenRecordingsLink) {
1813
+ viewScreenRecordingsLink.addEventListener('click', (e) => {
1814
+ e.preventDefault();
1815
+ const recordingsList = document.getElementById('recordingsList');
1816
+ if (recordingsList) {
1817
+ if (recordingsList.style.display === 'none') {
1818
+ loadScreenRecordings();
1819
+ recordingsList.style.display = 'block';
1820
+ viewScreenRecordingsLink.textContent = 'Hide Recordings';
1821
+ } else {
1822
+ recordingsList.style.display = 'none';
1823
+ viewScreenRecordingsLink.textContent = 'View Past Recordings';
1824
+ }
1825
+ }
1826
+ });
1827
+ }
1828
+
1829
+ /*
1830
+ * SNAPSHOT FEATURE DISABLED (2025-10-01)
1831
+ * View snapshots link handler disabled - see SNAPSHOT_FEATURE_DISABLED.md
1832
+ */
1833
+ /*
1834
+ const viewSnapshotsLink = document.getElementById('viewSnapshotsLink');
1835
+ if (viewSnapshotsLink) {
1836
+ viewSnapshotsLink.addEventListener('click', (e) => {
1837
+ e.preventDefault();
1838
+ const snapshotsList = document.getElementById('snapshotsList');
1839
+ if (snapshotsList) {
1840
+ if (snapshotsList.style.display === 'none') {
1841
+ loadSnapshots();
1842
+ snapshotsList.style.display = 'block';
1843
+ viewSnapshotsLink.textContent = 'Hide Snapshots';
1844
+ } else {
1845
+ snapshotsList.style.display = 'none';
1846
+ viewSnapshotsLink.textContent = 'View Past Snapshots';
1847
+ }
1848
+ }
1849
+ });
1850
+ }
1851
+ */
1852
+
1853
+ // Don't load recordings on startup - only when user clicks the link
1854
+
1855
+ // Cleanup debounce timer on unload
1856
+ window.addEventListener('beforeunload', () => {
1857
+ if (screenshotQualityDebounceTimer) {
1858
+ clearTimeout(screenshotQualityDebounceTimer);
1859
+ }
1860
+ });
1861
+ });
1862
+
1863
+ // Function to load and display workflow recordings
1864
+ async function loadWorkflowRecordings() {
1865
+ const workflowRecordingsList = document.getElementById('workflowRecordingsList');
1866
+ const workflowRecordingsContainer = document.getElementById('workflowRecordingsContainer');
1867
+
1868
+ if (!workflowRecordingsList || !workflowRecordingsContainer) {
1869
+ return;
1870
+ }
1871
+
1872
+ // Clear existing items
1873
+ workflowRecordingsContainer.innerHTML = '';
1874
+
1875
+ // Check server for workflow recordings
1876
+ const ports = CONFIG_PORTS;
1877
+ for (const port of ports) {
1878
+ try {
1879
+ const response = await fetch(`http://localhost:${port}/chromedebug/workflow-recordings`);
1880
+ if (response.ok) {
1881
+ const data = await response.json();
1882
+ console.log('Loaded workflow recordings from port', port, ':', data);
1883
+
1884
+ // Handle both data.recordings and direct array response
1885
+ const recordings = data.recordings || data;
1886
+
1887
+ if (Array.isArray(recordings) && recordings.length > 0) {
1888
+ // Display each workflow recording
1889
+ recordings.forEach(recording => {
1890
+ const workflowItem = document.createElement('div');
1891
+ workflowItem.className = 'recording-item';
1892
+
1893
+ const displayName = recording.name || `Workflow ${recording.session_id}`;
1894
+ const date = new Date(recording.timestamp);
1895
+ const formattedDate = date.toLocaleString();
1896
+
1897
+ workflowItem.innerHTML = `
1898
+ <div class="recording-info">
1899
+ <div style="font-weight: bold; margin-bottom: 4px;">${displayName}</div>
1900
+ <div style="color: #666; font-size: 11px;">
1901
+ ${recording.total_actions} actions • ${formattedDate}
1902
+ </div>
1903
+ ${recording.screenshot_settings ? '<div style="color: #2196F3; font-size: 10px;">&#128248; Screenshots included</div>' : ''}
1904
+ </div>
1905
+ <div class="recording-buttons">
1906
+ <button class="play-btn" data-session-id="${recording.session_id}" style="padding: 4px 8px; background: #4CAF50; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px;">
1907
+ &#9654; Play
1908
+ </button>
1909
+ <button class="get-prompt-btn" data-session-id="${recording.session_id}" data-name="${recording.name || ''}" style="padding: 4px 8px; background: #2196F3; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px;">
1910
+ Get Prompt
1911
+ </button>
1912
+ <button class="delete-btn" data-recording-id="${recording.id || recording.session_id}" style="padding: 4px 8px; background: #f44336; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px;">
1913
+ Delete
1914
+ </button>
1915
+ </div>
1916
+ `;
1917
+
1918
+ // Add event listeners
1919
+ const playBtn = workflowItem.querySelector('.play-btn');
1920
+ const getPromptBtn = workflowItem.querySelector('.get-prompt-btn');
1921
+ const deleteBtn = workflowItem.querySelector('.delete-btn');
1922
+
1923
+ playBtn.addEventListener('click', () => playWorkflow(recording.session_id));
1924
+ getPromptBtn.addEventListener('click', () => {
1925
+ const sessionId = recording.session_id;
1926
+ const name = recording.name;
1927
+ const port = window.CHROMEDEBUG_CONFIG?.ports?.[0] || '3001';
1928
+ let prompt;
1929
+
1930
+ if (name) {
1931
+ // Use name-based prompt similar to screen recording format
1932
+ prompt = `Please use the get_workflow_recording function in Chrome Debug to load the workflow recording "${name}" (session: ${sessionId}) that was recorded on port ${port}.`;
1933
+ } else {
1934
+ // Use session ID based prompt
1935
+ prompt = `Please use the get_workflow_recording function in Chrome Debug to load the workflow recording "${sessionId}" that was recorded on port ${port}.`;
1936
+ }
1937
+
1938
+ navigator.clipboard.writeText(prompt).then(() => {
1939
+ getPromptBtn.textContent = 'Copied!';
1940
+ getPromptBtn.style.background = '#4CAF50';
1941
+ setTimeout(() => {
1942
+ getPromptBtn.textContent = 'Get Prompt';
1943
+ getPromptBtn.style.background = '#2196F3';
1944
+ }, 2000);
1945
+ });
1946
+ });
1947
+ deleteBtn.addEventListener('click', () => {
1948
+ console.log('Delete clicked for recording:', recording);
1949
+ const recordingId = recording.id || recording.session_id;
1950
+ if (!recordingId) {
1951
+ console.error('No ID found for recording:', recording);
1952
+ alert('Cannot delete: Recording ID not found');
1953
+ return;
1954
+ }
1955
+ deleteWorkflowRecording(recordingId);
1956
+ });
1957
+
1958
+ workflowRecordingsContainer.appendChild(workflowItem);
1959
+ });
1960
+ } else {
1961
+ // Show "No recordings found" message
1962
+ const noRecordingsMsg = document.createElement('div');
1963
+ noRecordingsMsg.style.cssText = 'color: #666; font-size: 13px; text-align: center; padding: 20px;';
1964
+ noRecordingsMsg.textContent = 'No workflow recordings found';
1965
+ workflowRecordingsContainer.appendChild(noRecordingsMsg);
1966
+ }
1967
+ break; // Found working server
1968
+ }
1969
+ } catch (error) {
1970
+ console.error(`Failed to load recordings from port ${port}:`, error);
1971
+ }
1972
+ }
1973
+
1974
+ // If we couldn't connect to any server, show an error message
1975
+ if (workflowRecordingsContainer.innerHTML === '') {
1976
+ const errorMsg = document.createElement('div');
1977
+ errorMsg.style.cssText = 'color: #f44336; font-size: 13px; text-align: center; padding: 20px;';
1978
+ errorMsg.textContent = 'Could not connect to Chrome Debug server';
1979
+ workflowRecordingsContainer.appendChild(errorMsg);
1980
+ }
1981
+ }
1982
+
1983
+ // Function to play a workflow
1984
+ async function playWorkflow(sessionId) {
1985
+ console.log('Playing workflow:', sessionId);
1986
+
1987
+ try {
1988
+ // Get the current active tab
1989
+ const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
1990
+
1991
+ if (!tab || !tab.id) {
1992
+ console.error('No active tab found');
1993
+ alert('Please open a tab to play the workflow');
1994
+ return;
1995
+ }
1996
+
1997
+ // Get workflow data from server
1998
+ let workflowData = null;
1999
+ const ports = CONFIG_PORTS;
2000
+ for (const port of ports) {
2001
+ try {
2002
+ const response = await fetch(`http://localhost:${port}/chromedebug/workflow-recording/${sessionId}`);
2003
+ if (response.ok) {
2004
+ workflowData = await response.json();
2005
+ break;
2006
+ }
2007
+ } catch (error) {
2008
+ console.error(`Failed to get workflow from port ${port}:`, error);
2009
+ }
2010
+ }
2011
+
2012
+ if (!workflowData) {
2013
+ alert('Failed to load workflow data');
2014
+ return;
2015
+ }
2016
+
2017
+ // Check if we need to navigate to the workflow URL
2018
+ const currentUrl = tab.url;
2019
+ if (workflowData.url && currentUrl !== workflowData.url) {
2020
+ const navigate = confirm(`This workflow was recorded on:\n${workflowData.url}\n\nNavigate there now?`);
2021
+ if (navigate) {
2022
+ await chrome.tabs.update(tab.id, { url: workflowData.url });
2023
+ // Wait for navigation to complete
2024
+ await new Promise(resolve => {
2025
+ chrome.tabs.onUpdated.addListener(function listener(tabId, info) {
2026
+ if (tabId === tab.id && info.status === 'complete') {
2027
+ chrome.tabs.onUpdated.removeListener(listener);
2028
+ resolve();
2029
+ }
2030
+ });
2031
+ });
2032
+ // Give the page a moment to settle
2033
+ await new Promise(resolve => setTimeout(resolve, 1000));
2034
+ }
2035
+ }
2036
+
2037
+ // Send workflow to content script for playback
2038
+ chrome.tabs.sendMessage(tab.id, {
2039
+ action: 'playWorkflow',
2040
+ workflow: workflowData,
2041
+ actions: workflowData.actions
2042
+ }, (response) => {
2043
+ if (chrome.runtime.lastError) {
2044
+ console.error('Error playing workflow:', chrome.runtime.lastError);
2045
+ // Try to inject content script if it's not already loaded
2046
+ chrome.scripting.executeScript({
2047
+ target: { tabId: tab.id },
2048
+ files: ['content.js']
2049
+ }, () => {
2050
+ if (chrome.runtime.lastError) {
2051
+ console.error('Failed to inject content script:', chrome.runtime.lastError);
2052
+ alert('Failed to start workflow playback. Please reload the page and try again.');
2053
+ return;
2054
+ }
2055
+ // Try sending message again after injection
2056
+ setTimeout(() => {
2057
+ chrome.tabs.sendMessage(tab.id, {
2058
+ action: 'playWorkflow',
2059
+ workflow: workflowData,
2060
+ actions: workflowData.actions
2061
+ });
2062
+ }, 100);
2063
+ });
2064
+ } else if (response && response.success) {
2065
+ console.log('Workflow playback started');
2066
+ }
2067
+ });
2068
+ } catch (error) {
2069
+ console.error('Error playing workflow:', error);
2070
+ alert('Error playing workflow: ' + error.message);
2071
+ }
2072
+ }
2073
+
2074
+ // Function to export a workflow
2075
+ async function exportWorkflow(sessionId) {
2076
+ // Check server for workflow data
2077
+ const ports = CONFIG_PORTS;
2078
+ for (const port of ports) {
2079
+ try {
2080
+ const response = await fetch(`http://localhost:${port}/chromedebug/workflow-recording/${sessionId}`);
2081
+ if (response.ok) {
2082
+ const data = await response.json();
2083
+
2084
+ // Create and download JSON file
2085
+ const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
2086
+ const url = URL.createObjectURL(blob);
2087
+ const a = document.createElement('a');
2088
+ a.href = url;
2089
+ a.download = `workflow_${sessionId}_${Date.now()}.json`;
2090
+ document.body.appendChild(a);
2091
+ a.click();
2092
+ document.body.removeChild(a);
2093
+ URL.revokeObjectURL(url);
2094
+
2095
+ break; // Found working server
2096
+ }
2097
+ } catch (error) {
2098
+ console.error(`Failed to export workflow from port ${port}:`, error);
2099
+ }
2100
+ }
2101
+ }
2102
+
2103
+ // Function to delete a workflow recording
2104
+ async function deleteWorkflowRecording(recordingId) {
2105
+ if (!confirm('Are you sure you want to delete this workflow recording?')) {
2106
+ return;
2107
+ }
2108
+
2109
+ // Delete from server
2110
+ const ports = CONFIG_PORTS;
2111
+ for (const port of ports) {
2112
+ try {
2113
+ const response = await fetch(`http://localhost:${port}/chromedebug/workflow-recording/${recordingId}`, {
2114
+ method: 'DELETE'
2115
+ });
2116
+
2117
+ if (response.ok) {
2118
+ // Reload the list
2119
+ loadWorkflowRecordings();
2120
+ break; // Found working server
2121
+ }
2122
+ } catch (error) {
2123
+ console.error(`Failed to delete workflow from port ${port}:`, error);
2124
+ }
2125
+ }
2126
+ }
2127
+
2128
+ // Function to load and display screen recordings
2129
+ async function loadScreenRecordings() {
2130
+ const recordingsList = document.getElementById('recordingsList');
2131
+ const recordingsContainer = document.getElementById('recordingsContainer');
2132
+
2133
+ if (!recordingsList || !recordingsContainer) {
2134
+ return;
2135
+ }
2136
+
2137
+ // Clear existing items
2138
+ recordingsContainer.innerHTML = '';
2139
+
2140
+ // Check server for screen recordings
2141
+ const ports = CONFIG_PORTS;
2142
+ for (const port of ports) {
2143
+ try {
2144
+ const response = await fetch(`http://localhost:${port}/chromedebug/frame-sessions`);
2145
+ if (response.ok) {
2146
+ const data = await response.json();
2147
+ if (data && data.length > 0) {
2148
+ recordingsList.style.display = 'block';
2149
+
2150
+ // Display each screen recording
2151
+ data.forEach(recording => {
2152
+ const recordingItem = document.createElement('div');
2153
+ recordingItem.className = 'recording-item';
2154
+
2155
+ const date = new Date(recording.timestamp);
2156
+ const formattedDate = date.toLocaleString();
2157
+
2158
+ // Use session name if available, otherwise use session ID
2159
+ console.log('Recording data:', recording);
2160
+ console.log('Recording name:', recording.name);
2161
+ const displayName = recording.name || `Recording ${recording.sessionId}`;
2162
+ console.log('Display name:', displayName);
2163
+
2164
+ recordingItem.innerHTML = `
2165
+ <div class="recording-info">
2166
+ <div style="font-weight: bold; margin-bottom: 4px;">${displayName}</div>
2167
+ <div style="color: #666; font-size: 11px;">
2168
+ ${recording.totalFrames} frames • ${formattedDate}
2169
+ </div>
2170
+ </div>
2171
+ <div class="recording-buttons">
2172
+ <button class="copy-id-btn" data-session-id="${recording.sessionId}" style="padding: 4px 8px; background: #2196F3; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px;">
2173
+ Copy ID
2174
+ </button>
2175
+ <button class="delete-btn" data-session-id="${recording.sessionId}" style="padding: 4px 8px; background: #f44336; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px;">
2176
+ Delete
2177
+ </button>
2178
+ </div>
2179
+ `;
2180
+
2181
+ // Add event listeners
2182
+ const copyIdBtn = recordingItem.querySelector('.copy-id-btn');
2183
+ const deleteBtn = recordingItem.querySelector('.delete-btn');
2184
+
2185
+ copyIdBtn.addEventListener('click', () => {
2186
+ navigator.clipboard.writeText(recording.sessionId).then(() => {
2187
+ copyIdBtn.textContent = 'Copied!';
2188
+ copyIdBtn.style.background = '#4CAF50';
2189
+ setTimeout(() => {
2190
+ copyIdBtn.textContent = 'Copy ID';
2191
+ copyIdBtn.style.background = '#2196F3';
2192
+ }, 2000);
2193
+ });
2194
+ });
2195
+
2196
+ deleteBtn.addEventListener('click', async () => {
2197
+ if (confirm('Are you sure you want to delete this screen recording?')) {
2198
+ try {
2199
+ const deleteResponse = await fetch(`http://localhost:${port}/chromedebug/recording/${recording.sessionId}`, {
2200
+ method: 'DELETE'
2201
+ });
2202
+
2203
+ if (deleteResponse.ok) {
2204
+ // Reload the list
2205
+ loadScreenRecordings();
2206
+ }
2207
+ } catch (error) {
2208
+ console.error('Failed to delete recording:', error);
2209
+ }
2210
+ }
2211
+ });
2212
+
2213
+ recordingsContainer.appendChild(recordingItem);
2214
+ });
2215
+ } else {
2216
+ recordingsList.style.display = 'none';
2217
+ }
2218
+ break; // Found working server
2219
+ }
2220
+ } catch (error) {
2221
+ console.error(`Failed to load screen recordings from port ${port}:`, error);
2222
+ }
2223
+ }
2224
+ }
2225
+
2226
+ /*
2227
+ * SNAPSHOT FEATURE DISABLED (2025-10-01)
2228
+ *
2229
+ * This function loaded and displayed snapshot list in the popup UI.
2230
+ *
2231
+ * WHY DISABLED:
2232
+ * - Snapshots without console logs are just screenshots (users can do this natively)
2233
+ * - Console log capture requires always-on monitoring (privacy concern)
2234
+ * - Core value proposition (screenshot + searchable logs) cannot be achieved cleanly
2235
+ *
2236
+ * TO RE-ENABLE:
2237
+ * 1. Implement privacy-conscious always-on log monitoring
2238
+ * 2. Uncomment this function
2239
+ * 3. Re-enable backend /chromedebug/snapshots endpoint
2240
+ * 4. Re-enable snapshot UI in popup.html
2241
+ *
2242
+ * See: SNAPSHOT_FEATURE_DISABLED.md for full explanation
2243
+ */
2244
+ /*
2245
+ async function loadSnapshots() {
2246
+ const snapshotsList = document.getElementById('snapshotsList');
2247
+ const snapshotsContainer = document.getElementById('snapshotsContainer');
2248
+
2249
+ if (!snapshotsList || !snapshotsContainer) {
2250
+ return;
2251
+ }
2252
+
2253
+ // Clear existing items
2254
+ snapshotsContainer.innerHTML = '';
2255
+
2256
+ // Check server for snapshots
2257
+ const ports = CONFIG_PORTS;
2258
+ for (const port of ports) {
2259
+ try {
2260
+ const response = await fetch(`http://localhost:${port}/chromedebug/snapshots`);
2261
+ if (response.ok) {
2262
+ const data = await response.json();
2263
+ if (data && data.snapshots && data.snapshots.length > 0) {
2264
+ snapshotsList.style.display = 'block';
2265
+
2266
+ // Display each snapshot
2267
+ data.snapshots.forEach(snapshot => {
2268
+ const snapshotItem = document.createElement('div');
2269
+ snapshotItem.className = 'recording-item';
2270
+
2271
+ const date = new Date(snapshot.timestamp);
2272
+ const formattedDate = date.toLocaleString();
2273
+
2274
+ // Build note display
2275
+ const noteDisplay = snapshot.user_note
2276
+ ? `<div style="color: #666; font-size: 11px; margin-top: 2px;">Note: ${snapshot.user_note}</div>`
2277
+ : '';
2278
+
2279
+ snapshotItem.innerHTML = `
2280
+ <div class="recording-info">
2281
+ <div style="font-weight: bold; margin-bottom: 4px;">Snapshot ${snapshot.session_id}</div>
2282
+ <div style="color: #666; font-size: 11px;">
2283
+ ${formattedDate}
2284
+ </div>
2285
+ ${noteDisplay}
2286
+ </div>
2287
+ <div class="recording-buttons">
2288
+ <button class="copy-id-btn" data-session-id="${snapshot.session_id}" style="padding: 4px 8px; background: #2196F3; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px;">
2289
+ Copy ID
2290
+ </button>
2291
+ <button class="copy-prompt-btn" data-session-id="${snapshot.session_id}" style="padding: 4px 8px; background: #9C27B0; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px;">
2292
+ Copy Prompt
2293
+ </button>
2294
+ <button class="view-btn" data-session-id="${snapshot.session_id}" style="padding: 4px 8px; background: #4CAF50; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px;">
2295
+ View
2296
+ </button>
2297
+ <button class="delete-btn" data-session-id="${snapshot.session_id}" style="padding: 4px 8px; background: #f44336; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px;">
2298
+ Delete
2299
+ </button>
2300
+ </div>
2301
+ `;
2302
+
2303
+ // Add event listeners
2304
+ const copyIdBtn = snapshotItem.querySelector('.copy-id-btn');
2305
+ const copyPromptBtn = snapshotItem.querySelector('.copy-prompt-btn');
2306
+ const viewBtn = snapshotItem.querySelector('.view-btn');
2307
+ const deleteBtn = snapshotItem.querySelector('.delete-btn');
2308
+
2309
+ copyIdBtn.addEventListener('click', () => {
2310
+ navigator.clipboard.writeText(snapshot.session_id).then(() => {
2311
+ copyIdBtn.textContent = 'Copied!';
2312
+ copyIdBtn.style.background = '#4CAF50';
2313
+ setTimeout(() => {
2314
+ copyIdBtn.textContent = 'Copy ID';
2315
+ copyIdBtn.style.background = '#2196F3';
2316
+ }, 2000);
2317
+ });
2318
+ });
2319
+
2320
+ copyPromptBtn.addEventListener('click', () => {
2321
+ const prompt = `Please analyze this snapshot: ${snapshot.session_id}`;
2322
+ navigator.clipboard.writeText(prompt).then(() => {
2323
+ copyPromptBtn.textContent = 'Copied!';
2324
+ copyPromptBtn.style.background = '#4CAF50';
2325
+ setTimeout(() => {
2326
+ copyPromptBtn.textContent = 'Copy Prompt';
2327
+ copyPromptBtn.style.background = '#9C27B0';
2328
+ }, 2000);
2329
+ });
2330
+ });
2331
+
2332
+ viewBtn.addEventListener('click', async () => {
2333
+ try {
2334
+ // Snapshots are single-frame recordings, so get frame 0
2335
+ const frameResponse = await fetch(`http://localhost:${port}/chromedebug/frame/${snapshot.session_id}/0`);
2336
+ if (frameResponse.ok) {
2337
+ const frameData = await frameResponse.json();
2338
+ const logsCount = frameData.logs?.length || 0;
2339
+ const noteText = snapshot.user_note ? `\nNote: ${snapshot.user_note}` : '';
2340
+ alert(`Snapshot ${snapshot.session_id}${noteText}\n\nConsole Logs: ${logsCount} entries\n\nUse "Copy ID" to share with Claude Code for detailed analysis.`);
2341
+ }
2342
+ } catch (error) {
2343
+ console.error('Failed to load snapshot details:', error);
2344
+ alert('Failed to load snapshot details. Please try again.');
2345
+ }
2346
+ });
2347
+
2348
+ deleteBtn.addEventListener('click', async () => {
2349
+ if (confirm('Are you sure you want to delete this snapshot?')) {
2350
+ try {
2351
+ const deleteResponse = await fetch(`http://localhost:${port}/chromedebug/recording/${snapshot.session_id}`, {
2352
+ method: 'DELETE'
2353
+ });
2354
+
2355
+ if (deleteResponse.ok) {
2356
+ // Reload the list
2357
+ loadSnapshots();
2358
+ }
2359
+ } catch (error) {
2360
+ console.error('Failed to delete snapshot:', error);
2361
+ }
2362
+ }
2363
+ });
2364
+
2365
+ snapshotsContainer.appendChild(snapshotItem);
2366
+ });
2367
+ } else {
2368
+ snapshotsList.style.display = 'none';
2369
+ }
2370
+ break; // Found working server
2371
+ }
2372
+ } catch (error) {
2373
+ console.error(`Failed to load snapshots from port ${port}:`, error);
2374
+ }
2375
+ }
2376
+ }
2377
+ */
2378
+
2379
+ // Listen for storage changes
2380
+ chrome.storage.onChanged.addListener((changes, namespace) => {
2381
+ if (namespace === 'local') {
2382
+ if (changes.recordingActive && !isStoppingRecording) {
2383
+ isRecording = changes.recordingActive.newValue === true;
2384
+ if (changes.recordingStartTime) {
2385
+ recordingStartTime = changes.recordingStartTime.newValue;
2386
+ }
2387
+ updateRecordingUI();
2388
+ }
2389
+ if (changes.workflowRecording) {
2390
+ isWorkflowRecording = changes.workflowRecording.newValue === true;
2391
+ updateWorkflowRecordingUI();
2392
+ }
2393
+ }
2394
+ });
2395
+
2396
+ // Listen for messages from background script
2397
+ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
2398
+ console.log('Popup received message:', request);
2399
+ if (request.action === 'uploadComplete') {
2400
+ console.log('Upload complete with ID:', request.recordingId);
2401
+ const recordingStatus = document.getElementById('recordingStatus');
2402
+ if (recordingStatus) {
2403
+ recordingStatus.innerHTML = `<strong style="color: #4CAF50;">Recording saved!</strong>`;
2404
+ }
2405
+ // Add to recordings list
2406
+ addRecording(request.recordingId);
2407
+ // Clear status after 3 seconds
2408
+ setTimeout(() => {
2409
+ if (recordingStatus) recordingStatus.textContent = '';
2410
+ }, 3000);
2411
+ } else if (request.action === 'sessionComplete') {
2412
+ console.log('Session complete:', request.sessionId, 'with', request.totalChunks, 'chunks');
2413
+ const recordingStatus = document.getElementById('recordingStatus');
2414
+ if (recordingStatus) {
2415
+ recordingStatus.innerHTML = `<strong style="color: #4CAF50;">Recording session saved!</strong><br>` +
2416
+ `<small>${request.totalChunks} chunks (${Math.round(request.duration / 1000)}s)</small>`;
2417
+ }
2418
+ // Add session to recordings list
2419
+ addRecording(request.sessionId, true);
2420
+ // Clear status after 5 seconds
2421
+ setTimeout(() => {
2422
+ if (recordingStatus) recordingStatus.textContent = '';
2423
+ }, 5000);
2424
+ } else if (request.action === 'frameSessionComplete') {
2425
+ console.log('Frame session complete:', request.sessionId, 'with', request.totalFrames, 'frames');
2426
+ const recordingStatus = document.getElementById('recordingStatus');
2427
+ if (recordingStatus) {
2428
+ recordingStatus.innerHTML = `<strong style="color: #4CAF50;">Recording saved!</strong><br>` +
2429
+ `<small>${request.totalFrames} frames (${Math.round(request.duration / 1000)}s)</small>`;
2430
+ }
2431
+ // Add frame session to recordings list
2432
+ addRecording(request.sessionId, false, true, request.serverPort, null, request.sessionName);
2433
+ // Clear status after 5 seconds
2434
+ setTimeout(() => {
2435
+ if (recordingStatus) recordingStatus.textContent = '';
2436
+ }, 5000);
2437
+ } else if (request.action === 'uploadError') {
2438
+ const recordingStatus = document.getElementById('recordingStatus');
2439
+ if (recordingStatus) {
2440
+ recordingStatus.innerHTML = `<strong style="color: #f44336;">Upload error:</strong> ${request.error}`;
2441
+ }
2442
+ setTimeout(() => {
2443
+ if (recordingStatus) recordingStatus.textContent = '';
2444
+ }, 5000);
2445
+ }
2446
+ });