claude-mpm 4.2.23__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 +74 -18
- claude_mpm/services/monitor/management/lifecycle.py +176 -81
- claude_mpm/services/monitor/server.py +11 -8
- {claude_mpm-4.2.23.dist-info → claude_mpm-4.2.25.dist-info}/METADATA +1 -1
- {claude_mpm-4.2.23.dist-info → claude_mpm-4.2.25.dist-info}/RECORD +16 -14
- {claude_mpm-4.2.23.dist-info → claude_mpm-4.2.25.dist-info}/WHEEL +0 -0
- {claude_mpm-4.2.23.dist-info → claude_mpm-4.2.25.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.2.23.dist-info → claude_mpm-4.2.25.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.2.23.dist-info → claude_mpm-4.2.25.dist-info}/top_level.txt +0 -0
claude_mpm/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
4.2.
|
1
|
+
4.2.25
|
claude_mpm/cli/__init__.py
CHANGED
@@ -399,6 +399,16 @@ def _execute_command(command: str, args) -> int:
|
|
399
399
|
result = manage_mpm_init(args)
|
400
400
|
return result if result is not None else 0
|
401
401
|
|
402
|
+
# Handle uninstall command with lazy import
|
403
|
+
if command == "uninstall":
|
404
|
+
# Lazy import to avoid loading unless needed
|
405
|
+
from .commands.uninstall import UninstallCommand
|
406
|
+
|
407
|
+
cmd = UninstallCommand()
|
408
|
+
result = cmd.execute(args)
|
409
|
+
# Convert CommandResult to exit code
|
410
|
+
return result.exit_code if result else 0
|
411
|
+
|
402
412
|
# Map stable commands to their implementations
|
403
413
|
command_map = {
|
404
414
|
CLICommands.RUN.value: run_session,
|
@@ -93,7 +93,7 @@ class MonitorCommand(BaseCommand):
|
|
93
93
|
|
94
94
|
# Get force restart flag
|
95
95
|
force_restart = getattr(args, "force", False)
|
96
|
-
|
96
|
+
|
97
97
|
# Check if already running
|
98
98
|
if self.daemon.lifecycle.is_running() and not force_restart:
|
99
99
|
existing_pid = self.daemon.lifecycle.get_pid()
|
@@ -112,27 +112,29 @@ class MonitorCommand(BaseCommand):
|
|
112
112
|
if daemon_mode:
|
113
113
|
# Give it a moment to fully initialize
|
114
114
|
import time
|
115
|
+
|
115
116
|
time.sleep(0.5)
|
116
|
-
|
117
|
+
|
117
118
|
# Check if it's actually running
|
118
119
|
if not self.daemon.lifecycle.is_running():
|
119
120
|
return CommandResult.error_result(
|
120
121
|
"Monitor daemon failed to start. Check ~/.claude-mpm/monitor-daemon.log for details."
|
121
122
|
)
|
122
|
-
|
123
|
+
|
123
124
|
# Get the actual PID
|
124
125
|
actual_pid = self.daemon.lifecycle.get_pid()
|
125
126
|
mode_info = f" in background (PID: {actual_pid})"
|
126
127
|
else:
|
127
128
|
mode_info = " in foreground"
|
128
|
-
|
129
|
+
|
129
130
|
return CommandResult.success_result(
|
130
131
|
f"Unified monitor daemon started on {host}:{port}{mode_info}",
|
131
132
|
data={"url": f"http://{host}:{port}", "port": port, "mode": mode_str},
|
132
133
|
)
|
133
|
-
|
134
|
+
|
134
135
|
# Check if error was due to port already in use
|
135
136
|
import socket
|
137
|
+
|
136
138
|
try:
|
137
139
|
test_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
138
140
|
test_sock.connect((host, port))
|
@@ -142,7 +144,7 @@ class MonitorCommand(BaseCommand):
|
|
142
144
|
)
|
143
145
|
except:
|
144
146
|
pass
|
145
|
-
|
147
|
+
|
146
148
|
return CommandResult.error_result(
|
147
149
|
"Failed to start unified monitor daemon. Check ~/.claude-mpm/monitor-daemon.log for details."
|
148
150
|
)
|
@@ -0,0 +1,178 @@
|
|
1
|
+
"""
|
2
|
+
Uninstall command for claude-mpm CLI.
|
3
|
+
|
4
|
+
WHY: Users need a straightforward way to cleanly uninstall Claude MPM hooks
|
5
|
+
and other components without navigating through configuration menus.
|
6
|
+
|
7
|
+
DESIGN DECISIONS:
|
8
|
+
- Provide clear feedback about what is being removed
|
9
|
+
- Preserve user's other Claude settings
|
10
|
+
- Support both interactive confirmation and --yes flag
|
11
|
+
- Allow selective uninstallation of components
|
12
|
+
"""
|
13
|
+
|
14
|
+
from typing import Optional
|
15
|
+
|
16
|
+
from rich.console import Console
|
17
|
+
from rich.panel import Panel
|
18
|
+
from rich.prompt import Confirm
|
19
|
+
|
20
|
+
from ...services.hook_installer_service import HookInstallerService
|
21
|
+
from ...utils.console import console as default_console
|
22
|
+
from ..shared import BaseCommand, CommandResult
|
23
|
+
|
24
|
+
|
25
|
+
class UninstallCommand(BaseCommand):
|
26
|
+
"""Handle uninstallation of Claude MPM components."""
|
27
|
+
|
28
|
+
def __init__(self, console: Optional[Console] = None):
|
29
|
+
"""Initialize the uninstall command.
|
30
|
+
|
31
|
+
Args:
|
32
|
+
console: Optional Rich console for output.
|
33
|
+
"""
|
34
|
+
super().__init__("uninstall")
|
35
|
+
self.console = console or default_console
|
36
|
+
self.hook_service = HookInstallerService()
|
37
|
+
|
38
|
+
def run(self, args) -> CommandResult:
|
39
|
+
"""Execute the uninstall command.
|
40
|
+
|
41
|
+
Args:
|
42
|
+
args: Parsed command line arguments.
|
43
|
+
|
44
|
+
Returns:
|
45
|
+
CommandResult indicating success or failure.
|
46
|
+
"""
|
47
|
+
try:
|
48
|
+
# Check what component to uninstall
|
49
|
+
if args.component == "hooks" or args.all:
|
50
|
+
return self._uninstall_hooks(args)
|
51
|
+
if args.component == "all":
|
52
|
+
return self._uninstall_all(args)
|
53
|
+
# Default to hooks if no component specified
|
54
|
+
return self._uninstall_hooks(args)
|
55
|
+
|
56
|
+
except Exception as e:
|
57
|
+
self.console.print(f"[red]Error during uninstallation: {e}[/red]")
|
58
|
+
return CommandResult.error_result(str(e))
|
59
|
+
|
60
|
+
def _uninstall_hooks(self, args) -> CommandResult:
|
61
|
+
"""Uninstall Claude MPM hooks.
|
62
|
+
|
63
|
+
Args:
|
64
|
+
args: Parsed command line arguments.
|
65
|
+
|
66
|
+
Returns:
|
67
|
+
CommandResult indicating success or failure.
|
68
|
+
"""
|
69
|
+
try:
|
70
|
+
# Check if hooks are installed
|
71
|
+
if not self.hook_service.is_hooks_configured():
|
72
|
+
self.console.print(
|
73
|
+
"[yellow]No Claude MPM hooks are currently installed.[/yellow]"
|
74
|
+
)
|
75
|
+
return CommandResult.success_result("No hooks to uninstall")
|
76
|
+
|
77
|
+
# Get hook status for display
|
78
|
+
status = self.hook_service.get_hook_status()
|
79
|
+
|
80
|
+
# Show what will be removed
|
81
|
+
self.console.print(
|
82
|
+
"\n[cyan]The following Claude MPM hooks will be removed:[/cyan]"
|
83
|
+
)
|
84
|
+
for hook_type, configured in status.get("hook_types", {}).items():
|
85
|
+
if configured:
|
86
|
+
self.console.print(f" • {hook_type}")
|
87
|
+
|
88
|
+
# Confirm unless --yes flag is provided
|
89
|
+
if not args.yes:
|
90
|
+
if not Confirm.ask(
|
91
|
+
"\n[yellow]Do you want to proceed with uninstallation?[/yellow]"
|
92
|
+
):
|
93
|
+
self.console.print("[yellow]Uninstallation cancelled.[/yellow]")
|
94
|
+
return CommandResult.success_result(
|
95
|
+
"Uninstallation cancelled by user"
|
96
|
+
)
|
97
|
+
|
98
|
+
# Perform uninstallation
|
99
|
+
self.console.print("\n[cyan]Uninstalling Claude MPM hooks...[/cyan]")
|
100
|
+
success = self.hook_service.uninstall_hooks()
|
101
|
+
|
102
|
+
if success:
|
103
|
+
self.console.print(
|
104
|
+
Panel(
|
105
|
+
"[green]✓ Claude MPM hooks have been successfully uninstalled.[/green]\n\n"
|
106
|
+
"Your other Claude settings have been preserved.",
|
107
|
+
title="Uninstallation Complete",
|
108
|
+
border_style="green",
|
109
|
+
)
|
110
|
+
)
|
111
|
+
return CommandResult.success_result("Hooks uninstalled successfully")
|
112
|
+
self.console.print(
|
113
|
+
"[red]Failed to uninstall hooks. Check the logs for details.[/red]"
|
114
|
+
)
|
115
|
+
return CommandResult.error_result("Failed to uninstall hooks")
|
116
|
+
|
117
|
+
except Exception as e:
|
118
|
+
return CommandResult.error_result(f"Error uninstalling hooks: {e}")
|
119
|
+
|
120
|
+
def _uninstall_all(self, args) -> CommandResult:
|
121
|
+
"""Uninstall all Claude MPM components.
|
122
|
+
|
123
|
+
Args:
|
124
|
+
args: Parsed command line arguments.
|
125
|
+
|
126
|
+
Returns:
|
127
|
+
CommandResult indicating success or failure.
|
128
|
+
"""
|
129
|
+
# For now, we only have hooks to uninstall
|
130
|
+
# This method can be extended in the future for other components
|
131
|
+
result = self._uninstall_hooks(args)
|
132
|
+
|
133
|
+
# Additional cleanup can be added here
|
134
|
+
# For example: removing agent configurations, cache, etc.
|
135
|
+
|
136
|
+
return result
|
137
|
+
|
138
|
+
|
139
|
+
def add_uninstall_parser(subparsers):
|
140
|
+
"""Add the uninstall subparser.
|
141
|
+
|
142
|
+
Args:
|
143
|
+
subparsers: The subparsers object from the main parser.
|
144
|
+
|
145
|
+
Returns:
|
146
|
+
The configured uninstall parser.
|
147
|
+
"""
|
148
|
+
parser = subparsers.add_parser(
|
149
|
+
"uninstall",
|
150
|
+
help="Uninstall Claude MPM components",
|
151
|
+
description="Remove Claude MPM hooks and other components while preserving other Claude settings",
|
152
|
+
)
|
153
|
+
|
154
|
+
# Component selection
|
155
|
+
parser.add_argument(
|
156
|
+
"component",
|
157
|
+
nargs="?",
|
158
|
+
choices=["hooks", "all"],
|
159
|
+
default="hooks",
|
160
|
+
help="Component to uninstall (default: hooks)",
|
161
|
+
)
|
162
|
+
|
163
|
+
# Confirmation bypass
|
164
|
+
parser.add_argument(
|
165
|
+
"-y", "--yes", action="store_true", help="Skip confirmation prompt"
|
166
|
+
)
|
167
|
+
|
168
|
+
# Force uninstall
|
169
|
+
parser.add_argument(
|
170
|
+
"--force", action="store_true", help="Force uninstallation even if errors occur"
|
171
|
+
)
|
172
|
+
|
173
|
+
# All components
|
174
|
+
parser.add_argument(
|
175
|
+
"--all", action="store_true", help="Uninstall all Claude MPM components"
|
176
|
+
)
|
177
|
+
|
178
|
+
return parser
|
@@ -345,6 +345,14 @@ def create_parser(
|
|
345
345
|
except ImportError:
|
346
346
|
pass
|
347
347
|
|
348
|
+
# Add uninstall command parser
|
349
|
+
try:
|
350
|
+
from ..commands.uninstall import add_uninstall_parser
|
351
|
+
|
352
|
+
add_uninstall_parser(subparsers)
|
353
|
+
except ImportError:
|
354
|
+
pass
|
355
|
+
|
348
356
|
# Add debug command parser
|
349
357
|
try:
|
350
358
|
from .debug_parser import add_debug_subparser
|
@@ -75,8 +75,11 @@ class UnifiedDashboardManager(IUnifiedDashboardManager):
|
|
75
75
|
self._lock = threading.Lock()
|
76
76
|
|
77
77
|
def start_dashboard(
|
78
|
-
self,
|
79
|
-
|
78
|
+
self,
|
79
|
+
port: int = 8765,
|
80
|
+
background: bool = False,
|
81
|
+
open_browser: bool = True,
|
82
|
+
force_restart: bool = False,
|
80
83
|
) -> Tuple[bool, bool]:
|
81
84
|
"""
|
82
85
|
Start the dashboard using unified daemon.
|
@@ -95,19 +98,23 @@ class UnifiedDashboardManager(IUnifiedDashboardManager):
|
|
95
98
|
daemon = UnifiedMonitorDaemon(
|
96
99
|
host="localhost", port=port, daemon_mode=background
|
97
100
|
)
|
98
|
-
|
101
|
+
|
99
102
|
# Check if it's our service running
|
100
103
|
is_ours, pid = daemon.lifecycle.is_our_service("localhost")
|
101
|
-
|
104
|
+
|
102
105
|
if is_ours and not force_restart:
|
103
106
|
# Our service is already running, just open browser if needed
|
104
|
-
self.logger.info(
|
107
|
+
self.logger.info(
|
108
|
+
f"Our dashboard already running on port {port} (PID: {pid})"
|
109
|
+
)
|
105
110
|
browser_opened = False
|
106
111
|
if open_browser:
|
107
112
|
browser_opened = self.open_browser(self.get_dashboard_url(port))
|
108
113
|
return True, browser_opened
|
109
|
-
|
110
|
-
self.logger.info(
|
114
|
+
if is_ours and force_restart:
|
115
|
+
self.logger.info(
|
116
|
+
f"Force restarting our dashboard on port {port} (PID: {pid})"
|
117
|
+
)
|
111
118
|
elif self.is_dashboard_running(port) and not force_restart:
|
112
119
|
# Different service is using the port
|
113
120
|
self.logger.warning(f"Port {port} is in use by a different service")
|