claude-mpm 4.7.4__py3-none-any.whl → 4.18.2__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/BASE_AGENT_TEMPLATE.md +118 -0
- claude_mpm/agents/BASE_ENGINEER.md +286 -0
- claude_mpm/agents/BASE_PM.md +106 -1
- claude_mpm/agents/OUTPUT_STYLE.md +329 -11
- claude_mpm/agents/PM_INSTRUCTIONS.md +397 -459
- claude_mpm/agents/agent_loader.py +17 -5
- claude_mpm/agents/frontmatter_validator.py +284 -253
- claude_mpm/agents/templates/README.md +465 -0
- claude_mpm/agents/templates/agent-manager.json +4 -1
- claude_mpm/agents/templates/agentic-coder-optimizer.json +13 -3
- claude_mpm/agents/templates/api_qa.json +11 -2
- claude_mpm/agents/templates/circuit_breakers.md +638 -0
- claude_mpm/agents/templates/clerk-ops.json +12 -2
- claude_mpm/agents/templates/code_analyzer.json +8 -2
- claude_mpm/agents/templates/content-agent.json +358 -0
- claude_mpm/agents/templates/dart_engineer.json +15 -2
- claude_mpm/agents/templates/data_engineer.json +15 -2
- claude_mpm/agents/templates/documentation.json +10 -2
- claude_mpm/agents/templates/engineer.json +21 -1
- claude_mpm/agents/templates/gcp_ops_agent.json +12 -2
- claude_mpm/agents/templates/git_file_tracking.md +584 -0
- claude_mpm/agents/templates/golang_engineer.json +270 -0
- claude_mpm/agents/templates/imagemagick.json +4 -1
- claude_mpm/agents/templates/java_engineer.json +346 -0
- claude_mpm/agents/templates/local_ops_agent.json +1227 -6
- claude_mpm/agents/templates/memory_manager.json +4 -1
- claude_mpm/agents/templates/nextjs_engineer.json +141 -133
- claude_mpm/agents/templates/ops.json +12 -2
- claude_mpm/agents/templates/php-engineer.json +270 -174
- claude_mpm/agents/templates/pm_examples.md +474 -0
- claude_mpm/agents/templates/pm_red_flags.md +240 -0
- claude_mpm/agents/templates/product_owner.json +338 -0
- claude_mpm/agents/templates/project_organizer.json +14 -4
- claude_mpm/agents/templates/prompt-engineer.json +13 -2
- claude_mpm/agents/templates/python_engineer.json +174 -81
- claude_mpm/agents/templates/qa.json +11 -2
- claude_mpm/agents/templates/react_engineer.json +16 -3
- claude_mpm/agents/templates/refactoring_engineer.json +12 -2
- claude_mpm/agents/templates/research.json +34 -21
- claude_mpm/agents/templates/response_format.md +583 -0
- claude_mpm/agents/templates/ruby-engineer.json +129 -192
- claude_mpm/agents/templates/rust_engineer.json +270 -0
- claude_mpm/agents/templates/security.json +10 -2
- claude_mpm/agents/templates/svelte-engineer.json +225 -0
- claude_mpm/agents/templates/ticketing.json +10 -2
- claude_mpm/agents/templates/typescript_engineer.json +116 -125
- claude_mpm/agents/templates/validation_templates.md +312 -0
- claude_mpm/agents/templates/vercel_ops_agent.json +12 -2
- claude_mpm/agents/templates/version_control.json +12 -2
- claude_mpm/agents/templates/web_qa.json +11 -2
- claude_mpm/agents/templates/web_ui.json +15 -2
- claude_mpm/cli/__init__.py +34 -614
- claude_mpm/cli/commands/agent_manager.py +25 -12
- claude_mpm/cli/commands/agent_state_manager.py +186 -0
- claude_mpm/cli/commands/agents.py +235 -148
- claude_mpm/cli/commands/agents_detect.py +380 -0
- claude_mpm/cli/commands/agents_recommend.py +309 -0
- claude_mpm/cli/commands/aggregate.py +7 -3
- claude_mpm/cli/commands/analyze.py +9 -4
- claude_mpm/cli/commands/analyze_code.py +7 -2
- claude_mpm/cli/commands/auto_configure.py +570 -0
- claude_mpm/cli/commands/config.py +47 -13
- claude_mpm/cli/commands/configure.py +419 -1571
- claude_mpm/cli/commands/configure_agent_display.py +261 -0
- claude_mpm/cli/commands/configure_behavior_manager.py +204 -0
- claude_mpm/cli/commands/configure_hook_manager.py +225 -0
- claude_mpm/cli/commands/configure_models.py +18 -0
- claude_mpm/cli/commands/configure_navigation.py +167 -0
- claude_mpm/cli/commands/configure_paths.py +104 -0
- claude_mpm/cli/commands/configure_persistence.py +254 -0
- claude_mpm/cli/commands/configure_startup_manager.py +646 -0
- claude_mpm/cli/commands/configure_template_editor.py +497 -0
- claude_mpm/cli/commands/configure_validators.py +73 -0
- claude_mpm/cli/commands/local_deploy.py +537 -0
- claude_mpm/cli/commands/memory.py +54 -20
- claude_mpm/cli/commands/mpm_init.py +585 -196
- claude_mpm/cli/commands/mpm_init_handler.py +37 -3
- claude_mpm/cli/commands/search.py +170 -4
- claude_mpm/cli/commands/upgrade.py +152 -0
- claude_mpm/cli/executor.py +202 -0
- claude_mpm/cli/helpers.py +105 -0
- claude_mpm/cli/interactive/__init__.py +3 -0
- claude_mpm/cli/interactive/skills_wizard.py +491 -0
- claude_mpm/cli/parsers/__init__.py +7 -1
- claude_mpm/cli/parsers/agents_parser.py +9 -0
- claude_mpm/cli/parsers/auto_configure_parser.py +245 -0
- claude_mpm/cli/parsers/base_parser.py +110 -3
- claude_mpm/cli/parsers/local_deploy_parser.py +227 -0
- claude_mpm/cli/parsers/mpm_init_parser.py +65 -5
- claude_mpm/cli/shared/output_formatters.py +28 -19
- claude_mpm/cli/startup.py +481 -0
- claude_mpm/cli/utils.py +52 -1
- claude_mpm/commands/mpm-agents-detect.md +168 -0
- claude_mpm/commands/mpm-agents-recommend.md +214 -0
- claude_mpm/commands/mpm-agents.md +75 -1
- claude_mpm/commands/mpm-auto-configure.md +217 -0
- claude_mpm/commands/mpm-help.md +163 -0
- claude_mpm/commands/mpm-init.md +148 -3
- claude_mpm/commands/mpm-version.md +113 -0
- claude_mpm/commands/mpm.md +1 -0
- claude_mpm/config/agent_config.py +2 -2
- claude_mpm/config/model_config.py +428 -0
- claude_mpm/constants.py +1 -0
- claude_mpm/core/base_service.py +13 -12
- claude_mpm/core/enums.py +452 -0
- claude_mpm/core/factories.py +1 -1
- claude_mpm/core/instruction_reinforcement_hook.py +2 -1
- claude_mpm/core/interactive_session.py +9 -3
- claude_mpm/core/log_manager.py +2 -0
- claude_mpm/core/logging_config.py +6 -2
- claude_mpm/core/oneshot_session.py +8 -4
- claude_mpm/core/optimized_agent_loader.py +3 -3
- claude_mpm/core/output_style_manager.py +12 -192
- claude_mpm/core/service_registry.py +5 -1
- claude_mpm/core/types.py +2 -9
- claude_mpm/core/typing_utils.py +7 -6
- claude_mpm/dashboard/static/js/dashboard.js +0 -14
- claude_mpm/dashboard/templates/index.html +3 -41
- claude_mpm/hooks/__init__.py +20 -0
- claude_mpm/hooks/claude_hooks/event_handlers.py +4 -2
- claude_mpm/hooks/claude_hooks/response_tracking.py +35 -1
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +23 -2
- claude_mpm/hooks/failure_learning/__init__.py +60 -0
- claude_mpm/hooks/failure_learning/failure_detection_hook.py +235 -0
- claude_mpm/hooks/failure_learning/fix_detection_hook.py +217 -0
- claude_mpm/hooks/failure_learning/learning_extraction_hook.py +286 -0
- claude_mpm/hooks/instruction_reinforcement.py +7 -2
- claude_mpm/hooks/kuzu_enrichment_hook.py +263 -0
- claude_mpm/hooks/kuzu_memory_hook.py +37 -12
- claude_mpm/hooks/kuzu_response_hook.py +183 -0
- claude_mpm/models/resume_log.py +340 -0
- claude_mpm/services/agents/__init__.py +18 -5
- claude_mpm/services/agents/auto_config_manager.py +796 -0
- claude_mpm/services/agents/deployment/agent_configuration_manager.py +1 -1
- claude_mpm/services/agents/deployment/agent_record_service.py +1 -1
- claude_mpm/services/agents/deployment/agent_validator.py +17 -1
- claude_mpm/services/agents/deployment/async_agent_deployment.py +1 -1
- claude_mpm/services/agents/deployment/interface_adapter.py +3 -2
- claude_mpm/services/agents/deployment/local_template_deployment.py +1 -1
- claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +7 -6
- claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +7 -16
- claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +4 -3
- claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +5 -3
- claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +6 -5
- claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +9 -6
- claude_mpm/services/agents/deployment/validation/__init__.py +3 -1
- claude_mpm/services/agents/deployment/validation/validation_result.py +1 -9
- claude_mpm/services/agents/local_template_manager.py +1 -1
- claude_mpm/services/agents/memory/agent_memory_manager.py +5 -2
- claude_mpm/services/agents/observers.py +547 -0
- claude_mpm/services/agents/recommender.py +568 -0
- claude_mpm/services/agents/registry/modification_tracker.py +5 -2
- claude_mpm/services/command_handler_service.py +11 -5
- claude_mpm/services/core/__init__.py +33 -1
- claude_mpm/services/core/interfaces/__init__.py +90 -3
- claude_mpm/services/core/interfaces/agent.py +184 -0
- claude_mpm/services/core/interfaces/health.py +172 -0
- claude_mpm/services/core/interfaces/model.py +281 -0
- claude_mpm/services/core/interfaces/process.py +372 -0
- claude_mpm/services/core/interfaces/project.py +121 -0
- claude_mpm/services/core/interfaces/restart.py +307 -0
- claude_mpm/services/core/interfaces/stability.py +260 -0
- claude_mpm/services/core/memory_manager.py +11 -24
- claude_mpm/services/core/models/__init__.py +79 -0
- claude_mpm/services/core/models/agent_config.py +381 -0
- claude_mpm/services/core/models/health.py +162 -0
- claude_mpm/services/core/models/process.py +235 -0
- claude_mpm/services/core/models/restart.py +302 -0
- claude_mpm/services/core/models/stability.py +264 -0
- claude_mpm/services/core/models/toolchain.py +306 -0
- claude_mpm/services/core/path_resolver.py +23 -7
- claude_mpm/services/diagnostics/__init__.py +2 -2
- claude_mpm/services/diagnostics/checks/agent_check.py +25 -24
- claude_mpm/services/diagnostics/checks/claude_code_check.py +24 -23
- claude_mpm/services/diagnostics/checks/common_issues_check.py +25 -24
- claude_mpm/services/diagnostics/checks/configuration_check.py +24 -23
- claude_mpm/services/diagnostics/checks/filesystem_check.py +18 -17
- claude_mpm/services/diagnostics/checks/installation_check.py +30 -29
- claude_mpm/services/diagnostics/checks/instructions_check.py +20 -19
- claude_mpm/services/diagnostics/checks/mcp_check.py +50 -36
- claude_mpm/services/diagnostics/checks/mcp_services_check.py +38 -33
- claude_mpm/services/diagnostics/checks/monitor_check.py +23 -22
- claude_mpm/services/diagnostics/checks/startup_log_check.py +9 -8
- claude_mpm/services/diagnostics/diagnostic_runner.py +6 -5
- claude_mpm/services/diagnostics/doctor_reporter.py +28 -25
- claude_mpm/services/diagnostics/models.py +19 -24
- claude_mpm/services/infrastructure/monitoring/__init__.py +1 -1
- claude_mpm/services/infrastructure/monitoring/aggregator.py +12 -12
- claude_mpm/services/infrastructure/monitoring/base.py +5 -13
- claude_mpm/services/infrastructure/monitoring/network.py +7 -6
- claude_mpm/services/infrastructure/monitoring/process.py +13 -12
- claude_mpm/services/infrastructure/monitoring/resources.py +7 -6
- claude_mpm/services/infrastructure/monitoring/service.py +16 -15
- claude_mpm/services/infrastructure/resume_log_generator.py +439 -0
- claude_mpm/services/local_ops/__init__.py +163 -0
- claude_mpm/services/local_ops/crash_detector.py +257 -0
- claude_mpm/services/local_ops/health_checks/__init__.py +28 -0
- claude_mpm/services/local_ops/health_checks/http_check.py +224 -0
- claude_mpm/services/local_ops/health_checks/process_check.py +236 -0
- claude_mpm/services/local_ops/health_checks/resource_check.py +255 -0
- claude_mpm/services/local_ops/health_manager.py +430 -0
- claude_mpm/services/local_ops/log_monitor.py +396 -0
- claude_mpm/services/local_ops/memory_leak_detector.py +294 -0
- claude_mpm/services/local_ops/process_manager.py +595 -0
- claude_mpm/services/local_ops/resource_monitor.py +331 -0
- claude_mpm/services/local_ops/restart_manager.py +401 -0
- claude_mpm/services/local_ops/restart_policy.py +387 -0
- claude_mpm/services/local_ops/state_manager.py +372 -0
- claude_mpm/services/local_ops/unified_manager.py +600 -0
- claude_mpm/services/mcp_config_manager.py +9 -4
- claude_mpm/services/mcp_gateway/core/__init__.py +1 -2
- claude_mpm/services/mcp_gateway/core/base.py +18 -31
- claude_mpm/services/mcp_gateway/main.py +30 -0
- claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +206 -32
- claude_mpm/services/mcp_gateway/tools/health_check_tool.py +30 -28
- claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +25 -5
- claude_mpm/services/mcp_service_verifier.py +1 -1
- claude_mpm/services/memory/failure_tracker.py +563 -0
- claude_mpm/services/memory_hook_service.py +165 -4
- claude_mpm/services/model/__init__.py +147 -0
- claude_mpm/services/model/base_provider.py +365 -0
- claude_mpm/services/model/claude_provider.py +412 -0
- claude_mpm/services/model/model_router.py +453 -0
- claude_mpm/services/model/ollama_provider.py +415 -0
- claude_mpm/services/monitor/daemon_manager.py +3 -2
- claude_mpm/services/monitor/handlers/dashboard.py +2 -1
- claude_mpm/services/monitor/handlers/hooks.py +2 -1
- claude_mpm/services/monitor/management/lifecycle.py +3 -2
- claude_mpm/services/monitor/server.py +2 -1
- claude_mpm/services/project/__init__.py +23 -0
- claude_mpm/services/project/detection_strategies.py +719 -0
- claude_mpm/services/project/toolchain_analyzer.py +581 -0
- claude_mpm/services/self_upgrade_service.py +342 -0
- claude_mpm/services/session_management_service.py +3 -2
- claude_mpm/services/session_manager.py +205 -1
- claude_mpm/services/shared/async_service_base.py +16 -27
- claude_mpm/services/shared/lifecycle_service_base.py +1 -14
- claude_mpm/services/socketio/handlers/__init__.py +5 -2
- claude_mpm/services/socketio/handlers/hook.py +13 -2
- claude_mpm/services/socketio/handlers/registry.py +4 -2
- claude_mpm/services/socketio/server/main.py +10 -8
- claude_mpm/services/subprocess_launcher_service.py +14 -5
- claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +8 -7
- claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +6 -5
- claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +8 -7
- claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +7 -6
- claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +5 -4
- claude_mpm/services/unified/config_strategies/validation_strategy.py +13 -9
- claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +10 -3
- claude_mpm/services/unified/deployment_strategies/local.py +6 -5
- claude_mpm/services/unified/deployment_strategies/utils.py +6 -5
- claude_mpm/services/unified/deployment_strategies/vercel.py +7 -6
- claude_mpm/services/unified/interfaces.py +3 -1
- claude_mpm/services/unified/unified_analyzer.py +14 -10
- claude_mpm/services/unified/unified_config.py +2 -1
- claude_mpm/services/unified/unified_deployment.py +9 -4
- claude_mpm/services/version_service.py +104 -1
- claude_mpm/skills/__init__.py +21 -0
- claude_mpm/skills/bundled/__init__.py +6 -0
- claude_mpm/skills/bundled/api-documentation.md +393 -0
- claude_mpm/skills/bundled/async-testing.md +571 -0
- claude_mpm/skills/bundled/code-review.md +143 -0
- claude_mpm/skills/bundled/database-migration.md +199 -0
- claude_mpm/skills/bundled/docker-containerization.md +194 -0
- claude_mpm/skills/bundled/express-local-dev.md +1429 -0
- claude_mpm/skills/bundled/fastapi-local-dev.md +1199 -0
- claude_mpm/skills/bundled/git-workflow.md +414 -0
- claude_mpm/skills/bundled/imagemagick.md +204 -0
- claude_mpm/skills/bundled/json-data-handling.md +223 -0
- claude_mpm/skills/bundled/nextjs-local-dev.md +807 -0
- claude_mpm/skills/bundled/pdf.md +141 -0
- claude_mpm/skills/bundled/performance-profiling.md +567 -0
- claude_mpm/skills/bundled/refactoring-patterns.md +180 -0
- claude_mpm/skills/bundled/security-scanning.md +327 -0
- claude_mpm/skills/bundled/systematic-debugging.md +473 -0
- claude_mpm/skills/bundled/test-driven-development.md +378 -0
- claude_mpm/skills/bundled/vite-local-dev.md +1061 -0
- claude_mpm/skills/bundled/web-performance-optimization.md +2305 -0
- claude_mpm/skills/bundled/xlsx.md +157 -0
- claude_mpm/skills/registry.py +286 -0
- claude_mpm/skills/skill_manager.py +310 -0
- claude_mpm/storage/state_storage.py +15 -15
- claude_mpm/tools/code_tree_analyzer.py +177 -141
- claude_mpm/tools/code_tree_events.py +4 -2
- claude_mpm/utils/agent_dependency_loader.py +40 -20
- claude_mpm/utils/display_helper.py +260 -0
- claude_mpm/utils/git_analyzer.py +407 -0
- claude_mpm/utils/robust_installer.py +73 -19
- {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/METADATA +129 -12
- {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/RECORD +295 -193
- claude_mpm/dashboard/static/css/code-tree.css +0 -1639
- claude_mpm/dashboard/static/index-hub-backup.html +0 -713
- claude_mpm/dashboard/static/js/components/code-tree/tree-breadcrumb.js +0 -353
- claude_mpm/dashboard/static/js/components/code-tree/tree-constants.js +0 -235
- claude_mpm/dashboard/static/js/components/code-tree/tree-search.js +0 -409
- claude_mpm/dashboard/static/js/components/code-tree/tree-utils.js +0 -435
- claude_mpm/dashboard/static/js/components/code-tree.js +0 -5869
- claude_mpm/dashboard/static/js/components/code-viewer.js +0 -1386
- claude_mpm/hooks/claude_hooks/hook_handler_eventbus.py +0 -425
- claude_mpm/hooks/claude_hooks/hook_handler_original.py +0 -1041
- claude_mpm/hooks/claude_hooks/hook_handler_refactored.py +0 -347
- claude_mpm/services/agents/deployment/agent_lifecycle_manager_refactored.py +0 -575
- claude_mpm/services/project/analyzer_refactored.py +0 -450
- {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/WHEEL +0 -0
- {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/top_level.txt +0 -0
|
@@ -12,167 +12,30 @@ DESIGN DECISIONS:
|
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
14
|
import json
|
|
15
|
-
import os
|
|
16
|
-
import sys
|
|
17
15
|
from pathlib import Path
|
|
18
16
|
from typing import Dict, List, Optional
|
|
19
17
|
|
|
20
|
-
from rich.box import ROUNDED
|
|
21
|
-
from rich.columns import Columns
|
|
22
18
|
from rich.console import Console
|
|
23
|
-
from rich.panel import Panel
|
|
24
19
|
from rich.prompt import Confirm, Prompt
|
|
25
|
-
from rich.syntax import Syntax
|
|
26
|
-
from rich.table import Table
|
|
27
20
|
from rich.text import Text
|
|
28
21
|
|
|
29
22
|
from ...core.config import Config
|
|
30
|
-
from ...services.mcp_config_manager import MCPConfigManager
|
|
31
23
|
from ...services.version_service import VersionService
|
|
32
24
|
from ...utils.console import console as default_console
|
|
33
25
|
from ..shared import BaseCommand, CommandResult
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
class SimpleAgentManager:
|
|
48
|
-
"""Simple agent state management that discovers real agents from templates."""
|
|
49
|
-
|
|
50
|
-
def __init__(self, config_dir: Path):
|
|
51
|
-
self.config_dir = config_dir
|
|
52
|
-
self.config_file = config_dir / "agent_states.json"
|
|
53
|
-
self.config_dir.mkdir(parents=True, exist_ok=True)
|
|
54
|
-
self._load_states()
|
|
55
|
-
# Path to agent templates directory
|
|
56
|
-
self.templates_dir = (
|
|
57
|
-
Path(__file__).parent.parent.parent / "agents" / "templates"
|
|
58
|
-
)
|
|
59
|
-
# Add logger for error reporting
|
|
60
|
-
import logging
|
|
61
|
-
|
|
62
|
-
self.logger = logging.getLogger(__name__)
|
|
63
|
-
|
|
64
|
-
def _load_states(self):
|
|
65
|
-
"""Load agent states from file."""
|
|
66
|
-
if self.config_file.exists():
|
|
67
|
-
with self.config_file.open() as f:
|
|
68
|
-
self.states = json.load(f)
|
|
69
|
-
else:
|
|
70
|
-
self.states = {}
|
|
71
|
-
|
|
72
|
-
def _save_states(self):
|
|
73
|
-
"""Save agent states to file."""
|
|
74
|
-
with self.config_file.open("w") as f:
|
|
75
|
-
json.dump(self.states, f, indent=2)
|
|
76
|
-
|
|
77
|
-
def is_agent_enabled(self, agent_name: str) -> bool:
|
|
78
|
-
"""Check if an agent is enabled."""
|
|
79
|
-
return self.states.get(agent_name, {}).get("enabled", True)
|
|
80
|
-
|
|
81
|
-
def set_agent_enabled(self, agent_name: str, enabled: bool):
|
|
82
|
-
"""Set agent enabled state."""
|
|
83
|
-
if agent_name not in self.states:
|
|
84
|
-
self.states[agent_name] = {}
|
|
85
|
-
self.states[agent_name]["enabled"] = enabled
|
|
86
|
-
self._save_states()
|
|
87
|
-
|
|
88
|
-
def discover_agents(self) -> List[AgentConfig]:
|
|
89
|
-
"""Discover available agents from template JSON files."""
|
|
90
|
-
agents = []
|
|
91
|
-
|
|
92
|
-
# Scan templates directory for JSON files
|
|
93
|
-
if not self.templates_dir.exists():
|
|
94
|
-
# Fallback to a minimal set if templates dir doesn't exist
|
|
95
|
-
return [
|
|
96
|
-
AgentConfig("engineer", "Engineering agent (templates not found)", []),
|
|
97
|
-
AgentConfig("research", "Research agent (templates not found)", []),
|
|
98
|
-
]
|
|
99
|
-
|
|
100
|
-
try:
|
|
101
|
-
# Read all JSON template files
|
|
102
|
-
for template_file in sorted(self.templates_dir.glob("*.json")):
|
|
103
|
-
# Skip backup files
|
|
104
|
-
if "backup" in template_file.name.lower():
|
|
105
|
-
continue
|
|
106
|
-
|
|
107
|
-
try:
|
|
108
|
-
with template_file.open() as f:
|
|
109
|
-
template_data = json.load(f)
|
|
110
|
-
|
|
111
|
-
# Extract agent information from template
|
|
112
|
-
agent_id = template_data.get("agent_id", template_file.stem)
|
|
113
|
-
|
|
114
|
-
# Get metadata for display info
|
|
115
|
-
metadata = template_data.get("metadata", {})
|
|
116
|
-
metadata.get("name", agent_id)
|
|
117
|
-
description = metadata.get(
|
|
118
|
-
"description", "No description available"
|
|
119
|
-
)
|
|
120
|
-
|
|
121
|
-
# Extract capabilities/tools as dependencies for display
|
|
122
|
-
capabilities = template_data.get("capabilities", {})
|
|
123
|
-
tools = capabilities.get("tools", [])
|
|
124
|
-
# Ensure tools is a list before slicing
|
|
125
|
-
if not isinstance(tools, list):
|
|
126
|
-
tools = []
|
|
127
|
-
# Show first few tools as "dependencies" for UI purposes
|
|
128
|
-
display_tools = tools[:3] if len(tools) > 3 else tools
|
|
129
|
-
|
|
130
|
-
# Normalize agent ID (remove -agent suffix if present, replace underscores)
|
|
131
|
-
normalized_id = agent_id.replace("-agent", "").replace("_", "-")
|
|
132
|
-
|
|
133
|
-
agents.append(
|
|
134
|
-
AgentConfig(
|
|
135
|
-
name=normalized_id,
|
|
136
|
-
description=(
|
|
137
|
-
description[:80] + "..."
|
|
138
|
-
if len(description) > 80
|
|
139
|
-
else description
|
|
140
|
-
),
|
|
141
|
-
dependencies=display_tools,
|
|
142
|
-
)
|
|
143
|
-
)
|
|
144
|
-
|
|
145
|
-
except (json.JSONDecodeError, KeyError) as e:
|
|
146
|
-
# Log malformed templates but continue
|
|
147
|
-
self.logger.debug(
|
|
148
|
-
f"Skipping malformed template {template_file.name}: {e}"
|
|
149
|
-
)
|
|
150
|
-
continue
|
|
151
|
-
except Exception as e:
|
|
152
|
-
# Log unexpected errors but continue processing other templates
|
|
153
|
-
self.logger.debug(
|
|
154
|
-
f"Error processing template {template_file.name}: {e}"
|
|
155
|
-
)
|
|
156
|
-
continue
|
|
157
|
-
|
|
158
|
-
except Exception as e:
|
|
159
|
-
# If there's a catastrophic error reading templates directory
|
|
160
|
-
self.logger.error(f"Failed to read templates directory: {e}")
|
|
161
|
-
return [
|
|
162
|
-
AgentConfig("engineer", f"Error accessing templates: {e!s}", []),
|
|
163
|
-
AgentConfig("research", "Research agent", []),
|
|
164
|
-
]
|
|
165
|
-
|
|
166
|
-
# Sort agents by name for consistent display
|
|
167
|
-
agents.sort(key=lambda a: a.name)
|
|
168
|
-
|
|
169
|
-
return (
|
|
170
|
-
agents
|
|
171
|
-
if agents
|
|
172
|
-
else [
|
|
173
|
-
AgentConfig("engineer", "No agents found in templates", []),
|
|
174
|
-
]
|
|
175
|
-
)
|
|
26
|
+
from .agent_state_manager import SimpleAgentManager
|
|
27
|
+
from .configure_agent_display import AgentDisplay
|
|
28
|
+
from .configure_behavior_manager import BehaviorManager
|
|
29
|
+
from .configure_hook_manager import HookManager
|
|
30
|
+
from .configure_models import AgentConfig
|
|
31
|
+
from .configure_navigation import ConfigNavigation
|
|
32
|
+
from .configure_persistence import ConfigPersistence
|
|
33
|
+
from .configure_startup_manager import StartupManager
|
|
34
|
+
from .configure_template_editor import TemplateEditor
|
|
35
|
+
from .configure_validators import (
|
|
36
|
+
parse_id_selection,
|
|
37
|
+
validate_args as validate_configure_args,
|
|
38
|
+
)
|
|
176
39
|
|
|
177
40
|
|
|
178
41
|
class ConfigureCommand(BaseCommand):
|
|
@@ -185,25 +48,88 @@ class ConfigureCommand(BaseCommand):
|
|
|
185
48
|
self.current_scope = "project"
|
|
186
49
|
self.project_dir = Path.cwd()
|
|
187
50
|
self.agent_manager = None
|
|
51
|
+
self.hook_manager = HookManager(self.console)
|
|
52
|
+
self.behavior_manager = None # Initialized when scope is set
|
|
53
|
+
self._agent_display = None # Lazy-initialized
|
|
54
|
+
self._persistence = None # Lazy-initialized
|
|
55
|
+
self._navigation = None # Lazy-initialized
|
|
56
|
+
self._template_editor = None # Lazy-initialized
|
|
57
|
+
self._startup_manager = None # Lazy-initialized
|
|
188
58
|
|
|
189
59
|
def validate_args(self, args) -> Optional[str]:
|
|
190
60
|
"""Validate command arguments."""
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
61
|
+
return validate_configure_args(args)
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def agent_display(self) -> AgentDisplay:
|
|
65
|
+
"""Lazy-initialize agent display handler."""
|
|
66
|
+
if self._agent_display is None:
|
|
67
|
+
if self.agent_manager is None:
|
|
68
|
+
raise RuntimeError(
|
|
69
|
+
"agent_manager must be initialized before agent_display"
|
|
70
|
+
)
|
|
71
|
+
self._agent_display = AgentDisplay(
|
|
72
|
+
self.console,
|
|
73
|
+
self.agent_manager,
|
|
74
|
+
self._get_agent_template_path,
|
|
75
|
+
self._display_header,
|
|
76
|
+
)
|
|
77
|
+
return self._agent_display
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def persistence(self) -> ConfigPersistence:
|
|
81
|
+
"""Lazy-initialize persistence handler."""
|
|
82
|
+
if self._persistence is None:
|
|
83
|
+
# Note: agent_manager might be None for version_info calls
|
|
84
|
+
self._persistence = ConfigPersistence(
|
|
85
|
+
self.console,
|
|
86
|
+
self.version_service,
|
|
87
|
+
self.agent_manager, # Can be None for version operations
|
|
88
|
+
self._get_agent_template_path,
|
|
89
|
+
self._display_header,
|
|
90
|
+
self.current_scope,
|
|
91
|
+
self.project_dir,
|
|
92
|
+
)
|
|
93
|
+
return self._persistence
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def navigation(self) -> ConfigNavigation:
|
|
97
|
+
"""Lazy-initialize navigation handler."""
|
|
98
|
+
if self._navigation is None:
|
|
99
|
+
self._navigation = ConfigNavigation(self.console, self.project_dir)
|
|
100
|
+
# Sync scope from main command
|
|
101
|
+
self._navigation.current_scope = self.current_scope
|
|
102
|
+
return self._navigation
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def template_editor(self) -> TemplateEditor:
|
|
106
|
+
"""Lazy-initialize template editor."""
|
|
107
|
+
if self._template_editor is None:
|
|
108
|
+
if self.agent_manager is None:
|
|
109
|
+
raise RuntimeError(
|
|
110
|
+
"agent_manager must be initialized before template_editor"
|
|
111
|
+
)
|
|
112
|
+
self._template_editor = TemplateEditor(
|
|
113
|
+
self.console, self.agent_manager, self.current_scope, self.project_dir
|
|
114
|
+
)
|
|
115
|
+
return self._template_editor
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def startup_manager(self) -> StartupManager:
|
|
119
|
+
"""Lazy-initialize startup manager."""
|
|
120
|
+
if self._startup_manager is None:
|
|
121
|
+
if self.agent_manager is None:
|
|
122
|
+
raise RuntimeError(
|
|
123
|
+
"agent_manager must be initialized before startup_manager"
|
|
124
|
+
)
|
|
125
|
+
self._startup_manager = StartupManager(
|
|
126
|
+
self.agent_manager,
|
|
127
|
+
self.console,
|
|
128
|
+
self.current_scope,
|
|
129
|
+
self.project_dir,
|
|
130
|
+
self._display_header,
|
|
131
|
+
)
|
|
132
|
+
return self._startup_manager
|
|
207
133
|
|
|
208
134
|
def run(self, args) -> CommandResult:
|
|
209
135
|
"""Execute the configure command."""
|
|
@@ -212,12 +138,15 @@ class ConfigureCommand(BaseCommand):
|
|
|
212
138
|
if getattr(args, "project_dir", None):
|
|
213
139
|
self.project_dir = Path(args.project_dir)
|
|
214
140
|
|
|
215
|
-
# Initialize agent manager with appropriate config directory
|
|
141
|
+
# Initialize agent manager and behavior manager with appropriate config directory
|
|
216
142
|
if self.current_scope == "project":
|
|
217
143
|
config_dir = self.project_dir / ".claude-mpm"
|
|
218
144
|
else:
|
|
219
145
|
config_dir = Path.home() / ".claude-mpm"
|
|
220
146
|
self.agent_manager = SimpleAgentManager(config_dir)
|
|
147
|
+
self.behavior_manager = BehaviorManager(
|
|
148
|
+
config_dir, self.current_scope, self.console
|
|
149
|
+
)
|
|
221
150
|
|
|
222
151
|
# Disable colors if requested
|
|
223
152
|
if getattr(args, "no_colors", False):
|
|
@@ -282,20 +211,49 @@ class ConfigureCommand(BaseCommand):
|
|
|
282
211
|
if choice == "1":
|
|
283
212
|
self._manage_agents()
|
|
284
213
|
elif choice == "2":
|
|
285
|
-
self.
|
|
214
|
+
self._manage_skills()
|
|
286
215
|
elif choice == "3":
|
|
287
|
-
self.
|
|
216
|
+
self._edit_templates()
|
|
288
217
|
elif choice == "4":
|
|
218
|
+
self._manage_behaviors()
|
|
219
|
+
elif choice == "5":
|
|
289
220
|
# If user saves and wants to proceed to startup, exit the configurator
|
|
290
221
|
if self._manage_startup_configuration():
|
|
291
222
|
self.console.print(
|
|
292
223
|
"\n[green]Configuration saved. Exiting configurator...[/green]"
|
|
293
224
|
)
|
|
294
225
|
break
|
|
295
|
-
elif choice == "5":
|
|
296
|
-
self._switch_scope()
|
|
297
226
|
elif choice == "6":
|
|
227
|
+
self._switch_scope()
|
|
228
|
+
elif choice == "7":
|
|
298
229
|
self._show_version_info_interactive()
|
|
230
|
+
elif choice == "l":
|
|
231
|
+
# Check for pending agent changes
|
|
232
|
+
if self.agent_manager and self.agent_manager.has_pending_changes():
|
|
233
|
+
should_save = Confirm.ask(
|
|
234
|
+
"[yellow]You have unsaved agent changes. Save them before launching?[/yellow]",
|
|
235
|
+
default=True,
|
|
236
|
+
)
|
|
237
|
+
if should_save:
|
|
238
|
+
self.agent_manager.commit_deferred_changes()
|
|
239
|
+
self.console.print("[green]✓ Agent changes saved[/green]")
|
|
240
|
+
else:
|
|
241
|
+
self.agent_manager.discard_deferred_changes()
|
|
242
|
+
self.console.print(
|
|
243
|
+
"[yellow]⚠ Agent changes discarded[/yellow]"
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
# Save all configuration
|
|
247
|
+
self.console.print("\n[cyan]Saving configuration...[/cyan]")
|
|
248
|
+
if self._save_all_configuration():
|
|
249
|
+
# Launch Claude MPM (this will replace the process if successful)
|
|
250
|
+
self._launch_claude_mpm()
|
|
251
|
+
# If execvp fails, we'll return here and break
|
|
252
|
+
break
|
|
253
|
+
self.console.print(
|
|
254
|
+
"[red]✗ Failed to save configuration. Not launching.[/red]"
|
|
255
|
+
)
|
|
256
|
+
Prompt.ask("\nPress Enter to continue")
|
|
299
257
|
elif choice == "q":
|
|
300
258
|
self.console.print(
|
|
301
259
|
"\n[green]Configuration complete. Goodbye![/green]"
|
|
@@ -315,66 +273,15 @@ class ConfigureCommand(BaseCommand):
|
|
|
315
273
|
|
|
316
274
|
def _display_header(self) -> None:
|
|
317
275
|
"""Display the TUI header."""
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
from claude_mpm import __version__
|
|
322
|
-
|
|
323
|
-
# Create header panel
|
|
324
|
-
header_text = Text()
|
|
325
|
-
header_text.append("Claude MPM ", style="bold cyan")
|
|
326
|
-
header_text.append("Configuration Interface", style="bold white")
|
|
327
|
-
header_text.append(f"\nv{__version__}", style="dim cyan")
|
|
328
|
-
|
|
329
|
-
scope_text = Text(f"Scope: {self.current_scope.upper()}", style="yellow")
|
|
330
|
-
dir_text = Text(f"Directory: {self.project_dir}", style="dim")
|
|
331
|
-
|
|
332
|
-
header_content = Columns([header_text], align="center")
|
|
333
|
-
subtitle_content = f"{scope_text} | {dir_text}"
|
|
334
|
-
|
|
335
|
-
header_panel = Panel(
|
|
336
|
-
header_content,
|
|
337
|
-
subtitle=subtitle_content,
|
|
338
|
-
box=ROUNDED,
|
|
339
|
-
style="blue",
|
|
340
|
-
padding=(1, 2),
|
|
341
|
-
)
|
|
342
|
-
|
|
343
|
-
self.console.print(header_panel)
|
|
344
|
-
self.console.print()
|
|
276
|
+
# Sync scope to navigation before display
|
|
277
|
+
self.navigation.current_scope = self.current_scope
|
|
278
|
+
self.navigation.display_header()
|
|
345
279
|
|
|
346
280
|
def _show_main_menu(self) -> str:
|
|
347
281
|
"""Show the main menu and get user choice."""
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
("3", "Behavior Files", "Manage identity and workflow configurations"),
|
|
352
|
-
(
|
|
353
|
-
"4",
|
|
354
|
-
"Startup Configuration",
|
|
355
|
-
"Configure MCP services and agents to start",
|
|
356
|
-
),
|
|
357
|
-
("5", "Switch Scope", f"Current: {self.current_scope}"),
|
|
358
|
-
("6", "Version Info", "Display MPM and Claude versions"),
|
|
359
|
-
("q", "Quit", "Exit configuration interface"),
|
|
360
|
-
]
|
|
361
|
-
|
|
362
|
-
table = Table(show_header=False, box=None, padding=(0, 2))
|
|
363
|
-
table.add_column("Key", style="cyan", width=3)
|
|
364
|
-
table.add_column("Option", style="bold white", width=20)
|
|
365
|
-
table.add_column("Description", style="dim")
|
|
366
|
-
|
|
367
|
-
for key, option, desc in menu_items:
|
|
368
|
-
table.add_row(f"[{key}]", option, desc)
|
|
369
|
-
|
|
370
|
-
menu_panel = Panel(
|
|
371
|
-
table, title="[bold]Main Menu[/bold]", box=ROUNDED, style="green"
|
|
372
|
-
)
|
|
373
|
-
|
|
374
|
-
self.console.print(menu_panel)
|
|
375
|
-
self.console.print()
|
|
376
|
-
|
|
377
|
-
return Prompt.ask("[bold cyan]Select an option[/bold cyan]", default="q")
|
|
282
|
+
# Sync scope to navigation before display
|
|
283
|
+
self.navigation.current_scope = self.current_scope
|
|
284
|
+
return self.navigation.show_main_menu()
|
|
378
285
|
|
|
379
286
|
def _manage_agents(self) -> None:
|
|
380
287
|
"""Agent management interface."""
|
|
@@ -388,22 +295,41 @@ class ConfigureCommand(BaseCommand):
|
|
|
388
295
|
|
|
389
296
|
# Show agent menu
|
|
390
297
|
self.console.print("\n[bold]Agent Management Options:[/bold]")
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
self.console.print(
|
|
298
|
+
|
|
299
|
+
# Use Text objects to properly display shortcuts with styling
|
|
300
|
+
text_t = Text(" ")
|
|
301
|
+
text_t.append("[t]", style="bold blue")
|
|
302
|
+
text_t.append(" Toggle agents (enable/disable multiple)")
|
|
303
|
+
self.console.print(text_t)
|
|
304
|
+
|
|
305
|
+
text_c = Text(" ")
|
|
306
|
+
text_c.append("[c]", style="bold blue")
|
|
307
|
+
text_c.append(" Customize agent template")
|
|
308
|
+
self.console.print(text_c)
|
|
309
|
+
|
|
310
|
+
text_v = Text(" ")
|
|
311
|
+
text_v.append("[v]", style="bold blue")
|
|
312
|
+
text_v.append(" View agent details")
|
|
313
|
+
self.console.print(text_v)
|
|
314
|
+
|
|
315
|
+
text_r = Text(" ")
|
|
316
|
+
text_r.append("[r]", style="bold blue")
|
|
317
|
+
text_r.append(" Reset agent to defaults")
|
|
318
|
+
self.console.print(text_r)
|
|
319
|
+
|
|
320
|
+
text_b = Text(" ")
|
|
321
|
+
text_b.append("[b]", style="bold blue")
|
|
322
|
+
text_b.append(" Back to main menu")
|
|
323
|
+
self.console.print(text_b)
|
|
324
|
+
|
|
397
325
|
self.console.print()
|
|
398
326
|
|
|
399
|
-
choice = Prompt.ask("[bold
|
|
327
|
+
choice = Prompt.ask("[bold blue]Select an option[/bold blue]", default="b")
|
|
400
328
|
|
|
401
329
|
if choice == "b":
|
|
402
330
|
break
|
|
403
|
-
if choice == "
|
|
404
|
-
self.
|
|
405
|
-
elif choice == "d":
|
|
406
|
-
self._disable_agent_interactive(agents)
|
|
331
|
+
if choice == "t":
|
|
332
|
+
self._toggle_agents_interactive(agents)
|
|
407
333
|
elif choice == "c":
|
|
408
334
|
self._customize_agent_template(agents)
|
|
409
335
|
elif choice == "v":
|
|
@@ -416,1210 +342,366 @@ class ConfigureCommand(BaseCommand):
|
|
|
416
342
|
|
|
417
343
|
def _display_agents_table(self, agents: List[AgentConfig]) -> None:
|
|
418
344
|
"""Display a table of available agents."""
|
|
419
|
-
|
|
420
|
-
title=f"Available Agents ({len(agents)} total)",
|
|
421
|
-
box=ROUNDED,
|
|
422
|
-
show_lines=True,
|
|
423
|
-
)
|
|
345
|
+
self.agent_display.display_agents_table(agents)
|
|
424
346
|
|
|
425
|
-
|
|
426
|
-
table.
|
|
427
|
-
|
|
428
|
-
table.add_column("Description", style="white", width=45)
|
|
429
|
-
table.add_column("Model/Tools", style="dim", width=20)
|
|
430
|
-
|
|
431
|
-
for idx, agent in enumerate(agents, 1):
|
|
432
|
-
# Check if agent is enabled
|
|
433
|
-
is_enabled = self.agent_manager.is_agent_enabled(agent.name)
|
|
434
|
-
status = (
|
|
435
|
-
"[green]✓ Enabled[/green]" if is_enabled else "[red]✗ Disabled[/red]"
|
|
436
|
-
)
|
|
347
|
+
def _display_agents_with_pending_states(self, agents: List[AgentConfig]) -> None:
|
|
348
|
+
"""Display agents table with pending state indicators."""
|
|
349
|
+
self.agent_display.display_agents_with_pending_states(agents)
|
|
437
350
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
if agent.dependencies:
|
|
441
|
-
if len(agent.dependencies) > 2:
|
|
442
|
-
tools_display = f"{', '.join(agent.dependencies[:2])}..."
|
|
443
|
-
else:
|
|
444
|
-
tools_display = ", ".join(agent.dependencies)
|
|
445
|
-
else:
|
|
446
|
-
# Try to get model from template
|
|
447
|
-
try:
|
|
448
|
-
template_path = self._get_agent_template_path(agent.name)
|
|
449
|
-
if template_path.exists():
|
|
450
|
-
with template_path.open() as f:
|
|
451
|
-
template = json.load(f)
|
|
452
|
-
model = template.get("capabilities", {}).get("model", "default")
|
|
453
|
-
tools_display = f"Model: {model}"
|
|
454
|
-
else:
|
|
455
|
-
tools_display = "Default"
|
|
456
|
-
except Exception:
|
|
457
|
-
tools_display = "Default"
|
|
458
|
-
|
|
459
|
-
# Truncate description for table display
|
|
460
|
-
desc_display = (
|
|
461
|
-
agent.description[:42] + "..."
|
|
462
|
-
if len(agent.description) > 42
|
|
463
|
-
else agent.description
|
|
464
|
-
)
|
|
351
|
+
def _toggle_agents_interactive(self, agents: List[AgentConfig]) -> None:
|
|
352
|
+
"""Interactive multi-agent enable/disable with batch save."""
|
|
465
353
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
354
|
+
# Initialize pending states from current states
|
|
355
|
+
for agent in agents:
|
|
356
|
+
current_state = self.agent_manager.is_agent_enabled(agent.name)
|
|
357
|
+
self.agent_manager.set_agent_enabled_deferred(agent.name, current_state)
|
|
469
358
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
359
|
+
while True:
|
|
360
|
+
# Display table with pending states
|
|
361
|
+
self._display_agents_with_pending_states(agents)
|
|
362
|
+
|
|
363
|
+
# Show menu
|
|
364
|
+
self.console.print("\n[bold]Toggle Agent Status:[/bold]")
|
|
365
|
+
text_toggle = Text(" ")
|
|
366
|
+
text_toggle.append("[t]", style="bold blue")
|
|
367
|
+
text_toggle.append(" Enter agent IDs to toggle (e.g., '1,3,5' or '1-4')")
|
|
368
|
+
self.console.print(text_toggle)
|
|
369
|
+
|
|
370
|
+
text_all = Text(" ")
|
|
371
|
+
text_all.append("[a]", style="bold blue")
|
|
372
|
+
text_all.append(" Enable all agents")
|
|
373
|
+
self.console.print(text_all)
|
|
374
|
+
|
|
375
|
+
text_none = Text(" ")
|
|
376
|
+
text_none.append("[n]", style="bold blue")
|
|
377
|
+
text_none.append(" Disable all agents")
|
|
378
|
+
self.console.print(text_none)
|
|
379
|
+
|
|
380
|
+
text_save = Text(" ")
|
|
381
|
+
text_save.append("[s]", style="bold green")
|
|
382
|
+
text_save.append(" Save changes and return")
|
|
383
|
+
self.console.print(text_save)
|
|
384
|
+
|
|
385
|
+
text_cancel = Text(" ")
|
|
386
|
+
text_cancel.append("[c]", style="bold magenta")
|
|
387
|
+
text_cancel.append(" Cancel (discard changes)")
|
|
388
|
+
self.console.print(text_cancel)
|
|
389
|
+
|
|
390
|
+
choice = (
|
|
391
|
+
Prompt.ask("[bold blue]Select an option[/bold blue]", default="s")
|
|
392
|
+
.strip()
|
|
393
|
+
.lower()
|
|
394
|
+
)
|
|
473
395
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
self.
|
|
478
|
-
self.console.print("[green]All agents enabled successfully![/green]")
|
|
479
|
-
else:
|
|
480
|
-
try:
|
|
481
|
-
idx = int(agent_id) - 1
|
|
482
|
-
if 0 <= idx < len(agents):
|
|
483
|
-
agent = agents[idx]
|
|
484
|
-
self.agent_manager.set_agent_enabled(agent.name, True)
|
|
485
|
-
self.console.print(
|
|
486
|
-
f"[green]Agent '{agent.name}' enabled successfully![/green]"
|
|
487
|
-
)
|
|
396
|
+
if choice == "s":
|
|
397
|
+
if self.agent_manager.has_pending_changes():
|
|
398
|
+
self.agent_manager.commit_deferred_changes()
|
|
399
|
+
self.console.print("[green]✓ Changes saved successfully![/green]")
|
|
488
400
|
else:
|
|
489
|
-
self.console.print("[
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
if agent_id.lower() == "all":
|
|
500
|
-
if Confirm.ask("[yellow]Disable ALL agents?[/yellow]"):
|
|
401
|
+
self.console.print("[yellow]No changes to save.[/yellow]")
|
|
402
|
+
Prompt.ask("Press Enter to continue")
|
|
403
|
+
break
|
|
404
|
+
if choice == "c":
|
|
405
|
+
self.agent_manager.discard_deferred_changes()
|
|
406
|
+
self.console.print("[yellow]Changes discarded.[/yellow]")
|
|
407
|
+
Prompt.ask("Press Enter to continue")
|
|
408
|
+
break
|
|
409
|
+
if choice == "a":
|
|
501
410
|
for agent in agents:
|
|
502
|
-
self.agent_manager.
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
Prompt.ask("Press Enter to continue")
|
|
411
|
+
self.agent_manager.set_agent_enabled_deferred(agent.name, True)
|
|
412
|
+
elif choice == "n":
|
|
413
|
+
for agent in agents:
|
|
414
|
+
self.agent_manager.set_agent_enabled_deferred(agent.name, False)
|
|
415
|
+
elif choice == "t" or choice.replace(",", "").replace("-", "").isdigit():
|
|
416
|
+
selected_ids = self._parse_id_selection(
|
|
417
|
+
choice if choice != "t" else Prompt.ask("Enter IDs"), len(agents)
|
|
418
|
+
)
|
|
419
|
+
for idx in selected_ids:
|
|
420
|
+
if 1 <= idx <= len(agents):
|
|
421
|
+
agent = agents[idx - 1]
|
|
422
|
+
current = self.agent_manager.get_pending_state(agent.name)
|
|
423
|
+
self.agent_manager.set_agent_enabled_deferred(
|
|
424
|
+
agent.name, not current
|
|
425
|
+
)
|
|
519
426
|
|
|
520
427
|
def _customize_agent_template(self, agents: List[AgentConfig]) -> None:
|
|
521
428
|
"""Customize agent JSON template."""
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
try:
|
|
525
|
-
idx = int(agent_id) - 1
|
|
526
|
-
if 0 <= idx < len(agents):
|
|
527
|
-
agent = agents[idx]
|
|
528
|
-
self._edit_agent_template(agent)
|
|
529
|
-
else:
|
|
530
|
-
self.console.print("[red]Invalid agent ID.[/red]")
|
|
531
|
-
Prompt.ask("Press Enter to continue")
|
|
532
|
-
except ValueError:
|
|
533
|
-
self.console.print("[red]Invalid input. Please enter a number.[/red]")
|
|
534
|
-
Prompt.ask("Press Enter to continue")
|
|
429
|
+
self.template_editor.customize_agent_template(agents)
|
|
535
430
|
|
|
536
431
|
def _edit_agent_template(self, agent: AgentConfig) -> None:
|
|
537
432
|
"""Edit an agent's JSON template."""
|
|
538
|
-
self.
|
|
539
|
-
self.console.print(f"[bold]Editing template for: {agent.name}[/bold]\n")
|
|
540
|
-
|
|
541
|
-
# Get current template
|
|
542
|
-
template_path = self._get_agent_template_path(agent.name)
|
|
543
|
-
|
|
544
|
-
if template_path.exists():
|
|
545
|
-
with template_path.open() as f:
|
|
546
|
-
template = json.load(f)
|
|
547
|
-
is_system = str(template_path).startswith(
|
|
548
|
-
str(self.agent_manager.templates_dir)
|
|
549
|
-
)
|
|
550
|
-
else:
|
|
551
|
-
# Create a minimal template structure based on system templates
|
|
552
|
-
template = {
|
|
553
|
-
"schema_version": "1.2.0",
|
|
554
|
-
"agent_id": agent.name,
|
|
555
|
-
"agent_version": "1.0.0",
|
|
556
|
-
"agent_type": agent.name.replace("-", "_"),
|
|
557
|
-
"metadata": {
|
|
558
|
-
"name": agent.name.replace("-", " ").title() + " Agent",
|
|
559
|
-
"description": agent.description,
|
|
560
|
-
"tags": [agent.name],
|
|
561
|
-
"author": "Custom",
|
|
562
|
-
"created_at": "",
|
|
563
|
-
"updated_at": "",
|
|
564
|
-
},
|
|
565
|
-
"capabilities": {
|
|
566
|
-
"model": "opus",
|
|
567
|
-
"tools": (
|
|
568
|
-
agent.dependencies
|
|
569
|
-
if agent.dependencies
|
|
570
|
-
else ["Read", "Write", "Edit", "Bash"]
|
|
571
|
-
),
|
|
572
|
-
},
|
|
573
|
-
"instructions": {
|
|
574
|
-
"base_template": "BASE_AGENT_TEMPLATE.md",
|
|
575
|
-
"custom_instructions": "",
|
|
576
|
-
},
|
|
577
|
-
}
|
|
578
|
-
is_system = False
|
|
579
|
-
|
|
580
|
-
# Display current template
|
|
581
|
-
if is_system:
|
|
582
|
-
self.console.print(
|
|
583
|
-
"[yellow]Viewing SYSTEM template (read-only). Customization will create a local copy.[/yellow]\n"
|
|
584
|
-
)
|
|
585
|
-
|
|
586
|
-
self.console.print("[bold]Current Template:[/bold]")
|
|
587
|
-
# Truncate for display if too large
|
|
588
|
-
display_template = template.copy()
|
|
589
|
-
if (
|
|
590
|
-
"instructions" in display_template
|
|
591
|
-
and isinstance(display_template["instructions"], dict)
|
|
592
|
-
and (
|
|
593
|
-
"custom_instructions" in display_template["instructions"]
|
|
594
|
-
and len(str(display_template["instructions"]["custom_instructions"]))
|
|
595
|
-
> 200
|
|
596
|
-
)
|
|
597
|
-
):
|
|
598
|
-
display_template["instructions"]["custom_instructions"] = (
|
|
599
|
-
display_template["instructions"]["custom_instructions"][:200] + "..."
|
|
600
|
-
)
|
|
601
|
-
|
|
602
|
-
json_str = json.dumps(display_template, indent=2)
|
|
603
|
-
# Limit display to first 50 lines for readability
|
|
604
|
-
lines = json_str.split("\n")
|
|
605
|
-
if len(lines) > 50:
|
|
606
|
-
json_str = "\n".join(lines[:50]) + "\n... (truncated for display)"
|
|
607
|
-
|
|
608
|
-
syntax = Syntax(json_str, "json", theme="monokai", line_numbers=True)
|
|
609
|
-
self.console.print(syntax)
|
|
610
|
-
self.console.print()
|
|
611
|
-
|
|
612
|
-
# Editing options
|
|
613
|
-
self.console.print("[bold]Editing Options:[/bold]")
|
|
614
|
-
if not is_system:
|
|
615
|
-
self.console.print(" [cyan][1][/cyan] Edit in external editor")
|
|
616
|
-
self.console.print(" [cyan][2][/cyan] Add/modify a field")
|
|
617
|
-
self.console.print(" [cyan][3][/cyan] Remove a field")
|
|
618
|
-
self.console.print(" [cyan][4][/cyan] Reset to defaults")
|
|
619
|
-
else:
|
|
620
|
-
self.console.print(" [cyan][1][/cyan] Create customized copy")
|
|
621
|
-
self.console.print(" [cyan][2][/cyan] View full template")
|
|
622
|
-
self.console.print(" [cyan][b][/cyan] Back")
|
|
623
|
-
self.console.print()
|
|
624
|
-
|
|
625
|
-
choice = Prompt.ask("[bold cyan]Select an option[/bold cyan]", default="b")
|
|
626
|
-
|
|
627
|
-
if is_system:
|
|
628
|
-
if choice == "1":
|
|
629
|
-
# Create a customized copy
|
|
630
|
-
self._create_custom_template_copy(agent, template)
|
|
631
|
-
elif choice == "2":
|
|
632
|
-
# View full template
|
|
633
|
-
self._view_full_template(template)
|
|
634
|
-
elif choice == "1":
|
|
635
|
-
self._edit_in_external_editor(template_path, template)
|
|
636
|
-
elif choice == "2":
|
|
637
|
-
self._modify_template_field(template, template_path)
|
|
638
|
-
elif choice == "3":
|
|
639
|
-
self._remove_template_field(template, template_path)
|
|
640
|
-
elif choice == "4":
|
|
641
|
-
self._reset_template(agent, template_path)
|
|
642
|
-
|
|
643
|
-
if choice != "b":
|
|
644
|
-
Prompt.ask("Press Enter to continue")
|
|
433
|
+
self.template_editor.edit_agent_template(agent)
|
|
645
434
|
|
|
646
435
|
def _get_agent_template_path(self, agent_name: str) -> Path:
|
|
647
436
|
"""Get the path to an agent's template file."""
|
|
648
|
-
|
|
649
|
-
if self.current_scope == "project":
|
|
650
|
-
config_dir = self.project_dir / ".claude-mpm" / "agents"
|
|
651
|
-
else:
|
|
652
|
-
config_dir = Path.home() / ".claude-mpm" / "agents"
|
|
653
|
-
|
|
654
|
-
config_dir.mkdir(parents=True, exist_ok=True)
|
|
655
|
-
custom_template = config_dir / f"{agent_name}.json"
|
|
656
|
-
|
|
657
|
-
# If custom template exists, return it
|
|
658
|
-
if custom_template.exists():
|
|
659
|
-
return custom_template
|
|
660
|
-
|
|
661
|
-
# Otherwise, look for the system template
|
|
662
|
-
# Handle various naming conventions
|
|
663
|
-
possible_names = [
|
|
664
|
-
f"{agent_name}.json",
|
|
665
|
-
f"{agent_name.replace('-', '_')}.json",
|
|
666
|
-
f"{agent_name}-agent.json",
|
|
667
|
-
f"{agent_name.replace('-', '_')}_agent.json",
|
|
668
|
-
]
|
|
669
|
-
|
|
670
|
-
for name in possible_names:
|
|
671
|
-
system_template = self.agent_manager.templates_dir / name
|
|
672
|
-
if system_template.exists():
|
|
673
|
-
return system_template
|
|
674
|
-
|
|
675
|
-
# Return the custom template path for new templates
|
|
676
|
-
return custom_template
|
|
437
|
+
return self.template_editor.get_agent_template_path(agent_name)
|
|
677
438
|
|
|
678
439
|
def _edit_in_external_editor(self, template_path: Path, template: Dict) -> None:
|
|
679
440
|
"""Open template in external editor."""
|
|
680
|
-
|
|
681
|
-
import tempfile
|
|
682
|
-
|
|
683
|
-
# Write current template to temp file
|
|
684
|
-
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
|
|
685
|
-
json.dump(template, f, indent=2)
|
|
686
|
-
temp_path = f.name
|
|
687
|
-
|
|
688
|
-
# Get editor from environment
|
|
689
|
-
editor = os.environ.get("EDITOR", "nano")
|
|
690
|
-
|
|
691
|
-
try:
|
|
692
|
-
# Open in editor
|
|
693
|
-
subprocess.call([editor, temp_path])
|
|
694
|
-
|
|
695
|
-
# Read back the edited content
|
|
696
|
-
with temp_path.open() as f:
|
|
697
|
-
new_template = json.load(f)
|
|
698
|
-
|
|
699
|
-
# Save to actual template path
|
|
700
|
-
with template_path.open("w") as f:
|
|
701
|
-
json.dump(new_template, f, indent=2)
|
|
702
|
-
|
|
703
|
-
self.console.print("[green]Template updated successfully![/green]")
|
|
704
|
-
|
|
705
|
-
except Exception as e:
|
|
706
|
-
self.console.print(f"[red]Error editing template: {e}[/red]")
|
|
707
|
-
finally:
|
|
708
|
-
# Clean up temp file
|
|
709
|
-
Path(temp_path).unlink(missing_ok=True)
|
|
441
|
+
self.template_editor.edit_in_external_editor(template_path, template)
|
|
710
442
|
|
|
711
443
|
def _modify_template_field(self, template: Dict, template_path: Path) -> None:
|
|
712
444
|
"""Add or modify a field in the template."""
|
|
713
|
-
|
|
714
|
-
"Enter field name (use dot notation for nested, e.g., 'config.timeout')"
|
|
715
|
-
)
|
|
716
|
-
field_value = Prompt.ask("Enter field value (JSON format)")
|
|
717
|
-
|
|
718
|
-
try:
|
|
719
|
-
# Parse the value as JSON
|
|
720
|
-
value = json.loads(field_value)
|
|
721
|
-
|
|
722
|
-
# Navigate to the field location
|
|
723
|
-
parts = field_name.split(".")
|
|
724
|
-
current = template
|
|
725
|
-
|
|
726
|
-
for part in parts[:-1]:
|
|
727
|
-
if part not in current:
|
|
728
|
-
current[part] = {}
|
|
729
|
-
current = current[part]
|
|
730
|
-
|
|
731
|
-
# Set the value
|
|
732
|
-
current[parts[-1]] = value
|
|
733
|
-
|
|
734
|
-
# Save the template
|
|
735
|
-
with template_path.open("w") as f:
|
|
736
|
-
json.dump(template, f, indent=2)
|
|
737
|
-
|
|
738
|
-
self.console.print(
|
|
739
|
-
f"[green]Field '{field_name}' updated successfully![/green]"
|
|
740
|
-
)
|
|
741
|
-
|
|
742
|
-
except json.JSONDecodeError:
|
|
743
|
-
self.console.print("[red]Invalid JSON value. Please try again.[/red]")
|
|
744
|
-
except Exception as e:
|
|
745
|
-
self.console.print(f"[red]Error updating field: {e}[/red]")
|
|
445
|
+
self.template_editor.modify_template_field(template, template_path)
|
|
746
446
|
|
|
747
447
|
def _remove_template_field(self, template: Dict, template_path: Path) -> None:
|
|
748
448
|
"""Remove a field from the template."""
|
|
749
|
-
|
|
750
|
-
"Enter field name to remove (use dot notation for nested)"
|
|
751
|
-
)
|
|
752
|
-
|
|
753
|
-
try:
|
|
754
|
-
# Navigate to the field location
|
|
755
|
-
parts = field_name.split(".")
|
|
756
|
-
current = template
|
|
757
|
-
|
|
758
|
-
for part in parts[:-1]:
|
|
759
|
-
if part not in current:
|
|
760
|
-
raise KeyError(f"Field '{field_name}' not found")
|
|
761
|
-
current = current[part]
|
|
762
|
-
|
|
763
|
-
# Remove the field
|
|
764
|
-
if parts[-1] in current:
|
|
765
|
-
del current[parts[-1]]
|
|
766
|
-
|
|
767
|
-
# Save the template
|
|
768
|
-
with template_path.open("w") as f:
|
|
769
|
-
json.dump(template, f, indent=2)
|
|
770
|
-
|
|
771
|
-
self.console.print(
|
|
772
|
-
f"[green]Field '{field_name}' removed successfully![/green]"
|
|
773
|
-
)
|
|
774
|
-
else:
|
|
775
|
-
self.console.print(f"[red]Field '{field_name}' not found.[/red]")
|
|
776
|
-
|
|
777
|
-
except Exception as e:
|
|
778
|
-
self.console.print(f"[red]Error removing field: {e}[/red]")
|
|
449
|
+
self.template_editor.remove_template_field(template, template_path)
|
|
779
450
|
|
|
780
451
|
def _reset_template(self, agent: AgentConfig, template_path: Path) -> None:
|
|
781
452
|
"""Reset template to defaults."""
|
|
782
|
-
|
|
783
|
-
# Remove custom template file
|
|
784
|
-
template_path.unlink(missing_ok=True)
|
|
785
|
-
self.console.print(
|
|
786
|
-
f"[green]Template for '{agent.name}' reset to defaults![/green]"
|
|
787
|
-
)
|
|
453
|
+
self.template_editor.reset_template(agent, template_path)
|
|
788
454
|
|
|
789
455
|
def _create_custom_template_copy(self, agent: AgentConfig, template: Dict) -> None:
|
|
790
456
|
"""Create a customized copy of a system template."""
|
|
791
|
-
|
|
792
|
-
config_dir = self.project_dir / ".claude-mpm" / "agents"
|
|
793
|
-
else:
|
|
794
|
-
config_dir = Path.home() / ".claude-mpm" / "agents"
|
|
457
|
+
self.template_editor.create_custom_template_copy(agent, template)
|
|
795
458
|
|
|
796
|
-
|
|
797
|
-
|
|
459
|
+
def _view_full_template(self, template: Dict) -> None:
|
|
460
|
+
"""View the full template without truncation."""
|
|
461
|
+
self.template_editor.view_full_template(template)
|
|
798
462
|
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
)
|
|
802
|
-
return
|
|
463
|
+
def _reset_agent_defaults(self, agents: List[AgentConfig]) -> None:
|
|
464
|
+
"""Reset an agent to default enabled state and remove custom template."""
|
|
465
|
+
self.template_editor.reset_agent_defaults(agents)
|
|
803
466
|
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
467
|
+
def _edit_templates(self) -> None:
|
|
468
|
+
"""Template editing interface."""
|
|
469
|
+
self.template_editor.edit_templates_interface()
|
|
807
470
|
|
|
808
|
-
|
|
809
|
-
|
|
471
|
+
def _manage_behaviors(self) -> None:
|
|
472
|
+
"""Behavior file management interface."""
|
|
473
|
+
# Note: BehaviorManager handles its own loop and clears screen
|
|
474
|
+
# but doesn't display our header. We'll need to update BehaviorManager
|
|
475
|
+
# to accept a header callback in the future. For now, just delegate.
|
|
476
|
+
self.behavior_manager.manage_behaviors()
|
|
810
477
|
|
|
811
|
-
def
|
|
812
|
-
"""
|
|
813
|
-
|
|
814
|
-
|
|
478
|
+
def _manage_skills(self) -> None:
|
|
479
|
+
"""Skills management interface."""
|
|
480
|
+
from ...cli.interactive.skills_wizard import SkillsWizard
|
|
481
|
+
from ...skills.skill_manager import get_manager
|
|
815
482
|
|
|
816
|
-
|
|
817
|
-
|
|
483
|
+
wizard = SkillsWizard()
|
|
484
|
+
manager = get_manager()
|
|
818
485
|
|
|
819
|
-
|
|
486
|
+
while True:
|
|
487
|
+
self.console.clear()
|
|
488
|
+
self._display_header()
|
|
820
489
|
|
|
821
|
-
|
|
822
|
-
self.console.print(
|
|
490
|
+
self.console.print("\n[bold]Skills Management Options:[/bold]\n")
|
|
491
|
+
self.console.print(" [1] View Available Skills")
|
|
492
|
+
self.console.print(" [2] Configure Skills for Agents")
|
|
493
|
+
self.console.print(" [3] View Current Skill Mappings")
|
|
494
|
+
self.console.print(" [4] Auto-Link Skills to Agents")
|
|
495
|
+
self.console.print(" [b] Back to Main Menu")
|
|
496
|
+
self.console.print()
|
|
823
497
|
|
|
824
|
-
|
|
825
|
-
"""View detailed information about an agent."""
|
|
826
|
-
agent_id = Prompt.ask("Enter agent ID to view")
|
|
498
|
+
choice = Prompt.ask("[bold blue]Select an option[/bold blue]", default="b")
|
|
827
499
|
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
500
|
+
if choice == "1":
|
|
501
|
+
# View available skills
|
|
502
|
+
self.console.clear()
|
|
503
|
+
self._display_header()
|
|
504
|
+
wizard.list_available_skills()
|
|
505
|
+
Prompt.ask("\nPress Enter to continue")
|
|
832
506
|
|
|
507
|
+
elif choice == "2":
|
|
508
|
+
# Configure skills interactively
|
|
833
509
|
self.console.clear()
|
|
834
510
|
self._display_header()
|
|
835
511
|
|
|
836
|
-
#
|
|
837
|
-
|
|
838
|
-
|
|
512
|
+
# Get list of enabled agents
|
|
513
|
+
agents = self.agent_manager.discover_agents()
|
|
514
|
+
enabled_agents = [
|
|
515
|
+
a.name
|
|
516
|
+
for a in agents
|
|
517
|
+
if self.agent_manager.get_pending_state(a.name)
|
|
518
|
+
]
|
|
839
519
|
|
|
840
|
-
if
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
520
|
+
if not enabled_agents:
|
|
521
|
+
self.console.print(
|
|
522
|
+
"[yellow]No agents are currently enabled.[/yellow]"
|
|
523
|
+
)
|
|
524
|
+
self.console.print(
|
|
525
|
+
"Please enable agents first in Agent Management."
|
|
526
|
+
)
|
|
527
|
+
Prompt.ask("\nPress Enter to continue")
|
|
528
|
+
continue
|
|
529
|
+
|
|
530
|
+
# Run skills wizard
|
|
531
|
+
success, mapping = wizard.run_interactive_selection(enabled_agents)
|
|
844
532
|
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
533
|
+
if success:
|
|
534
|
+
# Save the configuration
|
|
535
|
+
manager.save_mappings_to_config()
|
|
536
|
+
self.console.print("\n[green]✓ Skills configuration saved![/green]")
|
|
537
|
+
else:
|
|
538
|
+
self.console.print(
|
|
539
|
+
"\n[yellow]Skills configuration cancelled.[/yellow]"
|
|
540
|
+
)
|
|
848
541
|
|
|
849
|
-
|
|
850
|
-
full_desc = metadata.get("description", agent.description)
|
|
542
|
+
Prompt.ask("\nPress Enter to continue")
|
|
851
543
|
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
544
|
+
elif choice == "3":
|
|
545
|
+
# View current mappings
|
|
546
|
+
self.console.clear()
|
|
547
|
+
self._display_header()
|
|
855
548
|
|
|
856
|
-
|
|
857
|
-
tags = metadata.get("tags", [])
|
|
549
|
+
self.console.print("\n[bold]Current Skill Mappings:[/bold]\n")
|
|
858
550
|
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
551
|
+
mappings = manager.list_agent_skill_mappings()
|
|
552
|
+
if not mappings:
|
|
553
|
+
self.console.print("[dim]No skill mappings configured yet.[/dim]")
|
|
554
|
+
else:
|
|
555
|
+
from rich.table import Table
|
|
862
556
|
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
557
|
+
table = Table(show_header=True, header_style="bold cyan")
|
|
558
|
+
table.add_column("Agent", style="yellow")
|
|
559
|
+
table.add_column("Skills", style="green")
|
|
866
560
|
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
[
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
"""
|
|
873
|
-
except Exception:
|
|
874
|
-
pass
|
|
875
|
-
|
|
876
|
-
# Create detail panel
|
|
877
|
-
detail_text = f"""
|
|
878
|
-
[bold]Name:[/bold] {agent.name}
|
|
879
|
-
[bold]Status:[/bold] {'[green]Enabled[/green]' if self.agent_manager.is_agent_enabled(agent.name) else '[red]Disabled[/red]'}
|
|
880
|
-
[bold]Template Path:[/bold] {template_path}
|
|
881
|
-
[bold]Is System Template:[/bold] {'Yes' if str(template_path).startswith(str(self.agent_manager.templates_dir)) else 'No (Custom)'}
|
|
882
|
-
{extra_info}
|
|
883
|
-
"""
|
|
884
|
-
|
|
885
|
-
panel = Panel(
|
|
886
|
-
detail_text.strip(),
|
|
887
|
-
title=f"[bold]{agent.name} Details[/bold]",
|
|
888
|
-
box=ROUNDED,
|
|
889
|
-
style="cyan",
|
|
890
|
-
)
|
|
561
|
+
for agent_id, skills in mappings.items():
|
|
562
|
+
skills_str = (
|
|
563
|
+
", ".join(skills) if skills else "[dim](none)[/dim]"
|
|
564
|
+
)
|
|
565
|
+
table.add_row(agent_id, skills_str)
|
|
891
566
|
|
|
892
|
-
|
|
567
|
+
self.console.print(table)
|
|
893
568
|
|
|
894
|
-
|
|
895
|
-
self.console.print("[red]Invalid agent ID.[/red]")
|
|
569
|
+
Prompt.ask("\nPress Enter to continue")
|
|
896
570
|
|
|
897
|
-
|
|
898
|
-
|
|
571
|
+
elif choice == "4":
|
|
572
|
+
# Auto-link skills
|
|
573
|
+
self.console.clear()
|
|
574
|
+
self._display_header()
|
|
899
575
|
|
|
900
|
-
|
|
576
|
+
self.console.print("\n[bold]Auto-Linking Skills to Agents...[/bold]\n")
|
|
901
577
|
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
578
|
+
# Get enabled agents
|
|
579
|
+
agents = self.agent_manager.discover_agents()
|
|
580
|
+
enabled_agents = [
|
|
581
|
+
a.name
|
|
582
|
+
for a in agents
|
|
583
|
+
if self.agent_manager.get_pending_state(a.name)
|
|
584
|
+
]
|
|
906
585
|
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
586
|
+
if not enabled_agents:
|
|
587
|
+
self.console.print(
|
|
588
|
+
"[yellow]No agents are currently enabled.[/yellow]"
|
|
589
|
+
)
|
|
590
|
+
self.console.print(
|
|
591
|
+
"Please enable agents first in Agent Management."
|
|
592
|
+
)
|
|
593
|
+
Prompt.ask("\nPress Enter to continue")
|
|
594
|
+
continue
|
|
912
595
|
|
|
913
|
-
|
|
596
|
+
# Auto-link
|
|
597
|
+
mapping = wizard._auto_link_skills(enabled_agents)
|
|
914
598
|
|
|
915
|
-
|
|
916
|
-
|
|
599
|
+
# Display preview
|
|
600
|
+
self.console.print("Auto-linked skills:\n")
|
|
601
|
+
for agent_id, skills in mapping.items():
|
|
602
|
+
self.console.print(f" [yellow]{agent_id}[/yellow]:")
|
|
603
|
+
for skill in skills:
|
|
604
|
+
self.console.print(f" - {skill}")
|
|
917
605
|
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
self.console.print(" [cyan][1][/cyan] Edit identity configuration")
|
|
921
|
-
self.console.print(" [cyan][2][/cyan] Edit workflow configuration")
|
|
922
|
-
self.console.print(" [cyan][3][/cyan] Import behavior file")
|
|
923
|
-
self.console.print(" [cyan][4][/cyan] Export behavior file")
|
|
924
|
-
self.console.print(" [cyan][b][/cyan] Back to main menu")
|
|
925
|
-
self.console.print()
|
|
606
|
+
# Confirm
|
|
607
|
+
confirm = Confirm.ask("\nApply this configuration?", default=True)
|
|
926
608
|
|
|
927
|
-
|
|
609
|
+
if confirm:
|
|
610
|
+
wizard._apply_skills_configuration(mapping)
|
|
611
|
+
manager.save_mappings_to_config()
|
|
612
|
+
self.console.print("\n[green]✓ Auto-linking complete![/green]")
|
|
613
|
+
else:
|
|
614
|
+
self.console.print("\n[yellow]Auto-linking cancelled.[/yellow]")
|
|
928
615
|
|
|
929
|
-
|
|
616
|
+
Prompt.ask("\nPress Enter to continue")
|
|
617
|
+
|
|
618
|
+
elif choice == "b":
|
|
930
619
|
break
|
|
931
|
-
if choice == "1":
|
|
932
|
-
self._edit_identity_config()
|
|
933
|
-
elif choice == "2":
|
|
934
|
-
self._edit_workflow_config()
|
|
935
|
-
elif choice == "3":
|
|
936
|
-
self._import_behavior_file()
|
|
937
|
-
elif choice == "4":
|
|
938
|
-
self._export_behavior_file()
|
|
939
620
|
else:
|
|
940
|
-
self.console.print("[red]Invalid choice.[/red]")
|
|
941
|
-
Prompt.ask("
|
|
621
|
+
self.console.print("[red]Invalid choice. Please try again.[/red]")
|
|
622
|
+
Prompt.ask("\nPress Enter to continue")
|
|
942
623
|
|
|
943
624
|
def _display_behavior_files(self) -> None:
|
|
944
625
|
"""Display current behavior files."""
|
|
945
|
-
|
|
946
|
-
config_dir = self.project_dir / ".claude-mpm" / "behaviors"
|
|
947
|
-
else:
|
|
948
|
-
config_dir = Path.home() / ".claude-mpm" / "behaviors"
|
|
949
|
-
|
|
950
|
-
config_dir.mkdir(parents=True, exist_ok=True)
|
|
951
|
-
|
|
952
|
-
table = Table(title="Behavior Files", box=ROUNDED)
|
|
953
|
-
table.add_column("File", style="cyan", width=30)
|
|
954
|
-
table.add_column("Size", style="dim", width=10)
|
|
955
|
-
table.add_column("Modified", style="white", width=20)
|
|
956
|
-
|
|
957
|
-
identity_file = config_dir / "identity.yaml"
|
|
958
|
-
workflow_file = config_dir / "workflow.yaml"
|
|
959
|
-
|
|
960
|
-
for file_path in [identity_file, workflow_file]:
|
|
961
|
-
if file_path.exists():
|
|
962
|
-
stat = file_path.stat()
|
|
963
|
-
size = f"{stat.st_size} bytes"
|
|
964
|
-
modified = f"{stat.st_mtime:.0f}" # Simplified timestamp
|
|
965
|
-
table.add_row(file_path.name, size, modified)
|
|
966
|
-
else:
|
|
967
|
-
table.add_row(file_path.name, "[dim]Not found[/dim]", "-")
|
|
968
|
-
|
|
969
|
-
self.console.print(table)
|
|
626
|
+
self.behavior_manager.display_behavior_files()
|
|
970
627
|
|
|
971
628
|
def _edit_identity_config(self) -> None:
|
|
972
629
|
"""Edit identity configuration."""
|
|
973
|
-
self.
|
|
974
|
-
"[yellow]Identity configuration editor - Coming soon![/yellow]"
|
|
975
|
-
)
|
|
976
|
-
Prompt.ask("Press Enter to continue")
|
|
630
|
+
self.behavior_manager.edit_identity_config()
|
|
977
631
|
|
|
978
632
|
def _edit_workflow_config(self) -> None:
|
|
979
633
|
"""Edit workflow configuration."""
|
|
980
|
-
self.
|
|
981
|
-
"[yellow]Workflow configuration editor - Coming soon![/yellow]"
|
|
982
|
-
)
|
|
983
|
-
Prompt.ask("Press Enter to continue")
|
|
634
|
+
self.behavior_manager.edit_workflow_config()
|
|
984
635
|
|
|
985
636
|
def _import_behavior_file(self) -> None:
|
|
986
637
|
"""Import a behavior file."""
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
try:
|
|
990
|
-
source = Path(file_path)
|
|
991
|
-
if not source.exists():
|
|
992
|
-
self.console.print(f"[red]File not found: {file_path}[/red]")
|
|
993
|
-
return
|
|
994
|
-
|
|
995
|
-
# Determine target directory
|
|
996
|
-
if self.current_scope == "project":
|
|
997
|
-
config_dir = self.project_dir / ".claude-mpm" / "behaviors"
|
|
998
|
-
else:
|
|
999
|
-
config_dir = Path.home() / ".claude-mpm" / "behaviors"
|
|
1000
|
-
|
|
1001
|
-
config_dir.mkdir(parents=True, exist_ok=True)
|
|
1002
|
-
|
|
1003
|
-
# Copy file
|
|
1004
|
-
import shutil
|
|
1005
|
-
|
|
1006
|
-
target = config_dir / source.name
|
|
1007
|
-
shutil.copy2(source, target)
|
|
1008
|
-
|
|
1009
|
-
self.console.print(f"[green]Successfully imported {source.name}![/green]")
|
|
1010
|
-
|
|
1011
|
-
except Exception as e:
|
|
1012
|
-
self.console.print(f"[red]Error importing file: {e}[/red]")
|
|
1013
|
-
|
|
1014
|
-
Prompt.ask("Press Enter to continue")
|
|
638
|
+
self.behavior_manager.import_behavior_file()
|
|
1015
639
|
|
|
1016
640
|
def _export_behavior_file(self) -> None:
|
|
1017
641
|
"""Export a behavior file."""
|
|
1018
|
-
self.
|
|
1019
|
-
Prompt.ask("Press Enter to continue")
|
|
642
|
+
self.behavior_manager.export_behavior_file()
|
|
1020
643
|
|
|
1021
644
|
def _manage_startup_configuration(self) -> bool:
|
|
1022
|
-
"""Manage startup configuration for MCP services and agents.
|
|
1023
|
-
|
|
1024
|
-
Returns:
|
|
1025
|
-
bool: True if user saved and wants to proceed to startup, False otherwise
|
|
1026
|
-
"""
|
|
1027
|
-
# Temporarily suppress INFO logging during Config initialization
|
|
1028
|
-
import logging
|
|
1029
|
-
|
|
1030
|
-
root_logger = logging.getLogger("claude_mpm")
|
|
1031
|
-
original_level = root_logger.level
|
|
1032
|
-
root_logger.setLevel(logging.WARNING)
|
|
1033
|
-
|
|
1034
|
-
try:
|
|
1035
|
-
# Load current configuration ONCE at the start
|
|
1036
|
-
config = Config()
|
|
1037
|
-
startup_config = self._load_startup_configuration(config)
|
|
1038
|
-
finally:
|
|
1039
|
-
# Restore original logging level
|
|
1040
|
-
root_logger.setLevel(original_level)
|
|
1041
|
-
|
|
1042
|
-
proceed_to_startup = False
|
|
1043
|
-
while True:
|
|
1044
|
-
self.console.clear()
|
|
1045
|
-
self._display_header()
|
|
1046
|
-
|
|
1047
|
-
self.console.print("[bold]Startup Configuration Management[/bold]\n")
|
|
1048
|
-
self.console.print(
|
|
1049
|
-
"[dim]Configure which MCP services, hook services, and system agents "
|
|
1050
|
-
"are enabled when Claude MPM starts.[/dim]\n"
|
|
1051
|
-
)
|
|
1052
|
-
|
|
1053
|
-
# Display current configuration (using in-memory state)
|
|
1054
|
-
self._display_startup_configuration(startup_config)
|
|
1055
|
-
|
|
1056
|
-
# Show menu options
|
|
1057
|
-
self.console.print("\n[bold]Options:[/bold]")
|
|
1058
|
-
self.console.print(" [cyan]1[/cyan] - Configure MCP Services")
|
|
1059
|
-
self.console.print(" [cyan]2[/cyan] - Configure Hook Services")
|
|
1060
|
-
self.console.print(" [cyan]3[/cyan] - Configure System Agents")
|
|
1061
|
-
self.console.print(" [cyan]4[/cyan] - Enable All")
|
|
1062
|
-
self.console.print(" [cyan]5[/cyan] - Disable All")
|
|
1063
|
-
self.console.print(" [cyan]6[/cyan] - Reset to Defaults")
|
|
1064
|
-
self.console.print(
|
|
1065
|
-
" [cyan]s[/cyan] - Save configuration and start claude-mpm"
|
|
1066
|
-
)
|
|
1067
|
-
self.console.print(" [cyan]b[/cyan] - Cancel and return without saving")
|
|
1068
|
-
self.console.print()
|
|
1069
|
-
|
|
1070
|
-
choice = Prompt.ask("[bold cyan]Select an option[/bold cyan]", default="s")
|
|
1071
|
-
|
|
1072
|
-
if choice == "b":
|
|
1073
|
-
break
|
|
1074
|
-
if choice == "1":
|
|
1075
|
-
self._configure_mcp_services(startup_config, config)
|
|
1076
|
-
elif choice == "2":
|
|
1077
|
-
self._configure_hook_services(startup_config, config)
|
|
1078
|
-
elif choice == "3":
|
|
1079
|
-
self._configure_system_agents(startup_config, config)
|
|
1080
|
-
elif choice == "4":
|
|
1081
|
-
self._enable_all_services(startup_config, config)
|
|
1082
|
-
elif choice == "5":
|
|
1083
|
-
self._disable_all_services(startup_config, config)
|
|
1084
|
-
elif choice == "6":
|
|
1085
|
-
self._reset_to_defaults(startup_config, config)
|
|
1086
|
-
elif choice == "s":
|
|
1087
|
-
# Save and exit if successful
|
|
1088
|
-
if self._save_startup_configuration(startup_config, config):
|
|
1089
|
-
proceed_to_startup = True
|
|
1090
|
-
break
|
|
1091
|
-
else:
|
|
1092
|
-
self.console.print("[red]Invalid choice.[/red]")
|
|
1093
|
-
Prompt.ask("Press Enter to continue")
|
|
1094
|
-
|
|
1095
|
-
return proceed_to_startup
|
|
645
|
+
"""Manage startup configuration for MCP services and agents."""
|
|
646
|
+
return self.startup_manager.manage_startup_configuration()
|
|
1096
647
|
|
|
1097
648
|
def _load_startup_configuration(self, config: Config) -> Dict:
|
|
1098
649
|
"""Load current startup configuration from config."""
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
# Ensure all required sections exist
|
|
1102
|
-
if "enabled_mcp_services" not in startup_config:
|
|
1103
|
-
# Get available MCP services from MCPConfigManager
|
|
1104
|
-
mcp_manager = MCPConfigManager()
|
|
1105
|
-
available_services = list(mcp_manager.STATIC_MCP_CONFIGS.keys())
|
|
1106
|
-
startup_config["enabled_mcp_services"] = available_services.copy()
|
|
1107
|
-
|
|
1108
|
-
if "enabled_hook_services" not in startup_config:
|
|
1109
|
-
# Default hook services (health-monitor enabled by default)
|
|
1110
|
-
startup_config["enabled_hook_services"] = [
|
|
1111
|
-
"monitor",
|
|
1112
|
-
"dashboard",
|
|
1113
|
-
"response-logger",
|
|
1114
|
-
"health-monitor",
|
|
1115
|
-
]
|
|
1116
|
-
|
|
1117
|
-
if "disabled_agents" not in startup_config:
|
|
1118
|
-
# NEW LOGIC: Track DISABLED agents instead of enabled
|
|
1119
|
-
# By default, NO agents are disabled (all agents enabled)
|
|
1120
|
-
startup_config["disabled_agents"] = []
|
|
1121
|
-
|
|
1122
|
-
return startup_config
|
|
650
|
+
return self.startup_manager.load_startup_configuration(config)
|
|
1123
651
|
|
|
1124
652
|
def _display_startup_configuration(self, startup_config: Dict) -> None:
|
|
1125
653
|
"""Display current startup configuration in a table."""
|
|
1126
|
-
|
|
1127
|
-
title="Current Startup Configuration", box=ROUNDED, show_lines=True
|
|
1128
|
-
)
|
|
1129
|
-
|
|
1130
|
-
table.add_column("Category", style="cyan", width=20)
|
|
1131
|
-
table.add_column("Enabled Services", style="white", width=50)
|
|
1132
|
-
table.add_column("Count", style="dim", width=10)
|
|
1133
|
-
|
|
1134
|
-
# MCP Services
|
|
1135
|
-
mcp_services = startup_config.get("enabled_mcp_services", [])
|
|
1136
|
-
mcp_display = ", ".join(mcp_services[:3]) + (
|
|
1137
|
-
"..." if len(mcp_services) > 3 else ""
|
|
1138
|
-
)
|
|
1139
|
-
table.add_row(
|
|
1140
|
-
"MCP Services",
|
|
1141
|
-
mcp_display if mcp_services else "[dim]None[/dim]",
|
|
1142
|
-
str(len(mcp_services)),
|
|
1143
|
-
)
|
|
1144
|
-
|
|
1145
|
-
# Hook Services
|
|
1146
|
-
hook_services = startup_config.get("enabled_hook_services", [])
|
|
1147
|
-
hook_display = ", ".join(hook_services[:3]) + (
|
|
1148
|
-
"..." if len(hook_services) > 3 else ""
|
|
1149
|
-
)
|
|
1150
|
-
table.add_row(
|
|
1151
|
-
"Hook Services",
|
|
1152
|
-
hook_display if hook_services else "[dim]None[/dim]",
|
|
1153
|
-
str(len(hook_services)),
|
|
1154
|
-
)
|
|
1155
|
-
|
|
1156
|
-
# System Agents - show count of ENABLED agents (total - disabled)
|
|
1157
|
-
all_agents = self.agent_manager.discover_agents() if self.agent_manager else []
|
|
1158
|
-
disabled_agents = startup_config.get("disabled_agents", [])
|
|
1159
|
-
enabled_count = len(all_agents) - len(disabled_agents)
|
|
1160
|
-
|
|
1161
|
-
# Show first few enabled agent names
|
|
1162
|
-
enabled_names = [a.name for a in all_agents if a.name not in disabled_agents]
|
|
1163
|
-
agent_display = ", ".join(enabled_names[:3]) + (
|
|
1164
|
-
"..." if len(enabled_names) > 3 else ""
|
|
1165
|
-
)
|
|
1166
|
-
table.add_row(
|
|
1167
|
-
"System Agents",
|
|
1168
|
-
agent_display if enabled_names else "[dim]All Disabled[/dim]",
|
|
1169
|
-
f"{enabled_count}/{len(all_agents)}",
|
|
1170
|
-
)
|
|
1171
|
-
|
|
1172
|
-
self.console.print(table)
|
|
654
|
+
self.startup_manager.display_startup_configuration(startup_config)
|
|
1173
655
|
|
|
1174
656
|
def _configure_mcp_services(self, startup_config: Dict, config: Config) -> None:
|
|
1175
657
|
"""Configure which MCP services to enable at startup."""
|
|
1176
|
-
self.
|
|
1177
|
-
self._display_header()
|
|
1178
|
-
self.console.print("[bold]Configure MCP Services[/bold]\n")
|
|
1179
|
-
|
|
1180
|
-
# Get available MCP services
|
|
1181
|
-
mcp_manager = MCPConfigManager()
|
|
1182
|
-
available_services = list(mcp_manager.STATIC_MCP_CONFIGS.keys())
|
|
1183
|
-
enabled_services = set(startup_config.get("enabled_mcp_services", []))
|
|
1184
|
-
|
|
1185
|
-
# Display services with checkboxes
|
|
1186
|
-
table = Table(box=ROUNDED, show_lines=True)
|
|
1187
|
-
table.add_column("ID", style="dim", width=5)
|
|
1188
|
-
table.add_column("Service", style="cyan", width=25)
|
|
1189
|
-
table.add_column("Status", width=15)
|
|
1190
|
-
table.add_column("Description", style="white", width=45)
|
|
1191
|
-
|
|
1192
|
-
service_descriptions = {
|
|
1193
|
-
"kuzu-memory": "Graph-based memory system for agents",
|
|
1194
|
-
"mcp-ticketer": "Ticket and issue tracking integration",
|
|
1195
|
-
"mcp-browser": "Browser automation and web scraping",
|
|
1196
|
-
"mcp-vector-search": "Semantic code search capabilities",
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
|
-
for idx, service in enumerate(available_services, 1):
|
|
1200
|
-
status = (
|
|
1201
|
-
"[green]✓ Enabled[/green]"
|
|
1202
|
-
if service in enabled_services
|
|
1203
|
-
else "[red]✗ Disabled[/red]"
|
|
1204
|
-
)
|
|
1205
|
-
description = service_descriptions.get(service, "MCP service")
|
|
1206
|
-
table.add_row(str(idx), service, status, description)
|
|
1207
|
-
|
|
1208
|
-
self.console.print(table)
|
|
1209
|
-
self.console.print("\n[bold]Commands:[/bold]")
|
|
1210
|
-
self.console.print(" Enter service IDs to toggle (e.g., '1,3' or '1-4')")
|
|
1211
|
-
self.console.print(" [cyan][a][/cyan] Enable all")
|
|
1212
|
-
self.console.print(" [cyan][n][/cyan] Disable all")
|
|
1213
|
-
self.console.print(" [cyan][b][/cyan] Back to previous menu")
|
|
1214
|
-
self.console.print()
|
|
1215
|
-
|
|
1216
|
-
choice = Prompt.ask("[bold cyan]Toggle services[/bold cyan]", default="b")
|
|
1217
|
-
|
|
1218
|
-
if choice == "b":
|
|
1219
|
-
return
|
|
1220
|
-
if choice == "a":
|
|
1221
|
-
startup_config["enabled_mcp_services"] = available_services.copy()
|
|
1222
|
-
self.console.print("[green]All MCP services enabled![/green]")
|
|
1223
|
-
elif choice == "n":
|
|
1224
|
-
startup_config["enabled_mcp_services"] = []
|
|
1225
|
-
self.console.print("[green]All MCP services disabled![/green]")
|
|
1226
|
-
else:
|
|
1227
|
-
# Parse service IDs
|
|
1228
|
-
try:
|
|
1229
|
-
selected_ids = self._parse_id_selection(choice, len(available_services))
|
|
1230
|
-
for idx in selected_ids:
|
|
1231
|
-
service = available_services[idx - 1]
|
|
1232
|
-
if service in enabled_services:
|
|
1233
|
-
enabled_services.remove(service)
|
|
1234
|
-
self.console.print(f"[red]Disabled {service}[/red]")
|
|
1235
|
-
else:
|
|
1236
|
-
enabled_services.add(service)
|
|
1237
|
-
self.console.print(f"[green]Enabled {service}[/green]")
|
|
1238
|
-
startup_config["enabled_mcp_services"] = list(enabled_services)
|
|
1239
|
-
except (ValueError, IndexError) as e:
|
|
1240
|
-
self.console.print(f"[red]Invalid selection: {e}[/red]")
|
|
1241
|
-
|
|
1242
|
-
Prompt.ask("Press Enter to continue")
|
|
658
|
+
self.startup_manager.configure_mcp_services(startup_config, config)
|
|
1243
659
|
|
|
1244
660
|
def _configure_hook_services(self, startup_config: Dict, config: Config) -> None:
|
|
1245
661
|
"""Configure which hook services to enable at startup."""
|
|
1246
|
-
self.
|
|
1247
|
-
self._display_header()
|
|
1248
|
-
self.console.print("[bold]Configure Hook Services[/bold]\n")
|
|
1249
|
-
|
|
1250
|
-
# Available hook services
|
|
1251
|
-
available_services = [
|
|
1252
|
-
("monitor", "Real-time event monitoring server (SocketIO)"),
|
|
1253
|
-
("dashboard", "Web-based dashboard interface"),
|
|
1254
|
-
("response-logger", "Agent response logging"),
|
|
1255
|
-
("health-monitor", "Service health and recovery monitoring"),
|
|
1256
|
-
]
|
|
1257
|
-
|
|
1258
|
-
enabled_services = set(startup_config.get("enabled_hook_services", []))
|
|
1259
|
-
|
|
1260
|
-
# Display services with checkboxes
|
|
1261
|
-
table = Table(box=ROUNDED, show_lines=True)
|
|
1262
|
-
table.add_column("ID", style="dim", width=5)
|
|
1263
|
-
table.add_column("Service", style="cyan", width=25)
|
|
1264
|
-
table.add_column("Status", width=15)
|
|
1265
|
-
table.add_column("Description", style="white", width=45)
|
|
1266
|
-
|
|
1267
|
-
for idx, (service, description) in enumerate(available_services, 1):
|
|
1268
|
-
status = (
|
|
1269
|
-
"[green]✓ Enabled[/green]"
|
|
1270
|
-
if service in enabled_services
|
|
1271
|
-
else "[red]✗ Disabled[/red]"
|
|
1272
|
-
)
|
|
1273
|
-
table.add_row(str(idx), service, status, description)
|
|
1274
|
-
|
|
1275
|
-
self.console.print(table)
|
|
1276
|
-
self.console.print("\n[bold]Commands:[/bold]")
|
|
1277
|
-
self.console.print(" Enter service IDs to toggle (e.g., '1,3' or '1-4')")
|
|
1278
|
-
self.console.print(" [cyan][a][/cyan] Enable all")
|
|
1279
|
-
self.console.print(" [cyan][n][/cyan] Disable all")
|
|
1280
|
-
self.console.print(" [cyan][b][/cyan] Back to previous menu")
|
|
1281
|
-
self.console.print()
|
|
1282
|
-
|
|
1283
|
-
choice = Prompt.ask("[bold cyan]Toggle services[/bold cyan]", default="b")
|
|
1284
|
-
|
|
1285
|
-
if choice == "b":
|
|
1286
|
-
return
|
|
1287
|
-
if choice == "a":
|
|
1288
|
-
startup_config["enabled_hook_services"] = [s[0] for s in available_services]
|
|
1289
|
-
self.console.print("[green]All hook services enabled![/green]")
|
|
1290
|
-
elif choice == "n":
|
|
1291
|
-
startup_config["enabled_hook_services"] = []
|
|
1292
|
-
self.console.print("[green]All hook services disabled![/green]")
|
|
1293
|
-
else:
|
|
1294
|
-
# Parse service IDs
|
|
1295
|
-
try:
|
|
1296
|
-
selected_ids = self._parse_id_selection(choice, len(available_services))
|
|
1297
|
-
for idx in selected_ids:
|
|
1298
|
-
service = available_services[idx - 1][0]
|
|
1299
|
-
if service in enabled_services:
|
|
1300
|
-
enabled_services.remove(service)
|
|
1301
|
-
self.console.print(f"[red]Disabled {service}[/red]")
|
|
1302
|
-
else:
|
|
1303
|
-
enabled_services.add(service)
|
|
1304
|
-
self.console.print(f"[green]Enabled {service}[/green]")
|
|
1305
|
-
startup_config["enabled_hook_services"] = list(enabled_services)
|
|
1306
|
-
except (ValueError, IndexError) as e:
|
|
1307
|
-
self.console.print(f"[red]Invalid selection: {e}[/red]")
|
|
1308
|
-
|
|
1309
|
-
Prompt.ask("Press Enter to continue")
|
|
662
|
+
self.startup_manager.configure_hook_services(startup_config, config)
|
|
1310
663
|
|
|
1311
664
|
def _configure_system_agents(self, startup_config: Dict, config: Config) -> None:
|
|
1312
|
-
"""Configure which system agents to deploy at startup.
|
|
1313
|
-
|
|
1314
|
-
NEW LOGIC: Uses disabled_agents list. All agents from templates are enabled by default.
|
|
1315
|
-
"""
|
|
1316
|
-
while True:
|
|
1317
|
-
self.console.clear()
|
|
1318
|
-
self._display_header()
|
|
1319
|
-
self.console.print("[bold]Configure System Agents[/bold]\n")
|
|
1320
|
-
self.console.print(
|
|
1321
|
-
"[dim]All agents discovered from templates are enabled by default. "
|
|
1322
|
-
"Mark agents as disabled to prevent deployment.[/dim]\n"
|
|
1323
|
-
)
|
|
1324
|
-
|
|
1325
|
-
# Discover available agents from template files
|
|
1326
|
-
agents = self.agent_manager.discover_agents()
|
|
1327
|
-
disabled_agents = set(startup_config.get("disabled_agents", []))
|
|
1328
|
-
|
|
1329
|
-
# Display agents with checkboxes
|
|
1330
|
-
table = Table(box=ROUNDED, show_lines=True)
|
|
1331
|
-
table.add_column("ID", style="dim", width=5)
|
|
1332
|
-
table.add_column("Agent", style="cyan", width=25)
|
|
1333
|
-
table.add_column("Status", width=15)
|
|
1334
|
-
table.add_column("Description", style="white", width=45)
|
|
1335
|
-
|
|
1336
|
-
for idx, agent in enumerate(agents, 1):
|
|
1337
|
-
# Agent is ENABLED if NOT in disabled list
|
|
1338
|
-
is_enabled = agent.name not in disabled_agents
|
|
1339
|
-
status = (
|
|
1340
|
-
"[green]✓ Enabled[/green]"
|
|
1341
|
-
if is_enabled
|
|
1342
|
-
else "[red]✗ Disabled[/red]"
|
|
1343
|
-
)
|
|
1344
|
-
desc_display = (
|
|
1345
|
-
agent.description[:42] + "..."
|
|
1346
|
-
if len(agent.description) > 42
|
|
1347
|
-
else agent.description
|
|
1348
|
-
)
|
|
1349
|
-
table.add_row(str(idx), agent.name, status, desc_display)
|
|
1350
|
-
|
|
1351
|
-
self.console.print(table)
|
|
1352
|
-
self.console.print("\n[bold]Commands:[/bold]")
|
|
1353
|
-
self.console.print(" Enter agent IDs to toggle (e.g., '1,3' or '1-4')")
|
|
1354
|
-
self.console.print(" [cyan]a[/cyan] - Enable all (clear disabled list)")
|
|
1355
|
-
self.console.print(" [cyan]n[/cyan] - Disable all")
|
|
1356
|
-
self.console.print(" [cyan]b[/cyan] - Back to previous menu")
|
|
1357
|
-
self.console.print()
|
|
1358
|
-
|
|
1359
|
-
choice = Prompt.ask("[bold cyan]Select option[/bold cyan]", default="b")
|
|
1360
|
-
|
|
1361
|
-
if choice == "b":
|
|
1362
|
-
return
|
|
1363
|
-
if choice == "a":
|
|
1364
|
-
# Enable all = empty disabled list
|
|
1365
|
-
startup_config["disabled_agents"] = []
|
|
1366
|
-
self.console.print("[green]All agents enabled![/green]")
|
|
1367
|
-
Prompt.ask("Press Enter to continue")
|
|
1368
|
-
elif choice == "n":
|
|
1369
|
-
# Disable all = all agents in disabled list
|
|
1370
|
-
startup_config["disabled_agents"] = [agent.name for agent in agents]
|
|
1371
|
-
self.console.print("[green]All agents disabled![/green]")
|
|
1372
|
-
Prompt.ask("Press Enter to continue")
|
|
1373
|
-
else:
|
|
1374
|
-
# Parse agent IDs
|
|
1375
|
-
try:
|
|
1376
|
-
selected_ids = self._parse_id_selection(choice, len(agents))
|
|
1377
|
-
for idx in selected_ids:
|
|
1378
|
-
agent = agents[idx - 1]
|
|
1379
|
-
if agent.name in disabled_agents:
|
|
1380
|
-
# Currently disabled, enable it (remove from disabled list)
|
|
1381
|
-
disabled_agents.remove(agent.name)
|
|
1382
|
-
self.console.print(f"[green]Enabled {agent.name}[/green]")
|
|
1383
|
-
else:
|
|
1384
|
-
# Currently enabled, disable it (add to disabled list)
|
|
1385
|
-
disabled_agents.add(agent.name)
|
|
1386
|
-
self.console.print(f"[red]Disabled {agent.name}[/red]")
|
|
1387
|
-
startup_config["disabled_agents"] = list(disabled_agents)
|
|
1388
|
-
# Refresh the display to show updated status immediately
|
|
1389
|
-
except (ValueError, IndexError) as e:
|
|
1390
|
-
self.console.print(f"[red]Invalid selection: {e}[/red]")
|
|
1391
|
-
Prompt.ask("Press Enter to continue")
|
|
665
|
+
"""Configure which system agents to deploy at startup."""
|
|
666
|
+
self.startup_manager.configure_system_agents(startup_config, config)
|
|
1392
667
|
|
|
1393
668
|
def _parse_id_selection(self, selection: str, max_id: int) -> List[int]:
|
|
1394
669
|
"""Parse ID selection string (e.g., '1,3,5' or '1-4')."""
|
|
1395
|
-
|
|
1396
|
-
parts = selection.split(",")
|
|
1397
|
-
|
|
1398
|
-
for part in parts:
|
|
1399
|
-
part = part.strip()
|
|
1400
|
-
if "-" in part:
|
|
1401
|
-
# Range selection
|
|
1402
|
-
start, end = part.split("-")
|
|
1403
|
-
start_id = int(start.strip())
|
|
1404
|
-
end_id = int(end.strip())
|
|
1405
|
-
if start_id < 1 or end_id > max_id or start_id > end_id:
|
|
1406
|
-
raise ValueError(f"Invalid range: {part}")
|
|
1407
|
-
ids.update(range(start_id, end_id + 1))
|
|
1408
|
-
else:
|
|
1409
|
-
# Single ID
|
|
1410
|
-
id_num = int(part)
|
|
1411
|
-
if id_num < 1 or id_num > max_id:
|
|
1412
|
-
raise ValueError(f"Invalid ID: {id_num}")
|
|
1413
|
-
ids.add(id_num)
|
|
1414
|
-
|
|
1415
|
-
return sorted(ids)
|
|
670
|
+
return parse_id_selection(selection, max_id)
|
|
1416
671
|
|
|
1417
672
|
def _enable_all_services(self, startup_config: Dict, config: Config) -> None:
|
|
1418
673
|
"""Enable all services and agents."""
|
|
1419
|
-
|
|
1420
|
-
# Enable all MCP services
|
|
1421
|
-
mcp_manager = MCPConfigManager()
|
|
1422
|
-
startup_config["enabled_mcp_services"] = list(
|
|
1423
|
-
mcp_manager.STATIC_MCP_CONFIGS.keys()
|
|
1424
|
-
)
|
|
1425
|
-
|
|
1426
|
-
# Enable all hook services
|
|
1427
|
-
startup_config["enabled_hook_services"] = [
|
|
1428
|
-
"monitor",
|
|
1429
|
-
"dashboard",
|
|
1430
|
-
"response-logger",
|
|
1431
|
-
"health-monitor",
|
|
1432
|
-
]
|
|
1433
|
-
|
|
1434
|
-
# Enable all agents (empty disabled list)
|
|
1435
|
-
startup_config["disabled_agents"] = []
|
|
1436
|
-
|
|
1437
|
-
self.console.print("[green]All services and agents enabled![/green]")
|
|
1438
|
-
Prompt.ask("Press Enter to continue")
|
|
674
|
+
self.startup_manager.enable_all_services(startup_config, config)
|
|
1439
675
|
|
|
1440
676
|
def _disable_all_services(self, startup_config: Dict, config: Config) -> None:
|
|
1441
677
|
"""Disable all services and agents."""
|
|
1442
|
-
|
|
1443
|
-
startup_config["enabled_mcp_services"] = []
|
|
1444
|
-
startup_config["enabled_hook_services"] = []
|
|
1445
|
-
# Disable all agents = add all to disabled list
|
|
1446
|
-
agents = self.agent_manager.discover_agents()
|
|
1447
|
-
startup_config["disabled_agents"] = [agent.name for agent in agents]
|
|
1448
|
-
|
|
1449
|
-
self.console.print("[green]All services and agents disabled![/green]")
|
|
1450
|
-
self.console.print(
|
|
1451
|
-
"[yellow]Note: You may need to enable at least some services for Claude MPM to function properly.[/yellow]"
|
|
1452
|
-
)
|
|
1453
|
-
Prompt.ask("Press Enter to continue")
|
|
678
|
+
self.startup_manager.disable_all_services(startup_config, config)
|
|
1454
679
|
|
|
1455
680
|
def _reset_to_defaults(self, startup_config: Dict, config: Config) -> None:
|
|
1456
681
|
"""Reset startup configuration to defaults."""
|
|
1457
|
-
|
|
1458
|
-
# Reset to default values
|
|
1459
|
-
mcp_manager = MCPConfigManager()
|
|
1460
|
-
startup_config["enabled_mcp_services"] = list(
|
|
1461
|
-
mcp_manager.STATIC_MCP_CONFIGS.keys()
|
|
1462
|
-
)
|
|
1463
|
-
startup_config["enabled_hook_services"] = [
|
|
1464
|
-
"monitor",
|
|
1465
|
-
"dashboard",
|
|
1466
|
-
"response-logger",
|
|
1467
|
-
"health-monitor",
|
|
1468
|
-
]
|
|
1469
|
-
# Default: All agents enabled (empty disabled list)
|
|
1470
|
-
startup_config["disabled_agents"] = []
|
|
1471
|
-
|
|
1472
|
-
self.console.print(
|
|
1473
|
-
"[green]Startup configuration reset to defaults![/green]"
|
|
1474
|
-
)
|
|
1475
|
-
Prompt.ask("Press Enter to continue")
|
|
682
|
+
self.startup_manager.reset_to_defaults(startup_config, config)
|
|
1476
683
|
|
|
1477
684
|
def _save_startup_configuration(self, startup_config: Dict, config: Config) -> bool:
|
|
1478
|
-
"""Save startup configuration to config file and return whether to proceed to startup.
|
|
685
|
+
"""Save startup configuration to config file and return whether to proceed to startup."""
|
|
686
|
+
return self.startup_manager.save_startup_configuration(startup_config, config)
|
|
1479
687
|
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
try:
|
|
1484
|
-
# Update the startup configuration
|
|
1485
|
-
config.set("startup", startup_config)
|
|
1486
|
-
|
|
1487
|
-
# IMPORTANT: Also update agent_deployment.disabled_agents so the deployment
|
|
1488
|
-
# system actually uses the configured disabled agents list
|
|
1489
|
-
config.set(
|
|
1490
|
-
"agent_deployment.disabled_agents",
|
|
1491
|
-
startup_config.get("disabled_agents", []),
|
|
1492
|
-
)
|
|
688
|
+
def _save_all_configuration(self) -> bool:
|
|
689
|
+
"""Save all configuration changes across all contexts."""
|
|
690
|
+
return self.startup_manager.save_all_configuration()
|
|
1493
691
|
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
else:
|
|
1498
|
-
config_file = Path.home() / ".claude-mpm" / "configuration.yaml"
|
|
1499
|
-
|
|
1500
|
-
# Ensure directory exists
|
|
1501
|
-
config_file.parent.mkdir(parents=True, exist_ok=True)
|
|
1502
|
-
|
|
1503
|
-
# Temporarily suppress INFO logging to avoid duplicate save messages
|
|
1504
|
-
import logging
|
|
1505
|
-
|
|
1506
|
-
root_logger = logging.getLogger("claude_mpm")
|
|
1507
|
-
original_level = root_logger.level
|
|
1508
|
-
root_logger.setLevel(logging.WARNING)
|
|
1509
|
-
|
|
1510
|
-
try:
|
|
1511
|
-
# Save configuration (this will log at INFO level which we've suppressed)
|
|
1512
|
-
config.save(config_file, format="yaml")
|
|
1513
|
-
finally:
|
|
1514
|
-
# Restore original logging level
|
|
1515
|
-
root_logger.setLevel(original_level)
|
|
1516
|
-
|
|
1517
|
-
self.console.print(
|
|
1518
|
-
f"[green]✓ Startup configuration saved to {config_file}[/green]"
|
|
1519
|
-
)
|
|
1520
|
-
self.console.print(
|
|
1521
|
-
"\n[cyan]Applying configuration and launching Claude MPM...[/cyan]\n"
|
|
1522
|
-
)
|
|
1523
|
-
|
|
1524
|
-
# Launch claude-mpm run command to get full startup cycle
|
|
1525
|
-
# This ensures:
|
|
1526
|
-
# 1. Configuration is loaded
|
|
1527
|
-
# 2. Enabled agents are deployed
|
|
1528
|
-
# 3. Disabled agents are removed from .claude/agents/
|
|
1529
|
-
# 4. MCP services and hooks are started
|
|
1530
|
-
try:
|
|
1531
|
-
# Use execvp to replace the current process with claude-mpm run
|
|
1532
|
-
# This ensures a clean transition from configurator to Claude MPM
|
|
1533
|
-
os.execvp("claude-mpm", ["claude-mpm", "run"])
|
|
1534
|
-
except Exception as e:
|
|
1535
|
-
self.console.print(
|
|
1536
|
-
f"[yellow]Could not launch Claude MPM automatically: {e}[/yellow]"
|
|
1537
|
-
)
|
|
1538
|
-
self.console.print(
|
|
1539
|
-
"[cyan]Please run 'claude-mpm' manually to start.[/cyan]"
|
|
1540
|
-
)
|
|
1541
|
-
Prompt.ask("Press Enter to continue")
|
|
1542
|
-
return True
|
|
1543
|
-
|
|
1544
|
-
# This line will never be reached if execvp succeeds
|
|
1545
|
-
return True
|
|
1546
|
-
|
|
1547
|
-
except Exception as e:
|
|
1548
|
-
self.console.print(f"[red]Error saving configuration: {e}[/red]")
|
|
1549
|
-
Prompt.ask("Press Enter to continue")
|
|
1550
|
-
return False
|
|
692
|
+
def _launch_claude_mpm(self) -> None:
|
|
693
|
+
"""Launch Claude MPM run command, replacing current process."""
|
|
694
|
+
self.navigation.launch_claude_mpm()
|
|
1551
695
|
|
|
1552
696
|
def _switch_scope(self) -> None:
|
|
1553
697
|
"""Switch between project and user scope."""
|
|
1554
|
-
self.
|
|
1555
|
-
|
|
1556
|
-
|
|
698
|
+
self.navigation.switch_scope()
|
|
699
|
+
# Sync scope back from navigation
|
|
700
|
+
self.current_scope = self.navigation.current_scope
|
|
1557
701
|
|
|
1558
702
|
def _show_version_info_interactive(self) -> None:
|
|
1559
703
|
"""Show version information in interactive mode."""
|
|
1560
|
-
self.
|
|
1561
|
-
self._display_header()
|
|
1562
|
-
|
|
1563
|
-
# Get version information
|
|
1564
|
-
mpm_version = self.version_service.get_version()
|
|
1565
|
-
build_number = self.version_service.get_build_number()
|
|
1566
|
-
|
|
1567
|
-
# Try to get Claude Code version using the installer's method
|
|
1568
|
-
claude_version = "Unknown"
|
|
1569
|
-
try:
|
|
1570
|
-
from ...hooks.claude_hooks.installer import HookInstaller
|
|
1571
|
-
|
|
1572
|
-
installer = HookInstaller()
|
|
1573
|
-
detected_version = installer.get_claude_version()
|
|
1574
|
-
if detected_version:
|
|
1575
|
-
is_compatible, _ = installer.is_version_compatible()
|
|
1576
|
-
claude_version = f"{detected_version} (Claude Code)"
|
|
1577
|
-
if not is_compatible:
|
|
1578
|
-
claude_version += (
|
|
1579
|
-
f" - Monitoring requires {installer.MIN_CLAUDE_VERSION}+"
|
|
1580
|
-
)
|
|
1581
|
-
else:
|
|
1582
|
-
# Fallback to direct subprocess call
|
|
1583
|
-
import subprocess
|
|
1584
|
-
|
|
1585
|
-
result = subprocess.run(
|
|
1586
|
-
["claude", "--version"],
|
|
1587
|
-
capture_output=True,
|
|
1588
|
-
text=True,
|
|
1589
|
-
timeout=5,
|
|
1590
|
-
check=False,
|
|
1591
|
-
)
|
|
1592
|
-
if result.returncode == 0:
|
|
1593
|
-
claude_version = result.stdout.strip()
|
|
1594
|
-
except Exception:
|
|
1595
|
-
pass
|
|
1596
|
-
|
|
1597
|
-
# Create version panel
|
|
1598
|
-
version_text = f"""
|
|
1599
|
-
[bold cyan]Claude MPM[/bold cyan]
|
|
1600
|
-
Version: {mpm_version}
|
|
1601
|
-
Build: {build_number}
|
|
1602
|
-
|
|
1603
|
-
[bold cyan]Claude Code[/bold cyan]
|
|
1604
|
-
Version: {claude_version}
|
|
1605
|
-
|
|
1606
|
-
[bold cyan]Python[/bold cyan]
|
|
1607
|
-
Version: {sys.version.split()[0]}
|
|
1608
|
-
|
|
1609
|
-
[bold cyan]Configuration[/bold cyan]
|
|
1610
|
-
Scope: {self.current_scope}
|
|
1611
|
-
Directory: {self.project_dir}
|
|
1612
|
-
"""
|
|
1613
|
-
|
|
1614
|
-
panel = Panel(
|
|
1615
|
-
version_text.strip(),
|
|
1616
|
-
title="[bold]Version Information[/bold]",
|
|
1617
|
-
box=ROUNDED,
|
|
1618
|
-
style="green",
|
|
1619
|
-
)
|
|
1620
|
-
|
|
1621
|
-
self.console.print(panel)
|
|
1622
|
-
Prompt.ask("\nPress Enter to continue")
|
|
704
|
+
self.persistence.show_version_info_interactive()
|
|
1623
705
|
|
|
1624
706
|
# Non-interactive command methods
|
|
1625
707
|
|
|
@@ -1661,261 +743,33 @@ Directory: {self.project_dir}
|
|
|
1661
743
|
|
|
1662
744
|
def _export_config(self, file_path: str) -> CommandResult:
|
|
1663
745
|
"""Export configuration to a file."""
|
|
1664
|
-
|
|
1665
|
-
# Gather all configuration
|
|
1666
|
-
config_data = {"scope": self.current_scope, "agents": {}, "behaviors": {}}
|
|
1667
|
-
|
|
1668
|
-
# Get agent states
|
|
1669
|
-
agents = self.agent_manager.discover_agents()
|
|
1670
|
-
for agent in agents:
|
|
1671
|
-
config_data["agents"][agent.name] = {
|
|
1672
|
-
"enabled": self.agent_manager.is_agent_enabled(agent.name),
|
|
1673
|
-
"template_path": str(self._get_agent_template_path(agent.name)),
|
|
1674
|
-
}
|
|
1675
|
-
|
|
1676
|
-
# Write to file
|
|
1677
|
-
output_path = Path(file_path)
|
|
1678
|
-
with output_path.open("w") as f:
|
|
1679
|
-
json.dump(config_data, f, indent=2)
|
|
1680
|
-
|
|
1681
|
-
return CommandResult.success_result(
|
|
1682
|
-
f"Configuration exported to {output_path}"
|
|
1683
|
-
)
|
|
1684
|
-
|
|
1685
|
-
except Exception as e:
|
|
1686
|
-
return CommandResult.error_result(f"Failed to export configuration: {e}")
|
|
746
|
+
return self.persistence.export_config(file_path)
|
|
1687
747
|
|
|
1688
748
|
def _import_config(self, file_path: str) -> CommandResult:
|
|
1689
749
|
"""Import configuration from a file."""
|
|
1690
|
-
|
|
1691
|
-
input_path = Path(file_path)
|
|
1692
|
-
if not input_path.exists():
|
|
1693
|
-
return CommandResult.error_result(f"File not found: {file_path}")
|
|
1694
|
-
|
|
1695
|
-
with input_path.open() as f:
|
|
1696
|
-
config_data = json.load(f)
|
|
1697
|
-
|
|
1698
|
-
# Apply agent states
|
|
1699
|
-
if "agents" in config_data:
|
|
1700
|
-
for agent_name, agent_config in config_data["agents"].items():
|
|
1701
|
-
if "enabled" in agent_config:
|
|
1702
|
-
self.agent_manager.set_agent_enabled(
|
|
1703
|
-
agent_name, agent_config["enabled"]
|
|
1704
|
-
)
|
|
1705
|
-
|
|
1706
|
-
return CommandResult.success_result(
|
|
1707
|
-
f"Configuration imported from {input_path}"
|
|
1708
|
-
)
|
|
1709
|
-
|
|
1710
|
-
except Exception as e:
|
|
1711
|
-
return CommandResult.error_result(f"Failed to import configuration: {e}")
|
|
750
|
+
return self.persistence.import_config(file_path)
|
|
1712
751
|
|
|
1713
752
|
def _show_version_info(self) -> CommandResult:
|
|
1714
753
|
"""Show version information in non-interactive mode."""
|
|
1715
|
-
|
|
1716
|
-
build_number = self.version_service.get_build_number()
|
|
1717
|
-
|
|
1718
|
-
data = {
|
|
1719
|
-
"mpm_version": mpm_version,
|
|
1720
|
-
"build_number": build_number,
|
|
1721
|
-
"python_version": sys.version.split()[0],
|
|
1722
|
-
}
|
|
1723
|
-
|
|
1724
|
-
# Try to get Claude version
|
|
1725
|
-
try:
|
|
1726
|
-
import subprocess
|
|
1727
|
-
|
|
1728
|
-
result = subprocess.run(
|
|
1729
|
-
["claude", "--version"],
|
|
1730
|
-
capture_output=True,
|
|
1731
|
-
text=True,
|
|
1732
|
-
timeout=5,
|
|
1733
|
-
check=False,
|
|
1734
|
-
)
|
|
1735
|
-
if result.returncode == 0:
|
|
1736
|
-
data["claude_version"] = result.stdout.strip()
|
|
1737
|
-
except Exception:
|
|
1738
|
-
data["claude_version"] = "Unknown"
|
|
1739
|
-
|
|
1740
|
-
# Print formatted output
|
|
1741
|
-
self.console.print(
|
|
1742
|
-
f"[bold]Claude MPM:[/bold] {mpm_version} (build {build_number})"
|
|
1743
|
-
)
|
|
1744
|
-
self.console.print(
|
|
1745
|
-
f"[bold]Claude Code:[/bold] {data.get('claude_version', 'Unknown')}"
|
|
1746
|
-
)
|
|
1747
|
-
self.console.print(f"[bold]Python:[/bold] {data['python_version']}")
|
|
1748
|
-
|
|
1749
|
-
return CommandResult.success_result("Version information displayed", data=data)
|
|
754
|
+
return self.persistence.show_version_info()
|
|
1750
755
|
|
|
1751
756
|
def _install_hooks(self, force: bool = False) -> CommandResult:
|
|
1752
757
|
"""Install Claude MPM hooks for Claude Code integration."""
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
installer = HookInstaller()
|
|
1757
|
-
|
|
1758
|
-
# Check Claude Code version compatibility first
|
|
1759
|
-
is_compatible, version_message = installer.is_version_compatible()
|
|
1760
|
-
self.console.print("[cyan]Checking Claude Code version...[/cyan]")
|
|
1761
|
-
self.console.print(version_message)
|
|
1762
|
-
|
|
1763
|
-
if not is_compatible:
|
|
1764
|
-
self.console.print(
|
|
1765
|
-
"\n[yellow]⚠ Hook monitoring is not available for your Claude Code version.[/yellow]"
|
|
1766
|
-
)
|
|
1767
|
-
self.console.print(
|
|
1768
|
-
"The dashboard and other features will work without real-time monitoring."
|
|
1769
|
-
)
|
|
1770
|
-
self.console.print(
|
|
1771
|
-
f"\n[dim]To enable monitoring, upgrade Claude Code to version {installer.MIN_CLAUDE_VERSION} or higher.[/dim]"
|
|
1772
|
-
)
|
|
1773
|
-
return CommandResult.success_result(
|
|
1774
|
-
"Version incompatible with hook monitoring",
|
|
1775
|
-
data={"compatible": False, "message": version_message},
|
|
1776
|
-
)
|
|
1777
|
-
|
|
1778
|
-
# Check current status
|
|
1779
|
-
status = installer.get_status()
|
|
1780
|
-
if status["installed"] and not force:
|
|
1781
|
-
self.console.print("[yellow]Hooks are already installed.[/yellow]")
|
|
1782
|
-
self.console.print("Use --force to reinstall.")
|
|
1783
|
-
|
|
1784
|
-
if not status["valid"]:
|
|
1785
|
-
self.console.print("\n[red]However, there are issues:[/red]")
|
|
1786
|
-
for issue in status["issues"]:
|
|
1787
|
-
self.console.print(f" - {issue}")
|
|
1788
|
-
|
|
1789
|
-
return CommandResult.success_result(
|
|
1790
|
-
"Hooks already installed", data=status
|
|
1791
|
-
)
|
|
1792
|
-
|
|
1793
|
-
# Install hooks
|
|
1794
|
-
self.console.print("[cyan]Installing Claude MPM hooks...[/cyan]")
|
|
1795
|
-
success = installer.install_hooks(force=force)
|
|
1796
|
-
|
|
1797
|
-
if success:
|
|
1798
|
-
self.console.print("[green]✓ Hooks installed successfully![/green]")
|
|
1799
|
-
self.console.print("\nYou can now use /mpm commands in Claude Code:")
|
|
1800
|
-
self.console.print(" /mpm - Show help")
|
|
1801
|
-
self.console.print(" /mpm status - Show claude-mpm status")
|
|
1802
|
-
|
|
1803
|
-
# Verify installation
|
|
1804
|
-
is_valid, issues = installer.verify_hooks()
|
|
1805
|
-
if not is_valid:
|
|
1806
|
-
self.console.print(
|
|
1807
|
-
"\n[yellow]Warning: Installation completed but verification found issues:[/yellow]"
|
|
1808
|
-
)
|
|
1809
|
-
for issue in issues:
|
|
1810
|
-
self.console.print(f" - {issue}")
|
|
1811
|
-
|
|
1812
|
-
return CommandResult.success_result("Hooks installed successfully")
|
|
1813
|
-
self.console.print("[red]✗ Hook installation failed[/red]")
|
|
1814
|
-
return CommandResult.error_result("Hook installation failed")
|
|
1815
|
-
|
|
1816
|
-
except ImportError:
|
|
1817
|
-
self.console.print("[red]Error: HookInstaller module not found[/red]")
|
|
1818
|
-
self.console.print("Please ensure claude-mpm is properly installed.")
|
|
1819
|
-
return CommandResult.error_result("HookInstaller module not found")
|
|
1820
|
-
except Exception as e:
|
|
1821
|
-
self.logger.error(f"Hook installation error: {e}", exc_info=True)
|
|
1822
|
-
return CommandResult.error_result(f"Hook installation failed: {e}")
|
|
758
|
+
# Share logger with hook manager for consistent error logging
|
|
759
|
+
self.hook_manager.logger = self.logger
|
|
760
|
+
return self.hook_manager.install_hooks(force=force)
|
|
1823
761
|
|
|
1824
762
|
def _verify_hooks(self) -> CommandResult:
|
|
1825
763
|
"""Verify that Claude MPM hooks are properly installed."""
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
installer = HookInstaller()
|
|
1830
|
-
status = installer.get_status()
|
|
1831
|
-
|
|
1832
|
-
self.console.print("[bold]Hook Installation Status[/bold]\n")
|
|
1833
|
-
|
|
1834
|
-
# Show Claude Code version and compatibility
|
|
1835
|
-
if status.get("claude_version"):
|
|
1836
|
-
self.console.print(f"Claude Code Version: {status['claude_version']}")
|
|
1837
|
-
if status.get("version_compatible"):
|
|
1838
|
-
self.console.print(
|
|
1839
|
-
"[green]✓[/green] Version compatible with hook monitoring"
|
|
1840
|
-
)
|
|
1841
|
-
else:
|
|
1842
|
-
self.console.print(
|
|
1843
|
-
f"[yellow]⚠[/yellow] {status.get('version_message', 'Version incompatible')}"
|
|
1844
|
-
)
|
|
1845
|
-
self.console.print()
|
|
1846
|
-
else:
|
|
1847
|
-
self.console.print(
|
|
1848
|
-
"[yellow]Claude Code version could not be detected[/yellow]"
|
|
1849
|
-
)
|
|
1850
|
-
self.console.print()
|
|
1851
|
-
|
|
1852
|
-
if status["installed"]:
|
|
1853
|
-
self.console.print(
|
|
1854
|
-
f"[green]✓[/green] Hooks installed at: {status['hook_script']}"
|
|
1855
|
-
)
|
|
1856
|
-
else:
|
|
1857
|
-
self.console.print("[red]✗[/red] Hooks not installed")
|
|
1858
|
-
|
|
1859
|
-
if status["settings_file"]:
|
|
1860
|
-
self.console.print(
|
|
1861
|
-
f"[green]✓[/green] Settings file: {status['settings_file']}"
|
|
1862
|
-
)
|
|
1863
|
-
else:
|
|
1864
|
-
self.console.print("[red]✗[/red] Settings file not found")
|
|
1865
|
-
|
|
1866
|
-
if status.get("configured_events"):
|
|
1867
|
-
self.console.print(
|
|
1868
|
-
f"[green]✓[/green] Configured events: {', '.join(status['configured_events'])}"
|
|
1869
|
-
)
|
|
1870
|
-
else:
|
|
1871
|
-
self.console.print("[red]✗[/red] No events configured")
|
|
1872
|
-
|
|
1873
|
-
if status["valid"]:
|
|
1874
|
-
self.console.print("\n[green]All checks passed![/green]")
|
|
1875
|
-
else:
|
|
1876
|
-
self.console.print("\n[red]Issues found:[/red]")
|
|
1877
|
-
for issue in status["issues"]:
|
|
1878
|
-
self.console.print(f" - {issue}")
|
|
1879
|
-
|
|
1880
|
-
return CommandResult.success_result(
|
|
1881
|
-
"Hook verification complete", data=status
|
|
1882
|
-
)
|
|
1883
|
-
|
|
1884
|
-
except ImportError:
|
|
1885
|
-
self.console.print("[red]Error: HookInstaller module not found[/red]")
|
|
1886
|
-
return CommandResult.error_result("HookInstaller module not found")
|
|
1887
|
-
except Exception as e:
|
|
1888
|
-
self.logger.error(f"Hook verification error: {e}", exc_info=True)
|
|
1889
|
-
return CommandResult.error_result(f"Hook verification failed: {e}")
|
|
764
|
+
# Share logger with hook manager for consistent error logging
|
|
765
|
+
self.hook_manager.logger = self.logger
|
|
766
|
+
return self.hook_manager.verify_hooks()
|
|
1890
767
|
|
|
1891
768
|
def _uninstall_hooks(self) -> CommandResult:
|
|
1892
769
|
"""Uninstall Claude MPM hooks."""
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
installer = HookInstaller()
|
|
1897
|
-
|
|
1898
|
-
# Confirm uninstallation
|
|
1899
|
-
if not Confirm.ask(
|
|
1900
|
-
"[yellow]Are you sure you want to uninstall Claude MPM hooks?[/yellow]"
|
|
1901
|
-
):
|
|
1902
|
-
return CommandResult.success_result("Uninstallation cancelled")
|
|
1903
|
-
|
|
1904
|
-
self.console.print("[cyan]Uninstalling Claude MPM hooks...[/cyan]")
|
|
1905
|
-
success = installer.uninstall_hooks()
|
|
1906
|
-
|
|
1907
|
-
if success:
|
|
1908
|
-
self.console.print("[green]✓ Hooks uninstalled successfully![/green]")
|
|
1909
|
-
return CommandResult.success_result("Hooks uninstalled successfully")
|
|
1910
|
-
self.console.print("[red]✗ Hook uninstallation failed[/red]")
|
|
1911
|
-
return CommandResult.error_result("Hook uninstallation failed")
|
|
1912
|
-
|
|
1913
|
-
except ImportError:
|
|
1914
|
-
self.console.print("[red]Error: HookInstaller module not found[/red]")
|
|
1915
|
-
return CommandResult.error_result("HookInstaller module not found")
|
|
1916
|
-
except Exception as e:
|
|
1917
|
-
self.logger.error(f"Hook uninstallation error: {e}", exc_info=True)
|
|
1918
|
-
return CommandResult.error_result(f"Hook uninstallation failed: {e}")
|
|
770
|
+
# Share logger with hook manager for consistent error logging
|
|
771
|
+
self.hook_manager.logger = self.logger
|
|
772
|
+
return self.hook_manager.uninstall_hooks()
|
|
1919
773
|
|
|
1920
774
|
def _run_agent_management(self) -> CommandResult:
|
|
1921
775
|
"""Jump directly to agent management."""
|
|
@@ -1939,13 +793,7 @@ Directory: {self.project_dir}
|
|
|
1939
793
|
|
|
1940
794
|
def _run_behavior_management(self) -> CommandResult:
|
|
1941
795
|
"""Jump directly to behavior management."""
|
|
1942
|
-
|
|
1943
|
-
self._manage_behaviors()
|
|
1944
|
-
return CommandResult.success_result("Behavior management completed")
|
|
1945
|
-
except KeyboardInterrupt:
|
|
1946
|
-
return CommandResult.success_result("Behavior management cancelled")
|
|
1947
|
-
except Exception as e:
|
|
1948
|
-
return CommandResult.error_result(f"Behavior management failed: {e}")
|
|
796
|
+
return self.behavior_manager.run_behavior_management()
|
|
1949
797
|
|
|
1950
798
|
def _run_startup_configuration(self) -> CommandResult:
|
|
1951
799
|
"""Jump directly to startup configuration."""
|