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
@@ -18,8 +18,7 @@ DEBUG = os.environ.get("CLAUDE_MPM_HOOK_DEBUG", "true").lower() != "false"
18
18
 
19
19
  # Response tracking integration
20
20
  # NOTE: ResponseTracker import moved to _initialize_response_tracking() for lazy loading
21
- # This prevents unnecessary import of base_agent_loader and other heavy dependencies
22
- # when hooks don't need response tracking
21
+ # This prevents unnecessary import of heavy dependencies when hooks don't need response tracking
23
22
  RESPONSE_TRACKING_AVAILABLE = (
24
23
  True # Assume available, will check on actual initialization
25
24
  )
@@ -51,7 +50,7 @@ class ResponseTrackingManager:
51
50
  response tracking without code changes.
52
51
 
53
52
  NOTE: ResponseTracker is imported lazily here to avoid loading
54
- base_agent_loader and other heavy dependencies unless actually needed.
53
+ heavy dependencies unless actually needed.
55
54
  """
56
55
  try:
57
56
  # Lazy import of ResponseTracker to avoid unnecessary dependency loading
@@ -115,6 +115,9 @@ class ConnectionManagerService:
115
115
  - Fallback: HTTP POST for reliability when direct connection fails
116
116
  - Eliminates duplicate events from multiple emission paths
117
117
  """
118
+ # Extract tool_call_id from data if present for correlation
119
+ tool_call_id = data.get("tool_call_id")
120
+
118
121
  # Create event data for normalization
119
122
  raw_event = {
120
123
  "type": "hook",
@@ -123,6 +126,7 @@ class ConnectionManagerService:
123
126
  "data": data,
124
127
  "source": "claude_hooks", # Identify the source
125
128
  "session_id": data.get("sessionId"), # Include session if available
129
+ "correlation_id": tool_call_id, # Set from tool_call_id for event correlation
126
130
  }
127
131
 
128
132
  # Normalize the event using EventNormalizer for consistent schema
@@ -13,6 +13,7 @@ agent outputs because:
13
13
  """
14
14
 
15
15
  import re
16
+ from datetime import datetime
16
17
  from typing import Dict, List
17
18
 
18
19
  from claude_mpm.core.config import Config
@@ -47,6 +48,16 @@ except ImportError as e:
47
48
  SOCKETIO_AVAILABLE = False
48
49
  get_socketio_server = None
49
50
 
51
+ # Try to import event bus with fallback handling
52
+ try:
53
+ from claude_mpm.services.event_bus.event_bus import EventBus
54
+
55
+ EVENT_BUS_AVAILABLE = True
56
+ except ImportError as e:
57
+ logger.debug(f"EventBus not available: {e}")
58
+ EVENT_BUS_AVAILABLE = False
59
+ EventBus = None
60
+
50
61
 
51
62
  class MemoryPreDelegationHook(PreDelegationHook):
52
63
  """Inject agent memory into delegation context.
@@ -83,6 +94,16 @@ class MemoryPreDelegationHook(PreDelegationHook):
83
94
  logger.info("Memory manager not available - hook will be inactive")
84
95
  self.memory_manager = None
85
96
 
97
+ # Initialize event bus for observability
98
+ if EVENT_BUS_AVAILABLE and EventBus:
99
+ try:
100
+ self.event_bus = EventBus.get_instance()
101
+ except Exception as e:
102
+ logger.debug(f"Failed to get EventBus instance: {e}")
103
+ self.event_bus = None
104
+ else:
105
+ self.event_bus = None
106
+
86
107
  def execute(self, context: HookContext) -> HookResult:
87
108
  """Add agent memory to delegation context.
88
109
 
@@ -137,7 +158,31 @@ INSTRUCTIONS: Review your memory above before proceeding. Apply learned patterns
137
158
 
138
159
  logger.info(f"Injected memory for agent '{agent_id}'")
139
160
 
140
- # Emit Socket.IO event for memory injected
161
+ # Calculate memory size for observability
162
+ memory_size = len(memory_content)
163
+
164
+ # Emit event bus event for observability
165
+ if self.event_bus:
166
+ try:
167
+ # Determine memory source (project or user level)
168
+ # This is inferred from the memory manager's behavior
169
+ memory_source = (
170
+ "runtime" # Runtime loading from memory manager
171
+ )
172
+
173
+ self.event_bus.publish(
174
+ "agent.memory.loaded",
175
+ {
176
+ "agent_id": agent_id,
177
+ "memory_source": memory_source,
178
+ "memory_size": memory_size,
179
+ "timestamp": datetime.now(datetime.UTC).isoformat(),
180
+ },
181
+ )
182
+ except Exception as event_error:
183
+ logger.debug(f"EventBus publish failed: {event_error}")
184
+
185
+ # Emit Socket.IO event for memory injected (legacy compatibility)
141
186
  try:
142
187
  socketio_server = get_socketio_server()
143
188
  # Calculate size of injected content
claude_mpm/init.py CHANGED
@@ -141,25 +141,6 @@ class ProjectInitializer:
141
141
  if not gitignore.exists():
142
142
  gitignore.write_text("logs/\n*.log\n*.pyc\n__pycache__/\n")
143
143
 
144
- # Also ensure MCP directories are in main project .gitignore
145
- try:
146
- from claude_mpm.services.project.project_organizer import (
147
- ProjectOrganizer,
148
- )
149
-
150
- # Check if we're in a git repository
151
- if (project_root / ".git").exists():
152
- organizer = ProjectOrganizer(project_root)
153
- # This will add MCP directories and other standard patterns
154
- organizer.update_gitignore()
155
- self.logger.debug(
156
- "Updated project .gitignore with MCP and standard patterns"
157
- )
158
- except Exception as e:
159
- self.logger.debug(
160
- f"Could not update project gitignore with MCP patterns: {e}"
161
- )
162
-
163
144
  # Log successful creation with details
164
145
  self.logger.info(f"Initialized project directory at {self.project_dir}")
165
146
  self.logger.debug("Created directories: agents, config, responses, logs")
@@ -88,18 +88,21 @@ fi
88
88
  #
89
89
  # STRATEGY:
90
90
  # This function implements a fallback chain to find Python with claude-mpm dependencies:
91
- # 1. Project-specific virtual environments (venv, .venv)
92
- # 2. Currently active virtual environment ($VIRTUAL_ENV)
93
- # 3. System python3 (may lack dependencies)
94
- # 4. System python (last resort)
91
+ # 1. UV-managed projects (uv.lock detected) - uses "uv run python"
92
+ # 2. pipx installations - uses pipx venv Python
93
+ # 3. Project-specific virtual environments (venv, .venv)
94
+ # 4. Currently active virtual environment ($VIRTUAL_ENV)
95
+ # 5. System python3 (may lack dependencies)
96
+ # 6. System python (last resort)
95
97
  #
96
98
  # WHY THIS APPROACH:
97
99
  # - Claude MPM requires specific packages (socketio, eventlet) not in system Python
98
- # - Virtual environments ensure dependency isolation and availability
100
+ # - UV and virtual environments ensure dependency isolation and availability
99
101
  # - Multiple naming conventions supported (venv vs .venv)
100
102
  # - Graceful degradation to system Python if no venv found
101
103
  #
102
104
  # ACTIVATION STRATEGY:
105
+ # - UV projects: use "uv run python" to execute in UV-managed environment
103
106
  # - Sources activate script to set up environment variables
104
107
  # - Returns specific Python path for exec (not just 'python')
105
108
  # - Maintains environment in same shell process
@@ -110,10 +113,18 @@ fi
110
113
  # - Caches result in process environment
111
114
  #
112
115
  # RETURNS:
113
- # Absolute path to Python executable with claude-mpm dependencies
116
+ # Absolute path to Python executable with claude-mpm dependencies, or "uv run python" for UV projects
114
117
  #
115
118
  find_python_command() {
116
- # 1. Check if we're in a pipx installation first
119
+ # 1. Check for UV project first (uv.lock or pyproject.toml with uv)
120
+ if [ -f "$CLAUDE_MPM_ROOT/uv.lock" ]; then
121
+ if command -v uv &> /dev/null; then
122
+ echo "uv run python"
123
+ return
124
+ fi
125
+ fi
126
+
127
+ # 2. Check if we're in a pipx installation
117
128
  if [[ "$SCRIPT_DIR" == *"/.local/pipx/venvs/claude-mpm/"* ]]; then
118
129
  # pipx installation - use the pipx venv's Python directly
119
130
  if [ -f "$CLAUDE_MPM_ROOT/bin/python" ]; then
@@ -122,7 +133,7 @@ find_python_command() {
122
133
  fi
123
134
  fi
124
135
 
125
- # 2. Check for project-local virtual environment (common in development)
136
+ # 3. Check for project-local virtual environment (common in development)
126
137
  if [ -f "$CLAUDE_MPM_ROOT/venv/bin/activate" ]; then
127
138
  source "$CLAUDE_MPM_ROOT/venv/bin/activate"
128
139
  echo "$CLAUDE_MPM_ROOT/venv/bin/python"
@@ -173,15 +184,44 @@ fi
173
184
  # Set Socket.IO configuration for hook events
174
185
  export CLAUDE_MPM_SOCKETIO_PORT="${CLAUDE_MPM_SOCKETIO_PORT:-8765}"
175
186
 
176
- # Run the Python hook handler with all input
177
- # Use exec to replace the shell process with Python
178
- if ! exec "$PYTHON_CMD" -m claude_mpm.hooks.claude_hooks.hook_handler "$@" 2>/tmp/claude-mpm-hook-error.log; then
179
- # If the Python handler fails, always return continue to not block Claude
187
+ # Function for debug logging
188
+ log_debug() {
180
189
  if [ "${CLAUDE_MPM_HOOK_DEBUG}" = "true" ]; then
181
- echo "[$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)] Hook handler failed, see /tmp/claude-mpm-hook-error.log" >> /tmp/claude-mpm-hook.log
182
- echo "[$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)] Error: $(cat /tmp/claude-mpm-hook-error.log 2>/dev/null | head -5)" >> /tmp/claude-mpm-hook.log
190
+ echo "[$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)] $1" >> /tmp/claude-mpm-hook.log
191
+ fi
192
+ }
193
+
194
+ # Test Python works and module exists
195
+ # Handle UV's multi-word command specially
196
+ if [[ "$PYTHON_CMD" == "uv run python" ]]; then
197
+ if ! uv run python -c "import claude_mpm" 2>/dev/null; then
198
+ log_debug "claude_mpm module not available, continuing without hook"
199
+ echo '{"action": "continue"}'
200
+ exit 0
201
+ fi
202
+ else
203
+ if ! $PYTHON_CMD -c "import claude_mpm" 2>/dev/null; then
204
+ log_debug "claude_mpm module not available, continuing without hook"
205
+ echo '{"action": "continue"}'
206
+ exit 0
183
207
  fi
184
- # Return continue action to prevent blocking Claude Code
185
- echo '{"action": "continue"}'
186
- exit 0
187
- fi
208
+ fi
209
+
210
+ # Run the Python hook handler with all input
211
+ # Use exec to replace the shell process with Python
212
+ # Handle UV's multi-word command specially
213
+ if [[ "$PYTHON_CMD" == "uv run python" ]]; then
214
+ exec uv run python -m claude_mpm.hooks.claude_hooks.hook_handler "$@" 2>/tmp/claude-mpm-hook-error.log
215
+ else
216
+ exec "$PYTHON_CMD" -m claude_mpm.hooks.claude_hooks.hook_handler "$@" 2>/tmp/claude-mpm-hook-error.log
217
+ fi
218
+
219
+ # Note: exec replaces the shell process, so code below only runs if exec fails
220
+ # If we reach here, the Python handler failed
221
+ if [ "${CLAUDE_MPM_HOOK_DEBUG}" = "true" ]; then
222
+ echo "[$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)] Hook handler failed, see /tmp/claude-mpm-hook-error.log" >> /tmp/claude-mpm-hook.log
223
+ echo "[$(date -u +%Y-%m-%dT%H:%M:%S.%3NZ)] Error: $(cat /tmp/claude-mpm-hook-error.log 2>/dev/null | head -5)" >> /tmp/claude-mpm-hook.log
224
+ fi
225
+ # Return continue action to prevent blocking Claude Code
226
+ echo '{"action": "continue"}'
227
+ exit 0
@@ -7,6 +7,12 @@ dashboard, which includes both the Socket.IO server and web interface.
7
7
 
8
8
  WHY: Provides a simple command to start the monitoring dashboard that tracks
9
9
  Claude MPM events and agent activity in real-time.
10
+
11
+ SINGLE INSTANCE ENFORCEMENT:
12
+ - Only ONE monitor instance runs at a time on port 8765 (default)
13
+ - If monitor already running on default port: reuse existing, open browser
14
+ - If user specifies --port explicitly: use that port, fail if busy
15
+ - No auto-increment port selection (prevents multiple instances)
10
16
  """
11
17
 
12
18
  import argparse
@@ -15,12 +21,36 @@ import webbrowser
15
21
 
16
22
  from claude_mpm.core.logging_config import get_logger
17
23
  from claude_mpm.services.monitor.daemon import UnifiedMonitorDaemon
18
- from claude_mpm.services.port_manager import PortManager
24
+ from claude_mpm.services.monitor.daemon_manager import DaemonManager
19
25
 
20
26
  DEFAULT_PORT = 8765
21
27
  logger = get_logger(__name__)
22
28
 
23
29
 
30
+ def check_existing_monitor(host: str, port: int) -> bool:
31
+ """Check if monitor is already running on the specified port.
32
+
33
+ Args:
34
+ host: Host to check
35
+ port: Port to check
36
+
37
+ Returns:
38
+ True if monitor is running, False otherwise
39
+ """
40
+ try:
41
+ import requests
42
+
43
+ response = requests.get(f"http://{host}:{port}/health", timeout=2)
44
+ if response.status_code == 200:
45
+ data = response.json()
46
+ # Check if it's our claude-mpm-monitor service
47
+ if data.get("service") == "claude-mpm-monitor":
48
+ return True
49
+ except Exception:
50
+ pass
51
+ return False
52
+
53
+
24
54
  def main():
25
55
  """Main entry point for monitor launcher."""
26
56
  parser = argparse.ArgumentParser(
@@ -30,8 +60,8 @@ def main():
30
60
  parser.add_argument(
31
61
  "--port",
32
62
  type=int,
33
- default=DEFAULT_PORT,
34
- help=f"Port to run on (default: {DEFAULT_PORT})",
63
+ default=None, # Changed: None means use DEFAULT_PORT with single-instance check
64
+ help=f"Port to run on (default: {DEFAULT_PORT}). If specified, fails if port is busy.",
35
65
  )
36
66
 
37
67
  parser.add_argument(
@@ -46,20 +76,70 @@ def main():
46
76
  "--background", action="store_true", help="Run in background daemon mode"
47
77
  )
48
78
 
79
+ parser.add_argument(
80
+ "--dev",
81
+ action="store_true",
82
+ help="Enable development mode with hot reload for Svelte changes",
83
+ )
84
+
49
85
  args = parser.parse_args()
50
86
 
51
- # Find available port
52
- port_manager = PortManager()
53
- actual_port = port_manager.find_available_port(preferred_port=args.port)
87
+ # Determine target port
88
+ user_specified_port = args.port is not None
89
+ target_port = args.port if user_specified_port else DEFAULT_PORT
90
+
91
+ # SINGLE INSTANCE ENFORCEMENT:
92
+ # Check if monitor already running on target port
93
+ if check_existing_monitor(args.host, target_port):
94
+ logger.info(f"Monitor already running at http://{args.host}:{target_port}")
54
95
 
55
- if actual_port != args.port:
56
- logger.info(f"Port {args.port} is in use, using port {actual_port} instead")
96
+ # Open browser to existing instance if requested
97
+ if not args.no_browser:
98
+ url = f"http://{args.host}:{target_port}"
99
+ logger.info(f"Opening browser to existing instance: {url}")
100
+ webbrowser.open(url)
101
+
102
+ # Success - reusing existing instance
103
+ return
104
+
105
+ # Port selection logic:
106
+ # - If user specified --port: Use that exact port, fail if busy
107
+ # - If no --port: Use DEFAULT_PORT (8765), fail if busy
108
+ # - Never auto-increment to find free port
109
+
110
+ # Create daemon manager for port checking
111
+ daemon_manager = DaemonManager(port=target_port, host=args.host)
112
+
113
+ if not daemon_manager._is_port_available():
114
+ if user_specified_port:
115
+ # User explicitly requested a port - fail with clear message
116
+ logger.error(
117
+ f"Port {target_port} is already in use by another service. "
118
+ f"Please stop the existing service or choose a different port."
119
+ )
120
+ sys.exit(1)
121
+ else:
122
+ # Default port is busy - fail with helpful message
123
+ logger.error(
124
+ f"Default port {DEFAULT_PORT} is already in use by another service. "
125
+ f"Please stop the existing service with 'claude-mpm monitor stop' "
126
+ f"or specify a different port with --port."
127
+ )
128
+ sys.exit(1)
57
129
 
58
130
  # Start the monitor daemon
59
- logger.info(f"Starting Claude MPM monitor on {args.host}:{actual_port}")
131
+ if args.dev:
132
+ logger.info(
133
+ f"Starting Claude MPM monitor on {args.host}:{target_port} (DEV MODE - hot reload enabled)"
134
+ )
135
+ else:
136
+ logger.info(f"Starting Claude MPM monitor on {args.host}:{target_port}")
60
137
 
61
138
  daemon = UnifiedMonitorDaemon(
62
- host=args.host, port=actual_port, daemon_mode=args.background
139
+ host=args.host,
140
+ port=target_port,
141
+ daemon_mode=args.background,
142
+ enable_hot_reload=args.dev,
63
143
  )
64
144
 
65
145
  success = daemon.start()
@@ -67,14 +147,14 @@ def main():
67
147
  if success:
68
148
  # Open browser if requested
69
149
  if not args.no_browser:
70
- url = f"http://{args.host}:{actual_port}"
150
+ url = f"http://{args.host}:{target_port}"
71
151
  logger.info(f"Opening browser to {url}")
72
152
  webbrowser.open(url)
73
153
 
74
154
  if args.background:
75
- logger.info(f"Monitor daemon started in background on port {actual_port}")
155
+ logger.info(f"Monitor daemon started in background on port {target_port}")
76
156
  else:
77
- logger.info(f"Monitor running on port {actual_port}")
157
+ logger.info(f"Monitor running on port {target_port}")
78
158
  logger.info("Press Ctrl+C to stop")
79
159
  else:
80
160
  logger.error("Failed to start monitor")
File without changes