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.
- claude_mpm/VERSION +1 -1
- claude_mpm/cli/__init__.py +11 -0
- claude_mpm/cli/commands/analyze.py +2 -1
- claude_mpm/cli/commands/configure.py +9 -8
- claude_mpm/cli/commands/configure_tui.py +3 -1
- claude_mpm/cli/commands/dashboard.py +288 -0
- claude_mpm/cli/commands/debug.py +0 -1
- claude_mpm/cli/commands/mpm_init.py +427 -0
- claude_mpm/cli/commands/mpm_init_handler.py +83 -0
- claude_mpm/cli/parsers/base_parser.py +15 -0
- claude_mpm/cli/parsers/dashboard_parser.py +113 -0
- claude_mpm/cli/parsers/mpm_init_parser.py +122 -0
- claude_mpm/constants.py +10 -0
- claude_mpm/dashboard/analysis_runner.py +52 -25
- claude_mpm/dashboard/static/built/components/activity-tree.js +1 -1
- claude_mpm/dashboard/static/built/components/code-tree.js +2 -0
- claude_mpm/dashboard/static/built/components/code-viewer.js +2 -0
- claude_mpm/dashboard/static/built/components/event-viewer.js +1 -1
- claude_mpm/dashboard/static/built/dashboard.js +1 -1
- claude_mpm/dashboard/static/built/socket-client.js +1 -1
- claude_mpm/dashboard/static/css/code-tree.css +330 -1
- claude_mpm/dashboard/static/dist/components/activity-tree.js +1 -1
- claude_mpm/dashboard/static/dist/components/code-tree.js +1 -1
- claude_mpm/dashboard/static/dist/components/event-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/activity-tree.js +212 -13
- claude_mpm/dashboard/static/js/components/code-tree.js +1999 -821
- claude_mpm/dashboard/static/js/components/event-viewer.js +58 -19
- claude_mpm/dashboard/static/js/dashboard.js +15 -3
- claude_mpm/dashboard/static/js/socket-client.js +74 -32
- claude_mpm/dashboard/templates/index.html +9 -11
- claude_mpm/services/agents/memory/memory_format_service.py +3 -1
- claude_mpm/services/cli/agent_cleanup_service.py +1 -4
- claude_mpm/services/cli/startup_checker.py +0 -1
- claude_mpm/services/core/cache_manager.py +0 -1
- claude_mpm/services/socketio/event_normalizer.py +64 -0
- claude_mpm/services/socketio/handlers/code_analysis.py +502 -0
- claude_mpm/services/socketio/server/connection_manager.py +3 -1
- claude_mpm/tools/code_tree_analyzer.py +843 -25
- claude_mpm/tools/code_tree_builder.py +0 -1
- claude_mpm/tools/code_tree_events.py +113 -15
- {claude_mpm-4.1.10.dist-info → claude_mpm-4.1.11.dist-info}/METADATA +2 -1
- {claude_mpm-4.1.10.dist-info → claude_mpm-4.1.11.dist-info}/RECORD +48 -41
- {claude_mpm-4.1.10.dist-info → claude_mpm-4.1.11.dist-info}/WHEEL +0 -0
- {claude_mpm-4.1.10.dist-info → claude_mpm-4.1.11.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.1.10.dist-info → claude_mpm-4.1.11.dist-info}/licenses/LICENSE +0 -0
- {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.
|
|
1
|
+
4.1.11
|
claude_mpm/cli/__init__.py
CHANGED
|
@@ -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
|
|
@@ -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
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
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__(
|
|
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
|