claude-mpm 5.1.9__py3-none-any.whl → 5.4.22__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.

Potentially problematic release.


This version of claude-mpm might be problematic. Click here for more details.

Files changed (176) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/__init__.py +4 -0
  3. claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +1 -1
  4. claude_mpm/agents/PM_INSTRUCTIONS.md +290 -34
  5. claude_mpm/agents/agent_loader.py +13 -44
  6. claude_mpm/agents/templates/circuit-breakers.md +138 -1
  7. claude_mpm/cli/__main__.py +4 -0
  8. claude_mpm/cli/chrome_devtools_installer.py +175 -0
  9. claude_mpm/cli/commands/agent_state_manager.py +8 -17
  10. claude_mpm/cli/commands/agents.py +0 -31
  11. claude_mpm/cli/commands/auto_configure.py +210 -25
  12. claude_mpm/cli/commands/config.py +88 -2
  13. claude_mpm/cli/commands/configure.py +1097 -158
  14. claude_mpm/cli/commands/configure_agent_display.py +15 -6
  15. claude_mpm/cli/commands/mpm_init/core.py +160 -46
  16. claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
  17. claude_mpm/cli/commands/mpm_init/prompts.py +280 -0
  18. claude_mpm/cli/commands/skills.py +214 -189
  19. claude_mpm/cli/commands/summarize.py +413 -0
  20. claude_mpm/cli/executor.py +11 -3
  21. claude_mpm/cli/parsers/agents_parser.py +0 -9
  22. claude_mpm/cli/parsers/auto_configure_parser.py +0 -138
  23. claude_mpm/cli/parsers/base_parser.py +5 -0
  24. claude_mpm/cli/parsers/config_parser.py +153 -83
  25. claude_mpm/cli/parsers/skills_parser.py +3 -2
  26. claude_mpm/cli/startup.py +550 -94
  27. claude_mpm/commands/mpm-config.md +265 -0
  28. claude_mpm/commands/mpm-help.md +14 -95
  29. claude_mpm/commands/mpm-organize.md +500 -0
  30. claude_mpm/config/agent_sources.py +27 -0
  31. claude_mpm/core/framework/formatters/content_formatter.py +3 -13
  32. claude_mpm/core/framework/loaders/agent_loader.py +8 -5
  33. claude_mpm/core/framework_loader.py +4 -2
  34. claude_mpm/core/logger.py +13 -0
  35. claude_mpm/core/socketio_pool.py +3 -3
  36. claude_mpm/core/unified_agent_registry.py +5 -15
  37. claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
  38. claude_mpm/hooks/claude_hooks/event_handlers.py +211 -78
  39. claude_mpm/hooks/claude_hooks/hook_handler.py +6 -0
  40. claude_mpm/hooks/claude_hooks/installer.py +33 -10
  41. claude_mpm/hooks/claude_hooks/memory_integration.py +26 -9
  42. claude_mpm/hooks/claude_hooks/response_tracking.py +2 -3
  43. claude_mpm/hooks/claude_hooks/services/connection_manager.py +4 -0
  44. claude_mpm/hooks/memory_integration_hook.py +46 -1
  45. claude_mpm/init.py +0 -19
  46. claude_mpm/scripts/claude-hook-handler.sh +58 -18
  47. claude_mpm/scripts/launch_monitor.py +93 -13
  48. claude_mpm/scripts/start_activity_logging.py +0 -0
  49. claude_mpm/services/agents/agent_recommendation_service.py +278 -0
  50. claude_mpm/services/agents/agent_review_service.py +280 -0
  51. claude_mpm/services/agents/deployment/agent_discovery_service.py +2 -3
  52. claude_mpm/services/agents/deployment/agent_template_builder.py +4 -2
  53. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +78 -9
  54. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +335 -53
  55. claude_mpm/services/agents/git_source_manager.py +34 -0
  56. claude_mpm/services/agents/loading/base_agent_manager.py +1 -13
  57. claude_mpm/services/agents/sources/git_source_sync_service.py +8 -1
  58. claude_mpm/services/agents/toolchain_detector.py +10 -6
  59. claude_mpm/services/analysis/__init__.py +11 -1
  60. claude_mpm/services/analysis/clone_detector.py +1030 -0
  61. claude_mpm/services/command_deployment_service.py +81 -10
  62. claude_mpm/services/event_bus/config.py +3 -1
  63. claude_mpm/services/git/git_operations_service.py +93 -8
  64. claude_mpm/services/monitor/daemon.py +9 -2
  65. claude_mpm/services/monitor/daemon_manager.py +39 -3
  66. claude_mpm/services/monitor/server.py +225 -19
  67. claude_mpm/services/self_upgrade_service.py +120 -12
  68. claude_mpm/services/skills/__init__.py +3 -0
  69. claude_mpm/services/skills/git_skill_source_manager.py +32 -2
  70. claude_mpm/services/skills/selective_skill_deployer.py +704 -0
  71. claude_mpm/services/skills/skill_to_agent_mapper.py +406 -0
  72. claude_mpm/services/skills_deployer.py +126 -9
  73. claude_mpm/services/socketio/event_normalizer.py +15 -1
  74. claude_mpm/services/socketio/server/core.py +160 -21
  75. claude_mpm/services/version_control/git_operations.py +103 -0
  76. claude_mpm/utils/agent_filters.py +17 -44
  77. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.22.dist-info}/METADATA +47 -84
  78. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.22.dist-info}/RECORD +82 -161
  79. claude_mpm-5.4.22.dist-info/entry_points.txt +5 -0
  80. claude_mpm-5.4.22.dist-info/licenses/LICENSE +94 -0
  81. claude_mpm-5.4.22.dist-info/licenses/LICENSE-FAQ.md +153 -0
  82. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +0 -292
  83. claude_mpm/agents/BASE_DOCUMENTATION.md +0 -53
  84. claude_mpm/agents/BASE_ENGINEER.md +0 -658
  85. claude_mpm/agents/BASE_OPS.md +0 -219
  86. claude_mpm/agents/BASE_PM.md +0 -480
  87. claude_mpm/agents/BASE_PROMPT_ENGINEER.md +0 -787
  88. claude_mpm/agents/BASE_QA.md +0 -167
  89. claude_mpm/agents/BASE_RESEARCH.md +0 -53
  90. claude_mpm/agents/base_agent.json +0 -31
  91. claude_mpm/agents/base_agent_loader.py +0 -601
  92. claude_mpm/cli/commands/agents_detect.py +0 -380
  93. claude_mpm/cli/commands/agents_recommend.py +0 -309
  94. claude_mpm/cli/ticket_cli.py +0 -35
  95. claude_mpm/commands/mpm-agents-auto-configure.md +0 -278
  96. claude_mpm/commands/mpm-agents-detect.md +0 -177
  97. claude_mpm/commands/mpm-agents-list.md +0 -131
  98. claude_mpm/commands/mpm-agents-recommend.md +0 -223
  99. claude_mpm/commands/mpm-config-view.md +0 -150
  100. claude_mpm/commands/mpm-ticket-organize.md +0 -304
  101. claude_mpm/dashboard/analysis_runner.py +0 -455
  102. claude_mpm/dashboard/index.html +0 -13
  103. claude_mpm/dashboard/open_dashboard.py +0 -66
  104. claude_mpm/dashboard/static/css/activity.css +0 -1958
  105. claude_mpm/dashboard/static/css/connection-status.css +0 -370
  106. claude_mpm/dashboard/static/css/dashboard.css +0 -4701
  107. claude_mpm/dashboard/static/js/components/activity-tree.js +0 -1871
  108. claude_mpm/dashboard/static/js/components/agent-hierarchy.js +0 -777
  109. claude_mpm/dashboard/static/js/components/agent-inference.js +0 -956
  110. claude_mpm/dashboard/static/js/components/build-tracker.js +0 -333
  111. claude_mpm/dashboard/static/js/components/code-simple.js +0 -857
  112. claude_mpm/dashboard/static/js/components/connection-debug.js +0 -654
  113. claude_mpm/dashboard/static/js/components/diff-viewer.js +0 -891
  114. claude_mpm/dashboard/static/js/components/event-processor.js +0 -542
  115. claude_mpm/dashboard/static/js/components/event-viewer.js +0 -1155
  116. claude_mpm/dashboard/static/js/components/export-manager.js +0 -368
  117. claude_mpm/dashboard/static/js/components/file-change-tracker.js +0 -443
  118. claude_mpm/dashboard/static/js/components/file-change-viewer.js +0 -690
  119. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +0 -724
  120. claude_mpm/dashboard/static/js/components/file-viewer.js +0 -580
  121. claude_mpm/dashboard/static/js/components/hud-library-loader.js +0 -211
  122. claude_mpm/dashboard/static/js/components/hud-manager.js +0 -671
  123. claude_mpm/dashboard/static/js/components/hud-visualizer.js +0 -1718
  124. claude_mpm/dashboard/static/js/components/module-viewer.js +0 -2764
  125. claude_mpm/dashboard/static/js/components/session-manager.js +0 -579
  126. claude_mpm/dashboard/static/js/components/socket-manager.js +0 -368
  127. claude_mpm/dashboard/static/js/components/ui-state-manager.js +0 -749
  128. claude_mpm/dashboard/static/js/components/unified-data-viewer.js +0 -1824
  129. claude_mpm/dashboard/static/js/components/working-directory.js +0 -920
  130. claude_mpm/dashboard/static/js/connection-manager.js +0 -536
  131. claude_mpm/dashboard/static/js/dashboard.js +0 -1914
  132. claude_mpm/dashboard/static/js/extension-error-handler.js +0 -164
  133. claude_mpm/dashboard/static/js/socket-client.js +0 -1474
  134. claude_mpm/dashboard/static/js/tab-isolation-fix.js +0 -185
  135. claude_mpm/dashboard/static/socket.io.min.js +0 -7
  136. claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +0 -7
  137. claude_mpm/dashboard/templates/code_simple.html +0 -153
  138. claude_mpm/dashboard/templates/index.html +0 -606
  139. claude_mpm/dashboard/test_dashboard.html +0 -372
  140. claude_mpm/scripts/mcp_server.py +0 -75
  141. claude_mpm/scripts/mcp_wrapper.py +0 -39
  142. claude_mpm/services/mcp_gateway/__init__.py +0 -159
  143. claude_mpm/services/mcp_gateway/auto_configure.py +0 -369
  144. claude_mpm/services/mcp_gateway/config/__init__.py +0 -17
  145. claude_mpm/services/mcp_gateway/config/config_loader.py +0 -296
  146. claude_mpm/services/mcp_gateway/config/config_schema.py +0 -243
  147. claude_mpm/services/mcp_gateway/config/configuration.py +0 -429
  148. claude_mpm/services/mcp_gateway/core/__init__.py +0 -43
  149. claude_mpm/services/mcp_gateway/core/base.py +0 -312
  150. claude_mpm/services/mcp_gateway/core/exceptions.py +0 -253
  151. claude_mpm/services/mcp_gateway/core/interfaces.py +0 -443
  152. claude_mpm/services/mcp_gateway/core/process_pool.py +0 -977
  153. claude_mpm/services/mcp_gateway/core/singleton_manager.py +0 -315
  154. claude_mpm/services/mcp_gateway/core/startup_verification.py +0 -316
  155. claude_mpm/services/mcp_gateway/main.py +0 -589
  156. claude_mpm/services/mcp_gateway/registry/__init__.py +0 -12
  157. claude_mpm/services/mcp_gateway/registry/service_registry.py +0 -412
  158. claude_mpm/services/mcp_gateway/registry/tool_registry.py +0 -489
  159. claude_mpm/services/mcp_gateway/server/__init__.py +0 -15
  160. claude_mpm/services/mcp_gateway/server/mcp_gateway.py +0 -414
  161. claude_mpm/services/mcp_gateway/server/stdio_handler.py +0 -372
  162. claude_mpm/services/mcp_gateway/server/stdio_server.py +0 -712
  163. claude_mpm/services/mcp_gateway/tools/__init__.py +0 -36
  164. claude_mpm/services/mcp_gateway/tools/base_adapter.py +0 -485
  165. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +0 -789
  166. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +0 -654
  167. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +0 -456
  168. claude_mpm/services/mcp_gateway/tools/hello_world.py +0 -551
  169. claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +0 -555
  170. claude_mpm/services/mcp_gateway/utils/__init__.py +0 -14
  171. claude_mpm/services/mcp_gateway/utils/package_version_checker.py +0 -160
  172. claude_mpm/services/mcp_gateway/utils/update_preferences.py +0 -170
  173. claude_mpm-5.1.9.dist-info/entry_points.txt +0 -10
  174. claude_mpm-5.1.9.dist-info/licenses/LICENSE +0 -21
  175. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.22.dist-info}/WHEEL +0 -0
  176. {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.22.dist-info}/top_level.txt +0 -0
@@ -1,455 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Code Analysis Runner for Dashboard
4
- ===================================
5
-
6
- WHY: Manages subprocess execution of code analysis, streaming results to
7
- Socket.IO clients in real-time while handling cancellation and error recovery.
8
-
9
- DESIGN DECISIONS:
10
- - Use subprocess for isolation and cancellation support
11
- - Stream output line-by-line for real-time updates
12
- - Queue multiple analysis requests
13
- - Handle process lifecycle management
14
- - Convert analyzer events to Socket.IO events
15
- """
16
-
17
- import json
18
- import os
19
- import subprocess
20
- import sys
21
- import threading
22
- from dataclasses import asdict, dataclass
23
- from datetime import datetime, timezone
24
- from pathlib import Path
25
- from queue import Queue
26
- from typing import Any, Dict, List, Optional
27
-
28
- from ..core.logging_config import get_logger
29
-
30
-
31
- @dataclass
32
- class AnalysisRequest:
33
- """Represents a code analysis request."""
34
-
35
- request_id: str
36
- path: str
37
- languages: Optional[List[str]] = None
38
- max_depth: Optional[int] = None
39
- ignore_patterns: Optional[List[str]] = None
40
- timestamp: datetime = None
41
-
42
- def __post_init__(self):
43
- if self.timestamp is None:
44
- self.timestamp = datetime.now(timezone.utc)
45
-
46
-
47
- class CodeAnalysisRunner:
48
- """Manages code analysis subprocess execution for the dashboard.
49
-
50
- WHY: Provides isolation between the dashboard server and analysis process,
51
- allowing for cancellation, resource limits, and crash recovery.
52
- """
53
-
54
- def __init__(self, socketio_server):
55
- """Initialize the analysis runner.
56
-
57
- Args:
58
- socketio_server: SocketIOServer instance for broadcasting events
59
- """
60
- self.logger = get_logger(__name__)
61
- self.server = socketio_server
62
- self.current_process = None
63
- self.current_request = None
64
- self.request_queue = Queue()
65
- self.running = False
66
- self.worker_thread = None
67
- self.cancel_event = threading.Event()
68
-
69
- # Statistics
70
- self.stats = {
71
- "analyses_started": 0,
72
- "analyses_completed": 0,
73
- "analyses_cancelled": 0,
74
- "analyses_failed": 0,
75
- "total_files": 0,
76
- "total_nodes": 0,
77
- }
78
-
79
- def start(self):
80
- """Start the analysis runner worker thread."""
81
- if self.running:
82
- return
83
-
84
- self.running = True
85
- self.cancel_event.clear()
86
- self.worker_thread = threading.Thread(target=self._worker_loop, daemon=True)
87
- self.worker_thread.start()
88
- self.logger.info("Code analysis runner started")
89
-
90
- def stop(self):
91
- """Stop the analysis runner and cleanup."""
92
- self.running = False
93
- self.cancel_current()
94
-
95
- # Add sentinel to queue to wake up worker
96
- self.request_queue.put(None)
97
-
98
- if self.worker_thread:
99
- self.worker_thread.join(timeout=5)
100
-
101
- self.logger.info("Code analysis runner stopped")
102
-
103
- def request_analysis(
104
- self,
105
- request_id: str,
106
- path: str,
107
- languages: Optional[List[str]] = None,
108
- max_depth: Optional[int] = None,
109
- ignore_patterns: Optional[List[str]] = None,
110
- ) -> bool:
111
- """Queue a new analysis request.
112
-
113
- Args:
114
- request_id: Unique request identifier
115
- path: Directory path to analyze
116
- languages: Optional list of languages to filter
117
- max_depth: Optional maximum directory depth
118
- ignore_patterns: Optional list of patterns to ignore
119
-
120
- Returns:
121
- True if request was queued successfully
122
- """
123
- # Validate path
124
- analysis_path = Path(path).resolve()
125
- if not analysis_path.exists():
126
- self._emit_error(request_id, f"Path does not exist: {path}")
127
- return False
128
-
129
- if not analysis_path.is_dir():
130
- self._emit_error(request_id, f"Path is not a directory: {path}")
131
- return False
132
-
133
- # Create request
134
- request = AnalysisRequest(
135
- request_id=request_id,
136
- path=str(analysis_path),
137
- languages=languages,
138
- max_depth=max_depth,
139
- ignore_patterns=ignore_patterns,
140
- )
141
-
142
- # Queue request
143
- self.request_queue.put(request)
144
- self.logger.info(f"Queued analysis request {request_id} for {path}")
145
-
146
- # Emit queued event
147
- self._emit_event(
148
- "code:analysis:queued",
149
- {
150
- "request_id": request_id,
151
- "path": str(analysis_path),
152
- "queue_size": self.request_queue.qsize(),
153
- },
154
- )
155
-
156
- return True
157
-
158
- def cancel_current(self):
159
- """Cancel the currently running analysis."""
160
- if self.current_process and self.current_process.poll() is None:
161
- self.cancel_event.set()
162
-
163
- # Try graceful termination first
164
- self.current_process.terminate()
165
- try:
166
- self.current_process.wait(timeout=2)
167
- except subprocess.TimeoutExpired:
168
- # Force kill if needed
169
- self.current_process.kill()
170
- self.current_process.wait()
171
-
172
- self.stats["analyses_cancelled"] += 1
173
-
174
- if self.current_request:
175
- self._emit_event(
176
- "code:analysis:cancelled",
177
- {
178
- "request_id": self.current_request.request_id,
179
- "path": self.current_request.path,
180
- },
181
- )
182
-
183
- self.logger.info("Cancelled current analysis")
184
-
185
- def get_status(self) -> Dict[str, Any]:
186
- """Get current runner status.
187
-
188
- Returns:
189
- Dictionary with current status and statistics
190
- """
191
- return {
192
- "running": self.running,
193
- "current_request": (
194
- asdict(self.current_request) if self.current_request else None
195
- ),
196
- "queue_size": self.request_queue.qsize(),
197
- "stats": self.stats.copy(),
198
- }
199
-
200
- def _worker_loop(self):
201
- """Worker thread loop for processing analysis requests."""
202
- while self.running:
203
- try:
204
- # Get next request (blocking with timeout)
205
- request = self.request_queue.get(timeout=1)
206
-
207
- if request is None: # Sentinel value
208
- break
209
-
210
- # Reset cancel event
211
- self.cancel_event.clear()
212
-
213
- # Process request
214
- self._process_request(request)
215
-
216
- except Exception as e:
217
- self.logger.error(f"Error in worker loop: {e}")
218
-
219
- def _process_request(self, request: AnalysisRequest):
220
- """Process a single analysis request.
221
-
222
- Args:
223
- request: The analysis request to process
224
- """
225
- self.current_request = request
226
- self.stats["analyses_started"] += 1
227
-
228
- try:
229
- # Emit start event
230
- self._emit_event(
231
- "code:analysis:start",
232
- {
233
- "request_id": request.request_id,
234
- "path": request.path,
235
- "languages": request.languages,
236
- "timestamp": request.timestamp.isoformat(),
237
- },
238
- )
239
-
240
- # Build command
241
- cmd = self._build_command(request)
242
- self.logger.info(f"Starting analysis subprocess: {' '.join(cmd)}")
243
-
244
- # Start subprocess
245
- try:
246
- self.current_process = subprocess.Popen(
247
- cmd,
248
- stdout=subprocess.PIPE,
249
- stderr=subprocess.PIPE,
250
- text=True,
251
- bufsize=1,
252
- universal_newlines=True,
253
- env=self._get_subprocess_env(),
254
- )
255
- self.logger.debug(
256
- f"Subprocess started with PID: {self.current_process.pid}"
257
- )
258
- except FileNotFoundError as e:
259
- raise subprocess.SubprocessError(
260
- f"Python executable not found: {cmd[0]}"
261
- ) from e
262
- except Exception as e:
263
- raise subprocess.SubprocessError(
264
- f"Failed to start subprocess: {e}"
265
- ) from e
266
-
267
- # Process output
268
- self._process_output(request)
269
-
270
- # Wait for completion
271
- return_code = self.current_process.wait()
272
-
273
- if self.cancel_event.is_set():
274
- # Analysis was cancelled
275
- pass # Event already emitted in cancel_current
276
- elif return_code == 0:
277
- # Success
278
- self.stats["analyses_completed"] += 1
279
- self._emit_event(
280
- "code:analysis:complete",
281
- {
282
- "request_id": request.request_id,
283
- "path": request.path,
284
- "stats": {
285
- "total_files": self.stats["total_files"],
286
- "total_nodes": self.stats["total_nodes"],
287
- },
288
- },
289
- )
290
- else:
291
- # Failure - capture any remaining stderr
292
- stderr_output = ""
293
- if self.current_process.stderr:
294
- try:
295
- # Read any remaining stderr output
296
- stderr_lines = []
297
- for line in self.current_process.stderr:
298
- stderr_lines.append(line.strip())
299
- stderr_output = "\n".join(stderr_lines)
300
- except Exception as e:
301
- stderr_output = f"Failed to read stderr: {e}"
302
-
303
- self.stats["analyses_failed"] += 1
304
- error_msg = f"Analysis failed with code {return_code}"
305
- if stderr_output:
306
- error_msg += f": {stderr_output}"
307
-
308
- self.logger.error(f"Subprocess failed: {error_msg}")
309
- self._emit_error(request.request_id, error_msg)
310
-
311
- except subprocess.SubprocessError as e:
312
- self.logger.error(f"Subprocess error for request {request.request_id}: {e}")
313
- self.stats["analyses_failed"] += 1
314
- self._emit_error(request.request_id, f"Failed to start analyzer: {e}")
315
- except Exception as e:
316
- self.logger.error(
317
- f"Error processing request {request.request_id}: {e}", exc_info=True
318
- )
319
- self.stats["analyses_failed"] += 1
320
- self._emit_error(request.request_id, str(e))
321
-
322
- finally:
323
- self.current_process = None
324
- self.current_request = None
325
-
326
- def _build_command(self, request: AnalysisRequest) -> List[str]:
327
- """Build the subprocess command for analysis.
328
-
329
- Args:
330
- request: The analysis request
331
-
332
- Returns:
333
- Command list for subprocess.Popen
334
- """
335
- # Get Python executable
336
- python_exe = sys.executable
337
-
338
- # Build command - use the CLI analyze-code command
339
- cmd = [
340
- python_exe,
341
- "-m",
342
- "claude_mpm",
343
- "analyze-code",
344
- request.path,
345
- "--emit-events",
346
- "--output",
347
- "json",
348
- ]
349
-
350
- # Add optional parameters
351
- if request.languages:
352
- cmd.extend(["--languages", ",".join(request.languages)])
353
-
354
- if request.max_depth:
355
- cmd.extend(["--max-depth", str(request.max_depth)])
356
-
357
- if request.ignore_patterns:
358
- for pattern in request.ignore_patterns:
359
- cmd.extend(["--ignore", pattern])
360
-
361
- return cmd
362
-
363
- def _get_subprocess_env(self) -> Dict[str, str]:
364
- """Get environment variables for subprocess.
365
-
366
- Returns:
367
- Environment dictionary for subprocess
368
- """
369
- env = os.environ.copy()
370
-
371
- # Ensure Socket.IO URL is set for event emission
372
- env["SOCKETIO_URL"] = f"http://localhost:{self.server.port}"
373
-
374
- # Set Python path to include our modules
375
- python_path = env.get("PYTHONPATH", "")
376
- src_path = str(Path(__file__).parent.parent.parent)
377
- if src_path not in python_path:
378
- env["PYTHONPATH"] = f"{src_path}:{python_path}" if python_path else src_path
379
-
380
- return env
381
-
382
- def _process_output(self, request: AnalysisRequest):
383
- """Process subprocess output and emit events.
384
-
385
- Args:
386
- request: The current analysis request
387
- """
388
- if not self.current_process:
389
- return
390
-
391
- # Read output line by line
392
- for line in iter(self.current_process.stdout.readline, ""):
393
- if self.cancel_event.is_set():
394
- break
395
-
396
- line = line.strip()
397
- if not line:
398
- continue
399
-
400
- try:
401
- # Parse JSON event
402
- event = json.loads(line)
403
-
404
- # Route event to appropriate handler
405
- event_type = event.get("type")
406
- event_data = event.get("data", {})
407
-
408
- # Add request ID to event data
409
- event_data["request_id"] = request.request_id
410
-
411
- # Update statistics based on event type
412
- if event_type == "code:file:complete":
413
- self.stats["total_files"] += 1
414
- elif event_type == "code:node:found":
415
- self.stats["total_nodes"] += 1
416
-
417
- # Emit to Socket.IO clients
418
- self._emit_event(event_type, event_data)
419
-
420
- except json.JSONDecodeError:
421
- # Not JSON, treat as log message
422
- self.logger.debug(f"Analyzer output: {line}")
423
- except Exception as e:
424
- self.logger.warning(f"Error processing analyzer output: {e}")
425
-
426
- def _emit_event(self, event_type: str, data: Dict[str, Any]):
427
- """Emit an event to Socket.IO clients.
428
-
429
- Args:
430
- event_type: Type of event
431
- data: Event data
432
- """
433
- if self.server:
434
- # Add timestamp if not present
435
- if "timestamp" not in data:
436
- data["timestamp"] = datetime.now(timezone.utc).isoformat()
437
-
438
- # Broadcast to all clients
439
- self.server.broadcast_event(event_type, data)
440
-
441
- def _emit_error(self, request_id: str, message: str):
442
- """Emit an error event.
443
-
444
- Args:
445
- request_id: Request that caused the error
446
- message: Error message
447
- """
448
- self._emit_event(
449
- "code:analysis:error",
450
- {
451
- "request_id": request_id,
452
- "message": message,
453
- "timestamp": datetime.now(timezone.utc).isoformat(),
454
- },
455
- )
@@ -1,13 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Claude MPM Dashboard</title>
7
- <meta http-equiv="refresh" content="0; url=/dashboard?autoconnect=true&port=8765">
8
- </head>
9
- <body>
10
- <p>Redirecting to Claude MPM Dashboard...</p>
11
- <p>If you are not redirected, <a href="/dashboard?autoconnect=true&port=8765">click here</a>.</p>
12
- </body>
13
- </html>
@@ -1,66 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Open the dashboard statically in the browser."""
3
-
4
- import webbrowser
5
- from pathlib import Path
6
-
7
- try:
8
- from ..services.port_manager import PortManager
9
- except ImportError:
10
- # Fallback for when running as standalone script
11
- import sys
12
-
13
- sys.path.insert(0, str(Path(__file__).parent.parent.parent))
14
- from claude_mpm.services.port_manager import PortManager
15
-
16
-
17
- def discover_socketio_port():
18
- """Discover the port of the running SocketIO server, preferring 8765."""
19
- try:
20
- port_manager = PortManager()
21
- instances = port_manager.list_active_instances()
22
-
23
- if instances:
24
- # First, check if port 8765 is being used
25
- for instance in instances:
26
- if instance.get("port") == 8765:
27
- return 8765
28
-
29
- # If 8765 is not available, return the first active instance port
30
- return instances[0].get("port", 8765)
31
- print("⚠️ No active SocketIO instances found, using default port 8765")
32
- return 8765
33
- except Exception as e:
34
- print(f"⚠️ Failed to discover SocketIO port: {e}")
35
- print(" Using default port 8765")
36
- return 8765
37
-
38
-
39
- def open_dashboard(port=8765, autoconnect=True):
40
- """Open the dashboard HTML file directly in the browser.
41
-
42
- Args:
43
- port: Socket.IO server port to connect to (defaults to 8765, auto-discovers if needed)
44
- autoconnect: Whether to auto-connect on load
45
- """
46
- # If default port 8765 is specified, check if we need to auto-discover
47
- if port == 8765:
48
- discovered_port = discover_socketio_port()
49
- if discovered_port != 8765:
50
- print(
51
- f"🔍 SocketIO server found on port {discovered_port} instead of default 8765"
52
- )
53
- port = discovered_port
54
- # Build HTTP URL to connect to the SocketIO server's dashboard
55
- dashboard_url = f"http://localhost:{port}"
56
-
57
- print(f"🌐 Opening dashboard: {dashboard_url}")
58
- print(f"📡 Dashboard served by Socket.IO server at localhost:{port}")
59
- webbrowser.open(dashboard_url)
60
-
61
- return dashboard_url
62
-
63
-
64
- if __name__ == "__main__":
65
- # Test opening the dashboard
66
- open_dashboard()