claude-mpm 4.1.8__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/agents/INSTRUCTIONS.md +26 -1
- claude_mpm/agents/agents_metadata.py +57 -0
- claude_mpm/agents/templates/.claude-mpm/memories/README.md +17 -0
- claude_mpm/agents/templates/.claude-mpm/memories/engineer_memories.md +3 -0
- claude_mpm/agents/templates/agent-manager.json +263 -17
- claude_mpm/agents/templates/agentic_coder_optimizer.json +222 -0
- claude_mpm/agents/templates/code_analyzer.json +18 -8
- claude_mpm/agents/templates/engineer.json +1 -1
- claude_mpm/agents/templates/logs/prompts/agent_engineer_20250826_014258_728.md +39 -0
- claude_mpm/agents/templates/qa.json +1 -1
- claude_mpm/agents/templates/research.json +1 -1
- claude_mpm/cli/__init__.py +15 -0
- claude_mpm/cli/commands/__init__.py +6 -0
- claude_mpm/cli/commands/analyze.py +548 -0
- claude_mpm/cli/commands/analyze_code.py +524 -0
- claude_mpm/cli/commands/configure.py +78 -28
- claude_mpm/cli/commands/configure_tui.py +62 -60
- claude_mpm/cli/commands/dashboard.py +288 -0
- claude_mpm/cli/commands/debug.py +1386 -0
- claude_mpm/cli/commands/mpm_init.py +427 -0
- claude_mpm/cli/commands/mpm_init_handler.py +83 -0
- claude_mpm/cli/parsers/analyze_code_parser.py +170 -0
- claude_mpm/cli/parsers/analyze_parser.py +135 -0
- claude_mpm/cli/parsers/base_parser.py +44 -0
- claude_mpm/cli/parsers/dashboard_parser.py +113 -0
- claude_mpm/cli/parsers/debug_parser.py +319 -0
- claude_mpm/cli/parsers/mpm_init_parser.py +122 -0
- claude_mpm/constants.py +13 -1
- claude_mpm/core/framework_loader.py +148 -6
- claude_mpm/core/log_manager.py +16 -13
- claude_mpm/core/logger.py +1 -1
- claude_mpm/core/unified_agent_registry.py +1 -1
- claude_mpm/dashboard/.claude-mpm/socketio-instances.json +1 -0
- claude_mpm/dashboard/analysis_runner.py +455 -0
- claude_mpm/dashboard/static/built/components/activity-tree.js +2 -0
- claude_mpm/dashboard/static/built/components/agent-inference.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/components/file-tool-tracker.js +1 -1
- claude_mpm/dashboard/static/built/components/module-viewer.js +1 -1
- claude_mpm/dashboard/static/built/components/session-manager.js +1 -1
- claude_mpm/dashboard/static/built/components/working-directory.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/activity.css +549 -0
- claude_mpm/dashboard/static/css/code-tree.css +1175 -0
- claude_mpm/dashboard/static/css/dashboard.css +245 -0
- claude_mpm/dashboard/static/dist/components/activity-tree.js +2 -0
- claude_mpm/dashboard/static/dist/components/code-tree.js +2 -0
- claude_mpm/dashboard/static/dist/components/code-viewer.js +2 -0
- claude_mpm/dashboard/static/dist/components/event-viewer.js +1 -1
- claude_mpm/dashboard/static/dist/components/session-manager.js +1 -1
- claude_mpm/dashboard/static/dist/components/working-directory.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 +1338 -0
- claude_mpm/dashboard/static/js/components/code-tree.js +2535 -0
- claude_mpm/dashboard/static/js/components/code-viewer.js +480 -0
- claude_mpm/dashboard/static/js/components/event-viewer.js +59 -9
- claude_mpm/dashboard/static/js/components/session-manager.js +40 -4
- claude_mpm/dashboard/static/js/components/socket-manager.js +12 -0
- claude_mpm/dashboard/static/js/components/ui-state-manager.js +4 -0
- claude_mpm/dashboard/static/js/components/working-directory.js +17 -1
- claude_mpm/dashboard/static/js/dashboard.js +51 -0
- claude_mpm/dashboard/static/js/socket-client.js +465 -29
- claude_mpm/dashboard/templates/index.html +182 -4
- claude_mpm/hooks/claude_hooks/hook_handler.py +182 -5
- claude_mpm/hooks/claude_hooks/installer.py +386 -113
- claude_mpm/scripts/claude-hook-handler.sh +161 -0
- claude_mpm/scripts/socketio_daemon.py +121 -8
- claude_mpm/services/agents/deployment/agent_lifecycle_manager_refactored.py +2 -2
- claude_mpm/services/agents/deployment/agent_record_service.py +1 -2
- claude_mpm/services/agents/memory/memory_format_service.py +1 -3
- claude_mpm/services/cli/agent_cleanup_service.py +1 -5
- claude_mpm/services/cli/agent_dependency_service.py +1 -1
- claude_mpm/services/cli/agent_validation_service.py +3 -4
- claude_mpm/services/cli/dashboard_launcher.py +2 -3
- claude_mpm/services/cli/startup_checker.py +0 -11
- claude_mpm/services/core/cache_manager.py +1 -3
- claude_mpm/services/core/path_resolver.py +1 -4
- claude_mpm/services/core/service_container.py +2 -2
- claude_mpm/services/diagnostics/checks/instructions_check.py +1 -2
- claude_mpm/services/infrastructure/monitoring/__init__.py +11 -11
- claude_mpm/services/infrastructure/monitoring.py +11 -11
- claude_mpm/services/project/architecture_analyzer.py +1 -1
- claude_mpm/services/project/dependency_analyzer.py +4 -4
- claude_mpm/services/project/language_analyzer.py +3 -3
- claude_mpm/services/project/metrics_collector.py +3 -6
- claude_mpm/services/socketio/event_normalizer.py +64 -0
- claude_mpm/services/socketio/handlers/__init__.py +2 -0
- claude_mpm/services/socketio/handlers/code_analysis.py +672 -0
- claude_mpm/services/socketio/handlers/registry.py +2 -0
- claude_mpm/services/socketio/server/connection_manager.py +6 -4
- claude_mpm/services/socketio/server/core.py +100 -11
- claude_mpm/services/socketio/server/main.py +8 -2
- claude_mpm/services/visualization/__init__.py +19 -0
- claude_mpm/services/visualization/mermaid_generator.py +938 -0
- claude_mpm/tools/__main__.py +208 -0
- claude_mpm/tools/code_tree_analyzer.py +1596 -0
- claude_mpm/tools/code_tree_builder.py +631 -0
- claude_mpm/tools/code_tree_events.py +416 -0
- claude_mpm/tools/socketio_debug.py +671 -0
- {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/METADATA +2 -1
- {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/RECORD +110 -74
- claude_mpm/agents/schema/agent_schema.json +0 -314
- {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/WHEEL +0 -0
- {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,671 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Professional SocketIO debugging tool for dashboard development.
|
|
4
|
+
|
|
5
|
+
This tool provides real-time monitoring and analysis of SocketIO events
|
|
6
|
+
for developers working on the claude-mpm dashboard.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import asyncio
|
|
11
|
+
import json
|
|
12
|
+
import signal
|
|
13
|
+
import sys
|
|
14
|
+
import time
|
|
15
|
+
from dataclasses import dataclass, field
|
|
16
|
+
from datetime import datetime
|
|
17
|
+
from enum import Enum
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Any, Dict, List, Optional, Set
|
|
20
|
+
|
|
21
|
+
import socketio
|
|
22
|
+
|
|
23
|
+
# Try to import Rich for enhanced output
|
|
24
|
+
try:
|
|
25
|
+
from rich.console import Console
|
|
26
|
+
from rich.layout import Layout
|
|
27
|
+
from rich.live import Live
|
|
28
|
+
from rich.panel import Panel
|
|
29
|
+
from rich.table import Table
|
|
30
|
+
from rich.text import Text
|
|
31
|
+
|
|
32
|
+
RICH_AVAILABLE = True
|
|
33
|
+
console = Console()
|
|
34
|
+
except ImportError:
|
|
35
|
+
RICH_AVAILABLE = False
|
|
36
|
+
console = None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class DisplayMode(Enum):
|
|
40
|
+
"""Display modes for event output."""
|
|
41
|
+
|
|
42
|
+
LIVE = "live"
|
|
43
|
+
SUMMARY = "summary"
|
|
44
|
+
FILTERED = "filtered"
|
|
45
|
+
RAW = "raw"
|
|
46
|
+
PRETTY = "pretty"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class ConnectionStatus(Enum):
|
|
50
|
+
"""Connection status states."""
|
|
51
|
+
|
|
52
|
+
DISCONNECTED = "❌ Disconnected"
|
|
53
|
+
CONNECTING = "⏳ Connecting"
|
|
54
|
+
CONNECTED = "✅ Connected"
|
|
55
|
+
ERROR = "⚠️ Error"
|
|
56
|
+
RECONNECTING = "🔄 Reconnecting"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass
|
|
60
|
+
class EventStats:
|
|
61
|
+
"""Statistics for event tracking."""
|
|
62
|
+
|
|
63
|
+
total_count: int = 0
|
|
64
|
+
event_types: Dict[str, int] = field(default_factory=dict)
|
|
65
|
+
tool_usage: Dict[str, int] = field(default_factory=dict)
|
|
66
|
+
sessions: Set[str] = field(default_factory=set)
|
|
67
|
+
first_event_time: Optional[float] = None
|
|
68
|
+
last_event_time: Optional[float] = None
|
|
69
|
+
events_per_second: float = 0.0
|
|
70
|
+
|
|
71
|
+
def update_rate(self):
|
|
72
|
+
"""Update events per second rate."""
|
|
73
|
+
if self.first_event_time and self.last_event_time:
|
|
74
|
+
duration = self.last_event_time - self.first_event_time
|
|
75
|
+
if duration > 0:
|
|
76
|
+
self.events_per_second = self.total_count / duration
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class SocketIODebugger:
|
|
80
|
+
"""Professional SocketIO debugging tool."""
|
|
81
|
+
|
|
82
|
+
def __init__(
|
|
83
|
+
self,
|
|
84
|
+
host: str = "localhost",
|
|
85
|
+
port: int = 8765,
|
|
86
|
+
mode: DisplayMode = DisplayMode.LIVE,
|
|
87
|
+
filter_types: Optional[List[str]] = None,
|
|
88
|
+
output_file: Optional[Path] = None,
|
|
89
|
+
quiet: bool = False,
|
|
90
|
+
show_raw: bool = False,
|
|
91
|
+
max_reconnect_attempts: int = 10,
|
|
92
|
+
reconnect_delay: float = 1.0,
|
|
93
|
+
):
|
|
94
|
+
"""Initialize the debugger."""
|
|
95
|
+
self.host = host
|
|
96
|
+
self.port = port
|
|
97
|
+
self.url = f"http://{host}:{port}"
|
|
98
|
+
self.mode = mode
|
|
99
|
+
self.filter_types = filter_types or []
|
|
100
|
+
self.output_file = output_file
|
|
101
|
+
self.quiet = quiet
|
|
102
|
+
self.show_raw = show_raw
|
|
103
|
+
self.max_reconnect_attempts = max_reconnect_attempts
|
|
104
|
+
self.reconnect_delay = reconnect_delay
|
|
105
|
+
|
|
106
|
+
# SocketIO client
|
|
107
|
+
self.sio = socketio.Client(
|
|
108
|
+
reconnection=True,
|
|
109
|
+
reconnection_attempts=max_reconnect_attempts,
|
|
110
|
+
reconnection_delay=reconnect_delay,
|
|
111
|
+
reconnection_delay_max=30,
|
|
112
|
+
logger=False,
|
|
113
|
+
engineio_logger=False,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# State tracking
|
|
117
|
+
self.status = ConnectionStatus.DISCONNECTED
|
|
118
|
+
self.stats = EventStats()
|
|
119
|
+
self.events: List[Dict[str, Any]] = []
|
|
120
|
+
self.connection_start = None
|
|
121
|
+
self.latency = 0.0
|
|
122
|
+
self.running = True
|
|
123
|
+
self.output_buffer = []
|
|
124
|
+
|
|
125
|
+
# Setup event handlers
|
|
126
|
+
self._setup_handlers()
|
|
127
|
+
|
|
128
|
+
# Setup signal handlers
|
|
129
|
+
signal.signal(signal.SIGINT, self._handle_interrupt)
|
|
130
|
+
signal.signal(signal.SIGTERM, self._handle_interrupt)
|
|
131
|
+
|
|
132
|
+
def _setup_handlers(self):
|
|
133
|
+
"""Setup SocketIO event handlers."""
|
|
134
|
+
|
|
135
|
+
@self.sio.event
|
|
136
|
+
def connect():
|
|
137
|
+
"""Handle connection event."""
|
|
138
|
+
self.status = ConnectionStatus.CONNECTED
|
|
139
|
+
self.connection_start = time.time()
|
|
140
|
+
self._log("success", f"Connected to {self.url}")
|
|
141
|
+
self._log("info", f"Socket ID: {self.sio.sid}")
|
|
142
|
+
|
|
143
|
+
# Request initial status and history
|
|
144
|
+
self.sio.emit("get_status")
|
|
145
|
+
self.sio.emit("get_history", {"limit": 50, "event_types": []})
|
|
146
|
+
|
|
147
|
+
@self.sio.event
|
|
148
|
+
def disconnect():
|
|
149
|
+
"""Handle disconnection event."""
|
|
150
|
+
self.status = ConnectionStatus.DISCONNECTED
|
|
151
|
+
self._log("warning", "Disconnected from server")
|
|
152
|
+
|
|
153
|
+
@self.sio.event
|
|
154
|
+
def connect_error(data):
|
|
155
|
+
"""Handle connection error."""
|
|
156
|
+
self.status = ConnectionStatus.ERROR
|
|
157
|
+
self._log("error", f"Connection error: {data}")
|
|
158
|
+
|
|
159
|
+
@self.sio.event
|
|
160
|
+
def claude_event(data):
|
|
161
|
+
"""Handle Claude event."""
|
|
162
|
+
self._process_event(data)
|
|
163
|
+
|
|
164
|
+
@self.sio.event
|
|
165
|
+
def status(data):
|
|
166
|
+
"""Handle status response."""
|
|
167
|
+
self._log("info", "Server Status:")
|
|
168
|
+
if not self.quiet:
|
|
169
|
+
self._format_json(data, indent=2)
|
|
170
|
+
|
|
171
|
+
@self.sio.event
|
|
172
|
+
def history(data):
|
|
173
|
+
"""Handle history response."""
|
|
174
|
+
if data.get("events"):
|
|
175
|
+
event_count = len(data["events"])
|
|
176
|
+
self._log("info", f"Received {event_count} historical events")
|
|
177
|
+
for event in data["events"]:
|
|
178
|
+
self._process_event(event, is_historical=True)
|
|
179
|
+
|
|
180
|
+
# Ping/pong for latency measurement
|
|
181
|
+
@self.sio.event
|
|
182
|
+
def pong(data):
|
|
183
|
+
"""Handle pong response for latency measurement."""
|
|
184
|
+
if "timestamp" in data:
|
|
185
|
+
self.latency = (time.time() - data["timestamp"]) * 1000
|
|
186
|
+
|
|
187
|
+
def _process_event(self, data: Dict[str, Any], is_historical: bool = False):
|
|
188
|
+
"""Process an incoming event."""
|
|
189
|
+
# Update statistics
|
|
190
|
+
self.stats.total_count += 1
|
|
191
|
+
event_type = data.get("type", "unknown")
|
|
192
|
+
|
|
193
|
+
# Track event types
|
|
194
|
+
self.stats.event_types[event_type] = (
|
|
195
|
+
self.stats.event_types.get(event_type, 0) + 1
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
# Track tool usage
|
|
199
|
+
if "tool" in data.get("data", {}):
|
|
200
|
+
tool_name = data["data"]["tool"]
|
|
201
|
+
self.stats.tool_usage[tool_name] = (
|
|
202
|
+
self.stats.tool_usage.get(tool_name, 0) + 1
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
# Track sessions
|
|
206
|
+
if "session_id" in data.get("data", {}):
|
|
207
|
+
self.stats.sessions.add(data["data"]["session_id"])
|
|
208
|
+
|
|
209
|
+
# Update timestamps
|
|
210
|
+
current_time = time.time()
|
|
211
|
+
if not self.stats.first_event_time:
|
|
212
|
+
self.stats.first_event_time = current_time
|
|
213
|
+
self.stats.last_event_time = current_time
|
|
214
|
+
self.stats.update_rate()
|
|
215
|
+
|
|
216
|
+
# Store event
|
|
217
|
+
self.events.append(data)
|
|
218
|
+
if len(self.events) > 10000: # Keep last 10k events
|
|
219
|
+
self.events = self.events[-10000:]
|
|
220
|
+
|
|
221
|
+
# Apply filtering
|
|
222
|
+
if self.filter_types and event_type not in self.filter_types:
|
|
223
|
+
return
|
|
224
|
+
|
|
225
|
+
# Display based on mode
|
|
226
|
+
if not self.quiet and not is_historical:
|
|
227
|
+
self._display_event(data)
|
|
228
|
+
|
|
229
|
+
# Write to file if specified
|
|
230
|
+
if self.output_file:
|
|
231
|
+
self._write_to_file(data)
|
|
232
|
+
|
|
233
|
+
def _display_event(self, data: Dict[str, Any]):
|
|
234
|
+
"""Display an event based on current mode."""
|
|
235
|
+
timestamp = datetime.now().strftime("%H:%M:%S.%f")[:-3]
|
|
236
|
+
event_type = data.get("type", "unknown")
|
|
237
|
+
event_data = data.get("data", {})
|
|
238
|
+
|
|
239
|
+
if self.show_raw or self.mode == DisplayMode.RAW:
|
|
240
|
+
# Raw JSON output
|
|
241
|
+
print(json.dumps(data, indent=2))
|
|
242
|
+
print("-" * 50)
|
|
243
|
+
elif self.mode == DisplayMode.PRETTY or (
|
|
244
|
+
self.mode == DisplayMode.LIVE and RICH_AVAILABLE
|
|
245
|
+
):
|
|
246
|
+
# Pretty formatted output
|
|
247
|
+
self._display_pretty(timestamp, event_type, event_data)
|
|
248
|
+
else:
|
|
249
|
+
# Basic formatted output
|
|
250
|
+
self._display_basic(timestamp, event_type, event_data)
|
|
251
|
+
|
|
252
|
+
def _display_pretty(
|
|
253
|
+
self, timestamp: str, event_type: str, event_data: Dict[str, Any]
|
|
254
|
+
):
|
|
255
|
+
"""Display event with rich formatting."""
|
|
256
|
+
if not RICH_AVAILABLE:
|
|
257
|
+
return self._display_basic(timestamp, event_type, event_data)
|
|
258
|
+
|
|
259
|
+
# Event type icons
|
|
260
|
+
icons = {
|
|
261
|
+
"Start": "🚀",
|
|
262
|
+
"Stop": "🛑",
|
|
263
|
+
"SubagentStart": "🤖",
|
|
264
|
+
"SubagentStop": "🤖",
|
|
265
|
+
"PreToolUse": "🔧",
|
|
266
|
+
"PostToolUse": "✅",
|
|
267
|
+
"Error": "❌",
|
|
268
|
+
"Warning": "⚠️",
|
|
269
|
+
"Info": "ℹ️",
|
|
270
|
+
"MemoryUpdate": "🧠",
|
|
271
|
+
"ConfigChange": "⚙️",
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
icon = icons.get(event_type, "📨")
|
|
275
|
+
|
|
276
|
+
# Format the event
|
|
277
|
+
output = Text()
|
|
278
|
+
output.append(f"[{timestamp}] ", style="dim cyan")
|
|
279
|
+
output.append(f"{icon} {event_type}", style="bold yellow")
|
|
280
|
+
|
|
281
|
+
# Add key details
|
|
282
|
+
if "tool" in event_data:
|
|
283
|
+
output.append("\n├─ Tool: ", style="dim")
|
|
284
|
+
output.append(event_data["tool"], style="green")
|
|
285
|
+
|
|
286
|
+
if "session_id" in event_data:
|
|
287
|
+
output.append("\n├─ Session: ", style="dim")
|
|
288
|
+
output.append(event_data["session_id"][:8], style="blue")
|
|
289
|
+
|
|
290
|
+
if "agent" in event_data:
|
|
291
|
+
output.append("\n├─ Agent: ", style="dim")
|
|
292
|
+
output.append(event_data["agent"], style="magenta")
|
|
293
|
+
|
|
294
|
+
if "duration" in event_data:
|
|
295
|
+
output.append("\n├─ Duration: ", style="dim")
|
|
296
|
+
output.append(f"{event_data['duration']:.2f}s", style="cyan")
|
|
297
|
+
|
|
298
|
+
if "success" in event_data:
|
|
299
|
+
success_icon = "✓" if event_data["success"] else "✗"
|
|
300
|
+
success_style = "green" if event_data["success"] else "red"
|
|
301
|
+
output.append("\n└─ Result: ", style="dim")
|
|
302
|
+
output.append(f"{success_icon}", style=success_style)
|
|
303
|
+
|
|
304
|
+
console.print(output)
|
|
305
|
+
console.print("─" * 50, style="dim")
|
|
306
|
+
return None
|
|
307
|
+
|
|
308
|
+
def _display_basic(
|
|
309
|
+
self, timestamp: str, event_type: str, event_data: Dict[str, Any]
|
|
310
|
+
):
|
|
311
|
+
"""Display event with basic formatting."""
|
|
312
|
+
print(f"[{timestamp}] {event_type}")
|
|
313
|
+
|
|
314
|
+
if "tool" in event_data:
|
|
315
|
+
print(f" Tool: {event_data['tool']}")
|
|
316
|
+
if "session_id" in event_data:
|
|
317
|
+
print(f" Session: {event_data['session_id'][:8]}")
|
|
318
|
+
if "agent" in event_data:
|
|
319
|
+
print(f" Agent: {event_data['agent']}")
|
|
320
|
+
if "duration" in event_data:
|
|
321
|
+
print(f" Duration: {event_data['duration']:.2f}s")
|
|
322
|
+
if "success" in event_data:
|
|
323
|
+
print(f" Success: {event_data['success']}")
|
|
324
|
+
|
|
325
|
+
print("-" * 50)
|
|
326
|
+
|
|
327
|
+
def _display_summary(self):
|
|
328
|
+
"""Display event summary statistics."""
|
|
329
|
+
if RICH_AVAILABLE:
|
|
330
|
+
self._display_summary_rich()
|
|
331
|
+
else:
|
|
332
|
+
self._display_summary_basic()
|
|
333
|
+
|
|
334
|
+
def _display_summary_rich(self):
|
|
335
|
+
"""Display rich summary with tables."""
|
|
336
|
+
# Connection info panel
|
|
337
|
+
conn_info = Panel(
|
|
338
|
+
f"Server: {self.url}\n"
|
|
339
|
+
f"Status: {self.status.value}\n"
|
|
340
|
+
f"Latency: {self.latency:.0f}ms\n"
|
|
341
|
+
f"Uptime: {self._get_uptime()}",
|
|
342
|
+
title="Connection Info",
|
|
343
|
+
border_style="blue",
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
# Event statistics table
|
|
347
|
+
stats_table = Table(title="Event Statistics")
|
|
348
|
+
stats_table.add_column("Metric", style="cyan")
|
|
349
|
+
stats_table.add_column("Value", style="yellow")
|
|
350
|
+
|
|
351
|
+
stats_table.add_row("Total Events", str(self.stats.total_count))
|
|
352
|
+
stats_table.add_row("Events/Second", f"{self.stats.events_per_second:.2f}")
|
|
353
|
+
stats_table.add_row("Active Sessions", str(len(self.stats.sessions)))
|
|
354
|
+
|
|
355
|
+
# Event types table
|
|
356
|
+
types_table = Table(title="Event Types")
|
|
357
|
+
types_table.add_column("Event Type", style="magenta")
|
|
358
|
+
types_table.add_column("Count", style="green")
|
|
359
|
+
|
|
360
|
+
for event_type, count in sorted(
|
|
361
|
+
self.stats.event_types.items(), key=lambda x: x[1], reverse=True
|
|
362
|
+
)[:10]:
|
|
363
|
+
types_table.add_row(event_type, str(count))
|
|
364
|
+
|
|
365
|
+
# Tool usage table
|
|
366
|
+
if self.stats.tool_usage:
|
|
367
|
+
tools_table = Table(title="Tool Usage")
|
|
368
|
+
tools_table.add_column("Tool", style="blue")
|
|
369
|
+
tools_table.add_column("Count", style="yellow")
|
|
370
|
+
|
|
371
|
+
for tool, count in sorted(
|
|
372
|
+
self.stats.tool_usage.items(), key=lambda x: x[1], reverse=True
|
|
373
|
+
)[:10]:
|
|
374
|
+
tools_table.add_row(tool, str(count))
|
|
375
|
+
|
|
376
|
+
console.print(conn_info)
|
|
377
|
+
console.print(stats_table)
|
|
378
|
+
console.print(types_table)
|
|
379
|
+
console.print(tools_table)
|
|
380
|
+
else:
|
|
381
|
+
console.print(conn_info)
|
|
382
|
+
console.print(stats_table)
|
|
383
|
+
console.print(types_table)
|
|
384
|
+
|
|
385
|
+
def _display_summary_basic(self):
|
|
386
|
+
"""Display basic summary."""
|
|
387
|
+
print("\n" + "=" * 60)
|
|
388
|
+
print("SOCKETIO EVENT MONITOR SUMMARY")
|
|
389
|
+
print("=" * 60)
|
|
390
|
+
print(f"Server: {self.url}")
|
|
391
|
+
print(f"Status: {self.status.value}")
|
|
392
|
+
print(f"Latency: {self.latency:.0f}ms")
|
|
393
|
+
print(f"Uptime: {self._get_uptime()}")
|
|
394
|
+
print("-" * 60)
|
|
395
|
+
print(f"Total Events: {self.stats.total_count}")
|
|
396
|
+
print(f"Events/Second: {self.stats.events_per_second:.2f}")
|
|
397
|
+
print(f"Active Sessions: {len(self.stats.sessions)}")
|
|
398
|
+
print("-" * 60)
|
|
399
|
+
print("Event Types:")
|
|
400
|
+
for event_type, count in sorted(
|
|
401
|
+
self.stats.event_types.items(), key=lambda x: x[1], reverse=True
|
|
402
|
+
)[:10]:
|
|
403
|
+
print(f" {event_type}: {count}")
|
|
404
|
+
|
|
405
|
+
if self.stats.tool_usage:
|
|
406
|
+
print("-" * 60)
|
|
407
|
+
print("Tool Usage:")
|
|
408
|
+
for tool, count in sorted(
|
|
409
|
+
self.stats.tool_usage.items(), key=lambda x: x[1], reverse=True
|
|
410
|
+
)[:10]:
|
|
411
|
+
print(f" {tool}: {count}")
|
|
412
|
+
|
|
413
|
+
print("=" * 60)
|
|
414
|
+
|
|
415
|
+
def _get_uptime(self) -> str:
|
|
416
|
+
"""Get formatted uptime string."""
|
|
417
|
+
if not self.connection_start:
|
|
418
|
+
return "00:00:00"
|
|
419
|
+
|
|
420
|
+
uptime = time.time() - self.connection_start
|
|
421
|
+
hours = int(uptime // 3600)
|
|
422
|
+
minutes = int((uptime % 3600) // 60)
|
|
423
|
+
seconds = int(uptime % 60)
|
|
424
|
+
|
|
425
|
+
return f"{hours:02d}:{minutes:02d}:{seconds:02d}"
|
|
426
|
+
|
|
427
|
+
def _write_to_file(self, data: Dict[str, Any]):
|
|
428
|
+
"""Write event to output file."""
|
|
429
|
+
if not self.output_file:
|
|
430
|
+
return
|
|
431
|
+
|
|
432
|
+
try:
|
|
433
|
+
with open(self.output_file, "a") as f:
|
|
434
|
+
f.write(json.dumps(data) + "\n")
|
|
435
|
+
except Exception as e:
|
|
436
|
+
self._log("error", f"Failed to write to file: {e}")
|
|
437
|
+
|
|
438
|
+
def _format_json(self, data: Any, indent: int = 2):
|
|
439
|
+
"""Format and print JSON data."""
|
|
440
|
+
try:
|
|
441
|
+
formatted = json.dumps(data, indent=indent, default=str)
|
|
442
|
+
print(formatted)
|
|
443
|
+
except Exception as e:
|
|
444
|
+
print(f"Failed to format JSON: {e}")
|
|
445
|
+
print(data)
|
|
446
|
+
|
|
447
|
+
def _log(self, level: str, message: str):
|
|
448
|
+
"""Log a message with appropriate formatting."""
|
|
449
|
+
if self.quiet and level not in ["error", "critical"]:
|
|
450
|
+
return
|
|
451
|
+
|
|
452
|
+
timestamp = datetime.now().strftime("%H:%M:%S")
|
|
453
|
+
|
|
454
|
+
if RICH_AVAILABLE:
|
|
455
|
+
styles = {
|
|
456
|
+
"info": "blue",
|
|
457
|
+
"success": "green",
|
|
458
|
+
"warning": "yellow",
|
|
459
|
+
"error": "red bold",
|
|
460
|
+
"critical": "red bold on white",
|
|
461
|
+
}
|
|
462
|
+
style = styles.get(level, "white")
|
|
463
|
+
console.print(f"[{timestamp}] {message}", style=style)
|
|
464
|
+
else:
|
|
465
|
+
prefixes = {
|
|
466
|
+
"info": "ℹ️",
|
|
467
|
+
"success": "✅",
|
|
468
|
+
"warning": "⚠️",
|
|
469
|
+
"error": "❌",
|
|
470
|
+
"critical": "🚨",
|
|
471
|
+
}
|
|
472
|
+
prefix = prefixes.get(level, "")
|
|
473
|
+
print(f"[{timestamp}] {prefix} {message}")
|
|
474
|
+
|
|
475
|
+
def _handle_interrupt(self, signum, frame):
|
|
476
|
+
"""Handle interrupt signal."""
|
|
477
|
+
self._log("info", "\nShutting down gracefully...")
|
|
478
|
+
self.running = False
|
|
479
|
+
self.stop()
|
|
480
|
+
|
|
481
|
+
async def _monitor_latency(self):
|
|
482
|
+
"""Monitor connection latency."""
|
|
483
|
+
while self.running and self.status == ConnectionStatus.CONNECTED:
|
|
484
|
+
try:
|
|
485
|
+
# Send ping with timestamp
|
|
486
|
+
self.sio.emit("ping", {"timestamp": time.time()})
|
|
487
|
+
await asyncio.sleep(5) # Check every 5 seconds
|
|
488
|
+
except Exception:
|
|
489
|
+
pass
|
|
490
|
+
|
|
491
|
+
def connect(self) -> bool:
|
|
492
|
+
"""Connect to the SocketIO server."""
|
|
493
|
+
self._log("info", f"Connecting to {self.url}...")
|
|
494
|
+
self.status = ConnectionStatus.CONNECTING
|
|
495
|
+
|
|
496
|
+
try:
|
|
497
|
+
self.sio.connect(self.url, wait_timeout=10)
|
|
498
|
+
return True
|
|
499
|
+
except Exception as e:
|
|
500
|
+
self.status = ConnectionStatus.ERROR
|
|
501
|
+
self._log("error", f"Failed to connect: {e}")
|
|
502
|
+
return False
|
|
503
|
+
|
|
504
|
+
def run(self):
|
|
505
|
+
"""Run the debugger."""
|
|
506
|
+
# Print header
|
|
507
|
+
if not self.quiet:
|
|
508
|
+
self._print_header()
|
|
509
|
+
|
|
510
|
+
# Connect to server
|
|
511
|
+
if not self.connect():
|
|
512
|
+
return False
|
|
513
|
+
|
|
514
|
+
# Start latency monitoring
|
|
515
|
+
loop = asyncio.new_event_loop()
|
|
516
|
+
asyncio.set_event_loop(loop)
|
|
517
|
+
latency_task = loop.create_task(self._monitor_latency())
|
|
518
|
+
|
|
519
|
+
try:
|
|
520
|
+
# Main monitoring loop
|
|
521
|
+
while self.running:
|
|
522
|
+
try:
|
|
523
|
+
if self.mode == DisplayMode.SUMMARY:
|
|
524
|
+
# In summary mode, update display periodically
|
|
525
|
+
time.sleep(1)
|
|
526
|
+
if not self.quiet:
|
|
527
|
+
# Clear screen and show summary
|
|
528
|
+
print("\033[2J\033[H") # Clear screen
|
|
529
|
+
self._display_summary()
|
|
530
|
+
else:
|
|
531
|
+
# In other modes, just keep connection alive
|
|
532
|
+
time.sleep(0.1)
|
|
533
|
+
|
|
534
|
+
# Check connection health
|
|
535
|
+
if not self.sio.connected and self.running:
|
|
536
|
+
self.status = ConnectionStatus.RECONNECTING
|
|
537
|
+
self._log(
|
|
538
|
+
"warning", "Connection lost, attempting to reconnect..."
|
|
539
|
+
)
|
|
540
|
+
self.connect()
|
|
541
|
+
|
|
542
|
+
except KeyboardInterrupt:
|
|
543
|
+
break
|
|
544
|
+
|
|
545
|
+
finally:
|
|
546
|
+
# Clean up
|
|
547
|
+
latency_task.cancel()
|
|
548
|
+
self.stop()
|
|
549
|
+
|
|
550
|
+
# Show final summary
|
|
551
|
+
if not self.quiet:
|
|
552
|
+
self._display_summary()
|
|
553
|
+
|
|
554
|
+
return True
|
|
555
|
+
|
|
556
|
+
def stop(self):
|
|
557
|
+
"""Stop the debugger and disconnect."""
|
|
558
|
+
self.running = False
|
|
559
|
+
if self.sio.connected:
|
|
560
|
+
self.sio.disconnect()
|
|
561
|
+
self._log("success", "Debugger stopped")
|
|
562
|
+
|
|
563
|
+
def _print_header(self):
|
|
564
|
+
"""Print the debugger header."""
|
|
565
|
+
if RICH_AVAILABLE:
|
|
566
|
+
console.print(
|
|
567
|
+
Panel.fit(
|
|
568
|
+
"[bold cyan]SocketIO Event Monitor v1.0[/bold cyan]\n"
|
|
569
|
+
"[dim]Professional debugging tool for claude-mpm dashboard[/dim]",
|
|
570
|
+
border_style="blue",
|
|
571
|
+
)
|
|
572
|
+
)
|
|
573
|
+
else:
|
|
574
|
+
print("╭─────────────────────────────────────────╮")
|
|
575
|
+
print("│ SocketIO Event Monitor v1.0 │")
|
|
576
|
+
print("├─────────────────────────────────────────┤")
|
|
577
|
+
print(f"│ Server: {self.url:<30} │")
|
|
578
|
+
print(f"│ Mode: {self.mode.value:<34} │")
|
|
579
|
+
print("╰─────────────────────────────────────────╯")
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
def main():
|
|
583
|
+
"""Main entry point for standalone execution."""
|
|
584
|
+
parser = argparse.ArgumentParser(
|
|
585
|
+
description="SocketIO debugging tool for claude-mpm dashboard development",
|
|
586
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
587
|
+
epilog="""
|
|
588
|
+
Examples:
|
|
589
|
+
# Monitor all events in real-time
|
|
590
|
+
%(prog)s
|
|
591
|
+
|
|
592
|
+
# Show event summary statistics
|
|
593
|
+
%(prog)s --summary
|
|
594
|
+
|
|
595
|
+
# Filter specific event types
|
|
596
|
+
%(prog)s --filter PreToolUse PostToolUse
|
|
597
|
+
|
|
598
|
+
# Save events to file for analysis
|
|
599
|
+
%(prog)s --output events.jsonl
|
|
600
|
+
|
|
601
|
+
# Connect to specific server
|
|
602
|
+
%(prog)s --host localhost --port 8765
|
|
603
|
+
|
|
604
|
+
# Quiet mode for scripts
|
|
605
|
+
%(prog)s --quiet --output events.jsonl
|
|
606
|
+
""",
|
|
607
|
+
)
|
|
608
|
+
|
|
609
|
+
# Connection options
|
|
610
|
+
parser.add_argument(
|
|
611
|
+
"--host", default="localhost", help="SocketIO server host (default: localhost)"
|
|
612
|
+
)
|
|
613
|
+
parser.add_argument(
|
|
614
|
+
"--port", type=int, default=8765, help="SocketIO server port (default: 8765)"
|
|
615
|
+
)
|
|
616
|
+
|
|
617
|
+
# Display options
|
|
618
|
+
parser.add_argument(
|
|
619
|
+
"--mode",
|
|
620
|
+
choices=["live", "summary", "filtered", "raw", "pretty"],
|
|
621
|
+
default="live",
|
|
622
|
+
help="Display mode (default: live)",
|
|
623
|
+
)
|
|
624
|
+
parser.add_argument(
|
|
625
|
+
"--filter", nargs="+", dest="filter_types", help="Filter specific event types"
|
|
626
|
+
)
|
|
627
|
+
parser.add_argument("--raw", action="store_true", help="Display raw JSON output")
|
|
628
|
+
parser.add_argument(
|
|
629
|
+
"--quiet", action="store_true", help="Suppress output except errors"
|
|
630
|
+
)
|
|
631
|
+
|
|
632
|
+
# Output options
|
|
633
|
+
parser.add_argument(
|
|
634
|
+
"--output", type=Path, help="Save events to file (JSONL format)"
|
|
635
|
+
)
|
|
636
|
+
|
|
637
|
+
# Connection options
|
|
638
|
+
parser.add_argument(
|
|
639
|
+
"--max-reconnect",
|
|
640
|
+
type=int,
|
|
641
|
+
default=10,
|
|
642
|
+
help="Maximum reconnection attempts (default: 10)",
|
|
643
|
+
)
|
|
644
|
+
parser.add_argument(
|
|
645
|
+
"--reconnect-delay",
|
|
646
|
+
type=float,
|
|
647
|
+
default=1.0,
|
|
648
|
+
help="Reconnection delay in seconds (default: 1.0)",
|
|
649
|
+
)
|
|
650
|
+
|
|
651
|
+
args = parser.parse_args()
|
|
652
|
+
|
|
653
|
+
# Create and run debugger
|
|
654
|
+
debugger = SocketIODebugger(
|
|
655
|
+
host=args.host,
|
|
656
|
+
port=args.port,
|
|
657
|
+
mode=DisplayMode(args.mode),
|
|
658
|
+
filter_types=args.filter_types,
|
|
659
|
+
output_file=args.output,
|
|
660
|
+
quiet=args.quiet,
|
|
661
|
+
show_raw=args.raw,
|
|
662
|
+
max_reconnect_attempts=args.max_reconnect,
|
|
663
|
+
reconnect_delay=args.reconnect_delay,
|
|
664
|
+
)
|
|
665
|
+
|
|
666
|
+
success = debugger.run()
|
|
667
|
+
sys.exit(0 if success else 1)
|
|
668
|
+
|
|
669
|
+
|
|
670
|
+
if __name__ == "__main__":
|
|
671
|
+
main()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: claude-mpm
|
|
3
|
-
Version: 4.1.
|
|
3
|
+
Version: 4.1.11
|
|
4
4
|
Summary: Claude Multi-Agent Project Manager - Orchestrate Claude with agent delegation and ticket tracking
|
|
5
5
|
Author-email: Bob Matsuoka <bob@matsuoka.com>
|
|
6
6
|
Maintainer: Claude MPM Team
|
|
@@ -50,6 +50,7 @@ Requires-Dist: textual>=0.47.0
|
|
|
50
50
|
Requires-Dist: rich>=13.0.0
|
|
51
51
|
Requires-Dist: pyee>=13.0.0
|
|
52
52
|
Requires-Dist: importlib-resources>=5.0; python_version < "3.9"
|
|
53
|
+
Requires-Dist: pathspec>=0.11.0
|
|
53
54
|
Provides-Extra: dev
|
|
54
55
|
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
55
56
|
Requires-Dist: pytest-asyncio; extra == "dev"
|