claude-mpm 4.2.24__py3-none-any.whl → 4.2.25__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- claude_mpm/VERSION +1 -1
- claude_mpm/cli/__init__.py +10 -0
- claude_mpm/cli/commands/monitor.py +8 -6
- claude_mpm/cli/commands/uninstall.py +178 -0
- claude_mpm/cli/parsers/base_parser.py +8 -0
- claude_mpm/services/cli/unified_dashboard_manager.py +14 -7
- claude_mpm/services/hook_installer_service.py +507 -0
- claude_mpm/services/monitor/daemon.py +72 -24
- claude_mpm/services/monitor/management/lifecycle.py +132 -90
- claude_mpm/services/monitor/server.py +11 -8
- {claude_mpm-4.2.24.dist-info → claude_mpm-4.2.25.dist-info}/METADATA +1 -1
- {claude_mpm-4.2.24.dist-info → claude_mpm-4.2.25.dist-info}/RECORD +16 -14
- {claude_mpm-4.2.24.dist-info → claude_mpm-4.2.25.dist-info}/WHEEL +0 -0
- {claude_mpm-4.2.24.dist-info → claude_mpm-4.2.25.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.2.24.dist-info → claude_mpm-4.2.25.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.2.24.dist-info → claude_mpm-4.2.25.dist-info}/top_level.txt +0 -0
@@ -23,6 +23,7 @@ from pathlib import Path
|
|
23
23
|
from typing import Optional
|
24
24
|
|
25
25
|
from ...core.logging_config import get_logger
|
26
|
+
from ..hook_installer_service import HookInstallerService
|
26
27
|
from .management.health import HealthMonitor
|
27
28
|
from .management.lifecycle import DaemonLifecycle
|
28
29
|
from .server import UnifiedMonitorServer
|
@@ -59,9 +60,9 @@ class UnifiedMonitorDaemon:
|
|
59
60
|
|
60
61
|
# Daemon management with port for verification
|
61
62
|
self.lifecycle = DaemonLifecycle(
|
62
|
-
pid_file=pid_file or self._get_default_pid_file(),
|
63
|
+
pid_file=pid_file or self._get_default_pid_file(),
|
63
64
|
log_file=log_file,
|
64
|
-
port=port
|
65
|
+
port=port,
|
65
66
|
)
|
66
67
|
|
67
68
|
# Core server
|
@@ -70,6 +71,9 @@ class UnifiedMonitorDaemon:
|
|
70
71
|
# Health monitoring
|
71
72
|
self.health_monitor = HealthMonitor(port=port)
|
72
73
|
|
74
|
+
# Hook installer service
|
75
|
+
self.hook_installer = HookInstallerService()
|
76
|
+
|
73
77
|
# State
|
74
78
|
self.running = False
|
75
79
|
self.shutdown_event = threading.Event()
|
@@ -100,7 +104,7 @@ class UnifiedMonitorDaemon:
|
|
100
104
|
|
101
105
|
def _start_daemon(self, force_restart: bool = False) -> bool:
|
102
106
|
"""Start as background daemon process.
|
103
|
-
|
107
|
+
|
104
108
|
Args:
|
105
109
|
force_restart: If True, restart existing service if it's ours
|
106
110
|
"""
|
@@ -109,14 +113,18 @@ class UnifiedMonitorDaemon:
|
|
109
113
|
# Check if already running
|
110
114
|
if self.lifecycle.is_running():
|
111
115
|
existing_pid = self.lifecycle.get_pid()
|
112
|
-
|
116
|
+
|
113
117
|
if force_restart:
|
114
118
|
# Check if it's our service
|
115
|
-
self.logger.debug(
|
119
|
+
self.logger.debug(
|
120
|
+
f"Checking if existing daemon (PID: {existing_pid}) is our service..."
|
121
|
+
)
|
116
122
|
is_ours, detected_pid = self.lifecycle.is_our_service(self.host)
|
117
|
-
|
123
|
+
|
118
124
|
if is_ours:
|
119
|
-
self.logger.info(
|
125
|
+
self.logger.info(
|
126
|
+
f"Force restarting our existing claude-mpm monitor daemon (PID: {detected_pid or existing_pid})"
|
127
|
+
)
|
120
128
|
# Stop the existing daemon
|
121
129
|
if self.lifecycle.stop_daemon():
|
122
130
|
# Wait a moment for port to be released
|
@@ -125,19 +133,27 @@ class UnifiedMonitorDaemon:
|
|
125
133
|
self.logger.error("Failed to stop existing daemon for restart")
|
126
134
|
return False
|
127
135
|
else:
|
128
|
-
self.logger.warning(
|
129
|
-
|
136
|
+
self.logger.warning(
|
137
|
+
f"Port {self.port} is in use by another service (PID: {existing_pid}). Cannot force restart."
|
138
|
+
)
|
139
|
+
self.logger.info(
|
140
|
+
"To restart the claude-mpm monitor, first stop the other service or use a different port."
|
141
|
+
)
|
130
142
|
return False
|
131
143
|
else:
|
132
144
|
self.logger.warning(f"Daemon already running with PID {existing_pid}")
|
133
145
|
return False
|
134
|
-
|
146
|
+
|
135
147
|
# Check for orphaned processes (service running but no PID file)
|
136
148
|
elif force_restart:
|
137
|
-
self.logger.debug(
|
149
|
+
self.logger.debug(
|
150
|
+
"No PID file found, checking for orphaned claude-mpm service..."
|
151
|
+
)
|
138
152
|
is_ours, pid = self.lifecycle.is_our_service(self.host)
|
139
153
|
if is_ours and pid:
|
140
|
-
self.logger.info(
|
154
|
+
self.logger.info(
|
155
|
+
f"Found orphaned claude-mpm monitor service (PID: {pid}), force restarting"
|
156
|
+
)
|
141
157
|
# Try to kill the orphaned process
|
142
158
|
try:
|
143
159
|
os.kill(pid, signal.SIGTERM)
|
@@ -155,7 +171,7 @@ class UnifiedMonitorDaemon:
|
|
155
171
|
except Exception as e:
|
156
172
|
self.logger.error(f"Failed to kill orphaned process: {e}")
|
157
173
|
return False
|
158
|
-
|
174
|
+
|
159
175
|
# Verify port is available before forking
|
160
176
|
port_available, error_msg = self.lifecycle.verify_port_available(self.host)
|
161
177
|
if not port_available:
|
@@ -186,7 +202,7 @@ class UnifiedMonitorDaemon:
|
|
186
202
|
|
187
203
|
def _start_foreground(self, force_restart: bool = False) -> bool:
|
188
204
|
"""Start in foreground mode.
|
189
|
-
|
205
|
+
|
190
206
|
Args:
|
191
207
|
force_restart: If True, restart existing service if it's ours
|
192
208
|
"""
|
@@ -195,14 +211,18 @@ class UnifiedMonitorDaemon:
|
|
195
211
|
# Check if already running (check PID file even in foreground mode)
|
196
212
|
if self.lifecycle.is_running():
|
197
213
|
existing_pid = self.lifecycle.get_pid()
|
198
|
-
|
214
|
+
|
199
215
|
if force_restart:
|
200
216
|
# Check if it's our service
|
201
|
-
self.logger.debug(
|
217
|
+
self.logger.debug(
|
218
|
+
f"Checking if existing daemon (PID: {existing_pid}) is our service..."
|
219
|
+
)
|
202
220
|
is_ours, detected_pid = self.lifecycle.is_our_service(self.host)
|
203
|
-
|
221
|
+
|
204
222
|
if is_ours:
|
205
|
-
self.logger.info(
|
223
|
+
self.logger.info(
|
224
|
+
f"Force restarting our existing claude-mpm monitor daemon (PID: {detected_pid or existing_pid})"
|
225
|
+
)
|
206
226
|
# Stop the existing daemon
|
207
227
|
if self.lifecycle.stop_daemon():
|
208
228
|
# Wait a moment for port to be released
|
@@ -211,21 +231,29 @@ class UnifiedMonitorDaemon:
|
|
211
231
|
self.logger.error("Failed to stop existing daemon for restart")
|
212
232
|
return False
|
213
233
|
else:
|
214
|
-
self.logger.warning(
|
215
|
-
|
234
|
+
self.logger.warning(
|
235
|
+
f"Port {self.port} is in use by another service (PID: {existing_pid}). Cannot force restart."
|
236
|
+
)
|
237
|
+
self.logger.info(
|
238
|
+
"To restart the claude-mpm monitor, first stop the other service or use a different port."
|
239
|
+
)
|
216
240
|
return False
|
217
241
|
else:
|
218
242
|
self.logger.warning(
|
219
243
|
f"Monitor daemon already running with PID {existing_pid}"
|
220
244
|
)
|
221
245
|
return False
|
222
|
-
|
246
|
+
|
223
247
|
# Check for orphaned processes (service running but no PID file)
|
224
248
|
elif force_restart:
|
225
|
-
self.logger.debug(
|
249
|
+
self.logger.debug(
|
250
|
+
"No PID file found, checking for orphaned claude-mpm service..."
|
251
|
+
)
|
226
252
|
is_ours, pid = self.lifecycle.is_our_service(self.host)
|
227
253
|
if is_ours and pid:
|
228
|
-
self.logger.info(
|
254
|
+
self.logger.info(
|
255
|
+
f"Found orphaned claude-mpm monitor service (PID: {pid}), force restarting"
|
256
|
+
)
|
229
257
|
# Try to kill the orphaned process
|
230
258
|
try:
|
231
259
|
os.kill(pid, signal.SIGTERM)
|
@@ -271,6 +299,26 @@ class UnifiedMonitorDaemon:
|
|
271
299
|
self.lifecycle._report_startup_error(error_msg)
|
272
300
|
return False
|
273
301
|
|
302
|
+
# Check and install hooks if needed
|
303
|
+
try:
|
304
|
+
if not self.hook_installer.is_hooks_configured():
|
305
|
+
self.logger.info("Claude Code hooks not configured, installing...")
|
306
|
+
if self.hook_installer.install_hooks():
|
307
|
+
self.logger.info("Claude Code hooks installed successfully")
|
308
|
+
else:
|
309
|
+
# Don't fail startup if hook installation fails
|
310
|
+
# The monitor can still function without hooks
|
311
|
+
self.logger.warning(
|
312
|
+
"Failed to install Claude Code hooks. Monitor will run without hook integration."
|
313
|
+
)
|
314
|
+
else:
|
315
|
+
self.logger.info("Claude Code hooks are already configured")
|
316
|
+
except Exception as e:
|
317
|
+
# Don't fail startup if hook checking fails
|
318
|
+
self.logger.warning(
|
319
|
+
f"Error checking/installing hooks: {e}. Monitor will run without hook integration."
|
320
|
+
)
|
321
|
+
|
274
322
|
# Start health monitoring
|
275
323
|
self.health_monitor.start()
|
276
324
|
|
@@ -285,7 +333,7 @@ class UnifiedMonitorDaemon:
|
|
285
333
|
|
286
334
|
self.running = True
|
287
335
|
self.logger.info("Unified monitor daemon started successfully")
|
288
|
-
|
336
|
+
|
289
337
|
# Report successful startup to parent (for daemon mode)
|
290
338
|
if self.daemon_mode:
|
291
339
|
self.lifecycle._report_startup_success()
|