claude-mpm 4.0.28__py3-none-any.whl → 4.0.29__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. claude_mpm/agents/templates/agent-manager.json +24 -0
  2. claude_mpm/agents/templates/agent-manager.md +304 -0
  3. claude_mpm/cli/__init__.py +2 -0
  4. claude_mpm/cli/commands/__init__.py +2 -0
  5. claude_mpm/cli/commands/agent_manager.py +517 -0
  6. claude_mpm/cli/commands/memory.py +1 -1
  7. claude_mpm/cli/parsers/agent_manager_parser.py +247 -0
  8. claude_mpm/cli/parsers/base_parser.py +7 -0
  9. claude_mpm/cli/shared/__init__.py +1 -1
  10. claude_mpm/constants.py +1 -0
  11. claude_mpm/core/claude_runner.py +3 -2
  12. claude_mpm/core/constants.py +2 -2
  13. claude_mpm/core/socketio_pool.py +2 -2
  14. claude_mpm/dashboard/static/built/components/event-viewer.js +1 -1
  15. claude_mpm/dashboard/static/built/components/module-viewer.js +1 -1
  16. claude_mpm/dashboard/static/built/dashboard.js +1 -1
  17. claude_mpm/dashboard/static/built/socket-client.js +1 -1
  18. claude_mpm/dashboard/static/css/dashboard.css +170 -0
  19. claude_mpm/dashboard/static/dist/components/module-viewer.js +1 -1
  20. claude_mpm/dashboard/static/dist/dashboard.js +1 -1
  21. claude_mpm/dashboard/static/dist/socket-client.js +1 -1
  22. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +21 -3
  23. claude_mpm/dashboard/static/js/components/module-viewer.js +129 -1
  24. claude_mpm/dashboard/static/js/dashboard.js +116 -0
  25. claude_mpm/dashboard/static/js/socket-client.js +0 -1
  26. claude_mpm/hooks/claude_hooks/connection_pool.py +1 -1
  27. claude_mpm/hooks/claude_hooks/hook_handler.py +1 -1
  28. claude_mpm/services/agents/agent_builder.py +455 -0
  29. claude_mpm/services/agents/deployment/agent_template_builder.py +10 -3
  30. claude_mpm/services/memory/__init__.py +2 -0
  31. claude_mpm/services/socketio/handlers/connection.py +27 -33
  32. {claude_mpm-4.0.28.dist-info → claude_mpm-4.0.29.dist-info}/METADATA +1 -1
  33. {claude_mpm-4.0.28.dist-info → claude_mpm-4.0.29.dist-info}/RECORD +38 -33
  34. /claude_mpm/cli/shared/{command_base.py → base_command.py} +0 -0
  35. {claude_mpm-4.0.28.dist-info → claude_mpm-4.0.29.dist-info}/WHEEL +0 -0
  36. {claude_mpm-4.0.28.dist-info → claude_mpm-4.0.29.dist-info}/entry_points.txt +0 -0
  37. {claude_mpm-4.0.28.dist-info → claude_mpm-4.0.29.dist-info}/licenses/LICENSE +0 -0
  38. {claude_mpm-4.0.28.dist-info → claude_mpm-4.0.29.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,517 @@
1
+ """Agent Manager CLI command for comprehensive agent lifecycle management.
2
+
3
+ This module provides CLI interface for:
4
+ - Creating and customizing agents
5
+ - Managing agent variants
6
+ - Deploying agents across tiers
7
+ - Customizing PM instructions
8
+ - Discovering and listing agents
9
+ """
10
+
11
+ import json
12
+ import os
13
+ import shutil
14
+ import sys
15
+ from pathlib import Path
16
+ from typing import Any, Dict, List, Optional
17
+
18
+ from ...core.config import Config
19
+ from ...core.logging_config import get_logger
20
+ from ...services.agents.agent_builder import AgentBuilderService
21
+ from ...services.agents.deployment.agent_deployment import AgentDeploymentService
22
+ from ..shared import AgentCommand, CommandResult, add_output_arguments
23
+
24
+
25
+ class AgentManagerCommand(AgentCommand):
26
+ """Agent Manager command for comprehensive agent management."""
27
+
28
+ def __init__(self):
29
+ super().__init__("agent-manager")
30
+ self.builder_service = AgentBuilderService()
31
+ self.deployment_service = None
32
+ self.logger = get_logger(__name__)
33
+
34
+ @property
35
+ def deployment(self):
36
+ """Lazy load deployment service."""
37
+ if self.deployment_service is None:
38
+ self.deployment_service = AgentDeploymentService()
39
+ return self.deployment_service
40
+
41
+ def run(self, args) -> CommandResult:
42
+ """Execute agent manager command.
43
+
44
+ Args:
45
+ args: Command arguments
46
+
47
+ Returns:
48
+ CommandResult with operation status
49
+ """
50
+ if not hasattr(args, 'agent_manager_command'):
51
+ return self._show_help()
52
+
53
+ command_map = {
54
+ 'list': self._list_agents,
55
+ 'create': self._create_agent,
56
+ 'variant': self._create_variant,
57
+ 'deploy': self._deploy_agent,
58
+ 'customize-pm': self._customize_pm,
59
+ 'show': self._show_agent,
60
+ 'test': self._test_agent,
61
+ 'templates': self._list_templates
62
+ }
63
+
64
+ command = args.agent_manager_command
65
+ if command in command_map:
66
+ return command_map[command](args)
67
+ else:
68
+ return CommandResult.error_result(f"Unknown command: {command}")
69
+
70
+ def _list_agents(self, args) -> CommandResult:
71
+ """List all agents across tiers with hierarchy.
72
+
73
+ Shows agents from:
74
+ 1. Project level (.claude/agents/)
75
+ 2. User level (~/.claude/agents/)
76
+ 3. System level (framework)
77
+ """
78
+ agents = {
79
+ "project": [],
80
+ "user": [],
81
+ "system": []
82
+ }
83
+
84
+ # Check project level
85
+ project_dir = Path.cwd() / ".claude" / "agents"
86
+ if project_dir.exists():
87
+ for agent_file in project_dir.glob("*.yaml"):
88
+ agents["project"].append(self._read_agent_summary(agent_file, "project"))
89
+
90
+ # Check user level
91
+ user_dir = Path.home() / ".claude" / "agents"
92
+ if user_dir.exists():
93
+ for agent_file in user_dir.glob("*.yaml"):
94
+ agent_id = agent_file.stem
95
+ # Skip if overridden by project
96
+ if not any(a["id"] == agent_id for a in agents["project"]):
97
+ agents["user"].append(self._read_agent_summary(agent_file, "user"))
98
+
99
+ # Get system agents
100
+ templates = self.builder_service.list_available_templates()
101
+ for template in templates:
102
+ agent_id = template["id"]
103
+ # Skip if overridden by project or user
104
+ if not any(a["id"] == agent_id for a in agents["project"] + agents["user"]):
105
+ agents["system"].append({
106
+ "id": agent_id,
107
+ "name": template["name"],
108
+ "tier": "system",
109
+ "description": template["description"],
110
+ "category": template.get("category", "custom")
111
+ })
112
+
113
+ # Format output
114
+ output_format = getattr(args, 'format', 'text')
115
+ if output_format == "json":
116
+ return CommandResult.success_result("Agents listed", data=agents)
117
+ else:
118
+ output = self._format_agent_list(agents)
119
+ return CommandResult.success_result(output)
120
+
121
+ def _create_agent(self, args) -> CommandResult:
122
+ """Create a new agent interactively or from arguments."""
123
+ try:
124
+ # Interactive mode if no arguments
125
+ if not hasattr(args, 'agent_id'):
126
+ return self._interactive_create()
127
+
128
+ # Create from arguments
129
+ config, instructions = self.builder_service.create_agent(
130
+ agent_id=args.agent_id,
131
+ name=getattr(args, 'name', args.agent_id),
132
+ description=getattr(args, 'description', f"Custom agent {args.agent_id}"),
133
+ model=getattr(args, 'model', 'sonnet'),
134
+ tool_choice=getattr(args, 'tool_choice', 'auto'),
135
+ base_template=getattr(args, 'template', None)
136
+ )
137
+
138
+ # Save agent files
139
+ result = self._save_agent(config, instructions, args.agent_id)
140
+
141
+ if result:
142
+ return CommandResult.success_result(f"Agent '{args.agent_id}' created successfully")
143
+ else:
144
+ return CommandResult.error_result("Failed to save agent files")
145
+
146
+ except Exception as e:
147
+ return CommandResult.error_result(f"Failed to create agent: {e}")
148
+
149
+ def _create_variant(self, args) -> CommandResult:
150
+ """Create an agent variant."""
151
+ try:
152
+ if not hasattr(args, 'base_agent'):
153
+ return CommandResult.error_result("Base agent ID required for variant creation")
154
+
155
+ modifications = {}
156
+ if hasattr(args, 'model'):
157
+ modifications['model'] = args.model
158
+ if hasattr(args, 'tool_choice'):
159
+ modifications['tool_choice'] = args.tool_choice
160
+
161
+ config, instructions = self.builder_service.create_variant(
162
+ base_agent_id=args.base_agent,
163
+ variant_id=args.variant_id,
164
+ variant_name=getattr(args, 'name', f"{args.base_agent}-variant"),
165
+ modifications=modifications,
166
+ instructions_append=getattr(args, 'instructions', None)
167
+ )
168
+
169
+ # Save variant
170
+ result = self._save_agent(config, instructions, args.variant_id)
171
+
172
+ if result:
173
+ return CommandResult.success_result(f"Variant '{args.variant_id}' created successfully")
174
+ else:
175
+ return CommandResult.error_result("Failed to save variant files")
176
+
177
+ except Exception as e:
178
+ return CommandResult.error_result(f"Failed to create variant: {e}")
179
+
180
+ def _deploy_agent(self, args) -> CommandResult:
181
+ """Deploy an agent to specified tier."""
182
+ try:
183
+ agent_id = args.agent_id
184
+ tier = getattr(args, 'tier', 'user')
185
+
186
+ # Determine deployment path
187
+ if tier == 'project':
188
+ deploy_path = Path.cwd() / ".claude" / "agents"
189
+ elif tier == 'user':
190
+ deploy_path = Path.home() / ".claude" / "agents"
191
+ else:
192
+ return CommandResult.error_result("Invalid tier. Use 'project' or 'user'")
193
+
194
+ # Create directory if needed
195
+ deploy_path.mkdir(parents=True, exist_ok=True)
196
+
197
+ # Find agent files
198
+ template_dir = Path(__file__).parent.parent.parent / "agents" / "templates"
199
+ json_file = template_dir / f"{agent_id}.json"
200
+ md_file = template_dir / f"{agent_id}.md"
201
+
202
+ if not json_file.exists():
203
+ return CommandResult.error_result(f"Agent '{agent_id}' not found")
204
+
205
+ # Deploy using deployment service
206
+ self.deployment.deploy_agent(agent_id, str(deploy_path))
207
+
208
+ return CommandResult.success_result(
209
+ f"Agent '{agent_id}' deployed to {tier} level"
210
+ )
211
+
212
+ except Exception as e:
213
+ return CommandResult.error_result(f"Deployment failed: {e}")
214
+
215
+ def _customize_pm(self, args) -> CommandResult:
216
+ """Customize PM instructions."""
217
+ try:
218
+ level = getattr(args, 'level', 'user')
219
+
220
+ if level == 'user':
221
+ pm_file = Path.home() / ".claude" / "CLAUDE.md"
222
+ elif level == 'project':
223
+ pm_file = Path.cwd() / "CLAUDE.md"
224
+ else:
225
+ return CommandResult.error_result("Invalid level. Use 'user' or 'project'")
226
+
227
+ # Create backup if file exists
228
+ if pm_file.exists():
229
+ backup_file = pm_file.with_suffix('.md.backup')
230
+ shutil.copy(pm_file, backup_file)
231
+ self.logger.info(f"Backup created: {backup_file}")
232
+
233
+ # Generate or load instructions
234
+ if hasattr(args, 'template'):
235
+ instructions = self._load_pm_template(args.template)
236
+ else:
237
+ instructions = self.builder_service.generate_pm_instructions(
238
+ delegation_patterns=getattr(args, 'patterns', None),
239
+ workflow_overrides=getattr(args, 'workflows', None),
240
+ custom_rules=getattr(args, 'rules', None)
241
+ )
242
+
243
+ # Save instructions
244
+ pm_file.parent.mkdir(parents=True, exist_ok=True)
245
+ pm_file.write_text(instructions)
246
+
247
+ return CommandResult.success_result(
248
+ f"PM instructions customized at {level} level: {pm_file}"
249
+ )
250
+
251
+ except Exception as e:
252
+ return CommandResult.error_result(f"Failed to customize PM: {e}")
253
+
254
+ def _show_agent(self, args) -> CommandResult:
255
+ """Show detailed agent information."""
256
+ try:
257
+ agent_id = args.agent_id
258
+
259
+ # Find agent across tiers
260
+ agent_info = self._find_agent(agent_id)
261
+
262
+ if not agent_info:
263
+ return CommandResult.error_result(f"Agent '{agent_id}' not found")
264
+
265
+ output_format = getattr(args, 'format', 'text')
266
+ if output_format == "json":
267
+ return CommandResult.success_result("Agent details", data=agent_info)
268
+ else:
269
+ output = self._format_agent_details(agent_info)
270
+ return CommandResult.success_result(output)
271
+
272
+ except Exception as e:
273
+ return CommandResult.error_result(f"Failed to show agent: {e}")
274
+
275
+ def _test_agent(self, args) -> CommandResult:
276
+ """Test agent configuration."""
277
+ try:
278
+ agent_id = args.agent_id
279
+
280
+ # Find agent configuration
281
+ config = self._load_agent_config(agent_id)
282
+
283
+ if not config:
284
+ return CommandResult.error_result(f"Agent '{agent_id}' not found")
285
+
286
+ # Validate configuration
287
+ errors = self.builder_service.validate_configuration(config)
288
+
289
+ if errors:
290
+ return CommandResult.error_result(
291
+ f"Validation failed:\n" + "\n".join(f" - {e}" for e in errors)
292
+ )
293
+
294
+ # Check for conflicts
295
+ conflicts = self._check_conflicts(agent_id)
296
+
297
+ if conflicts:
298
+ warning = f"Warning: Agent overrides {conflicts}"
299
+ else:
300
+ warning = ""
301
+
302
+ return CommandResult.success_result(
303
+ f"Agent '{agent_id}' configuration is valid. {warning}"
304
+ )
305
+
306
+ except Exception as e:
307
+ return CommandResult.error_result(f"Test failed: {e}")
308
+
309
+ def _list_templates(self, args) -> CommandResult:
310
+ """List available agent templates."""
311
+ templates = self.builder_service.list_available_templates()
312
+
313
+ output_format = getattr(args, 'format', 'text')
314
+ if output_format == "json":
315
+ return CommandResult.success_result("Templates listed", data=templates)
316
+ else:
317
+ output = "Available Agent Templates:\n\n"
318
+ for template in templates:
319
+ template_id = template.get('id', 'unknown')
320
+ template_name = template.get('name', 'Unnamed')
321
+ output += f" {template_id:<20} - {template_name}\n"
322
+ if template.get('description'):
323
+ output += f" {template['description']}\n"
324
+ return CommandResult.success_result(output)
325
+
326
+ def _interactive_create(self) -> CommandResult:
327
+ """Interactive agent creation wizard."""
328
+ print("\n=== Agent Creation Wizard ===\n")
329
+
330
+ # Get agent ID
331
+ agent_id = input("Agent ID (lowercase, hyphens only): ").strip()
332
+ if not agent_id:
333
+ return CommandResult.error_result("Agent ID is required")
334
+
335
+ # Get name
336
+ name = input(f"Display name [{agent_id}]: ").strip() or agent_id
337
+
338
+ # Get description
339
+ description = input("Description: ").strip()
340
+ if not description:
341
+ return CommandResult.error_result("Description is required")
342
+
343
+ # Get model
344
+ print("\nAvailable models: sonnet, opus, haiku")
345
+ model = input("Model [sonnet]: ").strip() or "sonnet"
346
+
347
+ # Get tool choice
348
+ print("\nTool choices: auto, required, any, none")
349
+ tool_choice = input("Tool choice [auto]: ").strip() or "auto"
350
+
351
+ # Create agent
352
+ try:
353
+ config, instructions = self.builder_service.create_agent(
354
+ agent_id=agent_id,
355
+ name=name,
356
+ description=description,
357
+ model=model,
358
+ tool_choice=tool_choice
359
+ )
360
+
361
+ # Save agent
362
+ if self._save_agent(config, instructions, agent_id):
363
+ return CommandResult.success_result(f"\nAgent '{agent_id}' created successfully!")
364
+ else:
365
+ return CommandResult.error_result("Failed to save agent files")
366
+
367
+ except Exception as e:
368
+ return CommandResult.error_result(f"Creation failed: {e}")
369
+
370
+ def _save_agent(self, config: Dict[str, Any], instructions: str, agent_id: str) -> bool:
371
+ """Save agent configuration and instructions.
372
+
373
+ Args:
374
+ config: Agent configuration dictionary
375
+ instructions: Agent instructions markdown
376
+ agent_id: Agent identifier
377
+
378
+ Returns:
379
+ True if saved successfully
380
+ """
381
+ try:
382
+ template_dir = Path(__file__).parent.parent.parent / "agents" / "templates"
383
+ template_dir.mkdir(parents=True, exist_ok=True)
384
+
385
+ # Save JSON configuration
386
+ json_file = template_dir / f"{agent_id}.json"
387
+ with open(json_file, 'w') as f:
388
+ json.dump(config, f, indent=2)
389
+
390
+ # Save instructions
391
+ md_file = template_dir / f"{agent_id}.md"
392
+ md_file.write_text(instructions)
393
+
394
+ self.logger.info(f"Agent saved: {json_file} and {md_file}")
395
+ return True
396
+
397
+ except Exception as e:
398
+ self.logger.error(f"Failed to save agent: {e}")
399
+ return False
400
+
401
+ def _read_agent_summary(self, agent_file: Path, tier: str) -> Dict[str, Any]:
402
+ """Read agent summary from file."""
403
+ try:
404
+ # For YAML files, extract basic info
405
+ agent_id = agent_file.stem
406
+ return {
407
+ "id": agent_id,
408
+ "name": agent_id.replace("-", " ").title(),
409
+ "tier": tier,
410
+ "file": str(agent_file)
411
+ }
412
+ except Exception:
413
+ return {}
414
+
415
+ def _format_agent_list(self, agents: Dict[str, List]) -> str:
416
+ """Format agent list for display."""
417
+ output = "=== Agent Hierarchy ===\n\n"
418
+
419
+ # Project agents
420
+ if agents["project"]:
421
+ output += "[P] PROJECT LEVEL (Highest Priority)\n"
422
+ for agent in agents["project"]:
423
+ output += f" {agent['id']:<20} - {agent.get('name', agent['id'])}\n"
424
+ output += "\n"
425
+
426
+ # User agents
427
+ if agents["user"]:
428
+ output += "[U] USER LEVEL\n"
429
+ for agent in agents["user"]:
430
+ output += f" {agent['id']:<20} - {agent.get('name', agent['id'])}\n"
431
+ output += "\n"
432
+
433
+ # System agents
434
+ if agents["system"]:
435
+ output += "[S] SYSTEM LEVEL (Framework Defaults)\n"
436
+ for agent in agents["system"]:
437
+ output += f" {agent['id']:<20} - {agent.get('name', agent['id'])}\n"
438
+
439
+ return output
440
+
441
+ def _find_agent(self, agent_id: str) -> Optional[Dict[str, Any]]:
442
+ """Find agent across all tiers."""
443
+ # Implementation would search across tiers
444
+ # This is a simplified version
445
+ return {"id": agent_id, "tier": "system"}
446
+
447
+ def _load_agent_config(self, agent_id: str) -> Optional[Dict[str, Any]]:
448
+ """Load agent configuration."""
449
+ try:
450
+ return self.builder_service._load_template(agent_id)
451
+ except:
452
+ return None
453
+
454
+ def _check_conflicts(self, agent_id: str) -> Optional[str]:
455
+ """Check for agent conflicts across tiers."""
456
+ # Check if agent exists in multiple tiers
457
+ # Return tier information if conflicts exist
458
+ return None
459
+
460
+ def _format_agent_details(self, agent_info: Dict[str, Any]) -> str:
461
+ """Format agent details for display."""
462
+ output = f"=== Agent: {agent_info['id']} ===\n\n"
463
+ for key, value in agent_info.items():
464
+ output += f"{key}: {value}\n"
465
+ return output
466
+
467
+ def _load_pm_template(self, template_name: str) -> str:
468
+ """Load PM instruction template."""
469
+ # Load predefined PM templates
470
+ return "# PM Instructions Template\n"
471
+
472
+ def _show_help(self) -> CommandResult:
473
+ """Show help for agent manager."""
474
+ help_text = """
475
+ Agent Manager - Comprehensive Agent Lifecycle Management
476
+
477
+ Commands:
478
+ list List all agents across tiers with hierarchy
479
+ create Create a new agent (interactive or with arguments)
480
+ variant Create an agent variant based on existing agent
481
+ deploy Deploy agent to project or user tier
482
+ customize-pm Customize PM instructions at user or project level
483
+ show Display detailed agent information
484
+ test Validate agent configuration
485
+ templates List available agent templates
486
+
487
+ Examples:
488
+ claude-mpm agent-manager list
489
+ claude-mpm agent-manager create --id my-agent --name "My Agent"
490
+ claude-mpm agent-manager variant --base research --id research-v2
491
+ claude-mpm agent-manager deploy --agent-id my-agent --tier user
492
+ claude-mpm agent-manager customize-pm --level project
493
+ """
494
+ return CommandResult.success_result(help_text)
495
+
496
+
497
+ # Module-level function for CLI integration
498
+ def manage_agent_manager(args) -> int:
499
+ """Entry point for agent-manager command from CLI.
500
+
501
+ Args:
502
+ args: Parsed command line arguments
503
+
504
+ Returns:
505
+ Exit code (0 for success, non-zero for failure)
506
+ """
507
+ command = AgentManagerCommand()
508
+ result = command.run(args)
509
+
510
+ if result.success:
511
+ if result.message:
512
+ print(result.message)
513
+ return 0
514
+ else:
515
+ if result.message:
516
+ print(f"Error: {result.message}", file=sys.stderr)
517
+ return 1
@@ -23,7 +23,7 @@ from ...core.config import Config
23
23
  from ...core.logger import get_logger
24
24
  from ...core.shared.config_loader import ConfigLoader
25
25
  from ...services.agents.memory import AgentMemoryManager
26
- from ..shared.command_base import MemoryCommand, CommandResult
26
+ from ..shared.base_command import MemoryCommand, CommandResult
27
27
  from ..shared.argument_patterns import add_memory_arguments, add_output_arguments
28
28
 
29
29