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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/PM_INSTRUCTIONS.md +8 -3
- 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 +174 -4
- 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/commands/skill_source.py +51 -2
- claude_mpm/cli/commands/skills.py +5 -3
- 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/commander_parser.py +43 -10
- claude_mpm/cli/parsers/mcp_parser.py +79 -0
- claude_mpm/cli/parsers/oauth_parser.py +165 -0
- claude_mpm/cli/parsers/skill_source_parser.py +4 -0
- claude_mpm/cli/parsers/skills_parser.py +5 -0
- claude_mpm/cli/startup.py +300 -33
- claude_mpm/cli/startup_display.py +4 -2
- claude_mpm/cli/startup_migrations.py +236 -0
- claude_mpm/commander/__init__.py +6 -0
- claude_mpm/commander/adapters/__init__.py +32 -3
- claude_mpm/commander/adapters/auggie.py +260 -0
- claude_mpm/commander/adapters/base.py +98 -1
- claude_mpm/commander/adapters/claude_code.py +32 -1
- claude_mpm/commander/adapters/codex.py +237 -0
- claude_mpm/commander/adapters/example_usage.py +310 -0
- claude_mpm/commander/adapters/mpm.py +389 -0
- claude_mpm/commander/adapters/registry.py +204 -0
- claude_mpm/commander/api/app.py +32 -16
- claude_mpm/commander/api/errors.py +21 -0
- claude_mpm/commander/api/routes/messages.py +11 -11
- claude_mpm/commander/api/routes/projects.py +20 -20
- claude_mpm/commander/api/routes/sessions.py +37 -26
- claude_mpm/commander/api/routes/work.py +86 -50
- claude_mpm/commander/api/schemas.py +4 -0
- claude_mpm/commander/chat/cli.py +47 -5
- claude_mpm/commander/chat/commands.py +44 -16
- claude_mpm/commander/chat/repl.py +1729 -82
- claude_mpm/commander/config.py +5 -3
- claude_mpm/commander/core/__init__.py +10 -0
- claude_mpm/commander/core/block_manager.py +325 -0
- claude_mpm/commander/core/response_manager.py +323 -0
- claude_mpm/commander/daemon.py +215 -10
- claude_mpm/commander/env_loader.py +59 -0
- claude_mpm/commander/events/manager.py +61 -1
- claude_mpm/commander/frameworks/base.py +91 -1
- 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 +546 -15
- claude_mpm/commander/memory/__init__.py +45 -0
- claude_mpm/commander/memory/compression.py +347 -0
- claude_mpm/commander/memory/embeddings.py +230 -0
- claude_mpm/commander/memory/entities.py +310 -0
- claude_mpm/commander/memory/example_usage.py +290 -0
- claude_mpm/commander/memory/integration.py +325 -0
- claude_mpm/commander/memory/search.py +381 -0
- claude_mpm/commander/memory/store.py +657 -0
- claude_mpm/commander/models/events.py +6 -0
- claude_mpm/commander/persistence/state_store.py +95 -1
- claude_mpm/commander/registry.py +10 -4
- claude_mpm/commander/runtime/monitor.py +32 -2
- claude_mpm/commander/tmux_orchestrator.py +3 -2
- claude_mpm/commander/work/executor.py +38 -20
- claude_mpm/commander/workflow/event_handler.py +25 -3
- claude_mpm/config/skill_sources.py +16 -0
- claude_mpm/constants.py +5 -0
- claude_mpm/core/claude_runner.py +152 -0
- claude_mpm/core/config.py +30 -22
- claude_mpm/core/config_constants.py +74 -9
- claude_mpm/core/constants.py +56 -12
- claude_mpm/core/hook_manager.py +2 -1
- claude_mpm/core/interactive_session.py +5 -4
- claude_mpm/core/logger.py +16 -2
- claude_mpm/core/logging_utils.py +40 -16
- claude_mpm/core/network_config.py +148 -0
- claude_mpm/core/oneshot_session.py +7 -6
- claude_mpm/core/output_style_manager.py +37 -7
- claude_mpm/core/socketio_pool.py +47 -15
- claude_mpm/core/unified_paths.py +68 -80
- claude_mpm/hooks/claude_hooks/auto_pause_handler.py +30 -31
- claude_mpm/hooks/claude_hooks/event_handlers.py +285 -194
- claude_mpm/hooks/claude_hooks/hook_handler.py +115 -32
- claude_mpm/hooks/claude_hooks/installer.py +222 -54
- claude_mpm/hooks/claude_hooks/memory_integration.py +52 -32
- claude_mpm/hooks/claude_hooks/response_tracking.py +40 -59
- claude_mpm/hooks/claude_hooks/services/__init__.py +21 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +25 -30
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +24 -28
- 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 +25 -38
- claude_mpm/hooks/claude_hooks/services/subagent_processor.py +49 -75
- claude_mpm/hooks/session_resume_hook.py +22 -18
- claude_mpm/hooks/templates/pre_tool_use_simple.py +6 -6
- claude_mpm/hooks/templates/pre_tool_use_template.py +16 -8
- 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 +10 -9
- claude_mpm/services/agents/agent_selection_service.py +2 -2
- claude_mpm/services/agents/single_tier_deployment_service.py +4 -4
- 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/services/pm_skills_deployer.py +5 -3
- claude_mpm/services/skills/git_skill_source_manager.py +79 -8
- claude_mpm/services/skills/selective_skill_deployer.py +28 -0
- claude_mpm/services/skills/skill_discovery_service.py +17 -1
- claude_mpm/services/skills_deployer.py +31 -5
- claude_mpm/skills/__init__.py +2 -1
- claude_mpm/skills/bundled/pm/mpm-session-pause/SKILL.md +170 -0
- claude_mpm/skills/registry.py +295 -90
- {claude_mpm-5.6.1.dist-info → claude_mpm-5.6.76.dist-info}/METADATA +28 -3
- {claude_mpm-5.6.1.dist-info → claude_mpm-5.6.76.dist-info}/RECORD +131 -93
- {claude_mpm-5.6.1.dist-info → claude_mpm-5.6.76.dist-info}/WHEEL +1 -1
- {claude_mpm-5.6.1.dist-info → claude_mpm-5.6.76.dist-info}/entry_points.txt +2 -0
- {claude_mpm-5.6.1.dist-info → claude_mpm-5.6.76.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.6.1.dist-info → claude_mpm-5.6.76.dist-info}/licenses/LICENSE-FAQ.md +0 -0
- {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
|
|
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
|
|
|
@@ -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()
|
|
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 [
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1173
|
-
|
|
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
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
1275
|
-
#
|
|
1276
|
-
|
|
1277
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|