claude-mpm 1.0.0__py3-none-any.whl → 1.1.0__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.py +3 -2
- claude_mpm/agents/__init__.py +2 -2
- claude_mpm/agents/agent-template.yaml +83 -0
- claude_mpm/agents/agent_loader.py +66 -90
- claude_mpm/agents/base_agent_loader.py +10 -15
- claude_mpm/cli.py +41 -47
- claude_mpm/cli_enhancements.py +297 -0
- claude_mpm/core/factories.py +1 -46
- claude_mpm/core/service_registry.py +0 -8
- claude_mpm/core/simple_runner.py +43 -0
- claude_mpm/generators/__init__.py +5 -0
- claude_mpm/generators/agent_profile_generator.py +137 -0
- claude_mpm/hooks/README.md +75 -221
- claude_mpm/hooks/builtin/mpm_command_hook.py +125 -0
- claude_mpm/hooks/claude_hooks/__init__.py +5 -0
- claude_mpm/hooks/claude_hooks/hook_handler.py +399 -0
- claude_mpm/hooks/claude_hooks/hook_wrapper.sh +47 -0
- claude_mpm/hooks/validation_hooks.py +181 -0
- claude_mpm/services/agent_management_service.py +4 -4
- claude_mpm/services/agent_profile_loader.py +1 -1
- claude_mpm/services/agent_registry.py +0 -1
- claude_mpm/services/base_agent_manager.py +3 -3
- claude_mpm/utils/error_handler.py +247 -0
- claude_mpm/validation/__init__.py +5 -0
- claude_mpm/validation/agent_validator.py +175 -0
- {claude_mpm-1.0.0.dist-info → claude_mpm-1.1.0.dist-info}/METADATA +44 -7
- {claude_mpm-1.0.0.dist-info → claude_mpm-1.1.0.dist-info}/RECORD +30 -26
- claude_mpm/config/hook_config.py +0 -42
- claude_mpm/hooks/hook_client.py +0 -264
- claude_mpm/hooks/hook_runner.py +0 -370
- claude_mpm/hooks/json_rpc_executor.py +0 -259
- claude_mpm/hooks/json_rpc_hook_client.py +0 -319
- claude_mpm/services/hook_service.py +0 -388
- claude_mpm/services/hook_service_manager.py +0 -223
- claude_mpm/services/json_rpc_hook_manager.py +0 -92
- {claude_mpm-1.0.0.dist-info → claude_mpm-1.1.0.dist-info}/WHEEL +0 -0
- {claude_mpm-1.0.0.dist-info → claude_mpm-1.1.0.dist-info}/entry_points.txt +0 -0
- {claude_mpm-1.0.0.dist-info → claude_mpm-1.1.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Enhanced CLI operations for claude-mpm.
|
|
3
|
+
|
|
4
|
+
Implements error handling and user guidance patterns from awesome-claude-code.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
import sys
|
|
9
|
+
import logging
|
|
10
|
+
from typing import Optional, Any, Dict, List
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
from claude_mpm.utils.error_handler import (
|
|
14
|
+
MPMError, ErrorContext, handle_errors, suggest_setup_fix
|
|
15
|
+
)
|
|
16
|
+
from claude_mpm.validation import AgentValidator, ValidationResult
|
|
17
|
+
from claude_mpm.hooks.validation_hooks import ValidationHooks, ValidationError
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class CLIContext:
|
|
23
|
+
"""Enhanced CLI context with validation and error handling."""
|
|
24
|
+
|
|
25
|
+
def __init__(self):
|
|
26
|
+
"""Initialize CLI context."""
|
|
27
|
+
self.validator = AgentValidator()
|
|
28
|
+
self.validation_hooks = ValidationHooks()
|
|
29
|
+
self.debug = False
|
|
30
|
+
self.dry_run = False
|
|
31
|
+
|
|
32
|
+
def setup_logging(self, debug: bool = False) -> None:
|
|
33
|
+
"""Setup logging based on debug flag."""
|
|
34
|
+
level = logging.DEBUG if debug else logging.INFO
|
|
35
|
+
format_str = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" if debug else "%(message)s"
|
|
36
|
+
|
|
37
|
+
logging.basicConfig(
|
|
38
|
+
level=level,
|
|
39
|
+
format=format_str,
|
|
40
|
+
handlers=[logging.StreamHandler(sys.stdout)]
|
|
41
|
+
)
|
|
42
|
+
self.debug = debug
|
|
43
|
+
|
|
44
|
+
def validate_prerequisites(self) -> bool:
|
|
45
|
+
"""Validate system prerequisites."""
|
|
46
|
+
print("Checking prerequisites...")
|
|
47
|
+
all_passed = True
|
|
48
|
+
|
|
49
|
+
# Check Python version
|
|
50
|
+
if sys.version_info < (3, 11):
|
|
51
|
+
print("❌ Python 3.11 or higher is required")
|
|
52
|
+
all_passed = False
|
|
53
|
+
else:
|
|
54
|
+
print("✓ Python version is compatible")
|
|
55
|
+
|
|
56
|
+
# Check required directories
|
|
57
|
+
required_dirs = [
|
|
58
|
+
Path.home() / '.claude-mpm',
|
|
59
|
+
Path.home() / '.claude-mpm' / 'profiles',
|
|
60
|
+
Path.home() / '.claude-mpm' / 'logs',
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
for dir_path in required_dirs:
|
|
64
|
+
if not dir_path.exists():
|
|
65
|
+
try:
|
|
66
|
+
dir_path.mkdir(parents=True)
|
|
67
|
+
print(f"✓ Created directory: {dir_path}")
|
|
68
|
+
except Exception as e:
|
|
69
|
+
print(f"❌ Failed to create directory {dir_path}: {e}")
|
|
70
|
+
all_passed = False
|
|
71
|
+
|
|
72
|
+
return all_passed
|
|
73
|
+
|
|
74
|
+
def handle_validation_result(self, result: ValidationResult,
|
|
75
|
+
operation: str = "operation") -> None:
|
|
76
|
+
"""Handle validation results with user-friendly output."""
|
|
77
|
+
if result.is_valid:
|
|
78
|
+
if result.warnings:
|
|
79
|
+
print(f"⚠️ {operation} completed with warnings:")
|
|
80
|
+
for warning in result.warnings:
|
|
81
|
+
print(f" - {warning}")
|
|
82
|
+
else:
|
|
83
|
+
print(f"✅ {operation} completed successfully")
|
|
84
|
+
else:
|
|
85
|
+
print(f"❌ {operation} failed validation:")
|
|
86
|
+
for error in result.errors:
|
|
87
|
+
print(f" - {error}")
|
|
88
|
+
|
|
89
|
+
if result.warnings:
|
|
90
|
+
print("\nAdditional warnings:")
|
|
91
|
+
for warning in result.warnings:
|
|
92
|
+
print(f" - {warning}")
|
|
93
|
+
|
|
94
|
+
sys.exit(1)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def create_enhanced_cli() -> click.Group:
|
|
98
|
+
"""Create enhanced CLI with better error handling."""
|
|
99
|
+
cli_context = CLIContext()
|
|
100
|
+
|
|
101
|
+
@click.group()
|
|
102
|
+
@click.option('--debug', is_flag=True, help='Enable debug logging')
|
|
103
|
+
@click.option('--dry-run', is_flag=True, help='Run without making changes')
|
|
104
|
+
@click.pass_context
|
|
105
|
+
def cli(ctx, debug: bool, dry_run: bool):
|
|
106
|
+
"""Enhanced claude-mpm CLI with validation and error handling."""
|
|
107
|
+
ctx.obj = cli_context
|
|
108
|
+
cli_context.setup_logging(debug)
|
|
109
|
+
cli_context.dry_run = dry_run
|
|
110
|
+
|
|
111
|
+
if debug:
|
|
112
|
+
print("🐛 Debug mode enabled")
|
|
113
|
+
if dry_run:
|
|
114
|
+
print("🏃 Dry-run mode enabled")
|
|
115
|
+
|
|
116
|
+
@cli.command()
|
|
117
|
+
@click.pass_context
|
|
118
|
+
def validate_setup(ctx):
|
|
119
|
+
"""Validate system setup and prerequisites."""
|
|
120
|
+
cli_ctx = ctx.obj
|
|
121
|
+
|
|
122
|
+
with ErrorContext("setup validation"):
|
|
123
|
+
if cli_ctx.validate_prerequisites():
|
|
124
|
+
print("\n✅ All prerequisites satisfied!")
|
|
125
|
+
print(" You're ready to use claude-mpm")
|
|
126
|
+
else:
|
|
127
|
+
print("\n❌ Setup validation failed")
|
|
128
|
+
print("\n💡 Setup hints:")
|
|
129
|
+
print(" - Ensure Python 3.11+ is installed")
|
|
130
|
+
print(" - Check file permissions for ~/.claude-mpm")
|
|
131
|
+
print(" - Run with --debug for more details")
|
|
132
|
+
sys.exit(1)
|
|
133
|
+
|
|
134
|
+
@cli.command()
|
|
135
|
+
@click.argument('profile_path', type=click.Path(exists=True))
|
|
136
|
+
@click.pass_context
|
|
137
|
+
@handle_errors(MPMError)
|
|
138
|
+
async def validate_profile(ctx, profile_path: str):
|
|
139
|
+
"""Validate an agent profile."""
|
|
140
|
+
cli_ctx = ctx.obj
|
|
141
|
+
profile = Path(profile_path)
|
|
142
|
+
|
|
143
|
+
print(f"Validating profile: {profile.name}")
|
|
144
|
+
|
|
145
|
+
# Run pre-load validation
|
|
146
|
+
result = await cli_ctx.validation_hooks.run_pre_load_validation(profile)
|
|
147
|
+
|
|
148
|
+
cli_ctx.handle_validation_result(result, f"Profile validation for {profile.name}")
|
|
149
|
+
|
|
150
|
+
if result.is_valid and result.locked_fields:
|
|
151
|
+
print(f"\n🔒 Locked fields: {', '.join(result.locked_fields)}")
|
|
152
|
+
|
|
153
|
+
@cli.command()
|
|
154
|
+
@click.option('--profile', '-p', help='Agent profile to load')
|
|
155
|
+
@click.option('--task', '-t', help='Task to execute')
|
|
156
|
+
@click.pass_context
|
|
157
|
+
@handle_errors(MPMError)
|
|
158
|
+
async def run_agent(ctx, profile: str, task: str):
|
|
159
|
+
"""Run an agent with enhanced validation."""
|
|
160
|
+
cli_ctx = ctx.obj
|
|
161
|
+
|
|
162
|
+
if not profile or not task:
|
|
163
|
+
raise click.UsageError("Both --profile and --task are required")
|
|
164
|
+
|
|
165
|
+
# Validate profile exists
|
|
166
|
+
profile_path = Path(profile)
|
|
167
|
+
if not profile_path.exists():
|
|
168
|
+
# Try default locations
|
|
169
|
+
default_locations = [
|
|
170
|
+
Path.home() / '.claude-mpm' / 'profiles' / f"{profile}.yaml",
|
|
171
|
+
Path.cwd() / 'agents' / f"{profile}.yaml",
|
|
172
|
+
]
|
|
173
|
+
|
|
174
|
+
for location in default_locations:
|
|
175
|
+
if location.exists():
|
|
176
|
+
profile_path = location
|
|
177
|
+
break
|
|
178
|
+
else:
|
|
179
|
+
raise MPMError(
|
|
180
|
+
f"Profile '{profile}' not found",
|
|
181
|
+
details={'searched_locations': [str(p) for p in default_locations]},
|
|
182
|
+
suggestions=[
|
|
183
|
+
"Check the profile name",
|
|
184
|
+
"Use 'mpm list-profiles' to see available profiles",
|
|
185
|
+
"Create a new profile with 'mpm create-profile'"
|
|
186
|
+
]
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
# Run validation
|
|
190
|
+
print(f"🔍 Validating profile: {profile_path.name}")
|
|
191
|
+
validation_result = await cli_ctx.validation_hooks.run_pre_load_validation(profile_path)
|
|
192
|
+
|
|
193
|
+
if not validation_result.is_valid:
|
|
194
|
+
cli_ctx.handle_validation_result(validation_result, "Profile validation")
|
|
195
|
+
return
|
|
196
|
+
|
|
197
|
+
# Validate task
|
|
198
|
+
print(f"🔍 Validating task...")
|
|
199
|
+
task_result = await cli_ctx.validation_hooks.run_pre_execute_validation(
|
|
200
|
+
profile_path.stem, task
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
if not task_result.is_valid:
|
|
204
|
+
cli_ctx.handle_validation_result(task_result, "Task validation")
|
|
205
|
+
return
|
|
206
|
+
|
|
207
|
+
if cli_ctx.dry_run:
|
|
208
|
+
print("\n🏃 Dry-run mode - would execute:")
|
|
209
|
+
print(f" Profile: {profile_path}")
|
|
210
|
+
print(f" Task: {task[:100]}{'...' if len(task) > 100 else ''}")
|
|
211
|
+
else:
|
|
212
|
+
print(f"\n🚀 Executing agent from {profile_path.name}...")
|
|
213
|
+
# Actual execution would go here
|
|
214
|
+
print("✅ Agent execution completed")
|
|
215
|
+
|
|
216
|
+
@cli.command()
|
|
217
|
+
@click.pass_context
|
|
218
|
+
def list_profiles(ctx):
|
|
219
|
+
"""List available agent profiles."""
|
|
220
|
+
cli_ctx = ctx.obj
|
|
221
|
+
|
|
222
|
+
profile_locations = [
|
|
223
|
+
Path.home() / '.claude-mpm' / 'profiles',
|
|
224
|
+
Path.cwd() / 'agents',
|
|
225
|
+
]
|
|
226
|
+
|
|
227
|
+
all_profiles = []
|
|
228
|
+
for location in profile_locations:
|
|
229
|
+
if location.exists():
|
|
230
|
+
profiles = list(location.glob('*.yaml')) + list(location.glob('*.yml'))
|
|
231
|
+
all_profiles.extend(profiles)
|
|
232
|
+
|
|
233
|
+
if not all_profiles:
|
|
234
|
+
print("No agent profiles found")
|
|
235
|
+
print("\n💡 Create a profile with: mpm create-profile")
|
|
236
|
+
return
|
|
237
|
+
|
|
238
|
+
print("Available agent profiles:")
|
|
239
|
+
for profile in sorted(all_profiles):
|
|
240
|
+
# Quick validation check
|
|
241
|
+
result = cli_ctx.validator.validate_profile(profile)
|
|
242
|
+
status = "✅" if result.is_valid else "⚠️"
|
|
243
|
+
print(f" {status} {profile.stem} ({profile})")
|
|
244
|
+
|
|
245
|
+
@cli.command()
|
|
246
|
+
@click.argument('name')
|
|
247
|
+
@click.option('--role', '-r', required=True, help='Agent role')
|
|
248
|
+
@click.option('--category', '-c', default='analysis', help='Agent category')
|
|
249
|
+
@click.pass_context
|
|
250
|
+
def create_profile(ctx, name: str, role: str, category: str):
|
|
251
|
+
"""Create a new agent profile from template."""
|
|
252
|
+
from claude_mpm.generators import AgentProfileGenerator
|
|
253
|
+
|
|
254
|
+
cli_ctx = ctx.obj
|
|
255
|
+
generator = AgentProfileGenerator()
|
|
256
|
+
|
|
257
|
+
print(f"Creating agent profile: {name}")
|
|
258
|
+
|
|
259
|
+
# Generate configuration
|
|
260
|
+
config = generator.create_agent_from_template(name, role, category)
|
|
261
|
+
|
|
262
|
+
# Generate profile
|
|
263
|
+
profile_content = generator.generate_profile(config)
|
|
264
|
+
|
|
265
|
+
# Save profile
|
|
266
|
+
profile_dir = Path.home() / '.claude-mpm' / 'profiles'
|
|
267
|
+
profile_dir.mkdir(parents=True, exist_ok=True)
|
|
268
|
+
|
|
269
|
+
profile_path = profile_dir / f"{name.lower().replace(' ', '_')}.yaml"
|
|
270
|
+
|
|
271
|
+
if profile_path.exists() and not click.confirm(f"Profile {profile_path} exists. Overwrite?"):
|
|
272
|
+
print("Aborted")
|
|
273
|
+
return
|
|
274
|
+
|
|
275
|
+
if cli_ctx.dry_run:
|
|
276
|
+
print(f"\n🏃 Dry-run mode - would create {profile_path}:")
|
|
277
|
+
print("---")
|
|
278
|
+
print(profile_content[:500] + "..." if len(profile_content) > 500 else profile_content)
|
|
279
|
+
print("---")
|
|
280
|
+
else:
|
|
281
|
+
profile_path.write_text(profile_content)
|
|
282
|
+
print(f"✅ Created profile: {profile_path}")
|
|
283
|
+
|
|
284
|
+
# Generate documentation
|
|
285
|
+
doc_path = profile_path.with_suffix('.md')
|
|
286
|
+
doc_content = generator.generate_agent_documentation(config)
|
|
287
|
+
doc_path.write_text(doc_content)
|
|
288
|
+
print(f"📝 Created documentation: {doc_path}")
|
|
289
|
+
|
|
290
|
+
return cli
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
# Export the enhanced CLI
|
|
294
|
+
enhanced_cli = create_enhanced_cli()
|
|
295
|
+
|
|
296
|
+
if __name__ == '__main__':
|
|
297
|
+
enhanced_cli()
|
claude_mpm/core/factories.py
CHANGED
|
@@ -12,7 +12,6 @@ from typing import Any, Dict, Optional, Type, TypeVar
|
|
|
12
12
|
from .container import DIContainer
|
|
13
13
|
from .logger import get_logger
|
|
14
14
|
from .config import Config
|
|
15
|
-
from ..services.json_rpc_hook_manager import JSONRPCHookManager
|
|
16
15
|
from ..services.agent_deployment import AgentDeploymentService
|
|
17
16
|
from ..orchestration.factory import OrchestratorFactory
|
|
18
17
|
from ..orchestration.base import BaseOrchestrator
|
|
@@ -31,47 +30,6 @@ class ServiceFactory(ABC):
|
|
|
31
30
|
pass
|
|
32
31
|
|
|
33
32
|
|
|
34
|
-
class HookManagerFactory(ServiceFactory):
|
|
35
|
-
"""Factory for creating hook manager instances."""
|
|
36
|
-
|
|
37
|
-
def create(
|
|
38
|
-
self,
|
|
39
|
-
container: DIContainer,
|
|
40
|
-
log_dir: Optional[Path] = None,
|
|
41
|
-
enabled: bool = True
|
|
42
|
-
) -> Optional[JSONRPCHookManager]:
|
|
43
|
-
"""
|
|
44
|
-
Create a hook manager instance.
|
|
45
|
-
|
|
46
|
-
Args:
|
|
47
|
-
container: DI container
|
|
48
|
-
log_dir: Log directory path
|
|
49
|
-
enabled: Whether hooks are enabled
|
|
50
|
-
|
|
51
|
-
Returns:
|
|
52
|
-
Hook manager instance or None if disabled
|
|
53
|
-
"""
|
|
54
|
-
config = container.resolve(Config)
|
|
55
|
-
|
|
56
|
-
# Check if hooks are enabled
|
|
57
|
-
if not enabled or config.get('hooks.enabled', True) is False:
|
|
58
|
-
logger.info("Hooks are disabled")
|
|
59
|
-
return None
|
|
60
|
-
|
|
61
|
-
# Get log directory from config if not provided
|
|
62
|
-
if log_dir is None:
|
|
63
|
-
log_dir = Path(config.get('log_dir', '.claude-mpm/logs'))
|
|
64
|
-
|
|
65
|
-
# Create hook manager
|
|
66
|
-
hook_manager = JSONRPCHookManager(log_dir=log_dir)
|
|
67
|
-
|
|
68
|
-
# Start the service
|
|
69
|
-
if hook_manager.start_service():
|
|
70
|
-
logger.info("Hook manager initialized successfully")
|
|
71
|
-
return hook_manager
|
|
72
|
-
else:
|
|
73
|
-
logger.warning("Failed to start hook manager")
|
|
74
|
-
return None
|
|
75
33
|
|
|
76
34
|
|
|
77
35
|
class OrchestratorFactoryWrapper(ServiceFactory):
|
|
@@ -110,9 +68,7 @@ class OrchestratorFactoryWrapper(ServiceFactory):
|
|
|
110
68
|
# Merge with provided kwargs
|
|
111
69
|
orch_config.update(kwargs)
|
|
112
70
|
|
|
113
|
-
#
|
|
114
|
-
if 'hook_manager' not in orch_config and container.is_registered(JSONRPCHookManager):
|
|
115
|
-
orch_config['hook_manager'] = container.resolve_optional(JSONRPCHookManager)
|
|
71
|
+
# No hook manager injection needed - Claude Code hooks are external
|
|
116
72
|
|
|
117
73
|
# Create orchestrator
|
|
118
74
|
orchestrator = self._factory.create_orchestrator(
|
|
@@ -248,7 +204,6 @@ class FactoryRegistry:
|
|
|
248
204
|
def __init__(self):
|
|
249
205
|
"""Initialize factory registry."""
|
|
250
206
|
self._factories: Dict[str, ServiceFactory] = {
|
|
251
|
-
'hook_manager': HookManagerFactory(),
|
|
252
207
|
'orchestrator': OrchestratorFactoryWrapper(),
|
|
253
208
|
'agent_service': AgentServiceFactory(),
|
|
254
209
|
'session_manager': SessionManagerFactory(),
|
|
@@ -37,7 +37,6 @@ class ServiceRegistry:
|
|
|
37
37
|
def register_core_services(self) -> None:
|
|
38
38
|
"""Register all core framework services."""
|
|
39
39
|
from ..services.shared_prompt_cache import SharedPromptCache
|
|
40
|
-
from ..services.json_rpc_hook_manager import JSONRPCHookManager
|
|
41
40
|
from ..services.ticket_manager import TicketManager
|
|
42
41
|
from ..services.agent_deployment import AgentDeploymentService
|
|
43
42
|
from .session_manager import SessionManager
|
|
@@ -72,13 +71,6 @@ class ServiceRegistry:
|
|
|
72
71
|
factory=lambda c: SharedPromptCache.get_instance()
|
|
73
72
|
)
|
|
74
73
|
|
|
75
|
-
# Register hook manager
|
|
76
|
-
self.register_service(
|
|
77
|
-
"hook_manager",
|
|
78
|
-
JSONRPCHookManager,
|
|
79
|
-
lifetime=ServiceLifetime.SINGLETON
|
|
80
|
-
)
|
|
81
|
-
|
|
82
74
|
# Register ticket manager
|
|
83
75
|
self.register_service(
|
|
84
76
|
"ticket_manager",
|
claude_mpm/core/simple_runner.py
CHANGED
|
@@ -254,6 +254,10 @@ class SimpleClaudeRunner:
|
|
|
254
254
|
"""Run Claude with a single prompt and return success status."""
|
|
255
255
|
start_time = time.time()
|
|
256
256
|
|
|
257
|
+
# Check for /mpm: commands
|
|
258
|
+
if prompt.strip().startswith("/mpm:"):
|
|
259
|
+
return self._handle_mpm_command(prompt.strip())
|
|
260
|
+
|
|
257
261
|
if self.project_logger:
|
|
258
262
|
self.project_logger.log_system(
|
|
259
263
|
f"Starting non-interactive session with prompt: {prompt[:100]}",
|
|
@@ -460,6 +464,45 @@ class SimpleClaudeRunner:
|
|
|
460
464
|
text_lower = text.lower()
|
|
461
465
|
return any(pattern.lower() in text_lower for pattern in delegation_patterns)
|
|
462
466
|
|
|
467
|
+
def _handle_mpm_command(self, prompt: str) -> bool:
|
|
468
|
+
"""Handle /mpm: commands directly without going to Claude."""
|
|
469
|
+
try:
|
|
470
|
+
# Extract command and arguments
|
|
471
|
+
command_line = prompt[5:].strip() # Remove "/mpm:"
|
|
472
|
+
parts = command_line.split()
|
|
473
|
+
|
|
474
|
+
if not parts:
|
|
475
|
+
print("No command specified. Available commands: test")
|
|
476
|
+
return True
|
|
477
|
+
|
|
478
|
+
command = parts[0]
|
|
479
|
+
args = parts[1:]
|
|
480
|
+
|
|
481
|
+
# Handle commands
|
|
482
|
+
if command == "test":
|
|
483
|
+
print("Hello World")
|
|
484
|
+
if self.project_logger:
|
|
485
|
+
self.project_logger.log_system(
|
|
486
|
+
"Executed /mpm:test command",
|
|
487
|
+
level="INFO",
|
|
488
|
+
component="command"
|
|
489
|
+
)
|
|
490
|
+
return True
|
|
491
|
+
else:
|
|
492
|
+
print(f"Unknown command: {command}")
|
|
493
|
+
print("Available commands: test")
|
|
494
|
+
return True
|
|
495
|
+
|
|
496
|
+
except Exception as e:
|
|
497
|
+
print(f"Error executing command: {e}")
|
|
498
|
+
if self.project_logger:
|
|
499
|
+
self.project_logger.log_system(
|
|
500
|
+
f"Failed to execute /mpm: command: {e}",
|
|
501
|
+
level="ERROR",
|
|
502
|
+
component="command"
|
|
503
|
+
)
|
|
504
|
+
return False
|
|
505
|
+
|
|
463
506
|
def _log_session_event(self, event_data: dict):
|
|
464
507
|
"""Log an event to the session log file."""
|
|
465
508
|
if self.session_log_file:
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent profile generator using template system.
|
|
3
|
+
|
|
4
|
+
Inspired by awesome-claude-code's template generation approach.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import yaml
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from typing import Dict, Any, Optional
|
|
11
|
+
import re
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AgentProfileGenerator:
|
|
15
|
+
"""Generates agent profiles from templates."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, template_path: Optional[Path] = None):
|
|
18
|
+
"""Initialize the generator with a template path."""
|
|
19
|
+
self.template_path = template_path or Path(__file__).parent.parent / "agents" / "agent-template.yaml"
|
|
20
|
+
self.template = self._load_template()
|
|
21
|
+
|
|
22
|
+
def _load_template(self) -> Dict[str, Any]:
|
|
23
|
+
"""Load the agent profile template."""
|
|
24
|
+
if not self.template_path.exists():
|
|
25
|
+
raise FileNotFoundError(f"Template not found: {self.template_path}")
|
|
26
|
+
|
|
27
|
+
with open(self.template_path, 'r') as f:
|
|
28
|
+
return yaml.safe_load(f)
|
|
29
|
+
|
|
30
|
+
def generate_profile(self, config: Dict[str, Any]) -> str:
|
|
31
|
+
"""Generate an agent profile from configuration."""
|
|
32
|
+
# Set default values
|
|
33
|
+
config.setdefault('VERSION', '1.0.0')
|
|
34
|
+
config.setdefault('CREATED_DATE', datetime.now().strftime('%Y-%m-%d'))
|
|
35
|
+
config.setdefault('AUTHOR', 'claude-mpm')
|
|
36
|
+
|
|
37
|
+
# Convert template to string
|
|
38
|
+
template_str = yaml.dump(self.template, default_flow_style=False)
|
|
39
|
+
|
|
40
|
+
# Replace placeholders
|
|
41
|
+
result = self._replace_placeholders(template_str, config)
|
|
42
|
+
|
|
43
|
+
# Clean up any remaining placeholders
|
|
44
|
+
result = re.sub(r'\{\{[^}]+\}\}', '', result)
|
|
45
|
+
|
|
46
|
+
return result
|
|
47
|
+
|
|
48
|
+
def _replace_placeholders(self, template: str, values: Dict[str, Any]) -> str:
|
|
49
|
+
"""Replace template placeholders with actual values."""
|
|
50
|
+
for key, value in values.items():
|
|
51
|
+
placeholder = f"{{{{{key}}}}}"
|
|
52
|
+
if isinstance(value, list):
|
|
53
|
+
# Format lists nicely
|
|
54
|
+
formatted_list = '\n'.join(f" - \"{item}\"" for item in value)
|
|
55
|
+
template = template.replace(placeholder, formatted_list)
|
|
56
|
+
else:
|
|
57
|
+
template = template.replace(placeholder, str(value))
|
|
58
|
+
|
|
59
|
+
return template
|
|
60
|
+
|
|
61
|
+
def generate_agent_documentation(self, agent_config: Dict[str, Any]) -> str:
|
|
62
|
+
"""Generate markdown documentation for an agent."""
|
|
63
|
+
doc_lines = []
|
|
64
|
+
|
|
65
|
+
# Header
|
|
66
|
+
doc_lines.append(f"# {agent_config.get('name', 'Agent')} Documentation")
|
|
67
|
+
doc_lines.append("")
|
|
68
|
+
|
|
69
|
+
# Description
|
|
70
|
+
if 'description' in agent_config:
|
|
71
|
+
doc_lines.append("## Description")
|
|
72
|
+
doc_lines.append(agent_config['description'])
|
|
73
|
+
doc_lines.append("")
|
|
74
|
+
|
|
75
|
+
# Capabilities
|
|
76
|
+
if 'capabilities' in agent_config:
|
|
77
|
+
doc_lines.append("## Capabilities")
|
|
78
|
+
for capability in agent_config['capabilities']:
|
|
79
|
+
doc_lines.append(f"- {capability}")
|
|
80
|
+
doc_lines.append("")
|
|
81
|
+
|
|
82
|
+
# Tools
|
|
83
|
+
if 'tools' in agent_config:
|
|
84
|
+
doc_lines.append("## Required Tools")
|
|
85
|
+
for tool in agent_config['tools']:
|
|
86
|
+
if isinstance(tool, dict):
|
|
87
|
+
doc_lines.append(f"- **{tool['name']}**: {tool.get('description', '')}")
|
|
88
|
+
else:
|
|
89
|
+
doc_lines.append(f"- {tool}")
|
|
90
|
+
doc_lines.append("")
|
|
91
|
+
|
|
92
|
+
# Examples
|
|
93
|
+
if 'examples' in agent_config:
|
|
94
|
+
doc_lines.append("## Usage Examples")
|
|
95
|
+
for i, example in enumerate(agent_config['examples'], 1):
|
|
96
|
+
doc_lines.append(f"### Example {i}: {example.get('scenario', 'Scenario')}")
|
|
97
|
+
doc_lines.append("```")
|
|
98
|
+
doc_lines.append(f"Input: {example.get('input', '')}")
|
|
99
|
+
doc_lines.append("```")
|
|
100
|
+
doc_lines.append("Expected Output:")
|
|
101
|
+
doc_lines.append("```")
|
|
102
|
+
doc_lines.append(example.get('expected_output', ''))
|
|
103
|
+
doc_lines.append("```")
|
|
104
|
+
doc_lines.append("")
|
|
105
|
+
|
|
106
|
+
# Best Practices
|
|
107
|
+
if 'best_practices' in agent_config:
|
|
108
|
+
doc_lines.append("## Best Practices")
|
|
109
|
+
for practice in agent_config['best_practices']:
|
|
110
|
+
doc_lines.append(f"- {practice}")
|
|
111
|
+
doc_lines.append("")
|
|
112
|
+
|
|
113
|
+
return '\n'.join(doc_lines)
|
|
114
|
+
|
|
115
|
+
def create_agent_from_template(self, agent_name: str, role: str,
|
|
116
|
+
category: str = "analysis") -> Dict[str, Any]:
|
|
117
|
+
"""Create a new agent configuration from template."""
|
|
118
|
+
return {
|
|
119
|
+
'AGENT_NAME': agent_name,
|
|
120
|
+
'AGENT_ID': agent_name.lower().replace(' ', '_'),
|
|
121
|
+
'ROLE': role,
|
|
122
|
+
'CATEGORY': category,
|
|
123
|
+
'DESCRIPTION': f"Agent profile for {agent_name}",
|
|
124
|
+
'AGENT_DESCRIPTION': f"Specialized {role} agent",
|
|
125
|
+
'SPECIALIZATION': role.lower(),
|
|
126
|
+
'CAPABILITIES_LIST': "- Analyze code structure\n- Identify patterns\n- Generate reports",
|
|
127
|
+
'CONSTRAINTS_LIST': "- Read-only operations\n- Respect file permissions",
|
|
128
|
+
'TOOL_NAME': 'code_analysis',
|
|
129
|
+
'TOOL_DESCRIPTION': 'Analyzes code structure and patterns',
|
|
130
|
+
'EXAMPLE_SCENARIO': 'Analyzing a Python project',
|
|
131
|
+
'EXAMPLE_INPUT': 'Analyze the architecture of this codebase',
|
|
132
|
+
'EXAMPLE_OUTPUT': 'Detailed analysis report with recommendations',
|
|
133
|
+
'BEST_PRACTICE_1': 'Always validate input parameters',
|
|
134
|
+
'BEST_PRACTICE_2': 'Provide clear, actionable insights',
|
|
135
|
+
'BEST_PRACTICE_3': 'Include examples in reports',
|
|
136
|
+
'ADDITIONAL_INSTRUCTIONS': 'Be thorough but concise in your analysis.'
|
|
137
|
+
}
|