claude-mpm 4.2.9__py3-none-any.whl → 4.2.12__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/cli/commands/dashboard.py +59 -126
- claude_mpm/cli/commands/monitor.py +82 -212
- claude_mpm/cli/commands/run.py +33 -33
- claude_mpm/cli/parsers/monitor_parser.py +12 -2
- claude_mpm/dashboard/static/css/code-tree.css +8 -16
- claude_mpm/dashboard/static/dist/components/code-tree.js +1 -1
- claude_mpm/dashboard/static/dist/components/file-viewer.js +2 -0
- claude_mpm/dashboard/static/dist/components/module-viewer.js +1 -1
- claude_mpm/dashboard/static/dist/components/unified-data-viewer.js +1 -1
- claude_mpm/dashboard/static/dist/dashboard.js +1 -1
- claude_mpm/dashboard/static/dist/socket-client.js +1 -1
- claude_mpm/dashboard/static/js/components/code-tree.js +692 -114
- claude_mpm/dashboard/static/js/components/file-viewer.js +538 -0
- claude_mpm/dashboard/static/js/components/module-viewer.js +26 -0
- claude_mpm/dashboard/static/js/components/unified-data-viewer.js +166 -14
- claude_mpm/dashboard/static/js/dashboard.js +108 -91
- claude_mpm/dashboard/static/js/socket-client.js +9 -7
- claude_mpm/dashboard/templates/index.html +2 -7
- claude_mpm/hooks/claude_hooks/hook_handler.py +1 -11
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +54 -59
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +112 -72
- claude_mpm/services/agents/deployment/agent_template_builder.py +0 -1
- claude_mpm/services/cli/unified_dashboard_manager.py +354 -0
- claude_mpm/services/monitor/__init__.py +20 -0
- claude_mpm/services/monitor/daemon.py +378 -0
- claude_mpm/services/monitor/event_emitter.py +342 -0
- claude_mpm/services/monitor/handlers/__init__.py +20 -0
- claude_mpm/services/monitor/handlers/code_analysis.py +334 -0
- claude_mpm/services/monitor/handlers/dashboard.py +298 -0
- claude_mpm/services/monitor/handlers/hooks.py +491 -0
- claude_mpm/services/monitor/management/__init__.py +18 -0
- claude_mpm/services/monitor/management/health.py +124 -0
- claude_mpm/services/monitor/management/lifecycle.py +338 -0
- claude_mpm/services/monitor/server.py +596 -0
- claude_mpm/tools/code_tree_analyzer.py +33 -17
- {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.12.dist-info}/METADATA +1 -1
- {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.12.dist-info}/RECORD +42 -37
- claude_mpm/cli/commands/socketio_monitor.py +0 -233
- claude_mpm/scripts/socketio_daemon.py +0 -571
- claude_mpm/scripts/socketio_daemon_hardened.py +0 -937
- claude_mpm/scripts/socketio_daemon_wrapper.py +0 -78
- claude_mpm/scripts/socketio_server_manager.py +0 -349
- claude_mpm/services/cli/dashboard_launcher.py +0 -423
- claude_mpm/services/cli/socketio_manager.py +0 -595
- claude_mpm/services/dashboard/stable_server.py +0 -1020
- claude_mpm/services/socketio/monitor_server.py +0 -505
- {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.12.dist-info}/WHEEL +0 -0
- {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.12.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.12.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.12.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unified Dashboard Manager Service
|
|
3
|
+
=================================
|
|
4
|
+
|
|
5
|
+
WHY: This service provides a centralized way to manage dashboard functionality using
|
|
6
|
+
the UnifiedMonitorDaemon. It replaces the old DashboardLauncher and SocketIOManager
|
|
7
|
+
services with a cleaner implementation that uses the unified daemon architecture.
|
|
8
|
+
|
|
9
|
+
DESIGN DECISIONS:
|
|
10
|
+
- Uses UnifiedMonitorDaemon for all server functionality
|
|
11
|
+
- Provides the same interface as the old services for compatibility
|
|
12
|
+
- Handles browser opening, process management, and status checking
|
|
13
|
+
- Integrates with PortManager for port allocation
|
|
14
|
+
- Thread-safe daemon management
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import threading
|
|
18
|
+
import time
|
|
19
|
+
import webbrowser
|
|
20
|
+
from abc import ABC, abstractmethod
|
|
21
|
+
from dataclasses import dataclass
|
|
22
|
+
from typing import Optional, Tuple
|
|
23
|
+
|
|
24
|
+
import requests
|
|
25
|
+
|
|
26
|
+
from ...core.logging_config import get_logger
|
|
27
|
+
from ...services.monitor.daemon import UnifiedMonitorDaemon
|
|
28
|
+
from ...services.port_manager import PortManager
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class DashboardInfo:
|
|
33
|
+
"""Information about a running dashboard."""
|
|
34
|
+
|
|
35
|
+
url: str
|
|
36
|
+
port: int
|
|
37
|
+
pid: Optional[int] = None
|
|
38
|
+
status: str = "running"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class IUnifiedDashboardManager(ABC):
|
|
42
|
+
"""Interface for unified dashboard management."""
|
|
43
|
+
|
|
44
|
+
@abstractmethod
|
|
45
|
+
def start_dashboard(
|
|
46
|
+
self, port: int = 8765, background: bool = False, open_browser: bool = True
|
|
47
|
+
) -> Tuple[bool, bool]:
|
|
48
|
+
"""Start the dashboard using unified daemon."""
|
|
49
|
+
|
|
50
|
+
@abstractmethod
|
|
51
|
+
def stop_dashboard(self, port: int = 8765) -> bool:
|
|
52
|
+
"""Stop the dashboard."""
|
|
53
|
+
|
|
54
|
+
@abstractmethod
|
|
55
|
+
def is_dashboard_running(self, port: int = 8765) -> bool:
|
|
56
|
+
"""Check if dashboard is running."""
|
|
57
|
+
|
|
58
|
+
@abstractmethod
|
|
59
|
+
def get_dashboard_url(self, port: int = 8765) -> str:
|
|
60
|
+
"""Get dashboard URL."""
|
|
61
|
+
|
|
62
|
+
@abstractmethod
|
|
63
|
+
def open_browser(self, url: str) -> bool:
|
|
64
|
+
"""Open URL in browser."""
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class UnifiedDashboardManager(IUnifiedDashboardManager):
|
|
68
|
+
"""Unified dashboard manager using UnifiedMonitorDaemon."""
|
|
69
|
+
|
|
70
|
+
def __init__(self, logger=None):
|
|
71
|
+
"""Initialize the unified dashboard manager."""
|
|
72
|
+
self.logger = logger or get_logger("UnifiedDashboardManager")
|
|
73
|
+
self.port_manager = PortManager()
|
|
74
|
+
self._background_daemons = {} # port -> daemon instance
|
|
75
|
+
self._lock = threading.Lock()
|
|
76
|
+
|
|
77
|
+
def start_dashboard(
|
|
78
|
+
self, port: int = 8765, background: bool = False, open_browser: bool = True
|
|
79
|
+
) -> Tuple[bool, bool]:
|
|
80
|
+
"""
|
|
81
|
+
Start the dashboard using unified daemon.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
port: Port to run dashboard on
|
|
85
|
+
background: Whether to run in background mode
|
|
86
|
+
open_browser: Whether to open browser automatically
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Tuple of (success, browser_opened)
|
|
90
|
+
"""
|
|
91
|
+
try:
|
|
92
|
+
# Check if already running
|
|
93
|
+
if self.is_dashboard_running(port):
|
|
94
|
+
self.logger.info(f"Dashboard already running on port {port}")
|
|
95
|
+
browser_opened = False
|
|
96
|
+
if open_browser:
|
|
97
|
+
browser_opened = self.open_browser(self.get_dashboard_url(port))
|
|
98
|
+
return True, browser_opened
|
|
99
|
+
|
|
100
|
+
self.logger.info(
|
|
101
|
+
f"Starting unified dashboard on port {port} (background: {background})"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
if background:
|
|
105
|
+
# Start daemon in background mode
|
|
106
|
+
daemon = UnifiedMonitorDaemon(
|
|
107
|
+
host="localhost", port=port, daemon_mode=True
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
success = daemon.start()
|
|
111
|
+
if success:
|
|
112
|
+
with self._lock:
|
|
113
|
+
self._background_daemons[port] = daemon
|
|
114
|
+
|
|
115
|
+
# Wait for daemon to be ready
|
|
116
|
+
if self._wait_for_dashboard(port, timeout=10):
|
|
117
|
+
browser_opened = False
|
|
118
|
+
if open_browser:
|
|
119
|
+
browser_opened = self.open_browser(
|
|
120
|
+
self.get_dashboard_url(port)
|
|
121
|
+
)
|
|
122
|
+
return True, browser_opened
|
|
123
|
+
self.logger.error("Dashboard daemon started but not responding")
|
|
124
|
+
return False, False
|
|
125
|
+
self.logger.error("Failed to start dashboard daemon")
|
|
126
|
+
return False, False
|
|
127
|
+
# For foreground mode, the caller should handle the daemon directly
|
|
128
|
+
# This is used by the CLI command that runs in foreground
|
|
129
|
+
self.logger.info("Foreground mode should be handled by caller")
|
|
130
|
+
return True, False
|
|
131
|
+
|
|
132
|
+
except Exception as e:
|
|
133
|
+
self.logger.error(f"Error starting dashboard: {e}")
|
|
134
|
+
return False, False
|
|
135
|
+
|
|
136
|
+
def stop_dashboard(self, port: int = 8765) -> bool:
|
|
137
|
+
"""
|
|
138
|
+
Stop the dashboard.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
port: Port of dashboard to stop
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
True if successfully stopped
|
|
145
|
+
"""
|
|
146
|
+
try:
|
|
147
|
+
# Check if we have a background daemon for this port
|
|
148
|
+
with self._lock:
|
|
149
|
+
daemon = self._background_daemons.get(port)
|
|
150
|
+
if daemon:
|
|
151
|
+
daemon.stop()
|
|
152
|
+
del self._background_daemons[port]
|
|
153
|
+
self.logger.info(f"Stopped background daemon on port {port}")
|
|
154
|
+
return True
|
|
155
|
+
|
|
156
|
+
# Try to stop via process management
|
|
157
|
+
if self.port_manager.is_port_in_use(port):
|
|
158
|
+
# Use port manager to find and stop the process
|
|
159
|
+
active_instances = self.port_manager.list_active_instances()
|
|
160
|
+
for instance in active_instances:
|
|
161
|
+
if instance.get("port") == port:
|
|
162
|
+
pid = instance.get("pid")
|
|
163
|
+
if pid:
|
|
164
|
+
try:
|
|
165
|
+
import psutil
|
|
166
|
+
|
|
167
|
+
process = psutil.Process(pid)
|
|
168
|
+
process.terminate()
|
|
169
|
+
process.wait(timeout=5)
|
|
170
|
+
self.logger.info(
|
|
171
|
+
f"Terminated dashboard process {pid} on port {port}"
|
|
172
|
+
)
|
|
173
|
+
return True
|
|
174
|
+
except Exception as e:
|
|
175
|
+
self.logger.warning(
|
|
176
|
+
f"Failed to terminate process {pid}: {e}"
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
self.logger.warning(f"No dashboard found running on port {port}")
|
|
180
|
+
return False
|
|
181
|
+
|
|
182
|
+
except Exception as e:
|
|
183
|
+
self.logger.error(f"Error stopping dashboard: {e}")
|
|
184
|
+
return False
|
|
185
|
+
|
|
186
|
+
def is_dashboard_running(self, port: int = 8765) -> bool:
|
|
187
|
+
"""
|
|
188
|
+
Check if dashboard is running.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
port: Port to check
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
True if dashboard is running
|
|
195
|
+
"""
|
|
196
|
+
try:
|
|
197
|
+
response = requests.get(f"http://localhost:{port}/health", timeout=2)
|
|
198
|
+
return response.status_code == 200
|
|
199
|
+
except requests.exceptions.RequestException:
|
|
200
|
+
return False
|
|
201
|
+
|
|
202
|
+
def get_dashboard_url(self, port: int = 8765) -> str:
|
|
203
|
+
"""
|
|
204
|
+
Get dashboard URL.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
port: Port number
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
Dashboard URL
|
|
211
|
+
"""
|
|
212
|
+
return f"http://localhost:{port}"
|
|
213
|
+
|
|
214
|
+
def open_browser(self, url: str) -> bool:
|
|
215
|
+
"""
|
|
216
|
+
Open URL in browser.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
url: URL to open
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
True if browser was opened successfully
|
|
223
|
+
"""
|
|
224
|
+
try:
|
|
225
|
+
self.logger.info(f"Opening browser to {url}")
|
|
226
|
+
webbrowser.open(url)
|
|
227
|
+
return True
|
|
228
|
+
except Exception as e:
|
|
229
|
+
self.logger.warning(f"Failed to open browser: {e}")
|
|
230
|
+
return False
|
|
231
|
+
|
|
232
|
+
def _wait_for_dashboard(self, port: int, timeout: int = 30) -> bool:
|
|
233
|
+
"""
|
|
234
|
+
Wait for dashboard to be ready.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
port: Port to check
|
|
238
|
+
timeout: Maximum time to wait
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
True if dashboard became ready
|
|
242
|
+
"""
|
|
243
|
+
start_time = time.time()
|
|
244
|
+
while time.time() - start_time < timeout:
|
|
245
|
+
if self.is_dashboard_running(port):
|
|
246
|
+
return True
|
|
247
|
+
time.sleep(0.5)
|
|
248
|
+
return False
|
|
249
|
+
|
|
250
|
+
def get_dashboard_info(self, port: int = 8765) -> Optional[DashboardInfo]:
|
|
251
|
+
"""
|
|
252
|
+
Get information about running dashboard.
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
port: Port to check
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
DashboardInfo if running, None otherwise
|
|
259
|
+
"""
|
|
260
|
+
if self.is_dashboard_running(port):
|
|
261
|
+
return DashboardInfo(
|
|
262
|
+
url=self.get_dashboard_url(port), port=port, status="running"
|
|
263
|
+
)
|
|
264
|
+
return None
|
|
265
|
+
|
|
266
|
+
def ensure_dependencies(self) -> Tuple[bool, Optional[str]]:
|
|
267
|
+
"""
|
|
268
|
+
Ensure required dependencies are available.
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
Tuple of (dependencies_ok, error_message)
|
|
272
|
+
"""
|
|
273
|
+
try:
|
|
274
|
+
import aiohttp
|
|
275
|
+
import socketio
|
|
276
|
+
|
|
277
|
+
return True, None
|
|
278
|
+
except ImportError as e:
|
|
279
|
+
error_msg = f"Required dependencies missing: {e}"
|
|
280
|
+
return False, error_msg
|
|
281
|
+
|
|
282
|
+
def find_available_port(self, preferred_port: int = 8765) -> int:
|
|
283
|
+
"""
|
|
284
|
+
Find an available port starting from the preferred port.
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
preferred_port: Preferred port to start checking from
|
|
288
|
+
|
|
289
|
+
Returns:
|
|
290
|
+
Available port number
|
|
291
|
+
"""
|
|
292
|
+
return self.port_manager.find_available_port(preferred_port)
|
|
293
|
+
|
|
294
|
+
def start_server(
|
|
295
|
+
self, port: Optional[int] = None, timeout: int = 30
|
|
296
|
+
) -> Tuple[bool, DashboardInfo]:
|
|
297
|
+
"""
|
|
298
|
+
Start the server (compatibility method for SocketIOManager interface).
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
port: Port to use (finds available if None)
|
|
302
|
+
timeout: Timeout for startup
|
|
303
|
+
|
|
304
|
+
Returns:
|
|
305
|
+
Tuple of (success, DashboardInfo)
|
|
306
|
+
"""
|
|
307
|
+
if port is None:
|
|
308
|
+
port = self.find_available_port()
|
|
309
|
+
|
|
310
|
+
success, browser_opened = self.start_dashboard(
|
|
311
|
+
port=port, background=True, open_browser=False
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
if success:
|
|
315
|
+
dashboard_info = DashboardInfo(
|
|
316
|
+
url=self.get_dashboard_url(port), port=port, status="running"
|
|
317
|
+
)
|
|
318
|
+
return True, dashboard_info
|
|
319
|
+
return False, DashboardInfo(url="", port=port, status="failed")
|
|
320
|
+
|
|
321
|
+
def is_server_running(self, port: int) -> bool:
|
|
322
|
+
"""
|
|
323
|
+
Check if server is running (compatibility method for SocketIOManager interface).
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
port: Port to check
|
|
327
|
+
|
|
328
|
+
Returns:
|
|
329
|
+
True if server is running
|
|
330
|
+
"""
|
|
331
|
+
return self.is_dashboard_running(port)
|
|
332
|
+
|
|
333
|
+
def stop_server(self, port: Optional[int] = None, timeout: int = 10) -> bool:
|
|
334
|
+
"""
|
|
335
|
+
Stop the server (compatibility method for SocketIOManager interface).
|
|
336
|
+
|
|
337
|
+
Args:
|
|
338
|
+
port: Port to stop (stops all if None)
|
|
339
|
+
timeout: Timeout for shutdown
|
|
340
|
+
|
|
341
|
+
Returns:
|
|
342
|
+
True if stopped successfully
|
|
343
|
+
"""
|
|
344
|
+
if port is None:
|
|
345
|
+
# Stop all background daemons
|
|
346
|
+
with self._lock:
|
|
347
|
+
ports_to_stop = list(self._background_daemons.keys())
|
|
348
|
+
|
|
349
|
+
success = True
|
|
350
|
+
for p in ports_to_stop:
|
|
351
|
+
if not self.stop_dashboard(p):
|
|
352
|
+
success = False
|
|
353
|
+
return success
|
|
354
|
+
return self.stop_dashboard(port)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unified Monitor Service for Claude MPM
|
|
3
|
+
=====================================
|
|
4
|
+
|
|
5
|
+
WHY: This module provides a single, stable daemon process that combines all
|
|
6
|
+
monitoring functionality into one cohesive service. It replaces the multiple
|
|
7
|
+
competing server implementations with a unified solution.
|
|
8
|
+
|
|
9
|
+
DESIGN DECISIONS:
|
|
10
|
+
- Single process handles HTTP dashboard, Socket.IO events, and real AST analysis
|
|
11
|
+
- Uses proven aiohttp + socketio foundation
|
|
12
|
+
- Integrates real CodeTreeAnalyzer instead of mock data
|
|
13
|
+
- Built for daemon operation with proper lifecycle management
|
|
14
|
+
- Single port (8765) for all functionality
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from .daemon import UnifiedMonitorDaemon
|
|
18
|
+
from .server import UnifiedMonitorServer
|
|
19
|
+
|
|
20
|
+
__all__ = ["UnifiedMonitorDaemon", "UnifiedMonitorServer"]
|