claude-mpm 4.24.0__py3-none-any.whl → 5.4.41__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of claude-mpm might be problematic. Click here for more details.

Files changed (623) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/__init__.py +4 -0
  3. claude_mpm/agents/BASE_AGENT.md +164 -0
  4. claude_mpm/agents/{OUTPUT_STYLE.md → CLAUDE_MPM_OUTPUT_STYLE.md} +3 -48
  5. claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +2002 -0
  6. claude_mpm/agents/MEMORY.md +1 -1
  7. claude_mpm/agents/PM_INSTRUCTIONS.md +735 -925
  8. claude_mpm/agents/WORKFLOW.md +5 -254
  9. claude_mpm/agents/__init__.py +6 -0
  10. claude_mpm/agents/agent_loader.py +14 -48
  11. claude_mpm/agents/base_agent.json +7 -4
  12. claude_mpm/agents/frontmatter_validator.py +71 -3
  13. claude_mpm/agents/templates/circuit-breakers.md +1391 -0
  14. claude_mpm/agents/templates/context-management-examples.md +544 -0
  15. claude_mpm/agents/templates/{pm_red_flags.md → pm-red-flags.md} +48 -0
  16. claude_mpm/agents/templates/pr-workflow-examples.md +427 -0
  17. claude_mpm/agents/templates/research-gate-examples.md +669 -0
  18. claude_mpm/agents/templates/structured-questions-examples.md +615 -0
  19. claude_mpm/agents/templates/ticket-completeness-examples.md +139 -0
  20. claude_mpm/agents/templates/ticketing-examples.md +277 -0
  21. claude_mpm/cli/__init__.py +37 -2
  22. claude_mpm/cli/__main__.py +4 -0
  23. claude_mpm/cli/chrome_devtools_installer.py +175 -0
  24. claude_mpm/cli/commands/__init__.py +2 -0
  25. claude_mpm/cli/commands/agent_source.py +774 -0
  26. claude_mpm/cli/commands/agent_state_manager.py +180 -31
  27. claude_mpm/cli/commands/agents.py +1116 -55
  28. claude_mpm/cli/commands/agents_cleanup.py +210 -0
  29. claude_mpm/cli/commands/agents_discover.py +338 -0
  30. claude_mpm/cli/commands/aggregate.py +1 -1
  31. claude_mpm/cli/commands/analyze.py +3 -3
  32. claude_mpm/cli/commands/auto_configure.py +725 -242
  33. claude_mpm/cli/commands/config.py +95 -6
  34. claude_mpm/cli/commands/configure.py +1875 -46
  35. claude_mpm/cli/commands/configure_agent_display.py +29 -10
  36. claude_mpm/cli/commands/configure_navigation.py +63 -46
  37. claude_mpm/cli/commands/debug.py +12 -12
  38. claude_mpm/cli/commands/doctor.py +10 -2
  39. claude_mpm/cli/commands/hook_errors.py +277 -0
  40. claude_mpm/cli/commands/local_deploy.py +1 -4
  41. claude_mpm/cli/commands/mcp_install_commands.py +1 -1
  42. claude_mpm/cli/commands/mpm_init/core.py +229 -2
  43. claude_mpm/cli/commands/mpm_init/git_activity.py +10 -10
  44. claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
  45. claude_mpm/cli/commands/mpm_init/prompts.py +286 -6
  46. claude_mpm/cli/commands/postmortem.py +401 -0
  47. claude_mpm/cli/commands/profile.py +277 -0
  48. claude_mpm/cli/commands/run.py +123 -165
  49. claude_mpm/cli/commands/skill_source.py +694 -0
  50. claude_mpm/cli/commands/skills.py +782 -20
  51. claude_mpm/cli/commands/summarize.py +413 -0
  52. claude_mpm/cli/executor.py +96 -3
  53. claude_mpm/cli/interactive/agent_wizard.py +1030 -45
  54. claude_mpm/cli/parsers/agent_source_parser.py +171 -0
  55. claude_mpm/cli/parsers/agents_parser.py +307 -10
  56. claude_mpm/cli/parsers/auto_configure_parser.py +13 -138
  57. claude_mpm/cli/parsers/base_parser.py +65 -0
  58. claude_mpm/cli/parsers/config_parser.py +162 -39
  59. claude_mpm/cli/parsers/profile_parser.py +148 -0
  60. claude_mpm/cli/parsers/skill_source_parser.py +169 -0
  61. claude_mpm/cli/parsers/skills_parser.py +146 -0
  62. claude_mpm/cli/parsers/source_parser.py +138 -0
  63. claude_mpm/cli/startup.py +1280 -118
  64. claude_mpm/cli/startup_display.py +480 -0
  65. claude_mpm/cli/utils.py +1 -1
  66. claude_mpm/cli_module/commands.py +1 -1
  67. claude_mpm/commands/mpm-config.md +21 -134
  68. claude_mpm/commands/mpm-doctor.md +16 -20
  69. claude_mpm/commands/mpm-help.md +13 -283
  70. claude_mpm/commands/mpm-init.md +88 -489
  71. claude_mpm/commands/mpm-monitor.md +23 -401
  72. claude_mpm/commands/mpm-organize.md +72 -247
  73. claude_mpm/commands/mpm-postmortem.md +21 -0
  74. claude_mpm/commands/mpm-session-resume.md +30 -0
  75. claude_mpm/commands/mpm-status.md +13 -68
  76. claude_mpm/commands/mpm-ticket-view.md +109 -0
  77. claude_mpm/commands/mpm-version.md +13 -106
  78. claude_mpm/commands/mpm.md +10 -0
  79. claude_mpm/config/agent_presets.py +488 -0
  80. claude_mpm/config/agent_sources.py +352 -0
  81. claude_mpm/config/skill_presets.py +392 -0
  82. claude_mpm/config/skill_sources.py +590 -0
  83. claude_mpm/constants.py +13 -0
  84. claude_mpm/core/claude_runner.py +5 -34
  85. claude_mpm/core/config.py +15 -1
  86. claude_mpm/core/constants.py +1 -1
  87. claude_mpm/core/framework/__init__.py +3 -16
  88. claude_mpm/core/framework/formatters/content_formatter.py +3 -13
  89. claude_mpm/core/framework/loaders/agent_loader.py +8 -5
  90. claude_mpm/core/framework/loaders/file_loader.py +54 -101
  91. claude_mpm/core/framework/loaders/instruction_loader.py +66 -5
  92. claude_mpm/core/framework_loader.py +4 -2
  93. claude_mpm/core/hook_error_memory.py +381 -0
  94. claude_mpm/core/hook_manager.py +41 -2
  95. claude_mpm/core/interactive_session.py +91 -10
  96. claude_mpm/core/logger.py +16 -1
  97. claude_mpm/core/oneshot_session.py +71 -8
  98. claude_mpm/core/optimized_startup.py +59 -0
  99. claude_mpm/core/output_style_manager.py +173 -43
  100. claude_mpm/core/protocols/__init__.py +23 -0
  101. claude_mpm/core/protocols/runner_protocol.py +103 -0
  102. claude_mpm/core/protocols/session_protocol.py +131 -0
  103. claude_mpm/core/shared/config_loader.py +1 -1
  104. claude_mpm/core/shared/singleton_manager.py +11 -4
  105. claude_mpm/core/socketio_pool.py +3 -3
  106. claude_mpm/core/system_context.py +38 -0
  107. claude_mpm/core/unified_agent_registry.py +134 -16
  108. claude_mpm/core/unified_config.py +22 -0
  109. claude_mpm/dashboard/static/svelte-build/_app/env.js +1 -0
  110. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.B_FtCwCQ.css +1 -0
  111. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.Cl_eSA4x.css +1 -0
  112. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BgChzWQ1.js +1 -0
  113. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CIXEwuWe.js +1 -0
  114. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CWc5urbQ.js +1 -0
  115. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DMkZpdF2.js +2 -0
  116. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DjhvlsAc.js +1 -0
  117. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/N4qtv3Hx.js +2 -0
  118. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/uj46x2Wr.js +1 -0
  119. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/app.DTL5mJO-.js +2 -0
  120. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.DzuEhzqh.js +1 -0
  121. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/0.CAGBuiOw.js +1 -0
  122. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/1.DFLC8jdE.js +1 -0
  123. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.DPvEihJJ.js +10 -0
  124. claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -0
  125. claude_mpm/dashboard/static/svelte-build/favicon.svg +7 -0
  126. claude_mpm/dashboard/static/svelte-build/index.html +36 -0
  127. claude_mpm/experimental/cli_enhancements.py +1 -5
  128. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-311.pyc +0 -0
  129. claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-311.pyc +0 -0
  130. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
  131. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
  132. claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
  133. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
  134. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
  135. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-311.pyc +0 -0
  136. claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
  137. claude_mpm/hooks/claude_hooks/event_handlers.py +214 -79
  138. claude_mpm/hooks/claude_hooks/hook_handler.py +155 -1
  139. claude_mpm/hooks/claude_hooks/installer.py +33 -10
  140. claude_mpm/hooks/claude_hooks/memory_integration.py +28 -0
  141. claude_mpm/hooks/claude_hooks/response_tracking.py +2 -3
  142. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-311.pyc +0 -0
  143. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager.cpython-311.pyc +0 -0
  144. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
  145. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-311.pyc +0 -0
  146. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
  147. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
  148. claude_mpm/hooks/claude_hooks/services/connection_manager.py +30 -6
  149. claude_mpm/hooks/failure_learning/__init__.py +2 -8
  150. claude_mpm/hooks/failure_learning/failure_detection_hook.py +1 -6
  151. claude_mpm/hooks/failure_learning/fix_detection_hook.py +1 -6
  152. claude_mpm/hooks/failure_learning/learning_extraction_hook.py +1 -6
  153. claude_mpm/hooks/kuzu_response_hook.py +1 -5
  154. claude_mpm/hooks/memory_integration_hook.py +46 -1
  155. claude_mpm/init.py +63 -19
  156. claude_mpm/models/agent_definition.py +7 -0
  157. claude_mpm/models/git_repository.py +198 -0
  158. claude_mpm/scripts/claude-hook-handler.sh +60 -20
  159. claude_mpm/scripts/launch_monitor.py +93 -13
  160. claude_mpm/scripts/start_activity_logging.py +3 -1
  161. claude_mpm/services/agents/agent_builder.py +48 -12
  162. claude_mpm/services/agents/agent_preset_service.py +238 -0
  163. claude_mpm/services/agents/agent_recommendation_service.py +278 -0
  164. claude_mpm/services/agents/agent_review_service.py +280 -0
  165. claude_mpm/services/agents/agent_selection_service.py +484 -0
  166. claude_mpm/services/agents/auto_deploy_index_parser.py +569 -0
  167. claude_mpm/services/agents/cache_git_manager.py +621 -0
  168. claude_mpm/services/agents/deployment/agent_deployment.py +148 -2
  169. claude_mpm/services/agents/deployment/agent_discovery_service.py +104 -73
  170. claude_mpm/services/agents/deployment/agent_format_converter.py +1 -1
  171. claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +1 -5
  172. claude_mpm/services/agents/deployment/agent_metrics_collector.py +3 -3
  173. claude_mpm/services/agents/deployment/agent_restore_handler.py +1 -4
  174. claude_mpm/services/agents/deployment/agent_template_builder.py +238 -15
  175. claude_mpm/services/agents/deployment/agents_directory_resolver.py +101 -15
  176. claude_mpm/services/agents/deployment/async_agent_deployment.py +2 -1
  177. claude_mpm/services/agents/deployment/facade/deployment_facade.py +3 -3
  178. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +422 -31
  179. claude_mpm/services/agents/deployment/pipeline/pipeline_executor.py +2 -2
  180. claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +1 -4
  181. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +841 -0
  182. claude_mpm/services/agents/deployment/single_agent_deployer.py +2 -2
  183. claude_mpm/services/agents/deployment/system_instructions_deployer.py +168 -46
  184. claude_mpm/services/agents/deployment/validation/deployment_validator.py +2 -2
  185. claude_mpm/services/agents/git_source_manager.py +663 -0
  186. claude_mpm/services/agents/loading/base_agent_manager.py +1 -13
  187. claude_mpm/services/agents/loading/framework_agent_loader.py +9 -12
  188. claude_mpm/services/agents/local_template_manager.py +50 -10
  189. claude_mpm/services/agents/recommender.py +5 -3
  190. claude_mpm/services/agents/single_tier_deployment_service.py +696 -0
  191. claude_mpm/services/agents/sources/__init__.py +13 -0
  192. claude_mpm/services/agents/sources/agent_sync_state.py +516 -0
  193. claude_mpm/services/agents/sources/git_source_sync_service.py +1094 -0
  194. claude_mpm/services/agents/startup_sync.py +259 -0
  195. claude_mpm/services/agents/toolchain_detector.py +478 -0
  196. claude_mpm/services/analysis/__init__.py +35 -0
  197. claude_mpm/services/analysis/clone_detector.py +1030 -0
  198. claude_mpm/services/analysis/postmortem_reporter.py +474 -0
  199. claude_mpm/services/analysis/postmortem_service.py +765 -0
  200. claude_mpm/services/cli/session_pause_manager.py +1 -1
  201. claude_mpm/services/command_deployment_service.py +271 -6
  202. claude_mpm/services/core/base.py +7 -2
  203. claude_mpm/services/core/interfaces/__init__.py +1 -3
  204. claude_mpm/services/core/interfaces/health.py +1 -4
  205. claude_mpm/services/core/models/__init__.py +2 -11
  206. claude_mpm/services/diagnostics/checks/__init__.py +4 -0
  207. claude_mpm/services/diagnostics/checks/agent_check.py +2 -4
  208. claude_mpm/services/diagnostics/checks/agent_sources_check.py +577 -0
  209. claude_mpm/services/diagnostics/checks/instructions_check.py +1 -2
  210. claude_mpm/services/diagnostics/checks/mcp_check.py +0 -1
  211. claude_mpm/services/diagnostics/checks/mcp_services_check.py +7 -15
  212. claude_mpm/services/diagnostics/checks/monitor_check.py +0 -1
  213. claude_mpm/services/diagnostics/checks/skill_sources_check.py +587 -0
  214. claude_mpm/services/diagnostics/diagnostic_runner.py +9 -0
  215. claude_mpm/services/diagnostics/doctor_reporter.py +40 -10
  216. claude_mpm/services/event_bus/config.py +3 -1
  217. claude_mpm/services/event_bus/direct_relay.py +3 -3
  218. claude_mpm/services/events/consumers/logging.py +1 -2
  219. claude_mpm/services/git/__init__.py +21 -0
  220. claude_mpm/services/git/git_operations_service.py +579 -0
  221. claude_mpm/services/github/__init__.py +21 -0
  222. claude_mpm/services/github/github_cli_service.py +397 -0
  223. claude_mpm/services/infrastructure/monitoring/__init__.py +1 -5
  224. claude_mpm/services/infrastructure/monitoring/aggregator.py +1 -6
  225. claude_mpm/services/instructions/__init__.py +9 -0
  226. claude_mpm/services/instructions/instruction_cache_service.py +374 -0
  227. claude_mpm/services/local_ops/__init__.py +3 -13
  228. claude_mpm/services/local_ops/health_checks/__init__.py +1 -3
  229. claude_mpm/services/local_ops/health_manager.py +1 -4
  230. claude_mpm/services/local_ops/resource_monitor.py +1 -1
  231. claude_mpm/services/mcp_config_manager.py +75 -145
  232. claude_mpm/services/mcp_service_verifier.py +6 -3
  233. claude_mpm/services/model/model_router.py +1 -2
  234. claude_mpm/services/monitor/daemon.py +38 -11
  235. claude_mpm/services/monitor/daemon_manager.py +134 -21
  236. claude_mpm/services/monitor/management/lifecycle.py +8 -1
  237. claude_mpm/services/monitor/server.py +700 -24
  238. claude_mpm/services/pm_skills_deployer.py +676 -0
  239. claude_mpm/services/port_manager.py +1 -1
  240. claude_mpm/services/pr/__init__.py +14 -0
  241. claude_mpm/services/pr/pr_template_service.py +329 -0
  242. claude_mpm/services/profile_manager.py +331 -0
  243. claude_mpm/services/project/documentation_manager.py +2 -1
  244. claude_mpm/services/project/project_organizer.py +4 -0
  245. claude_mpm/services/project/toolchain_analyzer.py +3 -1
  246. claude_mpm/services/runner_configuration_service.py +16 -3
  247. claude_mpm/services/self_upgrade_service.py +120 -12
  248. claude_mpm/services/session_management_service.py +16 -4
  249. claude_mpm/services/skills/__init__.py +21 -0
  250. claude_mpm/services/skills/git_skill_source_manager.py +1297 -0
  251. claude_mpm/services/skills/selective_skill_deployer.py +704 -0
  252. claude_mpm/services/skills/skill_discovery_service.py +568 -0
  253. claude_mpm/services/skills/skill_to_agent_mapper.py +406 -0
  254. claude_mpm/services/skills_config.py +547 -0
  255. claude_mpm/services/skills_deployer.py +1072 -0
  256. claude_mpm/services/socketio/dashboard_server.py +1 -0
  257. claude_mpm/services/socketio/event_normalizer.py +51 -6
  258. claude_mpm/services/socketio/handlers/connection.py +1 -1
  259. claude_mpm/services/socketio/handlers/git.py +1 -1
  260. claude_mpm/services/socketio/server/core.py +387 -112
  261. claude_mpm/services/socketio/server/main.py +1 -3
  262. claude_mpm/services/system_instructions_service.py +1 -3
  263. claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +0 -3
  264. claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +0 -1
  265. claude_mpm/services/unified/deployment_strategies/vercel.py +1 -5
  266. claude_mpm/services/unified/unified_deployment.py +1 -5
  267. claude_mpm/services/version_control/conflict_resolution.py +6 -4
  268. claude_mpm/services/version_control/git_operations.py +103 -0
  269. claude_mpm/services/visualization/__init__.py +1 -5
  270. claude_mpm/services/visualization/mermaid_generator.py +2 -3
  271. claude_mpm/skills/bundled/testing/webapp-testing/scripts/with_server.py +2 -2
  272. claude_mpm/skills/skill_manager.py +92 -3
  273. claude_mpm/skills/skills_registry.py +0 -1
  274. claude_mpm/templates/questions/__init__.py +38 -0
  275. claude_mpm/templates/questions/base.py +193 -0
  276. claude_mpm/templates/questions/pr_strategy.py +311 -0
  277. claude_mpm/templates/questions/project_init.py +385 -0
  278. claude_mpm/templates/questions/ticket_mgmt.py +394 -0
  279. claude_mpm/tools/__main__.py +8 -8
  280. claude_mpm/utils/agent_dependency_loader.py +91 -12
  281. claude_mpm/utils/agent_filters.py +261 -0
  282. claude_mpm/utils/dependency_cache.py +3 -1
  283. claude_mpm/utils/gitignore.py +244 -0
  284. claude_mpm/utils/migration.py +372 -0
  285. claude_mpm/utils/progress.py +387 -0
  286. claude_mpm/utils/robust_installer.py +49 -7
  287. claude_mpm/utils/structured_questions.py +619 -0
  288. {claude_mpm-4.24.0.dist-info → claude_mpm-5.4.41.dist-info}/METADATA +445 -122
  289. {claude_mpm-4.24.0.dist-info → claude_mpm-5.4.41.dist-info}/RECORD +298 -503
  290. claude_mpm-5.4.41.dist-info/entry_points.txt +5 -0
  291. claude_mpm-5.4.41.dist-info/licenses/LICENSE +94 -0
  292. claude_mpm-5.4.41.dist-info/licenses/LICENSE-FAQ.md +153 -0
  293. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +0 -292
  294. claude_mpm/agents/BASE_DOCUMENTATION.md +0 -53
  295. claude_mpm/agents/BASE_OPS.md +0 -219
  296. claude_mpm/agents/BASE_PM.md +0 -468
  297. claude_mpm/agents/BASE_PROMPT_ENGINEER.md +0 -787
  298. claude_mpm/agents/BASE_QA.md +0 -167
  299. claude_mpm/agents/BASE_RESEARCH.md +0 -53
  300. claude_mpm/agents/base_agent_loader.py +0 -626
  301. claude_mpm/agents/templates/.claude-mpm/memories/README.md +0 -17
  302. claude_mpm/agents/templates/.claude-mpm/memories/engineer_memories.md +0 -3
  303. claude_mpm/agents/templates/agent-manager.json +0 -273
  304. claude_mpm/agents/templates/agentic-coder-optimizer.json +0 -248
  305. claude_mpm/agents/templates/api_qa.json +0 -183
  306. claude_mpm/agents/templates/circuit_breakers.md +0 -638
  307. claude_mpm/agents/templates/clerk-ops.json +0 -235
  308. claude_mpm/agents/templates/code_analyzer.json +0 -101
  309. claude_mpm/agents/templates/content-agent.json +0 -358
  310. claude_mpm/agents/templates/dart_engineer.json +0 -307
  311. claude_mpm/agents/templates/data_engineer.json +0 -225
  312. claude_mpm/agents/templates/documentation.json +0 -238
  313. claude_mpm/agents/templates/engineer.json +0 -210
  314. claude_mpm/agents/templates/gcp_ops_agent.json +0 -253
  315. claude_mpm/agents/templates/golang_engineer.json +0 -270
  316. claude_mpm/agents/templates/imagemagick.json +0 -264
  317. claude_mpm/agents/templates/java_engineer.json +0 -346
  318. claude_mpm/agents/templates/javascript_engineer_agent.json +0 -380
  319. claude_mpm/agents/templates/local_ops_agent.json +0 -1840
  320. claude_mpm/agents/templates/logs/prompts/agent_engineer_20250826_014258_728.md +0 -39
  321. claude_mpm/agents/templates/logs/prompts/agent_engineer_20250901_010124_142.md +0 -400
  322. claude_mpm/agents/templates/memory_manager.json +0 -158
  323. claude_mpm/agents/templates/nextjs_engineer.json +0 -285
  324. claude_mpm/agents/templates/ops.json +0 -185
  325. claude_mpm/agents/templates/php-engineer.json +0 -287
  326. claude_mpm/agents/templates/product_owner.json +0 -338
  327. claude_mpm/agents/templates/project_organizer.json +0 -144
  328. claude_mpm/agents/templates/prompt-engineer.json +0 -737
  329. claude_mpm/agents/templates/python_engineer.json +0 -387
  330. claude_mpm/agents/templates/qa.json +0 -243
  331. claude_mpm/agents/templates/react_engineer.json +0 -239
  332. claude_mpm/agents/templates/refactoring_engineer.json +0 -276
  333. claude_mpm/agents/templates/research.json +0 -188
  334. claude_mpm/agents/templates/ruby-engineer.json +0 -280
  335. claude_mpm/agents/templates/rust_engineer.json +0 -275
  336. claude_mpm/agents/templates/security.json +0 -202
  337. claude_mpm/agents/templates/svelte-engineer.json +0 -225
  338. claude_mpm/agents/templates/tauri_engineer.json +0 -274
  339. claude_mpm/agents/templates/ticketing.json +0 -178
  340. claude_mpm/agents/templates/typescript_engineer.json +0 -285
  341. claude_mpm/agents/templates/vercel_ops_agent.json +0 -412
  342. claude_mpm/agents/templates/version_control.json +0 -159
  343. claude_mpm/agents/templates/web_qa.json +0 -400
  344. claude_mpm/agents/templates/web_ui.json +0 -189
  345. claude_mpm/cli/commands/agents_detect.py +0 -380
  346. claude_mpm/cli/commands/agents_recommend.py +0 -309
  347. claude_mpm/cli/ticket_cli.py +0 -35
  348. claude_mpm/commands/mpm-agents-detect.md +0 -168
  349. claude_mpm/commands/mpm-agents-recommend.md +0 -214
  350. claude_mpm/commands/mpm-agents.md +0 -122
  351. claude_mpm/commands/mpm-auto-configure.md +0 -269
  352. claude_mpm/commands/mpm-resume.md +0 -372
  353. claude_mpm/commands/mpm-tickets.md +0 -151
  354. claude_mpm/dashboard/.claude-mpm/socketio-instances.json +0 -1
  355. claude_mpm/dashboard/analysis_runner.py +0 -455
  356. claude_mpm/dashboard/index.html +0 -13
  357. claude_mpm/dashboard/open_dashboard.py +0 -66
  358. claude_mpm/dashboard/react/components/DataInspector/DataInspector.module.css +0 -188
  359. claude_mpm/dashboard/react/components/EventViewer/EventViewer.module.css +0 -156
  360. claude_mpm/dashboard/react/components/shared/ConnectionStatus.module.css +0 -38
  361. claude_mpm/dashboard/react/components/shared/FilterBar.module.css +0 -92
  362. claude_mpm/dashboard/static/archive/activity_dashboard_fixed.html +0 -248
  363. claude_mpm/dashboard/static/archive/activity_dashboard_test.html +0 -61
  364. claude_mpm/dashboard/static/archive/test_activity_connection.html +0 -179
  365. claude_mpm/dashboard/static/archive/test_claude_tree_tab.html +0 -68
  366. claude_mpm/dashboard/static/archive/test_dashboard.html +0 -409
  367. claude_mpm/dashboard/static/archive/test_dashboard_fixed.html +0 -519
  368. claude_mpm/dashboard/static/archive/test_dashboard_verification.html +0 -181
  369. claude_mpm/dashboard/static/archive/test_file_data.html +0 -315
  370. claude_mpm/dashboard/static/archive/test_file_tree_empty_state.html +0 -243
  371. claude_mpm/dashboard/static/archive/test_file_tree_fix.html +0 -234
  372. claude_mpm/dashboard/static/archive/test_file_tree_rename.html +0 -117
  373. claude_mpm/dashboard/static/archive/test_file_tree_tab.html +0 -115
  374. claude_mpm/dashboard/static/archive/test_file_viewer.html +0 -224
  375. claude_mpm/dashboard/static/archive/test_final_activity.html +0 -220
  376. claude_mpm/dashboard/static/archive/test_tab_fix.html +0 -139
  377. claude_mpm/dashboard/static/built/assets/events.DjpNxWNo.css +0 -1
  378. claude_mpm/dashboard/static/built/components/activity-tree.js +0 -2
  379. claude_mpm/dashboard/static/built/components/agent-hierarchy.js +0 -777
  380. claude_mpm/dashboard/static/built/components/agent-inference.js +0 -2
  381. claude_mpm/dashboard/static/built/components/build-tracker.js +0 -333
  382. claude_mpm/dashboard/static/built/components/code-simple.js +0 -857
  383. claude_mpm/dashboard/static/built/components/code-tree/tree-breadcrumb.js +0 -353
  384. claude_mpm/dashboard/static/built/components/code-tree/tree-constants.js +0 -235
  385. claude_mpm/dashboard/static/built/components/code-tree/tree-search.js +0 -409
  386. claude_mpm/dashboard/static/built/components/code-tree/tree-utils.js +0 -435
  387. claude_mpm/dashboard/static/built/components/code-tree.js +0 -2
  388. claude_mpm/dashboard/static/built/components/code-viewer.js +0 -2
  389. claude_mpm/dashboard/static/built/components/connection-debug.js +0 -654
  390. claude_mpm/dashboard/static/built/components/diff-viewer.js +0 -891
  391. claude_mpm/dashboard/static/built/components/event-processor.js +0 -2
  392. claude_mpm/dashboard/static/built/components/event-viewer.js +0 -2
  393. claude_mpm/dashboard/static/built/components/export-manager.js +0 -2
  394. claude_mpm/dashboard/static/built/components/file-change-tracker.js +0 -443
  395. claude_mpm/dashboard/static/built/components/file-change-viewer.js +0 -690
  396. claude_mpm/dashboard/static/built/components/file-tool-tracker.js +0 -2
  397. claude_mpm/dashboard/static/built/components/file-viewer.js +0 -2
  398. claude_mpm/dashboard/static/built/components/hud-library-loader.js +0 -2
  399. claude_mpm/dashboard/static/built/components/hud-manager.js +0 -2
  400. claude_mpm/dashboard/static/built/components/hud-visualizer.js +0 -2
  401. claude_mpm/dashboard/static/built/components/module-viewer.js +0 -2
  402. claude_mpm/dashboard/static/built/components/nav-bar.js +0 -145
  403. claude_mpm/dashboard/static/built/components/page-structure.js +0 -429
  404. claude_mpm/dashboard/static/built/components/session-manager.js +0 -2
  405. claude_mpm/dashboard/static/built/components/socket-manager.js +0 -2
  406. claude_mpm/dashboard/static/built/components/ui-state-manager.js +0 -2
  407. claude_mpm/dashboard/static/built/components/unified-data-viewer.js +0 -2
  408. claude_mpm/dashboard/static/built/components/working-directory.js +0 -2
  409. claude_mpm/dashboard/static/built/connection-manager.js +0 -536
  410. claude_mpm/dashboard/static/built/dashboard.js +0 -2
  411. claude_mpm/dashboard/static/built/extension-error-handler.js +0 -164
  412. claude_mpm/dashboard/static/built/react/events.js +0 -30
  413. claude_mpm/dashboard/static/built/shared/dom-helpers.js +0 -396
  414. claude_mpm/dashboard/static/built/shared/event-bus.js +0 -330
  415. claude_mpm/dashboard/static/built/shared/event-filter-service.js +0 -540
  416. claude_mpm/dashboard/static/built/shared/logger.js +0 -385
  417. claude_mpm/dashboard/static/built/shared/page-structure.js +0 -249
  418. claude_mpm/dashboard/static/built/shared/tooltip-service.js +0 -253
  419. claude_mpm/dashboard/static/built/socket-client.js +0 -2
  420. claude_mpm/dashboard/static/built/tab-isolation-fix.js +0 -185
  421. claude_mpm/dashboard/static/css/activity.css +0 -1958
  422. claude_mpm/dashboard/static/css/connection-status.css +0 -370
  423. claude_mpm/dashboard/static/css/dashboard.css +0 -4701
  424. claude_mpm/dashboard/static/dist/assets/events.DjpNxWNo.css +0 -1
  425. claude_mpm/dashboard/static/dist/components/activity-tree.js +0 -2
  426. claude_mpm/dashboard/static/dist/components/agent-inference.js +0 -2
  427. claude_mpm/dashboard/static/dist/components/code-tree.js +0 -2
  428. claude_mpm/dashboard/static/dist/components/code-viewer.js +0 -2
  429. claude_mpm/dashboard/static/dist/components/event-processor.js +0 -2
  430. claude_mpm/dashboard/static/dist/components/event-viewer.js +0 -2
  431. claude_mpm/dashboard/static/dist/components/export-manager.js +0 -2
  432. claude_mpm/dashboard/static/dist/components/file-tool-tracker.js +0 -2
  433. claude_mpm/dashboard/static/dist/components/file-viewer.js +0 -2
  434. claude_mpm/dashboard/static/dist/components/hud-library-loader.js +0 -2
  435. claude_mpm/dashboard/static/dist/components/hud-manager.js +0 -2
  436. claude_mpm/dashboard/static/dist/components/hud-visualizer.js +0 -2
  437. claude_mpm/dashboard/static/dist/components/module-viewer.js +0 -2
  438. claude_mpm/dashboard/static/dist/components/session-manager.js +0 -2
  439. claude_mpm/dashboard/static/dist/components/socket-manager.js +0 -2
  440. claude_mpm/dashboard/static/dist/components/ui-state-manager.js +0 -2
  441. claude_mpm/dashboard/static/dist/components/unified-data-viewer.js +0 -2
  442. claude_mpm/dashboard/static/dist/components/working-directory.js +0 -2
  443. claude_mpm/dashboard/static/dist/dashboard.js +0 -2
  444. claude_mpm/dashboard/static/dist/react/events.js +0 -30
  445. claude_mpm/dashboard/static/dist/socket-client.js +0 -2
  446. claude_mpm/dashboard/static/events.html +0 -607
  447. claude_mpm/dashboard/static/index.html +0 -635
  448. claude_mpm/dashboard/static/js/components/activity-tree.js +0 -1871
  449. claude_mpm/dashboard/static/js/components/agent-hierarchy.js +0 -777
  450. claude_mpm/dashboard/static/js/components/agent-inference.js +0 -956
  451. claude_mpm/dashboard/static/js/components/build-tracker.js +0 -333
  452. claude_mpm/dashboard/static/js/components/code-simple.js +0 -857
  453. claude_mpm/dashboard/static/js/components/connection-debug.js +0 -654
  454. claude_mpm/dashboard/static/js/components/diff-viewer.js +0 -891
  455. claude_mpm/dashboard/static/js/components/event-processor.js +0 -542
  456. claude_mpm/dashboard/static/js/components/event-viewer.js +0 -1155
  457. claude_mpm/dashboard/static/js/components/export-manager.js +0 -368
  458. claude_mpm/dashboard/static/js/components/file-change-tracker.js +0 -443
  459. claude_mpm/dashboard/static/js/components/file-change-viewer.js +0 -690
  460. claude_mpm/dashboard/static/js/components/file-tool-tracker.js +0 -724
  461. claude_mpm/dashboard/static/js/components/file-viewer.js +0 -580
  462. claude_mpm/dashboard/static/js/components/hud-library-loader.js +0 -211
  463. claude_mpm/dashboard/static/js/components/hud-manager.js +0 -671
  464. claude_mpm/dashboard/static/js/components/hud-visualizer.js +0 -1718
  465. claude_mpm/dashboard/static/js/components/module-viewer.js +0 -2764
  466. claude_mpm/dashboard/static/js/components/session-manager.js +0 -579
  467. claude_mpm/dashboard/static/js/components/socket-manager.js +0 -368
  468. claude_mpm/dashboard/static/js/components/ui-state-manager.js +0 -749
  469. claude_mpm/dashboard/static/js/components/unified-data-viewer.js +0 -1824
  470. claude_mpm/dashboard/static/js/components/working-directory.js +0 -920
  471. claude_mpm/dashboard/static/js/connection-manager.js +0 -536
  472. claude_mpm/dashboard/static/js/dashboard.js +0 -1896
  473. claude_mpm/dashboard/static/js/extension-error-handler.js +0 -164
  474. claude_mpm/dashboard/static/js/shared/dom-helpers.js +0 -396
  475. claude_mpm/dashboard/static/js/shared/event-bus.js +0 -330
  476. claude_mpm/dashboard/static/js/shared/logger.js +0 -385
  477. claude_mpm/dashboard/static/js/shared/tooltip-service.js +0 -253
  478. claude_mpm/dashboard/static/js/socket-client.js +0 -1457
  479. claude_mpm/dashboard/static/js/stores/dashboard-store.js +0 -562
  480. claude_mpm/dashboard/static/js/tab-isolation-fix.js +0 -185
  481. claude_mpm/dashboard/static/legacy/activity.html +0 -736
  482. claude_mpm/dashboard/static/legacy/agents.html +0 -786
  483. claude_mpm/dashboard/static/legacy/files.html +0 -747
  484. claude_mpm/dashboard/static/legacy/tools.html +0 -831
  485. claude_mpm/dashboard/static/monitors.html +0 -431
  486. claude_mpm/dashboard/static/production/events.html +0 -659
  487. claude_mpm/dashboard/static/production/main.html +0 -698
  488. claude_mpm/dashboard/static/production/monitors.html +0 -483
  489. claude_mpm/dashboard/static/socket.io.min.js +0 -7
  490. claude_mpm/dashboard/static/socket.io.v4.8.1.backup.js +0 -7
  491. claude_mpm/dashboard/static/test-archive/dashboard.html +0 -635
  492. claude_mpm/dashboard/static/test-archive/debug-events.html +0 -147
  493. claude_mpm/dashboard/static/test-archive/test-navigation.html +0 -256
  494. claude_mpm/dashboard/static/test-archive/test-react-exports.html +0 -180
  495. claude_mpm/dashboard/static/test-archive/test_debug.html +0 -25
  496. claude_mpm/dashboard/templates/code_simple.html +0 -153
  497. claude_mpm/dashboard/templates/index.html +0 -606
  498. claude_mpm/dashboard/test_dashboard.html +0 -372
  499. claude_mpm/scripts/mcp_server.py +0 -75
  500. claude_mpm/scripts/mcp_wrapper.py +0 -39
  501. claude_mpm/services/mcp_gateway/__init__.py +0 -159
  502. claude_mpm/services/mcp_gateway/auto_configure.py +0 -369
  503. claude_mpm/services/mcp_gateway/config/__init__.py +0 -17
  504. claude_mpm/services/mcp_gateway/config/config_loader.py +0 -296
  505. claude_mpm/services/mcp_gateway/config/config_schema.py +0 -243
  506. claude_mpm/services/mcp_gateway/config/configuration.py +0 -429
  507. claude_mpm/services/mcp_gateway/core/__init__.py +0 -43
  508. claude_mpm/services/mcp_gateway/core/base.py +0 -312
  509. claude_mpm/services/mcp_gateway/core/exceptions.py +0 -253
  510. claude_mpm/services/mcp_gateway/core/interfaces.py +0 -443
  511. claude_mpm/services/mcp_gateway/core/process_pool.py +0 -971
  512. claude_mpm/services/mcp_gateway/core/singleton_manager.py +0 -315
  513. claude_mpm/services/mcp_gateway/core/startup_verification.py +0 -316
  514. claude_mpm/services/mcp_gateway/main.py +0 -589
  515. claude_mpm/services/mcp_gateway/registry/__init__.py +0 -12
  516. claude_mpm/services/mcp_gateway/registry/service_registry.py +0 -412
  517. claude_mpm/services/mcp_gateway/registry/tool_registry.py +0 -489
  518. claude_mpm/services/mcp_gateway/server/__init__.py +0 -15
  519. claude_mpm/services/mcp_gateway/server/mcp_gateway.py +0 -419
  520. claude_mpm/services/mcp_gateway/server/stdio_handler.py +0 -372
  521. claude_mpm/services/mcp_gateway/server/stdio_server.py +0 -714
  522. claude_mpm/services/mcp_gateway/tools/__init__.py +0 -36
  523. claude_mpm/services/mcp_gateway/tools/base_adapter.py +0 -485
  524. claude_mpm/services/mcp_gateway/tools/document_summarizer.py +0 -789
  525. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +0 -654
  526. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +0 -456
  527. claude_mpm/services/mcp_gateway/tools/hello_world.py +0 -551
  528. claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +0 -551
  529. claude_mpm/services/mcp_gateway/utils/__init__.py +0 -14
  530. claude_mpm/services/mcp_gateway/utils/package_version_checker.py +0 -160
  531. claude_mpm/services/mcp_gateway/utils/update_preferences.py +0 -170
  532. claude_mpm/skills/bundled/collaboration/brainstorming/SKILL.md +0 -79
  533. claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/SKILL.md +0 -178
  534. claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/agent-prompts.md +0 -577
  535. claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/coordination-patterns.md +0 -467
  536. claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/examples.md +0 -537
  537. claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/troubleshooting.md +0 -730
  538. claude_mpm/skills/bundled/collaboration/requesting-code-review/SKILL.md +0 -112
  539. claude_mpm/skills/bundled/collaboration/requesting-code-review/references/code-reviewer-template.md +0 -146
  540. claude_mpm/skills/bundled/collaboration/requesting-code-review/references/review-examples.md +0 -412
  541. claude_mpm/skills/bundled/collaboration/writing-plans/SKILL.md +0 -81
  542. claude_mpm/skills/bundled/collaboration/writing-plans/references/best-practices.md +0 -362
  543. claude_mpm/skills/bundled/collaboration/writing-plans/references/plan-structure-templates.md +0 -312
  544. claude_mpm/skills/bundled/debugging/root-cause-tracing/SKILL.md +0 -152
  545. claude_mpm/skills/bundled/debugging/root-cause-tracing/references/advanced-techniques.md +0 -668
  546. claude_mpm/skills/bundled/debugging/root-cause-tracing/references/examples.md +0 -587
  547. claude_mpm/skills/bundled/debugging/root-cause-tracing/references/integration.md +0 -438
  548. claude_mpm/skills/bundled/debugging/root-cause-tracing/references/tracing-techniques.md +0 -391
  549. claude_mpm/skills/bundled/debugging/systematic-debugging/CREATION-LOG.md +0 -119
  550. claude_mpm/skills/bundled/debugging/systematic-debugging/SKILL.md +0 -148
  551. claude_mpm/skills/bundled/debugging/systematic-debugging/references/anti-patterns.md +0 -483
  552. claude_mpm/skills/bundled/debugging/systematic-debugging/references/examples.md +0 -452
  553. claude_mpm/skills/bundled/debugging/systematic-debugging/references/troubleshooting.md +0 -449
  554. claude_mpm/skills/bundled/debugging/systematic-debugging/references/workflow.md +0 -411
  555. claude_mpm/skills/bundled/debugging/systematic-debugging/test-academic.md +0 -14
  556. claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-1.md +0 -58
  557. claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-2.md +0 -68
  558. claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-3.md +0 -69
  559. claude_mpm/skills/bundled/debugging/verification-before-completion/SKILL.md +0 -131
  560. claude_mpm/skills/bundled/debugging/verification-before-completion/references/gate-function.md +0 -325
  561. claude_mpm/skills/bundled/debugging/verification-before-completion/references/integration-and-workflows.md +0 -490
  562. claude_mpm/skills/bundled/debugging/verification-before-completion/references/red-flags-and-failures.md +0 -425
  563. claude_mpm/skills/bundled/debugging/verification-before-completion/references/verification-patterns.md +0 -499
  564. claude_mpm/skills/bundled/main/artifacts-builder/SKILL.md +0 -86
  565. claude_mpm/skills/bundled/main/internal-comms/SKILL.md +0 -43
  566. claude_mpm/skills/bundled/main/internal-comms/examples/3p-updates.md +0 -47
  567. claude_mpm/skills/bundled/main/internal-comms/examples/company-newsletter.md +0 -65
  568. claude_mpm/skills/bundled/main/internal-comms/examples/faq-answers.md +0 -30
  569. claude_mpm/skills/bundled/main/internal-comms/examples/general-comms.md +0 -16
  570. claude_mpm/skills/bundled/main/mcp-builder/SKILL.md +0 -160
  571. claude_mpm/skills/bundled/main/mcp-builder/reference/design_principles.md +0 -412
  572. claude_mpm/skills/bundled/main/mcp-builder/reference/evaluation.md +0 -602
  573. claude_mpm/skills/bundled/main/mcp-builder/reference/mcp_best_practices.md +0 -915
  574. claude_mpm/skills/bundled/main/mcp-builder/reference/node_mcp_server.md +0 -916
  575. claude_mpm/skills/bundled/main/mcp-builder/reference/python_mcp_server.md +0 -752
  576. claude_mpm/skills/bundled/main/mcp-builder/reference/workflow.md +0 -1237
  577. claude_mpm/skills/bundled/main/skill-creator/SKILL.md +0 -189
  578. claude_mpm/skills/bundled/main/skill-creator/references/best-practices.md +0 -500
  579. claude_mpm/skills/bundled/main/skill-creator/references/creation-workflow.md +0 -464
  580. claude_mpm/skills/bundled/main/skill-creator/references/examples.md +0 -619
  581. claude_mpm/skills/bundled/main/skill-creator/references/progressive-disclosure.md +0 -437
  582. claude_mpm/skills/bundled/main/skill-creator/references/skill-structure.md +0 -231
  583. claude_mpm/skills/bundled/php/espocrm-development/SKILL.md +0 -170
  584. claude_mpm/skills/bundled/php/espocrm-development/references/architecture.md +0 -602
  585. claude_mpm/skills/bundled/php/espocrm-development/references/common-tasks.md +0 -821
  586. claude_mpm/skills/bundled/php/espocrm-development/references/development-workflow.md +0 -742
  587. claude_mpm/skills/bundled/php/espocrm-development/references/frontend-customization.md +0 -726
  588. claude_mpm/skills/bundled/php/espocrm-development/references/hooks-and-services.md +0 -764
  589. claude_mpm/skills/bundled/php/espocrm-development/references/testing-debugging.md +0 -831
  590. claude_mpm/skills/bundled/rust/desktop-applications/SKILL.md +0 -226
  591. claude_mpm/skills/bundled/rust/desktop-applications/references/architecture-patterns.md +0 -901
  592. claude_mpm/skills/bundled/rust/desktop-applications/references/native-gui-frameworks.md +0 -901
  593. claude_mpm/skills/bundled/rust/desktop-applications/references/platform-integration.md +0 -775
  594. claude_mpm/skills/bundled/rust/desktop-applications/references/state-management.md +0 -937
  595. claude_mpm/skills/bundled/rust/desktop-applications/references/tauri-framework.md +0 -770
  596. claude_mpm/skills/bundled/rust/desktop-applications/references/testing-deployment.md +0 -961
  597. claude_mpm/skills/bundled/testing/condition-based-waiting/SKILL.md +0 -119
  598. claude_mpm/skills/bundled/testing/condition-based-waiting/references/patterns-and-implementation.md +0 -253
  599. claude_mpm/skills/bundled/testing/test-driven-development/SKILL.md +0 -145
  600. claude_mpm/skills/bundled/testing/test-driven-development/references/anti-patterns.md +0 -543
  601. claude_mpm/skills/bundled/testing/test-driven-development/references/examples.md +0 -741
  602. claude_mpm/skills/bundled/testing/test-driven-development/references/integration.md +0 -470
  603. claude_mpm/skills/bundled/testing/test-driven-development/references/philosophy.md +0 -458
  604. claude_mpm/skills/bundled/testing/test-driven-development/references/workflow.md +0 -639
  605. claude_mpm/skills/bundled/testing/testing-anti-patterns/SKILL.md +0 -140
  606. claude_mpm/skills/bundled/testing/testing-anti-patterns/references/completeness-anti-patterns.md +0 -572
  607. claude_mpm/skills/bundled/testing/testing-anti-patterns/references/core-anti-patterns.md +0 -411
  608. claude_mpm/skills/bundled/testing/testing-anti-patterns/references/detection-guide.md +0 -569
  609. claude_mpm/skills/bundled/testing/testing-anti-patterns/references/tdd-connection.md +0 -695
  610. claude_mpm/skills/bundled/testing/webapp-testing/SKILL.md +0 -184
  611. claude_mpm/skills/bundled/testing/webapp-testing/decision-tree.md +0 -459
  612. claude_mpm/skills/bundled/testing/webapp-testing/playwright-patterns.md +0 -479
  613. claude_mpm/skills/bundled/testing/webapp-testing/reconnaissance-pattern.md +0 -687
  614. claude_mpm/skills/bundled/testing/webapp-testing/server-management.md +0 -758
  615. claude_mpm/skills/bundled/testing/webapp-testing/troubleshooting.md +0 -868
  616. claude_mpm-4.24.0.dist-info/entry_points.txt +0 -10
  617. claude_mpm-4.24.0.dist-info/licenses/LICENSE +0 -21
  618. /claude_mpm/agents/templates/{git_file_tracking.md → git-file-tracking.md} +0 -0
  619. /claude_mpm/agents/templates/{pm_examples.md → pm-examples.md} +0 -0
  620. /claude_mpm/agents/templates/{response_format.md → response-format.md} +0 -0
  621. /claude_mpm/agents/templates/{validation_templates.md → validation-templates.md} +0 -0
  622. {claude_mpm-4.24.0.dist-info → claude_mpm-5.4.41.dist-info}/WHEEL +0 -0
  623. {claude_mpm-4.24.0.dist-info → claude_mpm-5.4.41.dist-info}/top_level.txt +0 -0
@@ -12,15 +12,23 @@ DESIGN DECISIONS:
12
12
  """
13
13
 
14
14
  import json
15
+ import shutil
16
+ from collections import defaultdict
15
17
  from pathlib import Path
16
18
  from typing import Dict, List, Optional
17
19
 
20
+ import questionary
21
+ import questionary.constants
22
+ import questionary.prompts.common # For checkbox symbol customization
23
+ from questionary import Choice, Separator, Style
18
24
  from rich.console import Console
19
25
  from rich.prompt import Confirm, Prompt
20
26
  from rich.text import Text
21
27
 
22
28
  from ...core.config import Config
29
+ from ...services.agents.agent_recommendation_service import AgentRecommendationService
23
30
  from ...services.version_service import VersionService
31
+ from ...utils.agent_filters import apply_all_filters, get_deployed_agent_ids
24
32
  from ...utils.console import console as default_console
25
33
  from ..shared import BaseCommand, CommandResult
26
34
  from .agent_state_manager import SimpleAgentManager
@@ -41,6 +49,21 @@ from .configure_validators import (
41
49
  class ConfigureCommand(BaseCommand):
42
50
  """Interactive configuration management command."""
43
51
 
52
+ # Questionary style optimized for dark terminals (WCAG AAA compliant)
53
+ QUESTIONARY_STYLE = Style(
54
+ [
55
+ ("selected", "fg:#e0e0e0 bold"), # Light gray - excellent readability
56
+ ("pointer", "fg:#ffd700 bold"), # Gold/yellow - highly visible pointer
57
+ ("highlighted", "fg:#e0e0e0"), # Light gray - clear hover state
58
+ ("question", "fg:#e0e0e0 bold"), # Light gray bold - prominent questions
59
+ ("checkbox", "fg:#00ff00"), # Green - for checked boxes
60
+ (
61
+ "checkbox-selected",
62
+ "fg:#00ff00 bold",
63
+ ), # Green bold - for checked selected boxes
64
+ ]
65
+ )
66
+
44
67
  def __init__(self):
45
68
  super().__init__("configure")
46
69
  self.console = default_console
@@ -55,6 +78,7 @@ class ConfigureCommand(BaseCommand):
55
78
  self._navigation = None # Lazy-initialized
56
79
  self._template_editor = None # Lazy-initialized
57
80
  self._startup_manager = None # Lazy-initialized
81
+ self._recommendation_service = None # Lazy-initialized
58
82
 
59
83
  def validate_args(self, args) -> Optional[str]:
60
84
  """Validate command arguments."""
@@ -131,6 +155,13 @@ class ConfigureCommand(BaseCommand):
131
155
  )
132
156
  return self._startup_manager
133
157
 
158
+ @property
159
+ def recommendation_service(self) -> AgentRecommendationService:
160
+ """Lazy-initialize recommendation service."""
161
+ if self._recommendation_service is None:
162
+ self._recommendation_service = AgentRecommendationService()
163
+ return self._recommendation_service
164
+
134
165
  def run(self, args) -> CommandResult:
135
166
  """Execute the configure command."""
136
167
  # Set configuration scope
@@ -284,61 +315,153 @@ class ConfigureCommand(BaseCommand):
284
315
  return self.navigation.show_main_menu()
285
316
 
286
317
  def _manage_agents(self) -> None:
287
- """Agent management interface."""
318
+ """Enhanced agent management with remote agent discovery and installation."""
288
319
  while True:
289
320
  self.console.clear()
290
- self._display_header()
321
+ self.navigation.display_header()
322
+ self.console.print("\n[bold blue]═══ Agent Management ═══[/bold blue]\n")
291
323
 
292
- # Display available agents
293
- agents = self.agent_manager.discover_agents()
294
- self._display_agents_table(agents)
324
+ # Load all agents with spinner (don't show partial state)
325
+ agents = self._load_agents_with_spinner()
295
326
 
296
- # Show agent menu
297
- self.console.print("\n[bold]Agent Management Options:[/bold]")
327
+ if not agents:
328
+ self.console.print("[yellow]No agents found[/yellow]")
329
+ self.console.print(
330
+ "[dim]Configure sources with 'claude-mpm agent-source add'[/dim]\n"
331
+ )
332
+ Prompt.ask("\nPress Enter to continue")
333
+ break
298
334
 
299
- # Use Text objects to properly display shortcuts with styling
300
- text_t = Text(" ")
301
- text_t.append("[t]", style="bold blue")
302
- text_t.append(" Toggle agents (enable/disable multiple)")
303
- self.console.print(text_t)
335
+ # Now display everything at once (after all data loaded)
336
+ self._display_agent_sources_and_list(agents)
304
337
 
305
- text_c = Text(" ")
306
- text_c.append("[c]", style="bold blue")
307
- text_c.append(" Customize agent template")
308
- self.console.print(text_c)
338
+ # Step 3: Simplified menu - only "Select Agents" option
339
+ self.console.print()
340
+ self.logger.debug("About to show agent management menu")
341
+ try:
342
+ choice = questionary.select(
343
+ "Agent Management:",
344
+ choices=[
345
+ "Select Agents",
346
+ questionary.Separator(),
347
+ "← Back to main menu",
348
+ ],
349
+ style=self.QUESTIONARY_STYLE,
350
+ ).ask()
351
+
352
+ if choice is None or choice == "← Back to main menu":
353
+ break
309
354
 
310
- text_v = Text(" ")
311
- text_v.append("[v]", style="bold blue")
312
- text_v.append(" View agent details")
313
- self.console.print(text_v)
355
+ # Map selection to action
356
+ if choice == "Select Agents":
357
+ self.logger.debug("User selected 'Select Agents' from menu")
358
+ self._deploy_agents_unified(agents)
359
+ # Loop back to show updated state after deployment
314
360
 
315
- text_r = Text(" ")
316
- text_r.append("[r]", style="bold blue")
317
- text_r.append(" Reset agent to defaults")
318
- self.console.print(text_r)
361
+ except KeyboardInterrupt:
362
+ self.console.print("\n[yellow]Operation cancelled[/yellow]")
363
+ break
364
+ except Exception as e:
365
+ # Handle questionary menu failure
366
+ import sys
367
+
368
+ self.logger.error(f"Agent management menu failed: {e}", exc_info=True)
369
+ self.console.print("[red]Error: Interactive menu failed[/red]")
370
+ self.console.print(f"[dim]Reason: {e}[/dim]")
371
+ if not sys.stdin.isatty():
372
+ self.console.print(
373
+ "[dim]Interactive terminal required for this operation[/dim]"
374
+ )
375
+ self.console.print("[dim]Use command-line options instead:[/dim]")
376
+ self.console.print(
377
+ "[dim] claude-mpm configure --list-agents[/dim]"
378
+ )
379
+ self.console.print(
380
+ "[dim] claude-mpm configure --enable-agent <id>[/dim]"
381
+ )
382
+ Prompt.ask("\nPress Enter to continue")
383
+ break
319
384
 
320
- text_b = Text(" ")
321
- text_b.append("[b]", style="bold blue")
322
- text_b.append(" Back to main menu")
323
- self.console.print(text_b)
385
+ def _load_agents_with_spinner(self) -> List[AgentConfig]:
386
+ """Load agents with loading indicator, don't show partial state.
324
387
 
325
- self.console.print()
388
+ Returns:
389
+ List of discovered agents with deployment status set.
390
+ """
326
391
 
327
- choice = Prompt.ask("[bold blue]Select an option[/bold blue]", default="b")
392
+ agents = []
393
+ with self.console.status(
394
+ "[bold blue]Loading agents...[/bold blue]", spinner="dots"
395
+ ):
396
+ try:
397
+ # Discover agents (includes both local and remote)
398
+ agents = self.agent_manager.discover_agents(include_remote=True)
328
399
 
329
- if choice == "b":
330
- break
331
- if choice == "t":
332
- self._toggle_agents_interactive(agents)
333
- elif choice == "c":
334
- self._customize_agent_template(agents)
335
- elif choice == "v":
336
- self._view_agent_details(agents)
337
- elif choice == "r":
338
- self._reset_agent_defaults(agents)
339
- else:
340
- self.console.print("[red]Invalid choice.[/red]")
341
- Prompt.ask("Press Enter to continue")
400
+ # Set deployment status on each agent for display
401
+ deployed_ids = get_deployed_agent_ids()
402
+ for agent in agents:
403
+ # Use agent_id (technical ID) for comparison, not display name
404
+ agent_id = getattr(agent, "agent_id", agent.name)
405
+ agent_leaf_name = agent_id.split("/")[-1]
406
+ agent.is_deployed = agent_leaf_name in deployed_ids
407
+
408
+ # Filter BASE_AGENT from display (1M-502 Phase 1)
409
+ agents = self._filter_agent_configs(agents, filter_deployed=False)
410
+
411
+ except Exception as e:
412
+ self.console.print(f"[red]Error discovering agents: {e}[/red]")
413
+ self.logger.error(f"Agent discovery failed: {e}", exc_info=True)
414
+ agents = []
415
+
416
+ return agents
417
+
418
+ def _display_agent_sources_and_list(self, agents: List[AgentConfig]) -> None:
419
+ """Display agent sources and agent list (only after all data loaded).
420
+
421
+ Args:
422
+ agents: List of discovered agents with deployment status.
423
+ """
424
+ from rich.table import Table
425
+
426
+ # Step 1: Show configured sources
427
+ self.console.print("[bold white]═══ Agent Sources ═══[/bold white]\n")
428
+
429
+ sources = self._get_configured_sources()
430
+ if sources:
431
+ sources_table = Table(show_header=True, header_style="bold white")
432
+ sources_table.add_column(
433
+ "Source",
434
+ style="bright_yellow",
435
+ width=40,
436
+ no_wrap=True,
437
+ overflow="ellipsis",
438
+ )
439
+ sources_table.add_column("Status", style="green", width=15, no_wrap=True)
440
+ sources_table.add_column("Agents", style="yellow", width=10, no_wrap=True)
441
+
442
+ for source in sources:
443
+ status = "✓ Active" if source.get("enabled", True) else "Disabled"
444
+ agent_count = source.get("agent_count", "?")
445
+ sources_table.add_row(source["identifier"], status, str(agent_count))
446
+
447
+ self.console.print(sources_table)
448
+ else:
449
+ self.console.print("[yellow]No agent sources configured[/yellow]")
450
+ self.console.print(
451
+ "[dim]Default source 'bobmatnyc/claude-mpm-agents' will be used[/dim]\n"
452
+ )
453
+
454
+ # Step 2: Display available agents
455
+ self.console.print("\n[bold white]═══ Available Agents ═══[/bold white]\n")
456
+
457
+ if agents:
458
+ # Show progress spinner while recommendation service processes agents
459
+ with self.console.status(
460
+ "[bold blue]Preparing agent list...[/bold blue]", spinner="dots"
461
+ ):
462
+ self._display_agents_with_source_info(agents)
463
+ else:
464
+ self.console.print("[yellow]No agents available[/yellow]")
342
465
 
343
466
  def _display_agents_table(self, agents: List[AgentConfig]) -> None:
344
467
  """Display a table of available agents."""
@@ -397,6 +520,9 @@ class ConfigureCommand(BaseCommand):
397
520
  if self.agent_manager.has_pending_changes():
398
521
  self.agent_manager.commit_deferred_changes()
399
522
  self.console.print("[green]✓ Changes saved successfully![/green]")
523
+
524
+ # Auto-deploy enabled agents to .claude/agents/
525
+ self._auto_deploy_enabled_agents(agents)
400
526
  else:
401
527
  self.console.print("[yellow]No changes to save.[/yellow]")
402
528
  Prompt.ask("Press Enter to continue")
@@ -424,6 +550,60 @@ class ConfigureCommand(BaseCommand):
424
550
  agent.name, not current
425
551
  )
426
552
 
553
+ def _auto_deploy_enabled_agents(self, agents: List[AgentConfig]) -> None:
554
+ """Auto-deploy enabled agents after saving configuration.
555
+
556
+ WHY: When users enable agents, they expect them to be deployed
557
+ automatically to .claude/agents/ so they're available for use.
558
+ """
559
+ try:
560
+ # Get list of enabled agents from states
561
+ enabled_agents = [
562
+ agent
563
+ for agent in agents
564
+ if self.agent_manager.is_agent_enabled(agent.name)
565
+ ]
566
+
567
+ if not enabled_agents:
568
+ return
569
+
570
+ # Show deployment progress
571
+ self.console.print(
572
+ f"\n[bold blue]Deploying {len(enabled_agents)} enabled agent(s)...[/bold blue]"
573
+ )
574
+
575
+ # Deploy each enabled agent
576
+ success_count = 0
577
+ failed_count = 0
578
+
579
+ for agent in enabled_agents:
580
+ # Deploy to .claude/agents/ (project-level)
581
+ try:
582
+ if self._deploy_single_agent(agent, show_feedback=False):
583
+ success_count += 1
584
+ self.console.print(f"[green]✓ Deployed: {agent.name}[/green]")
585
+ else:
586
+ failed_count += 1
587
+ self.console.print(f"[yellow]⚠ Skipped: {agent.name}[/yellow]")
588
+ except Exception as e:
589
+ failed_count += 1
590
+ self.logger.error(f"Failed to deploy {agent.name}: {e}")
591
+ self.console.print(f"[red]✗ Failed: {agent.name}[/red]")
592
+
593
+ # Show summary
594
+ if success_count > 0:
595
+ self.console.print(
596
+ f"\n[green]✓ Successfully deployed {success_count} agent(s) to .claude/agents/[/green]"
597
+ )
598
+ if failed_count > 0:
599
+ self.console.print(
600
+ f"[yellow]⚠ {failed_count} agent(s) failed or were skipped[/yellow]"
601
+ )
602
+
603
+ except Exception as e:
604
+ self.logger.error(f"Auto-deployment failed: {e}", exc_info=True)
605
+ self.console.print(f"[red]✗ Auto-deployment error: {e}[/red]")
606
+
427
607
  def _customize_agent_template(self, agents: List[AgentConfig]) -> None:
428
608
  """Customize agent JSON template."""
429
609
  self.template_editor.customize_agent_template(agents)
@@ -511,6 +691,8 @@ class ConfigureCommand(BaseCommand):
511
691
 
512
692
  # Get list of enabled agents
513
693
  agents = self.agent_manager.discover_agents()
694
+ # Filter BASE_AGENT from all agent operations (1M-502 Phase 1)
695
+ agents = self._filter_agent_configs(agents, filter_deployed=False)
514
696
  enabled_agents = [
515
697
  a.name
516
698
  for a in agents
@@ -554,9 +736,9 @@ class ConfigureCommand(BaseCommand):
554
736
  else:
555
737
  from rich.table import Table
556
738
 
557
- table = Table(show_header=True, header_style="bold cyan")
558
- table.add_column("Agent", style="yellow")
559
- table.add_column("Skills", style="green")
739
+ table = Table(show_header=True, header_style="bold white")
740
+ table.add_column("Agent", style="white", no_wrap=True)
741
+ table.add_column("Skills", style="green", no_wrap=True)
560
742
 
561
743
  for agent_id, skills in mappings.items():
562
744
  skills_str = (
@@ -577,6 +759,8 @@ class ConfigureCommand(BaseCommand):
577
759
 
578
760
  # Get enabled agents
579
761
  agents = self.agent_manager.discover_agents()
762
+ # Filter BASE_AGENT from all agent operations (1M-502 Phase 1)
763
+ agents = self._filter_agent_configs(agents, filter_deployed=False)
580
764
  enabled_agents = [
581
765
  a.name
582
766
  for a in agents
@@ -708,6 +892,8 @@ class ConfigureCommand(BaseCommand):
708
892
  def _list_agents_non_interactive(self) -> CommandResult:
709
893
  """List agents in non-interactive mode."""
710
894
  agents = self.agent_manager.discover_agents()
895
+ # Filter BASE_AGENT from all agent lists (1M-502 Phase 1)
896
+ agents = self._filter_agent_configs(agents, filter_deployed=False)
711
897
 
712
898
  data = []
713
899
  for agent in agents:
@@ -809,6 +995,1649 @@ class ConfigureCommand(BaseCommand):
809
995
  except Exception as e:
810
996
  return CommandResult.error_result(f"Startup configuration failed: {e}")
811
997
 
998
+ # ========================================================================
999
+ # Enhanced Agent Management Methods (Remote Agent Discovery Integration)
1000
+ # ========================================================================
1001
+
1002
+ def _get_configured_sources(self) -> List[Dict]:
1003
+ """Get list of configured agent sources with agent counts."""
1004
+ try:
1005
+ from claude_mpm.config.agent_sources import AgentSourceConfiguration
1006
+
1007
+ config = AgentSourceConfiguration.load()
1008
+
1009
+ # Convert repositories to source dictionaries
1010
+ sources = []
1011
+ for repo in config.repositories:
1012
+ # Extract identifier from repository
1013
+ identifier = repo.identifier
1014
+
1015
+ # Count agents in cache
1016
+ # Note: identifier already includes subdirectory path (e.g., "bobmatnyc/claude-mpm-agents/agents")
1017
+ cache_dir = (
1018
+ Path.home() / ".claude-mpm" / "cache" / "agents" / identifier
1019
+ )
1020
+ agent_count = 0
1021
+ if cache_dir.exists():
1022
+ # cache_dir IS the agents directory - no need to append /agents
1023
+ agent_count = len(list(cache_dir.rglob("*.md")))
1024
+
1025
+ sources.append(
1026
+ {
1027
+ "identifier": identifier,
1028
+ "url": repo.url,
1029
+ "enabled": repo.enabled,
1030
+ "priority": repo.priority,
1031
+ "agent_count": agent_count,
1032
+ }
1033
+ )
1034
+
1035
+ return sources
1036
+ except Exception as e:
1037
+ self.logger.warning(f"Failed to get configured sources: {e}")
1038
+ return []
1039
+
1040
+ def _filter_agent_configs(
1041
+ self, agents: List[AgentConfig], filter_deployed: bool = False
1042
+ ) -> List[AgentConfig]:
1043
+ """Filter AgentConfig objects using agent_filters utilities.
1044
+
1045
+ Converts AgentConfig objects to dictionaries for filtering,
1046
+ then back to AgentConfig. Always filters BASE_AGENT.
1047
+ Optionally filters deployed agents.
1048
+
1049
+ Args:
1050
+ agents: List of AgentConfig objects
1051
+ filter_deployed: Whether to filter out deployed agents (default: False)
1052
+
1053
+ Returns:
1054
+ Filtered list of AgentConfig objects
1055
+ """
1056
+ # Convert AgentConfig to dict format for filtering
1057
+ agent_dicts = []
1058
+ for agent in agents:
1059
+ agent_dicts.append(
1060
+ {
1061
+ "agent_id": agent.name,
1062
+ "name": agent.name,
1063
+ "description": agent.description,
1064
+ "deployed": getattr(agent, "is_deployed", False),
1065
+ }
1066
+ )
1067
+
1068
+ # Apply filters (always filter BASE_AGENT)
1069
+ filtered_dicts = apply_all_filters(
1070
+ agent_dicts, filter_base=True, filter_deployed=filter_deployed
1071
+ )
1072
+
1073
+ # Convert back to AgentConfig objects
1074
+ filtered_names = {d["agent_id"] for d in filtered_dicts}
1075
+ return [a for a in agents if a.name in filtered_names]
1076
+
1077
+ @staticmethod
1078
+ def _calculate_column_widths(
1079
+ terminal_width: int, columns: Dict[str, int]
1080
+ ) -> Dict[str, int]:
1081
+ """Calculate dynamic column widths based on terminal size.
1082
+
1083
+ Args:
1084
+ terminal_width: Current terminal width in characters
1085
+ columns: Dict mapping column names to minimum widths
1086
+
1087
+ Returns:
1088
+ Dict mapping column names to calculated widths
1089
+
1090
+ Design:
1091
+ - Ensures minimum widths are respected
1092
+ - Distributes extra space proportionally
1093
+ - Handles narrow terminals gracefully (minimum 80 chars)
1094
+ """
1095
+ # Ensure minimum terminal width
1096
+ min_terminal_width = 80
1097
+ terminal_width = max(terminal_width, min_terminal_width)
1098
+
1099
+ # Calculate total minimum width needed
1100
+ total_min_width = sum(columns.values())
1101
+
1102
+ # Account for table borders and padding (2 chars per column + 2 for edges)
1103
+ overhead = (len(columns) * 2) + 2
1104
+ available_width = terminal_width - overhead
1105
+
1106
+ # If we have extra space, distribute proportionally
1107
+ if available_width > total_min_width:
1108
+ extra_space = available_width - total_min_width
1109
+ total_weight = sum(columns.values())
1110
+
1111
+ result = {}
1112
+ for col_name, min_width in columns.items():
1113
+ # Distribute extra space based on minimum width proportion
1114
+ proportion = min_width / total_weight
1115
+ extra = int(extra_space * proportion)
1116
+ result[col_name] = min_width + extra
1117
+ return result
1118
+ # Terminal too narrow, use minimum widths
1119
+ return columns.copy()
1120
+
1121
+ def _format_display_name(self, name: str) -> str:
1122
+ """Format internal agent name to human-readable display name.
1123
+
1124
+ Converts underscores/hyphens to spaces and title-cases.
1125
+ Examples:
1126
+ agentic_coder_optimizer -> Agentic Coder Optimizer
1127
+ python-engineer -> Python Engineer
1128
+ api_qa_agent -> Api Qa Agent
1129
+
1130
+ Args:
1131
+ name: Internal agent name (may contain underscores, hyphens)
1132
+
1133
+ Returns:
1134
+ Human-readable display name
1135
+ """
1136
+ return name.replace("_", " ").replace("-", " ").title()
1137
+
1138
+ def _display_agents_with_source_info(self, agents: List[AgentConfig]) -> None:
1139
+ """Display agents table with source information and installation status."""
1140
+ from rich.table import Table
1141
+
1142
+ # Get recommended agents for this project
1143
+ try:
1144
+ recommended_agents = self.recommendation_service.get_recommended_agents(
1145
+ str(self.project_dir)
1146
+ )
1147
+ except Exception as e:
1148
+ self.logger.warning(f"Failed to get recommended agents: {e}")
1149
+ recommended_agents = set()
1150
+
1151
+ # Get terminal width and calculate dynamic column widths
1152
+ terminal_width = shutil.get_terminal_size().columns
1153
+ min_widths = {
1154
+ "#": 4,
1155
+ "Agent ID": 30,
1156
+ "Name": 20,
1157
+ "Source": 15,
1158
+ "Status": 10,
1159
+ }
1160
+ widths = self._calculate_column_widths(terminal_width, min_widths)
1161
+
1162
+ agents_table = Table(show_header=True, header_style="bold cyan")
1163
+ agents_table.add_column(
1164
+ "#", style="bright_black", width=widths["#"], no_wrap=True
1165
+ )
1166
+ agents_table.add_column(
1167
+ "Agent ID",
1168
+ style="bright_black",
1169
+ width=widths["Agent ID"],
1170
+ no_wrap=True,
1171
+ overflow="ellipsis",
1172
+ )
1173
+ agents_table.add_column(
1174
+ "Name",
1175
+ style="bright_cyan",
1176
+ width=widths["Name"],
1177
+ no_wrap=True,
1178
+ overflow="ellipsis",
1179
+ )
1180
+ agents_table.add_column(
1181
+ "Source",
1182
+ style="bright_yellow",
1183
+ width=widths["Source"],
1184
+ no_wrap=True,
1185
+ )
1186
+ agents_table.add_column(
1187
+ "Status", style="bright_black", width=widths["Status"], no_wrap=True
1188
+ )
1189
+
1190
+ # FIX 3: Get deployed agent IDs once, before the loop (efficiency)
1191
+ deployed_ids = get_deployed_agent_ids()
1192
+
1193
+ recommended_count = 0
1194
+ for idx, agent in enumerate(agents, 1):
1195
+ # Determine source with repo name
1196
+ source_type = getattr(agent, "source_type", "local")
1197
+
1198
+ if source_type == "remote":
1199
+ # Get repo name from agent metadata
1200
+ source_dict = getattr(agent, "source_dict", {})
1201
+ repo_url = source_dict.get("source", "")
1202
+
1203
+ # Extract repo name from URL
1204
+ if (
1205
+ "bobmatnyc/claude-mpm" in repo_url
1206
+ or "claude-mpm" in repo_url.lower()
1207
+ ):
1208
+ source_label = "MPM Agents"
1209
+ elif "/" in repo_url:
1210
+ # Extract last part of org/repo
1211
+ parts = repo_url.rstrip("/").split("/")
1212
+ if len(parts) >= 2:
1213
+ source_label = f"{parts[-2]}/{parts[-1]}"
1214
+ else:
1215
+ source_label = "Community"
1216
+ else:
1217
+ source_label = "Community"
1218
+ else:
1219
+ source_label = "Local"
1220
+
1221
+ # FIX 2: Check actual deployment status from .claude/agents/ directory
1222
+ # Use agent_id (technical ID like "python-engineer") not display name
1223
+ agent_id = getattr(agent, "agent_id", agent.name)
1224
+ is_installed = agent_id in deployed_ids
1225
+ if is_installed:
1226
+ status = "[green]Installed[/green]"
1227
+ else:
1228
+ status = "Available"
1229
+
1230
+ # Check if agent is recommended
1231
+ # Handle both hierarchical paths (e.g., "engineer/backend/python-engineer")
1232
+ # and leaf names (e.g., "python-engineer")
1233
+ agent_full_path = agent.name
1234
+ agent_leaf_name = (
1235
+ agent_full_path.split("/")[-1]
1236
+ if "/" in agent_full_path
1237
+ else agent_full_path
1238
+ )
1239
+
1240
+ for recommended_id in recommended_agents:
1241
+ # Check if the recommended_id matches either the full path or just the leaf name
1242
+ recommended_leaf = (
1243
+ recommended_id.split("/")[-1]
1244
+ if "/" in recommended_id
1245
+ else recommended_id
1246
+ )
1247
+ if (
1248
+ agent_full_path == recommended_id
1249
+ or agent_leaf_name == recommended_leaf
1250
+ ):
1251
+ recommended_count += 1
1252
+ break
1253
+
1254
+ # FIX 1: Show agent_id (technical ID) in first column, not display name
1255
+ agent_id_display = getattr(agent, "agent_id", agent.name)
1256
+
1257
+ # Get display name and format it properly
1258
+ # Raw display_name from YAML may contain underscores (e.g., "agentic_coder_optimizer")
1259
+ raw_display_name = getattr(agent, "display_name", agent.name)
1260
+ display_name = self._format_display_name(raw_display_name)
1261
+
1262
+ agents_table.add_row(
1263
+ str(idx), agent_id_display, display_name, source_label, status
1264
+ )
1265
+
1266
+ self.console.print(agents_table)
1267
+
1268
+ # Show legend if there are recommended agents
1269
+ if recommended_count > 0:
1270
+ # Get detection summary for context
1271
+ try:
1272
+ summary = self.recommendation_service.get_detection_summary(
1273
+ str(self.project_dir)
1274
+ )
1275
+ detected_langs = (
1276
+ ", ".join(summary.get("detected_languages", [])) or "None"
1277
+ )
1278
+ ", ".join(summary.get("detected_frameworks", [])) or "None"
1279
+ self.console.print(
1280
+ f"\n[dim]* = recommended for this project "
1281
+ f"(detected: {detected_langs})[/dim]"
1282
+ )
1283
+ except Exception:
1284
+ self.console.print("\n[dim]* = recommended for this project[/dim]")
1285
+
1286
+ # Show installed vs available count (use deployed_ids for accuracy)
1287
+ # Use agent_id (technical ID) for comparison, not display name
1288
+ installed_count = sum(
1289
+ 1 for a in agents if getattr(a, "agent_id", a.name) in deployed_ids
1290
+ )
1291
+ available_count = len(agents) - installed_count
1292
+ self.console.print(
1293
+ f"\n[green]✓ {installed_count} installed[/green] | "
1294
+ f"[dim]{available_count} available[/dim] | "
1295
+ f"[yellow]{recommended_count} recommended[/yellow] | "
1296
+ f"[dim]Total: {len(agents)}[/dim]"
1297
+ )
1298
+
1299
+ def _manage_sources(self) -> None:
1300
+ """Interactive source management."""
1301
+ self.console.print("\n[bold white]═══ Manage Agent Sources ═══[/bold white]\n")
1302
+ self.console.print(
1303
+ "[dim]Use 'claude-mpm agent-source' command to add/remove sources[/dim]"
1304
+ )
1305
+ self.console.print("\nExamples:")
1306
+ self.console.print(" claude-mpm agent-source add <git-url>")
1307
+ self.console.print(" claude-mpm agent-source remove <identifier>")
1308
+ self.console.print(" claude-mpm agent-source list")
1309
+ Prompt.ask("\nPress Enter to continue")
1310
+
1311
+ def _deploy_agents_unified(self, agents: List[AgentConfig]) -> None:
1312
+ """Unified agent selection with inline controls for recommended, presets, and collections.
1313
+
1314
+ Design:
1315
+ - Single nested checkbox list with grouped agents by source/category
1316
+ - Inline controls at top: Select all, Select recommended, Select presets
1317
+ - Asterisk (*) marks recommended agents
1318
+ - Visual hierarchy: Source → Category → Individual agents
1319
+ - Loop with visual feedback: Controls update checkmarks immediately
1320
+ """
1321
+ if not agents:
1322
+ self.console.print("[yellow]No agents available[/yellow]")
1323
+ Prompt.ask("\nPress Enter to continue")
1324
+ return
1325
+
1326
+ from claude_mpm.utils.agent_filters import (
1327
+ filter_base_agents,
1328
+ get_deployed_agent_ids,
1329
+ )
1330
+
1331
+ # Filter BASE_AGENT but keep deployed agents visible
1332
+ all_agents = filter_base_agents(
1333
+ [
1334
+ {
1335
+ "agent_id": getattr(a, "agent_id", a.name),
1336
+ "name": a.name,
1337
+ "description": a.description,
1338
+ "deployed": getattr(a, "is_deployed", False),
1339
+ }
1340
+ for a in agents
1341
+ ]
1342
+ )
1343
+
1344
+ if not all_agents:
1345
+ self.console.print("[yellow]No agents available[/yellow]")
1346
+ Prompt.ask("\nPress Enter to continue")
1347
+ return
1348
+
1349
+ # Get deployed agent IDs and recommended agents
1350
+ deployed_ids = get_deployed_agent_ids()
1351
+
1352
+ try:
1353
+ recommended_agent_ids = self.recommendation_service.get_recommended_agents(
1354
+ str(self.project_dir)
1355
+ )
1356
+ except Exception as e:
1357
+ self.logger.warning(f"Failed to get recommended agents: {e}")
1358
+ recommended_agent_ids = set()
1359
+
1360
+ # Build mapping: leaf name -> full path for deployed agents
1361
+ # Use agent_id (technical ID) for comparison, not display name
1362
+ deployed_full_paths = set()
1363
+ for agent in agents:
1364
+ agent_id = getattr(agent, "agent_id", agent.name)
1365
+ agent_leaf_name = agent_id.split("/")[-1]
1366
+ if agent_leaf_name in deployed_ids:
1367
+ # Store agent_id for selection tracking (not display name)
1368
+ deployed_full_paths.add(agent_id)
1369
+
1370
+ # Track current selection state (starts with deployed, updated in loop)
1371
+ current_selection = deployed_full_paths.copy()
1372
+
1373
+ # Group agents by source/collection
1374
+ agent_map = {}
1375
+ collections = defaultdict(list)
1376
+
1377
+ for agent in agents:
1378
+ # Use agent_id (technical ID) for comparison, not display name
1379
+ agent_id = getattr(agent, "agent_id", agent.name)
1380
+ if agent_id in {a["agent_id"] for a in all_agents}:
1381
+ # Determine collection ID
1382
+ source_type = getattr(agent, "source_type", "local")
1383
+ if source_type == "remote":
1384
+ source_dict = getattr(agent, "source_dict", {})
1385
+ repo_url = source_dict.get("source", "")
1386
+ if "/" in repo_url:
1387
+ parts = repo_url.rstrip("/").split("/")
1388
+ if len(parts) >= 2:
1389
+ # Use more readable collection name
1390
+ if (
1391
+ "bobmatnyc/claude-mpm" in repo_url
1392
+ or "claude-mpm" in repo_url.lower()
1393
+ ):
1394
+ collection_id = "MPM Agents"
1395
+ else:
1396
+ collection_id = f"{parts[-2]}/{parts[-1]}"
1397
+ else:
1398
+ collection_id = "Community Agents"
1399
+ else:
1400
+ collection_id = "Community Agents"
1401
+ else:
1402
+ collection_id = "Local Agents"
1403
+
1404
+ collections[collection_id].append(agent)
1405
+ agent_map[agent_id] = agent
1406
+
1407
+ # Monkey-patch questionary symbols for better visibility
1408
+ questionary.prompts.common.INDICATOR_SELECTED = "[✓]"
1409
+ questionary.prompts.common.INDICATOR_UNSELECTED = "[ ]"
1410
+
1411
+ # MAIN LOOP: Re-display UI when controls are used
1412
+ while True:
1413
+ # Build unified checkbox choices with inline controls
1414
+ choices = []
1415
+
1416
+ for collection_id in sorted(collections.keys()):
1417
+ agents_in_collection = collections[collection_id]
1418
+
1419
+ # Count selected/total agents in collection
1420
+ # Use agent_id for selection tracking, not display name
1421
+ selected_count = sum(
1422
+ 1
1423
+ for agent in agents_in_collection
1424
+ if getattr(agent, "agent_id", agent.name) in current_selection
1425
+ )
1426
+ total_count = len(agents_in_collection)
1427
+
1428
+ # Add collection header
1429
+ choices.append(
1430
+ Separator(
1431
+ f"\n── {collection_id} ({selected_count}/{total_count} selected) ──"
1432
+ )
1433
+ )
1434
+
1435
+ # Determine if all agents in collection are selected
1436
+ all_selected = selected_count == total_count
1437
+
1438
+ # Add inline control: Select/Deselect all from this collection
1439
+ if all_selected:
1440
+ choices.append(
1441
+ Choice(
1442
+ f" [Deselect all from {collection_id}]",
1443
+ value=f"__DESELECT_ALL_{collection_id}__",
1444
+ checked=False,
1445
+ )
1446
+ )
1447
+ else:
1448
+ choices.append(
1449
+ Choice(
1450
+ f" [Select all from {collection_id}]",
1451
+ value=f"__SELECT_ALL_{collection_id}__",
1452
+ checked=False,
1453
+ )
1454
+ )
1455
+
1456
+ # Add inline control: Select recommended from this collection
1457
+ recommended_in_collection = [
1458
+ a
1459
+ for a in agents_in_collection
1460
+ if any(
1461
+ a.name == rec_id
1462
+ or a.name.split("/")[-1] == rec_id.split("/")[-1]
1463
+ for rec_id in recommended_agent_ids
1464
+ )
1465
+ ]
1466
+ if recommended_in_collection:
1467
+ recommended_selected = sum(
1468
+ 1
1469
+ for a in recommended_in_collection
1470
+ if a.name in current_selection
1471
+ )
1472
+ if recommended_selected == len(recommended_in_collection):
1473
+ choices.append(
1474
+ Choice(
1475
+ f" [Deselect recommended ({len(recommended_in_collection)} agents)]",
1476
+ value=f"__DESELECT_REC_{collection_id}__",
1477
+ checked=False,
1478
+ )
1479
+ )
1480
+ else:
1481
+ choices.append(
1482
+ Choice(
1483
+ f" [Select recommended ({len(recommended_in_collection)} agents)]",
1484
+ value=f"__SELECT_REC_{collection_id}__",
1485
+ checked=False,
1486
+ )
1487
+ )
1488
+
1489
+ # Add separator before individual agents
1490
+ choices.append(Separator())
1491
+
1492
+ # Group agents by category within collection (if hierarchical)
1493
+ category_groups = defaultdict(list)
1494
+ for agent in sorted(agents_in_collection, key=lambda a: a.name):
1495
+ # Extract category from hierarchical path (e.g., "engineer/backend/python-engineer")
1496
+ parts = agent.name.split("/")
1497
+ if len(parts) > 1:
1498
+ category = "/".join(parts[:-1]) # e.g., "engineer/backend"
1499
+ else:
1500
+ category = "" # No category
1501
+ category_groups[category].append(agent)
1502
+
1503
+ # Display agents grouped by category
1504
+ for category in sorted(category_groups.keys()):
1505
+ agents_in_category = category_groups[category]
1506
+
1507
+ # Add category separator if hierarchical
1508
+ if category:
1509
+ choices.append(Separator(f" {category}/"))
1510
+
1511
+ # Add individual agents
1512
+ for agent in agents_in_category:
1513
+ # Use agent_id (technical ID) for all tracking/selection
1514
+ agent_id = getattr(agent, "agent_id", agent.name)
1515
+ agent_leaf_name = agent_id.split("/")[-1]
1516
+ raw_display_name = getattr(
1517
+ agent, "display_name", agent_leaf_name
1518
+ )
1519
+ display_name = self._format_display_name(raw_display_name)
1520
+
1521
+ # Check if agent is deployed (exists in .claude/agents/)
1522
+
1523
+ # Format choice text (no asterisk needed)
1524
+ choice_text = f" {display_name}"
1525
+
1526
+ is_selected = agent_id in current_selection
1527
+
1528
+ choices.append(
1529
+ Choice(
1530
+ title=choice_text,
1531
+ value=agent_id, # Use agent_id for value
1532
+ checked=is_selected,
1533
+ )
1534
+ )
1535
+
1536
+ self.console.print("\n[bold cyan]Select Agents to Install[/bold cyan]")
1537
+ self.console.print("[dim][✓] Checked = Installed (uncheck to remove)[/dim]")
1538
+ self.console.print(
1539
+ "[dim][ ] Unchecked = Available (check to install)[/dim]"
1540
+ )
1541
+ self.console.print(
1542
+ "[dim]Use arrow keys to navigate, space to toggle, Enter to apply[/dim]\n"
1543
+ )
1544
+
1545
+ try:
1546
+ selected_values = questionary.checkbox(
1547
+ "Select agents:",
1548
+ choices=choices,
1549
+ instruction="(Space to toggle, Enter to continue)",
1550
+ style=self.QUESTIONARY_STYLE,
1551
+ ).ask()
1552
+ except Exception as e:
1553
+ import sys
1554
+
1555
+ self.logger.error(f"Questionary checkbox failed: {e}", exc_info=True)
1556
+ self.console.print(
1557
+ "[red]Error: Could not display interactive menu[/red]"
1558
+ )
1559
+ self.console.print(f"[dim]Reason: {e}[/dim]")
1560
+ if not sys.stdin.isatty():
1561
+ self.console.print("[dim]Interactive terminal required. Use:[/dim]")
1562
+ self.console.print(
1563
+ "[dim] --list-agents to see available agents[/dim]"
1564
+ )
1565
+ Prompt.ask("\nPress Enter to continue")
1566
+ return
1567
+
1568
+ if selected_values is None:
1569
+ self.console.print("[yellow]No changes made[/yellow]")
1570
+ Prompt.ask("\nPress Enter to continue")
1571
+ return
1572
+
1573
+ # Check for inline control selections
1574
+ controls_selected = [v for v in selected_values if v.startswith("__")]
1575
+
1576
+ if controls_selected:
1577
+ # Process controls and update current_selection
1578
+ for control in controls_selected:
1579
+ if control.startswith("__SELECT_ALL_"):
1580
+ collection_id = control.replace("__SELECT_ALL_", "").replace(
1581
+ "__", ""
1582
+ )
1583
+ # Add all agents from this collection to current_selection
1584
+ for agent in collections[collection_id]:
1585
+ agent_id = getattr(agent, "agent_id", agent.name)
1586
+ current_selection.add(agent_id)
1587
+ elif control.startswith("__DESELECT_ALL_"):
1588
+ collection_id = control.replace("__DESELECT_ALL_", "").replace(
1589
+ "__", ""
1590
+ )
1591
+ # Remove all agents from this collection
1592
+ for agent in collections[collection_id]:
1593
+ agent_id = getattr(agent, "agent_id", agent.name)
1594
+ current_selection.discard(agent_id)
1595
+ elif control.startswith("__SELECT_REC_"):
1596
+ collection_id = control.replace("__SELECT_REC_", "").replace(
1597
+ "__", ""
1598
+ )
1599
+ # Add all recommended agents from this collection
1600
+ for agent in collections[collection_id]:
1601
+ agent_id = getattr(agent, "agent_id", agent.name)
1602
+ if any(
1603
+ agent_id == rec_id
1604
+ or agent_id.split("/")[-1] == rec_id.split("/")[-1]
1605
+ for rec_id in recommended_agent_ids
1606
+ ):
1607
+ current_selection.add(agent_id)
1608
+ elif control.startswith("__DESELECT_REC_"):
1609
+ collection_id = control.replace("__DESELECT_REC_", "").replace(
1610
+ "__", ""
1611
+ )
1612
+ # Remove all recommended agents from this collection
1613
+ for agent in collections[collection_id]:
1614
+ agent_id = getattr(agent, "agent_id", agent.name)
1615
+ if any(
1616
+ agent_id == rec_id
1617
+ or agent_id.split("/")[-1] == rec_id.split("/")[-1]
1618
+ for rec_id in recommended_agent_ids
1619
+ ):
1620
+ current_selection.discard(agent_id)
1621
+
1622
+ # Loop back to re-display with updated selections
1623
+ continue
1624
+
1625
+ # No controls selected - use the individual selections as final
1626
+ final_selection = set(selected_values)
1627
+ break
1628
+
1629
+ # Determine changes
1630
+ to_deploy = final_selection - deployed_full_paths
1631
+ to_remove = deployed_full_paths - final_selection
1632
+
1633
+ if not to_deploy and not to_remove:
1634
+ self.console.print("[yellow]No changes needed[/yellow]")
1635
+ Prompt.ask("\nPress Enter to continue")
1636
+ return
1637
+
1638
+ # Show what will happen
1639
+ self.console.print("\n[bold]Changes to apply:[/bold]")
1640
+ if to_deploy:
1641
+ self.console.print(f"[green]Install {len(to_deploy)} agent(s)[/green]")
1642
+ for agent_id in to_deploy:
1643
+ self.console.print(f" + {agent_id}")
1644
+ if to_remove:
1645
+ self.console.print(f"[red]Remove {len(to_remove)} agent(s)[/red]")
1646
+ for agent_id in to_remove:
1647
+ self.console.print(f" - {agent_id}")
1648
+
1649
+ # Confirm
1650
+ if not Confirm.ask("\nApply these changes?", default=True):
1651
+ self.console.print("[yellow]Changes cancelled[/yellow]")
1652
+ Prompt.ask("\nPress Enter to continue")
1653
+ return
1654
+
1655
+ # Execute changes
1656
+ deploy_success = 0
1657
+ deploy_fail = 0
1658
+ remove_success = 0
1659
+ remove_fail = 0
1660
+
1661
+ # Install new agents
1662
+ for agent_id in to_deploy:
1663
+ agent = agent_map.get(agent_id)
1664
+ if agent and self._deploy_single_agent(agent, show_feedback=False):
1665
+ deploy_success += 1
1666
+ self.console.print(f"[green]✓ Installed: {agent_id}[/green]")
1667
+ else:
1668
+ deploy_fail += 1
1669
+ self.console.print(f"[red]✗ Failed to install: {agent_id}[/red]")
1670
+
1671
+ # Remove agents
1672
+ for agent_id in to_remove:
1673
+ try:
1674
+ import json
1675
+
1676
+ # Extract leaf name to match deployed filename
1677
+ leaf_name = agent_id.split("/")[-1] if "/" in agent_id else agent_id
1678
+
1679
+ # Remove from all possible locations
1680
+ paths_to_check = [
1681
+ Path.cwd() / ".claude-mpm" / "agents" / f"{leaf_name}.md",
1682
+ Path.cwd() / ".claude" / "agents" / f"{leaf_name}.md",
1683
+ Path.home() / ".claude" / "agents" / f"{leaf_name}.md",
1684
+ ]
1685
+
1686
+ removed = False
1687
+ for path in paths_to_check:
1688
+ if path.exists():
1689
+ path.unlink()
1690
+ removed = True
1691
+
1692
+ # Also remove from virtual deployment state
1693
+ deployment_state_paths = [
1694
+ Path.cwd() / ".claude" / "agents" / ".mpm_deployment_state",
1695
+ Path.home() / ".claude" / "agents" / ".mpm_deployment_state",
1696
+ ]
1697
+
1698
+ for state_path in deployment_state_paths:
1699
+ if state_path.exists():
1700
+ try:
1701
+ with state_path.open() as f:
1702
+ state = json.load(f)
1703
+ agents_in_state = state.get("last_check_results", {}).get(
1704
+ "agents", {}
1705
+ )
1706
+ if leaf_name in agents_in_state:
1707
+ del agents_in_state[leaf_name]
1708
+ removed = True
1709
+ with state_path.open("w") as f:
1710
+ json.dump(state, f, indent=2)
1711
+ except (json.JSONDecodeError, KeyError):
1712
+ pass
1713
+
1714
+ if removed:
1715
+ remove_success += 1
1716
+ self.console.print(f"[green]✓ Removed: {agent_id}[/green]")
1717
+ else:
1718
+ remove_fail += 1
1719
+ self.console.print(f"[yellow]⚠ Not found: {agent_id}[/yellow]")
1720
+ except Exception as e:
1721
+ remove_fail += 1
1722
+ self.console.print(f"[red]✗ Failed to remove {agent_id}: {e}[/red]")
1723
+
1724
+ # Show summary
1725
+ self.console.print()
1726
+ if deploy_success > 0:
1727
+ self.console.print(f"[green]✓ Installed {deploy_success} agent(s)[/green]")
1728
+ if deploy_fail > 0:
1729
+ self.console.print(f"[red]✗ Failed to install {deploy_fail} agent(s)[/red]")
1730
+ if remove_success > 0:
1731
+ self.console.print(f"[green]✓ Removed {remove_success} agent(s)[/green]")
1732
+ if remove_fail > 0:
1733
+ self.console.print(f"[red]✗ Failed to remove {remove_fail} agent(s)[/red]")
1734
+
1735
+ Prompt.ask("\nPress Enter to continue")
1736
+
1737
+ def _deploy_agents_individual(self, agents: List[AgentConfig]) -> None:
1738
+ """Manage agent installation state (unified install/remove interface).
1739
+
1740
+ DEPRECATED: Use _deploy_agents_unified instead.
1741
+ This method is kept for backward compatibility but should not be used.
1742
+ """
1743
+ if not agents:
1744
+ self.console.print("[yellow]No agents available[/yellow]")
1745
+ Prompt.ask("\nPress Enter to continue")
1746
+ return
1747
+
1748
+ # Get ALL agents (filter BASE_AGENT but keep deployed agents visible)
1749
+ from claude_mpm.utils.agent_filters import (
1750
+ filter_base_agents,
1751
+ get_deployed_agent_ids,
1752
+ )
1753
+
1754
+ # Filter BASE_AGENT but keep deployed agents visible
1755
+ all_agents = filter_base_agents(
1756
+ [
1757
+ {
1758
+ "agent_id": getattr(a, "agent_id", a.name),
1759
+ "name": a.name,
1760
+ "description": a.description,
1761
+ "deployed": getattr(a, "is_deployed", False),
1762
+ }
1763
+ for a in agents
1764
+ ]
1765
+ )
1766
+
1767
+ # Get deployed agent IDs (original state - for calculating final changes)
1768
+ # NOTE: deployed_ids contains LEAF NAMES (e.g., "python-engineer")
1769
+ deployed_ids = get_deployed_agent_ids()
1770
+
1771
+ if not all_agents:
1772
+ self.console.print("[yellow]No agents available[/yellow]")
1773
+ Prompt.ask("\nPress Enter to continue")
1774
+ return
1775
+
1776
+ # Build mapping: leaf name -> full path for deployed agents
1777
+ # This allows comparing deployed_ids (leaf names) with agent.agent_id (full paths)
1778
+ deployed_full_paths = set()
1779
+ for agent in agents:
1780
+ # FIX: Use agent_id (technical ID) instead of display name
1781
+ agent_id = getattr(agent, "agent_id", agent.name)
1782
+ agent_leaf_name = agent_id.split("/")[-1]
1783
+ if agent_leaf_name in deployed_ids:
1784
+ deployed_full_paths.add(agent_id)
1785
+
1786
+ # Track current selection state (starts with deployed full paths, updated after each iteration)
1787
+ current_selection = deployed_full_paths.copy()
1788
+
1789
+ # Loop to allow adjusting selection
1790
+ while True:
1791
+ # Build agent mapping and collections
1792
+ agent_map = {} # For lookup after selection
1793
+ collections = defaultdict(list)
1794
+
1795
+ for agent in agents:
1796
+ # FIX: Use agent_id (technical ID) for comparison
1797
+ agent_id = getattr(agent, "agent_id", agent.name)
1798
+ if agent_id in {a["agent_id"] for a in all_agents}:
1799
+ # Determine collection ID
1800
+ source_type = getattr(agent, "source_type", "local")
1801
+ if source_type == "remote":
1802
+ source_dict = getattr(agent, "source_dict", {})
1803
+ repo_url = source_dict.get("source", "")
1804
+ # Extract repository name from URL
1805
+ if "/" in repo_url:
1806
+ parts = repo_url.rstrip("/").split("/")
1807
+ if len(parts) >= 2:
1808
+ collection_id = f"{parts[-2]}/{parts[-1]}"
1809
+ else:
1810
+ collection_id = "remote"
1811
+ else:
1812
+ collection_id = "remote"
1813
+ else:
1814
+ collection_id = "local"
1815
+
1816
+ collections[collection_id].append(agent)
1817
+ agent_map[agent_id] = agent # FIX: Use agent_id as key
1818
+
1819
+ # STEP 1: Collection-level selection
1820
+ self.console.print("\n[bold cyan]Select Agent Collections[/bold cyan]")
1821
+ self.console.print(
1822
+ "[dim]Checking a collection installs ALL agents in that collection[/dim]"
1823
+ )
1824
+ self.console.print(
1825
+ "[dim]Unchecking a collection removes ALL agents in that collection[/dim]"
1826
+ )
1827
+ self.console.print(
1828
+ "[dim]For partial deployment, use 'Fine-tune individual agents'[/dim]\n"
1829
+ )
1830
+
1831
+ collection_choices = []
1832
+ for collection_id in sorted(collections.keys()):
1833
+ agents_in_collection = collections[collection_id]
1834
+
1835
+ # Check if ANY agent in this collection is currently deployed
1836
+ # This reflects actual deployment state, not just selection
1837
+ # FIX: Use agent_id for comparison with current_selection
1838
+ any_deployed = any(
1839
+ getattr(agent, "agent_id", agent.name) in current_selection
1840
+ for agent in agents_in_collection
1841
+ )
1842
+
1843
+ # Count deployed agents for display
1844
+ # FIX: Use agent_id for comparison with current_selection
1845
+ deployed_count = sum(
1846
+ 1
1847
+ for agent in agents_in_collection
1848
+ if getattr(agent, "agent_id", agent.name) in current_selection
1849
+ )
1850
+
1851
+ collection_choices.append(
1852
+ Choice(
1853
+ f"{collection_id} ({deployed_count}/{len(agents_in_collection)} deployed)",
1854
+ value=collection_id,
1855
+ checked=any_deployed,
1856
+ )
1857
+ )
1858
+
1859
+ # Add option to fine-tune individual agents
1860
+ collection_choices.append(Separator())
1861
+ collection_choices.append(
1862
+ Choice(
1863
+ "→ Fine-tune individual agents...",
1864
+ value="__INDIVIDUAL__",
1865
+ checked=False,
1866
+ )
1867
+ )
1868
+
1869
+ # Monkey-patch questionary symbols for better visibility
1870
+ questionary.prompts.common.INDICATOR_SELECTED = "[✓]"
1871
+ questionary.prompts.common.INDICATOR_UNSELECTED = "[ ]"
1872
+
1873
+ try:
1874
+ selected_collections = questionary.checkbox(
1875
+ "Select agent collections to deploy:",
1876
+ choices=collection_choices,
1877
+ instruction="(Space to toggle, Enter to continue)",
1878
+ style=self.QUESTIONARY_STYLE,
1879
+ ).ask()
1880
+ except Exception as e:
1881
+ import sys
1882
+
1883
+ self.logger.error(f"Questionary checkbox failed: {e}", exc_info=True)
1884
+ self.console.print(
1885
+ "[red]Error: Could not display interactive menu[/red]"
1886
+ )
1887
+ self.console.print(f"[dim]Reason: {e}[/dim]")
1888
+ if not sys.stdin.isatty():
1889
+ self.console.print("[dim]Interactive terminal required. Use:[/dim]")
1890
+ self.console.print(
1891
+ "[dim] --list-agents to see available agents[/dim]"
1892
+ )
1893
+ self.console.print(
1894
+ "[dim] --enable-agent/--disable-agent for scripting[/dim]"
1895
+ )
1896
+ else:
1897
+ self.console.print(
1898
+ "[dim]This might be a terminal compatibility issue.[/dim]"
1899
+ )
1900
+ Prompt.ask("\nPress Enter to continue")
1901
+ return
1902
+
1903
+ # Handle cancellation
1904
+ if selected_collections is None:
1905
+ import sys
1906
+
1907
+ if not sys.stdin.isatty():
1908
+ self.console.print(
1909
+ "[red]Error: Interactive terminal required for agent selection[/red]"
1910
+ )
1911
+ self.console.print(
1912
+ "[dim]Use --list-agents to see available agents[/dim]"
1913
+ )
1914
+ self.console.print(
1915
+ "[dim]Use --enable-agent/--disable-agent for non-interactive mode[/dim]"
1916
+ )
1917
+ else:
1918
+ self.console.print("[yellow]No changes made[/yellow]")
1919
+ Prompt.ask("\nPress Enter to continue")
1920
+ return
1921
+
1922
+ # STEP 2: Check if user wants individual selection
1923
+ if "__INDIVIDUAL__" in selected_collections:
1924
+ # Remove the __INDIVIDUAL__ marker
1925
+ selected_collections = [
1926
+ c for c in selected_collections if c != "__INDIVIDUAL__"
1927
+ ]
1928
+
1929
+ # Build individual agent choices with grouping
1930
+ agent_choices = []
1931
+ for collection_id in sorted(collections.keys()):
1932
+ agents_in_collection = collections[collection_id]
1933
+
1934
+ # Add collection header separator
1935
+ agent_choices.append(
1936
+ Separator(
1937
+ f"\n── {collection_id} ({len(agents_in_collection)} agents) ──"
1938
+ )
1939
+ )
1940
+
1941
+ # Add individual agents from this collection
1942
+ # FIX: Use agent_id for sorting, comparison, and values
1943
+ for agent in sorted(
1944
+ agents_in_collection,
1945
+ key=lambda a: getattr(a, "agent_id", a.name),
1946
+ ):
1947
+ agent_id = getattr(agent, "agent_id", agent.name)
1948
+ raw_display_name = getattr(agent, "display_name", agent.name)
1949
+ display_name = self._format_display_name(raw_display_name)
1950
+ is_selected = agent_id in deployed_full_paths
1951
+
1952
+ choice_text = f"{agent_id}"
1953
+ if display_name and display_name != agent_id:
1954
+ choice_text += f" - {display_name}"
1955
+
1956
+ agent_choices.append(
1957
+ Choice(
1958
+ title=choice_text, value=agent_id, checked=is_selected
1959
+ )
1960
+ )
1961
+
1962
+ self.console.print(
1963
+ "\n[bold cyan]Fine-tune Individual Agents[/bold cyan]"
1964
+ )
1965
+ self.console.print(
1966
+ "[dim][✓] Checked = Installed (uncheck to remove)[/dim]"
1967
+ )
1968
+ self.console.print(
1969
+ "[dim][ ] Unchecked = Available (check to install)[/dim]"
1970
+ )
1971
+ self.console.print(
1972
+ "[dim]Use arrow keys to navigate, space to toggle, Enter to apply[/dim]\n"
1973
+ )
1974
+
1975
+ try:
1976
+ selected_agent_ids = questionary.checkbox(
1977
+ "Select individual agents:",
1978
+ choices=agent_choices,
1979
+ style=self.QUESTIONARY_STYLE,
1980
+ ).ask()
1981
+ except Exception as e:
1982
+ import sys
1983
+
1984
+ self.logger.error(
1985
+ f"Questionary checkbox failed: {e}", exc_info=True
1986
+ )
1987
+ self.console.print(
1988
+ "[red]Error: Could not display interactive menu[/red]"
1989
+ )
1990
+ self.console.print(f"[dim]Reason: {e}[/dim]")
1991
+ Prompt.ask("\nPress Enter to continue")
1992
+ return
1993
+
1994
+ if selected_agent_ids is None:
1995
+ self.console.print("[yellow]No changes made[/yellow]")
1996
+ Prompt.ask("\nPress Enter to continue")
1997
+ return
1998
+
1999
+ # Update current_selection with individual selections
2000
+ current_selection = set(selected_agent_ids)
2001
+ else:
2002
+ # Apply collection-level selections
2003
+ # For each collection, if it's selected, include ALL its agents
2004
+ # If it's not selected, exclude ALL its agents
2005
+ final_selections = set()
2006
+ for collection_id in selected_collections:
2007
+ for agent in collections[collection_id]:
2008
+ # FIX: Use agent_id for selection tracking
2009
+ final_selections.add(getattr(agent, "agent_id", agent.name))
2010
+
2011
+ # Update current_selection
2012
+ # This replaces the previous selection entirely with the new collection selections
2013
+ current_selection = final_selections
2014
+
2015
+ # Determine actions based on ORIGINAL deployed state
2016
+ # Compare full paths to full paths (deployed_full_paths was built from deployed_ids)
2017
+ to_deploy = (
2018
+ current_selection - deployed_full_paths
2019
+ ) # Selected but not originally deployed
2020
+
2021
+ # For removal, verify files actually exist before adding to the set
2022
+ # This prevents "Not found" warnings when multiple agents share leaf names
2023
+ to_remove = set()
2024
+ for agent_id in deployed_full_paths - current_selection:
2025
+ # Extract leaf name to check file existence
2026
+ leaf_name = agent_id.split("/")[-1] if "/" in agent_id else agent_id
2027
+
2028
+ # Check all possible locations
2029
+ paths_to_check = [
2030
+ Path.cwd() / ".claude-mpm" / "agents" / f"{leaf_name}.md",
2031
+ Path.cwd() / ".claude" / "agents" / f"{leaf_name}.md",
2032
+ Path.home() / ".claude" / "agents" / f"{leaf_name}.md",
2033
+ ]
2034
+
2035
+ # Also check virtual deployment state
2036
+ state_exists = False
2037
+ deployment_state_paths = [
2038
+ Path.cwd() / ".claude" / "agents" / ".mpm_deployment_state",
2039
+ Path.home() / ".claude" / "agents" / ".mpm_deployment_state",
2040
+ ]
2041
+
2042
+ for state_path in deployment_state_paths:
2043
+ if state_path.exists():
2044
+ try:
2045
+ import json
2046
+
2047
+ with state_path.open() as f:
2048
+ state = json.load(f)
2049
+ agents_in_state = state.get("last_check_results", {}).get(
2050
+ "agents", {}
2051
+ )
2052
+ if leaf_name in agents_in_state:
2053
+ state_exists = True
2054
+ break
2055
+ except (json.JSONDecodeError, KeyError):
2056
+ continue
2057
+
2058
+ # Only add to removal set if file or state entry actually exists
2059
+ if any(p.exists() for p in paths_to_check) or state_exists:
2060
+ to_remove.add(agent_id)
2061
+
2062
+ if not to_deploy and not to_remove:
2063
+ self.console.print(
2064
+ "[yellow]No changes needed - all selected agents are already installed[/yellow]"
2065
+ )
2066
+ Prompt.ask("\nPress Enter to continue")
2067
+ return
2068
+
2069
+ # Show what will happen
2070
+ self.console.print("\n[bold]Changes to apply:[/bold]")
2071
+ if to_deploy:
2072
+ self.console.print(f"[green]Install {len(to_deploy)} agent(s)[/green]")
2073
+ for agent_id in to_deploy:
2074
+ self.console.print(f" + {agent_id}")
2075
+ if to_remove:
2076
+ self.console.print(f"[red]Remove {len(to_remove)} agent(s)[/red]")
2077
+ for agent_id in to_remove:
2078
+ self.console.print(f" - {agent_id}")
2079
+
2080
+ # Ask user to confirm, adjust, or cancel
2081
+ action = questionary.select(
2082
+ "\nWhat would you like to do?",
2083
+ choices=[
2084
+ questionary.Choice("Apply these changes", value="apply"),
2085
+ questionary.Choice("Adjust selection", value="adjust"),
2086
+ questionary.Choice("Cancel", value="cancel"),
2087
+ ],
2088
+ default="apply",
2089
+ style=self.QUESTIONARY_STYLE,
2090
+ ).ask()
2091
+
2092
+ if action == "cancel":
2093
+ self.console.print("[yellow]Changes cancelled[/yellow]")
2094
+ Prompt.ask("\nPress Enter to continue")
2095
+ return
2096
+ if action == "adjust":
2097
+ # current_selection is already updated, loop will use it
2098
+ continue
2099
+
2100
+ # Execute changes
2101
+ deploy_success = 0
2102
+ deploy_fail = 0
2103
+ remove_success = 0
2104
+ remove_fail = 0
2105
+
2106
+ # Install new agents
2107
+ for agent_id in to_deploy:
2108
+ agent = agent_map.get(agent_id)
2109
+ if agent and self._deploy_single_agent(agent, show_feedback=False):
2110
+ deploy_success += 1
2111
+ self.console.print(f"[green]✓ Installed: {agent_id}[/green]")
2112
+ else:
2113
+ deploy_fail += 1
2114
+ self.console.print(f"[red]✗ Failed to install: {agent_id}[/red]")
2115
+
2116
+ # Remove agents
2117
+ for agent_id in to_remove:
2118
+ try:
2119
+ import json
2120
+ # Note: Path is already imported at module level (line 17)
2121
+
2122
+ # Extract leaf name to match deployed filename
2123
+ # agent_id may be hierarchical (e.g., "engineer/mobile/tauri-engineer")
2124
+ # but deployed files use flattened leaf names (e.g., "tauri-engineer.md")
2125
+ if "/" in agent_id:
2126
+ leaf_name = agent_id.split("/")[-1]
2127
+ else:
2128
+ leaf_name = agent_id
2129
+
2130
+ # Remove from project, legacy, and user locations
2131
+ project_path = (
2132
+ Path.cwd() / ".claude-mpm" / "agents" / f"{leaf_name}.md"
2133
+ )
2134
+ legacy_path = Path.cwd() / ".claude" / "agents" / f"{leaf_name}.md"
2135
+ user_path = Path.home() / ".claude" / "agents" / f"{leaf_name}.md"
2136
+
2137
+ removed = False
2138
+ for path in [project_path, legacy_path, user_path]:
2139
+ if path.exists():
2140
+ path.unlink()
2141
+ removed = True
2142
+
2143
+ # Also remove from virtual deployment state
2144
+ deployment_state_paths = [
2145
+ Path.cwd() / ".claude" / "agents" / ".mpm_deployment_state",
2146
+ Path.home() / ".claude" / "agents" / ".mpm_deployment_state",
2147
+ ]
2148
+
2149
+ for state_path in deployment_state_paths:
2150
+ if state_path.exists():
2151
+ try:
2152
+ with state_path.open() as f:
2153
+ state = json.load(f)
2154
+
2155
+ # Remove agent from deployment state
2156
+ # Deployment state uses leaf names, not full hierarchical paths
2157
+ agents = state.get("last_check_results", {}).get(
2158
+ "agents", {}
2159
+ )
2160
+ if leaf_name in agents:
2161
+ del agents[leaf_name]
2162
+ removed = True
2163
+
2164
+ # Save updated state
2165
+ with state_path.open("w") as f:
2166
+ json.dump(state, f, indent=2)
2167
+ except (json.JSONDecodeError, KeyError) as e:
2168
+ # Log but don't fail - physical removal still counts
2169
+ self.logger.debug(
2170
+ f"Failed to update deployment state at {state_path}: {e}"
2171
+ )
2172
+
2173
+ if removed:
2174
+ remove_success += 1
2175
+ self.console.print(f"[green]✓ Removed: {agent_id}[/green]")
2176
+ else:
2177
+ remove_fail += 1
2178
+ self.console.print(f"[yellow]⚠ Not found: {agent_id}[/yellow]")
2179
+ except Exception as e:
2180
+ remove_fail += 1
2181
+ self.console.print(f"[red]✗ Failed to remove {agent_id}: {e}[/red]")
2182
+
2183
+ # Show summary
2184
+ self.console.print()
2185
+ if deploy_success > 0:
2186
+ self.console.print(
2187
+ f"[green]✓ Installed {deploy_success} agent(s)[/green]"
2188
+ )
2189
+ if deploy_fail > 0:
2190
+ self.console.print(
2191
+ f"[red]✗ Failed to install {deploy_fail} agent(s)[/red]"
2192
+ )
2193
+ if remove_success > 0:
2194
+ self.console.print(
2195
+ f"[green]✓ Removed {remove_success} agent(s)[/green]"
2196
+ )
2197
+ if remove_fail > 0:
2198
+ self.console.print(
2199
+ f"[red]✗ Failed to remove {remove_fail} agent(s)[/red]"
2200
+ )
2201
+
2202
+ Prompt.ask("\nPress Enter to continue")
2203
+ # Exit the loop after successful execution
2204
+ break
2205
+
2206
+ def _deploy_agents_preset(self) -> None:
2207
+ """Install agents using preset configuration."""
2208
+ try:
2209
+ from claude_mpm.services.agents.agent_preset_service import (
2210
+ AgentPresetService,
2211
+ )
2212
+ from claude_mpm.services.agents.git_source_manager import GitSourceManager
2213
+
2214
+ source_manager = GitSourceManager()
2215
+ preset_service = AgentPresetService(source_manager)
2216
+
2217
+ presets = preset_service.list_presets()
2218
+
2219
+ if not presets:
2220
+ self.console.print("[yellow]No presets available[/yellow]")
2221
+ Prompt.ask("\nPress Enter to continue")
2222
+ return
2223
+
2224
+ self.console.print("\n[bold white]═══ Available Presets ═══[/bold white]\n")
2225
+ for idx, preset in enumerate(presets, 1):
2226
+ self.console.print(f" {idx}. [white]{preset['name']}[/white]")
2227
+ self.console.print(f" {preset['description']}")
2228
+ self.console.print(f" [dim]Agents: {len(preset['agents'])}[/dim]\n")
2229
+
2230
+ selection = Prompt.ask("\nEnter preset number (or 'c' to cancel)")
2231
+ if selection.lower() == "c":
2232
+ return
2233
+
2234
+ idx = int(selection) - 1
2235
+ if 0 <= idx < len(presets):
2236
+ preset_name = presets[idx]["name"]
2237
+
2238
+ # Resolve and deploy preset
2239
+ resolution = preset_service.resolve_agents(preset_name)
2240
+
2241
+ if resolution.get("missing_agents"):
2242
+ self.console.print(
2243
+ f"[red]Missing agents: {len(resolution['missing_agents'])}[/red]"
2244
+ )
2245
+ for agent_id in resolution["missing_agents"]:
2246
+ self.console.print(f" • {agent_id}")
2247
+ Prompt.ask("\nPress Enter to continue")
2248
+ return
2249
+
2250
+ # Confirm installation
2251
+ self.console.print(
2252
+ f"\n[bold]Preset '{preset_name}' includes {len(resolution['agents'])} agents[/bold]"
2253
+ )
2254
+ if Confirm.ask("Install all agents?", default=True):
2255
+ installed = 0
2256
+ for agent in resolution["agents"]:
2257
+ # Convert dict to AgentConfig-like object for installation
2258
+ agent_config = AgentConfig(
2259
+ name=agent.get("agent_id", "unknown"),
2260
+ description=agent.get("metadata", {}).get(
2261
+ "description", ""
2262
+ ),
2263
+ dependencies=[],
2264
+ )
2265
+ agent_config.source_dict = agent
2266
+ agent_config.full_agent_id = agent.get("agent_id", "unknown")
2267
+
2268
+ if self._deploy_single_agent(agent_config, show_feedback=False):
2269
+ installed += 1
2270
+
2271
+ self.console.print(
2272
+ f"\n[green]✓ Installed {installed}/{len(resolution['agents'])} agents[/green]"
2273
+ )
2274
+
2275
+ Prompt.ask("\nPress Enter to continue")
2276
+ else:
2277
+ self.console.print("[red]Invalid selection[/red]")
2278
+ Prompt.ask("\nPress Enter to continue")
2279
+
2280
+ except Exception as e:
2281
+ self.console.print(f"[red]Error installing preset: {e}[/red]")
2282
+ self.logger.error(f"Preset installation failed: {e}", exc_info=True)
2283
+ Prompt.ask("\nPress Enter to continue")
2284
+
2285
+ def _select_recommended_agents(self, agents: List[AgentConfig]) -> None:
2286
+ """Select and install recommended agents based on toolchain detection."""
2287
+ if not agents:
2288
+ self.console.print("[yellow]No agents available[/yellow]")
2289
+ Prompt.ask("\nPress Enter to continue")
2290
+ return
2291
+
2292
+ self.console.clear()
2293
+ self.console.print(
2294
+ "\n[bold white]═══ Recommended Agents for This Project ═══[/bold white]\n"
2295
+ )
2296
+
2297
+ # Get recommended agent IDs
2298
+ try:
2299
+ recommended_agent_ids = self.recommendation_service.get_recommended_agents(
2300
+ str(self.project_dir)
2301
+ )
2302
+ except Exception as e:
2303
+ self.console.print(f"[red]Error detecting toolchain: {e}[/red]")
2304
+ self.logger.error(f"Toolchain detection failed: {e}", exc_info=True)
2305
+ Prompt.ask("\nPress Enter to continue")
2306
+ return
2307
+
2308
+ if not recommended_agent_ids:
2309
+ self.console.print("[yellow]No recommended agents found[/yellow]")
2310
+ Prompt.ask("\nPress Enter to continue")
2311
+ return
2312
+
2313
+ # Get detection summary
2314
+ try:
2315
+ summary = self.recommendation_service.get_detection_summary(
2316
+ str(self.project_dir)
2317
+ )
2318
+
2319
+ self.console.print("[bold]Detected Project Stack:[/bold]")
2320
+ if summary.get("detected_languages"):
2321
+ self.console.print(
2322
+ f" Languages: [cyan]{', '.join(summary['detected_languages'])}[/cyan]"
2323
+ )
2324
+ if summary.get("detected_frameworks"):
2325
+ self.console.print(
2326
+ f" Frameworks: [cyan]{', '.join(summary['detected_frameworks'])}[/cyan]"
2327
+ )
2328
+ self.console.print(
2329
+ f" Detection Quality: [{'green' if summary.get('detection_quality') == 'high' else 'yellow'}]{summary.get('detection_quality', 'unknown')}[/]"
2330
+ )
2331
+ self.console.print()
2332
+ except Exception:
2333
+ pass
2334
+
2335
+ # Build mapping: agent_id -> AgentConfig
2336
+ agent_map = {agent.name: agent for agent in agents}
2337
+
2338
+ # Also check leaf names for matching
2339
+ for agent in agents:
2340
+ leaf_name = agent.name.split("/")[-1] if "/" in agent.name else agent.name
2341
+ if leaf_name not in agent_map:
2342
+ agent_map[leaf_name] = agent
2343
+
2344
+ # Find matching agents from available agents
2345
+ matched_agents = []
2346
+ for recommended_id in recommended_agent_ids:
2347
+ # Try full path match first
2348
+ if recommended_id in agent_map:
2349
+ matched_agents.append(agent_map[recommended_id])
2350
+ else:
2351
+ # Try leaf name match
2352
+ recommended_leaf = (
2353
+ recommended_id.split("/")[-1]
2354
+ if "/" in recommended_id
2355
+ else recommended_id
2356
+ )
2357
+ if recommended_leaf in agent_map:
2358
+ matched_agents.append(agent_map[recommended_leaf])
2359
+
2360
+ if not matched_agents:
2361
+ self.console.print(
2362
+ "[yellow]No matching agents found in available sources[/yellow]"
2363
+ )
2364
+ Prompt.ask("\nPress Enter to continue")
2365
+ return
2366
+
2367
+ # Display recommended agents
2368
+ self.console.print(
2369
+ f"[bold]Recommended Agents ({len(matched_agents)}):[/bold]\n"
2370
+ )
2371
+
2372
+ from rich.table import Table
2373
+
2374
+ rec_table = Table(show_header=True, header_style="bold white")
2375
+ rec_table.add_column("#", style="dim", width=4)
2376
+ rec_table.add_column("Agent ID", style="cyan", width=40)
2377
+ rec_table.add_column("Status", style="white", width=15)
2378
+
2379
+ for idx, agent in enumerate(matched_agents, 1):
2380
+ is_installed = getattr(agent, "is_deployed", False)
2381
+ status = (
2382
+ "[green]Already Installed[/green]"
2383
+ if is_installed
2384
+ else "[yellow]Not Installed[/yellow]"
2385
+ )
2386
+ rec_table.add_row(str(idx), agent.name, status)
2387
+
2388
+ self.console.print(rec_table)
2389
+
2390
+ # Count how many need installation
2391
+ to_install = [a for a in matched_agents if not getattr(a, "is_deployed", False)]
2392
+ already_installed = len(matched_agents) - len(to_install)
2393
+
2394
+ self.console.print()
2395
+ if already_installed > 0:
2396
+ self.console.print(
2397
+ f"[green]✓ {already_installed} already installed[/green]"
2398
+ )
2399
+ if to_install:
2400
+ self.console.print(
2401
+ f"[yellow]⚠ {len(to_install)} need installation[/yellow]"
2402
+ )
2403
+ else:
2404
+ self.console.print(
2405
+ "[green]✓ All recommended agents are already installed![/green]"
2406
+ )
2407
+ Prompt.ask("\nPress Enter to continue")
2408
+ return
2409
+
2410
+ # Ask for confirmation
2411
+ self.console.print()
2412
+ if not Confirm.ask(
2413
+ f"Install {len(to_install)} recommended agent(s)?", default=True
2414
+ ):
2415
+ self.console.print("[yellow]Installation cancelled[/yellow]")
2416
+ Prompt.ask("\nPress Enter to continue")
2417
+ return
2418
+
2419
+ # Install agents
2420
+ self.console.print("\n[bold]Installing recommended agents...[/bold]\n")
2421
+
2422
+ success_count = 0
2423
+ fail_count = 0
2424
+
2425
+ for agent in to_install:
2426
+ try:
2427
+ if self._deploy_single_agent(agent, show_feedback=False):
2428
+ success_count += 1
2429
+ self.console.print(f"[green]✓ Installed: {agent.name}[/green]")
2430
+ else:
2431
+ fail_count += 1
2432
+ self.console.print(f"[red]✗ Failed: {agent.name}[/red]")
2433
+ except Exception as e:
2434
+ fail_count += 1
2435
+ self.console.print(f"[red]✗ Failed: {agent.name} - {e}[/red]")
2436
+
2437
+ # Show summary
2438
+ self.console.print()
2439
+ if success_count > 0:
2440
+ self.console.print(
2441
+ f"[green]✓ Successfully installed {success_count} agent(s)[/green]"
2442
+ )
2443
+ if fail_count > 0:
2444
+ self.console.print(f"[red]✗ Failed to install {fail_count} agent(s)[/red]")
2445
+
2446
+ Prompt.ask("\nPress Enter to continue")
2447
+
2448
+ def _deploy_single_agent(
2449
+ self, agent: AgentConfig, show_feedback: bool = True
2450
+ ) -> bool:
2451
+ """Install a single agent to the appropriate location."""
2452
+ try:
2453
+ # Check if this is a remote agent with source_dict
2454
+ source_dict = getattr(agent, "source_dict", None)
2455
+ full_agent_id = getattr(agent, "full_agent_id", agent.name)
2456
+
2457
+ if source_dict:
2458
+ # Deploy remote agent using its source file
2459
+ source_file = Path(source_dict.get("source_file", ""))
2460
+ if not source_file.exists():
2461
+ if show_feedback:
2462
+ self.console.print(
2463
+ f"[red]✗ Source file not found: {source_file}[/red]"
2464
+ )
2465
+ return False
2466
+
2467
+ # Determine target file name (use leaf name from hierarchical ID)
2468
+ if "/" in full_agent_id:
2469
+ target_name = full_agent_id.split("/")[-1] + ".md"
2470
+ else:
2471
+ target_name = full_agent_id + ".md"
2472
+
2473
+ # Deploy to project-level agents directory
2474
+ target_dir = self.project_dir / ".claude" / "agents"
2475
+ target_dir.mkdir(parents=True, exist_ok=True)
2476
+ target_file = target_dir / target_name
2477
+
2478
+ if show_feedback:
2479
+ self.console.print(
2480
+ f"\n[white]Installing {full_agent_id}...[/white]"
2481
+ )
2482
+
2483
+ # Copy the agent file
2484
+ import shutil
2485
+
2486
+ shutil.copy2(source_file, target_file)
2487
+
2488
+ if show_feedback:
2489
+ self.console.print(
2490
+ f"[green]✓ Successfully installed {full_agent_id} to {target_file}[/green]"
2491
+ )
2492
+ Prompt.ask("\nPress Enter to continue")
2493
+
2494
+ return True
2495
+ # Legacy local template installation (not implemented here)
2496
+ if show_feedback:
2497
+ self.console.print(
2498
+ "[yellow]Local template installation not yet implemented[/yellow]"
2499
+ )
2500
+ Prompt.ask("\nPress Enter to continue")
2501
+ return False
2502
+
2503
+ except Exception as e:
2504
+ if show_feedback:
2505
+ self.console.print(f"[red]Error installing agent: {e}[/red]")
2506
+ self.logger.error(f"Agent installation failed: {e}", exc_info=True)
2507
+ Prompt.ask("\nPress Enter to continue")
2508
+ return False
2509
+
2510
+ def _remove_agents(self, agents: List[AgentConfig]) -> None:
2511
+ """Remove installed agents."""
2512
+ # Filter to installed agents only
2513
+ installed = [a for a in agents if getattr(a, "is_deployed", False)]
2514
+
2515
+ if not installed:
2516
+ self.console.print("[yellow]No agents are currently installed[/yellow]")
2517
+ Prompt.ask("\nPress Enter to continue")
2518
+ return
2519
+
2520
+ self.console.print(f"\n[bold]Installed agents ({len(installed)}):[/bold]")
2521
+ for idx, agent in enumerate(installed, 1):
2522
+ raw_display_name = getattr(agent, "display_name", agent.name)
2523
+ display_name = self._format_display_name(raw_display_name)
2524
+ self.console.print(f" {idx}. {agent.name} - {display_name}")
2525
+
2526
+ selection = Prompt.ask("\nEnter agent number to remove (or 'c' to cancel)")
2527
+ if selection.lower() == "c":
2528
+ return
2529
+
2530
+ try:
2531
+ idx = int(selection) - 1
2532
+ if 0 <= idx < len(installed):
2533
+ agent = installed[idx]
2534
+ full_agent_id = getattr(agent, "full_agent_id", agent.name)
2535
+
2536
+ # Determine possible file names (hierarchical and leaf)
2537
+ file_names = [f"{full_agent_id}.md"]
2538
+ if "/" in full_agent_id:
2539
+ leaf_name = full_agent_id.split("/")[-1]
2540
+ file_names.append(f"{leaf_name}.md")
2541
+
2542
+ # Remove from both project and user directories
2543
+ removed = False
2544
+ project_agent_dir = Path.cwd() / ".claude-mpm" / "agents"
2545
+ user_agent_dir = Path.home() / ".claude" / "agents"
2546
+
2547
+ for file_name in file_names:
2548
+ project_file = project_agent_dir / file_name
2549
+ user_file = user_agent_dir / file_name
2550
+
2551
+ if project_file.exists():
2552
+ project_file.unlink()
2553
+ removed = True
2554
+ self.console.print(f"[green]✓ Removed {project_file}[/green]")
2555
+
2556
+ if user_file.exists():
2557
+ user_file.unlink()
2558
+ removed = True
2559
+ self.console.print(f"[green]✓ Removed {user_file}[/green]")
2560
+
2561
+ if removed:
2562
+ self.console.print(
2563
+ f"[green]✓ Successfully removed {full_agent_id}[/green]"
2564
+ )
2565
+ else:
2566
+ self.console.print("[yellow]Agent files not found[/yellow]")
2567
+
2568
+ Prompt.ask("\nPress Enter to continue")
2569
+ else:
2570
+ self.console.print("[red]Invalid selection[/red]")
2571
+ Prompt.ask("\nPress Enter to continue")
2572
+
2573
+ except (ValueError, IndexError):
2574
+ self.console.print("[red]Invalid selection[/red]")
2575
+ Prompt.ask("\nPress Enter to continue")
2576
+
2577
+ def _view_agent_details_enhanced(self, agents: List[AgentConfig]) -> None:
2578
+ """View detailed agent information with enhanced remote agent details."""
2579
+ if not agents:
2580
+ self.console.print("[yellow]No agents available[/yellow]")
2581
+ Prompt.ask("\nPress Enter to continue")
2582
+ return
2583
+
2584
+ self.console.print(f"\n[bold]Available agents ({len(agents)}):[/bold]")
2585
+ for idx, agent in enumerate(agents, 1):
2586
+ raw_display_name = getattr(agent, "display_name", agent.name)
2587
+ display_name = self._format_display_name(raw_display_name)
2588
+ self.console.print(f" {idx}. {agent.name} - {display_name}")
2589
+
2590
+ selection = Prompt.ask("\nEnter agent number to view (or 'c' to cancel)")
2591
+ if selection.lower() == "c":
2592
+ return
2593
+
2594
+ try:
2595
+ idx = int(selection) - 1
2596
+ if 0 <= idx < len(agents):
2597
+ agent = agents[idx]
2598
+
2599
+ self.console.clear()
2600
+ self.console.print("\n[bold white]═══ Agent Details ═══[/bold white]\n")
2601
+
2602
+ # Basic info
2603
+ self.console.print(f"[bold]ID:[/bold] {agent.name}")
2604
+ raw_display_name = getattr(agent, "display_name", "N/A")
2605
+ display_name = (
2606
+ self._format_display_name(raw_display_name)
2607
+ if raw_display_name != "N/A"
2608
+ else "N/A"
2609
+ )
2610
+ self.console.print(f"[bold]Name:[/bold] {display_name}")
2611
+ self.console.print(f"[bold]Description:[/bold] {agent.description}")
2612
+
2613
+ # Source info
2614
+ source_type = getattr(agent, "source_type", "local")
2615
+ self.console.print(f"[bold]Source Type:[/bold] {source_type}")
2616
+
2617
+ if source_type == "remote":
2618
+ source_dict = getattr(agent, "source_dict", {})
2619
+ category = source_dict.get("category", "N/A")
2620
+ source = source_dict.get("source", "N/A")
2621
+ version = source_dict.get("version", "N/A")
2622
+
2623
+ self.console.print(f"[bold]Category:[/bold] {category}")
2624
+ self.console.print(f"[bold]Source:[/bold] {source}")
2625
+ self.console.print(f"[bold]Version:[/bold] {version[:16]}...")
2626
+
2627
+ # Installation status
2628
+ is_installed = getattr(agent, "is_deployed", False)
2629
+ status = "Installed" if is_installed else "Available"
2630
+ self.console.print(f"[bold]Status:[/bold] {status}")
2631
+
2632
+ Prompt.ask("\nPress Enter to continue")
2633
+ else:
2634
+ self.console.print("[red]Invalid selection[/red]")
2635
+ Prompt.ask("\nPress Enter to continue")
2636
+
2637
+ except (ValueError, IndexError):
2638
+ self.console.print("[red]Invalid selection[/red]")
2639
+ Prompt.ask("\nPress Enter to continue")
2640
+
812
2641
 
813
2642
  def manage_configure(args) -> int:
814
2643
  """Main entry point for configuration management command.