claude-mpm 4.2.42__py3-none-any.whl → 4.2.44__py3-none-any.whl
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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/BASE_PM.md +75 -12
- claude_mpm/agents/INSTRUCTIONS.md +80 -10
- claude_mpm/core/api_validator.py +330 -0
- claude_mpm/core/framework_loader.py +22 -0
- claude_mpm/dashboard/static/js/components/code-viewer.js +88 -13
- claude_mpm/dashboard/static/js/components/ui-state-manager.js +1 -174
- claude_mpm/dashboard/templates/index.html +3 -27
- claude_mpm/services/monitor/server.py +0 -226
- {claude_mpm-4.2.42.dist-info → claude_mpm-4.2.44.dist-info}/METADATA +1 -1
- {claude_mpm-4.2.42.dist-info → claude_mpm-4.2.44.dist-info}/RECORD +15 -19
- claude_mpm/commands/mpm-browser-monitor.md +0 -370
- claude_mpm/commands/mpm-monitor.md +0 -177
- claude_mpm/dashboard/static/js/browser-console-monitor.js +0 -495
- claude_mpm/dashboard/static/js/components/browser-log-viewer.js +0 -763
- claude_mpm/services/monitor/handlers/browser.py +0 -451
- {claude_mpm-4.2.42.dist-info → claude_mpm-4.2.44.dist-info}/WHEEL +0 -0
- {claude_mpm-4.2.42.dist-info → claude_mpm-4.2.44.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.2.42.dist-info → claude_mpm-4.2.44.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.2.42.dist-info → claude_mpm-4.2.44.dist-info}/top_level.txt +0 -0
@@ -388,7 +388,9 @@ class CodeViewer {
|
|
388
388
|
if (window.socket) {
|
389
389
|
window.socket.on('claude_event', (event) => {
|
390
390
|
console.log('[CodeViewer] Received claude_event:', event);
|
391
|
-
|
391
|
+
|
392
|
+
// Process both hook events and direct file operation events
|
393
|
+
if (this.isFileOperationEvent(event) || this.isDirectFileEvent(event)) {
|
392
394
|
this.processClaudeEvent(event);
|
393
395
|
// Only update if the File Tree tab is active
|
394
396
|
if (this.isTabActive()) {
|
@@ -398,13 +400,31 @@ class CodeViewer {
|
|
398
400
|
}
|
399
401
|
}
|
400
402
|
});
|
403
|
+
|
404
|
+
// Also listen for specific file events
|
405
|
+
window.socket.on('file:read', (data) => {
|
406
|
+
console.log('[CodeViewer] Received file:read event:', data);
|
407
|
+
this.handleDirectFileEvent('Read', data);
|
408
|
+
});
|
409
|
+
|
410
|
+
window.socket.on('file:write', (data) => {
|
411
|
+
console.log('[CodeViewer] Received file:write event:', data);
|
412
|
+
this.handleDirectFileEvent('Write', data);
|
413
|
+
});
|
414
|
+
|
415
|
+
window.socket.on('file:edit', (data) => {
|
416
|
+
console.log('[CodeViewer] Received file:edit event:', data);
|
417
|
+
this.handleDirectFileEvent('Edit', data);
|
418
|
+
});
|
401
419
|
}
|
402
420
|
|
403
421
|
// Listen for events from event bus
|
404
422
|
if (window.eventBus) {
|
405
423
|
window.eventBus.on('claude_event', (event) => {
|
406
424
|
console.log('[CodeViewer] Received claude_event from eventBus:', event);
|
407
|
-
|
425
|
+
|
426
|
+
// Process both hook events and direct file operation events
|
427
|
+
if (this.isFileOperationEvent(event) || this.isDirectFileEvent(event)) {
|
408
428
|
this.processClaudeEvent(event);
|
409
429
|
// Only update if the File Tree tab is active
|
410
430
|
if (this.isTabActive()) {
|
@@ -452,6 +472,46 @@ class CodeViewer {
|
|
452
472
|
}
|
453
473
|
return false;
|
454
474
|
}
|
475
|
+
|
476
|
+
/**
|
477
|
+
* Check if an event is a direct file event (not wrapped in hook)
|
478
|
+
*/
|
479
|
+
isDirectFileEvent(event) {
|
480
|
+
// Check if this is a direct file operation event
|
481
|
+
if (event.type === 'file_operation' ||
|
482
|
+
(event.tool && ['Read', 'Write', 'Edit', 'MultiEdit', 'NotebookEdit'].includes(event.tool))) {
|
483
|
+
return true;
|
484
|
+
}
|
485
|
+
return false;
|
486
|
+
}
|
487
|
+
|
488
|
+
/**
|
489
|
+
* Handle direct file events from WebSocket
|
490
|
+
*/
|
491
|
+
handleDirectFileEvent(tool_name, data) {
|
492
|
+
const event = {
|
493
|
+
type: 'file_operation',
|
494
|
+
tool: tool_name,
|
495
|
+
data: {
|
496
|
+
tool_name: tool_name,
|
497
|
+
tool_parameters: data.parameters || data,
|
498
|
+
tool_output: data.output || null,
|
499
|
+
session_id: data.session_id || this.currentSession,
|
500
|
+
working_directory: data.working_directory || '/'
|
501
|
+
},
|
502
|
+
timestamp: data.timestamp || new Date().toISOString()
|
503
|
+
};
|
504
|
+
|
505
|
+
console.log('[CodeViewer] Processing direct file event:', event);
|
506
|
+
this.processClaudeEvent(event);
|
507
|
+
|
508
|
+
// Only update if the File Tree tab is active
|
509
|
+
if (this.isTabActive()) {
|
510
|
+
this.buildTreeData();
|
511
|
+
this.renderTree();
|
512
|
+
this.updateStats();
|
513
|
+
}
|
514
|
+
}
|
455
515
|
|
456
516
|
/**
|
457
517
|
* Check if an event is a file operation (legacy format)
|
@@ -465,18 +525,33 @@ class CodeViewer {
|
|
465
525
|
* Process a claude event with file operation
|
466
526
|
*/
|
467
527
|
processClaudeEvent(event) {
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
528
|
+
// Handle both hook events and direct file events
|
529
|
+
if (!this.isFileOperationEvent(event) && !this.isDirectFileEvent(event)) return;
|
530
|
+
|
531
|
+
let tool_name, tool_parameters, tool_output, timestamp, session_id, working_directory, filePath;
|
532
|
+
|
533
|
+
// Extract data based on event format
|
534
|
+
if (this.isFileOperationEvent(event)) {
|
535
|
+
// Hook event format
|
536
|
+
const data = event.data || {};
|
537
|
+
tool_name = data.tool_name;
|
538
|
+
tool_parameters = data.tool_parameters || {};
|
539
|
+
tool_output = data.tool_output;
|
540
|
+
timestamp = event.timestamp || new Date().toISOString();
|
541
|
+
session_id = event.session_id || data.session_id;
|
542
|
+
working_directory = data.working_directory || '/';
|
543
|
+
} else if (this.isDirectFileEvent(event)) {
|
544
|
+
// Direct file event format
|
545
|
+
const data = event.data || event;
|
546
|
+
tool_name = event.tool || data.tool_name;
|
547
|
+
tool_parameters = data.tool_parameters || data.parameters || {};
|
548
|
+
tool_output = data.tool_output || data.output;
|
549
|
+
timestamp = event.timestamp || data.timestamp || new Date().toISOString();
|
550
|
+
session_id = event.session_id || data.session_id;
|
551
|
+
working_directory = data.working_directory || '/';
|
552
|
+
}
|
478
553
|
|
479
|
-
|
554
|
+
filePath = tool_parameters.file_path || tool_parameters.notebook_path;
|
480
555
|
|
481
556
|
console.log('[CodeViewer] Processing file operation:', tool_name, filePath);
|
482
557
|
|
@@ -22,7 +22,6 @@ class UIStateManager {
|
|
22
22
|
'#files': 'files',
|
23
23
|
'#activity': 'activity',
|
24
24
|
'#file_tree': 'claude-tree',
|
25
|
-
'#browser_logs': 'browser-logs',
|
26
25
|
'': 'events', // default
|
27
26
|
};
|
28
27
|
|
@@ -33,8 +32,7 @@ class UIStateManager {
|
|
33
32
|
'tools': '#tools',
|
34
33
|
'files': '#files',
|
35
34
|
'activity': '#activity',
|
36
|
-
'claude-tree': '#file_tree'
|
37
|
-
'browser-logs': '#browser_logs'
|
35
|
+
'claude-tree': '#file_tree'
|
38
36
|
};
|
39
37
|
|
40
38
|
// Current active tab - will be set based on URL hash
|
@@ -157,7 +155,6 @@ class UIStateManager {
|
|
157
155
|
if (text.includes('activity')) return 'activity';
|
158
156
|
if (text.includes('agents')) return 'agents';
|
159
157
|
if (text.includes('tools')) return 'tools';
|
160
|
-
if (text.includes('browser')) return 'browser-logs'; // Added browser logs support
|
161
158
|
if (text.includes('files')) return 'files';
|
162
159
|
if (text.includes('file tree')) return 'claude-tree';
|
163
160
|
if (text.includes('code')) return 'code';
|
@@ -263,176 +260,6 @@ class UIStateManager {
|
|
263
260
|
window.CodeViewer.show();
|
264
261
|
}
|
265
262
|
}
|
266
|
-
|
267
|
-
// EXTREME NUCLEAR HANDLING for Browser Logs tab - FORCE COMPLETE ISOLATION
|
268
|
-
if (tabName === 'browser-logs') {
|
269
|
-
console.error('[UI-STATE v3 EXTREME] 🚨🚨🚨 SWITCHING TO BROWSER LOGS - EXTREME NUCLEAR MODE');
|
270
|
-
console.error('[UI-STATE v3 EXTREME] Stack trace:', new Error().stack);
|
271
|
-
|
272
|
-
// EXTREME DIAGNOSTIC: Check what's trying to render
|
273
|
-
const container = document.getElementById('browser-logs-container');
|
274
|
-
if (container) {
|
275
|
-
console.error('[UI-STATE v3 EXTREME] Container found, current innerHTML length:', container.innerHTML.length);
|
276
|
-
console.error('[UI-STATE v3 EXTREME] Container classes:', container.className);
|
277
|
-
console.error('[UI-STATE v3 EXTREME] Container children count:', container.children.length);
|
278
|
-
|
279
|
-
// EXTREME: Stop ALL event propagation
|
280
|
-
const stopAllEvents = (e) => {
|
281
|
-
e.stopPropagation();
|
282
|
-
e.stopImmediatePropagation();
|
283
|
-
e.preventDefault();
|
284
|
-
};
|
285
|
-
|
286
|
-
// EXTREME: Block EventViewer from touching this container
|
287
|
-
if (window.eventViewer) {
|
288
|
-
console.error('[UI-STATE v3 EXTREME] 🚨 EventViewer exists - DISABLING IT');
|
289
|
-
// Temporarily override EventViewer's renderEvents method
|
290
|
-
const originalRender = window.eventViewer.renderEvents;
|
291
|
-
window.eventViewer.renderEvents = function() {
|
292
|
-
const targetEl = document.getElementById('events-list');
|
293
|
-
// Only allow rendering if target is NOT in browser-logs-tab
|
294
|
-
if (targetEl && !targetEl.closest('#browser-logs-tab')) {
|
295
|
-
return originalRender.call(this);
|
296
|
-
}
|
297
|
-
console.error('[UI-STATE v3 EXTREME] BLOCKED EventViewer.renderEvents in Browser Logs tab!');
|
298
|
-
};
|
299
|
-
}
|
300
|
-
|
301
|
-
// EXTREME CLEAR: Multiple passes to ensure complete clearing
|
302
|
-
for (let i = 0; i < 3; i++) {
|
303
|
-
container.innerHTML = '';
|
304
|
-
container.textContent = '';
|
305
|
-
while (container.firstChild) {
|
306
|
-
container.removeChild(container.firstChild);
|
307
|
-
}
|
308
|
-
}
|
309
|
-
|
310
|
-
// Reset all attributes and classes
|
311
|
-
container.className = '';
|
312
|
-
container.removeAttribute('data-events');
|
313
|
-
container.removeAttribute('data-component');
|
314
|
-
container.setAttribute('data-component', 'browser-logs-only');
|
315
|
-
container.setAttribute('data-no-events', 'true');
|
316
|
-
|
317
|
-
// EXTREME: Set a guard flag
|
318
|
-
container.dataset.browserLogsGuard = 'active';
|
319
|
-
|
320
|
-
// EXTREME: Override container's innerHTML setter temporarily
|
321
|
-
const originalInnerHTML = Object.getOwnPropertyDescriptor(Element.prototype, 'innerHTML');
|
322
|
-
Object.defineProperty(container, 'innerHTML', {
|
323
|
-
set: function(value) {
|
324
|
-
if (value && typeof value === 'string' &&
|
325
|
-
(value.includes('[hook]') || value.includes('event-item') ||
|
326
|
-
value.includes('hook.pre_tool') || value.includes('hook.post_tool'))) {
|
327
|
-
console.error('[UI-STATE v3 EXTREME] 🚨 BLOCKED CONTAMINATED innerHTML:', value.substring(0, 100));
|
328
|
-
return;
|
329
|
-
}
|
330
|
-
originalInnerHTML.set.call(this, value);
|
331
|
-
},
|
332
|
-
get: function() {
|
333
|
-
return originalInnerHTML.get.call(this);
|
334
|
-
},
|
335
|
-
configurable: true
|
336
|
-
});
|
337
|
-
|
338
|
-
// Check if BrowserLogViewer exists
|
339
|
-
if (typeof BrowserLogViewer !== 'undefined') {
|
340
|
-
// ALWAYS recreate to ensure clean state
|
341
|
-
if (window.browserLogViewer) {
|
342
|
-
console.error('[UI-STATE v3 EXTREME] Destroying old BrowserLogViewer instance');
|
343
|
-
if (window.browserLogViewer.destroy) {
|
344
|
-
window.browserLogViewer.destroy();
|
345
|
-
}
|
346
|
-
window.browserLogViewer = null;
|
347
|
-
}
|
348
|
-
|
349
|
-
// Create fresh instance with extreme verification
|
350
|
-
console.error('[UI-STATE v3 EXTREME] Creating NEW BrowserLogViewer v3.0 EXTREME instance');
|
351
|
-
window.browserLogViewer = new BrowserLogViewer(container);
|
352
|
-
console.error('[UI-STATE v3 EXTREME] ✅ BrowserLogViewer v3.0 EXTREME INITIALIZED');
|
353
|
-
|
354
|
-
// Force immediate render
|
355
|
-
if (window.browserLogViewer.render) {
|
356
|
-
window.browserLogViewer.render();
|
357
|
-
}
|
358
|
-
} else {
|
359
|
-
// Fallback: Show hardcoded message if viewer not loaded
|
360
|
-
console.error('[UI-STATE v3 EXTREME] BrowserLogViewer not found - showing fallback');
|
361
|
-
// Restore innerHTML setter for fallback message
|
362
|
-
Object.defineProperty(container, 'innerHTML', originalInnerHTML);
|
363
|
-
container.innerHTML = `
|
364
|
-
<div style="padding: 20px; text-align: center; background: #f0f0f0; border: 3px solid red;">
|
365
|
-
<h1 style="color: red;">🚨 BROWSER LOGS ONLY 🚨</h1>
|
366
|
-
<h2 style="color: green;">NO HOOK EVENTS ALLOWED</h2>
|
367
|
-
<p style="color: red; font-weight: bold; font-size: 18px;">⚠️ Hook events ([hook]) are FORCEFULLY BLOCKED ⚠️</p>
|
368
|
-
<p>This tab shows ONLY browser console logs.</p>
|
369
|
-
<p style="color: blue;">Browser Log Viewer v3.0 EXTREME is loading...</p>
|
370
|
-
</div>
|
371
|
-
`;
|
372
|
-
}
|
373
|
-
|
374
|
-
// EXTREME: Multiple contamination checks
|
375
|
-
const checkContamination = () => {
|
376
|
-
const contamination = container.querySelectorAll('.event-item, .events-list, [class*="event"]');
|
377
|
-
if (contamination.length > 0) {
|
378
|
-
console.error(`[UI-STATE v3 EXTREME] 🚨 CONTAMINATION DETECTED (${contamination.length} items) - NUKING!`);
|
379
|
-
contamination.forEach(item => {
|
380
|
-
console.error('[UI-STATE v3 EXTREME] Removing contaminated element:', item.className);
|
381
|
-
item.remove();
|
382
|
-
});
|
383
|
-
if (window.browserLogViewer && window.browserLogViewer.render) {
|
384
|
-
window.browserLogViewer.render();
|
385
|
-
}
|
386
|
-
}
|
387
|
-
|
388
|
-
// Check text content for hook events
|
389
|
-
if (container.textContent.includes('[hook]') ||
|
390
|
-
container.textContent.includes('hook.pre_tool')) {
|
391
|
-
console.error('[UI-STATE v3 EXTREME] 🚨 TEXT CONTAMINATION DETECTED!');
|
392
|
-
if (window.browserLogViewer) {
|
393
|
-
container.innerHTML = '';
|
394
|
-
window.browserLogViewer.render();
|
395
|
-
}
|
396
|
-
}
|
397
|
-
};
|
398
|
-
|
399
|
-
// Run contamination checks multiple times
|
400
|
-
setTimeout(checkContamination, 50);
|
401
|
-
setTimeout(checkContamination, 100);
|
402
|
-
setTimeout(checkContamination, 200);
|
403
|
-
setTimeout(checkContamination, 500);
|
404
|
-
|
405
|
-
// EXTREME: Monitor for mutations
|
406
|
-
const observer = new MutationObserver((mutations) => {
|
407
|
-
for (const mutation of mutations) {
|
408
|
-
if (mutation.type === 'childList') {
|
409
|
-
for (const node of mutation.addedNodes) {
|
410
|
-
if (node.nodeType === Node.ELEMENT_NODE) {
|
411
|
-
const element = node;
|
412
|
-
if (element.classList?.contains('event-item') ||
|
413
|
-
element.textContent?.includes('[hook]')) {
|
414
|
-
console.error('[UI-STATE v3 EXTREME] 🚨 MUTATION DETECTED - BLOCKING!');
|
415
|
-
element.remove();
|
416
|
-
}
|
417
|
-
}
|
418
|
-
}
|
419
|
-
}
|
420
|
-
}
|
421
|
-
});
|
422
|
-
|
423
|
-
observer.observe(container, {
|
424
|
-
childList: true,
|
425
|
-
subtree: true,
|
426
|
-
characterData: true
|
427
|
-
});
|
428
|
-
|
429
|
-
// Store observer for cleanup
|
430
|
-
container.dataset.mutationObserver = 'active';
|
431
|
-
window.browserLogsMutationObserver = observer;
|
432
|
-
} else {
|
433
|
-
console.error('[UI-STATE v3 EXTREME] 🚨 BROWSER LOGS CONTAINER NOT FOUND!');
|
434
|
-
}
|
435
|
-
}
|
436
263
|
}, 100);
|
437
264
|
}
|
438
265
|
|
@@ -249,8 +249,7 @@
|
|
249
249
|
<a href="#tools" class="tab-button" data-tab="tools">🔧 Tools</a>
|
250
250
|
<a href="#files" class="tab-button" data-tab="files">📁 Files</a>
|
251
251
|
<a href="#activity" class="tab-button" data-tab="activity">🌳 Activity</a>
|
252
|
-
<a href="#file_tree" class="tab-button
|
253
|
-
<a href="#browser_logs" class="tab-button" data-tab="browser-logs">🌐 Browser Logs</a>
|
252
|
+
<a href="#file_tree" class="tab-button" data-tab="claude-tree">📝 File Tree</a>
|
254
253
|
</div>
|
255
254
|
|
256
255
|
<!-- Events Tab -->
|
@@ -390,19 +389,12 @@
|
|
390
389
|
</div>
|
391
390
|
|
392
391
|
<!-- File Tree Tab -->
|
393
|
-
<div class="tab-content
|
392
|
+
<div class="tab-content" id="claude-tree-tab">
|
394
393
|
<div id="claude-tree-container" style="width: 100%; height: 100%;">
|
395
394
|
<!-- File activity tree will be rendered here by code-viewer.js -->
|
396
395
|
</div>
|
397
396
|
</div>
|
398
397
|
|
399
|
-
<!-- Browser Logs Tab -->
|
400
|
-
<div class="tab-content" id="browser-logs-tab">
|
401
|
-
<div id="browser-logs-container" style="width: 100%; height: 100%;" data-component="browser-logs">
|
402
|
-
<!-- Browser logs will be rendered here by browser-log-viewer.js -->
|
403
|
-
<!-- This container is EXCLUSIVELY for browser console logs -->
|
404
|
-
</div>
|
405
|
-
</div>
|
406
398
|
|
407
399
|
</div>
|
408
400
|
</div>
|
@@ -508,23 +500,7 @@
|
|
508
500
|
loadModule('/static/dist/components/activity-tree.js'),
|
509
501
|
loadModule('/static/js/components/code-tree.js'), // TEMPORARY: Direct source for debugging
|
510
502
|
loadModule('/static/js/components/code-viewer.js'), // Code viewer now includes file change tracking
|
511
|
-
loadModule('/static/dist/components/file-viewer.js')
|
512
|
-
loadModule('/static/js/components/browser-log-viewer.js?v=2.0-NUCLEAR') // Browser console log viewer v2.0 NUCLEAR
|
513
|
-
.then(() => {
|
514
|
-
// Initialize BrowserLogViewer v2.0 immediately after loading
|
515
|
-
if (typeof BrowserLogViewer !== 'undefined') {
|
516
|
-
const container = document.getElementById('browser-logs-container');
|
517
|
-
if (container && !window.browserLogViewer) {
|
518
|
-
console.error('[Main] Initializing BrowserLogViewer v2.0 NUCLEAR...');
|
519
|
-
window.browserLogViewer = new BrowserLogViewer(container);
|
520
|
-
console.error('[Main] BrowserLogViewer v2.0 NUCLEAR initialized successfully');
|
521
|
-
} else {
|
522
|
-
console.error('[Main] BrowserLogViewer v2.0 already initialized or container not found');
|
523
|
-
}
|
524
|
-
} else {
|
525
|
-
console.error('[Main] BrowserLogViewer class not found - script may not have loaded');
|
526
|
-
}
|
527
|
-
})
|
503
|
+
loadModule('/static/dist/components/file-viewer.js') // File viewer for viewing file contents
|
528
504
|
]);
|
529
505
|
})
|
530
506
|
.then(() => {
|
@@ -28,7 +28,6 @@ from aiohttp import web
|
|
28
28
|
from ...core.logging_config import get_logger
|
29
29
|
from ...dashboard.api.simple_directory import list_directory
|
30
30
|
from .event_emitter import get_event_emitter
|
31
|
-
from .handlers.browser import BrowserHandler
|
32
31
|
from .handlers.code_analysis import CodeAnalysisHandler
|
33
32
|
from .handlers.dashboard import DashboardHandler
|
34
33
|
from .handlers.file import FileHandler
|
@@ -69,7 +68,6 @@ class UnifiedMonitorServer:
|
|
69
68
|
self.site = None
|
70
69
|
|
71
70
|
# Event handlers
|
72
|
-
self.browser_handler = None
|
73
71
|
self.code_analysis_handler = None
|
74
72
|
self.dashboard_handler = None
|
75
73
|
self.file_handler = None
|
@@ -272,14 +270,12 @@ class UnifiedMonitorServer:
|
|
272
270
|
"""Setup Socket.IO event handlers."""
|
273
271
|
try:
|
274
272
|
# Create event handlers
|
275
|
-
self.browser_handler = BrowserHandler(self.sio)
|
276
273
|
self.code_analysis_handler = CodeAnalysisHandler(self.sio)
|
277
274
|
self.dashboard_handler = DashboardHandler(self.sio)
|
278
275
|
self.file_handler = FileHandler(self.sio)
|
279
276
|
self.hook_handler = HookHandler(self.sio)
|
280
277
|
|
281
278
|
# Register handlers
|
282
|
-
self.browser_handler.register()
|
283
279
|
self.code_analysis_handler.register()
|
284
280
|
self.dashboard_handler.register()
|
285
281
|
self.file_handler.register()
|
@@ -512,219 +508,6 @@ class UnifiedMonitorServer:
|
|
512
508
|
{"working_directory": os.getcwd(), "success": True}
|
513
509
|
)
|
514
510
|
|
515
|
-
# Browser monitor script endpoint
|
516
|
-
async def browser_monitor_script_handler(request):
|
517
|
-
"""Serve the browser console monitoring script with dynamic port injection."""
|
518
|
-
try:
|
519
|
-
# Read the browser monitor script template
|
520
|
-
script_path = (
|
521
|
-
dashboard_dir / "static" / "js" / "browser-console-monitor.js"
|
522
|
-
)
|
523
|
-
|
524
|
-
if not script_path.exists():
|
525
|
-
return web.Response(
|
526
|
-
text="Browser monitor script not found",
|
527
|
-
status=404,
|
528
|
-
content_type="text/javascript",
|
529
|
-
)
|
530
|
-
|
531
|
-
# Read script content
|
532
|
-
with open(script_path, encoding="utf-8") as f:
|
533
|
-
script_content = f.read()
|
534
|
-
|
535
|
-
# Replace the port placeholder with actual monitor port
|
536
|
-
script_content = script_content.replace(
|
537
|
-
"__MONITOR_PORT__", str(self.port)
|
538
|
-
)
|
539
|
-
|
540
|
-
# Set appropriate headers for JavaScript
|
541
|
-
headers = {
|
542
|
-
"Content-Type": "application/javascript; charset=utf-8",
|
543
|
-
"Access-Control-Allow-Origin": "*",
|
544
|
-
"Access-Control-Allow-Methods": "GET, OPTIONS",
|
545
|
-
"Access-Control-Allow-Headers": "Content-Type",
|
546
|
-
"Cache-Control": "no-cache, no-store, must-revalidate",
|
547
|
-
"Pragma": "no-cache",
|
548
|
-
"Expires": "0",
|
549
|
-
}
|
550
|
-
|
551
|
-
return web.Response(text=script_content, headers=headers)
|
552
|
-
|
553
|
-
except Exception as e:
|
554
|
-
self.logger.error(f"Error serving browser monitor script: {e}")
|
555
|
-
return web.Response(
|
556
|
-
text=f"// Error loading browser monitor script: {e}",
|
557
|
-
status=500,
|
558
|
-
content_type="application/javascript",
|
559
|
-
)
|
560
|
-
|
561
|
-
# Browser log endpoints
|
562
|
-
async def api_browser_logs_handler(request):
|
563
|
-
"""Return list of browser log files."""
|
564
|
-
try:
|
565
|
-
from pathlib import Path
|
566
|
-
|
567
|
-
# Get browser log directory
|
568
|
-
log_dir = Path.home() / ".claude-mpm" / "logs" / "client"
|
569
|
-
|
570
|
-
if not log_dir.exists():
|
571
|
-
return web.json_response({"files": [], "total": 0})
|
572
|
-
|
573
|
-
# Get all log files
|
574
|
-
log_files = []
|
575
|
-
for file_path in log_dir.glob("*.log"):
|
576
|
-
stat = file_path.stat()
|
577
|
-
log_files.append(
|
578
|
-
{
|
579
|
-
"name": file_path.name,
|
580
|
-
"size": stat.st_size,
|
581
|
-
"modified": stat.st_mtime,
|
582
|
-
"modified_iso": datetime.fromtimestamp(
|
583
|
-
stat.st_mtime
|
584
|
-
).isoformat(),
|
585
|
-
}
|
586
|
-
)
|
587
|
-
|
588
|
-
# Sort by modification time (newest first)
|
589
|
-
log_files.sort(key=lambda x: x["modified"], reverse=True)
|
590
|
-
|
591
|
-
return web.json_response(
|
592
|
-
{
|
593
|
-
"files": log_files,
|
594
|
-
"total": len(log_files),
|
595
|
-
"directory": str(log_dir),
|
596
|
-
}
|
597
|
-
)
|
598
|
-
|
599
|
-
except Exception as e:
|
600
|
-
self.logger.error(f"Error listing browser logs: {e}")
|
601
|
-
return web.json_response(
|
602
|
-
{"error": str(e), "files": [], "total": 0}, status=500
|
603
|
-
)
|
604
|
-
|
605
|
-
async def api_browser_log_file_handler(request):
|
606
|
-
"""Return contents of specific browser log file."""
|
607
|
-
try:
|
608
|
-
import json
|
609
|
-
from pathlib import Path
|
610
|
-
|
611
|
-
filename = request.match_info.get("filename", "")
|
612
|
-
|
613
|
-
# Security: ensure filename is safe (no path traversal)
|
614
|
-
if (
|
615
|
-
not filename
|
616
|
-
or "/" in filename
|
617
|
-
or "\\" in filename
|
618
|
-
or ".." in filename
|
619
|
-
):
|
620
|
-
return web.json_response(
|
621
|
-
{"error": "Invalid filename"}, status=400
|
622
|
-
)
|
623
|
-
|
624
|
-
# Get log file path
|
625
|
-
log_dir = Path.home() / ".claude-mpm" / "logs" / "client"
|
626
|
-
log_file = log_dir / filename
|
627
|
-
|
628
|
-
# Check if file exists and is within log directory
|
629
|
-
if not log_file.exists() or not log_file.is_file():
|
630
|
-
return web.json_response(
|
631
|
-
{"error": "Log file not found"}, status=404
|
632
|
-
)
|
633
|
-
|
634
|
-
# Ensure file is within the log directory (security check)
|
635
|
-
try:
|
636
|
-
log_file.resolve().relative_to(log_dir.resolve())
|
637
|
-
except ValueError:
|
638
|
-
return web.json_response(
|
639
|
-
{"error": "Invalid file path"}, status=403
|
640
|
-
)
|
641
|
-
|
642
|
-
# Read log entries
|
643
|
-
entries = []
|
644
|
-
with open(log_file, encoding="utf-8") as f:
|
645
|
-
for line in f:
|
646
|
-
line = line.strip()
|
647
|
-
if line:
|
648
|
-
try:
|
649
|
-
entry = json.loads(line)
|
650
|
-
entries.append(entry)
|
651
|
-
except json.JSONDecodeError:
|
652
|
-
# If not JSON, treat as plain text log
|
653
|
-
entries.append(
|
654
|
-
{
|
655
|
-
"timestamp": "",
|
656
|
-
"level": "INFO",
|
657
|
-
"message": line,
|
658
|
-
}
|
659
|
-
)
|
660
|
-
|
661
|
-
# Limit entries for performance
|
662
|
-
max_entries = 1000
|
663
|
-
if len(entries) > max_entries:
|
664
|
-
entries = entries[-max_entries:] # Get last N entries
|
665
|
-
|
666
|
-
return web.json_response(
|
667
|
-
{
|
668
|
-
"filename": filename,
|
669
|
-
"entries": entries,
|
670
|
-
"total": len(entries),
|
671
|
-
"truncated": len(entries) == max_entries,
|
672
|
-
}
|
673
|
-
)
|
674
|
-
|
675
|
-
except Exception as e:
|
676
|
-
self.logger.error(f"Error reading browser log file: {e}")
|
677
|
-
return web.json_response(
|
678
|
-
{"error": str(e), "entries": []}, status=500
|
679
|
-
)
|
680
|
-
|
681
|
-
async def api_browser_log_handler(request):
|
682
|
-
"""Handle browser log submissions from injected monitoring script."""
|
683
|
-
try:
|
684
|
-
import json
|
685
|
-
from pathlib import Path
|
686
|
-
|
687
|
-
data = await request.json()
|
688
|
-
|
689
|
-
# Extract log data
|
690
|
-
browser_id = data.get("browser_id", "unknown")
|
691
|
-
timestamp = data.get("timestamp", datetime.utcnow().isoformat())
|
692
|
-
level = data.get("level", "INFO")
|
693
|
-
message = data.get("message", "")
|
694
|
-
url = data.get("url", "")
|
695
|
-
user_agent = data.get("userAgent", "")
|
696
|
-
|
697
|
-
# Create log entry
|
698
|
-
log_entry = {
|
699
|
-
"browser_id": browser_id,
|
700
|
-
"timestamp": timestamp,
|
701
|
-
"level": level,
|
702
|
-
"message": message,
|
703
|
-
"url": url,
|
704
|
-
"user_agent": user_agent,
|
705
|
-
}
|
706
|
-
|
707
|
-
# Save to file
|
708
|
-
log_dir = Path.home() / ".claude-mpm" / "logs" / "client"
|
709
|
-
log_dir.mkdir(parents=True, exist_ok=True)
|
710
|
-
|
711
|
-
# Use browser ID in filename for easy filtering
|
712
|
-
log_file = log_dir / f"{browser_id}.log"
|
713
|
-
|
714
|
-
# Append to log file
|
715
|
-
with open(log_file, "a", encoding="utf-8") as f:
|
716
|
-
f.write(json.dumps(log_entry) + "\n")
|
717
|
-
|
718
|
-
# Also emit to Socket.IO for real-time viewing
|
719
|
-
if self.sio:
|
720
|
-
await self.sio.emit("browser_log", log_entry)
|
721
|
-
|
722
|
-
return web.Response(status=204) # No content
|
723
|
-
|
724
|
-
except Exception as e:
|
725
|
-
self.logger.error(f"Error handling browser log: {e}")
|
726
|
-
return web.Response(text=str(e), status=500)
|
727
|
-
|
728
511
|
# Register routes
|
729
512
|
self.app.router.add_get("/", dashboard_index)
|
730
513
|
self.app.router.add_get("/health", health_check)
|
@@ -732,14 +515,6 @@ class UnifiedMonitorServer:
|
|
732
515
|
self.app.router.add_get("/api/config", config_handler)
|
733
516
|
self.app.router.add_get("/api/working-directory", working_directory_handler)
|
734
517
|
self.app.router.add_get("/api/directory", list_directory)
|
735
|
-
self.app.router.add_get(
|
736
|
-
"/api/browser-monitor.js", browser_monitor_script_handler
|
737
|
-
)
|
738
|
-
self.app.router.add_get("/api/browser-logs", api_browser_logs_handler)
|
739
|
-
self.app.router.add_get(
|
740
|
-
"/api/browser-logs/{filename}", api_browser_log_file_handler
|
741
|
-
)
|
742
|
-
self.app.router.add_post("/api/browser-log", api_browser_log_handler)
|
743
518
|
self.app.router.add_post("/api/events", api_events_handler)
|
744
519
|
self.app.router.add_post("/api/file", api_file_handler)
|
745
520
|
|
@@ -961,7 +736,6 @@ class UnifiedMonitorServer:
|
|
961
736
|
"host": self.host,
|
962
737
|
"port": self.port,
|
963
738
|
"handlers": {
|
964
|
-
"browser": self.browser_handler is not None,
|
965
739
|
"code_analysis": self.code_analysis_handler is not None,
|
966
740
|
"dashboard": self.dashboard_handler is not None,
|
967
741
|
"file": self.file_handler is not None,
|