claude-mpm 4.1.10__py3-none-any.whl → 4.1.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 (48) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/cli/__init__.py +11 -0
  3. claude_mpm/cli/commands/analyze.py +2 -1
  4. claude_mpm/cli/commands/configure.py +9 -8
  5. claude_mpm/cli/commands/configure_tui.py +3 -1
  6. claude_mpm/cli/commands/dashboard.py +288 -0
  7. claude_mpm/cli/commands/debug.py +0 -1
  8. claude_mpm/cli/commands/mpm_init.py +427 -0
  9. claude_mpm/cli/commands/mpm_init_handler.py +83 -0
  10. claude_mpm/cli/parsers/base_parser.py +15 -0
  11. claude_mpm/cli/parsers/dashboard_parser.py +113 -0
  12. claude_mpm/cli/parsers/mpm_init_parser.py +122 -0
  13. claude_mpm/constants.py +10 -0
  14. claude_mpm/dashboard/analysis_runner.py +52 -25
  15. claude_mpm/dashboard/static/built/components/activity-tree.js +1 -1
  16. claude_mpm/dashboard/static/built/components/code-tree.js +2 -0
  17. claude_mpm/dashboard/static/built/components/code-viewer.js +2 -0
  18. claude_mpm/dashboard/static/built/components/event-viewer.js +1 -1
  19. claude_mpm/dashboard/static/built/dashboard.js +1 -1
  20. claude_mpm/dashboard/static/built/socket-client.js +1 -1
  21. claude_mpm/dashboard/static/css/code-tree.css +330 -1
  22. claude_mpm/dashboard/static/dist/components/activity-tree.js +1 -1
  23. claude_mpm/dashboard/static/dist/components/code-tree.js +1 -1
  24. claude_mpm/dashboard/static/dist/components/event-viewer.js +1 -1
  25. claude_mpm/dashboard/static/dist/dashboard.js +1 -1
  26. claude_mpm/dashboard/static/dist/socket-client.js +1 -1
  27. claude_mpm/dashboard/static/js/components/activity-tree.js +212 -13
  28. claude_mpm/dashboard/static/js/components/code-tree.js +1999 -821
  29. claude_mpm/dashboard/static/js/components/event-viewer.js +58 -19
  30. claude_mpm/dashboard/static/js/dashboard.js +15 -3
  31. claude_mpm/dashboard/static/js/socket-client.js +74 -32
  32. claude_mpm/dashboard/templates/index.html +9 -11
  33. claude_mpm/services/agents/memory/memory_format_service.py +3 -1
  34. claude_mpm/services/cli/agent_cleanup_service.py +1 -4
  35. claude_mpm/services/cli/startup_checker.py +0 -1
  36. claude_mpm/services/core/cache_manager.py +0 -1
  37. claude_mpm/services/socketio/event_normalizer.py +64 -0
  38. claude_mpm/services/socketio/handlers/code_analysis.py +502 -0
  39. claude_mpm/services/socketio/server/connection_manager.py +3 -1
  40. claude_mpm/tools/code_tree_analyzer.py +843 -25
  41. claude_mpm/tools/code_tree_builder.py +0 -1
  42. claude_mpm/tools/code_tree_events.py +113 -15
  43. {claude_mpm-4.1.10.dist-info → claude_mpm-4.1.11.dist-info}/METADATA +2 -1
  44. {claude_mpm-4.1.10.dist-info → claude_mpm-4.1.11.dist-info}/RECORD +48 -41
  45. {claude_mpm-4.1.10.dist-info → claude_mpm-4.1.11.dist-info}/WHEEL +0 -0
  46. {claude_mpm-4.1.10.dist-info → claude_mpm-4.1.11.dist-info}/entry_points.txt +0 -0
  47. {claude_mpm-4.1.10.dist-info → claude_mpm-4.1.11.dist-info}/licenses/LICENSE +0 -0
  48. {claude_mpm-4.1.10.dist-info → claude_mpm-4.1.11.dist-info}/top_level.txt +0 -0
claude_mpm/VERSION CHANGED
@@ -1 +1 @@
1
- 4.1.10
1
+ 4.1.11
@@ -35,6 +35,7 @@ from .commands import ( # run_guarded_session is imported lazily to avoid loadi
35
35
  show_info,
36
36
  )
37
37
  from .commands.analyze_code import manage_analyze_code
38
+ from .commands.dashboard import manage_dashboard
38
39
  from .parser import create_parser, preprocess_args
39
40
  from .utils import ensure_directories, setup_logging
40
41
 
@@ -389,6 +390,14 @@ def _execute_command(command: str, args) -> int:
389
390
 
390
391
  result = execute_run_guarded(args)
391
392
  return result if result is not None else 0
393
+
394
+ # Handle mpm-init command with lazy import
395
+ if command == "mpm-init":
396
+ # Lazy import to avoid loading unless needed
397
+ from .commands.mpm_init_handler import manage_mpm_init
398
+
399
+ result = manage_mpm_init(args)
400
+ return result if result is not None else 0
392
401
 
393
402
  # Map stable commands to their implementations
394
403
  command_map = {
@@ -400,6 +409,7 @@ def _execute_command(command: str, args) -> int:
400
409
  CLICommands.AGENT_MANAGER.value: manage_agent_manager,
401
410
  CLICommands.MEMORY.value: manage_memory,
402
411
  CLICommands.MONITOR.value: manage_monitor,
412
+ CLICommands.DASHBOARD.value: manage_dashboard,
403
413
  CLICommands.CONFIG.value: manage_config,
404
414
  CLICommands.CONFIGURE.value: manage_configure,
405
415
  CLICommands.AGGREGATE.value: aggregate_command,
@@ -408,6 +418,7 @@ def _execute_command(command: str, args) -> int:
408
418
  CLICommands.MCP.value: manage_mcp,
409
419
  CLICommands.DOCTOR.value: run_doctor,
410
420
  "debug": manage_debug, # Add debug command
421
+ "mpm-init": None, # Will be handled separately with lazy import
411
422
  }
412
423
 
413
424
  # Execute command if found
@@ -306,7 +306,8 @@ class AnalyzeCommand(BaseCommand):
306
306
  text=True,
307
307
  cwd=str(args.target),
308
308
  env=env,
309
- timeout=600, check=False, # 10 minute timeout
309
+ timeout=600,
310
+ check=False, # 10 minute timeout
310
311
  )
311
312
 
312
313
  if result.returncode != 0:
@@ -574,16 +574,17 @@ class ConfigureCommand(BaseCommand):
574
574
  self.console.print("[bold]Current Template:[/bold]")
575
575
  # Truncate for display if too large
576
576
  display_template = template.copy()
577
- if "instructions" in display_template and isinstance(
578
- display_template["instructions"], dict
579
- ) and (
580
- "custom_instructions" in display_template["instructions"]
581
- and len(str(display_template["instructions"]["custom_instructions"]))
582
- > 200
577
+ if (
578
+ "instructions" in display_template
579
+ and isinstance(display_template["instructions"], dict)
580
+ and (
581
+ "custom_instructions" in display_template["instructions"]
582
+ and len(str(display_template["instructions"]["custom_instructions"]))
583
+ > 200
584
+ )
583
585
  ):
584
586
  display_template["instructions"]["custom_instructions"] = (
585
- display_template["instructions"]["custom_instructions"][:200]
586
- + "..."
587
+ display_template["instructions"]["custom_instructions"][:200] + "..."
587
588
  )
588
589
 
589
590
  json_str = json.dumps(display_template, indent=2)
@@ -1623,7 +1623,9 @@ class ConfigureTUI(App):
1623
1623
  Binding("ctrl+left", "focus_prev_pane", "Prev Pane", show=False),
1624
1624
  ]
1625
1625
 
1626
- def __init__(self, current_scope: str = "project", project_dir: Optional[Path] = None):
1626
+ def __init__(
1627
+ self, current_scope: str = "project", project_dir: Optional[Path] = None
1628
+ ):
1627
1629
  super().__init__()
1628
1630
  self.current_scope = current_scope
1629
1631
  self.project_dir = project_dir or Path.cwd()
@@ -0,0 +1,288 @@
1
+ """
2
+ Dashboard command implementation for claude-mpm.
3
+
4
+ WHY: This module provides CLI commands for managing the web dashboard interface,
5
+ allowing users to start, stop, check status, and open the dashboard in a browser.
6
+
7
+ DESIGN DECISIONS:
8
+ - Use DashboardLauncher service for consistent dashboard management
9
+ - Support both foreground and background operation modes
10
+ - Integrate with SocketIO server for real-time event streaming
11
+ - Provide browser auto-opening functionality
12
+ """
13
+
14
+ import signal
15
+ import sys
16
+ import time
17
+ from typing import Optional
18
+
19
+ from ...constants import DashboardCommands
20
+ from ...services.cli.dashboard_launcher import DashboardLauncher
21
+ from ...services.port_manager import PortManager
22
+ from ...services.socketio.server.main import SocketIOServer
23
+ from ..shared import BaseCommand, CommandResult
24
+
25
+
26
+ class DashboardCommand(BaseCommand):
27
+ """Dashboard command for managing the web dashboard interface."""
28
+
29
+ def __init__(self):
30
+ super().__init__("dashboard")
31
+ self.dashboard_launcher = DashboardLauncher(self.logger)
32
+ self.port_manager = PortManager()
33
+ self.server = None
34
+
35
+ def validate_args(self, args) -> Optional[str]:
36
+ """Validate command arguments."""
37
+ if hasattr(args, "dashboard_command") and args.dashboard_command:
38
+ valid_commands = [cmd.value for cmd in DashboardCommands]
39
+ if args.dashboard_command not in valid_commands:
40
+ return f"Unknown dashboard command: {args.dashboard_command}. Valid commands: {', '.join(valid_commands)}"
41
+ return None
42
+
43
+ def run(self, args) -> CommandResult:
44
+ """Execute the dashboard command."""
45
+ try:
46
+ # Handle default case (no subcommand) - default to status
47
+ if not hasattr(args, "dashboard_command") or not args.dashboard_command:
48
+ return self._status_dashboard(args)
49
+
50
+ # Route to specific subcommand handlers
51
+ command_map = {
52
+ DashboardCommands.START.value: self._start_dashboard,
53
+ DashboardCommands.STOP.value: self._stop_dashboard,
54
+ DashboardCommands.STATUS.value: self._status_dashboard,
55
+ DashboardCommands.OPEN.value: self._open_dashboard,
56
+ }
57
+
58
+ if args.dashboard_command in command_map:
59
+ return command_map[args.dashboard_command](args)
60
+
61
+ return CommandResult.error_result(
62
+ f"Unknown dashboard command: {args.dashboard_command}"
63
+ )
64
+
65
+ except Exception as e:
66
+ self.logger.error(f"Error executing dashboard command: {e}", exc_info=True)
67
+ return CommandResult.error_result(f"Error executing dashboard command: {e}")
68
+
69
+ def _start_dashboard(self, args) -> CommandResult:
70
+ """Start the dashboard server."""
71
+ port = getattr(args, "port", 8765)
72
+ host = getattr(args, "host", "localhost")
73
+ background = getattr(args, "background", False)
74
+
75
+ self.logger.info(
76
+ f"Starting dashboard on {host}:{port} (background: {background})"
77
+ )
78
+
79
+ # Check if dashboard is already running
80
+ if self.dashboard_launcher.is_dashboard_running(port):
81
+ dashboard_url = self.dashboard_launcher.get_dashboard_url(port)
82
+ return CommandResult.success_result(
83
+ f"Dashboard already running at {dashboard_url}",
84
+ data={"url": dashboard_url, "port": port},
85
+ )
86
+
87
+ if background:
88
+ # Use the dashboard launcher for background mode
89
+ success, browser_opened = self.dashboard_launcher.launch_dashboard(
90
+ port=port, monitor_mode=True
91
+ )
92
+ if success:
93
+ dashboard_url = self.dashboard_launcher.get_dashboard_url(port)
94
+ return CommandResult.success_result(
95
+ f"Dashboard started at {dashboard_url}",
96
+ data={
97
+ "url": dashboard_url,
98
+ "port": port,
99
+ "browser_opened": browser_opened,
100
+ },
101
+ )
102
+ return CommandResult.error_result("Failed to start dashboard in background")
103
+ # Run in foreground mode - directly start the SocketIO server
104
+ try:
105
+ print(f"Starting dashboard server on {host}:{port}...")
106
+ print("Press Ctrl+C to stop the server")
107
+
108
+ # Create and start the SocketIO server
109
+ self.server = SocketIOServer(host=host, port=port)
110
+
111
+ # Set up signal handlers for graceful shutdown
112
+ def signal_handler(signum, frame):
113
+ print("\nShutting down dashboard server...")
114
+ if self.server:
115
+ self.server.stop_sync()
116
+ sys.exit(0)
117
+
118
+ signal.signal(signal.SIGINT, signal_handler)
119
+ signal.signal(signal.SIGTERM, signal_handler)
120
+
121
+ # Start the server (this starts in background thread)
122
+ self.server.start_sync()
123
+
124
+ # Keep the main thread alive while server is running
125
+ # The server runs in a background thread, so we need to block here
126
+ try:
127
+ while self.server.is_running():
128
+ time.sleep(1)
129
+ except KeyboardInterrupt:
130
+ # Ctrl+C pressed, stop the server
131
+ pass
132
+
133
+ # Server has stopped or user interrupted
134
+ if self.server:
135
+ self.server.stop_sync()
136
+
137
+ return CommandResult.success_result("Dashboard server stopped")
138
+
139
+ except KeyboardInterrupt:
140
+ print("\nDashboard server stopped by user")
141
+ return CommandResult.success_result("Dashboard server stopped")
142
+ except Exception as e:
143
+ return CommandResult.error_result(f"Failed to start dashboard: {e}")
144
+
145
+ def _stop_dashboard(self, args) -> CommandResult:
146
+ """Stop the dashboard server."""
147
+ port = getattr(args, "port", 8765)
148
+
149
+ self.logger.info(f"Stopping dashboard on port {port}")
150
+
151
+ if not self.dashboard_launcher.is_dashboard_running(port):
152
+ return CommandResult.success_result(f"No dashboard running on port {port}")
153
+
154
+ if self.dashboard_launcher.stop_dashboard(port):
155
+ return CommandResult.success_result(f"Dashboard stopped on port {port}")
156
+
157
+ return CommandResult.error_result(f"Failed to stop dashboard on port {port}")
158
+
159
+ def _status_dashboard(self, args) -> CommandResult:
160
+ """Check dashboard server status."""
161
+ verbose = getattr(args, "verbose", False)
162
+ show_ports = getattr(args, "show_ports", False)
163
+
164
+ # Check default port first
165
+ default_port = 8765
166
+ dashboard_running = self.dashboard_launcher.is_dashboard_running(default_port)
167
+
168
+ status_data = {
169
+ "running": dashboard_running,
170
+ "default_port": default_port,
171
+ }
172
+
173
+ if dashboard_running:
174
+ status_data["url"] = self.dashboard_launcher.get_dashboard_url(default_port)
175
+
176
+ # Check all ports if requested
177
+ if show_ports:
178
+ port_status = {}
179
+ for port in range(8765, 8786):
180
+ is_running = self.dashboard_launcher.is_dashboard_running(port)
181
+ port_status[port] = {
182
+ "running": is_running,
183
+ "url": (
184
+ self.dashboard_launcher.get_dashboard_url(port)
185
+ if is_running
186
+ else None
187
+ ),
188
+ }
189
+ status_data["ports"] = port_status
190
+
191
+ # Get active instances from port manager
192
+ self.port_manager.cleanup_dead_instances()
193
+ active_instances = self.port_manager.list_active_instances()
194
+ if active_instances:
195
+ status_data["active_instances"] = active_instances
196
+
197
+ if verbose:
198
+ # Add more detailed information
199
+ import socket
200
+
201
+ status_data["hostname"] = socket.gethostname()
202
+ status_data["can_bind"] = self._check_port_available(default_port)
203
+
204
+ # Format output message
205
+ if dashboard_running:
206
+ message = f"Dashboard is running at {status_data['url']}"
207
+ else:
208
+ message = "Dashboard is not running"
209
+
210
+ return CommandResult.success_result(message, data=status_data)
211
+
212
+ def _open_dashboard(self, args) -> CommandResult:
213
+ """Open the dashboard in a browser, starting it if necessary."""
214
+ port = getattr(args, "port", 8765)
215
+
216
+ # Check if dashboard is running
217
+ if not self.dashboard_launcher.is_dashboard_running(port):
218
+ self.logger.info("Dashboard not running, starting it first...")
219
+ # Start dashboard in background
220
+ success, browser_opened = self.dashboard_launcher.launch_dashboard(
221
+ port=port, monitor_mode=True
222
+ )
223
+ if success:
224
+ dashboard_url = self.dashboard_launcher.get_dashboard_url(port)
225
+ return CommandResult.success_result(
226
+ f"Dashboard started and opened at {dashboard_url}",
227
+ data={
228
+ "url": dashboard_url,
229
+ "port": port,
230
+ "browser_opened": browser_opened,
231
+ },
232
+ )
233
+ return CommandResult.error_result("Failed to start and open dashboard")
234
+ # Dashboard already running, just open browser
235
+ dashboard_url = self.dashboard_launcher.get_dashboard_url(port)
236
+ if self.dashboard_launcher._open_browser(dashboard_url):
237
+ return CommandResult.success_result(
238
+ f"Opened dashboard at {dashboard_url}",
239
+ data={"url": dashboard_url, "port": port},
240
+ )
241
+ return CommandResult.success_result(
242
+ f"Dashboard running at {dashboard_url} (could not auto-open browser)",
243
+ data={"url": dashboard_url, "port": port},
244
+ )
245
+
246
+ def _check_port_available(self, port: int) -> bool:
247
+ """Check if a port is available for binding."""
248
+ import socket
249
+
250
+ try:
251
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
252
+ s.bind(("", port))
253
+ return True
254
+ except OSError:
255
+ return False
256
+
257
+
258
+ def manage_dashboard(args) -> int:
259
+ """
260
+ Main entry point for dashboard command.
261
+
262
+ Args:
263
+ args: Parsed command line arguments
264
+
265
+ Returns:
266
+ Exit code (0 for success, non-zero for error)
267
+ """
268
+ command = DashboardCommand()
269
+ error = command.validate_args(args)
270
+
271
+ if error:
272
+ command.logger.error(error)
273
+ print(f"Error: {error}")
274
+ return 1
275
+
276
+ result = command.run(args)
277
+
278
+ if result.success:
279
+ if result.message:
280
+ print(result.message)
281
+ if result.data and getattr(args, "verbose", False):
282
+ import json
283
+
284
+ print(json.dumps(result.data, indent=2))
285
+ return 0
286
+ if result.message:
287
+ print(f"Error: {result.message}")
288
+ return 1
@@ -389,7 +389,6 @@ def debug_agents(args, logger):
389
389
  # List deployed agents
390
390
  from pathlib import Path
391
391
 
392
-
393
392
  print("\n🤖 Deployed Agents:")
394
393
  print("=" * 60)
395
394