claude-mpm 4.2.9__py3-none-any.whl → 4.2.11__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.
Files changed (50) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/cli/commands/dashboard.py +59 -126
  3. claude_mpm/cli/commands/monitor.py +71 -212
  4. claude_mpm/cli/commands/run.py +33 -33
  5. claude_mpm/dashboard/static/css/code-tree.css +8 -16
  6. claude_mpm/dashboard/static/dist/components/code-tree.js +1 -1
  7. claude_mpm/dashboard/static/dist/components/file-viewer.js +2 -0
  8. claude_mpm/dashboard/static/dist/components/module-viewer.js +1 -1
  9. claude_mpm/dashboard/static/dist/components/unified-data-viewer.js +1 -1
  10. claude_mpm/dashboard/static/dist/dashboard.js +1 -1
  11. claude_mpm/dashboard/static/dist/socket-client.js +1 -1
  12. claude_mpm/dashboard/static/js/components/code-tree.js +692 -114
  13. claude_mpm/dashboard/static/js/components/file-viewer.js +538 -0
  14. claude_mpm/dashboard/static/js/components/module-viewer.js +26 -0
  15. claude_mpm/dashboard/static/js/components/unified-data-viewer.js +166 -14
  16. claude_mpm/dashboard/static/js/dashboard.js +108 -91
  17. claude_mpm/dashboard/static/js/socket-client.js +9 -7
  18. claude_mpm/dashboard/templates/index.html +2 -7
  19. claude_mpm/hooks/claude_hooks/hook_handler.py +1 -11
  20. claude_mpm/hooks/claude_hooks/services/connection_manager.py +54 -59
  21. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +112 -72
  22. claude_mpm/services/agents/deployment/agent_template_builder.py +0 -1
  23. claude_mpm/services/cli/unified_dashboard_manager.py +354 -0
  24. claude_mpm/services/monitor/__init__.py +20 -0
  25. claude_mpm/services/monitor/daemon.py +256 -0
  26. claude_mpm/services/monitor/event_emitter.py +279 -0
  27. claude_mpm/services/monitor/handlers/__init__.py +20 -0
  28. claude_mpm/services/monitor/handlers/code_analysis.py +334 -0
  29. claude_mpm/services/monitor/handlers/dashboard.py +298 -0
  30. claude_mpm/services/monitor/handlers/hooks.py +491 -0
  31. claude_mpm/services/monitor/management/__init__.py +18 -0
  32. claude_mpm/services/monitor/management/health.py +124 -0
  33. claude_mpm/services/monitor/management/lifecycle.py +298 -0
  34. claude_mpm/services/monitor/server.py +442 -0
  35. claude_mpm/tools/code_tree_analyzer.py +33 -17
  36. {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.11.dist-info}/METADATA +1 -1
  37. {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.11.dist-info}/RECORD +41 -36
  38. claude_mpm/cli/commands/socketio_monitor.py +0 -233
  39. claude_mpm/scripts/socketio_daemon.py +0 -571
  40. claude_mpm/scripts/socketio_daemon_hardened.py +0 -937
  41. claude_mpm/scripts/socketio_daemon_wrapper.py +0 -78
  42. claude_mpm/scripts/socketio_server_manager.py +0 -349
  43. claude_mpm/services/cli/dashboard_launcher.py +0 -423
  44. claude_mpm/services/cli/socketio_manager.py +0 -595
  45. claude_mpm/services/dashboard/stable_server.py +0 -1020
  46. claude_mpm/services/socketio/monitor_server.py +0 -505
  47. {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.11.dist-info}/WHEEL +0 -0
  48. {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.11.dist-info}/entry_points.txt +0 -0
  49. {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.11.dist-info}/licenses/LICENSE +0 -0
  50. {claude_mpm-4.2.9.dist-info → claude_mpm-4.2.11.dist-info}/top_level.txt +0 -0
@@ -1,423 +0,0 @@
1
- """
2
- Dashboard Launcher Service
3
- ===========================
4
-
5
- WHY: This service provides a centralized way to manage dashboard launching across
6
- the application, particularly for Socket.IO monitoring and other web dashboards.
7
- By extracting this logic from run.py, we reduce complexity and create a reusable
8
- service for any command that needs to launch a dashboard.
9
-
10
- DESIGN DECISIONS:
11
- - Interface-based design (IDashboardLauncher) for testability and flexibility
12
- - Support for multiple browser types and launch methods
13
- - Port management integration for finding available ports
14
- - Graceful fallback when browser launch fails
15
- - Platform-specific optimizations for tab reuse
16
- - Process management for standalone servers
17
- """
18
-
19
- import os
20
- import platform
21
- import socket
22
- import subprocess
23
- import sys
24
- import time
25
- import urllib.error
26
- import urllib.request
27
- import webbrowser
28
- from abc import ABC, abstractmethod
29
- from typing import Tuple
30
-
31
- from ...core.logger import get_logger
32
- from ...core.unified_paths import get_package_root
33
- from ...services.port_manager import PortManager
34
-
35
-
36
- # Interface
37
- class IDashboardLauncher(ABC):
38
- """Interface for dashboard launching service."""
39
-
40
- @abstractmethod
41
- def launch_dashboard(
42
- self, port: int = 8765, monitor_mode: bool = True
43
- ) -> Tuple[bool, bool]:
44
- """
45
- Launch the web dashboard.
46
-
47
- Args:
48
- port: Port number for the dashboard server
49
- monitor_mode: Whether to open in monitor mode
50
-
51
- Returns:
52
- Tuple of (success, browser_opened)
53
- """
54
-
55
- @abstractmethod
56
- def is_dashboard_running(self, port: int = 8765) -> bool:
57
- """
58
- Check if dashboard server is running.
59
-
60
- Args:
61
- port: Port to check
62
-
63
- Returns:
64
- True if dashboard is running on the specified port
65
- """
66
-
67
- @abstractmethod
68
- def get_dashboard_url(self, port: int = 8765) -> str:
69
- """
70
- Get the dashboard URL.
71
-
72
- Args:
73
- port: Port number
74
-
75
- Returns:
76
- Dashboard URL string
77
- """
78
-
79
- @abstractmethod
80
- def stop_dashboard(self, port: int = 8765) -> bool:
81
- """
82
- Stop the dashboard server.
83
-
84
- Args:
85
- port: Port of the server to stop
86
-
87
- Returns:
88
- True if successfully stopped
89
- """
90
-
91
- @abstractmethod
92
- def wait_for_dashboard(self, port: int = 8765, timeout: int = 30) -> bool:
93
- """
94
- Wait for dashboard to be ready.
95
-
96
- Args:
97
- port: Port to check
98
- timeout: Maximum time to wait in seconds
99
-
100
- Returns:
101
- True if dashboard became ready within timeout
102
- """
103
-
104
-
105
- # Implementation
106
- class DashboardLauncher(IDashboardLauncher):
107
- """Dashboard launcher service implementation."""
108
-
109
- def __init__(self, logger=None):
110
- """
111
- Initialize the dashboard launcher.
112
-
113
- Args:
114
- logger: Optional logger instance
115
- """
116
- self.logger = logger or get_logger("DashboardLauncher")
117
- self.port_manager = PortManager()
118
-
119
- def launch_dashboard(
120
- self, port: int = 8765, monitor_mode: bool = True
121
- ) -> Tuple[bool, bool]:
122
- """
123
- Launch the web dashboard.
124
-
125
- WHY: Provides a unified way to launch dashboards with proper error handling,
126
- browser management, and server lifecycle control.
127
-
128
- Args:
129
- port: Port number for the dashboard server
130
- monitor_mode: Whether to open in monitor mode
131
-
132
- Returns:
133
- Tuple of (success, browser_opened)
134
- """
135
- try:
136
- # Verify dependencies for Socket.IO dashboard
137
- if monitor_mode and not self._verify_socketio_dependencies():
138
- return False, False
139
-
140
- self.logger.info(
141
- f"Launching dashboard (port: {port}, monitor: {monitor_mode})"
142
- )
143
-
144
- # Clean up dead instances and check for existing servers
145
- self.port_manager.cleanup_dead_instances()
146
- active_instances = self.port_manager.list_active_instances()
147
-
148
- # Determine the port to use
149
- server_port = self._determine_server_port(port, active_instances)
150
- server_running = self.is_dashboard_running(server_port)
151
-
152
- # Get dashboard URL
153
- dashboard_url = self.get_dashboard_url(server_port)
154
-
155
- if server_running:
156
- self.logger.info(
157
- f"Dashboard server already running on port {server_port}"
158
- )
159
- print(f"✅ Dashboard server already running on port {server_port}")
160
- print(f"📊 Dashboard: {dashboard_url}")
161
- else:
162
- # Start the server
163
- print("🔧 Starting dashboard server...")
164
- if not self._start_dashboard_server(server_port):
165
- print("❌ Failed to start dashboard server")
166
- self._print_troubleshooting_tips(server_port)
167
- return False, False
168
-
169
- print("✅ Dashboard server started successfully")
170
- print(f"📊 Dashboard: {dashboard_url}")
171
-
172
- # Open browser unless suppressed
173
- browser_opened = False
174
- if not self._is_browser_suppressed():
175
- print("🌐 Opening dashboard in browser...")
176
- browser_opened = self._open_browser(dashboard_url)
177
- if not browser_opened:
178
- print("⚠️ Could not open browser automatically")
179
- print(f"📊 Please open manually: {dashboard_url}")
180
- else:
181
- print("🌐 Browser opening suppressed (CLAUDE_MPM_NO_BROWSER=1)")
182
- self.logger.info("Browser opening suppressed by environment variable")
183
-
184
- return True, browser_opened
185
-
186
- except Exception as e:
187
- self.logger.error(f"Failed to launch dashboard: {e}")
188
- print(f"❌ Failed to launch dashboard: {e}")
189
- return False, False
190
-
191
- def is_dashboard_running(self, port: int = 8765) -> bool:
192
- """
193
- Check if dashboard server is running.
194
-
195
- WHY: Prevents duplicate server launches and helps determine if we need
196
- to start a new server or connect to an existing one.
197
-
198
- Args:
199
- port: Port to check
200
-
201
- Returns:
202
- True if dashboard is running on the specified port
203
- """
204
- try:
205
- # First, do a basic TCP connection check
206
- with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
207
- s.settimeout(2.0)
208
- result = s.connect_ex(("127.0.0.1", port))
209
- if result != 0:
210
- self.logger.debug(f"TCP connection to port {port} failed")
211
- return False
212
-
213
- # If TCP connection succeeds, try HTTP health check
214
- try:
215
- response = urllib.request.urlopen(
216
- f"http://localhost:{port}/status", timeout=5
217
- )
218
- if response.getcode() == 200:
219
- self.logger.debug(f"Dashboard health check passed on port {port}")
220
- return True
221
- except Exception as e:
222
- self.logger.debug(f"HTTP health check failed for port {port}: {e}")
223
- # Server is listening but may not be fully ready yet
224
- return True # Still consider it running if TCP works
225
-
226
- except Exception as e:
227
- self.logger.debug(f"Error checking dashboard on port {port}: {e}")
228
-
229
- return False
230
-
231
- def get_dashboard_url(self, port: int = 8765) -> str:
232
- """
233
- Get the dashboard URL.
234
-
235
- Args:
236
- port: Port number
237
-
238
- Returns:
239
- Dashboard URL string
240
- """
241
- return f"http://localhost:{port}"
242
-
243
- def stop_dashboard(self, port: int = 8765) -> bool:
244
- """
245
- Stop the dashboard server.
246
-
247
- WHY: Provides clean shutdown of dashboard servers to free up ports
248
- and resources.
249
-
250
- Args:
251
- port: Port of the server to stop
252
-
253
- Returns:
254
- True if successfully stopped
255
- """
256
- try:
257
- daemon_script = get_package_root() / "scripts" / "socketio_daemon.py"
258
- if not daemon_script.exists():
259
- self.logger.error(f"Daemon script not found: {daemon_script}")
260
- return False
261
-
262
- # Stop the daemon
263
- result = subprocess.run(
264
- [sys.executable, str(daemon_script), "stop", "--port", str(port)],
265
- capture_output=True,
266
- text=True,
267
- timeout=10,
268
- check=False,
269
- )
270
-
271
- if result.returncode == 0:
272
- self.logger.info(f"Dashboard server stopped on port {port}")
273
- return True
274
-
275
- self.logger.warning(f"Failed to stop dashboard server: {result.stderr}")
276
- return False
277
-
278
- except Exception as e:
279
- self.logger.error(f"Error stopping dashboard server: {e}")
280
- return False
281
-
282
- def wait_for_dashboard(self, port: int = 8765, timeout: int = 30) -> bool:
283
- """
284
- Wait for dashboard to be ready.
285
-
286
- WHY: Ensures the dashboard is fully operational before attempting to
287
- open it in a browser, preventing "connection refused" errors.
288
-
289
- Args:
290
- port: Port to check
291
- timeout: Maximum time to wait in seconds
292
-
293
- Returns:
294
- True if dashboard became ready within timeout
295
- """
296
- start_time = time.time()
297
- while time.time() - start_time < timeout:
298
- if self.is_dashboard_running(port):
299
- return True
300
- time.sleep(0.5)
301
- return False
302
-
303
- # Private helper methods
304
- def _verify_socketio_dependencies(self) -> bool:
305
- """Verify Socket.IO dependencies are available."""
306
- try:
307
- import aiohttp
308
- import engineio
309
- import socketio
310
-
311
- self.logger.debug("Socket.IO dependencies verified")
312
- return True
313
- except ImportError as e:
314
- self.logger.error(f"Socket.IO dependencies not available: {e}")
315
- print(f"❌ Socket.IO dependencies missing: {e}")
316
- print(" Install with: pip install python-socketio aiohttp python-engineio")
317
- return False
318
-
319
- def _determine_server_port(
320
- self, requested_port: int, active_instances: list
321
- ) -> int:
322
- """Determine which port to use for the server."""
323
- if active_instances:
324
- # Prefer port 8765 if available
325
- for instance in active_instances:
326
- if instance.get("port") == 8765:
327
- return 8765
328
- # Otherwise use first active instance
329
- return active_instances[0].get("port", requested_port)
330
- return requested_port
331
-
332
- def _start_dashboard_server(self, port: int) -> bool:
333
- """Start the dashboard server."""
334
- try:
335
- daemon_script = get_package_root() / "scripts" / "socketio_daemon.py"
336
- if not daemon_script.exists():
337
- self.logger.error(f"Daemon script not found: {daemon_script}")
338
- return False
339
-
340
- # Start the daemon
341
- result = subprocess.run(
342
- [sys.executable, str(daemon_script), "start", "--port", str(port)],
343
- capture_output=True,
344
- text=True,
345
- timeout=30,
346
- check=False,
347
- )
348
-
349
- if result.returncode == 0:
350
- self.logger.info(f"Dashboard server started on port {port}")
351
- # Wait for server to be ready
352
- return self.wait_for_dashboard(port, timeout=10)
353
-
354
- self.logger.error(f"Failed to start dashboard server: {result.stderr}")
355
- return False
356
-
357
- except Exception as e:
358
- self.logger.error(f"Error starting dashboard server: {e}")
359
- return False
360
-
361
- def _is_browser_suppressed(self) -> bool:
362
- """Check if browser opening is suppressed."""
363
- return os.environ.get("CLAUDE_MPM_NO_BROWSER") == "1"
364
-
365
- def _open_browser(self, url: str) -> bool:
366
- """
367
- Open URL in browser with platform-specific optimizations.
368
-
369
- WHY: Different platforms have different ways to reuse browser tabs.
370
- This method tries platform-specific approaches before falling back
371
- to the standard webbrowser module.
372
- """
373
- try:
374
- system = platform.system().lower()
375
-
376
- if system == "darwin": # macOS
377
- try:
378
- # Try to open in existing tab with -g flag (background)
379
- subprocess.run(["open", "-g", url], check=True, timeout=5)
380
- self.logger.info("Opened browser on macOS")
381
- return True
382
- except Exception:
383
- pass
384
-
385
- elif system == "linux":
386
- try:
387
- # Try xdg-open for Linux
388
- subprocess.run(["xdg-open", url], check=True, timeout=5)
389
- self.logger.info("Opened browser on Linux")
390
- return True
391
- except Exception:
392
- pass
393
-
394
- elif system == "windows":
395
- try:
396
- # Try to use existing browser window
397
- webbrowser.get().open(url, new=0)
398
- self.logger.info("Opened browser on Windows")
399
- return True
400
- except Exception:
401
- pass
402
-
403
- # Fallback to standard webbrowser module
404
- webbrowser.open(url, new=0, autoraise=True)
405
- self.logger.info("Opened browser using webbrowser module")
406
- return True
407
-
408
- except Exception as e:
409
- self.logger.warning(f"Browser opening failed: {e}")
410
- try:
411
- # Final fallback
412
- webbrowser.open(url)
413
- return True
414
- except Exception:
415
- return False
416
-
417
- def _print_troubleshooting_tips(self, port: int):
418
- """Print troubleshooting tips for dashboard launch failures."""
419
- print("💡 Troubleshooting tips:")
420
- print(f" - Check if port {port} is already in use")
421
- print(" - Verify Socket.IO dependencies: pip install python-socketio aiohttp")
422
- print(" - Try a different port with --websocket-port")
423
- print(" - Check firewall settings for localhost connections")