claude-mpm 5.1.9__py3-none-any.whl → 5.4.22__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/__init__.py +4 -0
- claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +1 -1
- claude_mpm/agents/PM_INSTRUCTIONS.md +290 -34
- claude_mpm/agents/agent_loader.py +13 -44
- claude_mpm/agents/templates/circuit-breakers.md +138 -1
- claude_mpm/cli/__main__.py +4 -0
- claude_mpm/cli/chrome_devtools_installer.py +175 -0
- claude_mpm/cli/commands/agent_state_manager.py +8 -17
- claude_mpm/cli/commands/agents.py +0 -31
- claude_mpm/cli/commands/auto_configure.py +210 -25
- claude_mpm/cli/commands/config.py +88 -2
- claude_mpm/cli/commands/configure.py +1097 -158
- claude_mpm/cli/commands/configure_agent_display.py +15 -6
- claude_mpm/cli/commands/mpm_init/core.py +160 -46
- claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
- claude_mpm/cli/commands/mpm_init/prompts.py +280 -0
- claude_mpm/cli/commands/skills.py +214 -189
- claude_mpm/cli/commands/summarize.py +413 -0
- claude_mpm/cli/executor.py +11 -3
- claude_mpm/cli/parsers/agents_parser.py +0 -9
- claude_mpm/cli/parsers/auto_configure_parser.py +0 -138
- claude_mpm/cli/parsers/base_parser.py +5 -0
- claude_mpm/cli/parsers/config_parser.py +153 -83
- claude_mpm/cli/parsers/skills_parser.py +3 -2
- claude_mpm/cli/startup.py +550 -94
- claude_mpm/commands/mpm-config.md +265 -0
- claude_mpm/commands/mpm-help.md +14 -95
- claude_mpm/commands/mpm-organize.md +500 -0
- claude_mpm/config/agent_sources.py +27 -0
- claude_mpm/core/framework/formatters/content_formatter.py +3 -13
- claude_mpm/core/framework/loaders/agent_loader.py +8 -5
- claude_mpm/core/framework_loader.py +4 -2
- claude_mpm/core/logger.py +13 -0
- claude_mpm/core/socketio_pool.py +3 -3
- claude_mpm/core/unified_agent_registry.py +5 -15
- claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +211 -78
- claude_mpm/hooks/claude_hooks/hook_handler.py +6 -0
- claude_mpm/hooks/claude_hooks/installer.py +33 -10
- claude_mpm/hooks/claude_hooks/memory_integration.py +26 -9
- claude_mpm/hooks/claude_hooks/response_tracking.py +2 -3
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +4 -0
- claude_mpm/hooks/memory_integration_hook.py +46 -1
- claude_mpm/init.py +0 -19
- claude_mpm/scripts/claude-hook-handler.sh +58 -18
- claude_mpm/scripts/launch_monitor.py +93 -13
- claude_mpm/scripts/start_activity_logging.py +0 -0
- claude_mpm/services/agents/agent_recommendation_service.py +278 -0
- claude_mpm/services/agents/agent_review_service.py +280 -0
- claude_mpm/services/agents/deployment/agent_discovery_service.py +2 -3
- claude_mpm/services/agents/deployment/agent_template_builder.py +4 -2
- claude_mpm/services/agents/deployment/multi_source_deployment_service.py +78 -9
- claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +335 -53
- claude_mpm/services/agents/git_source_manager.py +34 -0
- claude_mpm/services/agents/loading/base_agent_manager.py +1 -13
- claude_mpm/services/agents/sources/git_source_sync_service.py +8 -1
- claude_mpm/services/agents/toolchain_detector.py +10 -6
- claude_mpm/services/analysis/__init__.py +11 -1
- claude_mpm/services/analysis/clone_detector.py +1030 -0
- claude_mpm/services/command_deployment_service.py +81 -10
- claude_mpm/services/event_bus/config.py +3 -1
- claude_mpm/services/git/git_operations_service.py +93 -8
- claude_mpm/services/monitor/daemon.py +9 -2
- claude_mpm/services/monitor/daemon_manager.py +39 -3
- claude_mpm/services/monitor/server.py +225 -19
- claude_mpm/services/self_upgrade_service.py +120 -12
- claude_mpm/services/skills/__init__.py +3 -0
- claude_mpm/services/skills/git_skill_source_manager.py +32 -2
- claude_mpm/services/skills/selective_skill_deployer.py +704 -0
- claude_mpm/services/skills/skill_to_agent_mapper.py +406 -0
- claude_mpm/services/skills_deployer.py +126 -9
- claude_mpm/services/socketio/event_normalizer.py +15 -1
- claude_mpm/services/socketio/server/core.py +160 -21
- claude_mpm/services/version_control/git_operations.py +103 -0
- claude_mpm/utils/agent_filters.py +17 -44
- {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.22.dist-info}/METADATA +47 -84
- {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.22.dist-info}/RECORD +82 -161
- claude_mpm-5.4.22.dist-info/entry_points.txt +5 -0
- claude_mpm-5.4.22.dist-info/licenses/LICENSE +94 -0
- claude_mpm-5.4.22.dist-info/licenses/LICENSE-FAQ.md +153 -0
- claude_mpm/agents/BASE_AGENT_TEMPLATE.md +0 -292
- claude_mpm/agents/BASE_DOCUMENTATION.md +0 -53
- claude_mpm/agents/BASE_ENGINEER.md +0 -658
- claude_mpm/agents/BASE_OPS.md +0 -219
- claude_mpm/agents/BASE_PM.md +0 -480
- claude_mpm/agents/BASE_PROMPT_ENGINEER.md +0 -787
- claude_mpm/agents/BASE_QA.md +0 -167
- claude_mpm/agents/BASE_RESEARCH.md +0 -53
- claude_mpm/agents/base_agent.json +0 -31
- claude_mpm/agents/base_agent_loader.py +0 -601
- claude_mpm/cli/commands/agents_detect.py +0 -380
- claude_mpm/cli/commands/agents_recommend.py +0 -309
- claude_mpm/cli/ticket_cli.py +0 -35
- claude_mpm/commands/mpm-agents-auto-configure.md +0 -278
- claude_mpm/commands/mpm-agents-detect.md +0 -177
- claude_mpm/commands/mpm-agents-list.md +0 -131
- claude_mpm/commands/mpm-agents-recommend.md +0 -223
- claude_mpm/commands/mpm-config-view.md +0 -150
- claude_mpm/commands/mpm-ticket-organize.md +0 -304
- claude_mpm/dashboard/analysis_runner.py +0 -455
- claude_mpm/dashboard/index.html +0 -13
- claude_mpm/dashboard/open_dashboard.py +0 -66
- claude_mpm/dashboard/static/css/activity.css +0 -1958
- claude_mpm/dashboard/static/css/connection-status.css +0 -370
- claude_mpm/dashboard/static/css/dashboard.css +0 -4701
- claude_mpm/dashboard/static/js/components/activity-tree.js +0 -1871
- claude_mpm/dashboard/static/js/components/agent-hierarchy.js +0 -777
- claude_mpm/dashboard/static/js/components/agent-inference.js +0 -956
- claude_mpm/dashboard/static/js/components/build-tracker.js +0 -333
- claude_mpm/dashboard/static/js/components/code-simple.js +0 -857
- claude_mpm/dashboard/static/js/components/connection-debug.js +0 -654
- claude_mpm/dashboard/static/js/components/diff-viewer.js +0 -891
- claude_mpm/dashboard/static/js/components/event-processor.js +0 -542
- claude_mpm/dashboard/static/js/components/event-viewer.js +0 -1155
- claude_mpm/dashboard/static/js/components/export-manager.js +0 -368
- claude_mpm/dashboard/static/js/components/file-change-tracker.js +0 -443
- claude_mpm/dashboard/static/js/components/file-change-viewer.js +0 -690
- claude_mpm/dashboard/static/js/components/file-tool-tracker.js +0 -724
- claude_mpm/dashboard/static/js/components/file-viewer.js +0 -580
- claude_mpm/dashboard/static/js/components/hud-library-loader.js +0 -211
- claude_mpm/dashboard/static/js/components/hud-manager.js +0 -671
- claude_mpm/dashboard/static/js/components/hud-visualizer.js +0 -1718
- claude_mpm/dashboard/static/js/components/module-viewer.js +0 -2764
- claude_mpm/dashboard/static/js/components/session-manager.js +0 -579
- claude_mpm/dashboard/static/js/components/socket-manager.js +0 -368
- claude_mpm/dashboard/static/js/components/ui-state-manager.js +0 -749
- claude_mpm/dashboard/static/js/components/unified-data-viewer.js +0 -1824
- claude_mpm/dashboard/static/js/components/working-directory.js +0 -920
- claude_mpm/dashboard/static/js/connection-manager.js +0 -536
- claude_mpm/dashboard/static/js/dashboard.js +0 -1914
- claude_mpm/dashboard/static/js/extension-error-handler.js +0 -164
- claude_mpm/dashboard/static/js/socket-client.js +0 -1474
- claude_mpm/dashboard/static/js/tab-isolation-fix.js +0 -185
- claude_mpm/dashboard/static/socket.io.min.js +0 -7
- claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +0 -7
- claude_mpm/dashboard/templates/code_simple.html +0 -153
- claude_mpm/dashboard/templates/index.html +0 -606
- claude_mpm/dashboard/test_dashboard.html +0 -372
- claude_mpm/scripts/mcp_server.py +0 -75
- claude_mpm/scripts/mcp_wrapper.py +0 -39
- claude_mpm/services/mcp_gateway/__init__.py +0 -159
- claude_mpm/services/mcp_gateway/auto_configure.py +0 -369
- claude_mpm/services/mcp_gateway/config/__init__.py +0 -17
- claude_mpm/services/mcp_gateway/config/config_loader.py +0 -296
- claude_mpm/services/mcp_gateway/config/config_schema.py +0 -243
- claude_mpm/services/mcp_gateway/config/configuration.py +0 -429
- claude_mpm/services/mcp_gateway/core/__init__.py +0 -43
- claude_mpm/services/mcp_gateway/core/base.py +0 -312
- claude_mpm/services/mcp_gateway/core/exceptions.py +0 -253
- claude_mpm/services/mcp_gateway/core/interfaces.py +0 -443
- claude_mpm/services/mcp_gateway/core/process_pool.py +0 -977
- claude_mpm/services/mcp_gateway/core/singleton_manager.py +0 -315
- claude_mpm/services/mcp_gateway/core/startup_verification.py +0 -316
- claude_mpm/services/mcp_gateway/main.py +0 -589
- claude_mpm/services/mcp_gateway/registry/__init__.py +0 -12
- claude_mpm/services/mcp_gateway/registry/service_registry.py +0 -412
- claude_mpm/services/mcp_gateway/registry/tool_registry.py +0 -489
- claude_mpm/services/mcp_gateway/server/__init__.py +0 -15
- claude_mpm/services/mcp_gateway/server/mcp_gateway.py +0 -414
- claude_mpm/services/mcp_gateway/server/stdio_handler.py +0 -372
- claude_mpm/services/mcp_gateway/server/stdio_server.py +0 -712
- claude_mpm/services/mcp_gateway/tools/__init__.py +0 -36
- claude_mpm/services/mcp_gateway/tools/base_adapter.py +0 -485
- claude_mpm/services/mcp_gateway/tools/document_summarizer.py +0 -789
- claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +0 -654
- claude_mpm/services/mcp_gateway/tools/health_check_tool.py +0 -456
- claude_mpm/services/mcp_gateway/tools/hello_world.py +0 -551
- claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +0 -555
- claude_mpm/services/mcp_gateway/utils/__init__.py +0 -14
- claude_mpm/services/mcp_gateway/utils/package_version_checker.py +0 -160
- claude_mpm/services/mcp_gateway/utils/update_preferences.py +0 -170
- claude_mpm-5.1.9.dist-info/entry_points.txt +0 -10
- claude_mpm-5.1.9.dist-info/licenses/LICENSE +0 -21
- {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.22.dist-info}/WHEEL +0 -0
- {claude_mpm-5.1.9.dist-info → claude_mpm-5.4.22.dist-info}/top_level.txt +0 -0
claude_mpm/cli/startup.py
CHANGED
|
@@ -14,6 +14,52 @@ import warnings
|
|
|
14
14
|
from pathlib import Path
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
def sync_hooks_on_startup(quiet: bool = False) -> bool:
|
|
18
|
+
"""Ensure hooks are up-to-date on startup.
|
|
19
|
+
|
|
20
|
+
WHY: Users can have stale hook configurations in settings.json that cause errors.
|
|
21
|
+
Reinstalling hooks ensures the hook format matches the current code.
|
|
22
|
+
|
|
23
|
+
DESIGN DECISION: Shows brief status message on success for user awareness.
|
|
24
|
+
Failures are logged but don't prevent startup to ensure claude-mpm remains functional.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
quiet: If True, suppress all output (used internally)
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
bool: True if hooks were synced successfully, False otherwise
|
|
31
|
+
"""
|
|
32
|
+
try:
|
|
33
|
+
from ..hooks.claude_hooks.installer import HookInstaller
|
|
34
|
+
|
|
35
|
+
installer = HookInstaller()
|
|
36
|
+
|
|
37
|
+
# Show brief status (hooks sync is fast)
|
|
38
|
+
if not quiet:
|
|
39
|
+
print("Syncing Claude Code hooks...", end=" ", flush=True)
|
|
40
|
+
|
|
41
|
+
# Reinstall hooks (force=True ensures update)
|
|
42
|
+
success = installer.install_hooks(force=True)
|
|
43
|
+
|
|
44
|
+
if not quiet:
|
|
45
|
+
if success:
|
|
46
|
+
print("✓")
|
|
47
|
+
else:
|
|
48
|
+
print("(skipped)")
|
|
49
|
+
|
|
50
|
+
return success
|
|
51
|
+
|
|
52
|
+
except Exception as e:
|
|
53
|
+
if not quiet:
|
|
54
|
+
print("(error)")
|
|
55
|
+
# Log but don't fail startup
|
|
56
|
+
from ..core.logger import get_logger
|
|
57
|
+
|
|
58
|
+
logger = get_logger("startup")
|
|
59
|
+
logger.warning(f"Hook sync failed (non-fatal): {e}")
|
|
60
|
+
return False
|
|
61
|
+
|
|
62
|
+
|
|
17
63
|
def check_legacy_cache() -> None:
|
|
18
64
|
"""Check for legacy cache/agents/ directory and warn user.
|
|
19
65
|
|
|
@@ -221,78 +267,157 @@ def discover_and_link_runtime_skills():
|
|
|
221
267
|
|
|
222
268
|
def deploy_output_style_on_startup():
|
|
223
269
|
"""
|
|
224
|
-
Deploy claude-mpm output
|
|
270
|
+
Deploy claude-mpm output styles to PROJECT-LEVEL directory on CLI startup.
|
|
271
|
+
|
|
272
|
+
WHY: Automatically deploy output styles to ensure consistent, professional
|
|
273
|
+
communication without emojis and exclamation points. Styles are project-specific
|
|
274
|
+
to allow different projects to have different communication styles.
|
|
225
275
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
276
|
+
DESIGN DECISION: This is non-blocking and idempotent. Deploys to project-level
|
|
277
|
+
directory (.claude/settings/output-styles/) instead of user-level to maintain
|
|
278
|
+
project isolation.
|
|
229
279
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
280
|
+
Deploys two styles:
|
|
281
|
+
- claude-mpm-style.md (professional mode)
|
|
282
|
+
- claude-mpm-teacher.md (teaching mode)
|
|
233
283
|
"""
|
|
234
284
|
try:
|
|
285
|
+
import shutil
|
|
235
286
|
from pathlib import Path
|
|
236
287
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
#
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
# File is empty, need to redeploy with content
|
|
259
|
-
pass # Fall through to deployment below
|
|
260
|
-
else:
|
|
261
|
-
# File has content, check if already active
|
|
262
|
-
settings = json.loads(settings_file.read_text())
|
|
263
|
-
if settings.get("activeOutputStyle") == "claude-mpm":
|
|
264
|
-
# Already deployed and active with content
|
|
265
|
-
already_configured = True
|
|
266
|
-
except Exception:
|
|
267
|
-
pass # Continue with deployment if we can't read settings
|
|
288
|
+
# Source files (in framework package)
|
|
289
|
+
package_dir = Path(__file__).parent.parent / "agents"
|
|
290
|
+
professional_source = package_dir / "CLAUDE_MPM_OUTPUT_STYLE.md"
|
|
291
|
+
teacher_source = package_dir / "CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md"
|
|
292
|
+
|
|
293
|
+
# Target directory (PROJECT-LEVEL, not user-level)
|
|
294
|
+
project_dir = Path.cwd()
|
|
295
|
+
output_styles_dir = project_dir / ".claude" / "settings" / "output-styles"
|
|
296
|
+
professional_target = output_styles_dir / "claude-mpm-style.md"
|
|
297
|
+
teacher_target = output_styles_dir / "claude-mpm-teacher.md"
|
|
298
|
+
|
|
299
|
+
# Create directory if it doesn't exist
|
|
300
|
+
output_styles_dir.mkdir(parents=True, exist_ok=True)
|
|
301
|
+
|
|
302
|
+
# Check if already deployed (both files exist and have content)
|
|
303
|
+
already_deployed = (
|
|
304
|
+
professional_target.exists()
|
|
305
|
+
and teacher_target.exists()
|
|
306
|
+
and professional_target.stat().st_size > 0
|
|
307
|
+
and teacher_target.stat().st_size > 0
|
|
308
|
+
)
|
|
268
309
|
|
|
269
|
-
if
|
|
270
|
-
# Show feedback that output
|
|
271
|
-
print("✓ Output
|
|
310
|
+
if already_deployed:
|
|
311
|
+
# Show feedback that output styles are ready
|
|
312
|
+
print("✓ Output styles ready", flush=True)
|
|
272
313
|
return
|
|
273
314
|
|
|
274
|
-
#
|
|
275
|
-
|
|
315
|
+
# Deploy both styles
|
|
316
|
+
deployed_count = 0
|
|
317
|
+
if professional_source.exists():
|
|
318
|
+
shutil.copy2(professional_source, professional_target)
|
|
319
|
+
deployed_count += 1
|
|
276
320
|
|
|
277
|
-
if
|
|
278
|
-
|
|
279
|
-
|
|
321
|
+
if teacher_source.exists():
|
|
322
|
+
shutil.copy2(teacher_source, teacher_target)
|
|
323
|
+
deployed_count += 1
|
|
280
324
|
|
|
281
|
-
|
|
325
|
+
if deployed_count > 0:
|
|
326
|
+
print(f"✓ Output styles deployed ({deployed_count} styles)", flush=True)
|
|
327
|
+
else:
|
|
328
|
+
# Source files missing - log but don't fail
|
|
329
|
+
from ..core.logger import get_logger
|
|
282
330
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
print("✓ Output style configured", flush=True)
|
|
331
|
+
logger = get_logger("cli")
|
|
332
|
+
logger.debug("Output style source files not found")
|
|
286
333
|
|
|
287
334
|
except Exception as e:
|
|
288
335
|
# Non-critical - log but don't fail startup
|
|
289
336
|
from ..core.logger import get_logger
|
|
290
337
|
|
|
291
338
|
logger = get_logger("cli")
|
|
292
|
-
logger.debug(f"Failed to deploy output
|
|
339
|
+
logger.debug(f"Failed to deploy output styles: {e}")
|
|
293
340
|
# Continue execution - output style deployment shouldn't block startup
|
|
294
341
|
|
|
295
342
|
|
|
343
|
+
def _cleanup_orphaned_agents(deploy_target: Path, deployed_agents: list[str]) -> int:
|
|
344
|
+
"""Remove agents that are managed by claude-mpm but no longer deployed.
|
|
345
|
+
|
|
346
|
+
WHY: When agent configurations change, old agents should be removed to avoid
|
|
347
|
+
confusion and stale agent references. Only removes claude-mpm managed agents,
|
|
348
|
+
leaving user-created agents untouched.
|
|
349
|
+
|
|
350
|
+
SAFETY: Only removes files with claude-mpm ownership markers in frontmatter.
|
|
351
|
+
Files without frontmatter or without ownership indicators are preserved.
|
|
352
|
+
|
|
353
|
+
Args:
|
|
354
|
+
deploy_target: Path to .claude/agents/ directory
|
|
355
|
+
deployed_agents: List of agent filenames that should remain
|
|
356
|
+
|
|
357
|
+
Returns:
|
|
358
|
+
Number of agents removed
|
|
359
|
+
"""
|
|
360
|
+
import re
|
|
361
|
+
|
|
362
|
+
import yaml
|
|
363
|
+
|
|
364
|
+
from ..core.logger import get_logger
|
|
365
|
+
|
|
366
|
+
logger = get_logger("cli")
|
|
367
|
+
removed_count = 0
|
|
368
|
+
deployed_set = set(deployed_agents)
|
|
369
|
+
|
|
370
|
+
if not deploy_target.exists():
|
|
371
|
+
return 0
|
|
372
|
+
|
|
373
|
+
# Scan all .md files in agents directory
|
|
374
|
+
for agent_file in deploy_target.glob("*.md"):
|
|
375
|
+
# Skip hidden files
|
|
376
|
+
if agent_file.name.startswith("."):
|
|
377
|
+
continue
|
|
378
|
+
|
|
379
|
+
# Skip if this agent should remain deployed
|
|
380
|
+
if agent_file.name in deployed_set:
|
|
381
|
+
continue
|
|
382
|
+
|
|
383
|
+
# Check if this is a claude-mpm managed agent
|
|
384
|
+
try:
|
|
385
|
+
content = agent_file.read_text(encoding="utf-8")
|
|
386
|
+
|
|
387
|
+
# Parse YAML frontmatter
|
|
388
|
+
if content.startswith("---"):
|
|
389
|
+
match = re.match(r"^---\n(.*?)\n---", content, re.DOTALL)
|
|
390
|
+
if match:
|
|
391
|
+
frontmatter = yaml.safe_load(match.group(1))
|
|
392
|
+
|
|
393
|
+
# Check ownership indicators
|
|
394
|
+
is_ours = False
|
|
395
|
+
if frontmatter:
|
|
396
|
+
author = frontmatter.get("author", "")
|
|
397
|
+
source = frontmatter.get("source", "")
|
|
398
|
+
agent_id = frontmatter.get("agent_id", "")
|
|
399
|
+
|
|
400
|
+
# It's ours if it has any of these markers
|
|
401
|
+
if (
|
|
402
|
+
"Claude MPM" in str(author)
|
|
403
|
+
or source == "remote"
|
|
404
|
+
or agent_id
|
|
405
|
+
):
|
|
406
|
+
is_ours = True
|
|
407
|
+
|
|
408
|
+
if is_ours:
|
|
409
|
+
# Safe to remove - it's our agent but not deployed
|
|
410
|
+
agent_file.unlink()
|
|
411
|
+
removed_count += 1
|
|
412
|
+
logger.info(f"Removed orphaned agent: {agent_file.name}")
|
|
413
|
+
|
|
414
|
+
except Exception as e:
|
|
415
|
+
logger.debug(f"Could not check agent {agent_file.name}: {e}")
|
|
416
|
+
# Don't remove if we can't verify ownership
|
|
417
|
+
|
|
418
|
+
return removed_count
|
|
419
|
+
|
|
420
|
+
|
|
296
421
|
def sync_remote_agents_on_startup():
|
|
297
422
|
"""
|
|
298
423
|
Synchronize agent templates from remote sources on startup.
|
|
@@ -308,7 +433,8 @@ def sync_remote_agents_on_startup():
|
|
|
308
433
|
Workflow:
|
|
309
434
|
1. Sync all enabled Git sources (download/cache files) - Phase 1 progress bar
|
|
310
435
|
2. Deploy agents to ~/.claude/agents/ - Phase 2 progress bar
|
|
311
|
-
3.
|
|
436
|
+
3. Cleanup orphaned agents (ours but no longer deployed) - Phase 3
|
|
437
|
+
4. Log deployment results
|
|
312
438
|
"""
|
|
313
439
|
# Check for legacy cache and warn user if found
|
|
314
440
|
check_legacy_cache()
|
|
@@ -386,6 +512,7 @@ def sync_remote_agents_on_startup():
|
|
|
386
512
|
# 1. Must have "/agents/" in path (from git repos)
|
|
387
513
|
# 2. Must not be in PM templates or doc files
|
|
388
514
|
# 3. Exclude BASE-AGENT.md which is not a deployable agent
|
|
515
|
+
# 4. Exclude build artifacts (dist/, build/, .cache/) to prevent double-counting
|
|
389
516
|
agent_files = [
|
|
390
517
|
f
|
|
391
518
|
for f in all_md_files
|
|
@@ -396,44 +523,98 @@ def sync_remote_agents_on_startup():
|
|
|
396
523
|
and f.name.lower() not in pm_templates
|
|
397
524
|
and f.name.lower() not in doc_files
|
|
398
525
|
and f.name.lower() != "base-agent.md"
|
|
526
|
+
# Exclude build artifacts (prevents double-counting source + built files)
|
|
527
|
+
and not any(
|
|
528
|
+
part in str(f).split("/")
|
|
529
|
+
for part in ["dist", "build", ".cache"]
|
|
530
|
+
)
|
|
399
531
|
)
|
|
400
532
|
]
|
|
401
533
|
agent_count = len(agent_files)
|
|
402
534
|
|
|
403
535
|
if agent_count > 0:
|
|
404
|
-
#
|
|
405
|
-
|
|
406
|
-
total=agent_count,
|
|
407
|
-
prefix="Deploying agents",
|
|
408
|
-
show_percentage=True,
|
|
409
|
-
show_counter=True,
|
|
410
|
-
)
|
|
411
|
-
|
|
412
|
-
# Deploy agents with progress callback
|
|
413
|
-
deploy_target = Path.home() / ".claude" / "agents"
|
|
536
|
+
# Deploy agents to project-level directory where Claude Code expects them
|
|
537
|
+
deploy_target = Path.cwd() / ".claude" / "agents"
|
|
414
538
|
deployment_result = deployment_service.deploy_agents(
|
|
415
539
|
target_dir=deploy_target,
|
|
416
540
|
force_rebuild=False, # Only deploy if versions differ
|
|
417
541
|
deployment_mode="update", # Version-aware updates
|
|
418
542
|
)
|
|
419
543
|
|
|
420
|
-
#
|
|
421
|
-
deploy_progress.update(agent_count)
|
|
422
|
-
|
|
423
|
-
# Finish deployment progress bar
|
|
544
|
+
# Get actual counts from deployment result (reflects configured agents)
|
|
424
545
|
deployed = len(deployment_result.get("deployed", []))
|
|
425
546
|
updated = len(deployment_result.get("updated", []))
|
|
426
547
|
skipped = len(deployment_result.get("skipped", []))
|
|
427
|
-
|
|
548
|
+
total_configured = deployed + updated + skipped
|
|
549
|
+
|
|
550
|
+
# FALLBACK: If deployment result doesn't track skipped agents (async path),
|
|
551
|
+
# count existing agents in target directory as "already deployed"
|
|
552
|
+
# This ensures accurate reporting when agents are already up-to-date
|
|
553
|
+
if total_configured == 0 and deploy_target.exists():
|
|
554
|
+
existing_agents = list(deploy_target.glob("*.md"))
|
|
555
|
+
# Filter out non-agent files (e.g., README.md, INSTRUCTIONS.md)
|
|
556
|
+
agent_count_in_target = len(
|
|
557
|
+
[
|
|
558
|
+
f
|
|
559
|
+
for f in existing_agents
|
|
560
|
+
if not f.name.startswith(("README", "INSTRUCTIONS"))
|
|
561
|
+
]
|
|
562
|
+
)
|
|
563
|
+
if agent_count_in_target > 0:
|
|
564
|
+
# All agents already deployed - count them as skipped
|
|
565
|
+
skipped = agent_count_in_target
|
|
566
|
+
total_configured = agent_count_in_target
|
|
567
|
+
|
|
568
|
+
# Create progress bar with actual configured agent count (not raw file count)
|
|
569
|
+
deploy_progress = ProgressBar(
|
|
570
|
+
total=total_configured if total_configured > 0 else 1,
|
|
571
|
+
prefix="Deploying agents",
|
|
572
|
+
show_percentage=True,
|
|
573
|
+
show_counter=True,
|
|
574
|
+
)
|
|
428
575
|
|
|
429
|
-
#
|
|
576
|
+
# Update progress bar to completion
|
|
577
|
+
deploy_progress.update(
|
|
578
|
+
total_configured if total_configured > 0 else 1
|
|
579
|
+
)
|
|
580
|
+
|
|
581
|
+
# Cleanup orphaned agents (ours but no longer deployed)
|
|
582
|
+
# Get list of deployed agent filenames (what should remain)
|
|
583
|
+
deployed_filenames = []
|
|
584
|
+
for agent_name in deployment_result.get("deployed", []):
|
|
585
|
+
deployed_filenames.append(f"{agent_name}.md")
|
|
586
|
+
for agent_name in deployment_result.get("updated", []):
|
|
587
|
+
deployed_filenames.append(f"{agent_name}.md")
|
|
588
|
+
for agent_name in deployment_result.get("skipped", []):
|
|
589
|
+
deployed_filenames.append(f"{agent_name}.md")
|
|
590
|
+
|
|
591
|
+
# Run cleanup and get count of removed agents
|
|
592
|
+
removed = _cleanup_orphaned_agents(
|
|
593
|
+
deploy_target, deployed_filenames
|
|
594
|
+
)
|
|
595
|
+
|
|
596
|
+
# Show total configured agents (deployed + updated + already existing)
|
|
597
|
+
# Include repo count for context and removed count if any
|
|
430
598
|
if deployed > 0 or updated > 0:
|
|
599
|
+
if removed > 0:
|
|
600
|
+
deploy_progress.finish(
|
|
601
|
+
f"Complete: {deployed} new, {updated} updated, {skipped} unchanged, "
|
|
602
|
+
f"{removed} removed ({total_configured} configured from {agent_count} in repo)"
|
|
603
|
+
)
|
|
604
|
+
else:
|
|
605
|
+
deploy_progress.finish(
|
|
606
|
+
f"Complete: {deployed} new, {updated} updated, {skipped} unchanged "
|
|
607
|
+
f"({total_configured} configured from {agent_count} in repo)"
|
|
608
|
+
)
|
|
609
|
+
elif removed > 0:
|
|
431
610
|
deploy_progress.finish(
|
|
432
|
-
f"Complete: {
|
|
611
|
+
f"Complete: {total_configured} agents ready - all unchanged, "
|
|
612
|
+
f"{removed} removed ({agent_count} available in repo)"
|
|
433
613
|
)
|
|
434
614
|
else:
|
|
435
615
|
deploy_progress.finish(
|
|
436
|
-
f"Complete: {
|
|
616
|
+
f"Complete: {total_configured} agents ready - all unchanged "
|
|
617
|
+
f"({agent_count} available in repo)"
|
|
437
618
|
)
|
|
438
619
|
|
|
439
620
|
# Display deployment errors to user (not just logs)
|
|
@@ -496,14 +677,21 @@ def sync_remote_skills_on_startup():
|
|
|
496
677
|
|
|
497
678
|
Workflow:
|
|
498
679
|
1. Sync all enabled Git sources (download/cache files) - Phase 1 progress bar
|
|
499
|
-
2.
|
|
500
|
-
3.
|
|
680
|
+
2. Scan deployed agents for skill requirements → save to configuration.yaml
|
|
681
|
+
3. Resolve which skills to deploy (user_defined vs agent_referenced)
|
|
682
|
+
4. Deploy resolved skills to ~/.claude/skills/ - Phase 2 progress bar
|
|
683
|
+
5. Log deployment results with source indication
|
|
501
684
|
"""
|
|
502
685
|
try:
|
|
503
686
|
from pathlib import Path
|
|
504
687
|
|
|
505
688
|
from ..config.skill_sources import SkillSourceConfiguration
|
|
506
689
|
from ..services.skills.git_skill_source_manager import GitSkillSourceManager
|
|
690
|
+
from ..services.skills.selective_skill_deployer import (
|
|
691
|
+
get_required_skills_from_agents,
|
|
692
|
+
get_skills_to_deploy,
|
|
693
|
+
save_agent_skills_to_config,
|
|
694
|
+
)
|
|
507
695
|
from ..utils.progress import ProgressBar
|
|
508
696
|
|
|
509
697
|
config = SkillSourceConfiguration()
|
|
@@ -523,6 +711,8 @@ def sync_remote_skills_on_startup():
|
|
|
523
711
|
|
|
524
712
|
# Discover total file count across all sources
|
|
525
713
|
total_file_count = 0
|
|
714
|
+
total_skill_dirs = 0 # Count actual skill directories (folders with SKILL.md)
|
|
715
|
+
|
|
526
716
|
for source in enabled_sources:
|
|
527
717
|
try:
|
|
528
718
|
# Parse GitHub URL
|
|
@@ -546,15 +736,26 @@ def sync_remote_skills_on_startup():
|
|
|
546
736
|
]
|
|
547
737
|
total_file_count += len(relevant_files)
|
|
548
738
|
|
|
739
|
+
# Count skill directories (unique directories containing SKILL.md)
|
|
740
|
+
skill_dirs = set()
|
|
741
|
+
for f in all_files:
|
|
742
|
+
if f.endswith("/SKILL.md"):
|
|
743
|
+
# Extract directory path
|
|
744
|
+
skill_dir = "/".join(f.split("/")[:-1])
|
|
745
|
+
skill_dirs.add(skill_dir)
|
|
746
|
+
total_skill_dirs += len(skill_dirs)
|
|
747
|
+
|
|
549
748
|
except Exception as e:
|
|
550
749
|
logger.debug(f"Failed to discover files for {source.id}: {e}")
|
|
551
750
|
# Use estimate if discovery fails
|
|
552
751
|
total_file_count += 150
|
|
752
|
+
total_skill_dirs += 50 # Estimate ~50 skills
|
|
553
753
|
|
|
554
754
|
# Create progress bar for sync phase with actual file count
|
|
755
|
+
# Note: We sync files (md, json, etc.), but will deploy skill directories
|
|
555
756
|
sync_progress = ProgressBar(
|
|
556
757
|
total=total_file_count if total_file_count > 0 else 1,
|
|
557
|
-
prefix="Syncing
|
|
758
|
+
prefix="Syncing skill files",
|
|
558
759
|
show_percentage=True,
|
|
559
760
|
show_counter=True,
|
|
560
761
|
)
|
|
@@ -571,51 +772,100 @@ def sync_remote_skills_on_startup():
|
|
|
571
772
|
|
|
572
773
|
if cached > 0:
|
|
573
774
|
sync_progress.finish(
|
|
574
|
-
f"Complete: {downloaded} downloaded, {cached} cached ({total_files}
|
|
775
|
+
f"Complete: {downloaded} downloaded, {cached} cached ({total_files} files, {total_skill_dirs} skills)"
|
|
575
776
|
)
|
|
576
777
|
else:
|
|
577
778
|
# All new downloads (first sync)
|
|
578
|
-
sync_progress.finish(
|
|
779
|
+
sync_progress.finish(
|
|
780
|
+
f"Complete: {downloaded} files downloaded ({total_skill_dirs} skills)"
|
|
781
|
+
)
|
|
579
782
|
|
|
580
|
-
# Phase 2:
|
|
581
|
-
# This
|
|
582
|
-
# into flat deployment (e.g., collaboration-dispatching-parallel-agents/SKILL.md)
|
|
783
|
+
# Phase 2: Scan agents and save to configuration.yaml
|
|
784
|
+
# This step populates configuration.yaml with agent-referenced skills
|
|
583
785
|
if results["synced_count"] > 0:
|
|
584
|
-
|
|
786
|
+
agents_dir = Path.cwd() / ".claude" / "agents"
|
|
787
|
+
|
|
788
|
+
# Scan agents for skill requirements
|
|
789
|
+
agent_skills = get_required_skills_from_agents(agents_dir)
|
|
790
|
+
|
|
791
|
+
# Save to project-level configuration.yaml
|
|
792
|
+
project_config_path = Path.cwd() / ".claude-mpm" / "configuration.yaml"
|
|
793
|
+
save_agent_skills_to_config(list(agent_skills), project_config_path)
|
|
794
|
+
|
|
795
|
+
# Phase 3: Resolve which skills to deploy (user_defined or agent_referenced)
|
|
796
|
+
skills_to_deploy, skill_source = get_skills_to_deploy(project_config_path)
|
|
797
|
+
|
|
798
|
+
# Get all skills to determine counts
|
|
585
799
|
all_skills = manager.get_all_skills()
|
|
586
|
-
|
|
800
|
+
total_skill_count = len(all_skills)
|
|
587
801
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
prefix="Deploying skill directories",
|
|
593
|
-
show_percentage=True,
|
|
594
|
-
show_counter=True,
|
|
595
|
-
)
|
|
802
|
+
# Determine skill count based on resolution
|
|
803
|
+
skill_count = (
|
|
804
|
+
len(skills_to_deploy) if skills_to_deploy else total_skill_count
|
|
805
|
+
)
|
|
596
806
|
|
|
597
|
-
|
|
807
|
+
if skill_count > 0:
|
|
808
|
+
# Deploy skills with resolved filter
|
|
598
809
|
# Deploy to project directory (like agents), not user directory
|
|
599
810
|
deployment_result = manager.deploy_skills(
|
|
600
811
|
target_dir=Path.cwd() / ".claude" / "skills",
|
|
601
812
|
force=False,
|
|
602
|
-
|
|
813
|
+
skill_filter=set(skills_to_deploy) if skills_to_deploy else None,
|
|
603
814
|
)
|
|
604
815
|
|
|
605
|
-
#
|
|
816
|
+
# Get actual counts from deployment result
|
|
606
817
|
deployed = deployment_result.get("deployed_count", 0)
|
|
607
818
|
skipped = deployment_result.get("skipped_count", 0)
|
|
819
|
+
filtered = deployment_result.get("filtered_count", 0)
|
|
608
820
|
total_available = deployed + skipped
|
|
609
821
|
|
|
822
|
+
# Only show progress bar if there are skills to deploy
|
|
823
|
+
if total_available > 0:
|
|
824
|
+
deploy_progress = ProgressBar(
|
|
825
|
+
total=total_available,
|
|
826
|
+
prefix="Deploying skill directories",
|
|
827
|
+
show_percentage=True,
|
|
828
|
+
show_counter=True,
|
|
829
|
+
)
|
|
830
|
+
# Update progress bar to completion
|
|
831
|
+
deploy_progress.update(total_available)
|
|
832
|
+
else:
|
|
833
|
+
# No skills to deploy - create dummy progress for message only
|
|
834
|
+
deploy_progress = ProgressBar(
|
|
835
|
+
total=1,
|
|
836
|
+
prefix="Deploying skill directories",
|
|
837
|
+
show_percentage=False,
|
|
838
|
+
show_counter=False,
|
|
839
|
+
)
|
|
840
|
+
deploy_progress.update(1)
|
|
841
|
+
|
|
610
842
|
# Show total available skills (deployed + already existing)
|
|
611
|
-
#
|
|
843
|
+
# Include source indication (user_defined vs agent_referenced)
|
|
844
|
+
# Note: total_skill_count is from the repo, total_available is what's deployed/needed
|
|
845
|
+
source_label = (
|
|
846
|
+
"user override" if skill_source == "user_defined" else "from agents"
|
|
847
|
+
)
|
|
848
|
+
|
|
612
849
|
if deployed > 0:
|
|
850
|
+
if filtered > 0:
|
|
851
|
+
deploy_progress.finish(
|
|
852
|
+
f"Complete: {deployed} new, {skipped} unchanged "
|
|
853
|
+
f"({total_available} {source_label}, {filtered} available in repo)"
|
|
854
|
+
)
|
|
855
|
+
else:
|
|
856
|
+
deploy_progress.finish(
|
|
857
|
+
f"Complete: {deployed} new, {skipped} unchanged "
|
|
858
|
+
f"({total_available} skills {source_label} from {total_skill_count} in repo)"
|
|
859
|
+
)
|
|
860
|
+
elif filtered > 0:
|
|
861
|
+
# Skills filtered means agents require fewer skills than available
|
|
613
862
|
deploy_progress.finish(
|
|
614
|
-
f"
|
|
863
|
+
f"No skills needed ({source_label}, {total_skill_count} available in repo)"
|
|
615
864
|
)
|
|
616
865
|
else:
|
|
617
866
|
deploy_progress.finish(
|
|
618
|
-
f"Complete: {total_available} skills
|
|
867
|
+
f"Complete: {total_available} skills {source_label} "
|
|
868
|
+
f"({total_skill_count} available in repo)"
|
|
619
869
|
)
|
|
620
870
|
|
|
621
871
|
# Log deployment errors if any
|
|
@@ -644,6 +894,202 @@ def sync_remote_skills_on_startup():
|
|
|
644
894
|
# Continue execution - skill sync failure shouldn't block startup
|
|
645
895
|
|
|
646
896
|
|
|
897
|
+
def show_agent_summary():
|
|
898
|
+
"""
|
|
899
|
+
Display agent availability summary on startup.
|
|
900
|
+
|
|
901
|
+
WHY: Users should see at a glance how many agents are available and installed
|
|
902
|
+
without having to run /mpm-agents list.
|
|
903
|
+
|
|
904
|
+
DESIGN DECISION: Fast, non-blocking check that counts agents from the deployment
|
|
905
|
+
directory. Shows simple "X installed / Y available" format. Failures are silent
|
|
906
|
+
to avoid blocking startup.
|
|
907
|
+
"""
|
|
908
|
+
try:
|
|
909
|
+
from pathlib import Path
|
|
910
|
+
|
|
911
|
+
# Count deployed agents (installed)
|
|
912
|
+
deploy_target = Path.cwd() / ".claude" / "agents"
|
|
913
|
+
installed_count = 0
|
|
914
|
+
if deploy_target.exists():
|
|
915
|
+
# Count .md files, excluding README and other docs
|
|
916
|
+
agent_files = [
|
|
917
|
+
f
|
|
918
|
+
for f in deploy_target.glob("*.md")
|
|
919
|
+
if not f.name.startswith(("README", "INSTRUCTIONS", "."))
|
|
920
|
+
]
|
|
921
|
+
installed_count = len(agent_files)
|
|
922
|
+
|
|
923
|
+
# Count available agents in cache (from remote sources)
|
|
924
|
+
cache_dir = Path.home() / ".claude-mpm" / "cache" / "remote-agents"
|
|
925
|
+
available_count = 0
|
|
926
|
+
if cache_dir.exists():
|
|
927
|
+
# Use same filtering logic as agent deployment (lines 486-533 in startup.py)
|
|
928
|
+
pm_templates = {
|
|
929
|
+
"base-agent.md",
|
|
930
|
+
"circuit_breakers.md",
|
|
931
|
+
"pm_examples.md",
|
|
932
|
+
"pm_red_flags.md",
|
|
933
|
+
"research_gate_examples.md",
|
|
934
|
+
"response_format.md",
|
|
935
|
+
"ticket_completeness_examples.md",
|
|
936
|
+
"validation_templates.md",
|
|
937
|
+
"git_file_tracking.md",
|
|
938
|
+
}
|
|
939
|
+
doc_files = {
|
|
940
|
+
"readme.md",
|
|
941
|
+
"changelog.md",
|
|
942
|
+
"contributing.md",
|
|
943
|
+
"implementation-summary.md",
|
|
944
|
+
"reorganization-plan.md",
|
|
945
|
+
"auto-deploy-index.md",
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
# Find all markdown files in agents/ directories
|
|
949
|
+
all_md_files = list(cache_dir.rglob("*.md"))
|
|
950
|
+
agent_files = [
|
|
951
|
+
f
|
|
952
|
+
for f in all_md_files
|
|
953
|
+
if (
|
|
954
|
+
"/agents/" in str(f)
|
|
955
|
+
and f.name.lower() not in pm_templates
|
|
956
|
+
and f.name.lower() not in doc_files
|
|
957
|
+
and f.name.lower() != "base-agent.md"
|
|
958
|
+
and not any(
|
|
959
|
+
part in str(f).split("/")
|
|
960
|
+
for part in ["dist", "build", ".cache"]
|
|
961
|
+
)
|
|
962
|
+
)
|
|
963
|
+
]
|
|
964
|
+
available_count = len(agent_files)
|
|
965
|
+
|
|
966
|
+
# Display summary if we have agents
|
|
967
|
+
if installed_count > 0 or available_count > 0:
|
|
968
|
+
print(
|
|
969
|
+
f"✓ Agents: {installed_count} installed / {available_count} available",
|
|
970
|
+
flush=True,
|
|
971
|
+
)
|
|
972
|
+
|
|
973
|
+
except Exception as e:
|
|
974
|
+
# Silent failure - agent summary is informational only
|
|
975
|
+
from ..core.logger import get_logger
|
|
976
|
+
|
|
977
|
+
logger = get_logger("cli")
|
|
978
|
+
logger.debug(f"Failed to generate agent summary: {e}")
|
|
979
|
+
|
|
980
|
+
|
|
981
|
+
def show_skill_summary():
|
|
982
|
+
"""
|
|
983
|
+
Display skill availability summary on startup.
|
|
984
|
+
|
|
985
|
+
WHY: Users should see at a glance how many skills are deployed and available
|
|
986
|
+
from collections, similar to the agent summary.
|
|
987
|
+
|
|
988
|
+
DESIGN DECISION: Fast, non-blocking check that counts skills from deployment
|
|
989
|
+
directory and collection repos. Shows "X installed (Y available)" format.
|
|
990
|
+
Failures are silent to avoid blocking startup.
|
|
991
|
+
"""
|
|
992
|
+
try:
|
|
993
|
+
from pathlib import Path
|
|
994
|
+
|
|
995
|
+
# Count deployed skills (installed)
|
|
996
|
+
skills_dir = Path.home() / ".claude" / "skills"
|
|
997
|
+
installed_count = 0
|
|
998
|
+
if skills_dir.exists():
|
|
999
|
+
# Count directories with SKILL.md (excludes collection repos)
|
|
1000
|
+
# Exclude collection directories (obra-superpowers, etc.)
|
|
1001
|
+
skill_dirs = [
|
|
1002
|
+
d
|
|
1003
|
+
for d in skills_dir.iterdir()
|
|
1004
|
+
if d.is_dir()
|
|
1005
|
+
and (d / "SKILL.md").exists()
|
|
1006
|
+
and not (d / ".git").exists() # Exclude collection repos
|
|
1007
|
+
]
|
|
1008
|
+
installed_count = len(skill_dirs)
|
|
1009
|
+
|
|
1010
|
+
# Count available skills in collections
|
|
1011
|
+
available_count = 0
|
|
1012
|
+
if skills_dir.exists():
|
|
1013
|
+
# Scan all collection directories (those with .git)
|
|
1014
|
+
for collection_dir in skills_dir.iterdir():
|
|
1015
|
+
if (
|
|
1016
|
+
not collection_dir.is_dir()
|
|
1017
|
+
or not (collection_dir / ".git").exists()
|
|
1018
|
+
):
|
|
1019
|
+
continue
|
|
1020
|
+
|
|
1021
|
+
# Count skill directories in this collection
|
|
1022
|
+
# Skills can be nested in: skills/category/skill-name/SKILL.md
|
|
1023
|
+
# or in flat structure: skill-name/SKILL.md
|
|
1024
|
+
for root, dirs, files in os.walk(collection_dir):
|
|
1025
|
+
if "SKILL.md" in files:
|
|
1026
|
+
# Exclude build artifacts and hidden directories (within the collection)
|
|
1027
|
+
# Get relative path from collection_dir to avoid excluding based on .claude parent
|
|
1028
|
+
root_path = Path(root)
|
|
1029
|
+
relative_parts = root_path.relative_to(collection_dir).parts
|
|
1030
|
+
if not any(
|
|
1031
|
+
part.startswith(".")
|
|
1032
|
+
or part in ["dist", "build", "__pycache__"]
|
|
1033
|
+
for part in relative_parts
|
|
1034
|
+
):
|
|
1035
|
+
available_count += 1
|
|
1036
|
+
|
|
1037
|
+
# Display summary if we have skills
|
|
1038
|
+
if installed_count > 0 or available_count > 0:
|
|
1039
|
+
print(
|
|
1040
|
+
f"✓ Skills: {installed_count} installed ({available_count} available)",
|
|
1041
|
+
flush=True,
|
|
1042
|
+
)
|
|
1043
|
+
|
|
1044
|
+
except Exception as e:
|
|
1045
|
+
# Silent failure - skill summary is informational only
|
|
1046
|
+
from ..core.logger import get_logger
|
|
1047
|
+
|
|
1048
|
+
logger = get_logger("cli")
|
|
1049
|
+
logger.debug(f"Failed to generate skill summary: {e}")
|
|
1050
|
+
|
|
1051
|
+
|
|
1052
|
+
def auto_install_chrome_devtools_on_startup():
|
|
1053
|
+
"""
|
|
1054
|
+
Automatically install chrome-devtools-mcp on startup if enabled.
|
|
1055
|
+
|
|
1056
|
+
WHY: Browser automation capabilities should be available out-of-the-box without
|
|
1057
|
+
manual MCP server configuration. chrome-devtools-mcp provides powerful browser
|
|
1058
|
+
interaction tools for Claude Code.
|
|
1059
|
+
|
|
1060
|
+
DESIGN DECISION: Non-blocking installation that doesn't prevent startup if it fails.
|
|
1061
|
+
Respects user configuration setting (enabled by default). Only installs if not
|
|
1062
|
+
already configured in Claude.
|
|
1063
|
+
"""
|
|
1064
|
+
try:
|
|
1065
|
+
# Check if auto-install is disabled in config
|
|
1066
|
+
from ..config.config_loader import ConfigLoader
|
|
1067
|
+
|
|
1068
|
+
config_loader = ConfigLoader()
|
|
1069
|
+
try:
|
|
1070
|
+
config = config_loader.load_main_config()
|
|
1071
|
+
chrome_devtools_config = config.get("chrome_devtools", {})
|
|
1072
|
+
if not chrome_devtools_config.get("auto_install", True):
|
|
1073
|
+
# Auto-install disabled, skip silently
|
|
1074
|
+
return
|
|
1075
|
+
except Exception:
|
|
1076
|
+
# If config loading fails, assume auto-install is enabled (default)
|
|
1077
|
+
pass
|
|
1078
|
+
|
|
1079
|
+
# Import and run chrome-devtools installation
|
|
1080
|
+
from ..cli.chrome_devtools_installer import auto_install_chrome_devtools
|
|
1081
|
+
|
|
1082
|
+
auto_install_chrome_devtools(quiet=False)
|
|
1083
|
+
|
|
1084
|
+
except Exception as e:
|
|
1085
|
+
# Import logger here to avoid circular imports
|
|
1086
|
+
from ..core.logger import get_logger
|
|
1087
|
+
|
|
1088
|
+
logger = get_logger("cli")
|
|
1089
|
+
logger.debug(f"Failed to auto-install chrome-devtools-mcp: {e}")
|
|
1090
|
+
# Continue execution - chrome-devtools installation failure shouldn't block startup
|
|
1091
|
+
|
|
1092
|
+
|
|
647
1093
|
def run_background_services():
|
|
648
1094
|
"""
|
|
649
1095
|
Initialize all background services on startup.
|
|
@@ -656,11 +1102,17 @@ def run_background_services():
|
|
|
656
1102
|
file creation in project .claude/ directories.
|
|
657
1103
|
See: SystemInstructionsDeployer and agent_deployment.py line 504-509
|
|
658
1104
|
"""
|
|
1105
|
+
# Sync hooks early to ensure up-to-date configuration
|
|
1106
|
+
# RATIONALE: Hooks should be synced before other services to fix stale configs
|
|
1107
|
+
# This is fast (<100ms) and non-blocking, so it doesn't delay startup
|
|
1108
|
+
sync_hooks_on_startup() # Shows "Syncing Claude Code hooks... ✓"
|
|
1109
|
+
|
|
659
1110
|
initialize_project_registry()
|
|
660
1111
|
check_mcp_auto_configuration()
|
|
661
1112
|
verify_mcp_gateway_startup()
|
|
662
1113
|
check_for_updates_async()
|
|
663
1114
|
sync_remote_agents_on_startup() # Sync agents from remote sources
|
|
1115
|
+
show_agent_summary() # Display agent counts after deployment
|
|
664
1116
|
|
|
665
1117
|
# Skills deployment order (precedence: remote > bundled)
|
|
666
1118
|
# 1. Deploy bundled skills first (base layer from package)
|
|
@@ -670,9 +1122,13 @@ def run_background_services():
|
|
|
670
1122
|
deploy_bundled_skills() # Base layer: package-bundled skills
|
|
671
1123
|
sync_remote_skills_on_startup() # Override layer: Git-based skills (takes precedence)
|
|
672
1124
|
discover_and_link_runtime_skills() # Discovery: user-added skills
|
|
1125
|
+
show_skill_summary() # Display skill counts after deployment
|
|
673
1126
|
|
|
674
1127
|
deploy_output_style_on_startup()
|
|
675
1128
|
|
|
1129
|
+
# Auto-install chrome-devtools-mcp for browser automation
|
|
1130
|
+
auto_install_chrome_devtools_on_startup()
|
|
1131
|
+
|
|
676
1132
|
|
|
677
1133
|
def setup_mcp_server_logging(args):
|
|
678
1134
|
"""
|