claude-mpm 5.6.23__py3-none-any.whl → 5.6.73__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.
- claude_mpm/VERSION +1 -1
- claude_mpm/auth/__init__.py +35 -0
- claude_mpm/auth/callback_server.py +328 -0
- claude_mpm/auth/models.py +104 -0
- claude_mpm/auth/oauth_manager.py +266 -0
- claude_mpm/auth/providers/__init__.py +12 -0
- claude_mpm/auth/providers/base.py +165 -0
- claude_mpm/auth/providers/google.py +261 -0
- claude_mpm/auth/token_storage.py +252 -0
- claude_mpm/cli/commands/commander.py +6 -6
- claude_mpm/cli/commands/mcp.py +29 -17
- claude_mpm/cli/commands/mcp_command_router.py +39 -0
- claude_mpm/cli/commands/mcp_service_commands.py +304 -0
- claude_mpm/cli/commands/oauth.py +481 -0
- claude_mpm/cli/executor.py +9 -0
- claude_mpm/cli/helpers.py +1 -1
- claude_mpm/cli/parsers/base_parser.py +13 -0
- claude_mpm/cli/parsers/mcp_parser.py +79 -0
- claude_mpm/cli/parsers/oauth_parser.py +165 -0
- claude_mpm/cli/startup.py +150 -33
- claude_mpm/cli/startup_display.py +3 -2
- claude_mpm/commander/chat/cli.py +5 -2
- claude_mpm/commander/chat/commands.py +42 -16
- claude_mpm/commander/chat/repl.py +1581 -70
- claude_mpm/commander/events/manager.py +61 -1
- claude_mpm/commander/frameworks/base.py +87 -0
- claude_mpm/commander/frameworks/mpm.py +9 -14
- claude_mpm/commander/git/__init__.py +5 -0
- claude_mpm/commander/git/worktree_manager.py +212 -0
- claude_mpm/commander/instance_manager.py +428 -13
- claude_mpm/commander/models/events.py +6 -0
- claude_mpm/commander/persistence/state_store.py +95 -1
- claude_mpm/commander/tmux_orchestrator.py +3 -2
- claude_mpm/constants.py +5 -0
- claude_mpm/core/hook_manager.py +2 -1
- claude_mpm/core/logging_utils.py +4 -2
- claude_mpm/core/output_style_manager.py +5 -2
- claude_mpm/core/socketio_pool.py +34 -10
- claude_mpm/hooks/claude_hooks/auto_pause_handler.py +1 -1
- claude_mpm/hooks/claude_hooks/event_handlers.py +206 -94
- claude_mpm/hooks/claude_hooks/hook_handler.py +115 -32
- claude_mpm/hooks/claude_hooks/installer.py +175 -51
- claude_mpm/hooks/claude_hooks/memory_integration.py +1 -1
- claude_mpm/hooks/claude_hooks/response_tracking.py +1 -1
- claude_mpm/hooks/claude_hooks/services/__init__.py +21 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +2 -2
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +2 -2
- claude_mpm/hooks/claude_hooks/services/container.py +326 -0
- claude_mpm/hooks/claude_hooks/services/protocols.py +328 -0
- claude_mpm/hooks/claude_hooks/services/state_manager.py +2 -2
- claude_mpm/hooks/claude_hooks/services/subagent_processor.py +2 -2
- claude_mpm/hooks/templates/pre_tool_use_simple.py +6 -6
- claude_mpm/hooks/templates/pre_tool_use_template.py +6 -6
- claude_mpm/init.py +21 -14
- claude_mpm/mcp/__init__.py +9 -0
- claude_mpm/mcp/google_workspace_server.py +610 -0
- claude_mpm/scripts/claude-hook-handler.sh +3 -3
- claude_mpm/services/command_deployment_service.py +44 -26
- claude_mpm/services/hook_installer_service.py +77 -8
- claude_mpm/services/mcp_config_manager.py +99 -19
- claude_mpm/services/mcp_service_registry.py +294 -0
- claude_mpm/services/monitor/server.py +6 -1
- {claude_mpm-5.6.23.dist-info → claude_mpm-5.6.73.dist-info}/METADATA +24 -1
- {claude_mpm-5.6.23.dist-info → claude_mpm-5.6.73.dist-info}/RECORD +69 -64
- {claude_mpm-5.6.23.dist-info → claude_mpm-5.6.73.dist-info}/WHEEL +1 -1
- {claude_mpm-5.6.23.dist-info → claude_mpm-5.6.73.dist-info}/entry_points.txt +2 -0
- claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
- {claude_mpm-5.6.23.dist-info → claude_mpm-5.6.73.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.6.23.dist-info → claude_mpm-5.6.73.dist-info}/licenses/LICENSE-FAQ.md +0 -0
- {claude_mpm-5.6.23.dist-info → claude_mpm-5.6.73.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""
|
|
2
|
+
OAuth command parser for claude-mpm CLI.
|
|
3
|
+
|
|
4
|
+
WHY: This module provides the oauth command with subcommands for
|
|
5
|
+
managing OAuth authentication for MCP services that require OAuth2 flows.
|
|
6
|
+
|
|
7
|
+
DESIGN DECISION: 'oauth' provides comprehensive OAuth management including
|
|
8
|
+
listing OAuth-capable services, setup, status, token revocation, and refresh.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
|
|
13
|
+
from .base_parser import add_common_arguments
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def add_oauth_subparser(subparsers) -> argparse.ArgumentParser:
|
|
17
|
+
"""
|
|
18
|
+
Add the oauth subparser with all OAuth management subcommands.
|
|
19
|
+
|
|
20
|
+
WHY: 'oauth' provides comprehensive OAuth management for MCP services
|
|
21
|
+
that require OAuth2 authentication flows.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
subparsers: The subparsers object from the main parser
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
The configured oauth subparser
|
|
28
|
+
"""
|
|
29
|
+
# OAuth command with subcommands
|
|
30
|
+
oauth_parser = subparsers.add_parser(
|
|
31
|
+
"oauth",
|
|
32
|
+
help="Manage OAuth authentication for MCP services",
|
|
33
|
+
description="""
|
|
34
|
+
Manage OAuth authentication for MCP services.
|
|
35
|
+
|
|
36
|
+
Available commands:
|
|
37
|
+
list List OAuth-capable MCP services
|
|
38
|
+
setup <service> Set up OAuth authentication for a service
|
|
39
|
+
status <service> Show OAuth token status for a service
|
|
40
|
+
revoke <service> Revoke OAuth tokens for a service
|
|
41
|
+
refresh <service> Refresh OAuth tokens for a service
|
|
42
|
+
|
|
43
|
+
Examples:
|
|
44
|
+
claude-mpm oauth list
|
|
45
|
+
claude-mpm oauth setup workspace-mcp
|
|
46
|
+
claude-mpm oauth status workspace-mcp
|
|
47
|
+
claude-mpm oauth revoke workspace-mcp
|
|
48
|
+
claude-mpm oauth refresh workspace-mcp
|
|
49
|
+
""",
|
|
50
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
51
|
+
)
|
|
52
|
+
add_common_arguments(oauth_parser)
|
|
53
|
+
|
|
54
|
+
# Add subcommands
|
|
55
|
+
oauth_subparsers = oauth_parser.add_subparsers(
|
|
56
|
+
dest="oauth_command", help="OAuth commands", metavar="SUBCOMMAND"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# List subcommand
|
|
60
|
+
list_parser = oauth_subparsers.add_parser(
|
|
61
|
+
"list",
|
|
62
|
+
help="List OAuth-capable MCP services",
|
|
63
|
+
description="List all MCP services that support OAuth authentication.",
|
|
64
|
+
)
|
|
65
|
+
add_common_arguments(list_parser)
|
|
66
|
+
list_parser.add_argument(
|
|
67
|
+
"--format",
|
|
68
|
+
choices=["table", "json"],
|
|
69
|
+
default="table",
|
|
70
|
+
help="Output format (default: table)",
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# Setup subcommand
|
|
74
|
+
setup_parser = oauth_subparsers.add_parser(
|
|
75
|
+
"setup",
|
|
76
|
+
help="Set up OAuth authentication for a service",
|
|
77
|
+
description="""
|
|
78
|
+
Set up OAuth authentication for an MCP service.
|
|
79
|
+
|
|
80
|
+
This command initiates the OAuth2 flow by:
|
|
81
|
+
1. Looking for credentials in .env.local, .env, or environment variables
|
|
82
|
+
2. Prompting for credentials if not found
|
|
83
|
+
3. Opening a browser for user authentication
|
|
84
|
+
4. Starting a local callback server to receive the OAuth redirect
|
|
85
|
+
5. Storing the tokens securely for future use
|
|
86
|
+
|
|
87
|
+
Required environment variables (checked in order):
|
|
88
|
+
1. .env.local file (highest priority)
|
|
89
|
+
2. .env file
|
|
90
|
+
3. Environment variables
|
|
91
|
+
|
|
92
|
+
For Google OAuth services:
|
|
93
|
+
GOOGLE_OAUTH_CLIENT_ID - Your OAuth client ID
|
|
94
|
+
GOOGLE_OAUTH_CLIENT_SECRET - Your OAuth client secret
|
|
95
|
+
|
|
96
|
+
Get credentials from: https://console.cloud.google.com/apis/credentials
|
|
97
|
+
""",
|
|
98
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
99
|
+
)
|
|
100
|
+
add_common_arguments(setup_parser)
|
|
101
|
+
setup_parser.add_argument(
|
|
102
|
+
"service_name",
|
|
103
|
+
help="Name of the MCP service to authenticate (e.g., workspace-mcp)",
|
|
104
|
+
)
|
|
105
|
+
setup_parser.add_argument(
|
|
106
|
+
"--no-browser",
|
|
107
|
+
action="store_true",
|
|
108
|
+
help="Don't open browser automatically, just print the URL",
|
|
109
|
+
)
|
|
110
|
+
setup_parser.add_argument(
|
|
111
|
+
"--port",
|
|
112
|
+
type=int,
|
|
113
|
+
default=8085,
|
|
114
|
+
help="Port for the OAuth callback server (default: 8085)",
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# Status subcommand
|
|
118
|
+
status_parser = oauth_subparsers.add_parser(
|
|
119
|
+
"status",
|
|
120
|
+
help="Show OAuth token status for a service",
|
|
121
|
+
description="Display the current OAuth token status including validity and expiration.",
|
|
122
|
+
)
|
|
123
|
+
add_common_arguments(status_parser)
|
|
124
|
+
status_parser.add_argument(
|
|
125
|
+
"service_name",
|
|
126
|
+
help="Name of the MCP service to check",
|
|
127
|
+
)
|
|
128
|
+
status_parser.add_argument(
|
|
129
|
+
"--format",
|
|
130
|
+
choices=["table", "json"],
|
|
131
|
+
default="table",
|
|
132
|
+
help="Output format (default: table)",
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# Revoke subcommand
|
|
136
|
+
revoke_parser = oauth_subparsers.add_parser(
|
|
137
|
+
"revoke",
|
|
138
|
+
help="Revoke OAuth tokens for a service",
|
|
139
|
+
description="Revoke and delete stored OAuth tokens for a service.",
|
|
140
|
+
)
|
|
141
|
+
add_common_arguments(revoke_parser)
|
|
142
|
+
revoke_parser.add_argument(
|
|
143
|
+
"service_name",
|
|
144
|
+
help="Name of the MCP service to revoke tokens for",
|
|
145
|
+
)
|
|
146
|
+
revoke_parser.add_argument(
|
|
147
|
+
"-y",
|
|
148
|
+
"--yes",
|
|
149
|
+
action="store_true",
|
|
150
|
+
help="Skip confirmation prompt",
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
# Refresh subcommand
|
|
154
|
+
refresh_parser = oauth_subparsers.add_parser(
|
|
155
|
+
"refresh",
|
|
156
|
+
help="Refresh OAuth tokens for a service",
|
|
157
|
+
description="Refresh the OAuth tokens using the stored refresh token.",
|
|
158
|
+
)
|
|
159
|
+
add_common_arguments(refresh_parser)
|
|
160
|
+
refresh_parser.add_argument(
|
|
161
|
+
"service_name",
|
|
162
|
+
help="Name of the MCP service to refresh tokens for",
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
return oauth_parser
|
claude_mpm/cli/startup.py
CHANGED
|
@@ -13,6 +13,49 @@ import sys
|
|
|
13
13
|
from pathlib import Path
|
|
14
14
|
|
|
15
15
|
|
|
16
|
+
def cleanup_user_level_hooks() -> bool:
|
|
17
|
+
"""Remove stale user-level hooks directory.
|
|
18
|
+
|
|
19
|
+
WHY: claude-mpm previously deployed hooks to ~/.claude/hooks/claude-mpm/
|
|
20
|
+
(user-level). This is now deprecated in favor of project-level hooks
|
|
21
|
+
configured in .claude/settings.local.json. Stale user-level hooks can
|
|
22
|
+
cause conflicts and confusion.
|
|
23
|
+
|
|
24
|
+
DESIGN DECISION: Runs early in startup, before project hook sync.
|
|
25
|
+
Non-blocking - failures are logged at debug level but don't prevent startup.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
bool: True if hooks were cleaned up, False if none found or cleanup failed
|
|
29
|
+
"""
|
|
30
|
+
import shutil
|
|
31
|
+
|
|
32
|
+
user_hooks_dir = Path.home() / ".claude" / "hooks" / "claude-mpm"
|
|
33
|
+
|
|
34
|
+
if not user_hooks_dir.exists():
|
|
35
|
+
return False
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
from ..core.logger import get_logger
|
|
39
|
+
|
|
40
|
+
logger = get_logger("startup")
|
|
41
|
+
logger.debug(f"Removing stale user-level hooks directory: {user_hooks_dir}")
|
|
42
|
+
|
|
43
|
+
shutil.rmtree(user_hooks_dir)
|
|
44
|
+
|
|
45
|
+
logger.debug("User-level hooks cleanup complete")
|
|
46
|
+
return True
|
|
47
|
+
except Exception as e:
|
|
48
|
+
# Non-critical - log but don't fail startup
|
|
49
|
+
try:
|
|
50
|
+
from ..core.logger import get_logger
|
|
51
|
+
|
|
52
|
+
logger = get_logger("startup")
|
|
53
|
+
logger.debug(f"Failed to cleanup user-level hooks (non-fatal): {e}")
|
|
54
|
+
except Exception: # nosec B110
|
|
55
|
+
pass # Avoid any errors in error handling
|
|
56
|
+
return False
|
|
57
|
+
|
|
58
|
+
|
|
16
59
|
def sync_hooks_on_startup(quiet: bool = False) -> bool:
|
|
17
60
|
"""Ensure hooks are up-to-date on startup.
|
|
18
61
|
|
|
@@ -20,7 +63,12 @@ def sync_hooks_on_startup(quiet: bool = False) -> bool:
|
|
|
20
63
|
Reinstalling hooks ensures the hook format matches the current code.
|
|
21
64
|
|
|
22
65
|
DESIGN DECISION: Shows brief status message on success for user awareness.
|
|
23
|
-
Failures are logged but don't prevent startup to ensure claude-mpm
|
|
66
|
+
Failures are logged but don't prevent startup to ensure claude-mpm
|
|
67
|
+
remains functional.
|
|
68
|
+
|
|
69
|
+
Workflow:
|
|
70
|
+
1. Cleanup stale user-level hooks (~/.claude/hooks/claude-mpm/)
|
|
71
|
+
2. Reinstall project-level hooks to .claude/settings.local.json
|
|
24
72
|
|
|
25
73
|
Args:
|
|
26
74
|
quiet: If True, suppress all output (used internally)
|
|
@@ -28,28 +76,45 @@ def sync_hooks_on_startup(quiet: bool = False) -> bool:
|
|
|
28
76
|
Returns:
|
|
29
77
|
bool: True if hooks were synced successfully, False otherwise
|
|
30
78
|
"""
|
|
79
|
+
is_tty = not quiet and sys.stdout.isatty()
|
|
80
|
+
|
|
81
|
+
# Step 1: Cleanup stale user-level hooks first
|
|
82
|
+
if is_tty:
|
|
83
|
+
print("Cleaning user-level hooks...", end=" ", flush=True)
|
|
84
|
+
|
|
85
|
+
cleaned = cleanup_user_level_hooks()
|
|
86
|
+
|
|
87
|
+
if is_tty:
|
|
88
|
+
if cleaned:
|
|
89
|
+
print("✓")
|
|
90
|
+
else:
|
|
91
|
+
print("(none found)")
|
|
92
|
+
|
|
93
|
+
# Step 2: Install project-level hooks
|
|
31
94
|
try:
|
|
32
95
|
from ..hooks.claude_hooks.installer import HookInstaller
|
|
33
96
|
|
|
34
97
|
installer = HookInstaller()
|
|
35
98
|
|
|
36
99
|
# Show brief status (hooks sync is fast)
|
|
37
|
-
if
|
|
38
|
-
print("
|
|
100
|
+
if is_tty:
|
|
101
|
+
print("Installing project hooks...", end=" ", flush=True)
|
|
39
102
|
|
|
40
103
|
# Reinstall hooks (force=True ensures update)
|
|
41
104
|
success = installer.install_hooks(force=True)
|
|
42
105
|
|
|
43
|
-
if
|
|
106
|
+
if is_tty:
|
|
44
107
|
if success:
|
|
45
|
-
|
|
108
|
+
# Count hooks from settings file
|
|
109
|
+
hook_count = _count_installed_hooks(installer.settings_file)
|
|
110
|
+
print(f"{hook_count} hooks configured ✓")
|
|
46
111
|
else:
|
|
47
112
|
print("(skipped)")
|
|
48
113
|
|
|
49
114
|
return success
|
|
50
115
|
|
|
51
116
|
except Exception as e:
|
|
52
|
-
if
|
|
117
|
+
if is_tty:
|
|
53
118
|
print("(error)")
|
|
54
119
|
# Log but don't fail startup
|
|
55
120
|
from ..core.logger import get_logger
|
|
@@ -59,6 +124,30 @@ def sync_hooks_on_startup(quiet: bool = False) -> bool:
|
|
|
59
124
|
return False
|
|
60
125
|
|
|
61
126
|
|
|
127
|
+
def _count_installed_hooks(settings_file: Path) -> int:
|
|
128
|
+
"""Count the number of hook event types configured in settings.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
settings_file: Path to the settings.local.json file
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
int: Number of hook event types configured (e.g., 7 for all events)
|
|
135
|
+
"""
|
|
136
|
+
import json
|
|
137
|
+
|
|
138
|
+
try:
|
|
139
|
+
if not settings_file.exists():
|
|
140
|
+
return 0
|
|
141
|
+
|
|
142
|
+
with settings_file.open() as f:
|
|
143
|
+
settings = json.load(f)
|
|
144
|
+
|
|
145
|
+
hooks = settings.get("hooks", {})
|
|
146
|
+
return len(hooks)
|
|
147
|
+
except Exception:
|
|
148
|
+
return 0
|
|
149
|
+
|
|
150
|
+
|
|
62
151
|
def cleanup_legacy_agent_cache() -> None:
|
|
63
152
|
"""Remove legacy hierarchical agent cache directories.
|
|
64
153
|
|
|
@@ -196,7 +285,7 @@ def should_skip_background_services(args, processed_argv):
|
|
|
196
285
|
"""
|
|
197
286
|
Determine if background services should be skipped for this command.
|
|
198
287
|
|
|
199
|
-
WHY: Some commands (help, version, configure, doctor) don't need
|
|
288
|
+
WHY: Some commands (help, version, configure, doctor, oauth) don't need
|
|
200
289
|
background services and should start faster.
|
|
201
290
|
|
|
202
291
|
Args:
|
|
@@ -219,6 +308,7 @@ def should_skip_background_services(args, processed_argv):
|
|
|
219
308
|
"hook-errors",
|
|
220
309
|
"autotodos",
|
|
221
310
|
"commander",
|
|
311
|
+
"oauth",
|
|
222
312
|
]
|
|
223
313
|
)
|
|
224
314
|
|
|
@@ -280,11 +370,13 @@ def deploy_bundled_skills():
|
|
|
280
370
|
if deployment_result.get("deployed"):
|
|
281
371
|
# Show simple feedback for deployed skills
|
|
282
372
|
deployed_count = len(deployment_result["deployed"])
|
|
283
|
-
|
|
373
|
+
if sys.stdout.isatty():
|
|
374
|
+
print(f"✓ Bundled skills ready ({deployed_count} deployed)", flush=True)
|
|
284
375
|
logger.info(f"Skills: Deployed {deployed_count} skill(s)")
|
|
285
376
|
elif not deployment_result.get("errors"):
|
|
286
377
|
# No deployment needed, skills already present
|
|
287
|
-
|
|
378
|
+
if sys.stdout.isatty():
|
|
379
|
+
print("✓ Bundled skills ready", flush=True)
|
|
288
380
|
|
|
289
381
|
if deployment_result.get("errors"):
|
|
290
382
|
logger.warning(
|
|
@@ -318,7 +410,8 @@ def discover_and_link_runtime_skills():
|
|
|
318
410
|
|
|
319
411
|
discover_skills()
|
|
320
412
|
# Show simple success feedback
|
|
321
|
-
|
|
413
|
+
if sys.stdout.isatty():
|
|
414
|
+
print("✓ Runtime skills linked", flush=True)
|
|
322
415
|
except Exception as e:
|
|
323
416
|
# Import logger here to avoid circular imports
|
|
324
417
|
from ..core.logger import get_logger
|
|
@@ -373,7 +466,8 @@ def deploy_output_style_on_startup():
|
|
|
373
466
|
|
|
374
467
|
if all_up_to_date:
|
|
375
468
|
# Show feedback that output styles are ready
|
|
376
|
-
|
|
469
|
+
if sys.stdout.isatty():
|
|
470
|
+
print("✓ Output styles ready", flush=True)
|
|
377
471
|
return
|
|
378
472
|
|
|
379
473
|
# Deploy all styles using the manager
|
|
@@ -383,7 +477,8 @@ def deploy_output_style_on_startup():
|
|
|
383
477
|
deployed_count = sum(1 for success in results.values() if success)
|
|
384
478
|
|
|
385
479
|
if deployed_count > 0:
|
|
386
|
-
|
|
480
|
+
if sys.stdout.isatty():
|
|
481
|
+
print(f"✓ Output styles deployed ({deployed_count} styles)", flush=True)
|
|
387
482
|
else:
|
|
388
483
|
# Deployment failed - log but don't fail startup
|
|
389
484
|
from ..core.logger import get_logger
|
|
@@ -1290,9 +1385,11 @@ def verify_and_show_pm_skills():
|
|
|
1290
1385
|
if result.verified:
|
|
1291
1386
|
# Show verified status with count
|
|
1292
1387
|
total_required = len(REQUIRED_PM_SKILLS)
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1388
|
+
if sys.stdout.isatty():
|
|
1389
|
+
print(
|
|
1390
|
+
f"✓ PM skills: {total_required}/{total_required} verified",
|
|
1391
|
+
flush=True,
|
|
1392
|
+
)
|
|
1296
1393
|
else:
|
|
1297
1394
|
# Show warning with details
|
|
1298
1395
|
missing_count = len(result.missing_skills)
|
|
@@ -1311,13 +1408,15 @@ def verify_and_show_pm_skills():
|
|
|
1311
1408
|
if "Auto-repaired" in result.message:
|
|
1312
1409
|
# Auto-repair succeeded
|
|
1313
1410
|
total_required = len(REQUIRED_PM_SKILLS)
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1411
|
+
if sys.stdout.isatty():
|
|
1412
|
+
print(
|
|
1413
|
+
f"✓ PM skills: {total_required}/{total_required} verified (auto-repaired)",
|
|
1414
|
+
flush=True,
|
|
1415
|
+
)
|
|
1318
1416
|
else:
|
|
1319
1417
|
# Auto-repair failed or not attempted
|
|
1320
|
-
|
|
1418
|
+
if sys.stdout.isatty():
|
|
1419
|
+
print(f"⚠ PM skills: {status}", flush=True)
|
|
1321
1420
|
|
|
1322
1421
|
# Log warnings for debugging
|
|
1323
1422
|
from ..core.logger import get_logger
|
|
@@ -1377,6 +1476,28 @@ def auto_install_chrome_devtools_on_startup():
|
|
|
1377
1476
|
# Continue execution - chrome-devtools installation failure shouldn't block startup
|
|
1378
1477
|
|
|
1379
1478
|
|
|
1479
|
+
def sync_deployment_on_startup(force_sync: bool = False) -> None:
|
|
1480
|
+
"""Consolidated deployment block: hooks + agents.
|
|
1481
|
+
|
|
1482
|
+
WHY: Groups all deployment tasks into a single logical block for clarity.
|
|
1483
|
+
This ensures hooks and agents are deployed together before other services.
|
|
1484
|
+
|
|
1485
|
+
Order:
|
|
1486
|
+
1. Hook cleanup (remove ~/.claude/hooks/claude-mpm/)
|
|
1487
|
+
2. Hook reinstall (update .claude/settings.local.json)
|
|
1488
|
+
3. Agent sync from remote Git sources
|
|
1489
|
+
|
|
1490
|
+
Args:
|
|
1491
|
+
force_sync: Force download even if cache is fresh (bypasses ETag).
|
|
1492
|
+
"""
|
|
1493
|
+
# Step 1-2: Hooks (cleanup + reinstall handled by sync_hooks_on_startup)
|
|
1494
|
+
sync_hooks_on_startup() # Shows "Syncing Claude Code hooks... ✓"
|
|
1495
|
+
|
|
1496
|
+
# Step 3: Agents from remote sources
|
|
1497
|
+
sync_remote_agents_on_startup(force_sync=force_sync)
|
|
1498
|
+
show_agent_summary() # Display agent counts after deployment
|
|
1499
|
+
|
|
1500
|
+
|
|
1380
1501
|
def run_background_services(force_sync: bool = False):
|
|
1381
1502
|
"""
|
|
1382
1503
|
Initialize all background services on startup.
|
|
@@ -1392,19 +1513,15 @@ def run_background_services(force_sync: bool = False):
|
|
|
1392
1513
|
Args:
|
|
1393
1514
|
force_sync: Force download even if cache is fresh (bypasses ETag).
|
|
1394
1515
|
"""
|
|
1395
|
-
#
|
|
1396
|
-
# RATIONALE: Hooks
|
|
1397
|
-
# This
|
|
1398
|
-
|
|
1516
|
+
# Consolidated deployment block: hooks + agents
|
|
1517
|
+
# RATIONALE: Hooks and agents are deployed together before other services
|
|
1518
|
+
# This ensures the deployment phase is complete before configuration checks
|
|
1519
|
+
sync_deployment_on_startup(force_sync=force_sync)
|
|
1399
1520
|
|
|
1400
1521
|
initialize_project_registry()
|
|
1401
1522
|
check_mcp_auto_configuration()
|
|
1402
1523
|
verify_mcp_gateway_startup()
|
|
1403
1524
|
check_for_updates_async()
|
|
1404
|
-
sync_remote_agents_on_startup(
|
|
1405
|
-
force_sync=force_sync
|
|
1406
|
-
) # Sync agents from remote sources
|
|
1407
|
-
show_agent_summary() # Display agent counts after deployment
|
|
1408
1525
|
|
|
1409
1526
|
# Skills deployment order (precedence: remote > bundled)
|
|
1410
1527
|
# 1. Deploy bundled skills first (base layer from package)
|
|
@@ -1516,7 +1633,9 @@ def check_mcp_auto_configuration():
|
|
|
1516
1633
|
from ..services.mcp_gateway.auto_configure import check_and_configure_mcp
|
|
1517
1634
|
|
|
1518
1635
|
# Show progress feedback - this operation can take 10+ seconds
|
|
1519
|
-
|
|
1636
|
+
# Only show progress message in TTY mode to avoid interfering with Claude Code's status display
|
|
1637
|
+
if sys.stdout.isatty():
|
|
1638
|
+
print("Checking MCP configuration...", end="", flush=True)
|
|
1520
1639
|
|
|
1521
1640
|
# This function handles all the logic:
|
|
1522
1641
|
# - Checks if already configured
|
|
@@ -1530,16 +1649,14 @@ def check_mcp_auto_configuration():
|
|
|
1530
1649
|
# Only use carriage return clearing if stdout is a real TTY
|
|
1531
1650
|
if sys.stdout.isatty():
|
|
1532
1651
|
print("\r" + " " * 30 + "\r", end="", flush=True)
|
|
1533
|
-
|
|
1534
|
-
print() # Simple newline for non-TTY (like Claude Code REPL)
|
|
1652
|
+
# In non-TTY mode, don't print anything - the "Checking..." message will just remain on its line
|
|
1535
1653
|
|
|
1536
1654
|
except Exception as e:
|
|
1537
1655
|
# Clear progress message on error
|
|
1538
1656
|
# Only use carriage return clearing if stdout is a real TTY
|
|
1539
1657
|
if sys.stdout.isatty():
|
|
1540
1658
|
print("\r" + " " * 30 + "\r", end="", flush=True)
|
|
1541
|
-
|
|
1542
|
-
print() # Simple newline for non-TTY (like Claude Code REPL)
|
|
1659
|
+
# In non-TTY mode, don't print anything - the "Checking..." message will just remain on its line
|
|
1543
1660
|
|
|
1544
1661
|
# Non-critical - log but don't fail
|
|
1545
1662
|
from ..core.logger import get_logger
|
|
@@ -531,7 +531,7 @@ def should_show_banner(args) -> bool:
|
|
|
531
531
|
"""
|
|
532
532
|
Determine if startup banner should be displayed.
|
|
533
533
|
|
|
534
|
-
Skip banner for: --help, --version, info, doctor, config, configure commands
|
|
534
|
+
Skip banner for: --help, --version, info, doctor, config, configure, oauth commands
|
|
535
535
|
"""
|
|
536
536
|
# Check for help/version flags
|
|
537
537
|
if hasattr(args, "help") and args.help:
|
|
@@ -541,7 +541,8 @@ def should_show_banner(args) -> bool:
|
|
|
541
541
|
|
|
542
542
|
# Check for commands that should skip banner
|
|
543
543
|
# Commander has its own banner, so skip the main MPM banner
|
|
544
|
-
|
|
544
|
+
# OAuth commands are lightweight utilities that should run immediately
|
|
545
|
+
skip_commands = {"info", "doctor", "config", "configure", "commander", "oauth"}
|
|
545
546
|
if hasattr(args, "command") and args.command in skip_commands:
|
|
546
547
|
return False
|
|
547
548
|
|
claude_mpm/commander/chat/cli.py
CHANGED
|
@@ -68,11 +68,14 @@ async def run_commander(
|
|
|
68
68
|
if config is None:
|
|
69
69
|
config = CommanderCLIConfig(port=port, state_dir=state_dir)
|
|
70
70
|
|
|
71
|
-
# Setup logging
|
|
71
|
+
# Setup logging - suppress noisy libraries
|
|
72
72
|
logging.basicConfig(
|
|
73
73
|
level=logging.INFO,
|
|
74
|
-
format="%(asctime)s
|
|
74
|
+
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
|
75
75
|
)
|
|
76
|
+
# Suppress httpx request logging (very verbose)
|
|
77
|
+
logging.getLogger("httpx").setLevel(logging.WARNING)
|
|
78
|
+
logging.getLogger("httpcore").setLevel(logging.WARNING)
|
|
76
79
|
|
|
77
80
|
# Initialize components
|
|
78
81
|
logger.info("Initializing Commander...")
|
|
@@ -11,12 +11,18 @@ class CommandType(Enum):
|
|
|
11
11
|
LIST = "list"
|
|
12
12
|
START = "start"
|
|
13
13
|
STOP = "stop"
|
|
14
|
+
CLOSE = "close"
|
|
15
|
+
REGISTER = "register"
|
|
14
16
|
CONNECT = "connect"
|
|
15
17
|
DISCONNECT = "disconnect"
|
|
18
|
+
SAVED = "saved"
|
|
19
|
+
FORGET = "forget"
|
|
16
20
|
STATUS = "status"
|
|
17
21
|
HELP = "help"
|
|
18
22
|
EXIT = "exit"
|
|
19
23
|
INSTANCES = "instances" # alias for list
|
|
24
|
+
MPM_OAUTH = "mpm-oauth"
|
|
25
|
+
CLEANUP = "cleanup"
|
|
20
26
|
|
|
21
27
|
|
|
22
28
|
@dataclass
|
|
@@ -31,27 +37,43 @@ class Command:
|
|
|
31
37
|
class CommandParser:
|
|
32
38
|
"""Parses user input into commands."""
|
|
33
39
|
|
|
34
|
-
|
|
40
|
+
# Map slash command names to CommandType
|
|
41
|
+
SLASH_COMMANDS = {
|
|
42
|
+
"register": CommandType.REGISTER,
|
|
43
|
+
"start": CommandType.START,
|
|
44
|
+
"stop": CommandType.STOP,
|
|
45
|
+
"close": CommandType.CLOSE,
|
|
46
|
+
"connect": CommandType.CONNECT,
|
|
47
|
+
"disconnect": CommandType.DISCONNECT,
|
|
48
|
+
"switch": CommandType.CONNECT, # alias for connect
|
|
49
|
+
"list": CommandType.LIST,
|
|
35
50
|
"ls": CommandType.LIST,
|
|
36
|
-
"
|
|
51
|
+
"saved": CommandType.SAVED,
|
|
52
|
+
"forget": CommandType.FORGET,
|
|
53
|
+
"status": CommandType.STATUS,
|
|
54
|
+
"help": CommandType.HELP,
|
|
55
|
+
"exit": CommandType.EXIT,
|
|
37
56
|
"quit": CommandType.EXIT,
|
|
38
57
|
"q": CommandType.EXIT,
|
|
58
|
+
"mpm-oauth": CommandType.MPM_OAUTH,
|
|
59
|
+
"cleanup": CommandType.CLEANUP,
|
|
39
60
|
}
|
|
40
61
|
|
|
41
62
|
def parse(self, input_text: str) -> Optional[Command]:
|
|
42
63
|
"""Parse input into a Command.
|
|
43
64
|
|
|
44
|
-
Returns None if input is not a
|
|
65
|
+
Returns None if input is not a slash command (natural language).
|
|
66
|
+
System commands must start with '/'.
|
|
45
67
|
|
|
46
68
|
Args:
|
|
47
69
|
input_text: Raw user input.
|
|
48
70
|
|
|
49
71
|
Returns:
|
|
50
|
-
Command if input is a
|
|
72
|
+
Command if input is a slash command, None otherwise.
|
|
51
73
|
|
|
52
74
|
Example:
|
|
53
75
|
>>> parser = CommandParser()
|
|
54
|
-
>>> cmd = parser.parse("list")
|
|
76
|
+
>>> cmd = parser.parse("/list")
|
|
55
77
|
>>> cmd.type
|
|
56
78
|
<CommandType.LIST: 'list'>
|
|
57
79
|
>>> parser.parse("tell me about the code")
|
|
@@ -60,22 +82,26 @@ class CommandParser:
|
|
|
60
82
|
if not input_text:
|
|
61
83
|
return None
|
|
62
84
|
|
|
63
|
-
|
|
85
|
+
# System commands must start with /
|
|
86
|
+
if not input_text.startswith("/"):
|
|
87
|
+
return None
|
|
88
|
+
|
|
89
|
+
# Remove the leading / and parse
|
|
90
|
+
cmd_line = input_text[1:]
|
|
91
|
+
parts = cmd_line.split()
|
|
92
|
+
if not parts:
|
|
93
|
+
return None
|
|
94
|
+
|
|
64
95
|
command_str = parts[0].lower()
|
|
65
96
|
args = parts[1:] if len(parts) > 1 else []
|
|
66
97
|
|
|
67
|
-
# Check if it's
|
|
68
|
-
if command_str in self.
|
|
69
|
-
cmd_type = self.
|
|
98
|
+
# Check if it's a valid slash command
|
|
99
|
+
if command_str in self.SLASH_COMMANDS:
|
|
100
|
+
cmd_type = self.SLASH_COMMANDS[command_str]
|
|
70
101
|
return Command(type=cmd_type, args=args, raw=input_text)
|
|
71
102
|
|
|
72
|
-
#
|
|
73
|
-
|
|
74
|
-
cmd_type = CommandType(command_str)
|
|
75
|
-
return Command(type=cmd_type, args=args, raw=input_text)
|
|
76
|
-
except ValueError:
|
|
77
|
-
# Not a built-in command
|
|
78
|
-
return None
|
|
103
|
+
# Unknown slash command
|
|
104
|
+
return None
|
|
79
105
|
|
|
80
106
|
def is_command(self, input_text: str) -> bool:
|
|
81
107
|
"""Check if input is a built-in command.
|