claude-mpm 5.6.1__py3-none-any.whl → 5.6.76__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.
Files changed (131) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/PM_INSTRUCTIONS.md +8 -3
  3. claude_mpm/auth/__init__.py +35 -0
  4. claude_mpm/auth/callback_server.py +328 -0
  5. claude_mpm/auth/models.py +104 -0
  6. claude_mpm/auth/oauth_manager.py +266 -0
  7. claude_mpm/auth/providers/__init__.py +12 -0
  8. claude_mpm/auth/providers/base.py +165 -0
  9. claude_mpm/auth/providers/google.py +261 -0
  10. claude_mpm/auth/token_storage.py +252 -0
  11. claude_mpm/cli/commands/commander.py +174 -4
  12. claude_mpm/cli/commands/mcp.py +29 -17
  13. claude_mpm/cli/commands/mcp_command_router.py +39 -0
  14. claude_mpm/cli/commands/mcp_service_commands.py +304 -0
  15. claude_mpm/cli/commands/oauth.py +481 -0
  16. claude_mpm/cli/commands/skill_source.py +51 -2
  17. claude_mpm/cli/commands/skills.py +5 -3
  18. claude_mpm/cli/executor.py +9 -0
  19. claude_mpm/cli/helpers.py +1 -1
  20. claude_mpm/cli/parsers/base_parser.py +13 -0
  21. claude_mpm/cli/parsers/commander_parser.py +43 -10
  22. claude_mpm/cli/parsers/mcp_parser.py +79 -0
  23. claude_mpm/cli/parsers/oauth_parser.py +165 -0
  24. claude_mpm/cli/parsers/skill_source_parser.py +4 -0
  25. claude_mpm/cli/parsers/skills_parser.py +5 -0
  26. claude_mpm/cli/startup.py +300 -33
  27. claude_mpm/cli/startup_display.py +4 -2
  28. claude_mpm/cli/startup_migrations.py +236 -0
  29. claude_mpm/commander/__init__.py +6 -0
  30. claude_mpm/commander/adapters/__init__.py +32 -3
  31. claude_mpm/commander/adapters/auggie.py +260 -0
  32. claude_mpm/commander/adapters/base.py +98 -1
  33. claude_mpm/commander/adapters/claude_code.py +32 -1
  34. claude_mpm/commander/adapters/codex.py +237 -0
  35. claude_mpm/commander/adapters/example_usage.py +310 -0
  36. claude_mpm/commander/adapters/mpm.py +389 -0
  37. claude_mpm/commander/adapters/registry.py +204 -0
  38. claude_mpm/commander/api/app.py +32 -16
  39. claude_mpm/commander/api/errors.py +21 -0
  40. claude_mpm/commander/api/routes/messages.py +11 -11
  41. claude_mpm/commander/api/routes/projects.py +20 -20
  42. claude_mpm/commander/api/routes/sessions.py +37 -26
  43. claude_mpm/commander/api/routes/work.py +86 -50
  44. claude_mpm/commander/api/schemas.py +4 -0
  45. claude_mpm/commander/chat/cli.py +47 -5
  46. claude_mpm/commander/chat/commands.py +44 -16
  47. claude_mpm/commander/chat/repl.py +1729 -82
  48. claude_mpm/commander/config.py +5 -3
  49. claude_mpm/commander/core/__init__.py +10 -0
  50. claude_mpm/commander/core/block_manager.py +325 -0
  51. claude_mpm/commander/core/response_manager.py +323 -0
  52. claude_mpm/commander/daemon.py +215 -10
  53. claude_mpm/commander/env_loader.py +59 -0
  54. claude_mpm/commander/events/manager.py +61 -1
  55. claude_mpm/commander/frameworks/base.py +91 -1
  56. claude_mpm/commander/frameworks/mpm.py +9 -14
  57. claude_mpm/commander/git/__init__.py +5 -0
  58. claude_mpm/commander/git/worktree_manager.py +212 -0
  59. claude_mpm/commander/instance_manager.py +546 -15
  60. claude_mpm/commander/memory/__init__.py +45 -0
  61. claude_mpm/commander/memory/compression.py +347 -0
  62. claude_mpm/commander/memory/embeddings.py +230 -0
  63. claude_mpm/commander/memory/entities.py +310 -0
  64. claude_mpm/commander/memory/example_usage.py +290 -0
  65. claude_mpm/commander/memory/integration.py +325 -0
  66. claude_mpm/commander/memory/search.py +381 -0
  67. claude_mpm/commander/memory/store.py +657 -0
  68. claude_mpm/commander/models/events.py +6 -0
  69. claude_mpm/commander/persistence/state_store.py +95 -1
  70. claude_mpm/commander/registry.py +10 -4
  71. claude_mpm/commander/runtime/monitor.py +32 -2
  72. claude_mpm/commander/tmux_orchestrator.py +3 -2
  73. claude_mpm/commander/work/executor.py +38 -20
  74. claude_mpm/commander/workflow/event_handler.py +25 -3
  75. claude_mpm/config/skill_sources.py +16 -0
  76. claude_mpm/constants.py +5 -0
  77. claude_mpm/core/claude_runner.py +152 -0
  78. claude_mpm/core/config.py +30 -22
  79. claude_mpm/core/config_constants.py +74 -9
  80. claude_mpm/core/constants.py +56 -12
  81. claude_mpm/core/hook_manager.py +2 -1
  82. claude_mpm/core/interactive_session.py +5 -4
  83. claude_mpm/core/logger.py +16 -2
  84. claude_mpm/core/logging_utils.py +40 -16
  85. claude_mpm/core/network_config.py +148 -0
  86. claude_mpm/core/oneshot_session.py +7 -6
  87. claude_mpm/core/output_style_manager.py +37 -7
  88. claude_mpm/core/socketio_pool.py +47 -15
  89. claude_mpm/core/unified_paths.py +68 -80
  90. claude_mpm/hooks/claude_hooks/auto_pause_handler.py +30 -31
  91. claude_mpm/hooks/claude_hooks/event_handlers.py +285 -194
  92. claude_mpm/hooks/claude_hooks/hook_handler.py +115 -32
  93. claude_mpm/hooks/claude_hooks/installer.py +222 -54
  94. claude_mpm/hooks/claude_hooks/memory_integration.py +52 -32
  95. claude_mpm/hooks/claude_hooks/response_tracking.py +40 -59
  96. claude_mpm/hooks/claude_hooks/services/__init__.py +21 -0
  97. claude_mpm/hooks/claude_hooks/services/connection_manager.py +25 -30
  98. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +24 -28
  99. claude_mpm/hooks/claude_hooks/services/container.py +326 -0
  100. claude_mpm/hooks/claude_hooks/services/protocols.py +328 -0
  101. claude_mpm/hooks/claude_hooks/services/state_manager.py +25 -38
  102. claude_mpm/hooks/claude_hooks/services/subagent_processor.py +49 -75
  103. claude_mpm/hooks/session_resume_hook.py +22 -18
  104. claude_mpm/hooks/templates/pre_tool_use_simple.py +6 -6
  105. claude_mpm/hooks/templates/pre_tool_use_template.py +16 -8
  106. claude_mpm/init.py +21 -14
  107. claude_mpm/mcp/__init__.py +9 -0
  108. claude_mpm/mcp/google_workspace_server.py +610 -0
  109. claude_mpm/scripts/claude-hook-handler.sh +10 -9
  110. claude_mpm/services/agents/agent_selection_service.py +2 -2
  111. claude_mpm/services/agents/single_tier_deployment_service.py +4 -4
  112. claude_mpm/services/command_deployment_service.py +44 -26
  113. claude_mpm/services/hook_installer_service.py +77 -8
  114. claude_mpm/services/mcp_config_manager.py +99 -19
  115. claude_mpm/services/mcp_service_registry.py +294 -0
  116. claude_mpm/services/monitor/server.py +6 -1
  117. claude_mpm/services/pm_skills_deployer.py +5 -3
  118. claude_mpm/services/skills/git_skill_source_manager.py +79 -8
  119. claude_mpm/services/skills/selective_skill_deployer.py +28 -0
  120. claude_mpm/services/skills/skill_discovery_service.py +17 -1
  121. claude_mpm/services/skills_deployer.py +31 -5
  122. claude_mpm/skills/__init__.py +2 -1
  123. claude_mpm/skills/bundled/pm/mpm-session-pause/SKILL.md +170 -0
  124. claude_mpm/skills/registry.py +295 -90
  125. {claude_mpm-5.6.1.dist-info → claude_mpm-5.6.76.dist-info}/METADATA +28 -3
  126. {claude_mpm-5.6.1.dist-info → claude_mpm-5.6.76.dist-info}/RECORD +131 -93
  127. {claude_mpm-5.6.1.dist-info → claude_mpm-5.6.76.dist-info}/WHEEL +1 -1
  128. {claude_mpm-5.6.1.dist-info → claude_mpm-5.6.76.dist-info}/entry_points.txt +2 -0
  129. {claude_mpm-5.6.1.dist-info → claude_mpm-5.6.76.dist-info}/licenses/LICENSE +0 -0
  130. {claude_mpm-5.6.1.dist-info → claude_mpm-5.6.76.dist-info}/licenses/LICENSE-FAQ.md +0 -0
  131. {claude_mpm-5.6.1.dist-info → claude_mpm-5.6.76.dist-info}/top_level.txt +0 -0
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
 
@@ -161,7 +250,25 @@ def setup_early_environment(argv):
161
250
  # CRITICAL: Suppress ALL logging by default
162
251
  # This catches all loggers (claude_mpm.*, service.*, framework_loader, etc.)
163
252
  # This will be overridden by setup_mcp_server_logging() based on user preference
164
- logging.getLogger().setLevel(logging.CRITICAL + 1) # Root logger catches everything
253
+ root_logger = logging.getLogger()
254
+ root_logger.setLevel(logging.CRITICAL + 1) # Root logger catches everything
255
+ root_logger.handlers = [] # Remove any handlers
256
+
257
+ # Also suppress common module loggers explicitly to prevent handler leakage
258
+ for logger_name in [
259
+ "claude_mpm",
260
+ "path_resolver",
261
+ "file_loader",
262
+ "framework_loader",
263
+ "service",
264
+ "instruction_loader",
265
+ "agent_loader",
266
+ "startup",
267
+ ]:
268
+ module_logger = logging.getLogger(logger_name)
269
+ module_logger.setLevel(logging.CRITICAL + 1)
270
+ module_logger.handlers = []
271
+ module_logger.propagate = False
165
272
 
166
273
  # Process argv
167
274
  if argv is None:
@@ -178,9 +285,15 @@ def should_skip_background_services(args, processed_argv):
178
285
  """
179
286
  Determine if background services should be skipped for this command.
180
287
 
181
- WHY: Some commands (help, version, configure, doctor) don't need
288
+ WHY: Some commands (help, version, configure, doctor, oauth) don't need
182
289
  background services and should start faster.
183
290
 
291
+ NOTE: Headless mode with --resume skips background services because:
292
+ - Each claude-mpm call is a NEW process (orchestrators like Vibe Kanban)
293
+ - First message (no --resume): Run full init (hooks, agents, skills)
294
+ - Follow-up messages (with --resume): Skip init to avoid latency
295
+ - Hooks/agents/skills are already deployed from the first message
296
+
184
297
  Args:
185
298
  args: Parsed arguments
186
299
  processed_argv: Processed command line arguments
@@ -188,11 +301,30 @@ def should_skip_background_services(args, processed_argv):
188
301
  Returns:
189
302
  bool: True if background services should be skipped
190
303
  """
304
+ # Headless mode with --resume: skip init for follow-up messages
305
+ # Each orchestrator call is a new process, so we need to skip init
306
+ # on follow-ups to avoid re-running hooks/agents/skills sync every time
307
+ is_headless = getattr(args, "headless", False)
308
+ has_resume = getattr(args, "resume", False) or "--resume" in (processed_argv or [])
309
+
310
+ if is_headless and has_resume:
311
+ return True
312
+
191
313
  skip_commands = ["--version", "-v", "--help", "-h"]
192
314
  return any(cmd in (processed_argv or sys.argv[1:]) for cmd in skip_commands) or (
193
315
  hasattr(args, "command")
194
316
  and args.command
195
- in ["info", "doctor", "config", "mcp", "configure", "hook-errors", "autotodos"]
317
+ in [
318
+ "info",
319
+ "doctor",
320
+ "config",
321
+ "mcp",
322
+ "configure",
323
+ "hook-errors",
324
+ "autotodos",
325
+ "commander",
326
+ "oauth",
327
+ ]
196
328
  )
197
329
 
198
330
 
@@ -253,11 +385,13 @@ def deploy_bundled_skills():
253
385
  if deployment_result.get("deployed"):
254
386
  # Show simple feedback for deployed skills
255
387
  deployed_count = len(deployment_result["deployed"])
256
- print(f"✓ Bundled skills ready ({deployed_count} deployed)", flush=True)
388
+ if sys.stdout.isatty():
389
+ print(f"✓ Bundled skills ready ({deployed_count} deployed)", flush=True)
257
390
  logger.info(f"Skills: Deployed {deployed_count} skill(s)")
258
391
  elif not deployment_result.get("errors"):
259
392
  # No deployment needed, skills already present
260
- print("✓ Bundled skills ready", flush=True)
393
+ if sys.stdout.isatty():
394
+ print("✓ Bundled skills ready", flush=True)
261
395
 
262
396
  if deployment_result.get("errors"):
263
397
  logger.warning(
@@ -291,7 +425,8 @@ def discover_and_link_runtime_skills():
291
425
 
292
426
  discover_skills()
293
427
  # Show simple success feedback
294
- print("✓ Runtime skills linked", flush=True)
428
+ if sys.stdout.isatty():
429
+ print("✓ Runtime skills linked", flush=True)
295
430
  except Exception as e:
296
431
  # Import logger here to avoid circular imports
297
432
  from ..core.logger import get_logger
@@ -346,7 +481,8 @@ def deploy_output_style_on_startup():
346
481
 
347
482
  if all_up_to_date:
348
483
  # Show feedback that output styles are ready
349
- print("✓ Output styles ready", flush=True)
484
+ if sys.stdout.isatty():
485
+ print("✓ Output styles ready", flush=True)
350
486
  return
351
487
 
352
488
  # Deploy all styles using the manager
@@ -356,7 +492,8 @@ def deploy_output_style_on_startup():
356
492
  deployed_count = sum(1 for success in results.values() if success)
357
493
 
358
494
  if deployed_count > 0:
359
- print(f"✓ Output styles deployed ({deployed_count} styles)", flush=True)
495
+ if sys.stdout.isatty():
496
+ print(f"✓ Output styles deployed ({deployed_count} styles)", flush=True)
360
497
  else:
361
498
  # Deployment failed - log but don't fail startup
362
499
  from ..core.logger import get_logger
@@ -451,6 +588,94 @@ def _cleanup_orphaned_agents(deploy_target: Path, deployed_agents: list[str]) ->
451
588
  return removed_count
452
589
 
453
590
 
591
+ def _save_deployment_state_after_reconciliation(
592
+ agent_result, project_path: Path
593
+ ) -> None:
594
+ """Save deployment state after reconciliation to prevent duplicate deployment.
595
+
596
+ WHY: After perform_startup_reconciliation() deploys agents to .claude/agents/,
597
+ we need to save a deployment state file so that ClaudeRunner.setup_agents()
598
+ can detect agents are already deployed and skip redundant deployment.
599
+
600
+ This prevents the "✓ Deployed 31 native agents" duplicate deployment that
601
+ occurs when setup_agents() doesn't know reconciliation already ran.
602
+
603
+ Args:
604
+ agent_result: DeploymentResult from perform_startup_reconciliation()
605
+ project_path: Project root directory
606
+
607
+ DESIGN DECISION: Use same state file format as ClaudeRunner._save_deployment_state()
608
+ Located at: .claude-mpm/cache/deployment_state.json
609
+
610
+ State file format:
611
+ {
612
+ "version": "5.6.13",
613
+ "agent_count": 15,
614
+ "deployment_hash": "sha256:...",
615
+ "deployed_at": 1234567890.123
616
+ }
617
+ """
618
+ import hashlib
619
+ import json
620
+ import time
621
+
622
+ from ..core.logger import get_logger
623
+
624
+ logger = get_logger("cli")
625
+
626
+ try:
627
+ # Get version from package
628
+ from claude_mpm import __version__
629
+
630
+ # Path to state file (matches ClaudeRunner._get_deployment_state_path())
631
+ state_file = project_path / ".claude-mpm" / "cache" / "deployment_state.json"
632
+ agents_dir = project_path / ".claude" / "agents"
633
+
634
+ # Count deployed agents
635
+ if agents_dir.exists():
636
+ agent_count = len(list(agents_dir.glob("*.md")))
637
+ else:
638
+ agent_count = 0
639
+
640
+ # Calculate deployment hash (matches ClaudeRunner._calculate_deployment_hash())
641
+ # CRITICAL: Must match exact hash algorithm used in ClaudeRunner
642
+ # Hashes filename + file content (not mtime) for consistency
643
+ deployment_hash = ""
644
+ if agents_dir.exists():
645
+ agent_files = sorted(agents_dir.glob("*.md"))
646
+ hash_obj = hashlib.sha256()
647
+ for agent_file in agent_files:
648
+ # Include filename and content in hash (matches ClaudeRunner)
649
+ hash_obj.update(agent_file.name.encode())
650
+ try:
651
+ hash_obj.update(agent_file.read_bytes())
652
+ except Exception as e:
653
+ logger.debug(f"Error reading {agent_file} for hash: {e}")
654
+
655
+ deployment_hash = hash_obj.hexdigest()
656
+
657
+ # Create state data
658
+ state_data = {
659
+ "version": __version__,
660
+ "agent_count": agent_count,
661
+ "deployment_hash": deployment_hash,
662
+ "deployed_at": time.time(),
663
+ }
664
+
665
+ # Ensure directory exists
666
+ state_file.parent.mkdir(parents=True, exist_ok=True)
667
+
668
+ # Write state file
669
+ state_file.write_text(json.dumps(state_data, indent=2))
670
+ logger.debug(
671
+ f"Saved deployment state after reconciliation: {agent_count} agents"
672
+ )
673
+
674
+ except Exception as e:
675
+ # Non-critical error - log but don't fail startup
676
+ logger.debug(f"Failed to save deployment state: {e}")
677
+
678
+
454
679
  def sync_remote_agents_on_startup(force_sync: bool = False):
455
680
  """
456
681
  Synchronize agent templates from remote sources on startup.
@@ -613,6 +838,12 @@ def sync_remote_agents_on_startup(force_sync: bool = False):
613
838
  )
614
839
  print(" Run with --verbose for detailed error information.\n")
615
840
 
841
+ # Save deployment state to prevent duplicate deployment in ClaudeRunner
842
+ # This ensures setup_agents() skips deployment since we already reconciled
843
+ _save_deployment_state_after_reconciliation(
844
+ agent_result=agent_result, project_path=project_path
845
+ )
846
+
616
847
  except Exception as e:
617
848
  # Deployment failure shouldn't block startup
618
849
  from ..core.logger import get_logger
@@ -1169,9 +1400,11 @@ def verify_and_show_pm_skills():
1169
1400
  if result.verified:
1170
1401
  # Show verified status with count
1171
1402
  total_required = len(REQUIRED_PM_SKILLS)
1172
- print(
1173
- f"✓ PM skills: {total_required}/{total_required} verified", flush=True
1174
- )
1403
+ if sys.stdout.isatty():
1404
+ print(
1405
+ f"✓ PM skills: {total_required}/{total_required} verified",
1406
+ flush=True,
1407
+ )
1175
1408
  else:
1176
1409
  # Show warning with details
1177
1410
  missing_count = len(result.missing_skills)
@@ -1190,13 +1423,15 @@ def verify_and_show_pm_skills():
1190
1423
  if "Auto-repaired" in result.message:
1191
1424
  # Auto-repair succeeded
1192
1425
  total_required = len(REQUIRED_PM_SKILLS)
1193
- print(
1194
- f"✓ PM skills: {total_required}/{total_required} verified (auto-repaired)",
1195
- flush=True,
1196
- )
1426
+ if sys.stdout.isatty():
1427
+ print(
1428
+ f"✓ PM skills: {total_required}/{total_required} verified (auto-repaired)",
1429
+ flush=True,
1430
+ )
1197
1431
  else:
1198
1432
  # Auto-repair failed or not attempted
1199
- print(f"⚠ PM skills: {status}", flush=True)
1433
+ if sys.stdout.isatty():
1434
+ print(f"⚠ PM skills: {status}", flush=True)
1200
1435
 
1201
1436
  # Log warnings for debugging
1202
1437
  from ..core.logger import get_logger
@@ -1256,6 +1491,28 @@ def auto_install_chrome_devtools_on_startup():
1256
1491
  # Continue execution - chrome-devtools installation failure shouldn't block startup
1257
1492
 
1258
1493
 
1494
+ def sync_deployment_on_startup(force_sync: bool = False) -> None:
1495
+ """Consolidated deployment block: hooks + agents.
1496
+
1497
+ WHY: Groups all deployment tasks into a single logical block for clarity.
1498
+ This ensures hooks and agents are deployed together before other services.
1499
+
1500
+ Order:
1501
+ 1. Hook cleanup (remove ~/.claude/hooks/claude-mpm/)
1502
+ 2. Hook reinstall (update .claude/settings.local.json)
1503
+ 3. Agent sync from remote Git sources
1504
+
1505
+ Args:
1506
+ force_sync: Force download even if cache is fresh (bypasses ETag).
1507
+ """
1508
+ # Step 1-2: Hooks (cleanup + reinstall handled by sync_hooks_on_startup)
1509
+ sync_hooks_on_startup() # Shows "Syncing Claude Code hooks... ✓"
1510
+
1511
+ # Step 3: Agents from remote sources
1512
+ sync_remote_agents_on_startup(force_sync=force_sync)
1513
+ show_agent_summary() # Display agent counts after deployment
1514
+
1515
+
1259
1516
  def run_background_services(force_sync: bool = False):
1260
1517
  """
1261
1518
  Initialize all background services on startup.
@@ -1271,19 +1528,21 @@ def run_background_services(force_sync: bool = False):
1271
1528
  Args:
1272
1529
  force_sync: Force download even if cache is fresh (bypasses ETag).
1273
1530
  """
1274
- # Sync hooks early to ensure up-to-date configuration
1275
- # RATIONALE: Hooks should be synced before other services to fix stale configs
1276
- # This is fast (<100ms) and non-blocking, so it doesn't delay startup
1277
- sync_hooks_on_startup() # Shows "Syncing Claude Code hooks... ✓"
1531
+ # Run startup migrations FIRST (before any sync operations)
1532
+ # These fix configuration issues from previous versions
1533
+ from .startup_migrations import run_migrations
1534
+
1535
+ run_migrations()
1536
+
1537
+ # Consolidated deployment block: hooks + agents
1538
+ # RATIONALE: Hooks and agents are deployed together before other services
1539
+ # This ensures the deployment phase is complete before configuration checks
1540
+ sync_deployment_on_startup(force_sync=force_sync)
1278
1541
 
1279
1542
  initialize_project_registry()
1280
1543
  check_mcp_auto_configuration()
1281
1544
  verify_mcp_gateway_startup()
1282
1545
  check_for_updates_async()
1283
- sync_remote_agents_on_startup(
1284
- force_sync=force_sync
1285
- ) # Sync agents from remote sources
1286
- show_agent_summary() # Display agent counts after deployment
1287
1546
 
1288
1547
  # Skills deployment order (precedence: remote > bundled)
1289
1548
  # 1. Deploy bundled skills first (base layer from package)
@@ -1395,7 +1654,9 @@ def check_mcp_auto_configuration():
1395
1654
  from ..services.mcp_gateway.auto_configure import check_and_configure_mcp
1396
1655
 
1397
1656
  # Show progress feedback - this operation can take 10+ seconds
1398
- print("Checking MCP configuration...", end="", flush=True)
1657
+ # Only show progress message in TTY mode to avoid interfering with Claude Code's status display
1658
+ if sys.stdout.isatty():
1659
+ print("Checking MCP configuration...", end="", flush=True)
1399
1660
 
1400
1661
  # This function handles all the logic:
1401
1662
  # - Checks if already configured
@@ -1406,11 +1667,17 @@ def check_mcp_auto_configuration():
1406
1667
  check_and_configure_mcp()
1407
1668
 
1408
1669
  # Clear the "Checking..." message by overwriting with spaces
1409
- print("\r" + " " * 30 + "\r", end="", flush=True)
1670
+ # Only use carriage return clearing if stdout is a real TTY
1671
+ if sys.stdout.isatty():
1672
+ print("\r" + " " * 30 + "\r", end="", flush=True)
1673
+ # In non-TTY mode, don't print anything - the "Checking..." message will just remain on its line
1410
1674
 
1411
1675
  except Exception as e:
1412
1676
  # Clear progress message on error
1413
- print("\r" + " " * 30 + "\r", end="", flush=True)
1677
+ # Only use carriage return clearing if stdout is a real TTY
1678
+ if sys.stdout.isatty():
1679
+ print("\r" + " " * 30 + "\r", end="", flush=True)
1680
+ # In non-TTY mode, don't print anything - the "Checking..." message will just remain on its line
1414
1681
 
1415
1682
  # Non-critical - log but don't fail
1416
1683
  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:
@@ -540,7 +540,9 @@ def should_show_banner(args) -> bool:
540
540
  return False
541
541
 
542
542
  # Check for commands that should skip banner
543
- skip_commands = {"info", "doctor", "config", "configure"}
543
+ # Commander has its own banner, so skip the main MPM banner
544
+ # OAuth commands are lightweight utilities that should run immediately
545
+ skip_commands = {"info", "doctor", "config", "configure", "commander", "oauth"}
544
546
  if hasattr(args, "command") and args.command in skip_commands:
545
547
  return False
546
548