@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.
- package/CLAUDE.md +344 -0
- package/LICENSE +21 -0
- package/README.md +250 -0
- package/chrome-extension/README.md +41 -0
- package/chrome-extension/background.js +3917 -0
- package/chrome-extension/chrome-session-manager.js +706 -0
- package/chrome-extension/content.css +181 -0
- package/chrome-extension/content.js +3022 -0
- package/chrome-extension/data-buffer.js +435 -0
- package/chrome-extension/dom-tracker.js +411 -0
- package/chrome-extension/extension-config.js +78 -0
- package/chrome-extension/firebase-client.js +278 -0
- package/chrome-extension/firebase-config.js +32 -0
- package/chrome-extension/firebase-config.module.js +22 -0
- package/chrome-extension/firebase-config.module.template.js +27 -0
- package/chrome-extension/firebase-config.template.js +36 -0
- package/chrome-extension/frame-capture.js +407 -0
- package/chrome-extension/icon128.png +1 -0
- package/chrome-extension/icon16.png +1 -0
- package/chrome-extension/icon48.png +1 -0
- package/chrome-extension/license-helper.js +181 -0
- package/chrome-extension/logger.js +23 -0
- package/chrome-extension/manifest.json +73 -0
- package/chrome-extension/network-tracker.js +510 -0
- package/chrome-extension/offscreen.html +10 -0
- package/chrome-extension/options.html +203 -0
- package/chrome-extension/options.js +282 -0
- package/chrome-extension/pako.min.js +2 -0
- package/chrome-extension/performance-monitor.js +533 -0
- package/chrome-extension/pii-redactor.js +405 -0
- package/chrome-extension/popup.html +532 -0
- package/chrome-extension/popup.js +2446 -0
- package/chrome-extension/upload-manager.js +323 -0
- package/chrome-extension/web-vitals.iife.js +1 -0
- package/config/api-keys.json +11 -0
- package/config/chrome-pilot-config.json +45 -0
- package/package.json +126 -0
- package/scripts/cleanup-processes.js +109 -0
- package/scripts/config-manager.js +280 -0
- package/scripts/generate-extension-config.js +53 -0
- package/scripts/setup-security.js +64 -0
- package/src/capture/architecture.js +426 -0
- package/src/capture/error-handling-tests.md +38 -0
- package/src/capture/error-handling-types.ts +360 -0
- package/src/capture/index.js +508 -0
- package/src/capture/interfaces.js +625 -0
- package/src/capture/memory-manager.js +713 -0
- package/src/capture/types.js +342 -0
- package/src/chrome-controller.js +2658 -0
- package/src/cli.js +19 -0
- package/src/config-loader.js +303 -0
- package/src/database.js +2178 -0
- package/src/firebase-license-manager.js +462 -0
- package/src/firebase-privacy-guard.js +397 -0
- package/src/http-server.js +1516 -0
- package/src/index-direct.js +157 -0
- package/src/index-modular.js +219 -0
- package/src/index-monolithic-backup.js +2230 -0
- package/src/index.js +305 -0
- package/src/legacy/chrome-controller-old.js +1406 -0
- package/src/legacy/index-express.js +625 -0
- package/src/legacy/index-old.js +977 -0
- package/src/legacy/routes.js +260 -0
- package/src/legacy/shared-storage.js +101 -0
- package/src/logger.js +10 -0
- package/src/mcp/handlers/chrome-tool-handler.js +306 -0
- package/src/mcp/handlers/element-tool-handler.js +51 -0
- package/src/mcp/handlers/frame-tool-handler.js +957 -0
- package/src/mcp/handlers/request-handler.js +104 -0
- package/src/mcp/handlers/workflow-tool-handler.js +636 -0
- package/src/mcp/server.js +68 -0
- package/src/mcp/tools/index.js +701 -0
- package/src/middleware/auth.js +371 -0
- package/src/middleware/security.js +267 -0
- package/src/port-discovery.js +258 -0
- package/src/routes/admin.js +182 -0
- package/src/services/browser-daemon.js +494 -0
- package/src/services/chrome-service.js +375 -0
- package/src/services/failover-manager.js +412 -0
- package/src/services/git-safety-service.js +675 -0
- package/src/services/heartbeat-manager.js +200 -0
- package/src/services/http-client.js +195 -0
- package/src/services/process-manager.js +318 -0
- package/src/services/process-tracker.js +574 -0
- package/src/services/profile-manager.js +449 -0
- package/src/services/project-manager.js +415 -0
- package/src/services/session-manager.js +497 -0
- package/src/services/session-registry.js +491 -0
- package/src/services/unified-session-manager.js +678 -0
- package/src/shared-storage-old.js +267 -0
- package/src/standalone-server.js +53 -0
- package/src/utils/extension-path.js +145 -0
- package/src/utils.js +187 -0
- package/src/validation/log-transformer.js +125 -0
- 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
|
+
});
|