claude-mpm 4.1.2__py3-none-any.whl → 4.1.3__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 (53) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/templates/engineer.json +33 -11
  3. claude_mpm/cli/commands/agents.py +556 -1009
  4. claude_mpm/cli/commands/memory.py +248 -927
  5. claude_mpm/cli/commands/run.py +139 -484
  6. claude_mpm/cli/startup_logging.py +76 -0
  7. claude_mpm/core/agent_registry.py +6 -10
  8. claude_mpm/core/framework_loader.py +114 -595
  9. claude_mpm/core/logging_config.py +2 -4
  10. claude_mpm/hooks/claude_hooks/event_handlers.py +7 -117
  11. claude_mpm/hooks/claude_hooks/hook_handler.py +91 -755
  12. claude_mpm/hooks/claude_hooks/hook_handler_original.py +1040 -0
  13. claude_mpm/hooks/claude_hooks/hook_handler_refactored.py +347 -0
  14. claude_mpm/hooks/claude_hooks/services/__init__.py +13 -0
  15. claude_mpm/hooks/claude_hooks/services/connection_manager.py +190 -0
  16. claude_mpm/hooks/claude_hooks/services/duplicate_detector.py +106 -0
  17. claude_mpm/hooks/claude_hooks/services/state_manager.py +282 -0
  18. claude_mpm/hooks/claude_hooks/services/subagent_processor.py +374 -0
  19. claude_mpm/services/agents/deployment/agent_deployment.py +42 -454
  20. claude_mpm/services/agents/deployment/base_agent_locator.py +132 -0
  21. claude_mpm/services/agents/deployment/deployment_results_manager.py +185 -0
  22. claude_mpm/services/agents/deployment/single_agent_deployer.py +315 -0
  23. claude_mpm/services/agents/memory/agent_memory_manager.py +42 -508
  24. claude_mpm/services/agents/memory/memory_categorization_service.py +165 -0
  25. claude_mpm/services/agents/memory/memory_file_service.py +103 -0
  26. claude_mpm/services/agents/memory/memory_format_service.py +201 -0
  27. claude_mpm/services/agents/memory/memory_limits_service.py +99 -0
  28. claude_mpm/services/agents/registry/__init__.py +1 -1
  29. claude_mpm/services/cli/__init__.py +18 -0
  30. claude_mpm/services/cli/agent_cleanup_service.py +407 -0
  31. claude_mpm/services/cli/agent_dependency_service.py +395 -0
  32. claude_mpm/services/cli/agent_listing_service.py +463 -0
  33. claude_mpm/services/cli/agent_output_formatter.py +605 -0
  34. claude_mpm/services/cli/agent_validation_service.py +589 -0
  35. claude_mpm/services/cli/dashboard_launcher.py +424 -0
  36. claude_mpm/services/cli/memory_crud_service.py +617 -0
  37. claude_mpm/services/cli/memory_output_formatter.py +604 -0
  38. claude_mpm/services/cli/session_manager.py +513 -0
  39. claude_mpm/services/cli/socketio_manager.py +498 -0
  40. claude_mpm/services/cli/startup_checker.py +370 -0
  41. claude_mpm/services/core/cache_manager.py +311 -0
  42. claude_mpm/services/core/memory_manager.py +637 -0
  43. claude_mpm/services/core/path_resolver.py +498 -0
  44. claude_mpm/services/core/service_container.py +520 -0
  45. claude_mpm/services/core/service_interfaces.py +436 -0
  46. claude_mpm/services/diagnostics/checks/agent_check.py +65 -19
  47. {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.3.dist-info}/METADATA +1 -1
  48. {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.3.dist-info}/RECORD +52 -22
  49. claude_mpm/cli/commands/run_config_checker.py +0 -159
  50. {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.3.dist-info}/WHEEL +0 -0
  51. {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.3.dist-info}/entry_points.txt +0 -0
  52. {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.3.dist-info}/licenses/LICENSE +0 -0
  53. {claude_mpm-4.1.2.dist-info → claude_mpm-4.1.3.dist-info}/top_level.txt +0 -0
@@ -12,16 +12,13 @@ DESIGN DECISIONS:
12
12
  """
13
13
 
14
14
  import json
15
- import os
16
- from pathlib import Path
17
15
 
18
- import yaml
19
-
20
- from ...agents.frontmatter_validator import FrontmatterValidator
21
16
  from ...constants import AgentCommands
22
- from ...core.agent_registry import AgentRegistryAdapter
23
- from ...core.logger import get_logger
24
- from ...core.shared.config_loader import ConfigLoader
17
+ from ...services.cli.agent_cleanup_service import AgentCleanupService
18
+ from ...services.cli.agent_dependency_service import AgentDependencyService
19
+ from ...services.cli.agent_listing_service import AgentListingService
20
+ from ...services.cli.agent_output_formatter import AgentOutputFormatter
21
+ from ...services.cli.agent_validation_service import AgentValidationService
25
22
  from ..shared import (
26
23
  AgentCommand,
27
24
  CommandResult,
@@ -35,6 +32,11 @@ class AgentsCommand(AgentCommand):
35
32
  def __init__(self):
36
33
  super().__init__("agents")
37
34
  self._deployment_service = None
35
+ self._listing_service = None
36
+ self._validation_service = None
37
+ self._dependency_service = None
38
+ self._cleanup_service = None
39
+ self._formatter = AgentOutputFormatter()
38
40
 
39
41
  @property
40
42
  def deployment_service(self):
@@ -52,6 +54,38 @@ class AgentsCommand(AgentCommand):
52
54
  raise ImportError("Agent deployment service not available")
53
55
  return self._deployment_service
54
56
 
57
+ @property
58
+ def listing_service(self):
59
+ """Get listing service instance (lazy loaded)."""
60
+ if self._listing_service is None:
61
+ self._listing_service = AgentListingService(
62
+ deployment_service=self.deployment_service
63
+ )
64
+ return self._listing_service
65
+
66
+ @property
67
+ def validation_service(self):
68
+ """Get validation service instance (lazy loaded)."""
69
+ if self._validation_service is None:
70
+ self._validation_service = AgentValidationService()
71
+ return self._validation_service
72
+
73
+ @property
74
+ def dependency_service(self):
75
+ """Get dependency service instance (lazy loaded)."""
76
+ if self._dependency_service is None:
77
+ self._dependency_service = AgentDependencyService()
78
+ return self._dependency_service
79
+
80
+ @property
81
+ def cleanup_service(self):
82
+ """Get cleanup service instance (lazy loaded)."""
83
+ if self._cleanup_service is None:
84
+ self._cleanup_service = AgentCleanupService(
85
+ deployment_service=self.deployment_service
86
+ )
87
+ return self._cleanup_service
88
+
55
89
  def validate_args(self, args) -> str:
56
90
  """Validate command arguments."""
57
91
  # Most agent commands are optional, so basic validation
@@ -106,6 +140,12 @@ class AgentsCommand(AgentCommand):
106
140
  # Parse the agent versions display into structured data
107
141
  if agent_versions:
108
142
  data = {"agent_versions": agent_versions, "has_agents": True}
143
+ formatted = (
144
+ self._formatter.format_as_json(data)
145
+ if output_format == "json"
146
+ else self._formatter.format_as_yaml(data)
147
+ )
148
+ print(formatted)
109
149
  return CommandResult.success_result(
110
150
  "Agent versions retrieved", data=data
111
151
  )
@@ -114,6 +154,12 @@ class AgentsCommand(AgentCommand):
114
154
  "has_agents": False,
115
155
  "suggestion": "To deploy agents, run: claude-mpm --mpm:agents deploy",
116
156
  }
157
+ formatted = (
158
+ self._formatter.format_as_json(data)
159
+ if output_format == "json"
160
+ else self._formatter.format_as_yaml(data)
161
+ )
162
+ print(formatted)
117
163
  return CommandResult.success_result(
118
164
  "No deployed agents found", data=data
119
165
  )
@@ -161,31 +207,34 @@ class AgentsCommand(AgentCommand):
161
207
  def _list_system_agents(self, args) -> CommandResult:
162
208
  """List available agent templates."""
163
209
  try:
164
- agents = self.deployment_service.list_available_agents()
210
+ verbose = getattr(args, "verbose", False)
211
+ agents = self.listing_service.list_system_agents(verbose=verbose)
212
+
165
213
  output_format = getattr(args, "format", "text")
214
+ quiet = getattr(args, "quiet", False)
166
215
 
167
- if output_format in ["json", "yaml"]:
168
- return CommandResult.success_result(
169
- f"Found {len(agents)} agent templates",
170
- data={"agents": agents, "count": len(agents)},
171
- )
172
- # Text output
173
- print("Available Agent Templates:")
174
- print("-" * 80)
175
- if not agents:
176
- print("No agent templates found")
177
- else:
178
- for agent in agents:
179
- print(f"📄 {agent['file']}")
180
- if "name" in agent:
181
- print(f" Name: {agent['name']}")
182
- if "description" in agent:
183
- print(f" Description: {agent['description']}")
184
- if "version" in agent:
185
- print(f" Version: {agent['version']}")
186
- print()
216
+ # Convert AgentInfo objects to dicts for formatter
217
+ agents_data = [
218
+ {
219
+ "name": agent.name,
220
+ "type": agent.type,
221
+ "path": agent.path,
222
+ "description": agent.description,
223
+ "specializations": agent.specializations,
224
+ "version": agent.version,
225
+ }
226
+ for agent in agents
227
+ ]
228
+
229
+ formatted = self._formatter.format_agent_list(
230
+ agents_data, output_format=output_format, verbose=verbose, quiet=quiet
231
+ )
232
+ print(formatted)
187
233
 
188
- return CommandResult.success_result(f"Listed {len(agents)} agent templates")
234
+ return CommandResult.success_result(
235
+ f"Listed {len(agents)} agent templates",
236
+ data={"agents": agents_data, "count": len(agents)},
237
+ )
189
238
 
190
239
  except Exception as e:
191
240
  self.logger.error(f"Error listing system agents: {e}", exc_info=True)
@@ -194,39 +243,47 @@ class AgentsCommand(AgentCommand):
194
243
  def _list_deployed_agents(self, args) -> CommandResult:
195
244
  """List deployed agents."""
196
245
  try:
197
- verification = self.deployment_service.verify_deployment()
246
+ verbose = getattr(args, "verbose", False)
247
+ agents, warnings = self.listing_service.list_deployed_agents(
248
+ verbose=verbose
249
+ )
250
+
198
251
  output_format = getattr(args, "format", "text")
252
+ quiet = getattr(args, "quiet", False)
199
253
 
200
- if output_format in ["json", "yaml"]:
201
- return CommandResult.success_result(
202
- f"Found {len(verification['agents_found'])} deployed agents",
203
- data={
204
- "agents": verification["agents_found"],
205
- "warnings": verification.get("warnings", []),
206
- "count": len(verification["agents_found"]),
207
- },
208
- )
209
- # Text output
210
- print("Deployed Agents:")
211
- print("-" * 80)
212
- if not verification["agents_found"]:
213
- print("No deployed agents found")
214
- else:
215
- for agent in verification["agents_found"]:
216
- print(f"📄 {agent['file']}")
217
- if "name" in agent:
218
- print(f" Name: {agent['name']}")
219
- if "path" in agent:
220
- print(f" Path: {agent['path']}")
221
- print()
254
+ # Convert AgentInfo objects to dicts for formatter
255
+ agents_data = [
256
+ {
257
+ "name": agent.name,
258
+ "type": agent.type,
259
+ "tier": agent.tier,
260
+ "path": agent.path,
261
+ "description": agent.description,
262
+ "specializations": agent.specializations,
263
+ "version": agent.version,
264
+ }
265
+ for agent in agents
266
+ ]
267
+
268
+ # Format the agent list
269
+ formatted = self._formatter.format_agent_list(
270
+ agents_data, output_format=output_format, verbose=verbose, quiet=quiet
271
+ )
272
+ print(formatted)
222
273
 
223
- if verification["warnings"]:
274
+ # Add warnings for text output
275
+ if output_format == "text" and warnings:
224
276
  print("\nWarnings:")
225
- for warning in verification["warnings"]:
277
+ for warning in warnings:
226
278
  print(f" ⚠️ {warning}")
227
279
 
228
280
  return CommandResult.success_result(
229
- f"Listed {len(verification['agents_found'])} deployed agents"
281
+ f"Listed {len(agents)} deployed agents",
282
+ data={
283
+ "agents": agents_data,
284
+ "warnings": warnings,
285
+ "count": len(agents),
286
+ },
230
287
  )
231
288
 
232
289
  except Exception as e:
@@ -236,27 +293,58 @@ class AgentsCommand(AgentCommand):
236
293
  def _list_agents_by_tier(self, args) -> CommandResult:
237
294
  """List agents grouped by tier/precedence."""
238
295
  try:
239
- agents_by_tier = self.deployment_service.list_agents_by_tier()
296
+ tier_info = self.listing_service.list_agents_by_tier()
240
297
  output_format = getattr(args, "format", "text")
241
298
 
242
- if output_format in ["json", "yaml"]:
243
- return CommandResult.success_result(
244
- "Agents listed by tier", data=agents_by_tier
245
- )
246
- # Text output
247
- print("Agents by Tier/Precedence:")
248
- print("=" * 50)
249
-
250
- for tier, agents in agents_by_tier.items():
251
- print(f"\n{tier.upper()}:")
252
- print("-" * 20)
253
- if agents:
254
- for agent in agents:
255
- print(f" • {agent}")
256
- else:
257
- print(" (none)")
299
+ # Convert to format expected by formatter
300
+ agents_by_tier = {
301
+ "project": [
302
+ {
303
+ "name": agent.name,
304
+ "type": agent.type,
305
+ "path": agent.path,
306
+ "active": agent.active,
307
+ "overridden_by": agent.overridden_by,
308
+ }
309
+ for agent in tier_info.project
310
+ ],
311
+ "user": [
312
+ {
313
+ "name": agent.name,
314
+ "type": agent.type,
315
+ "path": agent.path,
316
+ "active": agent.active,
317
+ "overridden_by": agent.overridden_by,
318
+ }
319
+ for agent in tier_info.user
320
+ ],
321
+ "system": [
322
+ {
323
+ "name": agent.name,
324
+ "type": agent.type,
325
+ "path": agent.path,
326
+ "active": agent.active,
327
+ "overridden_by": agent.overridden_by,
328
+ }
329
+ for agent in tier_info.system
330
+ ],
331
+ "summary": {
332
+ "total_count": tier_info.total_count,
333
+ "active_count": tier_info.active_count,
334
+ "project_count": len(tier_info.project),
335
+ "user_count": len(tier_info.user),
336
+ "system_count": len(tier_info.system),
337
+ },
338
+ }
258
339
 
259
- return CommandResult.success_result("Agents listed by tier")
340
+ formatted = self._formatter.format_agents_by_tier(
341
+ agents_by_tier, output_format=output_format
342
+ )
343
+ print(formatted)
344
+
345
+ return CommandResult.success_result(
346
+ "Agents listed by tier", data=agents_by_tier
347
+ )
260
348
 
261
349
  except Exception as e:
262
350
  self.logger.error(f"Error listing agents by tier: {e}", exc_info=True)
@@ -272,30 +360,39 @@ class AgentsCommand(AgentCommand):
272
360
  project_result = self.deployment_service.deploy_project_agents(force=force)
273
361
 
274
362
  # Combine results
275
- total_deployed = system_result.get(
276
- "deployed_count", 0
277
- ) + project_result.get("deployed_count", 0)
363
+ combined_result = {
364
+ "deployed_count": system_result.get("deployed_count", 0)
365
+ + project_result.get("deployed_count", 0),
366
+ "deployed": system_result.get("deployed", [])
367
+ + project_result.get("deployed", []),
368
+ "updated_count": system_result.get("updated_count", 0)
369
+ + project_result.get("updated_count", 0),
370
+ "updated": system_result.get("updated", [])
371
+ + project_result.get("updated", []),
372
+ "skipped": system_result.get("skipped", [])
373
+ + project_result.get("skipped", []),
374
+ "errors": system_result.get("errors", [])
375
+ + project_result.get("errors", []),
376
+ "target_dir": system_result.get("target_dir")
377
+ or project_result.get("target_dir"),
378
+ }
278
379
 
279
380
  output_format = getattr(args, "format", "text")
280
- if output_format in ["json", "yaml"]:
281
- return CommandResult.success_result(
282
- f"Deployed {total_deployed} agents",
283
- data={
284
- "system_agents": system_result,
285
- "project_agents": project_result,
286
- "total_deployed": total_deployed,
287
- },
288
- )
289
- # Text output
290
- if system_result.get("deployed_count", 0) > 0:
291
- print(f"✓ Deployed {system_result['deployed_count']} system agents")
292
- if project_result.get("deployed_count", 0) > 0:
293
- print(f"✓ Deployed {project_result['deployed_count']} project agents")
381
+ verbose = getattr(args, "verbose", False)
294
382
 
295
- if total_deployed == 0:
296
- print("No agents were deployed (all up to date)")
383
+ formatted = self._formatter.format_deployment_result(
384
+ combined_result, output_format=output_format, verbose=verbose
385
+ )
386
+ print(formatted)
297
387
 
298
- return CommandResult.success_result(f"Deployed {total_deployed} agents")
388
+ return CommandResult.success_result(
389
+ f"Deployed {combined_result['deployed_count']} agents",
390
+ data={
391
+ "system_agents": system_result,
392
+ "project_agents": project_result,
393
+ "total_deployed": combined_result["deployed_count"],
394
+ },
395
+ )
299
396
 
300
397
  except Exception as e:
301
398
  self.logger.error(f"Error deploying agents: {e}", exc_info=True)
@@ -304,21 +401,20 @@ class AgentsCommand(AgentCommand):
304
401
  def _clean_agents(self, args) -> CommandResult:
305
402
  """Clean deployed agents."""
306
403
  try:
307
- result = self.deployment_service.clean_deployment()
404
+ result = self.cleanup_service.clean_deployed_agents()
308
405
 
309
406
  output_format = getattr(args, "format", "text")
310
- if output_format in ["json", "yaml"]:
311
- return CommandResult.success_result(
312
- f"Cleaned {result.get('cleaned_count', 0)} agents", data=result
313
- )
314
- # Text output
315
- cleaned_count = result.get("cleaned_count", 0)
316
- if cleaned_count > 0:
317
- print(f"✓ Cleaned {cleaned_count} deployed agents")
318
- else:
319
- print("No deployed agents to clean")
407
+ dry_run = False # Regular clean is not a dry run
320
408
 
321
- return CommandResult.success_result(f"Cleaned {cleaned_count} agents")
409
+ formatted = self._formatter.format_cleanup_result(
410
+ result, output_format=output_format, dry_run=dry_run
411
+ )
412
+ print(formatted)
413
+
414
+ cleaned_count = result.get("cleaned_count", 0)
415
+ return CommandResult.success_result(
416
+ f"Cleaned {cleaned_count} agents", data=result
417
+ )
322
418
 
323
419
  except Exception as e:
324
420
  self.logger.error(f"Error cleaning agents: {e}", exc_info=True)
@@ -333,43 +429,178 @@ class AgentsCommand(AgentCommand):
333
429
  "Agent name is required for view command"
334
430
  )
335
431
 
336
- # Get agent details from deployment service
337
- agent_details = self.deployment_service.get_agent_details(agent_name)
432
+ # Get agent details from listing service
433
+ agent_details = self.listing_service.get_agent_details(agent_name)
338
434
 
339
- output_format = getattr(args, "format", "text")
340
- if output_format in ["json", "yaml"]:
341
- return CommandResult.success_result(
342
- f"Agent details for {agent_name}", data=agent_details
435
+ if not agent_details:
436
+ # Try to find the agent to provide helpful error message
437
+ agent = self.listing_service.find_agent(agent_name)
438
+ if not agent:
439
+ return CommandResult.error_result(f"Agent '{agent_name}' not found")
440
+ return CommandResult.error_result(
441
+ f"Could not retrieve details for agent '{agent_name}'"
343
442
  )
344
- # Text output
345
- print(f"Agent: {agent_name}")
346
- print("-" * 40)
347
- for key, value in agent_details.items():
348
- print(f"{key}: {value}")
349
443
 
350
- return CommandResult.success_result(f"Displayed details for {agent_name}")
444
+ output_format = getattr(args, "format", "text")
445
+ verbose = getattr(args, "verbose", False)
446
+
447
+ formatted = self._formatter.format_agent_details(
448
+ agent_details, output_format=output_format, verbose=verbose
449
+ )
450
+ print(formatted)
451
+
452
+ return CommandResult.success_result(
453
+ f"Displayed details for {agent_name}", data=agent_details
454
+ )
351
455
 
352
456
  except Exception as e:
353
457
  self.logger.error(f"Error viewing agent: {e}", exc_info=True)
354
458
  return CommandResult.error_result(f"Error viewing agent: {e}")
355
459
 
356
460
  def _fix_agents(self, args) -> CommandResult:
357
- """Fix agent deployment issues."""
461
+ """Fix agent frontmatter issues using validation service."""
358
462
  try:
359
- result = self.deployment_service.fix_deployment()
463
+ dry_run = getattr(args, "dry_run", False)
464
+ agent_name = getattr(args, "agent_name", None)
465
+ fix_all = getattr(args, "all", False)
360
466
 
361
467
  output_format = getattr(args, "format", "text")
362
- if output_format in ["json", "yaml"]:
363
- return CommandResult.success_result(
364
- "Agent deployment fixed", data=result
468
+
469
+ # Determine what to fix
470
+ if fix_all:
471
+ # Fix all agents
472
+ result = self.validation_service.fix_all_agents(dry_run=dry_run)
473
+
474
+ if output_format in ["json", "yaml"]:
475
+ formatted = (
476
+ self._formatter.format_as_json(result)
477
+ if output_format == "json"
478
+ else self._formatter.format_as_yaml(result)
479
+ )
480
+ print(formatted)
481
+ else:
482
+ # Text output
483
+ mode = "DRY RUN" if dry_run else "FIX"
484
+ print(
485
+ f"\n🔧 {mode}: Checking {result.get('total_agents', 0)} agent(s) for frontmatter issues...\n"
486
+ )
487
+
488
+ if result.get("results"):
489
+ for agent_result in result["results"]:
490
+ print(f"📄 {agent_result['agent']}:")
491
+ if agent_result.get("skipped"):
492
+ print(
493
+ f" ⚠️ Skipped: {agent_result.get('reason', 'Unknown reason')}"
494
+ )
495
+ elif agent_result.get("was_valid"):
496
+ print(" ✓ No issues found")
497
+ else:
498
+ if agent_result.get("errors_found", 0) > 0:
499
+ print(
500
+ f" ❌ Errors found: {agent_result['errors_found']}"
501
+ )
502
+ if agent_result.get("warnings_found", 0) > 0:
503
+ print(
504
+ f" ⚠️ Warnings found: {agent_result['warnings_found']}"
505
+ )
506
+ if dry_run:
507
+ if agent_result.get("corrections_available", 0) > 0:
508
+ print(
509
+ f" 🔧 Would fix: {agent_result['corrections_available']} issues"
510
+ )
511
+ elif agent_result.get("corrections_made", 0) > 0:
512
+ print(
513
+ f" ✓ Fixed: {agent_result['corrections_made']} issues"
514
+ )
515
+ print()
516
+
517
+ # Summary
518
+ print("=" * 80)
519
+ print("SUMMARY:")
520
+ print(f" Agents checked: {result.get('agents_checked', 0)}")
521
+ print(
522
+ f" Total issues found: {result.get('total_issues_found', 0)}"
523
+ )
524
+ if dry_run:
525
+ print(
526
+ f" Issues that would be fixed: {result.get('total_corrections_available', 0)}"
527
+ )
528
+ print("\n💡 Run without --dry-run to apply fixes")
529
+ else:
530
+ print(
531
+ f" Issues fixed: {result.get('total_corrections_made', 0)}"
532
+ )
533
+ if result.get("total_corrections_made", 0) > 0:
534
+ print("\n✓ Frontmatter issues have been fixed!")
535
+ print("=" * 80 + "\n")
536
+
537
+ msg = f"{'Would fix' if dry_run else 'Fixed'} {result.get('total_corrections_available' if dry_run else 'total_corrections_made', 0)} issues"
538
+ return CommandResult.success_result(msg, data=result)
539
+
540
+ if agent_name:
541
+ # Fix specific agent
542
+ result = self.validation_service.fix_agent_frontmatter(
543
+ agent_name, dry_run=dry_run
365
544
  )
366
- # Text output
367
- print("✓ Agent deployment issues fixed")
368
- if result.get("fixes_applied"):
369
- for fix in result["fixes_applied"]:
370
- print(f" - {fix}")
371
545
 
372
- return CommandResult.success_result("Agent deployment fixed")
546
+ if not result.get("success"):
547
+ return CommandResult.error_result(
548
+ result.get("error", "Failed to fix agent")
549
+ )
550
+
551
+ if output_format in ["json", "yaml"]:
552
+ formatted = (
553
+ self._formatter.format_as_json(result)
554
+ if output_format == "json"
555
+ else self._formatter.format_as_yaml(result)
556
+ )
557
+ print(formatted)
558
+ else:
559
+ # Text output
560
+ mode = "DRY RUN" if dry_run else "FIX"
561
+ print(
562
+ f"\n🔧 {mode}: Checking agent '{agent_name}' for frontmatter issues...\n"
563
+ )
564
+
565
+ print(f"📄 {agent_name}:")
566
+ if result.get("was_valid"):
567
+ print(" ✓ No issues found")
568
+ else:
569
+ if result.get("errors_found"):
570
+ print(" ❌ Errors:")
571
+ for error in result["errors_found"]:
572
+ print(f" - {error}")
573
+ if result.get("warnings_found"):
574
+ print(" ⚠️ Warnings:")
575
+ for warning in result["warnings_found"]:
576
+ print(f" - {warning}")
577
+ if dry_run:
578
+ if result.get("corrections_available"):
579
+ print(" 🔧 Would fix:")
580
+ for correction in result["corrections_available"]:
581
+ print(f" - {correction}")
582
+ elif result.get("corrections_made"):
583
+ print(" ✓ Fixed:")
584
+ for correction in result["corrections_made"]:
585
+ print(f" - {correction}")
586
+ print()
587
+
588
+ if dry_run and result.get("corrections_available"):
589
+ print("💡 Run without --dry-run to apply fixes\n")
590
+ elif not dry_run and result.get("corrections_made"):
591
+ print("✓ Frontmatter issues have been fixed!\n")
592
+
593
+ msg = f"{'Would fix' if dry_run else 'Fixed'} agent '{agent_name}'"
594
+ return CommandResult.success_result(msg, data=result)
595
+
596
+ # No agent specified and not --all
597
+ usage_msg = "Please specify an agent name or use --all to fix all agents\nUsage: claude-mpm agents fix [agent_name] [--dry-run] [--all]"
598
+ if output_format in ["json", "yaml"]:
599
+ return CommandResult.error_result(
600
+ "No agent specified", data={"usage": usage_msg}
601
+ )
602
+ print(f"❌ {usage_msg}")
603
+ return CommandResult.error_result("No agent specified")
373
604
 
374
605
  except Exception as e:
375
606
  self.logger.error(f"Error fixing agents: {e}", exc_info=True)
@@ -378,24 +609,25 @@ class AgentsCommand(AgentCommand):
378
609
  def _check_agent_dependencies(self, args) -> CommandResult:
379
610
  """Check agent dependencies."""
380
611
  try:
381
- result = self.deployment_service.check_dependencies()
612
+ agent_name = getattr(args, "agent", None)
613
+ result = self.dependency_service.check_dependencies(agent_name=agent_name)
382
614
 
383
- output_format = getattr(args, "format", "text")
384
- if output_format in ["json", "yaml"]:
385
- return CommandResult.success_result(
386
- "Dependency check completed", data=result
615
+ if not result["success"]:
616
+ if "available_agents" in result:
617
+ print(f"❌ Agent '{agent_name}' is not deployed")
618
+ print(
619
+ f" Available agents: {', '.join(result['available_agents'])}"
620
+ )
621
+ return CommandResult.error_result(
622
+ result.get("error", "Dependency check failed")
387
623
  )
388
- # Text output
389
- print("Agent Dependencies Check:")
390
- print("-" * 40)
391
- if result.get("missing_dependencies"):
392
- print("Missing dependencies:")
393
- for dep in result["missing_dependencies"]:
394
- print(f" - {dep}")
395
- else:
396
- print("✓ All dependencies satisfied")
397
624
 
398
- return CommandResult.success_result("Dependency check completed")
625
+ # Print the formatted report
626
+ print(result["report"])
627
+
628
+ return CommandResult.success_result(
629
+ "Dependency check completed", data=result
630
+ )
399
631
 
400
632
  except Exception as e:
401
633
  self.logger.error(f"Error checking dependencies: {e}", exc_info=True)
@@ -404,23 +636,45 @@ class AgentsCommand(AgentCommand):
404
636
  def _install_agent_dependencies(self, args) -> CommandResult:
405
637
  """Install agent dependencies."""
406
638
  try:
407
- result = self.deployment_service.install_dependencies()
639
+ agent_name = getattr(args, "agent", None)
640
+ dry_run = getattr(args, "dry_run", False)
641
+ result = self.dependency_service.install_dependencies(
642
+ agent_name=agent_name, dry_run=dry_run
643
+ )
408
644
 
409
- output_format = getattr(args, "format", "text")
410
- if output_format in ["json", "yaml"]:
411
- return CommandResult.success_result(
412
- f"Installed {result.get('installed_count', 0)} dependencies",
413
- data=result,
645
+ if not result["success"]:
646
+ if "available_agents" in result:
647
+ print(f"❌ Agent '{agent_name}' is not deployed")
648
+ print(
649
+ f" Available agents: {', '.join(result['available_agents'])}"
650
+ )
651
+ return CommandResult.error_result(
652
+ result.get("error", "Installation failed")
414
653
  )
415
- # Text output
416
- installed_count = result.get("installed_count", 0)
417
- if installed_count > 0:
418
- print(f"✓ Installed {installed_count} dependencies")
654
+
655
+ if result.get("missing_count") == 0:
656
+ print("✅ All Python dependencies are already installed")
657
+ elif dry_run:
658
+ print(
659
+ f"Found {len(result['missing_dependencies'])} missing dependencies:"
660
+ )
661
+ for dep in result["missing_dependencies"]:
662
+ print(f" - {dep}")
663
+ print("\n--dry-run specified, not installing anything")
664
+ print(f"Would install: {result['install_command']}")
419
665
  else:
420
- print("No dependencies needed installation")
666
+ print(
667
+ f"✅ Successfully installed {len(result.get('installed', []))} dependencies"
668
+ )
669
+ if result.get("still_missing"):
670
+ print(
671
+ f"⚠️ {len(result['still_missing'])} dependencies still missing after installation"
672
+ )
673
+ elif result.get("fully_resolved"):
674
+ print("✅ All dependencies verified after installation")
421
675
 
422
676
  return CommandResult.success_result(
423
- f"Installed {installed_count} dependencies"
677
+ "Dependency installation completed", data=result
424
678
  )
425
679
 
426
680
  except Exception as e:
@@ -430,27 +684,57 @@ class AgentsCommand(AgentCommand):
430
684
  def _list_agent_dependencies(self, args) -> CommandResult:
431
685
  """List agent dependencies."""
432
686
  try:
433
- result = self.deployment_service.list_dependencies()
434
-
435
687
  output_format = getattr(args, "format", "text")
436
- if output_format in ["json", "yaml"]:
437
- return CommandResult.success_result(
438
- f"Found {len(result.get('dependencies', []))} dependencies",
439
- data=result,
440
- )
441
- # Text output
442
- dependencies = result.get("dependencies", [])
443
- print("Agent Dependencies:")
444
- print("-" * 40)
445
- if dependencies:
446
- for dep in dependencies:
447
- status = "✓" if dep.get("installed") else "✗"
448
- print(f"{status} {dep.get('name', 'Unknown')}")
449
- else:
450
- print("No dependencies found")
688
+ result = self.dependency_service.list_dependencies(
689
+ format_type=output_format
690
+ )
691
+
692
+ if not result["success"]:
693
+ return CommandResult.error_result(result.get("error", "Listing failed"))
694
+
695
+ # Format output based on requested format
696
+ if output_format == "pip":
697
+ for dep in result["dependencies"]:
698
+ print(dep)
699
+ elif output_format == "json":
700
+ print(json.dumps(result["data"], indent=2))
701
+ else: # text format
702
+ print("=" * 60)
703
+ print("DEPENDENCIES FROM DEPLOYED AGENTS")
704
+ print("=" * 60)
705
+ print()
706
+
707
+ if result["python_dependencies"]:
708
+ print(
709
+ f"Python Dependencies ({len(result['python_dependencies'])}):"
710
+ )
711
+ print("-" * 30)
712
+ for dep in result["python_dependencies"]:
713
+ print(f" {dep}")
714
+ print()
715
+
716
+ if result["system_dependencies"]:
717
+ print(
718
+ f"System Dependencies ({len(result['system_dependencies'])}):"
719
+ )
720
+ print("-" * 30)
721
+ for dep in result["system_dependencies"]:
722
+ print(f" {dep}")
723
+ print()
724
+
725
+ print("Per-Agent Dependencies:")
726
+ print("-" * 30)
727
+ for agent_id in sorted(result["per_agent"].keys()):
728
+ deps = result["per_agent"][agent_id]
729
+ python_count = len(deps.get("python", []))
730
+ system_count = len(deps.get("system", []))
731
+ if python_count or system_count:
732
+ print(
733
+ f" {agent_id}: {python_count} Python, {system_count} System"
734
+ )
451
735
 
452
736
  return CommandResult.success_result(
453
- f"Listed {len(dependencies)} dependencies"
737
+ "Dependency listing completed", data=result
454
738
  )
455
739
 
456
740
  except Exception as e:
@@ -460,20 +744,93 @@ class AgentsCommand(AgentCommand):
460
744
  def _fix_agent_dependencies(self, args) -> CommandResult:
461
745
  """Fix agent dependency issues."""
462
746
  try:
463
- result = self.deployment_service.fix_dependencies()
747
+ max_retries = getattr(args, "max_retries", 3)
748
+ agent_name = getattr(args, "agent", None)
464
749
 
465
- output_format = getattr(args, "format", "text")
466
- if output_format in ["json", "yaml"]:
467
- return CommandResult.success_result(
468
- "Dependency issues fixed", data=result
750
+ print("=" * 70)
751
+ print("FIXING AGENT DEPENDENCIES WITH RETRY LOGIC")
752
+ print("=" * 70)
753
+ print()
754
+
755
+ result = self.dependency_service.fix_dependencies(
756
+ max_retries=max_retries, agent_name=agent_name
757
+ )
758
+
759
+ if not result["success"]:
760
+ if "error" in result and "not deployed" in result["error"]:
761
+ print(f"❌ {result['error']}")
762
+ return CommandResult.error_result(result.get("error", "Fix failed"))
763
+
764
+ if result.get("message") == "No deployed agents found":
765
+ print("No deployed agents found")
766
+ return CommandResult.success_result("No agents to fix")
767
+
768
+ if result.get("message") == "All dependencies are already satisfied":
769
+ print("\n✅ All dependencies are already satisfied!")
770
+ return CommandResult.success_result("All dependencies satisfied")
771
+
772
+ # Show what's missing
773
+ if result.get("missing_python"):
774
+ print(f"\n❌ Missing Python packages: {len(result['missing_python'])}")
775
+ for pkg in result["missing_python"][:10]:
776
+ print(f" - {pkg}")
777
+ if len(result["missing_python"]) > 10:
778
+ print(f" ... and {len(result['missing_python']) - 10} more")
779
+
780
+ if result.get("missing_system"):
781
+ print(f"\n❌ Missing system commands: {len(result['missing_system'])}")
782
+ for cmd in result["missing_system"]:
783
+ print(f" - {cmd}")
784
+ print("\n⚠️ System dependencies must be installed manually:")
785
+ print(f" macOS: brew install {' '.join(result['missing_system'])}")
786
+ print(f" Ubuntu: apt-get install {' '.join(result['missing_system'])}")
787
+
788
+ # Show incompatible packages
789
+ if result.get("incompatible"):
790
+ print(
791
+ f"\n⚠️ Skipping {len(result['incompatible'])} incompatible packages:"
469
792
  )
470
- # Text output
471
- print(" Agent dependency issues fixed")
472
- if result.get("fixes_applied"):
473
- for fix in result["fixes_applied"]:
474
- print(f" - {fix}")
793
+ for pkg in result["incompatible"][:5]:
794
+ print(f" - {pkg}")
795
+ if len(result["incompatible"]) > 5:
796
+ print(f" ... and {len(result['incompatible']) - 5} more")
797
+
798
+ # Show installation results
799
+ if result.get("fixed_python") or result.get("failed_python"):
800
+ print("\n" + "=" * 70)
801
+ print("INSTALLATION RESULTS:")
802
+ print("=" * 70)
803
+
804
+ if result.get("fixed_python"):
805
+ print(
806
+ f"✅ Successfully installed: {len(result['fixed_python'])} packages"
807
+ )
475
808
 
476
- return CommandResult.success_result("Dependency issues fixed")
809
+ if result.get("failed_python"):
810
+ print(
811
+ f"❌ Failed to install: {len(result['failed_python'])} packages"
812
+ )
813
+ errors = result.get("errors", {})
814
+ for pkg in result["failed_python"]:
815
+ print(f" - {pkg}: {errors.get(pkg, 'Unknown error')}")
816
+
817
+ # Final verification
818
+ if result.get("still_missing") is not None:
819
+ if not result["still_missing"]:
820
+ print("\n✅ All Python dependencies are now satisfied!")
821
+ else:
822
+ print(
823
+ f"\n⚠️ Still missing {len(result['still_missing'])} packages"
824
+ )
825
+ print("\nTry running again or install manually:")
826
+ missing_sample = result["still_missing"][:3]
827
+ print(f" pip install {' '.join(missing_sample)}")
828
+
829
+ print("\n" + "=" * 70)
830
+ print("DONE")
831
+ print("=" * 70)
832
+
833
+ return CommandResult.success_result("Dependency fix completed", data=result)
477
834
 
478
835
  except Exception as e:
479
836
  self.logger.error(f"Error fixing dependencies: {e}", exc_info=True)
@@ -482,75 +839,34 @@ class AgentsCommand(AgentCommand):
482
839
  def _cleanup_orphaned_agents(self, args) -> CommandResult:
483
840
  """Clean up orphaned agents that don't have templates."""
484
841
  try:
485
- from ...services.agents.deployment.multi_source_deployment_service import (
486
- MultiSourceAgentDeploymentService,
487
- )
488
-
489
842
  # Determine agents directory
843
+ agents_dir = None
490
844
  if hasattr(args, "agents_dir") and args.agents_dir:
491
845
  agents_dir = args.agents_dir
492
- else:
493
- # Check for project-level .claude/agents first
494
- project_agents_dir = Path.cwd() / ".claude" / "agents"
495
- if project_agents_dir.exists():
496
- agents_dir = project_agents_dir
497
- else:
498
- # Fall back to user home directory
499
- agents_dir = Path.home() / ".claude" / "agents"
500
-
501
- if not agents_dir.exists():
502
- return CommandResult.success_result(
503
- f"Agents directory not found: {agents_dir}"
504
- )
505
-
506
- # Initialize service
507
- service = MultiSourceAgentDeploymentService()
508
846
 
509
847
  # Determine if we're doing a dry run
510
848
  dry_run = getattr(args, "dry_run", True)
511
849
  if hasattr(args, "force") and args.force:
512
850
  dry_run = False
513
851
 
514
- # Perform cleanup
515
- results = service.cleanup_orphaned_agents(agents_dir, dry_run=dry_run)
852
+ # Perform cleanup using the cleanup service
853
+ results = self.cleanup_service.clean_orphaned_agents(
854
+ agents_dir=agents_dir, dry_run=dry_run
855
+ )
516
856
 
517
857
  output_format = getattr(args, "format", "text")
518
- quiet = getattr(args, "quiet", False)
519
-
520
- if output_format in ["json", "yaml"]:
521
- return CommandResult.success_result(
522
- f"Found {len(results.get('orphaned', []))} orphaned agents",
523
- data=results,
524
- )
525
- # Text output
526
- if not results.get("orphaned"):
527
- print("✅ No orphaned agents found")
528
- return CommandResult.success_result("No orphaned agents found")
529
858
 
530
- if not quiet:
531
- print(f"\nFound {len(results['orphaned'])} orphaned agent(s):")
532
- for orphan in results["orphaned"]:
533
- print(f" - {orphan['name']} v{orphan['version']}")
859
+ formatted = self._formatter.format_cleanup_result(
860
+ results, output_format=output_format, dry_run=dry_run
861
+ )
862
+ print(formatted)
534
863
 
535
- if dry_run:
536
- print(
537
- f"\n📝 This was a dry run. Use --force to actually remove "
538
- f"{len(results['orphaned'])} orphaned agent(s)"
864
+ # Determine success/error based on results
865
+ if results.get("errors") and not dry_run:
866
+ return CommandResult.error_result(
867
+ f"Cleanup completed with {len(results['errors'])} errors",
868
+ data=results,
539
869
  )
540
- else:
541
- if results.get("removed"):
542
- print(
543
- f"\n✅ Successfully removed {len(results['removed'])} orphaned agent(s)"
544
- )
545
-
546
- if results.get("errors"):
547
- print(f"\n❌ Encountered {len(results['errors'])} error(s):")
548
- for error in results["errors"]:
549
- print(f" - {error}")
550
- return CommandResult.error_result(
551
- f"Cleanup completed with {len(results['errors'])} errors",
552
- data=results,
553
- )
554
870
 
555
871
  return CommandResult.success_result(
556
872
  f"Cleanup {'preview' if dry_run else 'completed'}", data=results
@@ -575,772 +891,3 @@ def manage_agents(args):
575
891
  command.print_result(result, args)
576
892
 
577
893
  return result.exit_code
578
-
579
-
580
- def _list_agents(args, deployment_service):
581
- """Legacy function for backward compatibility."""
582
- command = AgentsCommand()
583
- command._deployment_service = deployment_service
584
- result = command._list_agents(args)
585
- return result.exit_code
586
-
587
-
588
- def _deploy_agents(args, deployment_service, force=False):
589
- """
590
- Deploy both system and project agents.
591
-
592
- WHY: Agents need to be deployed to the working directory for Claude Code to use them.
593
- This function handles both regular and forced deployment, including project-specific agents.
594
-
595
- Args:
596
- args: Command arguments with optional 'target' path
597
- deployment_service: Agent deployment service instance
598
- force: Whether to force rebuild all agents
599
- """
600
- # Load configuration to get exclusion settings using ConfigLoader
601
- config_loader = ConfigLoader()
602
- config = config_loader.load_main_config()
603
-
604
- # Check if user wants to override exclusions
605
- if hasattr(args, "include_all") and args.include_all:
606
- # Clear exclusion list if --include-all flag is set
607
- config.set("agent_deployment.excluded_agents", [])
608
- print("✅ Including all agents (exclusion configuration overridden)\n")
609
- else:
610
- excluded_agents = config.get("agent_deployment.excluded_agents", [])
611
-
612
- # Display exclusion information if agents are being excluded
613
- if excluded_agents:
614
- logger = get_logger("cli")
615
- logger.info(f"Configured agent exclusions: {excluded_agents}")
616
- print(
617
- f"\n⚠️ Excluding agents from deployment: {', '.join(excluded_agents)}"
618
- )
619
-
620
- # Warn if commonly used agents are being excluded
621
- common_agents = {"engineer", "qa", "security", "documentation"}
622
- excluded_common = {a.lower() for a in excluded_agents} & common_agents
623
- if excluded_common:
624
- print(
625
- f"⚠️ Warning: Common agents are being excluded: {', '.join(excluded_common)}"
626
- )
627
- print(
628
- " This may affect normal operations. Use 'claude-mpm agents deploy --include-all' to override.\n"
629
- )
630
-
631
- # Deploy system agents first
632
- if force:
633
- print("Force deploying all system agents...")
634
- else:
635
- print("Deploying system agents...")
636
-
637
- results = deployment_service.deploy_agents(None, force_rebuild=force, config=config)
638
-
639
- # Also deploy project agents if they exist
640
- from pathlib import Path
641
-
642
- # Use the user's working directory if available
643
- if "CLAUDE_MPM_USER_PWD" in os.environ:
644
- project_dir = Path(os.environ["CLAUDE_MPM_USER_PWD"])
645
- else:
646
- project_dir = Path.cwd()
647
-
648
- project_agents_dir = project_dir / ".claude-mpm" / "agents"
649
- if project_agents_dir.exists():
650
- json_files = list(project_agents_dir.glob("*.json"))
651
- if json_files:
652
- print(f"\nDeploying {len(json_files)} project agents...")
653
- from claude_mpm.services.agents.deployment.agent_deployment import (
654
- AgentDeploymentService,
655
- )
656
-
657
- project_service = AgentDeploymentService(
658
- templates_dir=project_agents_dir,
659
- base_agent_path=(
660
- project_agents_dir / "base_agent.json"
661
- if (project_agents_dir / "base_agent.json").exists()
662
- else None
663
- ),
664
- working_directory=project_dir, # Pass the project directory
665
- )
666
- # Pass the same configuration to project agent deployment
667
- # For project agents, let the service determine they should stay in project directory
668
- project_results = project_service.deploy_agents(
669
- target_dir=None, # Let service detect it's a project deployment
670
- force_rebuild=force,
671
- deployment_mode="project",
672
- config=config,
673
- )
674
-
675
- # Merge project results into main results
676
- if project_results.get("deployed"):
677
- results["deployed"].extend(project_results["deployed"])
678
- print(f"✓ Deployed {len(project_results['deployed'])} project agents")
679
- if project_results.get("updated"):
680
- results["updated"].extend(project_results["updated"])
681
- print(f"✓ Updated {len(project_results['updated'])} project agents")
682
- if project_results.get("errors"):
683
- results["errors"].extend(project_results["errors"])
684
-
685
- if results["deployed"]:
686
- print(
687
- f"\n✓ Successfully deployed {len(results['deployed'])} agents to {results['target_dir']}"
688
- )
689
- for agent in results["deployed"]:
690
- print(f" - {agent['name']}")
691
-
692
- if force and results.get("updated", []):
693
- print(f"\n✓ Updated {len(results['updated'])} agents")
694
- for agent in results["updated"]:
695
- print(f" - {agent['name']}")
696
-
697
- if force and results.get("skipped", []):
698
- print(f"\n✓ Skipped {len(results['skipped'])} up-to-date agents")
699
-
700
- if results["errors"]:
701
- print("\n❌ Errors during deployment:")
702
- for error in results["errors"]:
703
- print(f" - {error}")
704
-
705
- if force:
706
- # Set environment for force deploy
707
- env_vars = deployment_service.set_claude_environment(
708
- args.target.parent if args.target else None
709
- )
710
- print("\n✓ Set Claude environment variables:")
711
- for key, value in env_vars.items():
712
- print(f" - {key}={value}")
713
-
714
-
715
- def _clean_agents(args, deployment_service):
716
- """
717
- Clean deployed system agents.
718
-
719
- WHY: Users may want to remove deployed agents to start fresh or clean up
720
- their working directory.
721
-
722
- Args:
723
- args: Command arguments with optional 'target' path
724
- deployment_service: Agent deployment service instance
725
- """
726
- print("Cleaning deployed system agents...")
727
- results = deployment_service.clean_deployment(args.target)
728
-
729
- if results["removed"]:
730
- print(f"\n✓ Removed {len(results['removed'])} agents")
731
- for path in results["removed"]:
732
- print(f" - {Path(path).name}")
733
- else:
734
- print("No system agents found to remove")
735
-
736
- if results["errors"]:
737
- print("\n❌ Errors during cleanup:")
738
- for error in results["errors"]:
739
- print(f" - {error}")
740
-
741
-
742
- def _list_agents_by_tier():
743
- """
744
- List agents grouped by precedence tier.
745
-
746
- WHY: Users need to understand which agents are active across different tiers
747
- and which version takes precedence when multiple versions exist.
748
- """
749
- try:
750
- adapter = AgentRegistryAdapter()
751
- if not adapter.registry:
752
- print("❌ Could not initialize agent registry")
753
- return
754
-
755
- # Get all agents and group by tier
756
- all_agents = adapter.registry.list_agents()
757
-
758
- # Group agents by tier and name
759
- tiers = {"project": {}, "user": {}, "system": {}}
760
- agent_names = set()
761
-
762
- for agent_id, metadata in all_agents.items():
763
- tier = metadata.get("tier", "system")
764
- if tier in tiers:
765
- tiers[tier][agent_id] = metadata
766
- agent_names.add(agent_id)
767
-
768
- # Display header
769
- print("\n" + "=" * 80)
770
- print(" " * 25 + "AGENT HIERARCHY BY TIER")
771
- print("=" * 80)
772
- print("\nPrecedence: PROJECT > USER > SYSTEM")
773
- print("(Agents in higher tiers override those in lower tiers)\n")
774
-
775
- # Display each tier
776
- tier_order = [("PROJECT", "project"), ("USER", "user"), ("SYSTEM", "system")]
777
-
778
- for tier_display, tier_key in tier_order:
779
- agents = tiers[tier_key]
780
- print(f"\n{'─' * 35} {tier_display} TIER {'─' * 35}")
781
-
782
- if not agents:
783
- print(f" No agents at {tier_key} level")
784
- else:
785
- # Check paths to determine actual locations
786
- if tier_key == "project":
787
- print(" Location: .claude-mpm/agents/ (in current project)")
788
- elif tier_key == "user":
789
- print(" Location: ~/.claude-mpm/agents/")
790
- else:
791
- print(" Location: Built-in framework agents")
792
-
793
- print(f"\n Found {len(agents)} agent(s):\n")
794
-
795
- for agent_id, metadata in sorted(agents.items()):
796
- # Check if this agent is overridden by higher tiers
797
- is_active = True
798
- overridden_by = []
799
-
800
- for check_tier_display, check_tier_key in tier_order:
801
- if check_tier_key == tier_key:
802
- break
803
- if agent_id in tiers[check_tier_key]:
804
- is_active = False
805
- overridden_by.append(check_tier_display)
806
-
807
- # Display agent info
808
- status = (
809
- "✓ ACTIVE"
810
- if is_active
811
- else f"⊗ OVERRIDDEN by {', '.join(overridden_by)}"
812
- )
813
- print(f" 📄 {agent_id:<20} [{status}]")
814
-
815
- # Show metadata
816
- if "description" in metadata:
817
- print(f" Description: {metadata['description']}")
818
- if "path" in metadata:
819
- path = Path(metadata["path"])
820
- print(f" File: {path.name}")
821
- print()
822
-
823
- # Summary
824
- print("\n" + "=" * 80)
825
- print("SUMMARY:")
826
- print(f" Total unique agents: {len(agent_names)}")
827
- print(f" Project agents: {len(tiers['project'])}")
828
- print(f" User agents: {len(tiers['user'])}")
829
- print(f" System agents: {len(tiers['system'])}")
830
- print("=" * 80 + "\n")
831
-
832
- except Exception as e:
833
- print(f"❌ Error listing agents by tier: {e}")
834
-
835
-
836
- def _view_agent(args):
837
- """
838
- View detailed information about a specific agent.
839
-
840
- WHY: Users need to inspect agent configurations, frontmatter, and instructions
841
- to understand what an agent does and how it's configured.
842
-
843
- Args:
844
- args: Command arguments with 'agent_name' attribute
845
- """
846
- if not hasattr(args, "agent_name") or not args.agent_name:
847
- print("❌ Please specify an agent name to view")
848
- print("Usage: claude-mpm agents view <agent_name>")
849
- return
850
-
851
- try:
852
- adapter = AgentRegistryAdapter()
853
- if not adapter.registry:
854
- print("❌ Could not initialize agent registry")
855
- return
856
-
857
- # Get the agent
858
- agent = adapter.registry.get_agent(args.agent_name)
859
- if not agent:
860
- print(f"❌ Agent '{args.agent_name}' not found")
861
- print("\nAvailable agents:")
862
- all_agents = adapter.registry.list_agents()
863
- for agent_id in sorted(all_agents.keys()):
864
- print(f" - {agent_id}")
865
- return
866
-
867
- # Read the agent file
868
- agent_path = Path(agent.path)
869
- if not agent_path.exists():
870
- print(f"❌ Agent file not found: {agent_path}")
871
- return
872
-
873
- with open(agent_path) as f:
874
- content = f.read()
875
-
876
- # Display agent information
877
- print("\n" + "=" * 80)
878
- print(f" AGENT: {agent.name}")
879
- print("=" * 80)
880
-
881
- # Basic info
882
- print("\n📋 BASIC INFORMATION:")
883
- print(f" Name: {agent.name}")
884
- print(f" Type: {agent.type}")
885
- print(f" Tier: {agent.tier.upper()}")
886
- print(f" Path: {agent_path}")
887
- if agent.description:
888
- print(f" Description: {agent.description}")
889
- if agent.specializations:
890
- print(f" Specializations: {', '.join(agent.specializations)}")
891
-
892
- # Extract and display frontmatter
893
- if content.startswith("---"):
894
- try:
895
- end_marker = content.find("\n---\n", 4)
896
- if end_marker == -1:
897
- end_marker = content.find("\n---\r\n", 4)
898
-
899
- if end_marker != -1:
900
- frontmatter_str = content[4:end_marker]
901
- frontmatter = yaml.safe_load(frontmatter_str)
902
-
903
- print("\n📝 FRONTMATTER:")
904
- for key, value in frontmatter.items():
905
- if isinstance(value, list):
906
- print(f" {key}: [{', '.join(str(v) for v in value)}]")
907
- elif isinstance(value, dict):
908
- print(f" {key}:")
909
- for k, v in value.items():
910
- print(f" {k}: {v}")
911
- else:
912
- print(f" {key}: {value}")
913
-
914
- # Extract instructions preview
915
- instructions_start = end_marker + 5
916
- instructions = content[instructions_start:].strip()
917
-
918
- if instructions:
919
- print("\n📖 INSTRUCTIONS PREVIEW (first 500 chars):")
920
- print(" " + "-" * 76)
921
- preview = instructions[:500]
922
- if len(instructions) > 500:
923
- preview += f"...\n\n [Truncated - {len(instructions) / 1024:.1f}KB total]"
924
-
925
- for line in preview.split("\n"):
926
- print(f" {line}")
927
- print(" " + "-" * 76)
928
- except Exception as e:
929
- print(f"\n⚠️ Could not parse frontmatter: {e}")
930
- else:
931
- print("\n⚠️ No frontmatter found in agent file")
932
-
933
- # File stats
934
- import os
935
-
936
- stat = os.stat(agent_path)
937
- from datetime import datetime
938
-
939
- modified = datetime.fromtimestamp(stat.st_mtime).strftime("%Y-%m-%d %H:%M:%S")
940
- print("\n📊 FILE STATS:")
941
- print(f" Size: {stat.st_size:,} bytes")
942
- print(f" Last modified: {modified}")
943
-
944
- print("\n" + "=" * 80 + "\n")
945
-
946
- except Exception as e:
947
- print(f"❌ Error viewing agent: {e}")
948
-
949
-
950
- def _fix_agents(args):
951
- """
952
- Fix agent frontmatter issues using FrontmatterValidator.
953
-
954
- WHY: Agent files may have formatting issues in their frontmatter that prevent
955
- proper loading. This command automatically fixes common issues.
956
-
957
- Args:
958
- args: Command arguments with 'agent_name', 'dry_run', and 'all' flags
959
- """
960
- validator = FrontmatterValidator()
961
-
962
- try:
963
- adapter = AgentRegistryAdapter()
964
- if not adapter.registry:
965
- print("❌ Could not initialize agent registry")
966
- return
967
-
968
- # Determine which agents to fix
969
- agents_to_fix = []
970
-
971
- if hasattr(args, "all") and args.all:
972
- # Fix all agents
973
- all_agents = adapter.registry.list_agents()
974
- for agent_id, metadata in all_agents.items():
975
- agents_to_fix.append((agent_id, metadata["path"]))
976
- print(
977
- f"\n🔧 Checking {len(agents_to_fix)} agent(s) for frontmatter issues...\n"
978
- )
979
- elif hasattr(args, "agent_name") and args.agent_name:
980
- # Fix specific agent
981
- agent = adapter.registry.get_agent(args.agent_name)
982
- if not agent:
983
- print(f"❌ Agent '{args.agent_name}' not found")
984
- return
985
- agents_to_fix.append((agent.name, agent.path))
986
- print(f"\n🔧 Checking agent '{agent.name}' for frontmatter issues...\n")
987
- else:
988
- print("❌ Please specify an agent name or use --all to fix all agents")
989
- print("Usage: claude-mpm agents fix [agent_name] [--dry-run] [--all]")
990
- return
991
-
992
- dry_run = hasattr(args, "dry_run") and args.dry_run
993
- if dry_run:
994
- print("🔍 DRY RUN MODE - No changes will be made\n")
995
-
996
- # Process each agent
997
- total_issues = 0
998
- total_fixed = 0
999
-
1000
- for agent_name, agent_path in agents_to_fix:
1001
- path = Path(agent_path)
1002
- if not path.exists():
1003
- print(f"⚠️ Skipping {agent_name}: File not found at {path}")
1004
- continue
1005
-
1006
- print(f"📄 {agent_name}:")
1007
-
1008
- # Validate and potentially fix
1009
- result = validator.correct_file(path, dry_run=dry_run)
1010
-
1011
- if result.is_valid and not result.corrections:
1012
- print(" ✓ No issues found")
1013
- else:
1014
- if result.errors:
1015
- print(" ❌ Errors:")
1016
- for error in result.errors:
1017
- print(f" - {error}")
1018
- total_issues += len(result.errors)
1019
-
1020
- if result.warnings:
1021
- print(" ⚠️ Warnings:")
1022
- for warning in result.warnings:
1023
- print(f" - {warning}")
1024
- total_issues += len(result.warnings)
1025
-
1026
- if result.corrections:
1027
- if dry_run:
1028
- print(" 🔧 Would fix:")
1029
- else:
1030
- print(" ✓ Fixed:")
1031
- total_fixed += len(result.corrections)
1032
- for correction in result.corrections:
1033
- print(f" - {correction}")
1034
-
1035
- print()
1036
-
1037
- # Summary
1038
- print("=" * 80)
1039
- print("SUMMARY:")
1040
- print(f" Agents checked: {len(agents_to_fix)}")
1041
- print(f" Total issues found: {total_issues}")
1042
- if dry_run:
1043
- print(
1044
- f" Issues that would be fixed: {sum(1 for _, path in agents_to_fix if validator.validate_file(Path(path)).corrections)}"
1045
- )
1046
- print("\n💡 Run without --dry-run to apply fixes")
1047
- else:
1048
- print(f" Issues fixed: {total_fixed}")
1049
- if total_fixed > 0:
1050
- print("\n✓ Frontmatter issues have been fixed!")
1051
- print("=" * 80 + "\n")
1052
-
1053
- except Exception as e:
1054
- print(f"❌ Error fixing agents: {e}")
1055
-
1056
-
1057
- def _check_agent_dependencies(args):
1058
- """
1059
- Check dependencies for deployed agents.
1060
-
1061
- Args:
1062
- args: Parsed command line arguments
1063
- """
1064
- from ...utils.agent_dependency_loader import AgentDependencyLoader
1065
-
1066
- getattr(args, "verbose", False)
1067
- specific_agent = getattr(args, "agent", None)
1068
-
1069
- loader = AgentDependencyLoader(auto_install=False)
1070
-
1071
- # Discover deployed agents
1072
- loader.discover_deployed_agents()
1073
-
1074
- # Filter to specific agent if requested
1075
- if specific_agent:
1076
- if specific_agent not in loader.deployed_agents:
1077
- print(f"❌ Agent '{specific_agent}' is not deployed")
1078
- print(f" Available agents: {', '.join(loader.deployed_agents.keys())}")
1079
- return
1080
- # Keep only the specified agent
1081
- loader.deployed_agents = {
1082
- specific_agent: loader.deployed_agents[specific_agent]
1083
- }
1084
-
1085
- # Load dependencies and check
1086
- loader.load_agent_dependencies()
1087
- results = loader.analyze_dependencies()
1088
-
1089
- # Print report
1090
- report = loader.format_report(results)
1091
- print(report)
1092
-
1093
-
1094
- def _install_agent_dependencies(args):
1095
- """
1096
- Install missing dependencies for deployed agents.
1097
-
1098
- Args:
1099
- args: Parsed command line arguments
1100
- """
1101
-
1102
- from ...utils.agent_dependency_loader import AgentDependencyLoader
1103
-
1104
- specific_agent = getattr(args, "agent", None)
1105
- dry_run = getattr(args, "dry_run", False)
1106
-
1107
- loader = AgentDependencyLoader(auto_install=not dry_run)
1108
-
1109
- # Discover deployed agents
1110
- loader.discover_deployed_agents()
1111
-
1112
- # Filter to specific agent if requested
1113
- if specific_agent:
1114
- if specific_agent not in loader.deployed_agents:
1115
- print(f"❌ Agent '{specific_agent}' is not deployed")
1116
- print(f" Available agents: {', '.join(loader.deployed_agents.keys())}")
1117
- return
1118
- loader.deployed_agents = {
1119
- specific_agent: loader.deployed_agents[specific_agent]
1120
- }
1121
-
1122
- # Load dependencies
1123
- loader.load_agent_dependencies()
1124
- results = loader.analyze_dependencies()
1125
-
1126
- missing_deps = results["summary"]["missing_python"]
1127
-
1128
- if not missing_deps:
1129
- print("✅ All Python dependencies are already installed")
1130
- return
1131
-
1132
- print(f"Found {len(missing_deps)} missing dependencies:")
1133
- for dep in missing_deps:
1134
- print(f" - {dep}")
1135
-
1136
- if dry_run:
1137
- print("\n--dry-run specified, not installing anything")
1138
- print(f"Would install: pip install {' '.join(missing_deps)}")
1139
- else:
1140
- print(f"\nInstalling {len(missing_deps)} dependencies...")
1141
- success, error = loader.install_missing_dependencies(missing_deps)
1142
-
1143
- if success:
1144
- print("✅ Successfully installed all dependencies")
1145
-
1146
- # Re-check after installation
1147
- loader.checked_packages.clear()
1148
- results = loader.analyze_dependencies()
1149
-
1150
- if results["summary"]["missing_python"]:
1151
- print(
1152
- f"⚠️ {len(results['summary']['missing_python'])} dependencies still missing after installation"
1153
- )
1154
- else:
1155
- print("✅ All dependencies verified after installation")
1156
- else:
1157
- print(f"❌ Failed to install dependencies: {error}")
1158
-
1159
-
1160
- def _list_agent_dependencies(args):
1161
- """
1162
- List all dependencies from deployed agents.
1163
-
1164
- Args:
1165
- args: Parsed command line arguments
1166
- """
1167
-
1168
- from ...utils.agent_dependency_loader import AgentDependencyLoader
1169
-
1170
- output_format = getattr(args, "format", "text")
1171
-
1172
- loader = AgentDependencyLoader(auto_install=False)
1173
-
1174
- # Discover and load
1175
- loader.discover_deployed_agents()
1176
- loader.load_agent_dependencies()
1177
-
1178
- # Collect all unique dependencies
1179
- all_python_deps = set()
1180
- all_system_deps = set()
1181
-
1182
- for agent_id, deps in loader.agent_dependencies.items():
1183
- if "python" in deps:
1184
- all_python_deps.update(deps["python"])
1185
- if "system" in deps:
1186
- all_system_deps.update(deps["system"])
1187
-
1188
- # Format output based on requested format
1189
- if output_format == "pip":
1190
- # Output pip-installable format
1191
- for dep in sorted(all_python_deps):
1192
- print(dep)
1193
-
1194
- elif output_format == "json":
1195
- # Output JSON format
1196
- output = {
1197
- "python": sorted(all_python_deps),
1198
- "system": sorted(all_system_deps),
1199
- "agents": {},
1200
- }
1201
- for agent_id, deps in loader.agent_dependencies.items():
1202
- output["agents"][agent_id] = deps
1203
- print(json.dumps(output, indent=2))
1204
-
1205
- else: # text format
1206
- print("=" * 60)
1207
- print("DEPENDENCIES FROM DEPLOYED AGENTS")
1208
- print("=" * 60)
1209
- print()
1210
-
1211
- if all_python_deps:
1212
- print(f"Python Dependencies ({len(all_python_deps)}):")
1213
- print("-" * 30)
1214
- for dep in sorted(all_python_deps):
1215
- print(f" {dep}")
1216
- print()
1217
-
1218
- if all_system_deps:
1219
- print(f"System Dependencies ({len(all_system_deps)}):")
1220
- print("-" * 30)
1221
- for dep in sorted(all_system_deps):
1222
- print(f" {dep}")
1223
- print()
1224
-
1225
- print("Per-Agent Dependencies:")
1226
- print("-" * 30)
1227
- for agent_id in sorted(loader.agent_dependencies.keys()):
1228
- deps = loader.agent_dependencies[agent_id]
1229
- python_count = len(deps.get("python", []))
1230
- system_count = len(deps.get("system", []))
1231
- if python_count or system_count:
1232
- print(f" {agent_id}: {python_count} Python, {system_count} System")
1233
-
1234
-
1235
- def _fix_agent_dependencies(args):
1236
- """
1237
- Fix missing agent dependencies with robust retry logic.
1238
-
1239
- WHY: Network issues and temporary package unavailability can cause
1240
- dependency installation to fail. This command uses robust retry logic
1241
- to maximize success rate.
1242
-
1243
- Args:
1244
- args: Parsed command line arguments
1245
- """
1246
- from ...utils.agent_dependency_loader import AgentDependencyLoader
1247
- from ...utils.robust_installer import RobustPackageInstaller
1248
-
1249
- max_retries = getattr(args, "max_retries", 3)
1250
-
1251
- print("=" * 70)
1252
- print("FIXING AGENT DEPENDENCIES WITH RETRY LOGIC")
1253
- print("=" * 70)
1254
- print()
1255
-
1256
- loader = AgentDependencyLoader(auto_install=False)
1257
-
1258
- # Discover and analyze
1259
- print("Discovering deployed agents...")
1260
- loader.discover_deployed_agents()
1261
-
1262
- if not loader.deployed_agents:
1263
- print("No deployed agents found")
1264
- return
1265
-
1266
- print(f"Found {len(loader.deployed_agents)} deployed agents")
1267
- print("Analyzing dependencies...")
1268
-
1269
- loader.load_agent_dependencies()
1270
- results = loader.analyze_dependencies()
1271
-
1272
- missing_python = results["summary"]["missing_python"]
1273
- missing_system = results["summary"]["missing_system"]
1274
-
1275
- if not missing_python and not missing_system:
1276
- print("\n✅ All dependencies are already satisfied!")
1277
- return
1278
-
1279
- # Show what's missing
1280
- if missing_python:
1281
- print(f"\n❌ Missing Python packages: {len(missing_python)}")
1282
- for pkg in missing_python[:10]:
1283
- print(f" - {pkg}")
1284
- if len(missing_python) > 10:
1285
- print(f" ... and {len(missing_python) - 10} more")
1286
-
1287
- if missing_system:
1288
- print(f"\n❌ Missing system commands: {len(missing_system)}")
1289
- for cmd in missing_system:
1290
- print(f" - {cmd}")
1291
- print("\n⚠️ System dependencies must be installed manually:")
1292
- print(f" macOS: brew install {' '.join(missing_system)}")
1293
- print(f" Ubuntu: apt-get install {' '.join(missing_system)}")
1294
-
1295
- # Fix Python dependencies with robust installer
1296
- if missing_python:
1297
- print(
1298
- f"\n🔧 Fixing Python dependencies with {max_retries} retries per package..."
1299
- )
1300
-
1301
- # Check compatibility
1302
- compatible, incompatible = loader.check_python_compatibility(missing_python)
1303
-
1304
- if incompatible:
1305
- print(f"\n⚠️ Skipping {len(incompatible)} incompatible packages:")
1306
- for pkg in incompatible[:5]:
1307
- print(f" - {pkg}")
1308
- if len(incompatible) > 5:
1309
- print(f" ... and {len(incompatible) - 5} more")
1310
-
1311
- if compatible:
1312
- installer = RobustPackageInstaller(
1313
- max_retries=max_retries, retry_delay=2.0, timeout=300
1314
- )
1315
-
1316
- print(f"\nInstalling {len(compatible)} compatible packages...")
1317
- successful, failed, errors = installer.install_packages(compatible)
1318
-
1319
- print("\n" + "=" * 70)
1320
- print("INSTALLATION RESULTS:")
1321
- print("=" * 70)
1322
-
1323
- if successful:
1324
- print(f"✅ Successfully installed: {len(successful)} packages")
1325
-
1326
- if failed:
1327
- print(f"❌ Failed to install: {len(failed)} packages")
1328
- for pkg in failed:
1329
- print(f" - {pkg}: {errors.get(pkg, 'Unknown error')}")
1330
-
1331
- # Re-check
1332
- print("\nVerifying installation...")
1333
- loader.checked_packages.clear()
1334
- final_results = loader.analyze_dependencies()
1335
-
1336
- final_missing = final_results["summary"]["missing_python"]
1337
- if not final_missing:
1338
- print("✅ All Python dependencies are now satisfied!")
1339
- else:
1340
- print(f"⚠️ Still missing {len(final_missing)} packages")
1341
- print("\nTry running again or install manually:")
1342
- print(f" pip install {' '.join(final_missing[:3])}")
1343
-
1344
- print("\n" + "=" * 70)
1345
- print("DONE")
1346
- print("=" * 70)