@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,323 @@
1
+ // Upload Manager for Chrome Debug Full Data Recording
2
+ // Handles reliable batch uploads with retry logic and compression
3
+
4
+ // Version will be provided by background.js which imports this file
5
+ console.log('[UploadManager] Loaded version: 2.0.4-BUILD-20250119');
6
+
7
+ // Cleanup any existing stale intervals to prevent multiple instances
8
+ if (typeof window !== 'undefined' && window._chromePilotUploadIntervals) {
9
+ window._chromePilotUploadIntervals.forEach(intervalId => clearInterval(intervalId));
10
+ window._chromePilotUploadIntervals = [];
11
+ } else if (typeof window !== 'undefined') {
12
+ window._chromePilotUploadIntervals = [];
13
+ }
14
+
15
+ class UploadManager {
16
+ constructor(dataBuffer) {
17
+ // Validate dataBuffer parameter
18
+ if (!dataBuffer || typeof dataBuffer !== 'object') {
19
+ throw new Error('[UploadManager] Invalid dataBuffer provided to constructor');
20
+ }
21
+
22
+ this.dataBuffer = dataBuffer;
23
+ this.uploadQueue = [];
24
+ this.isUploading = false;
25
+ this.retryAttempts = 3;
26
+ this.retryDelay = 1000; // Start with 1 second
27
+ this.maxRetryDelay = 30000; // Max 30 seconds
28
+ this.serverUrl = null;
29
+ this.uploadInterval = null;
30
+ this.uploadIntervalTime = 5000; // Check for uploads every 5 seconds
31
+ }
32
+
33
+ async init() {
34
+ // Get server URL from available ports
35
+ this.serverUrl = await this.findAvailableServer();
36
+
37
+ if (!this.serverUrl) {
38
+ console.warn('[UploadManager] No server available, will retry later');
39
+ // Don't start periodic upload if no server is available
40
+ return false;
41
+ }
42
+
43
+ // Only start periodic upload check if server is available
44
+ this.startPeriodicUpload();
45
+
46
+ console.log('[UploadManager] Initialized with server:', this.serverUrl);
47
+ return true;
48
+ }
49
+
50
+ async findAvailableServer() {
51
+ const ports = [3001, 3000, 3002, 3028]; // From config
52
+
53
+ for (const port of ports) {
54
+ try {
55
+ const response = await fetch(`http://localhost:${port}/chromedebug/status`, {
56
+ method: 'GET',
57
+ mode: 'cors'
58
+ });
59
+
60
+ if (response.ok) {
61
+ return `http://localhost:${port}`;
62
+ }
63
+ } catch (error) {
64
+ // Try next port
65
+ continue;
66
+ }
67
+ }
68
+
69
+ return null;
70
+ }
71
+
72
+ startPeriodicUpload() {
73
+ if (this.uploadInterval) {
74
+ clearInterval(this.uploadInterval);
75
+ // Remove from global tracking if it exists
76
+ if (typeof window !== 'undefined' && window._chromePilotUploadIntervals) {
77
+ const index = window._chromePilotUploadIntervals.indexOf(this.uploadInterval);
78
+ if (index > -1) {
79
+ window._chromePilotUploadIntervals.splice(index, 1);
80
+ }
81
+ }
82
+ }
83
+
84
+ this.uploadInterval = setInterval(async () => {
85
+ if (!this.isUploading) {
86
+ await this.processUploadQueue();
87
+ }
88
+ }, this.uploadIntervalTime);
89
+
90
+ // Track interval globally for cleanup
91
+ if (typeof window !== 'undefined') {
92
+ if (!window._chromePilotUploadIntervals) window._chromePilotUploadIntervals = [];
93
+ window._chromePilotUploadIntervals.push(this.uploadInterval);
94
+ }
95
+ }
96
+
97
+ stopPeriodicUpload() {
98
+ if (this.uploadInterval) {
99
+ clearInterval(this.uploadInterval);
100
+ // Remove from global tracking if it exists
101
+ if (typeof window !== 'undefined' && window._chromePilotUploadIntervals) {
102
+ const index = window._chromePilotUploadIntervals.indexOf(this.uploadInterval);
103
+ if (index > -1) {
104
+ window._chromePilotUploadIntervals.splice(index, 1);
105
+ }
106
+ }
107
+ this.uploadInterval = null;
108
+ }
109
+ }
110
+
111
+ async processUploadQueue() {
112
+ // IMMEDIATE defensive check - MUST be first line (v2.0 fix)
113
+ if (!this.dataBuffer || typeof this.dataBuffer.getPendingBatches !== 'function') {
114
+ console.warn('[UploadManager v2.0.3] DataBuffer not ready, skipping');
115
+ return;
116
+ }
117
+
118
+ if (this.isUploading) return;
119
+
120
+ this.isUploading = true;
121
+
122
+ try {
123
+ // Check server availability
124
+ if (!this.serverUrl) {
125
+ this.serverUrl = await this.findAvailableServer();
126
+ if (!this.serverUrl) {
127
+ console.log('[UploadManager] No server available, skipping upload');
128
+ return;
129
+ }
130
+ }
131
+
132
+ // Get pending batches from buffer
133
+ const batches = await this.dataBuffer.getPendingBatches();
134
+
135
+ if (batches.length === 0) {
136
+ return;
137
+ }
138
+
139
+ console.log(`[UploadManager] Processing ${batches.length} pending batches`);
140
+
141
+ for (const batch of batches) {
142
+ const success = await this.uploadBatch(batch);
143
+
144
+ if (success) {
145
+ await this.dataBuffer.markBatchUploaded(batch.id);
146
+ console.log(`[UploadManager] Batch ${batch.id} uploaded successfully`);
147
+ } else {
148
+ console.warn(`[UploadManager] Failed to upload batch ${batch.id}`);
149
+ }
150
+ }
151
+ } catch (error) {
152
+ console.error('[UploadManager] Error processing upload queue:', error);
153
+ } finally {
154
+ this.isUploading = false;
155
+ }
156
+ }
157
+
158
+ async uploadBatch(batch, attemptNumber = 1) {
159
+ try {
160
+ // Prepare the data
161
+ let data = batch.events;
162
+ let contentType = 'application/json';
163
+ let body;
164
+
165
+ if (batch.compressed) {
166
+ // Data is compressed, send as binary
167
+ const uint8Array = new Uint8Array(data);
168
+ body = uint8Array.buffer;
169
+ contentType = 'application/octet-stream';
170
+ } else {
171
+ // Data is not compressed, send as JSON
172
+ body = JSON.stringify({
173
+ recording_id: batch.recording_id,
174
+ timestamp: batch.timestamp,
175
+ events: data
176
+ });
177
+ }
178
+
179
+ // Determine endpoint based on data type
180
+ const endpoint = this.getEndpointForBatch(batch);
181
+
182
+ const response = await fetch(`${this.serverUrl}${endpoint}`, {
183
+ method: 'POST',
184
+ headers: {
185
+ 'Content-Type': contentType,
186
+ 'X-Recording-ID': batch.recording_id,
187
+ 'X-Batch-ID': batch.id.toString(),
188
+ 'X-Compressed': batch.compressed ? 'true' : 'false'
189
+ },
190
+ body: body
191
+ });
192
+
193
+ if (response.ok) {
194
+ return true;
195
+ }
196
+
197
+ // Handle specific error codes
198
+ if (response.status === 413) {
199
+ // Payload too large, need to split the batch
200
+ console.warn('[UploadManager] Batch too large, splitting...');
201
+ return await this.splitAndUploadBatch(batch);
202
+ }
203
+
204
+ if (response.status >= 500 && attemptNumber < this.retryAttempts) {
205
+ // Server error, retry with exponential backoff
206
+ const delay = Math.min(this.retryDelay * Math.pow(2, attemptNumber - 1), this.maxRetryDelay);
207
+ console.log(`[UploadManager] Server error, retrying in ${delay}ms...`);
208
+ await this.sleep(delay);
209
+ return await this.uploadBatch(batch, attemptNumber + 1);
210
+ }
211
+
212
+ return false;
213
+ } catch (error) {
214
+ console.error('[UploadManager] Upload error:', error);
215
+
216
+ if (attemptNumber < this.retryAttempts) {
217
+ const delay = Math.min(this.retryDelay * Math.pow(2, attemptNumber - 1), this.maxRetryDelay);
218
+ console.log(`[UploadManager] Network error, retrying in ${delay}ms...`);
219
+ await this.sleep(delay);
220
+ return await this.uploadBatch(batch, attemptNumber + 1);
221
+ }
222
+
223
+ return false;
224
+ }
225
+ }
226
+
227
+ async splitAndUploadBatch(batch) {
228
+ // Split large batch into smaller chunks
229
+ const events = batch.compressed ?
230
+ JSON.parse(pako.ungzip(new Uint8Array(batch.events), { to: 'string' })) :
231
+ batch.events;
232
+
233
+ const chunkSize = Math.ceil(events.length / 2);
234
+ const chunks = [];
235
+
236
+ for (let i = 0; i < events.length; i += chunkSize) {
237
+ chunks.push(events.slice(i, i + chunkSize));
238
+ }
239
+
240
+ let allSuccess = true;
241
+
242
+ for (const chunk of chunks) {
243
+ const subBatch = {
244
+ ...batch,
245
+ events: chunk,
246
+ compressed: false // Send chunks uncompressed for simplicity
247
+ };
248
+
249
+ // Compress the chunk if pako is available
250
+ if (typeof pako !== 'undefined') {
251
+ try {
252
+ const jsonString = JSON.stringify(chunk);
253
+ const compressed = pako.gzip(jsonString);
254
+ subBatch.events = Array.from(compressed);
255
+ subBatch.compressed = true;
256
+ } catch (error) {
257
+ console.error('[UploadManager] Compression failed for chunk:', error);
258
+ }
259
+ }
260
+
261
+ const success = await this.uploadBatch(subBatch);
262
+ if (!success) {
263
+ allSuccess = false;
264
+ }
265
+ }
266
+
267
+ return allSuccess;
268
+ }
269
+
270
+ getEndpointForBatch(batch) {
271
+ // All batch uploads go to the same endpoint
272
+ // The server will handle different event types internally
273
+ return '/chromedebug/upload/batch';
274
+ }
275
+
276
+ async queueForUpload(recordingId, events) {
277
+ // Add events to buffer and trigger upload if threshold reached
278
+ await this.dataBuffer.addBatch(events);
279
+
280
+ // Check if we should create a batch
281
+ const stats = await this.dataBuffer.getStats();
282
+ if (stats.eventCount >= this.dataBuffer.batchSize) {
283
+ const batch = await this.dataBuffer.createBatch(recordingId);
284
+ if (batch && !this.isUploading) {
285
+ // Trigger immediate upload for important batches
286
+ this.processUploadQueue();
287
+ }
288
+ }
289
+ }
290
+
291
+ async forceUpload(recordingId) {
292
+ // Force creation and upload of all pending data for a recording
293
+ const batch = await this.dataBuffer.createBatch(recordingId);
294
+ if (batch) {
295
+ await this.processUploadQueue();
296
+ }
297
+ }
298
+
299
+ sleep(ms) {
300
+ return new Promise(resolve => setTimeout(resolve, ms));
301
+ }
302
+
303
+ async getStats() {
304
+ const bufferStats = await this.dataBuffer.getStats();
305
+ return {
306
+ ...bufferStats,
307
+ serverUrl: this.serverUrl,
308
+ isUploading: this.isUploading,
309
+ uploadQueueLength: this.uploadQueue.length
310
+ };
311
+ }
312
+
313
+ destroy() {
314
+ this.stopPeriodicUpload();
315
+ this.uploadQueue = [];
316
+ this.isUploading = false;
317
+ }
318
+ }
319
+
320
+ // Export for use in background script
321
+ if (typeof module !== 'undefined' && module.exports) {
322
+ module.exports = UploadManager;
323
+ }
@@ -0,0 +1 @@
1
+ var webVitals=function(e){"use strict";let t=-1;const n=e=>{addEventListener("pageshow",(n=>{n.persisted&&(t=n.timeStamp,e(n))}),!0)},i=(e,t,n,i)=>{let s,r;return o=>{t.value>=0&&(o||i)&&(r=t.value-(s??0),(r||void 0===s)&&(s=t.value,t.delta=r,t.rating=((e,t)=>e>t[1]?"poor":e>t[0]?"needs-improvement":"good")(t.value,n),e(t)))}},s=e=>{requestAnimationFrame((()=>requestAnimationFrame((()=>e()))))},r=()=>{const e=performance.getEntriesByType("navigation")[0];if(e&&e.responseStart>0&&e.responseStart<performance.now())return e},o=()=>{const e=r();return e?.activationStart??0},c=(e,n=-1)=>{const i=r();let s="navigate";t>=0?s="back-forward-cache":i&&(document.prerendering||o()>0?s="prerender":document.wasDiscarded?s="restore":i.type&&(s=i.type.replace(/_/g,"-")));return{name:e,value:n,rating:"good",delta:0,entries:[],id:`v5-${Date.now()}-${Math.floor(8999999999999*Math.random())+1e12}`,navigationType:s}},a=new WeakMap;function d(e,t){return a.get(e)||a.set(e,new t),a.get(e)}class h{t;i=0;o=[];h(e){if(e.hadRecentInput)return;const t=this.o[0],n=this.o.at(-1);this.i&&t&&n&&e.startTime-n.startTime<1e3&&e.startTime-t.startTime<5e3?(this.i+=e.value,this.o.push(e)):(this.i=e.value,this.o=[e]),this.t?.(e)}}const f=(e,t,n={})=>{try{if(PerformanceObserver.supportedEntryTypes.includes(e)){const i=new PerformanceObserver((e=>{Promise.resolve().then((()=>{t(e.getEntries())}))}));return i.observe({type:e,buffered:!0,...n}),i}}catch{}},u=e=>{let t=!1;return()=>{t||(e(),t=!0)}};let l=-1;const m=new Set,v=()=>"hidden"!==document.visibilityState||document.prerendering?1/0:0,p=e=>{if("hidden"===document.visibilityState){if("visibilitychange"===e.type)for(const e of m)e();isFinite(l)||(l="visibilitychange"===e.type?e.timeStamp:0,removeEventListener("prerenderingchange",p,!0))}},g=()=>{if(l<0){const e=o(),t=document.prerendering?void 0:globalThis.performance.getEntriesByType("visibility-state").filter((t=>"hidden"===t.name&&t.startTime>e))[0]?.startTime;l=t??v(),addEventListener("visibilitychange",p,!0),addEventListener("prerenderingchange",p,!0),n((()=>{setTimeout((()=>{l=v()}))}))}return{get firstHiddenTime(){return l},onHidden(e){m.add(e)}}},y=e=>{document.prerendering?addEventListener("prerenderingchange",(()=>e()),!0):e()},b=[1800,3e3],E=(e,t={})=>{y((()=>{const r=g();let a,d=c("FCP");const h=f("paint",(e=>{for(const t of e)"first-contentful-paint"===t.name&&(h.disconnect(),t.startTime<r.firstHiddenTime&&(d.value=Math.max(t.startTime-o(),0),d.entries.push(t),a(!0)))}));h&&(a=i(e,d,b,t.reportAllChanges),n((n=>{d=c("FCP"),a=i(e,d,b,t.reportAllChanges),s((()=>{d.value=performance.now()-n.timeStamp,a(!0)}))})))}))},L=[.1,.25];let P=0,T=1/0,_=0;const M=e=>{for(const t of e)t.interactionId&&(T=Math.min(T,t.interactionId),_=Math.max(_,t.interactionId),P=_?(_-T)/7+1:0)};let w;const C=()=>w?P:performance.interactionCount??0,I=()=>{"interactionCount"in performance||w||(w=f("event",M,{type:"event",buffered:!0,durationThreshold:0}))};let F=0;class k{u=[];l=new Map;m;v;p(){F=C(),this.u.length=0,this.l.clear()}L(){const e=Math.min(this.u.length-1,Math.floor((C()-F)/50));return this.u[e]}h(e){if(this.m?.(e),!e.interactionId&&"first-input"!==e.entryType)return;const t=this.u.at(-1);let n=this.l.get(e.interactionId);if(n||this.u.length<10||e.duration>t.P){if(n?e.duration>n.P?(n.entries=[e],n.P=e.duration):e.duration===n.P&&e.startTime===n.entries[0].startTime&&n.entries.push(e):(n={id:e.interactionId,entries:[e],P:e.duration},this.l.set(n.id,n),this.u.push(n)),this.u.sort(((e,t)=>t.P-e.P)),this.u.length>10){const e=this.u.splice(10);for(const t of e)this.l.delete(t.id)}this.v?.(n)}}}const A=e=>{const t=globalThis.requestIdleCallback||setTimeout;"hidden"===document.visibilityState?e():(e=u(e),addEventListener("visibilitychange",e,{once:!0,capture:!0}),t((()=>{e(),removeEventListener("visibilitychange",e,{capture:!0})})))},B=[200,500];class S{m;h(e){this.m?.(e)}}const N=[2500,4e3],q=[800,1800],H=e=>{document.prerendering?y((()=>H(e))):"complete"!==document.readyState?addEventListener("load",(()=>H(e)),!0):setTimeout(e)};return e.CLSThresholds=L,e.FCPThresholds=b,e.INPThresholds=B,e.LCPThresholds=N,e.TTFBThresholds=q,e.onCLS=(e,t={})=>{const r=g();E(u((()=>{let o,a=c("CLS",0);const u=d(t,h),l=e=>{for(const t of e)u.h(t);u.i>a.value&&(a.value=u.i,a.entries=u.o,o())},m=f("layout-shift",l);m&&(o=i(e,a,L,t.reportAllChanges),r.onHidden((()=>{l(m.takeRecords()),o(!0)})),n((()=>{u.i=0,a=c("CLS",0),o=i(e,a,L,t.reportAllChanges),s((()=>o()))})),setTimeout(o))})))},e.onFCP=E,e.onINP=(e,t={})=>{if(!globalThis.PerformanceEventTiming||!("interactionId"in PerformanceEventTiming.prototype))return;const s=g();y((()=>{I();let r,o=c("INP");const a=d(t,k),h=e=>{A((()=>{for(const t of e)a.h(t);const t=a.L();t&&t.P!==o.value&&(o.value=t.P,o.entries=t.entries,r())}))},u=f("event",h,{durationThreshold:t.durationThreshold??40});r=i(e,o,B,t.reportAllChanges),u&&(u.observe({type:"first-input",buffered:!0}),s.onHidden((()=>{h(u.takeRecords()),r(!0)})),n((()=>{a.p(),o=c("INP"),r=i(e,o,B,t.reportAllChanges)})))}))},e.onLCP=(e,t={})=>{y((()=>{const r=g();let a,h=c("LCP");const l=d(t,S),m=e=>{t.reportAllChanges||(e=e.slice(-1));for(const t of e)l.h(t),t.startTime<r.firstHiddenTime&&(h.value=Math.max(t.startTime-o(),0),h.entries=[t],a())},v=f("largest-contentful-paint",m);if(v){a=i(e,h,N,t.reportAllChanges);const r=u((()=>{m(v.takeRecords()),v.disconnect(),a(!0)})),o=e=>{e.isTrusted&&(A(r),removeEventListener(e.type,o,{capture:!0}))};for(const e of["keydown","click","visibilitychange"])addEventListener(e,o,{capture:!0});n((n=>{h=c("LCP"),a=i(e,h,N,t.reportAllChanges),s((()=>{h.value=performance.now()-n.timeStamp,a(!0)}))}))}}))},e.onTTFB=(e,t={})=>{let s=c("TTFB"),a=i(e,s,q,t.reportAllChanges);H((()=>{const d=r();d&&(s.value=Math.max(d.responseStart-o(),0),s.entries=[d],a(!0),n((()=>{s=c("TTFB",0),a=i(e,s,q,t.reportAllChanges),a(!0)})))}))},e}({});
@@ -0,0 +1,11 @@
1
+ [
2
+ {
3
+ "id": "c8d93006-6483-494f-865f-f8bb8fba6e02",
4
+ "name": "Default Admin",
5
+ "role": "admin",
6
+ "hashedKey": "$2b$12$MNv6YBqYw1JumuiPr.1VxefkIgbzlEDJOm6T3BPlgWySGR820TqYy",
7
+ "createdAt": "2025-10-14T20:38:14.583Z",
8
+ "lastUsed": null,
9
+ "active": true
10
+ }
11
+ ]
@@ -0,0 +1,45 @@
1
+ {
2
+ "serverPorts": {
3
+ "httpServer": {
4
+ "preferredPorts": [
5
+ 3000,
6
+ 3001,
7
+ 3002,
8
+ 3028,
9
+ 3029,
10
+ 3030
11
+ ],
12
+ "description": "Ports to try for HTTP server discovery, in priority order"
13
+ },
14
+ "mcpServer": {
15
+ "preferredPorts": [
16
+ 3028,
17
+ 3029,
18
+ 3030,
19
+ 3031,
20
+ 3032,
21
+ 3033
22
+ ],
23
+ "description": "Ports to try for MCP server discovery, in priority order"
24
+ },
25
+ "discoveryRange": {
26
+ "start": 3000,
27
+ "end": 3033,
28
+ "description": "Port range for automatic discovery when preferred ports are not available"
29
+ }
30
+ },
31
+ "chromeExtension": {
32
+ "extensionPath": null,
33
+ "extensionPathDescription": "Optional: Custom path to ChromeDebug extension. If null, will search standard locations. Can also be set via CHROMEDEBUG_EXTENSION_PATH environment variable.",
34
+ "serverDiscovery": {
35
+ "timeoutMs": 3000,
36
+ "description": "Timeout for server discovery requests in milliseconds"
37
+ },
38
+ "portScanOrder": "httpFirst",
39
+ "description": "Order to try ports: 'httpFirst' tries HTTP ports first, 'mcpFirst' tries MCP ports first, 'sequential' tries all ports in numerical order"
40
+ },
41
+ "logging": {
42
+ "level": "info",
43
+ "description": "Logging level: debug, info, warn, error"
44
+ }
45
+ }
package/package.json ADDED
@@ -0,0 +1,126 @@
1
+ {
2
+ "name": "@dynamicu/chromedebug-mcp",
3
+ "version": "2.2.0",
4
+ "description": "ChromeDebug MCP - MCP server that provides full control over a Chrome browser instance for debugging and automation with AI assistants like Claude Code",
5
+ "main": "src/index.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "chromedebug-mcp": "./src/cli.js"
9
+ },
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/dynamicupgrade/ChromePilot.git"
16
+ },
17
+ "bugs": {
18
+ "url": "https://github.com/dynamicupgrade/ChromePilot/issues"
19
+ },
20
+ "homepage": "https://github.com/dynamicupgrade/ChromePilot#readme",
21
+ "scripts": {
22
+ "cleanup": "node scripts/cleanup-processes.js",
23
+ "cleanup-force": "node scripts/cleanup-processes.js --force",
24
+ "start": "npm run cleanup && node src/index.js",
25
+ "go": "NODE_ENV=development npm start",
26
+ "single-server": "npm run cleanup && node src/index.js --single-server",
27
+ "dev": "npm run cleanup && node --watch src/index.js",
28
+ "start-http": "npm run cleanup && node src/http-server.js",
29
+ "server": "npm run cleanup && node src/standalone-server.js",
30
+ "server-only": "npm run cleanup && node src/standalone-server.js",
31
+ "start-legacy": "node scripts/start.js",
32
+ "update-extension-config": "node scripts/generate-extension-config.js",
33
+ "setup-security": "node scripts/setup-security.js",
34
+ "config": "node scripts/config-manager.js",
35
+ "config-show": "node scripts/config-manager.js show",
36
+ "config-ports": "node scripts/config-manager.js list-ports",
37
+ "postinstall": "npm run update-extension-config",
38
+ "test": "NODE_OPTIONS='--experimental-vm-modules' jest",
39
+ "test:watch": "NODE_OPTIONS='--experimental-vm-modules' jest --watch",
40
+ "test:coverage": "NODE_OPTIONS='--experimental-vm-modules' jest --coverage",
41
+ "test:unit": "NODE_OPTIONS='--experimental-vm-modules' jest --testPathPattern='(?<!integration)\\.test\\.js$'",
42
+ "test:integration": "NODE_OPTIONS='--experimental-vm-modules' jest --testPathPattern='integration\\.test\\.js$'",
43
+ "test:chrome": "NODE_OPTIONS='--experimental-vm-modules' jest tests/chrome-controller.test.js",
44
+ "test:http": "NODE_OPTIONS='--experimental-vm-modules' jest tests/http-server.test.js",
45
+ "test:db": "NODE_OPTIONS='--experimental-vm-modules' jest tests/database.test.js",
46
+ "test:dual-id-fix": "NODE_OPTIONS='--experimental-vm-modules' jest tests/dual-recording-id.test.js --testNamePattern='Dual Recording ID Fix Verification'",
47
+ "test:recording": "NODE_OPTIONS='--experimental-vm-modules' jest tests/recording-system-comprehensive.test.js",
48
+ "test:extension": "NODE_OPTIONS='--experimental-vm-modules' jest tests/chrome-extension-recording.test.js",
49
+ "test:migration": "NODE_OPTIONS='--experimental-vm-modules' jest tests/dual-id-migration.test.js",
50
+ "test:regression": "NODE_OPTIONS='--experimental-vm-modules' jest tests/recording-regression.test.js",
51
+ "test:recording-all": "NODE_OPTIONS='--experimental-vm-modules' jest tests/recording-system-comprehensive.test.js tests/chrome-extension-recording.test.js tests/dual-id-migration.test.js tests/recording-regression.test.js tests/dual-recording-id.test.js",
52
+ "run-migration": "node migrate-dual-ids.js",
53
+ "test:mcp": "NODE_OPTIONS='--experimental-vm-modules' jest tests/mcp-server.test.js",
54
+ "tbv:microtest": "cd chrome-extension && npm run tbv:microtest",
55
+ "build:free": "webpack --config webpack.config.free.cjs",
56
+ "build:pro": "webpack --config webpack.config.pro.cjs",
57
+ "build:both": "npm run build:free && npm run build:pro"
58
+ },
59
+ "keywords": [
60
+ "mcp",
61
+ "model-context-protocol",
62
+ "chrome",
63
+ "debugger",
64
+ "puppeteer",
65
+ "automation",
66
+ "browser-automation",
67
+ "ai-assistant",
68
+ "claude-code",
69
+ "debugging",
70
+ "testing",
71
+ "screen-recording",
72
+ "developer-tools"
73
+ ],
74
+ "author": "dynamicupgrade",
75
+ "license": "MIT",
76
+ "engines": {
77
+ "node": ">=18.0.0",
78
+ "npm": ">=8.0.0"
79
+ },
80
+ "files": [
81
+ "src/",
82
+ "chrome-extension/*.js",
83
+ "!chrome-extension/test-*.js",
84
+ "!chrome-extension/jest.config.js",
85
+ "chrome-extension/*.css",
86
+ "chrome-extension/*.html",
87
+ "chrome-extension/*.png",
88
+ "chrome-extension/manifest.json",
89
+ "chrome-extension/README.md",
90
+ "config/",
91
+ "scripts/",
92
+ "README.md",
93
+ "LICENSE",
94
+ "CLAUDE.md"
95
+ ],
96
+ "dependencies": {
97
+ "@modelcontextprotocol/sdk": "^1.0.4",
98
+ "bcrypt": "^6.0.0",
99
+ "better-sqlite3": "^12.2.0",
100
+ "cors": "^2.8.5",
101
+ "express": "^4.18.2",
102
+ "express-rate-limit": "^8.0.1",
103
+ "express-validator": "^7.2.1",
104
+ "firebase-admin": "^12.7.0",
105
+ "glob": "^11.0.0",
106
+ "helmet": "^8.1.0",
107
+ "joi": "^18.0.0",
108
+ "jsonwebtoken": "^9.0.2",
109
+ "multer": "^2.0.2",
110
+ "puppeteer": "^23.10.0",
111
+ "uuid": "^11.1.0",
112
+ "web-vitals": "^5.1.0",
113
+ "ws": "^8.16.0"
114
+ },
115
+ "devDependencies": {
116
+ "@jest/globals": "^30.0.5",
117
+ "copy-webpack-plugin": "^13.0.1",
118
+ "jest": "^30.0.5",
119
+ "jsdom": "^27.0.0",
120
+ "puppeteer-core": "^24.16.0",
121
+ "sqlite3": "^5.1.7",
122
+ "supertest": "^7.1.4",
123
+ "webpack": "^5.102.1",
124
+ "webpack-cli": "^6.0.1"
125
+ }
126
+ }
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Cleanup ChromeDebug MCP processes safely
5
+ * Only kills processes that were explicitly registered by our application
6
+ */
7
+
8
+ import {
9
+ killAllRegisteredProcesses,
10
+ cleanupDeadProcesses,
11
+ getRegisteredProcesses,
12
+ findUntrackedChromePilotProcesses,
13
+ killUntrackedChromePilotProcesses
14
+ } from '../src/services/process-tracker.js';
15
+
16
+ async function main() {
17
+ // Parse command line arguments
18
+ const args = process.argv.slice(2);
19
+ const forceCleanup = args.includes('--force') || args.includes('-f');
20
+
21
+ console.log('ChromeDebug MCP Process Cleanup');
22
+ console.log('================================');
23
+ if (forceCleanup) {
24
+ console.log('⚠️ FORCE MODE: Will also clean up untracked ChromeDebug processes');
25
+ }
26
+
27
+ // First clean up any dead processes from tracking
28
+ const removedCount = cleanupDeadProcesses();
29
+ if (removedCount > 0) {
30
+ console.log(`Removed ${removedCount} dead processes from tracking`);
31
+ }
32
+
33
+ // Get current registered processes
34
+ const processes = getRegisteredProcesses();
35
+ let totalKilled = 0;
36
+ let totalFailed = 0;
37
+ let allKilledPids = [];
38
+
39
+ // Kill registered processes
40
+ if (processes.length > 0) {
41
+ console.log(`\nFound ${processes.length} registered ChromeDebug processes:`);
42
+ processes.forEach(proc => {
43
+ console.log(` - PID ${proc.pid} (${proc.type}) started ${proc.startTime}`);
44
+ });
45
+
46
+ const results = await killAllRegisteredProcesses();
47
+ totalKilled += results.killed.length;
48
+ totalFailed += results.failed.length;
49
+ allKilledPids.push(...results.killed);
50
+
51
+ console.log('\nRegistered Process Cleanup:');
52
+ console.log(`Successfully killed: ${results.killed.length}`);
53
+ if (results.failed.length > 0) {
54
+ console.log(`Failed to kill: ${results.failed.length} - PIDs: ${results.failed.join(', ')}`);
55
+ }
56
+ } else {
57
+ console.log('\nNo registered ChromeDebug processes found');
58
+ }
59
+
60
+ // Handle untracked processes if --force flag is used
61
+ if (forceCleanup) {
62
+ console.log('\n🔍 Searching for untracked ChromeDebug processes...');
63
+ const untrackedResults = await killUntrackedChromePilotProcesses();
64
+
65
+ if (untrackedResults.total > 0) {
66
+ totalKilled += untrackedResults.killed.length;
67
+ totalFailed += untrackedResults.failed.length;
68
+ allKilledPids.push(...untrackedResults.killed);
69
+
70
+ console.log('\nUntracked Process Cleanup:');
71
+ console.log(`Successfully killed: ${untrackedResults.killed.length}`);
72
+ if (untrackedResults.failed.length > 0) {
73
+ console.log(`Failed to kill: ${untrackedResults.failed.length} - PIDs: ${untrackedResults.failed.join(', ')}`);
74
+ }
75
+ }
76
+ } else {
77
+ // Just show what untracked processes would be found
78
+ const untrackedProcesses = await findUntrackedChromePilotProcesses();
79
+ if (untrackedProcesses.length > 0) {
80
+ console.log(`\n💡 Found ${untrackedProcesses.length} untracked ChromeDebug processes:`);
81
+ untrackedProcesses.forEach(proc => {
82
+ console.log(` - ${proc.description}`);
83
+ });
84
+ console.log('\n Use --force flag to clean up untracked processes too');
85
+ }
86
+ }
87
+
88
+ // Final summary
89
+ console.log('\n' + '='.repeat(50));
90
+ console.log('Final Cleanup Results:');
91
+ console.log(`Total processes killed: ${totalKilled}`);
92
+ console.log(`Total failures: ${totalFailed}`);
93
+
94
+ if (allKilledPids.length > 0) {
95
+ console.log(`All killed PIDs: ${allKilledPids.join(', ')}`);
96
+ }
97
+
98
+ if (totalFailed > 0) {
99
+ console.log('\n⚠️ Some processes could not be killed. Check permissions or process status.');
100
+ process.exit(1);
101
+ }
102
+
103
+ console.log('\n✅ Cleanup completed successfully');
104
+ }
105
+
106
+ main().catch(error => {
107
+ console.error('Cleanup failed:', error);
108
+ process.exit(1);
109
+ });