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.
Files changed (111) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/INSTRUCTIONS.md +26 -1
  3. claude_mpm/agents/agents_metadata.py +57 -0
  4. claude_mpm/agents/templates/.claude-mpm/memories/README.md +17 -0
  5. claude_mpm/agents/templates/.claude-mpm/memories/engineer_memories.md +3 -0
  6. claude_mpm/agents/templates/agent-manager.json +263 -17
  7. claude_mpm/agents/templates/agentic_coder_optimizer.json +222 -0
  8. claude_mpm/agents/templates/code_analyzer.json +18 -8
  9. claude_mpm/agents/templates/engineer.json +1 -1
  10. claude_mpm/agents/templates/logs/prompts/agent_engineer_20250826_014258_728.md +39 -0
  11. claude_mpm/agents/templates/qa.json +1 -1
  12. claude_mpm/agents/templates/research.json +1 -1
  13. claude_mpm/cli/__init__.py +15 -0
  14. claude_mpm/cli/commands/__init__.py +6 -0
  15. claude_mpm/cli/commands/analyze.py +548 -0
  16. claude_mpm/cli/commands/analyze_code.py +524 -0
  17. claude_mpm/cli/commands/configure.py +78 -28
  18. claude_mpm/cli/commands/configure_tui.py +62 -60
  19. claude_mpm/cli/commands/dashboard.py +288 -0
  20. claude_mpm/cli/commands/debug.py +1386 -0
  21. claude_mpm/cli/commands/mpm_init.py +427 -0
  22. claude_mpm/cli/commands/mpm_init_handler.py +83 -0
  23. claude_mpm/cli/parsers/analyze_code_parser.py +170 -0
  24. claude_mpm/cli/parsers/analyze_parser.py +135 -0
  25. claude_mpm/cli/parsers/base_parser.py +44 -0
  26. claude_mpm/cli/parsers/dashboard_parser.py +113 -0
  27. claude_mpm/cli/parsers/debug_parser.py +319 -0
  28. claude_mpm/cli/parsers/mpm_init_parser.py +122 -0
  29. claude_mpm/constants.py +13 -1
  30. claude_mpm/core/framework_loader.py +148 -6
  31. claude_mpm/core/log_manager.py +16 -13
  32. claude_mpm/core/logger.py +1 -1
  33. claude_mpm/core/unified_agent_registry.py +1 -1
  34. claude_mpm/dashboard/.claude-mpm/socketio-instances.json +1 -0
  35. claude_mpm/dashboard/analysis_runner.py +455 -0
  36. claude_mpm/dashboard/static/built/components/activity-tree.js +2 -0
  37. claude_mpm/dashboard/static/built/components/agent-inference.js +1 -1
  38. claude_mpm/dashboard/static/built/components/code-tree.js +2 -0
  39. claude_mpm/dashboard/static/built/components/code-viewer.js +2 -0
  40. claude_mpm/dashboard/static/built/components/event-viewer.js +1 -1
  41. claude_mpm/dashboard/static/built/components/file-tool-tracker.js +1 -1
  42. claude_mpm/dashboard/static/built/components/module-viewer.js +1 -1
  43. claude_mpm/dashboard/static/built/components/session-manager.js +1 -1
  44. claude_mpm/dashboard/static/built/components/working-directory.js +1 -1
  45. claude_mpm/dashboard/static/built/dashboard.js +1 -1
  46. claude_mpm/dashboard/static/built/socket-client.js +1 -1
  47. claude_mpm/dashboard/static/css/activity.css +549 -0
  48. claude_mpm/dashboard/static/css/code-tree.css +1175 -0
  49. claude_mpm/dashboard/static/css/dashboard.css +245 -0
  50. claude_mpm/dashboard/static/dist/components/activity-tree.js +2 -0
  51. claude_mpm/dashboard/static/dist/components/code-tree.js +2 -0
  52. claude_mpm/dashboard/static/dist/components/code-viewer.js +2 -0
  53. claude_mpm/dashboard/static/dist/components/event-viewer.js +1 -1
  54. claude_mpm/dashboard/static/dist/components/session-manager.js +1 -1
  55. claude_mpm/dashboard/static/dist/components/working-directory.js +1 -1
  56. claude_mpm/dashboard/static/dist/dashboard.js +1 -1
  57. claude_mpm/dashboard/static/dist/socket-client.js +1 -1
  58. claude_mpm/dashboard/static/js/components/activity-tree.js +1338 -0
  59. claude_mpm/dashboard/static/js/components/code-tree.js +2535 -0
  60. claude_mpm/dashboard/static/js/components/code-viewer.js +480 -0
  61. claude_mpm/dashboard/static/js/components/event-viewer.js +59 -9
  62. claude_mpm/dashboard/static/js/components/session-manager.js +40 -4
  63. claude_mpm/dashboard/static/js/components/socket-manager.js +12 -0
  64. claude_mpm/dashboard/static/js/components/ui-state-manager.js +4 -0
  65. claude_mpm/dashboard/static/js/components/working-directory.js +17 -1
  66. claude_mpm/dashboard/static/js/dashboard.js +51 -0
  67. claude_mpm/dashboard/static/js/socket-client.js +465 -29
  68. claude_mpm/dashboard/templates/index.html +182 -4
  69. claude_mpm/hooks/claude_hooks/hook_handler.py +182 -5
  70. claude_mpm/hooks/claude_hooks/installer.py +386 -113
  71. claude_mpm/scripts/claude-hook-handler.sh +161 -0
  72. claude_mpm/scripts/socketio_daemon.py +121 -8
  73. claude_mpm/services/agents/deployment/agent_lifecycle_manager_refactored.py +2 -2
  74. claude_mpm/services/agents/deployment/agent_record_service.py +1 -2
  75. claude_mpm/services/agents/memory/memory_format_service.py +1 -3
  76. claude_mpm/services/cli/agent_cleanup_service.py +1 -5
  77. claude_mpm/services/cli/agent_dependency_service.py +1 -1
  78. claude_mpm/services/cli/agent_validation_service.py +3 -4
  79. claude_mpm/services/cli/dashboard_launcher.py +2 -3
  80. claude_mpm/services/cli/startup_checker.py +0 -11
  81. claude_mpm/services/core/cache_manager.py +1 -3
  82. claude_mpm/services/core/path_resolver.py +1 -4
  83. claude_mpm/services/core/service_container.py +2 -2
  84. claude_mpm/services/diagnostics/checks/instructions_check.py +1 -2
  85. claude_mpm/services/infrastructure/monitoring/__init__.py +11 -11
  86. claude_mpm/services/infrastructure/monitoring.py +11 -11
  87. claude_mpm/services/project/architecture_analyzer.py +1 -1
  88. claude_mpm/services/project/dependency_analyzer.py +4 -4
  89. claude_mpm/services/project/language_analyzer.py +3 -3
  90. claude_mpm/services/project/metrics_collector.py +3 -6
  91. claude_mpm/services/socketio/event_normalizer.py +64 -0
  92. claude_mpm/services/socketio/handlers/__init__.py +2 -0
  93. claude_mpm/services/socketio/handlers/code_analysis.py +672 -0
  94. claude_mpm/services/socketio/handlers/registry.py +2 -0
  95. claude_mpm/services/socketio/server/connection_manager.py +6 -4
  96. claude_mpm/services/socketio/server/core.py +100 -11
  97. claude_mpm/services/socketio/server/main.py +8 -2
  98. claude_mpm/services/visualization/__init__.py +19 -0
  99. claude_mpm/services/visualization/mermaid_generator.py +938 -0
  100. claude_mpm/tools/__main__.py +208 -0
  101. claude_mpm/tools/code_tree_analyzer.py +1596 -0
  102. claude_mpm/tools/code_tree_builder.py +631 -0
  103. claude_mpm/tools/code_tree_events.py +416 -0
  104. claude_mpm/tools/socketio_debug.py +671 -0
  105. {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/METADATA +2 -1
  106. {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/RECORD +110 -74
  107. claude_mpm/agents/schema/agent_schema.json +0 -314
  108. {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/WHEEL +0 -0
  109. {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/entry_points.txt +0 -0
  110. {claude_mpm-4.1.8.dist-info → claude_mpm-4.1.11.dist-info}/licenses/LICENSE +0 -0
  111. {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.8
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"