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.

Files changed (82) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/auth/__init__.py +35 -0
  3. claude_mpm/auth/callback_server.py +328 -0
  4. claude_mpm/auth/models.py +104 -0
  5. claude_mpm/auth/oauth_manager.py +266 -0
  6. claude_mpm/auth/providers/__init__.py +12 -0
  7. claude_mpm/auth/providers/base.py +165 -0
  8. claude_mpm/auth/providers/google.py +261 -0
  9. claude_mpm/auth/token_storage.py +252 -0
  10. claude_mpm/cli/commands/commander.py +6 -6
  11. claude_mpm/cli/commands/mcp.py +29 -17
  12. claude_mpm/cli/commands/mcp_command_router.py +39 -0
  13. claude_mpm/cli/commands/mcp_service_commands.py +304 -0
  14. claude_mpm/cli/commands/oauth.py +481 -0
  15. claude_mpm/cli/executor.py +9 -0
  16. claude_mpm/cli/helpers.py +1 -1
  17. claude_mpm/cli/parsers/base_parser.py +13 -0
  18. claude_mpm/cli/parsers/mcp_parser.py +79 -0
  19. claude_mpm/cli/parsers/oauth_parser.py +165 -0
  20. claude_mpm/cli/startup.py +150 -33
  21. claude_mpm/cli/startup_display.py +3 -2
  22. claude_mpm/commander/chat/cli.py +5 -2
  23. claude_mpm/commander/chat/commands.py +42 -16
  24. claude_mpm/commander/chat/repl.py +1581 -70
  25. claude_mpm/commander/events/manager.py +61 -1
  26. claude_mpm/commander/frameworks/base.py +87 -0
  27. claude_mpm/commander/frameworks/mpm.py +9 -14
  28. claude_mpm/commander/git/__init__.py +5 -0
  29. claude_mpm/commander/git/worktree_manager.py +212 -0
  30. claude_mpm/commander/instance_manager.py +428 -13
  31. claude_mpm/commander/models/events.py +6 -0
  32. claude_mpm/commander/persistence/state_store.py +95 -1
  33. claude_mpm/commander/tmux_orchestrator.py +3 -2
  34. claude_mpm/constants.py +5 -0
  35. claude_mpm/core/hook_manager.py +2 -1
  36. claude_mpm/core/logging_utils.py +4 -2
  37. claude_mpm/core/output_style_manager.py +5 -2
  38. claude_mpm/core/socketio_pool.py +34 -10
  39. claude_mpm/hooks/claude_hooks/auto_pause_handler.py +1 -1
  40. claude_mpm/hooks/claude_hooks/event_handlers.py +206 -94
  41. claude_mpm/hooks/claude_hooks/hook_handler.py +115 -32
  42. claude_mpm/hooks/claude_hooks/installer.py +175 -51
  43. claude_mpm/hooks/claude_hooks/memory_integration.py +1 -1
  44. claude_mpm/hooks/claude_hooks/response_tracking.py +1 -1
  45. claude_mpm/hooks/claude_hooks/services/__init__.py +21 -0
  46. claude_mpm/hooks/claude_hooks/services/connection_manager.py +2 -2
  47. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +2 -2
  48. claude_mpm/hooks/claude_hooks/services/container.py +326 -0
  49. claude_mpm/hooks/claude_hooks/services/protocols.py +328 -0
  50. claude_mpm/hooks/claude_hooks/services/state_manager.py +2 -2
  51. claude_mpm/hooks/claude_hooks/services/subagent_processor.py +2 -2
  52. claude_mpm/hooks/templates/pre_tool_use_simple.py +6 -6
  53. claude_mpm/hooks/templates/pre_tool_use_template.py +6 -6
  54. claude_mpm/init.py +21 -14
  55. claude_mpm/mcp/__init__.py +9 -0
  56. claude_mpm/mcp/google_workspace_server.py +610 -0
  57. claude_mpm/scripts/claude-hook-handler.sh +3 -3
  58. claude_mpm/services/command_deployment_service.py +44 -26
  59. claude_mpm/services/hook_installer_service.py +77 -8
  60. claude_mpm/services/mcp_config_manager.py +99 -19
  61. claude_mpm/services/mcp_service_registry.py +294 -0
  62. claude_mpm/services/monitor/server.py +6 -1
  63. {claude_mpm-5.6.23.dist-info → claude_mpm-5.6.73.dist-info}/METADATA +24 -1
  64. {claude_mpm-5.6.23.dist-info → claude_mpm-5.6.73.dist-info}/RECORD +69 -64
  65. {claude_mpm-5.6.23.dist-info → claude_mpm-5.6.73.dist-info}/WHEEL +1 -1
  66. {claude_mpm-5.6.23.dist-info → claude_mpm-5.6.73.dist-info}/entry_points.txt +2 -0
  67. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-311.pyc +0 -0
  68. claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-311.pyc +0 -0
  69. claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-311.pyc +0 -0
  70. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
  71. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
  72. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
  73. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
  74. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-311.pyc +0 -0
  75. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-311.pyc +0 -0
  76. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
  77. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-311.pyc +0 -0
  78. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
  79. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
  80. {claude_mpm-5.6.23.dist-info → claude_mpm-5.6.73.dist-info}/licenses/LICENSE +0 -0
  81. {claude_mpm-5.6.23.dist-info → claude_mpm-5.6.73.dist-info}/licenses/LICENSE-FAQ.md +0 -0
  82. {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 remains functional.
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 not quiet:
38
- print("Syncing Claude Code hooks...", end=" ", flush=True)
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 not quiet:
106
+ if is_tty:
44
107
  if success:
45
- print("✓")
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 not quiet:
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
- print(f"✓ Bundled skills ready ({deployed_count} deployed)", flush=True)
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
- print("✓ Bundled skills ready", flush=True)
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
- print("✓ Runtime skills linked", flush=True)
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
- print("✓ Output styles ready", flush=True)
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
- print(f"✓ Output styles deployed ({deployed_count} styles)", flush=True)
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
- print(
1294
- f"✓ PM skills: {total_required}/{total_required} verified", flush=True
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
- print(
1315
- f"✓ PM skills: {total_required}/{total_required} verified (auto-repaired)",
1316
- flush=True,
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
- print(f"⚠ PM skills: {status}", flush=True)
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
- # Sync hooks early to ensure up-to-date configuration
1396
- # RATIONALE: Hooks should be synced before other services to fix stale configs
1397
- # This is fast (<100ms) and non-blocking, so it doesn't delay startup
1398
- sync_hooks_on_startup() # Shows "Syncing Claude Code hooks... ✓"
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
- print("Checking MCP configuration...", end="", flush=True)
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
- else:
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
- else:
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
- skip_commands = {"info", "doctor", "config", "configure", "commander"}
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
 
@@ -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 - %(name)s - %(levelname)s - %(message)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
- ALIASES = {
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
- "instances": CommandType.LIST,
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 built-in command (natural language).
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 built-in command, None otherwise.
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
- parts = input_text.split()
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 an alias
68
- if command_str in self.ALIASES:
69
- cmd_type = self.ALIASES[command_str]
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
- # Check if it's a direct command
73
- try:
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.