claude-mpm 3.4.10__py3-none-any.whl → 5.4.85__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1062) hide show
  1. claude_mpm/BUILD_NUMBER +1 -0
  2. claude_mpm/VERSION +1 -0
  3. claude_mpm/__init__.py +50 -12
  4. claude_mpm/__main__.py +7 -2
  5. claude_mpm/agents/BASE_AGENT.md +164 -0
  6. claude_mpm/agents/BASE_ENGINEER.md +658 -0
  7. claude_mpm/agents/CLAUDE_MPM_FOUNDERS_OUTPUT_STYLE.md +405 -0
  8. claude_mpm/agents/CLAUDE_MPM_OUTPUT_STYLE.md +112 -0
  9. claude_mpm/agents/CLAUDE_MPM_TEACHER_OUTPUT_STYLE.md +186 -0
  10. claude_mpm/agents/MEMORY.md +72 -0
  11. claude_mpm/agents/PM_INSTRUCTIONS.md +1429 -0
  12. claude_mpm/agents/WORKFLOW.md +111 -0
  13. claude_mpm/agents/__init__.py +92 -80
  14. claude_mpm/agents/agent-template.yaml +83 -0
  15. claude_mpm/agents/agent_loader.py +560 -745
  16. claude_mpm/agents/agent_loader_integration.py +53 -55
  17. claude_mpm/agents/agents_metadata.py +186 -27
  18. claude_mpm/agents/async_agent_loader.py +436 -0
  19. claude_mpm/agents/base_agent.json +8 -4
  20. claude_mpm/agents/frontmatter_validator.py +754 -0
  21. claude_mpm/agents/system_agent_config.py +222 -155
  22. claude_mpm/agents/templates/README.md +465 -0
  23. claude_mpm/agents/templates/__init__.py +17 -13
  24. claude_mpm/agents/templates/circuit-breakers.md +1391 -0
  25. claude_mpm/agents/templates/context-management-examples.md +544 -0
  26. claude_mpm/agents/templates/git-file-tracking.md +584 -0
  27. claude_mpm/agents/templates/pm-examples.md +474 -0
  28. claude_mpm/agents/templates/pm-red-flags.md +310 -0
  29. claude_mpm/agents/templates/pr-workflow-examples.md +427 -0
  30. claude_mpm/agents/templates/research-gate-examples.md +669 -0
  31. claude_mpm/agents/templates/response-format.md +583 -0
  32. claude_mpm/agents/templates/structured-questions-examples.md +615 -0
  33. claude_mpm/agents/templates/ticket-completeness-examples.md +139 -0
  34. claude_mpm/agents/templates/ticketing-examples.md +277 -0
  35. claude_mpm/agents/templates/validation-templates.md +312 -0
  36. claude_mpm/cli/__init__.py +94 -128
  37. claude_mpm/cli/__main__.py +33 -0
  38. claude_mpm/cli/chrome_devtools_installer.py +175 -0
  39. claude_mpm/cli/commands/__init__.py +36 -12
  40. claude_mpm/cli/commands/agent_manager.py +1403 -0
  41. claude_mpm/cli/commands/agent_source.py +774 -0
  42. claude_mpm/cli/commands/agent_state_manager.py +335 -0
  43. claude_mpm/cli/commands/agents.py +2501 -168
  44. claude_mpm/cli/commands/agents_cleanup.py +210 -0
  45. claude_mpm/cli/commands/agents_discover.py +338 -0
  46. claude_mpm/cli/commands/agents_reconcile.py +197 -0
  47. claude_mpm/cli/commands/aggregate.py +540 -0
  48. claude_mpm/cli/commands/analyze.py +553 -0
  49. claude_mpm/cli/commands/analyze_code.py +528 -0
  50. claude_mpm/cli/commands/auto_configure.py +1053 -0
  51. claude_mpm/cli/commands/cleanup.py +588 -0
  52. claude_mpm/cli/commands/cleanup_orphaned_agents.py +150 -0
  53. claude_mpm/cli/commands/config.py +586 -0
  54. claude_mpm/cli/commands/configure.py +3253 -0
  55. claude_mpm/cli/commands/configure_agent_display.py +282 -0
  56. claude_mpm/cli/commands/configure_behavior_manager.py +204 -0
  57. claude_mpm/cli/commands/configure_hook_manager.py +225 -0
  58. claude_mpm/cli/commands/configure_models.py +18 -0
  59. claude_mpm/cli/commands/configure_navigation.py +184 -0
  60. claude_mpm/cli/commands/configure_paths.py +104 -0
  61. claude_mpm/cli/commands/configure_persistence.py +254 -0
  62. claude_mpm/cli/commands/configure_startup_manager.py +646 -0
  63. claude_mpm/cli/commands/configure_template_editor.py +497 -0
  64. claude_mpm/cli/commands/configure_validators.py +73 -0
  65. claude_mpm/cli/commands/dashboard.py +286 -0
  66. claude_mpm/cli/commands/debug.py +1386 -0
  67. claude_mpm/cli/commands/doctor.py +243 -0
  68. claude_mpm/cli/commands/hook_errors.py +277 -0
  69. claude_mpm/cli/commands/info.py +195 -74
  70. claude_mpm/cli/commands/local_deploy.py +534 -0
  71. claude_mpm/cli/commands/mcp.py +205 -0
  72. claude_mpm/cli/commands/mcp_command_router.py +161 -0
  73. claude_mpm/cli/commands/mcp_config.py +154 -0
  74. claude_mpm/cli/commands/mcp_config_commands.py +20 -0
  75. claude_mpm/cli/commands/mcp_external_commands.py +249 -0
  76. claude_mpm/cli/commands/mcp_install_commands.py +346 -0
  77. claude_mpm/cli/commands/mcp_pipx_config.py +208 -0
  78. claude_mpm/cli/commands/mcp_server_commands.py +155 -0
  79. claude_mpm/cli/commands/mcp_setup_external.py +868 -0
  80. claude_mpm/cli/commands/mcp_tool_commands.py +34 -0
  81. claude_mpm/cli/commands/memory.py +585 -846
  82. claude_mpm/cli/commands/monitor.py +228 -310
  83. claude_mpm/cli/commands/mpm_init/__init__.py +73 -0
  84. claude_mpm/cli/commands/mpm_init/core.py +759 -0
  85. claude_mpm/cli/commands/mpm_init/display.py +341 -0
  86. claude_mpm/cli/commands/mpm_init/git_activity.py +427 -0
  87. claude_mpm/cli/commands/mpm_init/knowledge_extractor.py +481 -0
  88. claude_mpm/cli/commands/mpm_init/modes.py +397 -0
  89. claude_mpm/cli/commands/mpm_init/prompts.py +722 -0
  90. claude_mpm/cli/commands/mpm_init_cli.py +396 -0
  91. claude_mpm/cli/commands/mpm_init_handler.py +195 -0
  92. claude_mpm/cli/commands/postmortem.py +401 -0
  93. claude_mpm/cli/commands/profile.py +276 -0
  94. claude_mpm/cli/commands/run.py +910 -488
  95. claude_mpm/cli/commands/search.py +458 -0
  96. claude_mpm/cli/commands/skill_source.py +694 -0
  97. claude_mpm/cli/commands/skills.py +1398 -0
  98. claude_mpm/cli/commands/summarize.py +413 -0
  99. claude_mpm/cli/commands/tickets.py +536 -53
  100. claude_mpm/cli/commands/uninstall.py +176 -0
  101. claude_mpm/cli/commands/upgrade.py +152 -0
  102. claude_mpm/cli/commands/verify.py +119 -0
  103. claude_mpm/cli/executor.py +298 -0
  104. claude_mpm/cli/helpers.py +105 -0
  105. claude_mpm/cli/interactive/__init__.py +31 -0
  106. claude_mpm/cli/interactive/agent_wizard.py +1927 -0
  107. claude_mpm/cli/interactive/questionary_styles.py +65 -0
  108. claude_mpm/cli/interactive/skill_selector.py +481 -0
  109. claude_mpm/cli/interactive/skills_wizard.py +491 -0
  110. claude_mpm/cli/parser.py +87 -563
  111. claude_mpm/cli/parsers/__init__.py +35 -0
  112. claude_mpm/cli/parsers/agent_manager_parser.py +393 -0
  113. claude_mpm/cli/parsers/agent_source_parser.py +171 -0
  114. claude_mpm/cli/parsers/agents_parser.py +575 -0
  115. claude_mpm/cli/parsers/analyze_code_parser.py +170 -0
  116. claude_mpm/cli/parsers/analyze_parser.py +135 -0
  117. claude_mpm/cli/parsers/auto_configure_parser.py +120 -0
  118. claude_mpm/cli/parsers/base_parser.py +649 -0
  119. claude_mpm/cli/parsers/config_parser.py +208 -0
  120. claude_mpm/cli/parsers/configure_parser.py +138 -0
  121. claude_mpm/cli/parsers/dashboard_parser.py +113 -0
  122. claude_mpm/cli/parsers/debug_parser.py +319 -0
  123. claude_mpm/cli/parsers/local_deploy_parser.py +227 -0
  124. claude_mpm/cli/parsers/mcp_parser.py +195 -0
  125. claude_mpm/cli/parsers/memory_parser.py +138 -0
  126. claude_mpm/cli/parsers/monitor_parser.py +142 -0
  127. claude_mpm/cli/parsers/mpm_init_parser.py +311 -0
  128. claude_mpm/cli/parsers/profile_parser.py +147 -0
  129. claude_mpm/cli/parsers/run_parser.py +157 -0
  130. claude_mpm/cli/parsers/search_parser.py +245 -0
  131. claude_mpm/cli/parsers/skill_source_parser.py +169 -0
  132. claude_mpm/cli/parsers/skills_parser.py +277 -0
  133. claude_mpm/cli/parsers/source_parser.py +138 -0
  134. claude_mpm/cli/parsers/tickets_parser.py +203 -0
  135. claude_mpm/cli/shared/__init__.py +40 -0
  136. claude_mpm/cli/shared/argument_patterns.py +205 -0
  137. claude_mpm/cli/shared/base_command.py +242 -0
  138. claude_mpm/cli/shared/error_handling.py +242 -0
  139. claude_mpm/cli/shared/output_formatters.py +241 -0
  140. claude_mpm/cli/startup.py +1578 -0
  141. claude_mpm/cli/startup_display.py +480 -0
  142. claude_mpm/cli/startup_logging.py +839 -0
  143. claude_mpm/cli/utils.py +136 -47
  144. claude_mpm/cli_module/__init__.py +6 -6
  145. claude_mpm/cli_module/args.py +188 -140
  146. claude_mpm/cli_module/commands.py +79 -70
  147. claude_mpm/cli_module/migration_example.py +42 -64
  148. claude_mpm/commands/__init__.py +14 -0
  149. claude_mpm/commands/mpm-config.md +28 -0
  150. claude_mpm/commands/mpm-doctor.md +20 -0
  151. claude_mpm/commands/mpm-help.md +20 -0
  152. claude_mpm/commands/mpm-init.md +120 -0
  153. claude_mpm/commands/mpm-monitor.md +31 -0
  154. claude_mpm/commands/mpm-organize.md +120 -0
  155. claude_mpm/commands/mpm-postmortem.md +21 -0
  156. claude_mpm/commands/mpm-session-resume.md +30 -0
  157. claude_mpm/commands/mpm-status.md +20 -0
  158. claude_mpm/commands/mpm-ticket-view.md +109 -0
  159. claude_mpm/commands/mpm-version.md +20 -0
  160. claude_mpm/commands/mpm.md +31 -0
  161. claude_mpm/config/__init__.py +42 -2
  162. claude_mpm/config/agent_config.py +402 -0
  163. claude_mpm/config/agent_presets.py +488 -0
  164. claude_mpm/config/agent_sources.py +352 -0
  165. claude_mpm/config/experimental_features.py +217 -0
  166. claude_mpm/config/model_config.py +428 -0
  167. claude_mpm/config/paths.py +258 -0
  168. claude_mpm/config/skill_presets.py +392 -0
  169. claude_mpm/config/skill_sources.py +590 -0
  170. claude_mpm/config/socketio_config.py +125 -83
  171. claude_mpm/constants.py +133 -22
  172. claude_mpm/core/__init__.py +62 -36
  173. claude_mpm/core/agent_name_normalizer.py +71 -73
  174. claude_mpm/core/agent_registry.py +385 -492
  175. claude_mpm/core/agent_session_manager.py +81 -70
  176. claude_mpm/core/api_validator.py +330 -0
  177. claude_mpm/core/base_service.py +159 -122
  178. claude_mpm/core/cache.py +560 -0
  179. claude_mpm/core/claude_runner.py +696 -916
  180. claude_mpm/core/config.py +613 -122
  181. claude_mpm/core/config_aliases.py +74 -73
  182. claude_mpm/core/config_constants.py +314 -0
  183. claude_mpm/core/constants.py +361 -0
  184. claude_mpm/core/container.py +646 -104
  185. claude_mpm/core/enums.py +452 -0
  186. claude_mpm/core/error_handler.py +623 -0
  187. claude_mpm/core/exceptions.py +536 -0
  188. claude_mpm/core/factories.py +105 -109
  189. claude_mpm/core/file_utils.py +764 -0
  190. claude_mpm/core/framework/__init__.py +25 -0
  191. claude_mpm/core/framework/formatters/__init__.py +11 -0
  192. claude_mpm/core/framework/formatters/capability_generator.py +367 -0
  193. claude_mpm/core/framework/formatters/content_formatter.py +278 -0
  194. claude_mpm/core/framework/formatters/context_generator.py +185 -0
  195. claude_mpm/core/framework/loaders/__init__.py +13 -0
  196. claude_mpm/core/framework/loaders/agent_loader.py +213 -0
  197. claude_mpm/core/framework/loaders/file_loader.py +176 -0
  198. claude_mpm/core/framework/loaders/instruction_loader.py +222 -0
  199. claude_mpm/core/framework/loaders/packaged_loader.py +232 -0
  200. claude_mpm/core/framework/processors/__init__.py +11 -0
  201. claude_mpm/core/framework/processors/memory_processor.py +230 -0
  202. claude_mpm/core/framework/processors/metadata_processor.py +146 -0
  203. claude_mpm/core/framework/processors/template_processor.py +244 -0
  204. claude_mpm/core/framework_loader.py +485 -414
  205. claude_mpm/core/hook_error_memory.py +381 -0
  206. claude_mpm/core/hook_manager.py +246 -86
  207. claude_mpm/core/hook_performance_config.py +147 -0
  208. claude_mpm/core/injectable_service.py +72 -63
  209. claude_mpm/core/instruction_reinforcement_hook.py +267 -0
  210. claude_mpm/core/interactive_session.py +670 -0
  211. claude_mpm/core/interfaces.py +570 -164
  212. claude_mpm/core/lazy.py +467 -0
  213. claude_mpm/core/log_manager.py +707 -0
  214. claude_mpm/core/logger.py +295 -134
  215. claude_mpm/core/logging_config.py +474 -0
  216. claude_mpm/core/logging_utils.py +520 -0
  217. claude_mpm/core/minimal_framework_loader.py +24 -22
  218. claude_mpm/core/mixins.py +30 -29
  219. claude_mpm/core/oneshot_session.py +594 -0
  220. claude_mpm/core/optimized_agent_loader.py +479 -0
  221. claude_mpm/core/optimized_startup.py +554 -0
  222. claude_mpm/core/output_style_manager.py +491 -0
  223. claude_mpm/core/pm_hook_interceptor.py +197 -82
  224. claude_mpm/core/protocols/__init__.py +23 -0
  225. claude_mpm/core/protocols/runner_protocol.py +103 -0
  226. claude_mpm/core/protocols/session_protocol.py +131 -0
  227. claude_mpm/core/service_registry.py +153 -116
  228. claude_mpm/core/session_manager.py +179 -64
  229. claude_mpm/core/shared/__init__.py +17 -0
  230. claude_mpm/core/shared/config_loader.py +326 -0
  231. claude_mpm/core/shared/path_resolver.py +281 -0
  232. claude_mpm/core/shared/singleton_manager.py +221 -0
  233. claude_mpm/core/socketio_pool.py +400 -137
  234. claude_mpm/core/system_context.py +38 -0
  235. claude_mpm/core/tool_access_control.py +64 -57
  236. claude_mpm/core/types.py +307 -0
  237. claude_mpm/core/typing_utils.py +553 -0
  238. claude_mpm/core/unified_agent_registry.py +969 -0
  239. claude_mpm/core/unified_config.py +612 -0
  240. claude_mpm/core/unified_paths.py +958 -0
  241. claude_mpm/dashboard/__init__.py +12 -0
  242. claude_mpm/dashboard/api/simple_directory.py +261 -0
  243. claude_mpm/dashboard/static/svelte-build/_app/env.js +1 -0
  244. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.DWzvg0-y.css +1 -0
  245. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.ThTw9_ym.css +1 -0
  246. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/4TdZjIqw.js +1 -0
  247. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/5shd3_w0.js +24 -0
  248. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B0uc0UOD.js +36 -0
  249. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B7RN905-.js +1 -0
  250. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/B7xVLGWV.js +2 -0
  251. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BIF9m_hv.js +61 -0
  252. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BKjSRqUr.js +1 -0
  253. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BPYeabCQ.js +1 -0
  254. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BQaXIfA_.js +331 -0
  255. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BSNlmTZj.js +1 -0
  256. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Be7GpZd6.js +7 -0
  257. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Bh0LDWpI.js +145 -0
  258. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BofRWZRR.js +10 -0
  259. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BovzEFCE.js +30 -0
  260. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C30mlcqg.js +165 -0
  261. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C4B-KCzX.js +1 -0
  262. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C4JcI4KD.js +122 -0
  263. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CBBdVcY8.js +1 -0
  264. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CDuw-vjf.js +1 -0
  265. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C_Usid8X.js +15 -0
  266. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cfqx1Qun.js +10 -0
  267. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CiIAseT4.js +128 -0
  268. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CmKTTxBW.js +1 -0
  269. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CnA0NrzZ.js +1 -0
  270. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cs_tUR18.js +24 -0
  271. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Cu_Erd72.js +261 -0
  272. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CyWMqx4W.js +43 -0
  273. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CzZX-COe.js +220 -0
  274. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CzeYkLYB.js +65 -0
  275. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D3k0OPJN.js +4 -0
  276. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/D9lljYKQ.js +1 -0
  277. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DGkLK5U1.js +267 -0
  278. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DI7hHRFL.js +1 -0
  279. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DLVjFsZ3.js +139 -0
  280. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DUrLdbGD.js +89 -0
  281. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DVp1hx9R.js +1 -0
  282. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DY1XQ8fi.js +2 -0
  283. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DZX00Y4g.js +1 -0
  284. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Da0KfYnO.js +1 -0
  285. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DaimHw_p.js +68 -0
  286. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dfy6j1xT.js +323 -0
  287. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dhb8PKl3.js +1 -0
  288. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dle-35c7.js +64 -0
  289. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DmxopI1J.js +1 -0
  290. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/DwBR2MJi.js +60 -0
  291. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/GYwsonyD.js +1 -0
  292. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Gi6I4Gst.js +1 -0
  293. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/NqQ1dWOy.js +1 -0
  294. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/RJiighC3.js +1 -0
  295. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Vzk33B_K.js +2 -0
  296. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/ZGh7QtNv.js +7 -0
  297. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/bT1r9zLR.js +1 -0
  298. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/bTOqqlTd.js +1 -0
  299. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/eNVUfhuA.js +1 -0
  300. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/iEWssX7S.js +162 -0
  301. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/sQeU3Y1z.js +1 -0
  302. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/uuIeMWc-.js +1 -0
  303. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/app.D6-I5TpK.js +2 -0
  304. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.NWzMBYRp.js +1 -0
  305. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/0.m1gL8KXf.js +1 -0
  306. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/1.CgNOuw-d.js +1 -0
  307. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.C0GcWctS.js +1 -0
  308. claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -0
  309. claude_mpm/dashboard/static/svelte-build/favicon.svg +7 -0
  310. claude_mpm/dashboard/static/svelte-build/index.html +36 -0
  311. claude_mpm/dashboard-svelte/node_modules/katex/src/fonts/generate_fonts.py +58 -0
  312. claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/extract_tfms.py +114 -0
  313. claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/extract_ttfs.py +122 -0
  314. claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/format_json.py +28 -0
  315. claude_mpm/dashboard-svelte/node_modules/katex/src/metrics/parse_tfm.py +211 -0
  316. claude_mpm/experimental/__init__.py +10 -0
  317. claude_mpm/experimental/cli_enhancements.py +104 -89
  318. claude_mpm/generators/__init__.py +1 -1
  319. claude_mpm/generators/agent_profile_generator.py +76 -66
  320. claude_mpm/hooks/__init__.py +37 -1
  321. claude_mpm/hooks/base_hook.py +37 -32
  322. claude_mpm/hooks/claude_hooks/__init__.py +1 -1
  323. claude_mpm/hooks/claude_hooks/connection_pool.py +250 -0
  324. claude_mpm/hooks/claude_hooks/correlation_manager.py +60 -0
  325. claude_mpm/hooks/claude_hooks/event_handlers.py +888 -0
  326. claude_mpm/hooks/claude_hooks/hook_handler.py +652 -875
  327. claude_mpm/hooks/claude_hooks/hook_wrapper.sh +10 -7
  328. claude_mpm/hooks/claude_hooks/installer.py +806 -0
  329. claude_mpm/hooks/claude_hooks/memory_integration.py +249 -0
  330. claude_mpm/hooks/claude_hooks/response_tracking.py +412 -0
  331. claude_mpm/hooks/claude_hooks/services/__init__.py +15 -0
  332. claude_mpm/hooks/claude_hooks/services/connection_manager.py +229 -0
  333. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +254 -0
  334. claude_mpm/hooks/claude_hooks/services/duplicate_detector.py +106 -0
  335. claude_mpm/hooks/claude_hooks/services/state_manager.py +284 -0
  336. claude_mpm/hooks/claude_hooks/services/subagent_processor.py +374 -0
  337. claude_mpm/hooks/claude_hooks/tool_analysis.py +224 -0
  338. claude_mpm/hooks/failure_learning/__init__.py +54 -0
  339. claude_mpm/hooks/failure_learning/failure_detection_hook.py +230 -0
  340. claude_mpm/hooks/failure_learning/fix_detection_hook.py +212 -0
  341. claude_mpm/hooks/failure_learning/learning_extraction_hook.py +281 -0
  342. claude_mpm/hooks/instruction_reinforcement.py +301 -0
  343. claude_mpm/hooks/kuzu_enrichment_hook.py +263 -0
  344. claude_mpm/hooks/kuzu_memory_hook.py +386 -0
  345. claude_mpm/hooks/kuzu_response_hook.py +179 -0
  346. claude_mpm/hooks/memory_integration_hook.py +201 -107
  347. claude_mpm/hooks/session_resume_hook.py +121 -0
  348. claude_mpm/hooks/templates/pre_tool_use_simple.py +78 -0
  349. claude_mpm/hooks/templates/pre_tool_use_template.py +323 -0
  350. claude_mpm/hooks/tool_call_interceptor.py +92 -76
  351. claude_mpm/hooks/validation_hooks.py +62 -54
  352. claude_mpm/init.py +518 -83
  353. claude_mpm/models/__init__.py +9 -9
  354. claude_mpm/models/agent_definition.py +40 -23
  355. claude_mpm/models/agent_session.py +538 -0
  356. claude_mpm/models/git_repository.py +198 -0
  357. claude_mpm/models/resume_log.py +340 -0
  358. claude_mpm/schemas/__init__.py +12 -0
  359. claude_mpm/scripts/__init__.py +15 -0
  360. claude_mpm/scripts/claude-hook-handler.sh +227 -0
  361. claude_mpm/scripts/launch_monitor.py +165 -0
  362. claude_mpm/scripts/mpm_doctor.py +322 -0
  363. claude_mpm/scripts/socketio_daemon.py +189 -200
  364. claude_mpm/scripts/start_activity_logging.py +91 -0
  365. claude_mpm/services/__init__.py +208 -39
  366. claude_mpm/services/agent_capabilities_service.py +266 -0
  367. claude_mpm/services/agents/__init__.py +89 -0
  368. claude_mpm/services/agents/agent_builder.py +514 -0
  369. claude_mpm/services/agents/agent_preset_service.py +238 -0
  370. claude_mpm/services/agents/agent_recommendation_service.py +278 -0
  371. claude_mpm/services/agents/agent_review_service.py +280 -0
  372. claude_mpm/services/agents/agent_selection_service.py +484 -0
  373. claude_mpm/services/agents/auto_config_manager.py +796 -0
  374. claude_mpm/services/agents/auto_deploy_index_parser.py +569 -0
  375. claude_mpm/services/agents/cache_git_manager.py +621 -0
  376. claude_mpm/services/agents/deployment/__init__.py +21 -0
  377. claude_mpm/services/agents/deployment/agent_config_provider.py +410 -0
  378. claude_mpm/services/agents/deployment/agent_configuration_manager.py +358 -0
  379. claude_mpm/services/agents/deployment/agent_definition_factory.py +80 -0
  380. claude_mpm/services/agents/deployment/agent_deployment.py +1037 -0
  381. claude_mpm/services/agents/deployment/agent_discovery_service.py +546 -0
  382. claude_mpm/services/agents/deployment/agent_environment_manager.py +288 -0
  383. claude_mpm/services/agents/deployment/agent_filesystem_manager.py +383 -0
  384. claude_mpm/services/agents/deployment/agent_format_converter.py +505 -0
  385. claude_mpm/services/agents/deployment/agent_frontmatter_validator.py +160 -0
  386. claude_mpm/services/agents/deployment/agent_lifecycle_manager.py +957 -0
  387. claude_mpm/services/agents/deployment/agent_metrics_collector.py +273 -0
  388. claude_mpm/services/agents/deployment/agent_operation_service.py +573 -0
  389. claude_mpm/services/agents/deployment/agent_record_service.py +418 -0
  390. claude_mpm/services/agents/deployment/agent_restore_handler.py +84 -0
  391. claude_mpm/services/agents/deployment/agent_state_service.py +381 -0
  392. claude_mpm/services/agents/deployment/agent_template_builder.py +1377 -0
  393. claude_mpm/services/agents/deployment/agent_validator.py +376 -0
  394. claude_mpm/services/agents/deployment/agent_version_manager.py +322 -0
  395. claude_mpm/services/{agent_versioning.py → agents/deployment/agent_versioning.py} +10 -13
  396. claude_mpm/services/agents/deployment/agents_directory_resolver.py +149 -0
  397. claude_mpm/services/agents/deployment/async_agent_deployment.py +768 -0
  398. claude_mpm/services/agents/deployment/base_agent_locator.py +132 -0
  399. claude_mpm/services/agents/deployment/config/__init__.py +13 -0
  400. claude_mpm/services/agents/deployment/config/deployment_config.py +181 -0
  401. claude_mpm/services/agents/deployment/config/deployment_config_manager.py +200 -0
  402. claude_mpm/services/agents/deployment/deployment_config_loader.py +178 -0
  403. claude_mpm/services/agents/deployment/deployment_reconciler.py +577 -0
  404. claude_mpm/services/agents/deployment/deployment_results_manager.py +185 -0
  405. claude_mpm/services/agents/deployment/deployment_type_detector.py +120 -0
  406. claude_mpm/services/agents/deployment/deployment_wrapper.py +129 -0
  407. claude_mpm/services/agents/deployment/facade/__init__.py +18 -0
  408. claude_mpm/services/agents/deployment/facade/async_deployment_executor.py +159 -0
  409. claude_mpm/services/agents/deployment/facade/deployment_executor.py +70 -0
  410. claude_mpm/services/agents/deployment/facade/deployment_facade.py +269 -0
  411. claude_mpm/services/agents/deployment/facade/sync_deployment_executor.py +178 -0
  412. claude_mpm/services/agents/deployment/interface_adapter.py +226 -0
  413. claude_mpm/services/agents/deployment/lifecycle_health_checker.py +85 -0
  414. claude_mpm/services/agents/deployment/lifecycle_performance_tracker.py +100 -0
  415. claude_mpm/services/agents/deployment/local_template_deployment.py +362 -0
  416. claude_mpm/services/agents/deployment/multi_source_deployment_service.py +1478 -0
  417. claude_mpm/services/agents/deployment/pipeline/__init__.py +32 -0
  418. claude_mpm/services/agents/deployment/pipeline/pipeline_builder.py +158 -0
  419. claude_mpm/services/agents/deployment/pipeline/pipeline_context.py +162 -0
  420. claude_mpm/services/agents/deployment/pipeline/pipeline_executor.py +169 -0
  421. claude_mpm/services/agents/deployment/pipeline/steps/__init__.py +19 -0
  422. claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +240 -0
  423. claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +110 -0
  424. claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +80 -0
  425. claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +92 -0
  426. claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +101 -0
  427. claude_mpm/services/agents/deployment/processors/__init__.py +15 -0
  428. claude_mpm/services/agents/deployment/processors/agent_deployment_context.py +102 -0
  429. claude_mpm/services/agents/deployment/processors/agent_deployment_result.py +235 -0
  430. claude_mpm/services/agents/deployment/processors/agent_processor.py +269 -0
  431. claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +311 -0
  432. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +862 -0
  433. claude_mpm/services/agents/deployment/results/__init__.py +13 -0
  434. claude_mpm/services/agents/deployment/results/deployment_metrics.py +200 -0
  435. claude_mpm/services/agents/deployment/results/deployment_result_builder.py +249 -0
  436. claude_mpm/services/agents/deployment/single_agent_deployer.py +315 -0
  437. claude_mpm/services/agents/deployment/startup_reconciliation.py +138 -0
  438. claude_mpm/services/agents/deployment/strategies/__init__.py +25 -0
  439. claude_mpm/services/agents/deployment/strategies/base_strategy.py +113 -0
  440. claude_mpm/services/agents/deployment/strategies/project_strategy.py +148 -0
  441. claude_mpm/services/agents/deployment/strategies/strategy_selector.py +117 -0
  442. claude_mpm/services/agents/deployment/strategies/system_strategy.py +131 -0
  443. claude_mpm/services/agents/deployment/strategies/user_strategy.py +130 -0
  444. claude_mpm/services/agents/deployment/system_instructions_deployer.py +228 -0
  445. claude_mpm/services/agents/deployment/validation/__init__.py +21 -0
  446. claude_mpm/services/agents/deployment/validation/agent_validator.py +323 -0
  447. claude_mpm/services/agents/deployment/validation/deployment_validator.py +238 -0
  448. claude_mpm/services/agents/deployment/validation/template_validator.py +319 -0
  449. claude_mpm/services/agents/deployment/validation/validation_result.py +214 -0
  450. claude_mpm/services/agents/git_source_manager.py +682 -0
  451. claude_mpm/services/agents/loading/__init__.py +11 -0
  452. claude_mpm/services/{agent_profile_loader.py → agents/loading/agent_profile_loader.py} +306 -228
  453. claude_mpm/services/{base_agent_manager.py → agents/loading/base_agent_manager.py} +106 -91
  454. claude_mpm/services/agents/loading/framework_agent_loader.py +433 -0
  455. claude_mpm/services/agents/local_template_manager.py +784 -0
  456. claude_mpm/services/agents/management/__init__.py +9 -0
  457. claude_mpm/services/{agent_capabilities_generator.py → agents/management/agent_capabilities_generator.py} +92 -69
  458. claude_mpm/services/{agent_management_service.py → agents/management/agent_management_service.py} +219 -168
  459. claude_mpm/services/agents/memory/__init__.py +22 -0
  460. claude_mpm/services/agents/memory/agent_memory_manager.py +784 -0
  461. claude_mpm/services/{agent_persistence_service.py → agents/memory/agent_persistence_service.py} +20 -18
  462. claude_mpm/services/agents/memory/content_manager.py +470 -0
  463. claude_mpm/services/agents/memory/memory_categorization_service.py +167 -0
  464. claude_mpm/services/agents/memory/memory_file_service.py +129 -0
  465. claude_mpm/services/agents/memory/memory_format_service.py +201 -0
  466. claude_mpm/services/agents/memory/memory_limits_service.py +101 -0
  467. claude_mpm/services/agents/memory/template_generator.py +83 -0
  468. claude_mpm/services/agents/observers.py +547 -0
  469. claude_mpm/services/agents/recommender.py +617 -0
  470. claude_mpm/services/agents/registry/__init__.py +30 -0
  471. claude_mpm/services/agents/registry/deployed_agent_discovery.py +273 -0
  472. claude_mpm/services/{agent_modification_tracker.py → agents/registry/modification_tracker.py} +370 -295
  473. claude_mpm/services/agents/single_tier_deployment_service.py +696 -0
  474. claude_mpm/services/agents/sources/__init__.py +13 -0
  475. claude_mpm/services/agents/sources/agent_sync_state.py +516 -0
  476. claude_mpm/services/agents/sources/git_source_sync_service.py +1205 -0
  477. claude_mpm/services/agents/startup_sync.py +262 -0
  478. claude_mpm/services/agents/toolchain_detector.py +478 -0
  479. claude_mpm/services/analysis/__init__.py +35 -0
  480. claude_mpm/services/analysis/clone_detector.py +1030 -0
  481. claude_mpm/services/analysis/postmortem_reporter.py +474 -0
  482. claude_mpm/services/analysis/postmortem_service.py +765 -0
  483. claude_mpm/services/async_session_logger.py +665 -0
  484. claude_mpm/services/claude_session_logger.py +321 -0
  485. claude_mpm/services/cli/__init__.py +18 -0
  486. claude_mpm/services/cli/agent_cleanup_service.py +408 -0
  487. claude_mpm/services/cli/agent_dependency_service.py +395 -0
  488. claude_mpm/services/cli/agent_listing_service.py +463 -0
  489. claude_mpm/services/cli/agent_output_formatter.py +605 -0
  490. claude_mpm/services/cli/agent_validation_service.py +590 -0
  491. claude_mpm/services/cli/memory_crud_service.py +622 -0
  492. claude_mpm/services/cli/memory_output_formatter.py +604 -0
  493. claude_mpm/services/cli/resume_service.py +617 -0
  494. claude_mpm/services/cli/session_manager.py +604 -0
  495. claude_mpm/services/cli/session_pause_manager.py +504 -0
  496. claude_mpm/services/cli/session_resume_helper.py +372 -0
  497. claude_mpm/services/cli/startup_checker.py +362 -0
  498. claude_mpm/services/cli/unified_dashboard_manager.py +439 -0
  499. claude_mpm/services/command_deployment_service.py +446 -0
  500. claude_mpm/services/command_handler_service.py +221 -0
  501. claude_mpm/services/communication/__init__.py +22 -0
  502. claude_mpm/services/core/__init__.py +108 -0
  503. claude_mpm/services/core/base.py +269 -0
  504. claude_mpm/services/core/cache_manager.py +309 -0
  505. claude_mpm/services/core/interfaces/__init__.py +273 -0
  506. claude_mpm/services/core/interfaces/agent.py +514 -0
  507. claude_mpm/services/core/interfaces/communication.py +316 -0
  508. claude_mpm/services/core/interfaces/health.py +169 -0
  509. claude_mpm/services/core/interfaces/infrastructure.py +357 -0
  510. claude_mpm/services/core/interfaces/model.py +281 -0
  511. claude_mpm/services/core/interfaces/process.py +372 -0
  512. claude_mpm/services/core/interfaces/project.py +121 -0
  513. claude_mpm/services/core/interfaces/restart.py +307 -0
  514. claude_mpm/services/core/interfaces/service.py +405 -0
  515. claude_mpm/services/core/interfaces/stability.py +260 -0
  516. claude_mpm/services/core/interfaces.py +81 -0
  517. claude_mpm/services/core/memory_manager.py +682 -0
  518. claude_mpm/services/core/models/__init__.py +70 -0
  519. claude_mpm/services/core/models/agent_config.py +384 -0
  520. claude_mpm/services/core/models/health.py +162 -0
  521. claude_mpm/services/core/models/process.py +239 -0
  522. claude_mpm/services/core/models/restart.py +302 -0
  523. claude_mpm/services/core/models/stability.py +264 -0
  524. claude_mpm/services/core/models/toolchain.py +306 -0
  525. claude_mpm/services/core/path_resolver.py +517 -0
  526. claude_mpm/services/core/service_container.py +520 -0
  527. claude_mpm/services/core/service_interfaces.py +436 -0
  528. claude_mpm/services/diagnostics/__init__.py +18 -0
  529. claude_mpm/services/diagnostics/checks/__init__.py +38 -0
  530. claude_mpm/services/diagnostics/checks/agent_check.py +370 -0
  531. claude_mpm/services/diagnostics/checks/agent_sources_check.py +577 -0
  532. claude_mpm/services/diagnostics/checks/base_check.py +60 -0
  533. claude_mpm/services/diagnostics/checks/claude_code_check.py +270 -0
  534. claude_mpm/services/diagnostics/checks/common_issues_check.py +363 -0
  535. claude_mpm/services/diagnostics/checks/configuration_check.py +306 -0
  536. claude_mpm/services/diagnostics/checks/filesystem_check.py +233 -0
  537. claude_mpm/services/diagnostics/checks/installation_check.py +520 -0
  538. claude_mpm/services/diagnostics/checks/instructions_check.py +415 -0
  539. claude_mpm/services/diagnostics/checks/mcp_check.py +330 -0
  540. claude_mpm/services/diagnostics/checks/mcp_services_check.py +1058 -0
  541. claude_mpm/services/diagnostics/checks/monitor_check.py +281 -0
  542. claude_mpm/services/diagnostics/checks/skill_sources_check.py +587 -0
  543. claude_mpm/services/diagnostics/checks/startup_log_check.py +319 -0
  544. claude_mpm/services/diagnostics/diagnostic_runner.py +286 -0
  545. claude_mpm/services/diagnostics/doctor_reporter.py +578 -0
  546. claude_mpm/services/diagnostics/models.py +138 -0
  547. claude_mpm/services/event_aggregator.py +582 -0
  548. claude_mpm/services/event_bus/__init__.py +18 -0
  549. claude_mpm/services/event_bus/config.py +186 -0
  550. claude_mpm/services/event_bus/direct_relay.py +312 -0
  551. claude_mpm/services/event_bus/event_bus.py +396 -0
  552. claude_mpm/services/event_bus/relay.py +326 -0
  553. claude_mpm/services/events/__init__.py +44 -0
  554. claude_mpm/services/events/consumers/__init__.py +18 -0
  555. claude_mpm/services/events/consumers/dead_letter.py +306 -0
  556. claude_mpm/services/events/consumers/logging.py +184 -0
  557. claude_mpm/services/events/consumers/metrics.py +241 -0
  558. claude_mpm/services/events/consumers/socketio.py +377 -0
  559. claude_mpm/services/events/core.py +480 -0
  560. claude_mpm/services/events/interfaces.py +214 -0
  561. claude_mpm/services/events/producers/__init__.py +14 -0
  562. claude_mpm/services/events/producers/hook.py +269 -0
  563. claude_mpm/services/events/producers/system.py +329 -0
  564. claude_mpm/services/exceptions.py +433 -353
  565. claude_mpm/services/framework_claude_md_generator/__init__.py +81 -80
  566. claude_mpm/services/framework_claude_md_generator/content_assembler.py +74 -67
  567. claude_mpm/services/framework_claude_md_generator/content_validator.py +66 -62
  568. claude_mpm/services/framework_claude_md_generator/deployment_manager.py +82 -60
  569. claude_mpm/services/framework_claude_md_generator/section_generators/__init__.py +36 -37
  570. claude_mpm/services/framework_claude_md_generator/section_generators/agents.py +41 -40
  571. claude_mpm/services/framework_claude_md_generator/section_generators/claude_pm_init.py +15 -15
  572. claude_mpm/services/framework_claude_md_generator/section_generators/core_responsibilities.py +5 -4
  573. claude_mpm/services/framework_claude_md_generator/section_generators/delegation_constraints.py +4 -3
  574. claude_mpm/services/framework_claude_md_generator/section_generators/environment_config.py +4 -3
  575. claude_mpm/services/framework_claude_md_generator/section_generators/footer.py +6 -5
  576. claude_mpm/services/framework_claude_md_generator/section_generators/header.py +8 -7
  577. claude_mpm/services/framework_claude_md_generator/section_generators/orchestration_principles.py +5 -4
  578. claude_mpm/services/framework_claude_md_generator/section_generators/role_designation.py +6 -5
  579. claude_mpm/services/framework_claude_md_generator/section_generators/subprocess_validation.py +9 -8
  580. claude_mpm/services/framework_claude_md_generator/section_generators/todo_task_tools.py +26 -30
  581. claude_mpm/services/framework_claude_md_generator/section_generators/troubleshooting.py +6 -5
  582. claude_mpm/services/framework_claude_md_generator/section_manager.py +28 -27
  583. claude_mpm/services/framework_claude_md_generator/version_manager.py +31 -30
  584. claude_mpm/services/git/__init__.py +21 -0
  585. claude_mpm/services/git/git_operations_service.py +579 -0
  586. claude_mpm/services/github/__init__.py +21 -0
  587. claude_mpm/services/github/github_cli_service.py +397 -0
  588. claude_mpm/services/hook_installer_service.py +506 -0
  589. claude_mpm/services/hook_service.py +159 -111
  590. claude_mpm/services/infrastructure/__init__.py +52 -0
  591. claude_mpm/services/infrastructure/context_preservation.py +569 -0
  592. claude_mpm/services/infrastructure/daemon_manager.py +279 -0
  593. claude_mpm/services/infrastructure/logging.py +209 -0
  594. claude_mpm/services/infrastructure/monitoring/__init__.py +39 -0
  595. claude_mpm/services/infrastructure/monitoring/aggregator.py +432 -0
  596. claude_mpm/services/infrastructure/monitoring/base.py +122 -0
  597. claude_mpm/services/infrastructure/monitoring/legacy.py +203 -0
  598. claude_mpm/services/infrastructure/monitoring/network.py +219 -0
  599. claude_mpm/services/infrastructure/monitoring/process.py +343 -0
  600. claude_mpm/services/infrastructure/monitoring/resources.py +244 -0
  601. claude_mpm/services/infrastructure/monitoring/service.py +368 -0
  602. claude_mpm/services/infrastructure/monitoring.py +71 -0
  603. claude_mpm/services/infrastructure/resume_log_generator.py +439 -0
  604. claude_mpm/services/instructions/__init__.py +9 -0
  605. claude_mpm/services/instructions/instruction_cache_service.py +374 -0
  606. claude_mpm/services/local_ops/__init__.py +155 -0
  607. claude_mpm/services/local_ops/crash_detector.py +257 -0
  608. claude_mpm/services/local_ops/health_checks/__init__.py +26 -0
  609. claude_mpm/services/local_ops/health_checks/http_check.py +224 -0
  610. claude_mpm/services/local_ops/health_checks/process_check.py +236 -0
  611. claude_mpm/services/local_ops/health_checks/resource_check.py +255 -0
  612. claude_mpm/services/local_ops/health_manager.py +427 -0
  613. claude_mpm/services/local_ops/log_monitor.py +396 -0
  614. claude_mpm/services/local_ops/memory_leak_detector.py +294 -0
  615. claude_mpm/services/local_ops/process_manager.py +595 -0
  616. claude_mpm/services/local_ops/resource_monitor.py +331 -0
  617. claude_mpm/services/local_ops/restart_manager.py +401 -0
  618. claude_mpm/services/local_ops/restart_policy.py +387 -0
  619. claude_mpm/services/local_ops/state_manager.py +372 -0
  620. claude_mpm/services/local_ops/unified_manager.py +600 -0
  621. claude_mpm/services/mcp_config_manager.py +1542 -0
  622. claude_mpm/services/mcp_service_verifier.py +732 -0
  623. claude_mpm/services/memory/__init__.py +19 -0
  624. claude_mpm/services/{memory_builder.py → memory/builder.py} +465 -373
  625. claude_mpm/services/memory/cache/__init__.py +14 -0
  626. claude_mpm/services/{shared_prompt_cache.py → memory/cache/shared_prompt_cache.py} +237 -200
  627. claude_mpm/services/memory/cache/simple_cache.py +331 -0
  628. claude_mpm/services/memory/failure_tracker.py +578 -0
  629. claude_mpm/services/memory/indexed_memory.py +648 -0
  630. claude_mpm/services/{memory_optimizer.py → memory/optimizer.py} +272 -243
  631. claude_mpm/services/memory/router.py +951 -0
  632. claude_mpm/services/memory_hook_service.py +470 -0
  633. claude_mpm/services/model/__init__.py +147 -0
  634. claude_mpm/services/model/base_provider.py +365 -0
  635. claude_mpm/services/model/claude_provider.py +412 -0
  636. claude_mpm/services/model/model_router.py +452 -0
  637. claude_mpm/services/model/ollama_provider.py +415 -0
  638. claude_mpm/services/monitor/__init__.py +20 -0
  639. claude_mpm/services/monitor/daemon.py +698 -0
  640. claude_mpm/services/monitor/daemon_manager.py +1076 -0
  641. claude_mpm/services/monitor/event_emitter.py +350 -0
  642. claude_mpm/services/monitor/handlers/__init__.py +21 -0
  643. claude_mpm/services/monitor/handlers/code_analysis.py +332 -0
  644. claude_mpm/services/monitor/handlers/dashboard.py +299 -0
  645. claude_mpm/services/monitor/handlers/file.py +264 -0
  646. claude_mpm/services/monitor/handlers/hooks.py +512 -0
  647. claude_mpm/services/monitor/management/__init__.py +18 -0
  648. claude_mpm/services/monitor/management/health.py +124 -0
  649. claude_mpm/services/monitor/management/lifecycle.py +730 -0
  650. claude_mpm/services/monitor/server.py +1493 -0
  651. claude_mpm/services/monitor_build_service.py +349 -0
  652. claude_mpm/services/native_agent_converter.py +356 -0
  653. claude_mpm/services/orphan_detection.py +786 -0
  654. claude_mpm/services/pm_skills_deployer.py +711 -0
  655. claude_mpm/services/port_manager.py +597 -0
  656. claude_mpm/services/pr/__init__.py +14 -0
  657. claude_mpm/services/pr/pr_template_service.py +329 -0
  658. claude_mpm/services/profile_manager.py +337 -0
  659. claude_mpm/services/project/__init__.py +44 -0
  660. claude_mpm/services/{project_analyzer.py → project/analyzer.py} +541 -291
  661. claude_mpm/services/project/analyzer_v2.py +566 -0
  662. claude_mpm/services/project/architecture_analyzer.py +461 -0
  663. claude_mpm/services/project/archive_manager.py +1045 -0
  664. claude_mpm/services/project/dependency_analyzer.py +462 -0
  665. claude_mpm/services/project/detection_strategies.py +719 -0
  666. claude_mpm/services/project/documentation_manager.py +554 -0
  667. claude_mpm/services/project/enhanced_analyzer.py +572 -0
  668. claude_mpm/services/project/language_analyzer.py +265 -0
  669. claude_mpm/services/project/metrics_collector.py +407 -0
  670. claude_mpm/services/project/project_organizer.py +1009 -0
  671. claude_mpm/services/project/registry.py +636 -0
  672. claude_mpm/services/project/toolchain_analyzer.py +583 -0
  673. claude_mpm/services/project_port_allocator.py +596 -0
  674. claude_mpm/services/recovery_manager.py +293 -240
  675. claude_mpm/services/response_tracker.py +267 -0
  676. claude_mpm/services/runner_configuration_service.py +605 -0
  677. claude_mpm/services/self_upgrade_service.py +608 -0
  678. claude_mpm/services/session_management_service.py +314 -0
  679. claude_mpm/services/session_manager.py +380 -0
  680. claude_mpm/services/shared/__init__.py +21 -0
  681. claude_mpm/services/shared/async_service_base.py +216 -0
  682. claude_mpm/services/shared/config_service_base.py +301 -0
  683. claude_mpm/services/shared/lifecycle_service_base.py +308 -0
  684. claude_mpm/services/shared/manager_base.py +315 -0
  685. claude_mpm/services/shared/service_factory.py +309 -0
  686. claude_mpm/services/skills/__init__.py +21 -0
  687. claude_mpm/services/skills/git_skill_source_manager.py +1340 -0
  688. claude_mpm/services/skills/selective_skill_deployer.py +743 -0
  689. claude_mpm/services/skills/skill_discovery_service.py +568 -0
  690. claude_mpm/services/skills/skill_to_agent_mapper.py +406 -0
  691. claude_mpm/services/skills_config.py +547 -0
  692. claude_mpm/services/skills_deployer.py +1168 -0
  693. claude_mpm/services/socketio/__init__.py +25 -0
  694. claude_mpm/services/socketio/client_proxy.py +229 -0
  695. claude_mpm/services/socketio/dashboard_server.py +362 -0
  696. claude_mpm/services/socketio/event_normalizer.py +798 -0
  697. claude_mpm/services/socketio/handlers/__init__.py +30 -0
  698. claude_mpm/services/socketio/handlers/base.py +136 -0
  699. claude_mpm/services/socketio/handlers/code_analysis.py +682 -0
  700. claude_mpm/services/socketio/handlers/connection.py +643 -0
  701. claude_mpm/services/socketio/handlers/connection_handler.py +333 -0
  702. claude_mpm/services/socketio/handlers/file.py +263 -0
  703. claude_mpm/services/socketio/handlers/git.py +962 -0
  704. claude_mpm/services/socketio/handlers/hook.py +211 -0
  705. claude_mpm/services/socketio/handlers/memory.py +26 -0
  706. claude_mpm/services/socketio/handlers/project.py +24 -0
  707. claude_mpm/services/socketio/handlers/registry.py +214 -0
  708. claude_mpm/services/socketio/migration_utils.py +343 -0
  709. claude_mpm/services/socketio/monitor_client.py +364 -0
  710. claude_mpm/services/socketio/server/__init__.py +18 -0
  711. claude_mpm/services/socketio/server/broadcaster.py +569 -0
  712. claude_mpm/services/socketio/server/connection_manager.py +579 -0
  713. claude_mpm/services/socketio/server/core.py +1079 -0
  714. claude_mpm/services/socketio/server/eventbus_integration.py +245 -0
  715. claude_mpm/services/socketio/server/main.py +501 -0
  716. claude_mpm/services/socketio_client_manager.py +173 -143
  717. claude_mpm/services/socketio_server.py +38 -1657
  718. claude_mpm/services/subprocess_launcher_service.py +322 -0
  719. claude_mpm/services/system_instructions_service.py +270 -0
  720. claude_mpm/services/ticket_manager.py +25 -209
  721. claude_mpm/services/ticket_services/__init__.py +26 -0
  722. claude_mpm/services/ticket_services/crud_service.py +328 -0
  723. claude_mpm/services/ticket_services/formatter_service.py +290 -0
  724. claude_mpm/services/ticket_services/search_service.py +324 -0
  725. claude_mpm/services/ticket_services/validation_service.py +303 -0
  726. claude_mpm/services/ticket_services/workflow_service.py +244 -0
  727. claude_mpm/services/unified/__init__.py +65 -0
  728. claude_mpm/services/unified/analyzer_strategies/__init__.py +44 -0
  729. claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +518 -0
  730. claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +680 -0
  731. claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +900 -0
  732. claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +745 -0
  733. claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +733 -0
  734. claude_mpm/services/unified/config_strategies/__init__.py +175 -0
  735. claude_mpm/services/unified/config_strategies/config_schema.py +731 -0
  736. claude_mpm/services/unified/config_strategies/context_strategy.py +747 -0
  737. claude_mpm/services/unified/config_strategies/error_handling_strategy.py +1005 -0
  738. claude_mpm/services/unified/config_strategies/file_loader_strategy.py +881 -0
  739. claude_mpm/services/unified/config_strategies/unified_config_service.py +823 -0
  740. claude_mpm/services/unified/config_strategies/validation_strategy.py +1148 -0
  741. claude_mpm/services/unified/deployment_strategies/__init__.py +97 -0
  742. claude_mpm/services/unified/deployment_strategies/base.py +553 -0
  743. claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +573 -0
  744. claude_mpm/services/unified/deployment_strategies/local.py +607 -0
  745. claude_mpm/services/unified/deployment_strategies/utils.py +667 -0
  746. claude_mpm/services/unified/deployment_strategies/vercel.py +471 -0
  747. claude_mpm/services/unified/interfaces.py +475 -0
  748. claude_mpm/services/unified/migration.py +509 -0
  749. claude_mpm/services/unified/strategies.py +534 -0
  750. claude_mpm/services/unified/unified_analyzer.py +542 -0
  751. claude_mpm/services/unified/unified_config.py +691 -0
  752. claude_mpm/services/unified/unified_deployment.py +466 -0
  753. claude_mpm/services/utility_service.py +280 -0
  754. claude_mpm/services/version_control/__init__.py +34 -37
  755. claude_mpm/services/version_control/branch_strategy.py +26 -17
  756. claude_mpm/services/version_control/conflict_resolution.py +52 -36
  757. claude_mpm/services/version_control/git_operations.py +183 -49
  758. claude_mpm/services/version_control/semantic_versioning.py +172 -61
  759. claude_mpm/services/version_control/version_parser.py +546 -0
  760. claude_mpm/services/version_service.py +379 -0
  761. claude_mpm/services/visualization/__init__.py +15 -0
  762. claude_mpm/services/visualization/mermaid_generator.py +937 -0
  763. claude_mpm/skills/__init__.py +42 -0
  764. claude_mpm/skills/agent_skills_injector.py +324 -0
  765. claude_mpm/skills/bundled/LICENSE_ATTRIBUTIONS.md +79 -0
  766. claude_mpm/skills/bundled/__init__.py +6 -0
  767. claude_mpm/skills/bundled/api-documentation.md +393 -0
  768. claude_mpm/skills/bundled/async-testing.md +571 -0
  769. claude_mpm/skills/bundled/code-review.md +143 -0
  770. claude_mpm/skills/bundled/collaboration/brainstorming/SKILL.md +79 -0
  771. claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/SKILL.md +178 -0
  772. claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/agent-prompts.md +577 -0
  773. claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/coordination-patterns.md +467 -0
  774. claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/examples.md +537 -0
  775. claude_mpm/skills/bundled/collaboration/dispatching-parallel-agents/references/troubleshooting.md +730 -0
  776. claude_mpm/skills/bundled/collaboration/git-worktrees.md +317 -0
  777. claude_mpm/skills/bundled/collaboration/requesting-code-review/SKILL.md +112 -0
  778. claude_mpm/skills/bundled/collaboration/requesting-code-review/references/code-reviewer-template.md +146 -0
  779. claude_mpm/skills/bundled/collaboration/requesting-code-review/references/review-examples.md +412 -0
  780. claude_mpm/skills/bundled/collaboration/stacked-prs.md +251 -0
  781. claude_mpm/skills/bundled/collaboration/writing-plans/SKILL.md +81 -0
  782. claude_mpm/skills/bundled/collaboration/writing-plans/references/best-practices.md +362 -0
  783. claude_mpm/skills/bundled/collaboration/writing-plans/references/plan-structure-templates.md +312 -0
  784. claude_mpm/skills/bundled/database-migration.md +199 -0
  785. claude_mpm/skills/bundled/debugging/root-cause-tracing/SKILL.md +152 -0
  786. claude_mpm/skills/bundled/debugging/root-cause-tracing/references/advanced-techniques.md +668 -0
  787. claude_mpm/skills/bundled/debugging/root-cause-tracing/references/examples.md +587 -0
  788. claude_mpm/skills/bundled/debugging/root-cause-tracing/references/integration.md +438 -0
  789. claude_mpm/skills/bundled/debugging/root-cause-tracing/references/tracing-techniques.md +391 -0
  790. claude_mpm/skills/bundled/debugging/systematic-debugging/CREATION-LOG.md +119 -0
  791. claude_mpm/skills/bundled/debugging/systematic-debugging/SKILL.md +148 -0
  792. claude_mpm/skills/bundled/debugging/systematic-debugging/references/anti-patterns.md +483 -0
  793. claude_mpm/skills/bundled/debugging/systematic-debugging/references/examples.md +452 -0
  794. claude_mpm/skills/bundled/debugging/systematic-debugging/references/troubleshooting.md +449 -0
  795. claude_mpm/skills/bundled/debugging/systematic-debugging/references/workflow.md +411 -0
  796. claude_mpm/skills/bundled/debugging/systematic-debugging/test-academic.md +14 -0
  797. claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-1.md +58 -0
  798. claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-2.md +68 -0
  799. claude_mpm/skills/bundled/debugging/systematic-debugging/test-pressure-3.md +69 -0
  800. claude_mpm/skills/bundled/debugging/verification-before-completion/SKILL.md +131 -0
  801. claude_mpm/skills/bundled/debugging/verification-before-completion/references/gate-function.md +325 -0
  802. claude_mpm/skills/bundled/debugging/verification-before-completion/references/integration-and-workflows.md +490 -0
  803. claude_mpm/skills/bundled/debugging/verification-before-completion/references/red-flags-and-failures.md +425 -0
  804. claude_mpm/skills/bundled/debugging/verification-before-completion/references/verification-patterns.md +499 -0
  805. claude_mpm/skills/bundled/docker-containerization.md +194 -0
  806. claude_mpm/skills/bundled/express-local-dev.md +1429 -0
  807. claude_mpm/skills/bundled/fastapi-local-dev.md +1199 -0
  808. claude_mpm/skills/bundled/git-workflow.md +414 -0
  809. claude_mpm/skills/bundled/imagemagick.md +204 -0
  810. claude_mpm/skills/bundled/infrastructure/env-manager/INTEGRATION.md +611 -0
  811. claude_mpm/skills/bundled/infrastructure/env-manager/README.md +596 -0
  812. claude_mpm/skills/bundled/infrastructure/env-manager/SKILL.md +260 -0
  813. claude_mpm/skills/bundled/infrastructure/env-manager/examples/nextjs-env-structure.md +315 -0
  814. claude_mpm/skills/bundled/infrastructure/env-manager/references/frameworks.md +436 -0
  815. claude_mpm/skills/bundled/infrastructure/env-manager/references/security.md +433 -0
  816. claude_mpm/skills/bundled/infrastructure/env-manager/references/synchronization.md +452 -0
  817. claude_mpm/skills/bundled/infrastructure/env-manager/references/troubleshooting.md +404 -0
  818. claude_mpm/skills/bundled/infrastructure/env-manager/references/validation.md +420 -0
  819. claude_mpm/skills/bundled/infrastructure/env-manager/scripts/validate_env.py +576 -0
  820. claude_mpm/skills/bundled/json-data-handling.md +223 -0
  821. claude_mpm/skills/bundled/main/artifacts-builder/SKILL.md +86 -0
  822. claude_mpm/skills/bundled/main/internal-comms/SKILL.md +43 -0
  823. claude_mpm/skills/bundled/main/internal-comms/examples/3p-updates.md +47 -0
  824. claude_mpm/skills/bundled/main/internal-comms/examples/company-newsletter.md +65 -0
  825. claude_mpm/skills/bundled/main/internal-comms/examples/faq-answers.md +30 -0
  826. claude_mpm/skills/bundled/main/internal-comms/examples/general-comms.md +16 -0
  827. claude_mpm/skills/bundled/main/mcp-builder/SKILL.md +160 -0
  828. claude_mpm/skills/bundled/main/mcp-builder/reference/design_principles.md +412 -0
  829. claude_mpm/skills/bundled/main/mcp-builder/reference/evaluation.md +602 -0
  830. claude_mpm/skills/bundled/main/mcp-builder/reference/mcp_best_practices.md +915 -0
  831. claude_mpm/skills/bundled/main/mcp-builder/reference/node_mcp_server.md +916 -0
  832. claude_mpm/skills/bundled/main/mcp-builder/reference/python_mcp_server.md +752 -0
  833. claude_mpm/skills/bundled/main/mcp-builder/reference/workflow.md +1237 -0
  834. claude_mpm/skills/bundled/main/mcp-builder/scripts/connections.py +157 -0
  835. claude_mpm/skills/bundled/main/mcp-builder/scripts/evaluation.py +425 -0
  836. claude_mpm/skills/bundled/main/skill-creator/SKILL.md +189 -0
  837. claude_mpm/skills/bundled/main/skill-creator/references/best-practices.md +500 -0
  838. claude_mpm/skills/bundled/main/skill-creator/references/creation-workflow.md +464 -0
  839. claude_mpm/skills/bundled/main/skill-creator/references/examples.md +619 -0
  840. claude_mpm/skills/bundled/main/skill-creator/references/progressive-disclosure.md +437 -0
  841. claude_mpm/skills/bundled/main/skill-creator/references/skill-structure.md +231 -0
  842. claude_mpm/skills/bundled/main/skill-creator/scripts/init_skill.py +303 -0
  843. claude_mpm/skills/bundled/main/skill-creator/scripts/package_skill.py +113 -0
  844. claude_mpm/skills/bundled/main/skill-creator/scripts/quick_validate.py +72 -0
  845. claude_mpm/skills/bundled/nextjs-local-dev.md +807 -0
  846. claude_mpm/skills/bundled/pdf.md +141 -0
  847. claude_mpm/skills/bundled/performance-profiling.md +573 -0
  848. claude_mpm/skills/bundled/php/espocrm-development/SKILL.md +170 -0
  849. claude_mpm/skills/bundled/php/espocrm-development/references/architecture.md +602 -0
  850. claude_mpm/skills/bundled/php/espocrm-development/references/common-tasks.md +821 -0
  851. claude_mpm/skills/bundled/php/espocrm-development/references/development-workflow.md +742 -0
  852. claude_mpm/skills/bundled/php/espocrm-development/references/frontend-customization.md +726 -0
  853. claude_mpm/skills/bundled/php/espocrm-development/references/hooks-and-services.md +764 -0
  854. claude_mpm/skills/bundled/php/espocrm-development/references/testing-debugging.md +831 -0
  855. claude_mpm/skills/bundled/pm/pm-bug-reporting/pm-bug-reporting.md +248 -0
  856. claude_mpm/skills/bundled/pm/pm-delegation-patterns/SKILL.md +167 -0
  857. claude_mpm/skills/bundled/pm/pm-git-file-tracking/SKILL.md +113 -0
  858. claude_mpm/skills/bundled/pm/pm-pr-workflow/SKILL.md +124 -0
  859. claude_mpm/skills/bundled/pm/pm-teaching-mode/SKILL.md +657 -0
  860. claude_mpm/skills/bundled/pm/pm-ticketing-integration/SKILL.md +154 -0
  861. claude_mpm/skills/bundled/pm/pm-verification-protocols/SKILL.md +198 -0
  862. claude_mpm/skills/bundled/react/flexlayout-react.md +742 -0
  863. claude_mpm/skills/bundled/refactoring-patterns.md +180 -0
  864. claude_mpm/skills/bundled/rust/desktop-applications/SKILL.md +226 -0
  865. claude_mpm/skills/bundled/rust/desktop-applications/references/architecture-patterns.md +901 -0
  866. claude_mpm/skills/bundled/rust/desktop-applications/references/native-gui-frameworks.md +901 -0
  867. claude_mpm/skills/bundled/rust/desktop-applications/references/platform-integration.md +775 -0
  868. claude_mpm/skills/bundled/rust/desktop-applications/references/state-management.md +937 -0
  869. claude_mpm/skills/bundled/rust/desktop-applications/references/tauri-framework.md +770 -0
  870. claude_mpm/skills/bundled/rust/desktop-applications/references/testing-deployment.md +961 -0
  871. claude_mpm/skills/bundled/security-scanning.md +439 -0
  872. claude_mpm/skills/bundled/systematic-debugging.md +473 -0
  873. claude_mpm/skills/bundled/tauri/tauri-async-patterns.md +495 -0
  874. claude_mpm/skills/bundled/tauri/tauri-build-deploy.md +599 -0
  875. claude_mpm/skills/bundled/tauri/tauri-command-patterns.md +535 -0
  876. claude_mpm/skills/bundled/tauri/tauri-error-handling.md +613 -0
  877. claude_mpm/skills/bundled/tauri/tauri-event-system.md +648 -0
  878. claude_mpm/skills/bundled/tauri/tauri-file-system.md +673 -0
  879. claude_mpm/skills/bundled/tauri/tauri-frontend-integration.md +767 -0
  880. claude_mpm/skills/bundled/tauri/tauri-performance.md +669 -0
  881. claude_mpm/skills/bundled/tauri/tauri-state-management.md +573 -0
  882. claude_mpm/skills/bundled/tauri/tauri-testing.md +384 -0
  883. claude_mpm/skills/bundled/tauri/tauri-window-management.md +628 -0
  884. claude_mpm/skills/bundled/test-driven-development.md +378 -0
  885. claude_mpm/skills/bundled/testing/condition-based-waiting/SKILL.md +119 -0
  886. claude_mpm/skills/bundled/testing/condition-based-waiting/references/patterns-and-implementation.md +253 -0
  887. claude_mpm/skills/bundled/testing/test-driven-development/SKILL.md +145 -0
  888. claude_mpm/skills/bundled/testing/test-driven-development/references/anti-patterns.md +543 -0
  889. claude_mpm/skills/bundled/testing/test-driven-development/references/examples.md +741 -0
  890. claude_mpm/skills/bundled/testing/test-driven-development/references/integration.md +470 -0
  891. claude_mpm/skills/bundled/testing/test-driven-development/references/philosophy.md +458 -0
  892. claude_mpm/skills/bundled/testing/test-driven-development/references/workflow.md +639 -0
  893. claude_mpm/skills/bundled/testing/test-quality-inspector/SKILL.md +458 -0
  894. claude_mpm/skills/bundled/testing/test-quality-inspector/examples/example-inspection-report.md +411 -0
  895. claude_mpm/skills/bundled/testing/test-quality-inspector/references/assertion-quality.md +317 -0
  896. claude_mpm/skills/bundled/testing/test-quality-inspector/references/inspection-checklist.md +270 -0
  897. claude_mpm/skills/bundled/testing/test-quality-inspector/references/red-flags.md +436 -0
  898. claude_mpm/skills/bundled/testing/testing-anti-patterns/SKILL.md +140 -0
  899. claude_mpm/skills/bundled/testing/testing-anti-patterns/references/completeness-anti-patterns.md +572 -0
  900. claude_mpm/skills/bundled/testing/testing-anti-patterns/references/core-anti-patterns.md +411 -0
  901. claude_mpm/skills/bundled/testing/testing-anti-patterns/references/detection-guide.md +569 -0
  902. claude_mpm/skills/bundled/testing/testing-anti-patterns/references/tdd-connection.md +695 -0
  903. claude_mpm/skills/bundled/testing/webapp-testing/SKILL.md +184 -0
  904. claude_mpm/skills/bundled/testing/webapp-testing/decision-tree.md +459 -0
  905. claude_mpm/skills/bundled/testing/webapp-testing/examples/console_logging.py +35 -0
  906. claude_mpm/skills/bundled/testing/webapp-testing/examples/element_discovery.py +44 -0
  907. claude_mpm/skills/bundled/testing/webapp-testing/examples/static_html_automation.py +34 -0
  908. claude_mpm/skills/bundled/testing/webapp-testing/playwright-patterns.md +479 -0
  909. claude_mpm/skills/bundled/testing/webapp-testing/reconnaissance-pattern.md +687 -0
  910. claude_mpm/skills/bundled/testing/webapp-testing/scripts/with_server.py +129 -0
  911. claude_mpm/skills/bundled/testing/webapp-testing/server-management.md +758 -0
  912. claude_mpm/skills/bundled/testing/webapp-testing/troubleshooting.md +868 -0
  913. claude_mpm/skills/bundled/vite-local-dev.md +1061 -0
  914. claude_mpm/skills/bundled/web-performance-optimization.md +2305 -0
  915. claude_mpm/skills/bundled/xlsx.md +157 -0
  916. claude_mpm/skills/registry.py +286 -0
  917. claude_mpm/skills/skill_manager.py +405 -0
  918. claude_mpm/skills/skills_registry.py +347 -0
  919. claude_mpm/skills/skills_service.py +739 -0
  920. claude_mpm/storage/__init__.py +9 -0
  921. claude_mpm/storage/state_storage.py +546 -0
  922. claude_mpm/templates/.pre-commit-config.yaml +112 -0
  923. claude_mpm/templates/questions/__init__.py +38 -0
  924. claude_mpm/templates/questions/base.py +193 -0
  925. claude_mpm/templates/questions/pr_strategy.py +311 -0
  926. claude_mpm/templates/questions/project_init.py +385 -0
  927. claude_mpm/templates/questions/ticket_mgmt.py +394 -0
  928. claude_mpm/ticket_wrapper.py +2 -2
  929. claude_mpm/tools/__init__.py +10 -0
  930. claude_mpm/tools/__main__.py +208 -0
  931. claude_mpm/tools/code_tree_analyzer/__init__.py +45 -0
  932. claude_mpm/tools/code_tree_analyzer/analysis.py +299 -0
  933. claude_mpm/tools/code_tree_analyzer/cache.py +131 -0
  934. claude_mpm/tools/code_tree_analyzer/core.py +380 -0
  935. claude_mpm/tools/code_tree_analyzer/discovery.py +403 -0
  936. claude_mpm/tools/code_tree_analyzer/events.py +168 -0
  937. claude_mpm/tools/code_tree_analyzer/gitignore.py +308 -0
  938. claude_mpm/tools/code_tree_analyzer/models.py +39 -0
  939. claude_mpm/tools/code_tree_analyzer/multilang_analyzer.py +224 -0
  940. claude_mpm/tools/code_tree_analyzer/python_analyzer.py +284 -0
  941. claude_mpm/tools/code_tree_builder.py +631 -0
  942. claude_mpm/tools/code_tree_events.py +420 -0
  943. claude_mpm/tools/socketio_debug.py +671 -0
  944. claude_mpm/utils/__init__.py +8 -8
  945. claude_mpm/utils/agent_dependency_loader.py +1189 -0
  946. claude_mpm/utils/agent_filters.py +261 -0
  947. claude_mpm/utils/common.py +544 -0
  948. claude_mpm/utils/config_manager.py +168 -126
  949. claude_mpm/utils/console.py +11 -0
  950. claude_mpm/utils/database_connector.py +298 -0
  951. claude_mpm/utils/dependency_cache.py +373 -0
  952. claude_mpm/utils/dependency_manager.py +60 -59
  953. claude_mpm/utils/dependency_strategies.py +381 -0
  954. claude_mpm/utils/display_helper.py +260 -0
  955. claude_mpm/utils/environment_context.py +313 -0
  956. claude_mpm/utils/error_handler.py +78 -66
  957. claude_mpm/utils/file_utils.py +305 -0
  958. claude_mpm/utils/framework_detection.py +12 -11
  959. claude_mpm/utils/git_analyzer.py +407 -0
  960. claude_mpm/utils/gitignore.py +244 -0
  961. claude_mpm/utils/import_migration_example.py +12 -60
  962. claude_mpm/utils/imports.py +48 -45
  963. claude_mpm/utils/log_cleanup.py +627 -0
  964. claude_mpm/utils/migration.py +372 -0
  965. claude_mpm/utils/path_operations.py +110 -104
  966. claude_mpm/utils/progress.py +387 -0
  967. claude_mpm/utils/robust_installer.py +844 -0
  968. claude_mpm/utils/session_logging.py +121 -0
  969. claude_mpm/utils/structured_questions.py +619 -0
  970. claude_mpm/utils/subprocess_utils.py +343 -0
  971. claude_mpm/validation/__init__.py +1 -1
  972. claude_mpm/validation/agent_validator.py +214 -108
  973. claude_mpm/validation/frontmatter_validator.py +252 -0
  974. claude_mpm-5.4.85.dist-info/METADATA +1023 -0
  975. claude_mpm-5.4.85.dist-info/RECORD +980 -0
  976. {claude_mpm-3.4.10.dist-info → claude_mpm-5.4.85.dist-info}/entry_points.txt +1 -3
  977. claude_mpm-5.4.85.dist-info/licenses/LICENSE +94 -0
  978. claude_mpm-5.4.85.dist-info/licenses/LICENSE-FAQ.md +153 -0
  979. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +0 -88
  980. claude_mpm/agents/INSTRUCTIONS.md +0 -352
  981. claude_mpm/agents/backups/INSTRUCTIONS.md +0 -352
  982. claude_mpm/agents/base_agent_loader.py +0 -529
  983. claude_mpm/agents/schema/agent_schema.json +0 -314
  984. claude_mpm/agents/templates/.claude-mpm/memories/README.md +0 -36
  985. claude_mpm/agents/templates/backup/data_engineer_agent_20250726_234551.json +0 -46
  986. claude_mpm/agents/templates/backup/documentation_agent_20250726_234551.json +0 -45
  987. claude_mpm/agents/templates/backup/engineer_agent_20250726_234551.json +0 -49
  988. claude_mpm/agents/templates/backup/ops_agent_20250726_234551.json +0 -46
  989. claude_mpm/agents/templates/backup/qa_agent_20250726_234551.json +0 -45
  990. claude_mpm/agents/templates/backup/research_agent_20250726_234551.json +0 -49
  991. claude_mpm/agents/templates/backup/security_agent_20250726_234551.json +0 -46
  992. claude_mpm/agents/templates/backup/version_control_agent_20250726_234551.json +0 -46
  993. claude_mpm/agents/templates/data_engineer.json +0 -110
  994. claude_mpm/agents/templates/documentation.json +0 -109
  995. claude_mpm/agents/templates/engineer.json +0 -113
  996. claude_mpm/agents/templates/ops.json +0 -109
  997. claude_mpm/agents/templates/pm.json +0 -25
  998. claude_mpm/agents/templates/qa.json +0 -111
  999. claude_mpm/agents/templates/research.json +0 -65
  1000. claude_mpm/agents/templates/security.json +0 -113
  1001. claude_mpm/agents/templates/test_integration.json +0 -112
  1002. claude_mpm/agents/templates/version_control.json +0 -107
  1003. claude_mpm/cli/commands/ui.py +0 -57
  1004. claude_mpm/core/simple_runner.py +0 -1046
  1005. claude_mpm/dashboard/open_dashboard.py +0 -34
  1006. claude_mpm/deployment_paths.py +0 -261
  1007. claude_mpm/hooks/builtin/__init__.py +0 -1
  1008. claude_mpm/hooks/builtin/logging_hook_example.py +0 -165
  1009. claude_mpm/hooks/builtin/memory_hooks_example.py +0 -67
  1010. claude_mpm/hooks/builtin/mpm_command_hook.py +0 -125
  1011. claude_mpm/hooks/builtin/post_delegation_hook_example.py +0 -124
  1012. claude_mpm/hooks/builtin/pre_delegation_hook_example.py +0 -125
  1013. claude_mpm/hooks/builtin/submit_hook_example.py +0 -100
  1014. claude_mpm/hooks/builtin/ticket_extraction_hook_example.py +0 -237
  1015. claude_mpm/hooks/builtin/todo_agent_prefix_hook.py +0 -240
  1016. claude_mpm/hooks/builtin/workflow_start_hook.py +0 -181
  1017. claude_mpm/orchestration/__init__.py +0 -6
  1018. claude_mpm/orchestration/archive/direct_orchestrator.py +0 -195
  1019. claude_mpm/orchestration/archive/factory.py +0 -215
  1020. claude_mpm/orchestration/archive/hook_enabled_orchestrator.py +0 -188
  1021. claude_mpm/orchestration/archive/hook_integration_example.py +0 -178
  1022. claude_mpm/orchestration/archive/interactive_subprocess_orchestrator.py +0 -826
  1023. claude_mpm/orchestration/archive/orchestrator.py +0 -501
  1024. claude_mpm/orchestration/archive/pexpect_orchestrator.py +0 -252
  1025. claude_mpm/orchestration/archive/pty_orchestrator.py +0 -270
  1026. claude_mpm/orchestration/archive/simple_orchestrator.py +0 -82
  1027. claude_mpm/orchestration/archive/subprocess_orchestrator.py +0 -801
  1028. claude_mpm/orchestration/archive/system_prompt_orchestrator.py +0 -278
  1029. claude_mpm/orchestration/archive/wrapper_orchestrator.py +0 -187
  1030. claude_mpm/schemas/workflow_validator.py +0 -411
  1031. claude_mpm/services/agent_deployment.py +0 -1534
  1032. claude_mpm/services/agent_lifecycle_manager.py +0 -1169
  1033. claude_mpm/services/agent_memory_manager.py +0 -1415
  1034. claude_mpm/services/agent_registry.py +0 -676
  1035. claude_mpm/services/deployed_agent_discovery.py +0 -226
  1036. claude_mpm/services/framework_agent_loader.py +0 -337
  1037. claude_mpm/services/framework_claude_md_generator.py +0 -621
  1038. claude_mpm/services/health_monitor.py +0 -892
  1039. claude_mpm/services/memory_router.py +0 -538
  1040. claude_mpm/services/parent_directory_manager/__init__.py +0 -577
  1041. claude_mpm/services/parent_directory_manager/backup_manager.py +0 -258
  1042. claude_mpm/services/parent_directory_manager/config_manager.py +0 -210
  1043. claude_mpm/services/parent_directory_manager/deduplication_manager.py +0 -279
  1044. claude_mpm/services/parent_directory_manager/framework_protector.py +0 -143
  1045. claude_mpm/services/parent_directory_manager/operations.py +0 -186
  1046. claude_mpm/services/parent_directory_manager/state_manager.py +0 -624
  1047. claude_mpm/services/parent_directory_manager/template_deployer.py +0 -579
  1048. claude_mpm/services/parent_directory_manager/validation_manager.py +0 -378
  1049. claude_mpm/services/parent_directory_manager/version_control_helper.py +0 -339
  1050. claude_mpm/services/parent_directory_manager/version_manager.py +0 -222
  1051. claude_mpm/services/standalone_socketio_server.py +0 -1300
  1052. claude_mpm/services/ticket_manager_di.py +0 -318
  1053. claude_mpm/services/ticketing_service_original.py +0 -508
  1054. claude_mpm/ui/__init__.py +0 -1
  1055. claude_mpm/ui/rich_terminal_ui.py +0 -295
  1056. claude_mpm/ui/terminal_ui.py +0 -328
  1057. claude_mpm/utils/paths.py +0 -289
  1058. claude_mpm-3.4.10.dist-info/METADATA +0 -183
  1059. claude_mpm-3.4.10.dist-info/RECORD +0 -201
  1060. claude_mpm-3.4.10.dist-info/licenses/LICENSE +0 -21
  1061. {claude_mpm-3.4.10.dist-info → claude_mpm-5.4.85.dist-info}/WHEEL +0 -0
  1062. {claude_mpm-3.4.10.dist-info → claude_mpm-5.4.85.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,3253 @@
1
+ """
2
+ Interactive configuration management command for claude-mpm CLI.
3
+
4
+ WHY: Users need an intuitive, interactive way to manage agent configurations,
5
+ edit templates, and configure behavior files without manually editing JSON/YAML files.
6
+
7
+ DESIGN DECISIONS:
8
+ - Use Rich for modern TUI with menus, tables, and panels
9
+ - Support both project-level and user-level configurations
10
+ - Provide non-interactive options for scripting
11
+ - Allow direct navigation to specific sections
12
+ """
13
+
14
+ import json
15
+ import shutil
16
+ from collections import defaultdict
17
+ from pathlib import Path
18
+ from typing import Dict, List, Optional
19
+
20
+ import questionary
21
+ import questionary.constants
22
+ import questionary.prompts.common # For checkbox symbol customization
23
+ from questionary import Choice, Separator, Style
24
+ from rich.console import Console
25
+ from rich.prompt import Confirm, Prompt
26
+ from rich.text import Text
27
+
28
+ from ...core.config import Config
29
+ from ...core.unified_config import UnifiedConfig
30
+ from ...services.agents.agent_recommendation_service import AgentRecommendationService
31
+ from ...services.version_service import VersionService
32
+ from ...utils.agent_filters import apply_all_filters, get_deployed_agent_ids
33
+ from ...utils.console import console as default_console
34
+ from ..shared import BaseCommand, CommandResult
35
+ from .agent_state_manager import SimpleAgentManager
36
+ from .configure_agent_display import AgentDisplay
37
+ from .configure_behavior_manager import BehaviorManager
38
+ from .configure_hook_manager import HookManager
39
+ from .configure_models import AgentConfig
40
+ from .configure_navigation import ConfigNavigation
41
+ from .configure_persistence import ConfigPersistence
42
+ from .configure_startup_manager import StartupManager
43
+ from .configure_template_editor import TemplateEditor
44
+ from .configure_validators import (
45
+ parse_id_selection,
46
+ validate_args as validate_configure_args,
47
+ )
48
+
49
+
50
+ class ConfigureCommand(BaseCommand):
51
+ """Interactive configuration management command."""
52
+
53
+ # Questionary style optimized for dark terminals (WCAG AAA compliant)
54
+ QUESTIONARY_STYLE = Style(
55
+ [
56
+ ("selected", "fg:#e0e0e0 bold"), # Light gray - excellent readability
57
+ ("pointer", "fg:#ffd700 bold"), # Gold/yellow - highly visible pointer
58
+ ("highlighted", "fg:#e0e0e0"), # Light gray - clear hover state
59
+ ("question", "fg:#e0e0e0 bold"), # Light gray bold - prominent questions
60
+ ("checkbox", "fg:#00ff00"), # Green - for checked boxes
61
+ (
62
+ "checkbox-selected",
63
+ "fg:#00ff00 bold",
64
+ ), # Green bold - for checked selected boxes
65
+ ]
66
+ )
67
+
68
+ def __init__(self):
69
+ super().__init__("configure")
70
+ self.console = default_console
71
+ self.version_service = VersionService()
72
+ self.current_scope = "project"
73
+ self.project_dir = Path.cwd()
74
+ self.agent_manager = None
75
+ self.hook_manager = HookManager(self.console)
76
+ self.behavior_manager = None # Initialized when scope is set
77
+ self._agent_display = None # Lazy-initialized
78
+ self._persistence = None # Lazy-initialized
79
+ self._navigation = None # Lazy-initialized
80
+ self._template_editor = None # Lazy-initialized
81
+ self._startup_manager = None # Lazy-initialized
82
+ self._recommendation_service = None # Lazy-initialized
83
+ self._unified_config = None # Lazy-initialized
84
+
85
+ def validate_args(self, args) -> Optional[str]:
86
+ """Validate command arguments."""
87
+ return validate_configure_args(args)
88
+
89
+ @property
90
+ def agent_display(self) -> AgentDisplay:
91
+ """Lazy-initialize agent display handler."""
92
+ if self._agent_display is None:
93
+ if self.agent_manager is None:
94
+ raise RuntimeError(
95
+ "agent_manager must be initialized before agent_display"
96
+ )
97
+ self._agent_display = AgentDisplay(
98
+ self.console,
99
+ self.agent_manager,
100
+ self._get_agent_template_path,
101
+ self._display_header,
102
+ )
103
+ return self._agent_display
104
+
105
+ @property
106
+ def persistence(self) -> ConfigPersistence:
107
+ """Lazy-initialize persistence handler."""
108
+ if self._persistence is None:
109
+ # Note: agent_manager might be None for version_info calls
110
+ self._persistence = ConfigPersistence(
111
+ self.console,
112
+ self.version_service,
113
+ self.agent_manager, # Can be None for version operations
114
+ self._get_agent_template_path,
115
+ self._display_header,
116
+ self.current_scope,
117
+ self.project_dir,
118
+ )
119
+ return self._persistence
120
+
121
+ @property
122
+ def navigation(self) -> ConfigNavigation:
123
+ """Lazy-initialize navigation handler."""
124
+ if self._navigation is None:
125
+ self._navigation = ConfigNavigation(self.console, self.project_dir)
126
+ # Sync scope from main command
127
+ self._navigation.current_scope = self.current_scope
128
+ return self._navigation
129
+
130
+ @property
131
+ def template_editor(self) -> TemplateEditor:
132
+ """Lazy-initialize template editor."""
133
+ if self._template_editor is None:
134
+ if self.agent_manager is None:
135
+ raise RuntimeError(
136
+ "agent_manager must be initialized before template_editor"
137
+ )
138
+ self._template_editor = TemplateEditor(
139
+ self.console, self.agent_manager, self.current_scope, self.project_dir
140
+ )
141
+ return self._template_editor
142
+
143
+ @property
144
+ def startup_manager(self) -> StartupManager:
145
+ """Lazy-initialize startup manager."""
146
+ if self._startup_manager is None:
147
+ if self.agent_manager is None:
148
+ raise RuntimeError(
149
+ "agent_manager must be initialized before startup_manager"
150
+ )
151
+ self._startup_manager = StartupManager(
152
+ self.agent_manager,
153
+ self.console,
154
+ self.current_scope,
155
+ self.project_dir,
156
+ self._display_header,
157
+ )
158
+ return self._startup_manager
159
+
160
+ @property
161
+ def recommendation_service(self) -> AgentRecommendationService:
162
+ """Lazy-initialize recommendation service."""
163
+ if self._recommendation_service is None:
164
+ self._recommendation_service = AgentRecommendationService()
165
+ return self._recommendation_service
166
+
167
+ @property
168
+ def unified_config(self) -> UnifiedConfig:
169
+ """Lazy-initialize unified config."""
170
+ if self._unified_config is None:
171
+ try:
172
+ self._unified_config = UnifiedConfig()
173
+ except Exception as e:
174
+ self.logger.warning(f"Failed to load unified config: {e}")
175
+ # Fallback to default config
176
+ self._unified_config = UnifiedConfig()
177
+ return self._unified_config
178
+
179
+ def run(self, args) -> CommandResult:
180
+ """Execute the configure command."""
181
+ # Set configuration scope
182
+ self.current_scope = getattr(args, "scope", "project")
183
+ if getattr(args, "project_dir", None):
184
+ self.project_dir = Path(args.project_dir)
185
+
186
+ # Initialize agent manager and behavior manager with appropriate config directory
187
+ if self.current_scope == "project":
188
+ config_dir = self.project_dir / ".claude-mpm"
189
+ else:
190
+ config_dir = Path.home() / ".claude-mpm"
191
+ self.agent_manager = SimpleAgentManager(config_dir)
192
+ self.behavior_manager = BehaviorManager(
193
+ config_dir, self.current_scope, self.console
194
+ )
195
+
196
+ # Disable colors if requested
197
+ if getattr(args, "no_colors", False):
198
+ self.console = Console(color_system=None)
199
+
200
+ # Handle non-interactive options first
201
+ if getattr(args, "list_agents", False):
202
+ return self._list_agents_non_interactive()
203
+
204
+ if getattr(args, "enable_agent", None):
205
+ return self._enable_agent_non_interactive(args.enable_agent)
206
+
207
+ if getattr(args, "disable_agent", None):
208
+ return self._disable_agent_non_interactive(args.disable_agent)
209
+
210
+ if getattr(args, "export_config", None):
211
+ return self._export_config(args.export_config)
212
+
213
+ if getattr(args, "import_config", None):
214
+ return self._import_config(args.import_config)
215
+
216
+ if getattr(args, "version_info", False):
217
+ return self._show_version_info()
218
+
219
+ # Handle hook installation
220
+ if getattr(args, "install_hooks", False):
221
+ return self._install_hooks(force=getattr(args, "force", False))
222
+
223
+ if getattr(args, "verify_hooks", False):
224
+ return self._verify_hooks()
225
+
226
+ if getattr(args, "uninstall_hooks", False):
227
+ return self._uninstall_hooks()
228
+
229
+ # Handle direct navigation options
230
+ if getattr(args, "agents", False):
231
+ return self._run_agent_management()
232
+
233
+ if getattr(args, "templates", False):
234
+ return self._run_template_editing()
235
+
236
+ if getattr(args, "behaviors", False):
237
+ return self._run_behavior_management()
238
+
239
+ if getattr(args, "startup", False):
240
+ return self._run_startup_configuration()
241
+
242
+ # Launch interactive TUI
243
+ return self._run_interactive_tui(args)
244
+
245
+ def _run_interactive_tui(self, args) -> CommandResult:
246
+ """Run the main interactive menu interface."""
247
+ # Rich-based menu interface
248
+ try:
249
+ self.console.clear()
250
+
251
+ while True:
252
+ # Display main menu
253
+ self._display_header()
254
+ choice = self._show_main_menu()
255
+
256
+ if choice == "1":
257
+ self._manage_agents()
258
+ elif choice == "2":
259
+ self._manage_skills()
260
+ elif choice == "3":
261
+ self._edit_templates()
262
+ elif choice == "4":
263
+ self._manage_behaviors()
264
+ elif choice == "5":
265
+ # If user saves and wants to proceed to startup, exit the configurator
266
+ if self._manage_startup_configuration():
267
+ self.console.print(
268
+ "\n[green]Configuration saved. Exiting configurator...[/green]"
269
+ )
270
+ break
271
+ elif choice == "6":
272
+ self._switch_scope()
273
+ elif choice == "7":
274
+ self._show_version_info_interactive()
275
+ elif choice == "l":
276
+ # Check for pending agent changes
277
+ if self.agent_manager and self.agent_manager.has_pending_changes():
278
+ should_save = Confirm.ask(
279
+ "[yellow]You have unsaved agent changes. Save them before launching?[/yellow]",
280
+ default=True,
281
+ )
282
+ if should_save:
283
+ self.agent_manager.commit_deferred_changes()
284
+ self.console.print("[green]✓ Agent changes saved[/green]")
285
+ else:
286
+ self.agent_manager.discard_deferred_changes()
287
+ self.console.print(
288
+ "[yellow]⚠ Agent changes discarded[/yellow]"
289
+ )
290
+
291
+ # Save all configuration
292
+ self.console.print("\n[cyan]Saving configuration...[/cyan]")
293
+ if self._save_all_configuration():
294
+ # Launch Claude MPM (this will replace the process if successful)
295
+ self._launch_claude_mpm()
296
+ # If execvp fails, we'll return here and break
297
+ break
298
+ self.console.print(
299
+ "[red]✗ Failed to save configuration. Not launching.[/red]"
300
+ )
301
+ Prompt.ask("\nPress Enter to continue")
302
+ elif choice == "q":
303
+ self.console.print(
304
+ "\n[green]Configuration complete. Goodbye![/green]"
305
+ )
306
+ break
307
+ else:
308
+ self.console.print("[red]Invalid choice. Please try again.[/red]")
309
+
310
+ return CommandResult.success_result("Configuration completed")
311
+
312
+ except KeyboardInterrupt:
313
+ self.console.print("\n[yellow]Configuration cancelled.[/yellow]")
314
+ return CommandResult.success_result("Configuration cancelled")
315
+ except Exception as e:
316
+ self.logger.error(f"Configuration error: {e}", exc_info=True)
317
+ return CommandResult.error_result(f"Configuration failed: {e}")
318
+
319
+ def _display_header(self) -> None:
320
+ """Display the TUI header."""
321
+ # Sync scope to navigation before display
322
+ self.navigation.current_scope = self.current_scope
323
+ self.navigation.display_header()
324
+
325
+ def _show_main_menu(self) -> str:
326
+ """Show the main menu and get user choice."""
327
+ # Sync scope to navigation before display
328
+ self.navigation.current_scope = self.current_scope
329
+ return self.navigation.show_main_menu()
330
+
331
+ def _manage_agents(self) -> None:
332
+ """Enhanced agent management with remote agent discovery and installation."""
333
+ while True:
334
+ self.console.clear()
335
+ self.navigation.display_header()
336
+ self.console.print("\n[bold blue]═══ Agent Management ═══[/bold blue]\n")
337
+
338
+ # Load all agents with spinner (don't show partial state)
339
+ agents = self._load_agents_with_spinner()
340
+
341
+ if not agents:
342
+ self.console.print("[yellow]No agents found[/yellow]")
343
+ self.console.print(
344
+ "[dim]Configure sources with 'claude-mpm agent-source add'[/dim]\n"
345
+ )
346
+ Prompt.ask("\nPress Enter to continue")
347
+ break
348
+
349
+ # Now display everything at once (after all data loaded)
350
+ self._display_agent_sources_and_list(agents)
351
+
352
+ # Step 3: Simplified menu - only "Select Agents" option
353
+ self.console.print()
354
+ self.logger.debug("About to show agent management menu")
355
+ try:
356
+ choice = questionary.select(
357
+ "Agent Management:",
358
+ choices=[
359
+ "Select Agents",
360
+ questionary.Separator(),
361
+ "← Back to main menu",
362
+ ],
363
+ style=self.QUESTIONARY_STYLE,
364
+ ).ask()
365
+
366
+ if choice is None or choice == "← Back to main menu":
367
+ break
368
+
369
+ # Map selection to action
370
+ if choice == "Select Agents":
371
+ self.logger.debug("User selected 'Select Agents' from menu")
372
+ self._deploy_agents_unified(agents)
373
+ # Loop back to show updated state after deployment
374
+
375
+ except KeyboardInterrupt:
376
+ self.console.print("\n[yellow]Operation cancelled[/yellow]")
377
+ break
378
+ except Exception as e:
379
+ # Handle questionary menu failure
380
+ import sys
381
+
382
+ self.logger.error(f"Agent management menu failed: {e}", exc_info=True)
383
+ self.console.print("[red]Error: Interactive menu failed[/red]")
384
+ self.console.print(f"[dim]Reason: {e}[/dim]")
385
+ if not sys.stdin.isatty():
386
+ self.console.print(
387
+ "[dim]Interactive terminal required for this operation[/dim]"
388
+ )
389
+ self.console.print("[dim]Use command-line options instead:[/dim]")
390
+ self.console.print(
391
+ "[dim] claude-mpm configure --list-agents[/dim]"
392
+ )
393
+ self.console.print(
394
+ "[dim] claude-mpm configure --enable-agent <id>[/dim]"
395
+ )
396
+ Prompt.ask("\nPress Enter to continue")
397
+ break
398
+
399
+ def _load_agents_with_spinner(self) -> List[AgentConfig]:
400
+ """Load agents with loading indicator, don't show partial state.
401
+
402
+ Returns:
403
+ List of discovered agents with deployment status set.
404
+ """
405
+
406
+ agents = []
407
+ with self.console.status(
408
+ "[bold blue]Loading agents...[/bold blue]", spinner="dots"
409
+ ):
410
+ try:
411
+ # Discover agents (includes both local and remote)
412
+ agents = self.agent_manager.discover_agents(include_remote=True)
413
+
414
+ # Set deployment status on each agent for display
415
+ deployed_ids = get_deployed_agent_ids()
416
+ for agent in agents:
417
+ # Use agent_id (technical ID) for comparison, not display name
418
+ agent_id = getattr(agent, "agent_id", agent.name)
419
+ agent_leaf_name = agent_id.split("/")[-1]
420
+ agent.is_deployed = agent_leaf_name in deployed_ids
421
+
422
+ # Filter BASE_AGENT from display (1M-502 Phase 1)
423
+ agents = self._filter_agent_configs(agents, filter_deployed=False)
424
+
425
+ except Exception as e:
426
+ self.console.print(f"[red]Error discovering agents: {e}[/red]")
427
+ self.logger.error(f"Agent discovery failed: {e}", exc_info=True)
428
+ agents = []
429
+
430
+ return agents
431
+
432
+ def _display_agent_sources_and_list(self, agents: List[AgentConfig]) -> None:
433
+ """Display agent sources and agent list (only after all data loaded).
434
+
435
+ Args:
436
+ agents: List of discovered agents with deployment status.
437
+ """
438
+ from rich.table import Table
439
+
440
+ # Step 1: Show configured sources
441
+ self.console.print("[bold white]═══ Agent Sources ═══[/bold white]\n")
442
+
443
+ sources = self._get_configured_sources()
444
+ if sources:
445
+ sources_table = Table(show_header=True, header_style="bold white")
446
+ sources_table.add_column(
447
+ "Source",
448
+ style="bright_yellow",
449
+ width=40,
450
+ no_wrap=True,
451
+ overflow="ellipsis",
452
+ )
453
+ sources_table.add_column("Status", style="green", width=15, no_wrap=True)
454
+ sources_table.add_column("Agents", style="yellow", width=10, no_wrap=True)
455
+
456
+ for source in sources:
457
+ status = "✓ Active" if source.get("enabled", True) else "Disabled"
458
+ agent_count = source.get("agent_count", "?")
459
+ sources_table.add_row(source["identifier"], status, str(agent_count))
460
+
461
+ self.console.print(sources_table)
462
+ else:
463
+ self.console.print("[yellow]No agent sources configured[/yellow]")
464
+ self.console.print(
465
+ "[dim]Default source 'bobmatnyc/claude-mpm-agents' will be used[/dim]\n"
466
+ )
467
+
468
+ # Step 2: Display available agents
469
+ self.console.print("\n[bold white]═══ Available Agents ═══[/bold white]\n")
470
+
471
+ if agents:
472
+ # Show progress spinner while recommendation service processes agents
473
+ with self.console.status(
474
+ "[bold blue]Preparing agent list...[/bold blue]", spinner="dots"
475
+ ):
476
+ self._display_agents_with_source_info(agents)
477
+ else:
478
+ self.console.print("[yellow]No agents available[/yellow]")
479
+
480
+ def _display_agents_table(self, agents: List[AgentConfig]) -> None:
481
+ """Display a table of available agents."""
482
+ self.agent_display.display_agents_table(agents)
483
+
484
+ def _display_agents_with_pending_states(self, agents: List[AgentConfig]) -> None:
485
+ """Display agents table with pending state indicators."""
486
+ self.agent_display.display_agents_with_pending_states(agents)
487
+
488
+ def _toggle_agents_interactive(self, agents: List[AgentConfig]) -> None:
489
+ """Interactive multi-agent enable/disable with batch save."""
490
+
491
+ # Initialize pending states from current states
492
+ for agent in agents:
493
+ current_state = self.agent_manager.is_agent_enabled(agent.name)
494
+ self.agent_manager.set_agent_enabled_deferred(agent.name, current_state)
495
+
496
+ while True:
497
+ # Display table with pending states
498
+ self._display_agents_with_pending_states(agents)
499
+
500
+ # Show menu
501
+ self.console.print("\n[bold]Toggle Agent Status:[/bold]")
502
+ text_toggle = Text(" ")
503
+ text_toggle.append("[t]", style="bold blue")
504
+ text_toggle.append(" Enter agent IDs to toggle (e.g., '1,3,5' or '1-4')")
505
+ self.console.print(text_toggle)
506
+
507
+ text_all = Text(" ")
508
+ text_all.append("[a]", style="bold blue")
509
+ text_all.append(" Enable all agents")
510
+ self.console.print(text_all)
511
+
512
+ text_none = Text(" ")
513
+ text_none.append("[n]", style="bold blue")
514
+ text_none.append(" Disable all agents")
515
+ self.console.print(text_none)
516
+
517
+ text_save = Text(" ")
518
+ text_save.append("[s]", style="bold green")
519
+ text_save.append(" Save changes and return")
520
+ self.console.print(text_save)
521
+
522
+ text_cancel = Text(" ")
523
+ text_cancel.append("[c]", style="bold magenta")
524
+ text_cancel.append(" Cancel (discard changes)")
525
+ self.console.print(text_cancel)
526
+
527
+ choice = (
528
+ Prompt.ask("[bold blue]Select an option[/bold blue]", default="s")
529
+ .strip()
530
+ .lower()
531
+ )
532
+
533
+ if choice == "s":
534
+ if self.agent_manager.has_pending_changes():
535
+ self.agent_manager.commit_deferred_changes()
536
+ self.console.print("[green]✓ Changes saved successfully![/green]")
537
+
538
+ # Auto-deploy enabled agents to .claude/agents/
539
+ self._auto_deploy_enabled_agents(agents)
540
+ else:
541
+ self.console.print("[yellow]No changes to save.[/yellow]")
542
+ Prompt.ask("Press Enter to continue")
543
+ break
544
+ if choice == "c":
545
+ self.agent_manager.discard_deferred_changes()
546
+ self.console.print("[yellow]Changes discarded.[/yellow]")
547
+ Prompt.ask("Press Enter to continue")
548
+ break
549
+ if choice == "a":
550
+ for agent in agents:
551
+ self.agent_manager.set_agent_enabled_deferred(agent.name, True)
552
+ elif choice == "n":
553
+ for agent in agents:
554
+ self.agent_manager.set_agent_enabled_deferred(agent.name, False)
555
+ elif choice == "t" or choice.replace(",", "").replace("-", "").isdigit():
556
+ selected_ids = self._parse_id_selection(
557
+ choice if choice != "t" else Prompt.ask("Enter IDs"), len(agents)
558
+ )
559
+ for idx in selected_ids:
560
+ if 1 <= idx <= len(agents):
561
+ agent = agents[idx - 1]
562
+ current = self.agent_manager.get_pending_state(agent.name)
563
+ self.agent_manager.set_agent_enabled_deferred(
564
+ agent.name, not current
565
+ )
566
+
567
+ def _auto_deploy_enabled_agents(self, agents: List[AgentConfig]) -> None:
568
+ """Auto-deploy enabled agents after saving configuration.
569
+
570
+ WHY: When users enable agents, they expect them to be deployed
571
+ automatically to .claude/agents/ so they're available for use.
572
+ """
573
+ try:
574
+ # Get list of enabled agents from states
575
+ enabled_agents = [
576
+ agent
577
+ for agent in agents
578
+ if self.agent_manager.is_agent_enabled(agent.name)
579
+ ]
580
+
581
+ if not enabled_agents:
582
+ return
583
+
584
+ # Show deployment progress
585
+ self.console.print(
586
+ f"\n[bold blue]Deploying {len(enabled_agents)} enabled agent(s)...[/bold blue]"
587
+ )
588
+
589
+ # Deploy each enabled agent
590
+ success_count = 0
591
+ failed_count = 0
592
+
593
+ for agent in enabled_agents:
594
+ # Deploy to .claude/agents/ (project-level)
595
+ try:
596
+ if self._deploy_single_agent(agent, show_feedback=False):
597
+ success_count += 1
598
+ self.console.print(f"[green]✓ Deployed: {agent.name}[/green]")
599
+ else:
600
+ failed_count += 1
601
+ self.console.print(f"[yellow]⚠ Skipped: {agent.name}[/yellow]")
602
+ except Exception as e:
603
+ failed_count += 1
604
+ self.logger.error(f"Failed to deploy {agent.name}: {e}")
605
+ self.console.print(f"[red]✗ Failed: {agent.name}[/red]")
606
+
607
+ # Show summary
608
+ if success_count > 0:
609
+ self.console.print(
610
+ f"\n[green]✓ Successfully deployed {success_count} agent(s) to .claude/agents/[/green]"
611
+ )
612
+ if failed_count > 0:
613
+ self.console.print(
614
+ f"[yellow]⚠ {failed_count} agent(s) failed or were skipped[/yellow]"
615
+ )
616
+
617
+ except Exception as e:
618
+ self.logger.error(f"Auto-deployment failed: {e}", exc_info=True)
619
+ self.console.print(f"[red]✗ Auto-deployment error: {e}[/red]")
620
+
621
+ def _customize_agent_template(self, agents: List[AgentConfig]) -> None:
622
+ """Customize agent JSON template."""
623
+ self.template_editor.customize_agent_template(agents)
624
+
625
+ def _edit_agent_template(self, agent: AgentConfig) -> None:
626
+ """Edit an agent's JSON template."""
627
+ self.template_editor.edit_agent_template(agent)
628
+
629
+ def _get_agent_template_path(self, agent_name: str) -> Path:
630
+ """Get the path to an agent's template file."""
631
+ return self.template_editor.get_agent_template_path(agent_name)
632
+
633
+ def _edit_in_external_editor(self, template_path: Path, template: Dict) -> None:
634
+ """Open template in external editor."""
635
+ self.template_editor.edit_in_external_editor(template_path, template)
636
+
637
+ def _modify_template_field(self, template: Dict, template_path: Path) -> None:
638
+ """Add or modify a field in the template."""
639
+ self.template_editor.modify_template_field(template, template_path)
640
+
641
+ def _remove_template_field(self, template: Dict, template_path: Path) -> None:
642
+ """Remove a field from the template."""
643
+ self.template_editor.remove_template_field(template, template_path)
644
+
645
+ def _reset_template(self, agent: AgentConfig, template_path: Path) -> None:
646
+ """Reset template to defaults."""
647
+ self.template_editor.reset_template(agent, template_path)
648
+
649
+ def _create_custom_template_copy(self, agent: AgentConfig, template: Dict) -> None:
650
+ """Create a customized copy of a system template."""
651
+ self.template_editor.create_custom_template_copy(agent, template)
652
+
653
+ def _view_full_template(self, template: Dict) -> None:
654
+ """View the full template without truncation."""
655
+ self.template_editor.view_full_template(template)
656
+
657
+ def _reset_agent_defaults(self, agents: List[AgentConfig]) -> None:
658
+ """Reset an agent to default enabled state and remove custom template."""
659
+ self.template_editor.reset_agent_defaults(agents)
660
+
661
+ def _edit_templates(self) -> None:
662
+ """Template editing interface."""
663
+ self.template_editor.edit_templates_interface()
664
+
665
+ def _manage_behaviors(self) -> None:
666
+ """Behavior file management interface."""
667
+ # Note: BehaviorManager handles its own loop and clears screen
668
+ # but doesn't display our header. We'll need to update BehaviorManager
669
+ # to accept a header callback in the future. For now, just delegate.
670
+ self.behavior_manager.manage_behaviors()
671
+
672
+ def _manage_skills(self) -> None:
673
+ """Skills management interface with questionary checkbox selection."""
674
+ from ...cli.interactive.skills_wizard import SkillsWizard
675
+ from ...skills.skill_manager import get_manager
676
+
677
+ wizard = SkillsWizard()
678
+ manager = get_manager()
679
+
680
+ while True:
681
+ self.console.clear()
682
+ self._display_header()
683
+
684
+ self.console.print("\n[bold]Skills Management[/bold]")
685
+
686
+ # Show action options
687
+ self.console.print("\n[bold]Actions:[/bold]")
688
+ self.console.print(" [1] Install/Uninstall skills")
689
+ self.console.print(" [2] Configure skills for agents")
690
+ self.console.print(" [3] View current skill mappings")
691
+ self.console.print(" [4] Auto-link skills to agents")
692
+ self.console.print(" [b] Back to main menu")
693
+ self.console.print()
694
+
695
+ choice = Prompt.ask("[bold blue]Select an option[/bold blue]", default="b")
696
+
697
+ if choice == "1":
698
+ # Install/Uninstall skills with category-based selection
699
+ self._manage_skill_installation()
700
+
701
+ elif choice == "2":
702
+ # Configure skills interactively
703
+ self.console.clear()
704
+ self._display_header()
705
+
706
+ # Get list of enabled agents
707
+ agents = self.agent_manager.discover_agents()
708
+ # Filter BASE_AGENT from all agent operations (1M-502 Phase 1)
709
+ agents = self._filter_agent_configs(agents, filter_deployed=False)
710
+ enabled_agents = [
711
+ a.name
712
+ for a in agents
713
+ if self.agent_manager.get_pending_state(a.name)
714
+ ]
715
+
716
+ if not enabled_agents:
717
+ self.console.print(
718
+ "[yellow]No agents are currently enabled.[/yellow]"
719
+ )
720
+ self.console.print(
721
+ "Please enable agents first in Agent Management."
722
+ )
723
+ Prompt.ask("\nPress Enter to continue")
724
+ continue
725
+
726
+ # Run skills wizard
727
+ success, mapping = wizard.run_interactive_selection(enabled_agents)
728
+
729
+ if success:
730
+ # Save the configuration
731
+ manager.save_mappings_to_config()
732
+ self.console.print("\n[green]✓ Skills configuration saved![/green]")
733
+ else:
734
+ self.console.print(
735
+ "\n[yellow]Skills configuration cancelled.[/yellow]"
736
+ )
737
+
738
+ Prompt.ask("\nPress Enter to continue")
739
+
740
+ elif choice == "3":
741
+ # View current mappings
742
+ self.console.clear()
743
+ self._display_header()
744
+
745
+ self.console.print("\n[bold]Current Skill Mappings:[/bold]\n")
746
+
747
+ mappings = manager.list_agent_skill_mappings()
748
+ if not mappings:
749
+ self.console.print("[dim]No skill mappings configured yet.[/dim]")
750
+ else:
751
+ from rich.table import Table
752
+
753
+ table = Table(show_header=True, header_style="bold white")
754
+ table.add_column("Agent", style="white", no_wrap=True)
755
+ table.add_column("Skills", style="green", no_wrap=True)
756
+
757
+ for agent_id, skills in mappings.items():
758
+ skills_str = (
759
+ ", ".join(skills) if skills else "[dim](none)[/dim]"
760
+ )
761
+ table.add_row(agent_id, skills_str)
762
+
763
+ self.console.print(table)
764
+
765
+ Prompt.ask("\nPress Enter to continue")
766
+
767
+ elif choice == "4":
768
+ # Auto-link skills
769
+ self.console.clear()
770
+ self._display_header()
771
+
772
+ self.console.print("\n[bold]Auto-Linking Skills to Agents...[/bold]\n")
773
+
774
+ # Get enabled agents
775
+ agents = self.agent_manager.discover_agents()
776
+ # Filter BASE_AGENT from all agent operations (1M-502 Phase 1)
777
+ agents = self._filter_agent_configs(agents, filter_deployed=False)
778
+ enabled_agents = [
779
+ a.name
780
+ for a in agents
781
+ if self.agent_manager.get_pending_state(a.name)
782
+ ]
783
+
784
+ if not enabled_agents:
785
+ self.console.print(
786
+ "[yellow]No agents are currently enabled.[/yellow]"
787
+ )
788
+ self.console.print(
789
+ "Please enable agents first in Agent Management."
790
+ )
791
+ Prompt.ask("\nPress Enter to continue")
792
+ continue
793
+
794
+ # Auto-link
795
+ mapping = wizard._auto_link_skills(enabled_agents)
796
+
797
+ # Display preview
798
+ self.console.print("Auto-linked skills:\n")
799
+ for agent_id, skills in mapping.items():
800
+ self.console.print(f" [yellow]{agent_id}[/yellow]:")
801
+ for skill in skills:
802
+ self.console.print(f" - {skill}")
803
+
804
+ # Confirm
805
+ confirm = Confirm.ask("\nApply this configuration?", default=True)
806
+
807
+ if confirm:
808
+ wizard._apply_skills_configuration(mapping)
809
+ manager.save_mappings_to_config()
810
+ self.console.print("\n[green]✓ Auto-linking complete![/green]")
811
+ else:
812
+ self.console.print("\n[yellow]Auto-linking cancelled.[/yellow]")
813
+
814
+ Prompt.ask("\nPress Enter to continue")
815
+
816
+ elif choice == "b":
817
+ break
818
+ else:
819
+ self.console.print("[red]Invalid choice. Please try again.[/red]")
820
+ Prompt.ask("\nPress Enter to continue")
821
+
822
+ def _detect_skill_patterns(self, skills: list[dict]) -> dict[str, list[dict]]:
823
+ """Group skills by detected common prefixes.
824
+
825
+ Args:
826
+ skills: List of skill dictionaries
827
+
828
+ Returns:
829
+ Dict mapping pattern prefix to list of skills.
830
+ Skills without pattern match go under "" (empty string) key.
831
+ """
832
+ from collections import defaultdict
833
+
834
+ # Count prefix occurrences (try 1-segment and 2-segment prefixes)
835
+ prefix_counts = defaultdict(list)
836
+
837
+ for skill in skills:
838
+ skill_id = skill.get("name", skill.get("skill_id", ""))
839
+
840
+ # Try to extract prefixes (split by hyphen)
841
+ parts = skill_id.split("-")
842
+
843
+ if len(parts) >= 2:
844
+ # Try 2-segment prefix first (e.g., "toolchains-universal")
845
+ two_seg_prefix = f"{parts[0]}-{parts[1]}"
846
+ prefix_counts[two_seg_prefix].append(skill)
847
+
848
+ # Also try 1-segment prefix (e.g., "digitalocean")
849
+ one_seg_prefix = parts[0]
850
+ if one_seg_prefix != two_seg_prefix:
851
+ prefix_counts[one_seg_prefix].append(skill)
852
+
853
+ # Build pattern groups (require at least 2 skills per pattern)
854
+ pattern_groups = defaultdict(list)
855
+ used_skills = set()
856
+
857
+ # Prefer longer (more specific) prefixes
858
+ sorted_prefixes = sorted(prefix_counts.keys(), key=lambda x: (-len(x), x))
859
+
860
+ for prefix in sorted_prefixes:
861
+ matching_skills = prefix_counts[prefix]
862
+
863
+ # Only create a pattern group if we have 2+ skills and they're not already grouped
864
+ available_skills = [s for s in matching_skills if id(s) not in used_skills]
865
+
866
+ if len(available_skills) >= 2:
867
+ pattern_groups[prefix] = available_skills
868
+ used_skills.update(id(s) for s in available_skills)
869
+
870
+ # Add ungrouped skills to "" (Other) group
871
+ for skill in skills:
872
+ if id(skill) not in used_skills:
873
+ pattern_groups[""].append(skill)
874
+
875
+ return dict(pattern_groups)
876
+
877
+ def _get_pattern_icon(self, prefix: str) -> str:
878
+ """Get icon for a pattern prefix.
879
+
880
+ Args:
881
+ prefix: Pattern prefix (e.g., "digitalocean", "vercel")
882
+
883
+ Returns:
884
+ Emoji icon for the pattern
885
+ """
886
+ pattern_icons = {
887
+ "digitalocean": "🌊",
888
+ "aws": "☁️",
889
+ "github": "🐙",
890
+ "google": "🔍",
891
+ "vercel": "▲",
892
+ "netlify": "🦋",
893
+ "universal-testing": "🧪",
894
+ "universal-debugging": "🐛",
895
+ "universal-security": "🔒",
896
+ "toolchains-python": "🐍",
897
+ "toolchains-typescript": "📘",
898
+ "toolchains-javascript": "📒",
899
+ }
900
+ return pattern_icons.get(prefix, "📦")
901
+
902
+ def _manage_skill_installation(self) -> None:
903
+ """Manage skill installation with category-based questionary checkbox selection."""
904
+ import questionary
905
+
906
+ # Get all skills
907
+ all_skills = self._get_all_skills_from_git()
908
+ if not all_skills:
909
+ self.console.print(
910
+ "[yellow]No skills available. Try syncing skills first.[/yellow]"
911
+ )
912
+ Prompt.ask("\nPress Enter to continue")
913
+ return
914
+
915
+ # Get deployed skills
916
+ deployed = self._get_deployed_skill_ids()
917
+
918
+ # Group by category
919
+ grouped = {}
920
+ for skill in all_skills:
921
+ # Try to get category from tags or use toolchain
922
+ category = None
923
+ tags = skill.get("tags", [])
924
+
925
+ # Look for category tag
926
+ for tag in tags:
927
+ if tag in [
928
+ "universal",
929
+ "python",
930
+ "typescript",
931
+ "javascript",
932
+ "go",
933
+ "rust",
934
+ ]:
935
+ category = tag
936
+ break
937
+
938
+ # Fallback to toolchain or universal
939
+ if not category:
940
+ category = skill.get("toolchain", "universal")
941
+
942
+ if category not in grouped:
943
+ grouped[category] = []
944
+ grouped[category].append(skill)
945
+
946
+ # Category icons
947
+ icons = {
948
+ "universal": "🌐",
949
+ "python": "🐍",
950
+ "typescript": "📘",
951
+ "javascript": "📒",
952
+ "go": "🔷",
953
+ "rust": "⚙️",
954
+ }
955
+
956
+ # Sort categories: universal first, then alphabetically
957
+ categories = sorted(grouped.keys(), key=lambda x: (x != "universal", x))
958
+
959
+ while True:
960
+ # Show category selection first
961
+ self.console.clear()
962
+ self._display_header()
963
+ self.console.print("\n[bold cyan]Skills Management[/bold cyan]")
964
+ self.console.print(
965
+ f"[dim]{len(all_skills)} skills available, {len(deployed)} installed[/dim]\n"
966
+ )
967
+
968
+ cat_choices = [
969
+ Choice(
970
+ title=f"{icons.get(cat, '📦')} {cat.title()} ({len(grouped[cat])} skills)",
971
+ value=cat,
972
+ )
973
+ for cat in categories
974
+ ]
975
+ cat_choices.append(Choice(title="← Back to main menu", value="back"))
976
+
977
+ selected_cat = questionary.select(
978
+ "Select a category:", choices=cat_choices, style=self.QUESTIONARY_STYLE
979
+ ).ask()
980
+
981
+ if selected_cat is None or selected_cat == "back":
982
+ return
983
+
984
+ # Show skills in category with checkbox selection
985
+ category_skills = grouped[selected_cat]
986
+
987
+ # Detect pattern groups within category
988
+ pattern_groups = self._detect_skill_patterns(category_skills)
989
+
990
+ # Build choices with pattern grouping and installation status
991
+ skill_choices = []
992
+
993
+ # Track which skills belong to which group for expansion later
994
+ group_to_skills = {}
995
+
996
+ # Sort pattern groups: "" (Other) last, rest alphabetically
997
+ sorted_patterns = sorted(pattern_groups.keys(), key=lambda x: (x == "", x))
998
+
999
+ for pattern in sorted_patterns:
1000
+ pattern_skills = pattern_groups[pattern]
1001
+
1002
+ # Skip empty groups
1003
+ if not pattern_skills:
1004
+ continue
1005
+
1006
+ # Collect skill IDs in this group
1007
+ skill_ids_in_group = []
1008
+ for skill in pattern_skills:
1009
+ skill_id = skill.get("name", skill.get("skill_id", "unknown"))
1010
+ skill_ids_in_group.append(skill_id)
1011
+
1012
+ # Check if all skills in group are installed
1013
+ all_installed = all(
1014
+ skill.get(
1015
+ "deployment_name", skill.get("name", skill.get("skill_id"))
1016
+ )
1017
+ in deployed
1018
+ or skill.get("name", skill.get("skill_id")) in deployed
1019
+ for skill in pattern_skills
1020
+ )
1021
+
1022
+ # Add pattern group header as selectable choice
1023
+ if pattern:
1024
+ # Named pattern group
1025
+ pattern_icon = self._get_pattern_icon(pattern)
1026
+ skill_count = len(pattern_skills)
1027
+ group_key = f"__group__:{pattern}"
1028
+ group_to_skills[group_key] = skill_ids_in_group
1029
+
1030
+ skill_choices.append(
1031
+ Choice(
1032
+ title=f"{pattern_icon} {pattern} ({skill_count} skills) [Select All]",
1033
+ value=group_key,
1034
+ checked=all_installed,
1035
+ )
1036
+ )
1037
+ elif pattern_skills:
1038
+ # "Other" group - only show if there are skills
1039
+ group_key = "__group__:Other"
1040
+ group_to_skills[group_key] = skill_ids_in_group
1041
+
1042
+ skill_choices.append(
1043
+ Choice(
1044
+ title=f"📦 Other ({len(pattern_skills)} skills) [Select All]",
1045
+ value=group_key,
1046
+ checked=all_installed,
1047
+ )
1048
+ )
1049
+
1050
+ # Add skills in this pattern group
1051
+ for skill in sorted(pattern_skills, key=lambda x: x.get("name", "")):
1052
+ skill_id = skill.get("name", skill.get("skill_id", "unknown"))
1053
+ deploy_name = skill.get("deployment_name", skill_id)
1054
+ description = skill.get("description", "")[:50]
1055
+
1056
+ # Check if installed
1057
+ is_installed = deploy_name in deployed or skill_id in deployed
1058
+
1059
+ # Add indentation for pattern-grouped skills (all skills are indented)
1060
+ skill_choices.append(
1061
+ Choice(
1062
+ title=f" {skill_id} - {description}",
1063
+ value=skill_id,
1064
+ checked=is_installed,
1065
+ )
1066
+ )
1067
+
1068
+ # Add spacing between pattern groups (not after last group)
1069
+ if pattern != sorted_patterns[-1]:
1070
+ skill_choices.append(Separator())
1071
+
1072
+ self.console.clear()
1073
+ self._display_header()
1074
+ self.console.print(
1075
+ f"\n{icons.get(selected_cat, '📦')} [bold]{selected_cat.title()}[/bold]"
1076
+ )
1077
+ self.console.print(
1078
+ "[dim]Use spacebar to toggle individual skills or entire groups, enter to confirm[/dim]\n"
1079
+ )
1080
+
1081
+ selected = questionary.checkbox(
1082
+ "Select skills to install:",
1083
+ choices=skill_choices,
1084
+ style=self.QUESTIONARY_STYLE,
1085
+ ).ask()
1086
+
1087
+ if selected is None:
1088
+ continue # User cancelled, go back to category selection
1089
+
1090
+ # Process group selections - expand to individual skills
1091
+ selected_set = set()
1092
+ for item in selected:
1093
+ if item.startswith("__group__:"):
1094
+ # Expand group selection to all skills in that group
1095
+ selected_set.update(group_to_skills[item])
1096
+ else:
1097
+ # Individual skill selection
1098
+ selected_set.add(item)
1099
+
1100
+ current_in_cat = set()
1101
+
1102
+ # Find currently installed skills in this category
1103
+ for skill in category_skills:
1104
+ skill_id = skill.get("name", skill.get("skill_id", "unknown"))
1105
+ deploy_name = skill.get("deployment_name", skill_id)
1106
+ if deploy_name in deployed or skill_id in deployed:
1107
+ current_in_cat.add(skill_id)
1108
+
1109
+ # Install newly selected
1110
+ to_install = selected_set - current_in_cat
1111
+ for skill_id in to_install:
1112
+ skill = next(
1113
+ (
1114
+ s
1115
+ for s in category_skills
1116
+ if s.get("name") == skill_id or s.get("skill_id") == skill_id
1117
+ ),
1118
+ None,
1119
+ )
1120
+ if skill:
1121
+ self._install_skill_from_dict(skill)
1122
+ self.console.print(f"[green]✓ Installed {skill_id}[/green]")
1123
+
1124
+ # Uninstall deselected
1125
+ to_uninstall = current_in_cat - selected_set
1126
+ for skill_id in to_uninstall:
1127
+ # Find the skill to get deployment_name
1128
+ skill = next(
1129
+ (
1130
+ s
1131
+ for s in category_skills
1132
+ if s.get("name") == skill_id or s.get("skill_id") == skill_id
1133
+ ),
1134
+ None,
1135
+ )
1136
+ if skill:
1137
+ deploy_name = skill.get("deployment_name", skill_id)
1138
+ # Use the name that's actually in deployed set
1139
+ name_to_uninstall = (
1140
+ deploy_name if deploy_name in deployed else skill_id
1141
+ )
1142
+ self._uninstall_skill_by_name(name_to_uninstall)
1143
+ self.console.print(f"[yellow]✗ Uninstalled {skill_id}[/yellow]")
1144
+
1145
+ # Update deployed set for next iteration
1146
+ deployed = self._get_deployed_skill_ids()
1147
+
1148
+ # Show completion message
1149
+ if to_install or to_uninstall:
1150
+ Prompt.ask("\nPress Enter to continue")
1151
+
1152
+ def _get_all_skills_from_git(self) -> list:
1153
+ """Get all skills from Git-based skill manager.
1154
+
1155
+ Returns:
1156
+ List of skill dicts with full metadata from GitSkillSourceManager.
1157
+ """
1158
+ from ...config.skill_sources import SkillSourceConfiguration
1159
+ from ...services.skills.git_skill_source_manager import GitSkillSourceManager
1160
+
1161
+ try:
1162
+ config = SkillSourceConfiguration()
1163
+ manager = GitSkillSourceManager(config)
1164
+ return manager.get_all_skills()
1165
+ except Exception as e:
1166
+ self.console.print(
1167
+ f"[yellow]Warning: Could not load Git skills: {e}[/yellow]"
1168
+ )
1169
+ return []
1170
+
1171
+ def _display_skills_table_grouped(self) -> None:
1172
+ """Display skills in a table grouped by category, like agents."""
1173
+ from rich import box
1174
+ from rich.table import Table
1175
+
1176
+ # Get all skills from Git manager
1177
+ all_skills = self._get_all_skills_from_git()
1178
+ deployed_ids = self._get_deployed_skill_ids()
1179
+
1180
+ if not all_skills:
1181
+ self.console.print(
1182
+ "[yellow]No skills available. Try syncing skills first.[/yellow]"
1183
+ )
1184
+ return
1185
+
1186
+ # Group skills by category/toolchain
1187
+ grouped = {}
1188
+ for skill in all_skills:
1189
+ # Try to get category from tags or use toolchain
1190
+ category = None
1191
+ tags = skill.get("tags", [])
1192
+
1193
+ # Look for category tag
1194
+ for tag in tags:
1195
+ if tag in [
1196
+ "universal",
1197
+ "python",
1198
+ "typescript",
1199
+ "javascript",
1200
+ "go",
1201
+ "rust",
1202
+ ]:
1203
+ category = tag
1204
+ break
1205
+
1206
+ # Fallback to toolchain or universal
1207
+ if not category:
1208
+ category = skill.get("toolchain", "universal")
1209
+
1210
+ if category not in grouped:
1211
+ grouped[category] = []
1212
+ grouped[category].append(skill)
1213
+
1214
+ # Sort categories: universal first, then alphabetically
1215
+ categories = sorted(grouped.keys(), key=lambda x: (x != "universal", x))
1216
+
1217
+ # Track global skill number across all categories
1218
+ skill_counter = 0
1219
+
1220
+ for category in categories:
1221
+ category_skills = grouped[category]
1222
+
1223
+ # Category header with icon
1224
+ icons = {
1225
+ "universal": "🌐",
1226
+ "python": "🐍",
1227
+ "typescript": "📘",
1228
+ "javascript": "📒",
1229
+ "go": "🔷",
1230
+ "rust": "⚙️",
1231
+ }
1232
+ icon = icons.get(category, "📦")
1233
+ self.console.print(
1234
+ f"\n{icon} [bold cyan]{category.title()}[/bold cyan] ({len(category_skills)} skills)"
1235
+ )
1236
+
1237
+ # Create table for this category
1238
+ table = Table(show_header=True, header_style="bold", box=box.SIMPLE)
1239
+ table.add_column("#", style="dim", width=4)
1240
+ table.add_column("Skill ID", style="cyan", width=35)
1241
+ table.add_column("Description", style="white", width=45)
1242
+ table.add_column("Status", style="green", width=12)
1243
+
1244
+ for skill in sorted(category_skills, key=lambda x: x.get("name", "")):
1245
+ skill_counter += 1
1246
+ skill_id = skill.get("name", skill.get("skill_id", "unknown"))
1247
+ # Use deployment_name for matching if available
1248
+ deploy_name = skill.get("deployment_name", skill_id)
1249
+ description = skill.get("description", "")[:45]
1250
+
1251
+ # Check if installed - handle both deployment_name and skill_id
1252
+ is_installed = deploy_name in deployed_ids or skill_id in deployed_ids
1253
+ status = "[green]✓ Installed[/green]" if is_installed else "Available"
1254
+
1255
+ table.add_row(str(skill_counter), skill_id, description, status)
1256
+
1257
+ self.console.print(table)
1258
+
1259
+ # Summary
1260
+ total = len(all_skills)
1261
+ installed = sum(
1262
+ 1
1263
+ for s in all_skills
1264
+ if s.get("deployment_name", s.get("name", "")) in deployed_ids
1265
+ or s.get("name", "") in deployed_ids
1266
+ )
1267
+ self.console.print(
1268
+ f"\n[dim]Showing {total} skills ({installed} installed)[/dim]"
1269
+ )
1270
+
1271
+ def _get_deployed_skill_ids(self) -> set:
1272
+ """Get set of deployed skill IDs from .claude/skills/ directory.
1273
+
1274
+ Returns:
1275
+ Set of skill directory names and common variations for matching.
1276
+ """
1277
+ from pathlib import Path
1278
+
1279
+ skills_dir = Path.cwd() / ".claude" / "skills"
1280
+ if not skills_dir.exists():
1281
+ return set()
1282
+
1283
+ # Each deployed skill is a directory in .claude/skills/
1284
+ deployed_ids = set()
1285
+ for skill_dir in skills_dir.iterdir():
1286
+ if skill_dir.is_dir() and not skill_dir.name.startswith("."):
1287
+ # Add both the directory name and common variations
1288
+ deployed_ids.add(skill_dir.name)
1289
+ # Also add without prefix for matching (e.g., universal-testing -> testing)
1290
+ if skill_dir.name.startswith("universal-"):
1291
+ deployed_ids.add(skill_dir.name.replace("universal-", "", 1))
1292
+
1293
+ return deployed_ids
1294
+
1295
+ def _install_skill(self, skill) -> None:
1296
+ """Install a skill to .claude/skills/ directory."""
1297
+ import shutil
1298
+ from pathlib import Path
1299
+
1300
+ # Target directory
1301
+ target_dir = Path.cwd() / ".claude" / "skills" / skill.skill_id
1302
+ target_dir.mkdir(parents=True, exist_ok=True)
1303
+
1304
+ # Copy skill file(s)
1305
+ if skill.path.is_file():
1306
+ # Single file skill - copy to skill.md in target directory
1307
+ shutil.copy2(skill.path, target_dir / "skill.md")
1308
+ elif skill.path.is_dir():
1309
+ # Directory-based skill - copy all contents
1310
+ for item in skill.path.iterdir():
1311
+ if item.is_file():
1312
+ shutil.copy2(item, target_dir / item.name)
1313
+ elif item.is_dir():
1314
+ shutil.copytree(item, target_dir / item.name, dirs_exist_ok=True)
1315
+
1316
+ def _uninstall_skill(self, skill) -> None:
1317
+ """Uninstall a skill from .claude/skills/ directory."""
1318
+ import shutil
1319
+ from pathlib import Path
1320
+
1321
+ target_dir = Path.cwd() / ".claude" / "skills" / skill.skill_id
1322
+ if target_dir.exists():
1323
+ shutil.rmtree(target_dir)
1324
+
1325
+ def _install_skill_from_dict(self, skill_dict: dict) -> None:
1326
+ """Install a skill from Git skill dict to .claude/skills/ directory.
1327
+
1328
+ Args:
1329
+ skill_dict: Skill metadata dict from GitSkillSourceManager.get_all_skills()
1330
+ """
1331
+ from pathlib import Path
1332
+
1333
+ skill_id = skill_dict.get("name", skill_dict.get("skill_id", "unknown"))
1334
+ content = skill_dict.get("content", "")
1335
+
1336
+ if not content:
1337
+ self.console.print(
1338
+ f"[yellow]Warning: Skill '{skill_id}' has no content[/yellow]"
1339
+ )
1340
+ return
1341
+
1342
+ # Target directory using deployment_name if available
1343
+ deploy_name = skill_dict.get("deployment_name", skill_id)
1344
+ target_dir = Path.cwd() / ".claude" / "skills" / deploy_name
1345
+ target_dir.mkdir(parents=True, exist_ok=True)
1346
+
1347
+ # Write skill content to skill.md
1348
+ skill_file = target_dir / "skill.md"
1349
+ skill_file.write_text(content, encoding="utf-8")
1350
+
1351
+ def _uninstall_skill_by_name(self, skill_name: str) -> None:
1352
+ """Uninstall a skill by name from .claude/skills/ directory.
1353
+
1354
+ Args:
1355
+ skill_name: Name of skill directory to remove
1356
+ """
1357
+ import shutil
1358
+ from pathlib import Path
1359
+
1360
+ target_dir = Path.cwd() / ".claude" / "skills" / skill_name
1361
+ if target_dir.exists():
1362
+ shutil.rmtree(target_dir)
1363
+
1364
+ def _display_behavior_files(self) -> None:
1365
+ """Display current behavior files."""
1366
+ self.behavior_manager.display_behavior_files()
1367
+
1368
+ def _edit_identity_config(self) -> None:
1369
+ """Edit identity configuration."""
1370
+ self.behavior_manager.edit_identity_config()
1371
+
1372
+ def _edit_workflow_config(self) -> None:
1373
+ """Edit workflow configuration."""
1374
+ self.behavior_manager.edit_workflow_config()
1375
+
1376
+ def _import_behavior_file(self) -> None:
1377
+ """Import a behavior file."""
1378
+ self.behavior_manager.import_behavior_file()
1379
+
1380
+ def _export_behavior_file(self) -> None:
1381
+ """Export a behavior file."""
1382
+ self.behavior_manager.export_behavior_file()
1383
+
1384
+ def _manage_startup_configuration(self) -> bool:
1385
+ """Manage startup configuration for MCP services and agents."""
1386
+ return self.startup_manager.manage_startup_configuration()
1387
+
1388
+ def _load_startup_configuration(self, config: Config) -> Dict:
1389
+ """Load current startup configuration from config."""
1390
+ return self.startup_manager.load_startup_configuration(config)
1391
+
1392
+ def _display_startup_configuration(self, startup_config: Dict) -> None:
1393
+ """Display current startup configuration in a table."""
1394
+ self.startup_manager.display_startup_configuration(startup_config)
1395
+
1396
+ def _configure_mcp_services(self, startup_config: Dict, config: Config) -> None:
1397
+ """Configure which MCP services to enable at startup."""
1398
+ self.startup_manager.configure_mcp_services(startup_config, config)
1399
+
1400
+ def _configure_hook_services(self, startup_config: Dict, config: Config) -> None:
1401
+ """Configure which hook services to enable at startup."""
1402
+ self.startup_manager.configure_hook_services(startup_config, config)
1403
+
1404
+ def _configure_system_agents(self, startup_config: Dict, config: Config) -> None:
1405
+ """Configure which system agents to deploy at startup."""
1406
+ self.startup_manager.configure_system_agents(startup_config, config)
1407
+
1408
+ def _parse_id_selection(self, selection: str, max_id: int) -> List[int]:
1409
+ """Parse ID selection string (e.g., '1,3,5' or '1-4')."""
1410
+ return parse_id_selection(selection, max_id)
1411
+
1412
+ def _enable_all_services(self, startup_config: Dict, config: Config) -> None:
1413
+ """Enable all services and agents."""
1414
+ self.startup_manager.enable_all_services(startup_config, config)
1415
+
1416
+ def _disable_all_services(self, startup_config: Dict, config: Config) -> None:
1417
+ """Disable all services and agents."""
1418
+ self.startup_manager.disable_all_services(startup_config, config)
1419
+
1420
+ def _reset_to_defaults(self, startup_config: Dict, config: Config) -> None:
1421
+ """Reset startup configuration to defaults."""
1422
+ self.startup_manager.reset_to_defaults(startup_config, config)
1423
+
1424
+ def _save_startup_configuration(self, startup_config: Dict, config: Config) -> bool:
1425
+ """Save startup configuration to config file and return whether to proceed to startup."""
1426
+ return self.startup_manager.save_startup_configuration(startup_config, config)
1427
+
1428
+ def _save_all_configuration(self) -> bool:
1429
+ """Save all configuration changes across all contexts."""
1430
+ return self.startup_manager.save_all_configuration()
1431
+
1432
+ def _launch_claude_mpm(self) -> None:
1433
+ """Launch Claude MPM run command, replacing current process."""
1434
+ self.navigation.launch_claude_mpm()
1435
+
1436
+ def _switch_scope(self) -> None:
1437
+ """Switch between project and user scope."""
1438
+ self.navigation.switch_scope()
1439
+ # Sync scope back from navigation
1440
+ self.current_scope = self.navigation.current_scope
1441
+
1442
+ def _show_version_info_interactive(self) -> None:
1443
+ """Show version information in interactive mode."""
1444
+ self.persistence.show_version_info_interactive()
1445
+
1446
+ # Non-interactive command methods
1447
+
1448
+ def _list_agents_non_interactive(self) -> CommandResult:
1449
+ """List agents in non-interactive mode."""
1450
+ agents = self.agent_manager.discover_agents()
1451
+ # Filter BASE_AGENT from all agent lists (1M-502 Phase 1)
1452
+ agents = self._filter_agent_configs(agents, filter_deployed=False)
1453
+
1454
+ data = []
1455
+ for agent in agents:
1456
+ data.append(
1457
+ {
1458
+ "name": agent.name,
1459
+ "enabled": self.agent_manager.is_agent_enabled(agent.name),
1460
+ "description": agent.description,
1461
+ "dependencies": agent.dependencies,
1462
+ }
1463
+ )
1464
+
1465
+ # Print as JSON for scripting
1466
+ print(json.dumps(data, indent=2))
1467
+
1468
+ return CommandResult.success_result("Agents listed", data={"agents": data})
1469
+
1470
+ def _enable_agent_non_interactive(self, agent_name: str) -> CommandResult:
1471
+ """Enable an agent in non-interactive mode."""
1472
+ try:
1473
+ self.agent_manager.set_agent_enabled(agent_name, True)
1474
+ return CommandResult.success_result(f"Agent '{agent_name}' enabled")
1475
+ except Exception as e:
1476
+ return CommandResult.error_result(f"Failed to enable agent: {e}")
1477
+
1478
+ def _disable_agent_non_interactive(self, agent_name: str) -> CommandResult:
1479
+ """Disable an agent in non-interactive mode."""
1480
+ try:
1481
+ self.agent_manager.set_agent_enabled(agent_name, False)
1482
+ return CommandResult.success_result(f"Agent '{agent_name}' disabled")
1483
+ except Exception as e:
1484
+ return CommandResult.error_result(f"Failed to disable agent: {e}")
1485
+
1486
+ def _export_config(self, file_path: str) -> CommandResult:
1487
+ """Export configuration to a file."""
1488
+ return self.persistence.export_config(file_path)
1489
+
1490
+ def _import_config(self, file_path: str) -> CommandResult:
1491
+ """Import configuration from a file."""
1492
+ return self.persistence.import_config(file_path)
1493
+
1494
+ def _show_version_info(self) -> CommandResult:
1495
+ """Show version information in non-interactive mode."""
1496
+ return self.persistence.show_version_info()
1497
+
1498
+ def _install_hooks(self, force: bool = False) -> CommandResult:
1499
+ """Install Claude MPM hooks for Claude Code integration."""
1500
+ # Share logger with hook manager for consistent error logging
1501
+ self.hook_manager.logger = self.logger
1502
+ return self.hook_manager.install_hooks(force=force)
1503
+
1504
+ def _verify_hooks(self) -> CommandResult:
1505
+ """Verify that Claude MPM hooks are properly installed."""
1506
+ # Share logger with hook manager for consistent error logging
1507
+ self.hook_manager.logger = self.logger
1508
+ return self.hook_manager.verify_hooks()
1509
+
1510
+ def _uninstall_hooks(self) -> CommandResult:
1511
+ """Uninstall Claude MPM hooks."""
1512
+ # Share logger with hook manager for consistent error logging
1513
+ self.hook_manager.logger = self.logger
1514
+ return self.hook_manager.uninstall_hooks()
1515
+
1516
+ def _run_agent_management(self) -> CommandResult:
1517
+ """Jump directly to agent management."""
1518
+ try:
1519
+ self._manage_agents()
1520
+ return CommandResult.success_result("Agent management completed")
1521
+ except KeyboardInterrupt:
1522
+ return CommandResult.success_result("Agent management cancelled")
1523
+ except Exception as e:
1524
+ return CommandResult.error_result(f"Agent management failed: {e}")
1525
+
1526
+ def _run_template_editing(self) -> CommandResult:
1527
+ """Jump directly to template editing."""
1528
+ try:
1529
+ self._edit_templates()
1530
+ return CommandResult.success_result("Template editing completed")
1531
+ except KeyboardInterrupt:
1532
+ return CommandResult.success_result("Template editing cancelled")
1533
+ except Exception as e:
1534
+ return CommandResult.error_result(f"Template editing failed: {e}")
1535
+
1536
+ def _run_behavior_management(self) -> CommandResult:
1537
+ """Jump directly to behavior management."""
1538
+ return self.behavior_manager.run_behavior_management()
1539
+
1540
+ def _run_startup_configuration(self) -> CommandResult:
1541
+ """Jump directly to startup configuration."""
1542
+ try:
1543
+ proceed = self._manage_startup_configuration()
1544
+ if proceed:
1545
+ return CommandResult.success_result(
1546
+ "Startup configuration saved, proceeding to startup"
1547
+ )
1548
+ return CommandResult.success_result("Startup configuration completed")
1549
+ except KeyboardInterrupt:
1550
+ return CommandResult.success_result("Startup configuration cancelled")
1551
+ except Exception as e:
1552
+ return CommandResult.error_result(f"Startup configuration failed: {e}")
1553
+
1554
+ # ========================================================================
1555
+ # Enhanced Agent Management Methods (Remote Agent Discovery Integration)
1556
+ # ========================================================================
1557
+
1558
+ def _get_configured_sources(self) -> List[Dict]:
1559
+ """Get list of configured agent sources with agent counts."""
1560
+ try:
1561
+ from claude_mpm.config.agent_sources import AgentSourceConfiguration
1562
+
1563
+ config = AgentSourceConfiguration.load()
1564
+
1565
+ # Convert repositories to source dictionaries
1566
+ sources = []
1567
+ for repo in config.repositories:
1568
+ # Extract identifier from repository
1569
+ identifier = repo.identifier
1570
+
1571
+ # Count agents in cache
1572
+ # Note: identifier already includes subdirectory path (e.g., "bobmatnyc/claude-mpm-agents/agents")
1573
+ cache_dir = (
1574
+ Path.home() / ".claude-mpm" / "cache" / "agents" / identifier
1575
+ )
1576
+ agent_count = 0
1577
+ if cache_dir.exists():
1578
+ # cache_dir IS the agents directory - no need to append /agents
1579
+ agent_count = len(list(cache_dir.rglob("*.md")))
1580
+
1581
+ sources.append(
1582
+ {
1583
+ "identifier": identifier,
1584
+ "url": repo.url,
1585
+ "enabled": repo.enabled,
1586
+ "priority": repo.priority,
1587
+ "agent_count": agent_count,
1588
+ }
1589
+ )
1590
+
1591
+ return sources
1592
+ except Exception as e:
1593
+ self.logger.warning(f"Failed to get configured sources: {e}")
1594
+ return []
1595
+
1596
+ def _filter_agent_configs(
1597
+ self, agents: List[AgentConfig], filter_deployed: bool = False
1598
+ ) -> List[AgentConfig]:
1599
+ """Filter AgentConfig objects using agent_filters utilities.
1600
+
1601
+ Converts AgentConfig objects to dictionaries for filtering,
1602
+ then back to AgentConfig. Always filters BASE_AGENT.
1603
+ Optionally filters deployed agents.
1604
+
1605
+ Args:
1606
+ agents: List of AgentConfig objects
1607
+ filter_deployed: Whether to filter out deployed agents (default: False)
1608
+
1609
+ Returns:
1610
+ Filtered list of AgentConfig objects
1611
+ """
1612
+ # Convert AgentConfig to dict format for filtering
1613
+ agent_dicts = []
1614
+ for agent in agents:
1615
+ agent_dicts.append(
1616
+ {
1617
+ "agent_id": agent.name,
1618
+ "name": agent.name,
1619
+ "description": agent.description,
1620
+ "deployed": getattr(agent, "is_deployed", False),
1621
+ }
1622
+ )
1623
+
1624
+ # Apply filters (always filter BASE_AGENT)
1625
+ filtered_dicts = apply_all_filters(
1626
+ agent_dicts, filter_base=True, filter_deployed=filter_deployed
1627
+ )
1628
+
1629
+ # Convert back to AgentConfig objects
1630
+ filtered_names = {d["agent_id"] for d in filtered_dicts}
1631
+ return [a for a in agents if a.name in filtered_names]
1632
+
1633
+ @staticmethod
1634
+ def _calculate_column_widths(
1635
+ terminal_width: int, columns: Dict[str, int]
1636
+ ) -> Dict[str, int]:
1637
+ """Calculate dynamic column widths based on terminal size.
1638
+
1639
+ Args:
1640
+ terminal_width: Current terminal width in characters
1641
+ columns: Dict mapping column names to minimum widths
1642
+
1643
+ Returns:
1644
+ Dict mapping column names to calculated widths
1645
+
1646
+ Design:
1647
+ - Ensures minimum widths are respected
1648
+ - Distributes extra space proportionally
1649
+ - Handles narrow terminals gracefully (minimum 80 chars)
1650
+ """
1651
+ # Ensure minimum terminal width
1652
+ min_terminal_width = 80
1653
+ terminal_width = max(terminal_width, min_terminal_width)
1654
+
1655
+ # Calculate total minimum width needed
1656
+ total_min_width = sum(columns.values())
1657
+
1658
+ # Account for table borders and padding (2 chars per column + 2 for edges)
1659
+ overhead = (len(columns) * 2) + 2
1660
+ available_width = terminal_width - overhead
1661
+
1662
+ # If we have extra space, distribute proportionally
1663
+ if available_width > total_min_width:
1664
+ extra_space = available_width - total_min_width
1665
+ total_weight = sum(columns.values())
1666
+
1667
+ result = {}
1668
+ for col_name, min_width in columns.items():
1669
+ # Distribute extra space based on minimum width proportion
1670
+ proportion = min_width / total_weight
1671
+ extra = int(extra_space * proportion)
1672
+ result[col_name] = min_width + extra
1673
+ return result
1674
+ # Terminal too narrow, use minimum widths
1675
+ return columns.copy()
1676
+
1677
+ def _format_display_name(self, name: str) -> str:
1678
+ """Format internal agent name to human-readable display name.
1679
+
1680
+ Converts underscores/hyphens to spaces and title-cases.
1681
+ Examples:
1682
+ agentic_coder_optimizer -> Agentic Coder Optimizer
1683
+ python-engineer -> Python Engineer
1684
+ api_qa_agent -> Api Qa Agent
1685
+
1686
+ Args:
1687
+ name: Internal agent name (may contain underscores, hyphens)
1688
+
1689
+ Returns:
1690
+ Human-readable display name
1691
+ """
1692
+ return name.replace("_", " ").replace("-", " ").title()
1693
+
1694
+ def _display_agents_with_source_info(self, agents: List[AgentConfig]) -> None:
1695
+ """Display agents table with source information and installation status."""
1696
+ from rich.table import Table
1697
+
1698
+ # Get recommended agents for this project
1699
+ try:
1700
+ recommended_agents = self.recommendation_service.get_recommended_agents(
1701
+ str(self.project_dir)
1702
+ )
1703
+ except Exception as e:
1704
+ self.logger.warning(f"Failed to get recommended agents: {e}")
1705
+ recommended_agents = set()
1706
+
1707
+ # Get terminal width and calculate dynamic column widths
1708
+ terminal_width = shutil.get_terminal_size().columns
1709
+ min_widths = {
1710
+ "#": 4,
1711
+ "Agent ID": 30,
1712
+ "Name": 20,
1713
+ "Source": 15,
1714
+ "Status": 10,
1715
+ }
1716
+ widths = self._calculate_column_widths(terminal_width, min_widths)
1717
+
1718
+ agents_table = Table(show_header=True, header_style="bold cyan")
1719
+ agents_table.add_column(
1720
+ "#", style="bright_black", width=widths["#"], no_wrap=True
1721
+ )
1722
+ agents_table.add_column(
1723
+ "Agent ID",
1724
+ style="bright_black",
1725
+ width=widths["Agent ID"],
1726
+ no_wrap=True,
1727
+ overflow="ellipsis",
1728
+ )
1729
+ agents_table.add_column(
1730
+ "Name",
1731
+ style="bright_cyan",
1732
+ width=widths["Name"],
1733
+ no_wrap=True,
1734
+ overflow="ellipsis",
1735
+ )
1736
+ agents_table.add_column(
1737
+ "Source",
1738
+ style="bright_yellow",
1739
+ width=widths["Source"],
1740
+ no_wrap=True,
1741
+ )
1742
+ agents_table.add_column(
1743
+ "Status", style="bright_black", width=widths["Status"], no_wrap=True
1744
+ )
1745
+
1746
+ # FIX 3: Get deployed agent IDs once, before the loop (efficiency)
1747
+ deployed_ids = get_deployed_agent_ids()
1748
+
1749
+ recommended_count = 0
1750
+ for idx, agent in enumerate(agents, 1):
1751
+ # Determine source with repo name
1752
+ source_type = getattr(agent, "source_type", "local")
1753
+
1754
+ if source_type == "remote":
1755
+ # Get repo name from agent metadata
1756
+ source_dict = getattr(agent, "source_dict", {})
1757
+ repo_url = source_dict.get("source", "")
1758
+
1759
+ # Extract repo name from URL
1760
+ if (
1761
+ "bobmatnyc/claude-mpm" in repo_url
1762
+ or "claude-mpm" in repo_url.lower()
1763
+ ):
1764
+ source_label = "MPM Agents"
1765
+ elif "/" in repo_url:
1766
+ # Extract last part of org/repo
1767
+ parts = repo_url.rstrip("/").split("/")
1768
+ if len(parts) >= 2:
1769
+ source_label = f"{parts[-2]}/{parts[-1]}"
1770
+ else:
1771
+ source_label = "Community"
1772
+ else:
1773
+ source_label = "Community"
1774
+ else:
1775
+ source_label = "Local"
1776
+
1777
+ # FIX 2: Check actual deployment status from .claude/agents/ directory
1778
+ # Use agent_id (technical ID like "python-engineer") not display name
1779
+ agent_id = getattr(agent, "agent_id", agent.name)
1780
+ is_installed = agent_id in deployed_ids
1781
+ if is_installed:
1782
+ status = "[green]Installed[/green]"
1783
+ else:
1784
+ status = "Available"
1785
+
1786
+ # Check if agent is recommended
1787
+ # Handle both hierarchical paths (e.g., "engineer/backend/python-engineer")
1788
+ # and leaf names (e.g., "python-engineer")
1789
+ agent_full_path = agent.name
1790
+ agent_leaf_name = (
1791
+ agent_full_path.split("/")[-1]
1792
+ if "/" in agent_full_path
1793
+ else agent_full_path
1794
+ )
1795
+
1796
+ for recommended_id in recommended_agents:
1797
+ # Check if the recommended_id matches either the full path or just the leaf name
1798
+ recommended_leaf = (
1799
+ recommended_id.split("/")[-1]
1800
+ if "/" in recommended_id
1801
+ else recommended_id
1802
+ )
1803
+ if (
1804
+ agent_full_path == recommended_id
1805
+ or agent_leaf_name == recommended_leaf
1806
+ ):
1807
+ recommended_count += 1
1808
+ break
1809
+
1810
+ # FIX 1: Show agent_id (technical ID) in first column, not display name
1811
+ agent_id_display = getattr(agent, "agent_id", agent.name)
1812
+
1813
+ # Get display name and format it properly
1814
+ # Raw display_name from YAML may contain underscores (e.g., "agentic_coder_optimizer")
1815
+ raw_display_name = getattr(agent, "display_name", agent.name)
1816
+ display_name = self._format_display_name(raw_display_name)
1817
+
1818
+ agents_table.add_row(
1819
+ str(idx), agent_id_display, display_name, source_label, status
1820
+ )
1821
+
1822
+ self.console.print(agents_table)
1823
+
1824
+ # Show legend if there are recommended agents
1825
+ if recommended_count > 0:
1826
+ # Get detection summary for context
1827
+ try:
1828
+ summary = self.recommendation_service.get_detection_summary(
1829
+ str(self.project_dir)
1830
+ )
1831
+ detected_langs = (
1832
+ ", ".join(summary.get("detected_languages", [])) or "None"
1833
+ )
1834
+ ", ".join(summary.get("detected_frameworks", [])) or "None"
1835
+ self.console.print(
1836
+ f"\n[dim]* = recommended for this project "
1837
+ f"(detected: {detected_langs})[/dim]"
1838
+ )
1839
+ except Exception:
1840
+ self.console.print("\n[dim]* = recommended for this project[/dim]")
1841
+
1842
+ # Show installed vs available count (use deployed_ids for accuracy)
1843
+ # Use agent_id (technical ID) for comparison, not display name
1844
+ installed_count = sum(
1845
+ 1 for a in agents if getattr(a, "agent_id", a.name) in deployed_ids
1846
+ )
1847
+ available_count = len(agents) - installed_count
1848
+ self.console.print(
1849
+ f"\n[green]✓ {installed_count} installed[/green] | "
1850
+ f"[dim]{available_count} available[/dim] | "
1851
+ f"[yellow]{recommended_count} recommended[/yellow] | "
1852
+ f"[dim]Total: {len(agents)}[/dim]"
1853
+ )
1854
+
1855
+ def _manage_sources(self) -> None:
1856
+ """Interactive source management."""
1857
+ self.console.print("\n[bold white]═══ Manage Agent Sources ═══[/bold white]\n")
1858
+ self.console.print(
1859
+ "[dim]Use 'claude-mpm agent-source' command to add/remove sources[/dim]"
1860
+ )
1861
+ self.console.print("\nExamples:")
1862
+ self.console.print(" claude-mpm agent-source add <git-url>")
1863
+ self.console.print(" claude-mpm agent-source remove <identifier>")
1864
+ self.console.print(" claude-mpm agent-source list")
1865
+ Prompt.ask("\nPress Enter to continue")
1866
+
1867
+ def _deploy_agents_unified(self, agents: List[AgentConfig]) -> None:
1868
+ """Unified agent selection with inline controls for recommended, presets, and collections.
1869
+
1870
+ Design:
1871
+ - Single nested checkbox list with grouped agents by source/category
1872
+ - Inline controls at top: Select all, Select recommended, Select presets
1873
+ - Asterisk (*) marks recommended agents
1874
+ - Visual hierarchy: Source → Category → Individual agents
1875
+ - Loop with visual feedback: Controls update checkmarks immediately
1876
+ """
1877
+ if not agents:
1878
+ self.console.print("[yellow]No agents available[/yellow]")
1879
+ Prompt.ask("\nPress Enter to continue")
1880
+ return
1881
+
1882
+ from claude_mpm.utils.agent_filters import (
1883
+ filter_base_agents,
1884
+ get_deployed_agent_ids,
1885
+ )
1886
+
1887
+ # Filter BASE_AGENT but keep deployed agents visible
1888
+ all_agents = filter_base_agents(
1889
+ [
1890
+ {
1891
+ "agent_id": getattr(a, "agent_id", a.name),
1892
+ "name": a.name,
1893
+ "description": a.description,
1894
+ "deployed": getattr(a, "is_deployed", False),
1895
+ }
1896
+ for a in agents
1897
+ ]
1898
+ )
1899
+
1900
+ if not all_agents:
1901
+ self.console.print("[yellow]No agents available[/yellow]")
1902
+ Prompt.ask("\nPress Enter to continue")
1903
+ return
1904
+
1905
+ # Get deployed agent IDs and recommended agents
1906
+ deployed_ids = get_deployed_agent_ids()
1907
+
1908
+ try:
1909
+ recommended_agent_ids = self.recommendation_service.get_recommended_agents(
1910
+ str(self.project_dir)
1911
+ )
1912
+ except Exception as e:
1913
+ self.logger.warning(f"Failed to get recommended agents: {e}")
1914
+ recommended_agent_ids = set()
1915
+
1916
+ # Build mapping: leaf name -> full path for deployed agents
1917
+ # Use agent_id (technical ID) for comparison, not display name
1918
+ deployed_full_paths = set()
1919
+ for agent in agents:
1920
+ agent_id = getattr(agent, "agent_id", agent.name)
1921
+ agent_leaf_name = agent_id.split("/")[-1]
1922
+ if agent_leaf_name in deployed_ids:
1923
+ # Store agent_id for selection tracking (not display name)
1924
+ deployed_full_paths.add(agent_id)
1925
+
1926
+ # Track current selection state (starts with deployed, updated in loop)
1927
+ current_selection = deployed_full_paths.copy()
1928
+
1929
+ # Group agents by source/collection
1930
+ agent_map = {}
1931
+ collections = defaultdict(list)
1932
+
1933
+ for agent in agents:
1934
+ # Use agent_id (technical ID) for comparison, not display name
1935
+ agent_id = getattr(agent, "agent_id", agent.name)
1936
+ if agent_id in {a["agent_id"] for a in all_agents}:
1937
+ # Determine collection ID
1938
+ source_type = getattr(agent, "source_type", "local")
1939
+ if source_type == "remote":
1940
+ source_dict = getattr(agent, "source_dict", {})
1941
+ repo_url = source_dict.get("source", "")
1942
+ if "/" in repo_url:
1943
+ parts = repo_url.rstrip("/").split("/")
1944
+ if len(parts) >= 2:
1945
+ # Use more readable collection name
1946
+ if (
1947
+ "bobmatnyc/claude-mpm" in repo_url
1948
+ or "claude-mpm" in repo_url.lower()
1949
+ ):
1950
+ collection_id = "MPM Agents"
1951
+ else:
1952
+ collection_id = f"{parts[-2]}/{parts[-1]}"
1953
+ else:
1954
+ collection_id = "Community Agents"
1955
+ else:
1956
+ collection_id = "Community Agents"
1957
+ else:
1958
+ collection_id = "Local Agents"
1959
+
1960
+ collections[collection_id].append(agent)
1961
+ agent_map[agent_id] = agent
1962
+
1963
+ # Monkey-patch questionary symbols for better visibility
1964
+ questionary.prompts.common.INDICATOR_SELECTED = "[✓]"
1965
+ questionary.prompts.common.INDICATOR_UNSELECTED = "[ ]"
1966
+
1967
+ # MAIN LOOP: Re-display UI when controls are used
1968
+ while True:
1969
+ # Build unified checkbox choices with inline controls
1970
+ choices = []
1971
+
1972
+ for collection_id in sorted(collections.keys()):
1973
+ agents_in_collection = collections[collection_id]
1974
+
1975
+ # Count selected/total agents in collection
1976
+ # Use agent_id for selection tracking, not display name
1977
+ selected_count = sum(
1978
+ 1
1979
+ for agent in agents_in_collection
1980
+ if getattr(agent, "agent_id", agent.name) in current_selection
1981
+ )
1982
+ total_count = len(agents_in_collection)
1983
+
1984
+ # Add collection header
1985
+ choices.append(
1986
+ Separator(
1987
+ f"\n── {collection_id} ({selected_count}/{total_count} selected) ──"
1988
+ )
1989
+ )
1990
+
1991
+ # Determine if all agents in collection are selected
1992
+ all_selected = selected_count == total_count
1993
+
1994
+ # Add inline control: Select/Deselect all from this collection
1995
+ if all_selected:
1996
+ deselect_value = f"__DESELECT_ALL_{collection_id}__"
1997
+ choices.append(
1998
+ Choice(
1999
+ f" [Deselect all from {collection_id}]", # nosec B608
2000
+ value=deselect_value,
2001
+ checked=False,
2002
+ )
2003
+ )
2004
+ else:
2005
+ select_value = f"__SELECT_ALL_{collection_id}__"
2006
+ choices.append(
2007
+ Choice(
2008
+ f" [Select all from {collection_id}]", # nosec B608
2009
+ value=select_value,
2010
+ checked=False,
2011
+ )
2012
+ )
2013
+
2014
+ # Add inline control: Select recommended from this collection
2015
+ recommended_in_collection = [
2016
+ a
2017
+ for a in agents_in_collection
2018
+ if any(
2019
+ a.name == rec_id
2020
+ or a.name.split("/")[-1] == rec_id.split("/")[-1]
2021
+ for rec_id in recommended_agent_ids
2022
+ )
2023
+ ]
2024
+ if recommended_in_collection:
2025
+ recommended_selected = sum(
2026
+ 1
2027
+ for a in recommended_in_collection
2028
+ if a.name in current_selection
2029
+ )
2030
+ if recommended_selected == len(recommended_in_collection):
2031
+ choices.append(
2032
+ Choice(
2033
+ f" [Deselect recommended ({len(recommended_in_collection)} agents)]",
2034
+ value=f"__DESELECT_REC_{collection_id}__",
2035
+ checked=False,
2036
+ )
2037
+ )
2038
+ else:
2039
+ choices.append(
2040
+ Choice(
2041
+ f" [Select recommended ({len(recommended_in_collection)} agents)]",
2042
+ value=f"__SELECT_REC_{collection_id}__",
2043
+ checked=False,
2044
+ )
2045
+ )
2046
+
2047
+ # Add separator before individual agents
2048
+ choices.append(Separator())
2049
+
2050
+ # Group agents by category within collection (if hierarchical)
2051
+ category_groups = defaultdict(list)
2052
+ for agent in sorted(agents_in_collection, key=lambda a: a.name):
2053
+ # Extract category from hierarchical path (e.g., "engineer/backend/python-engineer")
2054
+ parts = agent.name.split("/")
2055
+ if len(parts) > 1:
2056
+ category = "/".join(parts[:-1]) # e.g., "engineer/backend"
2057
+ else:
2058
+ category = "" # No category
2059
+ category_groups[category].append(agent)
2060
+
2061
+ # Display agents grouped by category
2062
+ for category in sorted(category_groups.keys()):
2063
+ agents_in_category = category_groups[category]
2064
+
2065
+ # Add category separator if hierarchical
2066
+ if category:
2067
+ choices.append(Separator(f" {category}/"))
2068
+
2069
+ # Add individual agents
2070
+ for agent in agents_in_category:
2071
+ # Use agent_id (technical ID) for all tracking/selection
2072
+ agent_id = getattr(agent, "agent_id", agent.name)
2073
+ agent_leaf_name = agent_id.split("/")[-1]
2074
+ raw_display_name = getattr(
2075
+ agent, "display_name", agent_leaf_name
2076
+ )
2077
+ display_name = self._format_display_name(raw_display_name)
2078
+
2079
+ # Check if agent is required (cannot be unchecked)
2080
+ required_agents = set(self.unified_config.agents.required)
2081
+ is_required = (
2082
+ agent_leaf_name in required_agents
2083
+ or agent_id in required_agents
2084
+ )
2085
+
2086
+ # Format choice text with [Required] indicator
2087
+ if is_required:
2088
+ choice_text = f" {display_name} [Required]"
2089
+ else:
2090
+ choice_text = f" {display_name}"
2091
+
2092
+ # Required agents are always selected
2093
+ is_selected = is_required or agent_id in current_selection
2094
+
2095
+ # Add to current selection if required
2096
+ if is_required:
2097
+ current_selection.add(agent_id)
2098
+
2099
+ choices.append(
2100
+ Choice(
2101
+ title=choice_text,
2102
+ value=agent_id, # Use agent_id for value
2103
+ checked=is_selected,
2104
+ disabled=is_required, # Disable checkbox for required agents
2105
+ )
2106
+ )
2107
+
2108
+ self.console.print("\n[bold cyan]Select Agents to Install[/bold cyan]")
2109
+ self.console.print("[dim][✓] Checked = Installed (uncheck to remove)[/dim]")
2110
+ self.console.print(
2111
+ "[dim][ ] Unchecked = Available (check to install)[/dim]"
2112
+ )
2113
+ self.console.print("[dim][Required] = Core agents (always installed)[/dim]")
2114
+ self.console.print(
2115
+ "[dim]Use arrow keys to navigate, space to toggle, Enter to apply[/dim]\n"
2116
+ )
2117
+
2118
+ try:
2119
+ selected_values = questionary.checkbox(
2120
+ "Select agents:",
2121
+ choices=choices,
2122
+ instruction="(Space to toggle, Enter to continue)",
2123
+ style=self.QUESTIONARY_STYLE,
2124
+ ).ask()
2125
+ except Exception as e:
2126
+ import sys
2127
+
2128
+ self.logger.error(f"Questionary checkbox failed: {e}", exc_info=True)
2129
+ self.console.print(
2130
+ "[red]Error: Could not display interactive menu[/red]"
2131
+ )
2132
+ self.console.print(f"[dim]Reason: {e}[/dim]")
2133
+ if not sys.stdin.isatty():
2134
+ self.console.print("[dim]Interactive terminal required. Use:[/dim]")
2135
+ self.console.print(
2136
+ "[dim] --list-agents to see available agents[/dim]"
2137
+ )
2138
+ Prompt.ask("\nPress Enter to continue")
2139
+ return
2140
+
2141
+ if selected_values is None:
2142
+ self.console.print("[yellow]No changes made[/yellow]")
2143
+ Prompt.ask("\nPress Enter to continue")
2144
+ return
2145
+
2146
+ # Check for inline control selections
2147
+ controls_selected = [v for v in selected_values if v.startswith("__")]
2148
+
2149
+ if controls_selected:
2150
+ # Process controls and update current_selection
2151
+ for control in controls_selected:
2152
+ if control.startswith("__SELECT_ALL_"):
2153
+ collection_id = control.replace("__SELECT_ALL_", "").replace(
2154
+ "__", ""
2155
+ )
2156
+ # Add all agents from this collection to current_selection
2157
+ for agent in collections[collection_id]:
2158
+ agent_id = getattr(agent, "agent_id", agent.name)
2159
+ current_selection.add(agent_id)
2160
+ elif control.startswith("__DESELECT_ALL_"):
2161
+ collection_id = control.replace("__DESELECT_ALL_", "").replace(
2162
+ "__", ""
2163
+ )
2164
+ # Remove all agents from this collection
2165
+ for agent in collections[collection_id]:
2166
+ agent_id = getattr(agent, "agent_id", agent.name)
2167
+ current_selection.discard(agent_id)
2168
+ elif control.startswith("__SELECT_REC_"):
2169
+ collection_id = control.replace("__SELECT_REC_", "").replace(
2170
+ "__", ""
2171
+ )
2172
+ # Add all recommended agents from this collection
2173
+ for agent in collections[collection_id]:
2174
+ agent_id = getattr(agent, "agent_id", agent.name)
2175
+ if any(
2176
+ agent_id == rec_id
2177
+ or agent_id.split("/")[-1] == rec_id.split("/")[-1]
2178
+ for rec_id in recommended_agent_ids
2179
+ ):
2180
+ current_selection.add(agent_id)
2181
+ elif control.startswith("__DESELECT_REC_"):
2182
+ collection_id = control.replace("__DESELECT_REC_", "").replace(
2183
+ "__", ""
2184
+ )
2185
+ # Remove all recommended agents from this collection
2186
+ for agent in collections[collection_id]:
2187
+ agent_id = getattr(agent, "agent_id", agent.name)
2188
+ if any(
2189
+ agent_id == rec_id
2190
+ or agent_id.split("/")[-1] == rec_id.split("/")[-1]
2191
+ for rec_id in recommended_agent_ids
2192
+ ):
2193
+ current_selection.discard(agent_id)
2194
+
2195
+ # Loop back to re-display with updated selections
2196
+ continue
2197
+
2198
+ # No controls selected - use the individual selections as final
2199
+ final_selection = set(selected_values)
2200
+
2201
+ # Ensure required agents are always in the final selection
2202
+ required_agents = set(self.unified_config.agents.required)
2203
+ for agent in agents:
2204
+ agent_id = getattr(agent, "agent_id", agent.name)
2205
+ agent_leaf_name = agent_id.split("/")[-1]
2206
+ if agent_leaf_name in required_agents or agent_id in required_agents:
2207
+ final_selection.add(agent_id)
2208
+
2209
+ break
2210
+
2211
+ # Determine changes
2212
+ to_deploy = final_selection - deployed_full_paths
2213
+ to_remove = deployed_full_paths - final_selection
2214
+
2215
+ # Prevent removal of required agents
2216
+ required_agents = set(self.unified_config.agents.required)
2217
+ to_remove_filtered = set()
2218
+ for agent_id in to_remove:
2219
+ agent_leaf_name = agent_id.split("/")[-1]
2220
+ if (
2221
+ agent_leaf_name not in required_agents
2222
+ and agent_id not in required_agents
2223
+ ):
2224
+ to_remove_filtered.add(agent_id)
2225
+ else:
2226
+ self.console.print(
2227
+ f"[yellow]⚠ Cannot remove required agent: {agent_id}[/yellow]"
2228
+ )
2229
+ to_remove = to_remove_filtered
2230
+
2231
+ if not to_deploy and not to_remove:
2232
+ self.console.print("[yellow]No changes needed[/yellow]")
2233
+ Prompt.ask("\nPress Enter to continue")
2234
+ return
2235
+
2236
+ # Show what will happen
2237
+ self.console.print("\n[bold]Changes to apply:[/bold]")
2238
+ if to_deploy:
2239
+ self.console.print(f"[green]Install {len(to_deploy)} agent(s)[/green]")
2240
+ for agent_id in to_deploy:
2241
+ self.console.print(f" + {agent_id}")
2242
+ if to_remove:
2243
+ self.console.print(f"[red]Remove {len(to_remove)} agent(s)[/red]")
2244
+ for agent_id in to_remove:
2245
+ self.console.print(f" - {agent_id}")
2246
+
2247
+ # Confirm
2248
+ if not Confirm.ask("\nApply these changes?", default=True):
2249
+ self.console.print("[yellow]Changes cancelled[/yellow]")
2250
+ Prompt.ask("\nPress Enter to continue")
2251
+ return
2252
+
2253
+ # Execute changes
2254
+ deploy_success = 0
2255
+ deploy_fail = 0
2256
+ remove_success = 0
2257
+ remove_fail = 0
2258
+
2259
+ # Install new agents
2260
+ for agent_id in to_deploy:
2261
+ agent = agent_map.get(agent_id)
2262
+ if agent and self._deploy_single_agent(agent, show_feedback=False):
2263
+ deploy_success += 1
2264
+ self.console.print(f"[green]✓ Installed: {agent_id}[/green]")
2265
+ else:
2266
+ deploy_fail += 1
2267
+ self.console.print(f"[red]✗ Failed to install: {agent_id}[/red]")
2268
+
2269
+ # Remove agents
2270
+ for agent_id in to_remove:
2271
+ try:
2272
+ import json
2273
+
2274
+ # Extract leaf name to match deployed filename
2275
+ leaf_name = agent_id.split("/")[-1] if "/" in agent_id else agent_id
2276
+
2277
+ # Remove from all possible locations
2278
+ paths_to_check = [
2279
+ Path.cwd() / ".claude-mpm" / "agents" / f"{leaf_name}.md",
2280
+ Path.cwd() / ".claude" / "agents" / f"{leaf_name}.md",
2281
+ Path.home() / ".claude" / "agents" / f"{leaf_name}.md",
2282
+ ]
2283
+
2284
+ removed = False
2285
+ for path in paths_to_check:
2286
+ if path.exists():
2287
+ path.unlink()
2288
+ removed = True
2289
+
2290
+ # Also remove from virtual deployment state
2291
+ deployment_state_paths = [
2292
+ Path.cwd() / ".claude" / "agents" / ".mpm_deployment_state",
2293
+ Path.home() / ".claude" / "agents" / ".mpm_deployment_state",
2294
+ ]
2295
+
2296
+ for state_path in deployment_state_paths:
2297
+ if state_path.exists():
2298
+ try:
2299
+ with state_path.open() as f:
2300
+ state = json.load(f)
2301
+ agents_in_state = state.get("last_check_results", {}).get(
2302
+ "agents", {}
2303
+ )
2304
+ if leaf_name in agents_in_state:
2305
+ del agents_in_state[leaf_name]
2306
+ removed = True
2307
+ with state_path.open("w") as f:
2308
+ json.dump(state, f, indent=2)
2309
+ except (json.JSONDecodeError, KeyError):
2310
+ pass
2311
+
2312
+ if removed:
2313
+ remove_success += 1
2314
+ self.console.print(f"[green]✓ Removed: {agent_id}[/green]")
2315
+ else:
2316
+ remove_fail += 1
2317
+ self.console.print(f"[yellow]⚠ Not found: {agent_id}[/yellow]")
2318
+ except Exception as e:
2319
+ remove_fail += 1
2320
+ self.console.print(f"[red]✗ Failed to remove {agent_id}: {e}[/red]")
2321
+
2322
+ # Show summary
2323
+ self.console.print()
2324
+ if deploy_success > 0:
2325
+ self.console.print(f"[green]✓ Installed {deploy_success} agent(s)[/green]")
2326
+ if deploy_fail > 0:
2327
+ self.console.print(f"[red]✗ Failed to install {deploy_fail} agent(s)[/red]")
2328
+ if remove_success > 0:
2329
+ self.console.print(f"[green]✓ Removed {remove_success} agent(s)[/green]")
2330
+ if remove_fail > 0:
2331
+ self.console.print(f"[red]✗ Failed to remove {remove_fail} agent(s)[/red]")
2332
+
2333
+ Prompt.ask("\nPress Enter to continue")
2334
+
2335
+ def _deploy_agents_individual(self, agents: List[AgentConfig]) -> None:
2336
+ """Manage agent installation state (unified install/remove interface).
2337
+
2338
+ DEPRECATED: Use _deploy_agents_unified instead.
2339
+ This method is kept for backward compatibility but should not be used.
2340
+ """
2341
+ if not agents:
2342
+ self.console.print("[yellow]No agents available[/yellow]")
2343
+ Prompt.ask("\nPress Enter to continue")
2344
+ return
2345
+
2346
+ # Get ALL agents (filter BASE_AGENT but keep deployed agents visible)
2347
+ from claude_mpm.utils.agent_filters import (
2348
+ filter_base_agents,
2349
+ get_deployed_agent_ids,
2350
+ )
2351
+
2352
+ # Filter BASE_AGENT but keep deployed agents visible
2353
+ all_agents = filter_base_agents(
2354
+ [
2355
+ {
2356
+ "agent_id": getattr(a, "agent_id", a.name),
2357
+ "name": a.name,
2358
+ "description": a.description,
2359
+ "deployed": getattr(a, "is_deployed", False),
2360
+ }
2361
+ for a in agents
2362
+ ]
2363
+ )
2364
+
2365
+ # Get deployed agent IDs (original state - for calculating final changes)
2366
+ # NOTE: deployed_ids contains LEAF NAMES (e.g., "python-engineer")
2367
+ deployed_ids = get_deployed_agent_ids()
2368
+
2369
+ if not all_agents:
2370
+ self.console.print("[yellow]No agents available[/yellow]")
2371
+ Prompt.ask("\nPress Enter to continue")
2372
+ return
2373
+
2374
+ # Build mapping: leaf name -> full path for deployed agents
2375
+ # This allows comparing deployed_ids (leaf names) with agent.agent_id (full paths)
2376
+ deployed_full_paths = set()
2377
+ for agent in agents:
2378
+ # FIX: Use agent_id (technical ID) instead of display name
2379
+ agent_id = getattr(agent, "agent_id", agent.name)
2380
+ agent_leaf_name = agent_id.split("/")[-1]
2381
+ if agent_leaf_name in deployed_ids:
2382
+ deployed_full_paths.add(agent_id)
2383
+
2384
+ # Track current selection state (starts with deployed full paths, updated after each iteration)
2385
+ current_selection = deployed_full_paths.copy()
2386
+
2387
+ # Loop to allow adjusting selection
2388
+ while True:
2389
+ # Build agent mapping and collections
2390
+ agent_map = {} # For lookup after selection
2391
+ collections = defaultdict(list)
2392
+
2393
+ for agent in agents:
2394
+ # FIX: Use agent_id (technical ID) for comparison
2395
+ agent_id = getattr(agent, "agent_id", agent.name)
2396
+ if agent_id in {a["agent_id"] for a in all_agents}:
2397
+ # Determine collection ID
2398
+ source_type = getattr(agent, "source_type", "local")
2399
+ if source_type == "remote":
2400
+ source_dict = getattr(agent, "source_dict", {})
2401
+ repo_url = source_dict.get("source", "")
2402
+ # Extract repository name from URL
2403
+ if "/" in repo_url:
2404
+ parts = repo_url.rstrip("/").split("/")
2405
+ if len(parts) >= 2:
2406
+ collection_id = f"{parts[-2]}/{parts[-1]}"
2407
+ else:
2408
+ collection_id = "remote"
2409
+ else:
2410
+ collection_id = "remote"
2411
+ else:
2412
+ collection_id = "local"
2413
+
2414
+ collections[collection_id].append(agent)
2415
+ agent_map[agent_id] = agent # FIX: Use agent_id as key
2416
+
2417
+ # STEP 1: Collection-level selection
2418
+ self.console.print("\n[bold cyan]Select Agent Collections[/bold cyan]")
2419
+ self.console.print(
2420
+ "[dim]Checking a collection installs ALL agents in that collection[/dim]"
2421
+ )
2422
+ self.console.print(
2423
+ "[dim]Unchecking a collection removes ALL agents in that collection[/dim]"
2424
+ )
2425
+ self.console.print(
2426
+ "[dim]For partial deployment, use 'Fine-tune individual agents'[/dim]\n"
2427
+ )
2428
+
2429
+ collection_choices = []
2430
+ for collection_id in sorted(collections.keys()):
2431
+ agents_in_collection = collections[collection_id]
2432
+
2433
+ # Check if ANY agent in this collection is currently deployed
2434
+ # This reflects actual deployment state, not just selection
2435
+ # FIX: Use agent_id for comparison with current_selection
2436
+ any_deployed = any(
2437
+ getattr(agent, "agent_id", agent.name) in current_selection
2438
+ for agent in agents_in_collection
2439
+ )
2440
+
2441
+ # Count deployed agents for display
2442
+ # FIX: Use agent_id for comparison with current_selection
2443
+ deployed_count = sum(
2444
+ 1
2445
+ for agent in agents_in_collection
2446
+ if getattr(agent, "agent_id", agent.name) in current_selection
2447
+ )
2448
+
2449
+ collection_choices.append(
2450
+ Choice(
2451
+ f"{collection_id} ({deployed_count}/{len(agents_in_collection)} deployed)",
2452
+ value=collection_id,
2453
+ checked=any_deployed,
2454
+ )
2455
+ )
2456
+
2457
+ # Add option to fine-tune individual agents
2458
+ collection_choices.append(Separator())
2459
+ collection_choices.append(
2460
+ Choice(
2461
+ "→ Fine-tune individual agents...",
2462
+ value="__INDIVIDUAL__",
2463
+ checked=False,
2464
+ )
2465
+ )
2466
+
2467
+ # Monkey-patch questionary symbols for better visibility
2468
+ questionary.prompts.common.INDICATOR_SELECTED = "[✓]"
2469
+ questionary.prompts.common.INDICATOR_UNSELECTED = "[ ]"
2470
+
2471
+ try:
2472
+ selected_collections = questionary.checkbox(
2473
+ "Select agent collections to deploy:",
2474
+ choices=collection_choices,
2475
+ instruction="(Space to toggle, Enter to continue)",
2476
+ style=self.QUESTIONARY_STYLE,
2477
+ ).ask()
2478
+ except Exception as e:
2479
+ import sys
2480
+
2481
+ self.logger.error(f"Questionary checkbox failed: {e}", exc_info=True)
2482
+ self.console.print(
2483
+ "[red]Error: Could not display interactive menu[/red]"
2484
+ )
2485
+ self.console.print(f"[dim]Reason: {e}[/dim]")
2486
+ if not sys.stdin.isatty():
2487
+ self.console.print("[dim]Interactive terminal required. Use:[/dim]")
2488
+ self.console.print(
2489
+ "[dim] --list-agents to see available agents[/dim]"
2490
+ )
2491
+ self.console.print(
2492
+ "[dim] --enable-agent/--disable-agent for scripting[/dim]"
2493
+ )
2494
+ else:
2495
+ self.console.print(
2496
+ "[dim]This might be a terminal compatibility issue.[/dim]"
2497
+ )
2498
+ Prompt.ask("\nPress Enter to continue")
2499
+ return
2500
+
2501
+ # Handle cancellation
2502
+ if selected_collections is None:
2503
+ import sys
2504
+
2505
+ if not sys.stdin.isatty():
2506
+ self.console.print(
2507
+ "[red]Error: Interactive terminal required for agent selection[/red]"
2508
+ )
2509
+ self.console.print(
2510
+ "[dim]Use --list-agents to see available agents[/dim]"
2511
+ )
2512
+ self.console.print(
2513
+ "[dim]Use --enable-agent/--disable-agent for non-interactive mode[/dim]"
2514
+ )
2515
+ else:
2516
+ self.console.print("[yellow]No changes made[/yellow]")
2517
+ Prompt.ask("\nPress Enter to continue")
2518
+ return
2519
+
2520
+ # STEP 2: Check if user wants individual selection
2521
+ if "__INDIVIDUAL__" in selected_collections:
2522
+ # Remove the __INDIVIDUAL__ marker
2523
+ selected_collections = [
2524
+ c for c in selected_collections if c != "__INDIVIDUAL__"
2525
+ ]
2526
+
2527
+ # Build individual agent choices with grouping
2528
+ agent_choices = []
2529
+ for collection_id in sorted(collections.keys()):
2530
+ agents_in_collection = collections[collection_id]
2531
+
2532
+ # Add collection header separator
2533
+ agent_choices.append(
2534
+ Separator(
2535
+ f"\n── {collection_id} ({len(agents_in_collection)} agents) ──"
2536
+ )
2537
+ )
2538
+
2539
+ # Add individual agents from this collection
2540
+ # FIX: Use agent_id for sorting, comparison, and values
2541
+ for agent in sorted(
2542
+ agents_in_collection,
2543
+ key=lambda a: getattr(a, "agent_id", a.name),
2544
+ ):
2545
+ agent_id = getattr(agent, "agent_id", agent.name)
2546
+ raw_display_name = getattr(agent, "display_name", agent.name)
2547
+ display_name = self._format_display_name(raw_display_name)
2548
+ is_selected = agent_id in deployed_full_paths
2549
+
2550
+ choice_text = f"{agent_id}"
2551
+ if display_name and display_name != agent_id:
2552
+ choice_text += f" - {display_name}"
2553
+
2554
+ agent_choices.append(
2555
+ Choice(
2556
+ title=choice_text, value=agent_id, checked=is_selected
2557
+ )
2558
+ )
2559
+
2560
+ self.console.print(
2561
+ "\n[bold cyan]Fine-tune Individual Agents[/bold cyan]"
2562
+ )
2563
+ self.console.print(
2564
+ "[dim][✓] Checked = Installed (uncheck to remove)[/dim]"
2565
+ )
2566
+ self.console.print(
2567
+ "[dim][ ] Unchecked = Available (check to install)[/dim]"
2568
+ )
2569
+ self.console.print(
2570
+ "[dim]Use arrow keys to navigate, space to toggle, Enter to apply[/dim]\n"
2571
+ )
2572
+
2573
+ try:
2574
+ selected_agent_ids = questionary.checkbox(
2575
+ "Select individual agents:",
2576
+ choices=agent_choices,
2577
+ style=self.QUESTIONARY_STYLE,
2578
+ ).ask()
2579
+ except Exception as e:
2580
+ import sys
2581
+
2582
+ self.logger.error(
2583
+ f"Questionary checkbox failed: {e}", exc_info=True
2584
+ )
2585
+ self.console.print(
2586
+ "[red]Error: Could not display interactive menu[/red]"
2587
+ )
2588
+ self.console.print(f"[dim]Reason: {e}[/dim]")
2589
+ Prompt.ask("\nPress Enter to continue")
2590
+ return
2591
+
2592
+ if selected_agent_ids is None:
2593
+ self.console.print("[yellow]No changes made[/yellow]")
2594
+ Prompt.ask("\nPress Enter to continue")
2595
+ return
2596
+
2597
+ # Update current_selection with individual selections
2598
+ current_selection = set(selected_agent_ids)
2599
+ else:
2600
+ # Apply collection-level selections
2601
+ # For each collection, if it's selected, include ALL its agents
2602
+ # If it's not selected, exclude ALL its agents
2603
+ final_selections = set()
2604
+ for collection_id in selected_collections:
2605
+ for agent in collections[collection_id]:
2606
+ # FIX: Use agent_id for selection tracking
2607
+ final_selections.add(getattr(agent, "agent_id", agent.name))
2608
+
2609
+ # Update current_selection
2610
+ # This replaces the previous selection entirely with the new collection selections
2611
+ current_selection = final_selections
2612
+
2613
+ # Determine actions based on ORIGINAL deployed state
2614
+ # Compare full paths to full paths (deployed_full_paths was built from deployed_ids)
2615
+ to_deploy = (
2616
+ current_selection - deployed_full_paths
2617
+ ) # Selected but not originally deployed
2618
+
2619
+ # For removal, verify files actually exist before adding to the set
2620
+ # This prevents "Not found" warnings when multiple agents share leaf names
2621
+ to_remove = set()
2622
+ for agent_id in deployed_full_paths - current_selection:
2623
+ # Extract leaf name to check file existence
2624
+ leaf_name = agent_id.split("/")[-1] if "/" in agent_id else agent_id
2625
+
2626
+ # Check all possible locations
2627
+ paths_to_check = [
2628
+ Path.cwd() / ".claude-mpm" / "agents" / f"{leaf_name}.md",
2629
+ Path.cwd() / ".claude" / "agents" / f"{leaf_name}.md",
2630
+ Path.home() / ".claude" / "agents" / f"{leaf_name}.md",
2631
+ ]
2632
+
2633
+ # Also check virtual deployment state
2634
+ state_exists = False
2635
+ deployment_state_paths = [
2636
+ Path.cwd() / ".claude" / "agents" / ".mpm_deployment_state",
2637
+ Path.home() / ".claude" / "agents" / ".mpm_deployment_state",
2638
+ ]
2639
+
2640
+ for state_path in deployment_state_paths:
2641
+ if state_path.exists():
2642
+ try:
2643
+ import json
2644
+
2645
+ with state_path.open() as f:
2646
+ state = json.load(f)
2647
+ agents_in_state = state.get("last_check_results", {}).get(
2648
+ "agents", {}
2649
+ )
2650
+ if leaf_name in agents_in_state:
2651
+ state_exists = True
2652
+ break
2653
+ except (json.JSONDecodeError, KeyError):
2654
+ continue
2655
+
2656
+ # Only add to removal set if file or state entry actually exists
2657
+ if any(p.exists() for p in paths_to_check) or state_exists:
2658
+ to_remove.add(agent_id)
2659
+
2660
+ if not to_deploy and not to_remove:
2661
+ self.console.print(
2662
+ "[yellow]No changes needed - all selected agents are already installed[/yellow]"
2663
+ )
2664
+ Prompt.ask("\nPress Enter to continue")
2665
+ return
2666
+
2667
+ # Show what will happen
2668
+ self.console.print("\n[bold]Changes to apply:[/bold]")
2669
+ if to_deploy:
2670
+ self.console.print(f"[green]Install {len(to_deploy)} agent(s)[/green]")
2671
+ for agent_id in to_deploy:
2672
+ self.console.print(f" + {agent_id}")
2673
+ if to_remove:
2674
+ self.console.print(f"[red]Remove {len(to_remove)} agent(s)[/red]")
2675
+ for agent_id in to_remove:
2676
+ self.console.print(f" - {agent_id}")
2677
+
2678
+ # Ask user to confirm, adjust, or cancel
2679
+ action = questionary.select(
2680
+ "\nWhat would you like to do?",
2681
+ choices=[
2682
+ questionary.Choice("Apply these changes", value="apply"),
2683
+ questionary.Choice("Adjust selection", value="adjust"),
2684
+ questionary.Choice("Cancel", value="cancel"),
2685
+ ],
2686
+ default="apply",
2687
+ style=self.QUESTIONARY_STYLE,
2688
+ ).ask()
2689
+
2690
+ if action == "cancel":
2691
+ self.console.print("[yellow]Changes cancelled[/yellow]")
2692
+ Prompt.ask("\nPress Enter to continue")
2693
+ return
2694
+ if action == "adjust":
2695
+ # current_selection is already updated, loop will use it
2696
+ continue
2697
+
2698
+ # Execute changes
2699
+ deploy_success = 0
2700
+ deploy_fail = 0
2701
+ remove_success = 0
2702
+ remove_fail = 0
2703
+
2704
+ # Install new agents
2705
+ for agent_id in to_deploy:
2706
+ agent = agent_map.get(agent_id)
2707
+ if agent and self._deploy_single_agent(agent, show_feedback=False):
2708
+ deploy_success += 1
2709
+ self.console.print(f"[green]✓ Installed: {agent_id}[/green]")
2710
+ else:
2711
+ deploy_fail += 1
2712
+ self.console.print(f"[red]✗ Failed to install: {agent_id}[/red]")
2713
+
2714
+ # Remove agents
2715
+ for agent_id in to_remove:
2716
+ try:
2717
+ import json
2718
+ # Note: Path is already imported at module level (line 17)
2719
+
2720
+ # Extract leaf name to match deployed filename
2721
+ # agent_id may be hierarchical (e.g., "engineer/mobile/tauri-engineer")
2722
+ # but deployed files use flattened leaf names (e.g., "tauri-engineer.md")
2723
+ if "/" in agent_id:
2724
+ leaf_name = agent_id.split("/")[-1]
2725
+ else:
2726
+ leaf_name = agent_id
2727
+
2728
+ # Remove from project, legacy, and user locations
2729
+ project_path = (
2730
+ Path.cwd() / ".claude-mpm" / "agents" / f"{leaf_name}.md"
2731
+ )
2732
+ legacy_path = Path.cwd() / ".claude" / "agents" / f"{leaf_name}.md"
2733
+ user_path = Path.home() / ".claude" / "agents" / f"{leaf_name}.md"
2734
+
2735
+ removed = False
2736
+ for path in [project_path, legacy_path, user_path]:
2737
+ if path.exists():
2738
+ path.unlink()
2739
+ removed = True
2740
+
2741
+ # Also remove from virtual deployment state
2742
+ deployment_state_paths = [
2743
+ Path.cwd() / ".claude" / "agents" / ".mpm_deployment_state",
2744
+ Path.home() / ".claude" / "agents" / ".mpm_deployment_state",
2745
+ ]
2746
+
2747
+ for state_path in deployment_state_paths:
2748
+ if state_path.exists():
2749
+ try:
2750
+ with state_path.open() as f:
2751
+ state = json.load(f)
2752
+
2753
+ # Remove agent from deployment state
2754
+ # Deployment state uses leaf names, not full hierarchical paths
2755
+ agents = state.get("last_check_results", {}).get(
2756
+ "agents", {}
2757
+ )
2758
+ if leaf_name in agents:
2759
+ del agents[leaf_name]
2760
+ removed = True
2761
+
2762
+ # Save updated state
2763
+ with state_path.open("w") as f:
2764
+ json.dump(state, f, indent=2)
2765
+ except (json.JSONDecodeError, KeyError) as e:
2766
+ # Log but don't fail - physical removal still counts
2767
+ self.logger.debug(
2768
+ f"Failed to update deployment state at {state_path}: {e}"
2769
+ )
2770
+
2771
+ if removed:
2772
+ remove_success += 1
2773
+ self.console.print(f"[green]✓ Removed: {agent_id}[/green]")
2774
+ else:
2775
+ remove_fail += 1
2776
+ self.console.print(f"[yellow]⚠ Not found: {agent_id}[/yellow]")
2777
+ except Exception as e:
2778
+ remove_fail += 1
2779
+ self.console.print(f"[red]✗ Failed to remove {agent_id}: {e}[/red]")
2780
+
2781
+ # Show summary
2782
+ self.console.print()
2783
+ if deploy_success > 0:
2784
+ self.console.print(
2785
+ f"[green]✓ Installed {deploy_success} agent(s)[/green]"
2786
+ )
2787
+ if deploy_fail > 0:
2788
+ self.console.print(
2789
+ f"[red]✗ Failed to install {deploy_fail} agent(s)[/red]"
2790
+ )
2791
+ if remove_success > 0:
2792
+ self.console.print(
2793
+ f"[green]✓ Removed {remove_success} agent(s)[/green]"
2794
+ )
2795
+ if remove_fail > 0:
2796
+ self.console.print(
2797
+ f"[red]✗ Failed to remove {remove_fail} agent(s)[/red]"
2798
+ )
2799
+
2800
+ Prompt.ask("\nPress Enter to continue")
2801
+ # Exit the loop after successful execution
2802
+ break
2803
+
2804
+ def _deploy_agents_preset(self) -> None:
2805
+ """Install agents using preset configuration."""
2806
+ try:
2807
+ from claude_mpm.services.agents.agent_preset_service import (
2808
+ AgentPresetService,
2809
+ )
2810
+ from claude_mpm.services.agents.git_source_manager import GitSourceManager
2811
+
2812
+ source_manager = GitSourceManager()
2813
+ preset_service = AgentPresetService(source_manager)
2814
+
2815
+ presets = preset_service.list_presets()
2816
+
2817
+ if not presets:
2818
+ self.console.print("[yellow]No presets available[/yellow]")
2819
+ Prompt.ask("\nPress Enter to continue")
2820
+ return
2821
+
2822
+ self.console.print("\n[bold white]═══ Available Presets ═══[/bold white]\n")
2823
+ for idx, preset in enumerate(presets, 1):
2824
+ self.console.print(f" {idx}. [white]{preset['name']}[/white]")
2825
+ self.console.print(f" {preset['description']}")
2826
+ self.console.print(f" [dim]Agents: {len(preset['agents'])}[/dim]\n")
2827
+
2828
+ selection = Prompt.ask("\nEnter preset number (or 'c' to cancel)")
2829
+ if selection.lower() == "c":
2830
+ return
2831
+
2832
+ idx = int(selection) - 1
2833
+ if 0 <= idx < len(presets):
2834
+ preset_name = presets[idx]["name"]
2835
+
2836
+ # Resolve and deploy preset
2837
+ resolution = preset_service.resolve_agents(preset_name)
2838
+
2839
+ if resolution.get("missing_agents"):
2840
+ self.console.print(
2841
+ f"[red]Missing agents: {len(resolution['missing_agents'])}[/red]"
2842
+ )
2843
+ for agent_id in resolution["missing_agents"]:
2844
+ self.console.print(f" • {agent_id}")
2845
+ Prompt.ask("\nPress Enter to continue")
2846
+ return
2847
+
2848
+ # Confirm installation
2849
+ self.console.print(
2850
+ f"\n[bold]Preset '{preset_name}' includes {len(resolution['agents'])} agents[/bold]"
2851
+ )
2852
+ if Confirm.ask("Install all agents?", default=True):
2853
+ installed = 0
2854
+ for agent in resolution["agents"]:
2855
+ # Convert dict to AgentConfig-like object for installation
2856
+ agent_config = AgentConfig(
2857
+ name=agent.get("agent_id", "unknown"),
2858
+ description=agent.get("metadata", {}).get(
2859
+ "description", ""
2860
+ ),
2861
+ dependencies=[],
2862
+ )
2863
+ agent_config.source_dict = agent
2864
+ agent_config.full_agent_id = agent.get("agent_id", "unknown")
2865
+
2866
+ if self._deploy_single_agent(agent_config, show_feedback=False):
2867
+ installed += 1
2868
+
2869
+ self.console.print(
2870
+ f"\n[green]✓ Installed {installed}/{len(resolution['agents'])} agents[/green]"
2871
+ )
2872
+
2873
+ Prompt.ask("\nPress Enter to continue")
2874
+ else:
2875
+ self.console.print("[red]Invalid selection[/red]")
2876
+ Prompt.ask("\nPress Enter to continue")
2877
+
2878
+ except Exception as e:
2879
+ self.console.print(f"[red]Error installing preset: {e}[/red]")
2880
+ self.logger.error(f"Preset installation failed: {e}", exc_info=True)
2881
+ Prompt.ask("\nPress Enter to continue")
2882
+
2883
+ def _select_recommended_agents(self, agents: List[AgentConfig]) -> None:
2884
+ """Select and install recommended agents based on toolchain detection."""
2885
+ if not agents:
2886
+ self.console.print("[yellow]No agents available[/yellow]")
2887
+ Prompt.ask("\nPress Enter to continue")
2888
+ return
2889
+
2890
+ self.console.clear()
2891
+ self.console.print(
2892
+ "\n[bold white]═══ Recommended Agents for This Project ═══[/bold white]\n"
2893
+ )
2894
+
2895
+ # Get recommended agent IDs
2896
+ try:
2897
+ recommended_agent_ids = self.recommendation_service.get_recommended_agents(
2898
+ str(self.project_dir)
2899
+ )
2900
+ except Exception as e:
2901
+ self.console.print(f"[red]Error detecting toolchain: {e}[/red]")
2902
+ self.logger.error(f"Toolchain detection failed: {e}", exc_info=True)
2903
+ Prompt.ask("\nPress Enter to continue")
2904
+ return
2905
+
2906
+ if not recommended_agent_ids:
2907
+ self.console.print("[yellow]No recommended agents found[/yellow]")
2908
+ Prompt.ask("\nPress Enter to continue")
2909
+ return
2910
+
2911
+ # Get detection summary
2912
+ try:
2913
+ summary = self.recommendation_service.get_detection_summary(
2914
+ str(self.project_dir)
2915
+ )
2916
+
2917
+ self.console.print("[bold]Detected Project Stack:[/bold]")
2918
+ if summary.get("detected_languages"):
2919
+ self.console.print(
2920
+ f" Languages: [cyan]{', '.join(summary['detected_languages'])}[/cyan]"
2921
+ )
2922
+ if summary.get("detected_frameworks"):
2923
+ self.console.print(
2924
+ f" Frameworks: [cyan]{', '.join(summary['detected_frameworks'])}[/cyan]"
2925
+ )
2926
+ self.console.print(
2927
+ f" Detection Quality: [{'green' if summary.get('detection_quality') == 'high' else 'yellow'}]{summary.get('detection_quality', 'unknown')}[/]"
2928
+ )
2929
+ self.console.print()
2930
+ except Exception: # nosec B110 - Suppress broad except for failed safety check
2931
+ # Silent failure on safety check - non-critical feature
2932
+ pass
2933
+
2934
+ # Build mapping: agent_id -> AgentConfig
2935
+ agent_map = {agent.name: agent for agent in agents}
2936
+
2937
+ # Also check leaf names for matching
2938
+ for agent in agents:
2939
+ leaf_name = agent.name.split("/")[-1] if "/" in agent.name else agent.name
2940
+ if leaf_name not in agent_map:
2941
+ agent_map[leaf_name] = agent
2942
+
2943
+ # Find matching agents from available agents
2944
+ matched_agents = []
2945
+ for recommended_id in recommended_agent_ids:
2946
+ # Try full path match first
2947
+ if recommended_id in agent_map:
2948
+ matched_agents.append(agent_map[recommended_id])
2949
+ else:
2950
+ # Try leaf name match
2951
+ recommended_leaf = (
2952
+ recommended_id.split("/")[-1]
2953
+ if "/" in recommended_id
2954
+ else recommended_id
2955
+ )
2956
+ if recommended_leaf in agent_map:
2957
+ matched_agents.append(agent_map[recommended_leaf])
2958
+
2959
+ if not matched_agents:
2960
+ self.console.print(
2961
+ "[yellow]No matching agents found in available sources[/yellow]"
2962
+ )
2963
+ Prompt.ask("\nPress Enter to continue")
2964
+ return
2965
+
2966
+ # Display recommended agents
2967
+ self.console.print(
2968
+ f"[bold]Recommended Agents ({len(matched_agents)}):[/bold]\n"
2969
+ )
2970
+
2971
+ from rich.table import Table
2972
+
2973
+ rec_table = Table(show_header=True, header_style="bold white")
2974
+ rec_table.add_column("#", style="dim", width=4)
2975
+ rec_table.add_column("Agent ID", style="cyan", width=40)
2976
+ rec_table.add_column("Status", style="white", width=15)
2977
+
2978
+ for idx, agent in enumerate(matched_agents, 1):
2979
+ is_installed = getattr(agent, "is_deployed", False)
2980
+ status = (
2981
+ "[green]Already Installed[/green]"
2982
+ if is_installed
2983
+ else "[yellow]Not Installed[/yellow]"
2984
+ )
2985
+ rec_table.add_row(str(idx), agent.name, status)
2986
+
2987
+ self.console.print(rec_table)
2988
+
2989
+ # Count how many need installation
2990
+ to_install = [a for a in matched_agents if not getattr(a, "is_deployed", False)]
2991
+ already_installed = len(matched_agents) - len(to_install)
2992
+
2993
+ self.console.print()
2994
+ if already_installed > 0:
2995
+ self.console.print(
2996
+ f"[green]✓ {already_installed} already installed[/green]"
2997
+ )
2998
+ if to_install:
2999
+ self.console.print(
3000
+ f"[yellow]⚠ {len(to_install)} need installation[/yellow]"
3001
+ )
3002
+ else:
3003
+ self.console.print(
3004
+ "[green]✓ All recommended agents are already installed![/green]"
3005
+ )
3006
+ Prompt.ask("\nPress Enter to continue")
3007
+ return
3008
+
3009
+ # Ask for confirmation
3010
+ self.console.print()
3011
+ if not Confirm.ask(
3012
+ f"Install {len(to_install)} recommended agent(s)?", default=True
3013
+ ):
3014
+ self.console.print("[yellow]Installation cancelled[/yellow]")
3015
+ Prompt.ask("\nPress Enter to continue")
3016
+ return
3017
+
3018
+ # Install agents
3019
+ self.console.print("\n[bold]Installing recommended agents...[/bold]\n")
3020
+
3021
+ success_count = 0
3022
+ fail_count = 0
3023
+
3024
+ for agent in to_install:
3025
+ try:
3026
+ if self._deploy_single_agent(agent, show_feedback=False):
3027
+ success_count += 1
3028
+ self.console.print(f"[green]✓ Installed: {agent.name}[/green]")
3029
+ else:
3030
+ fail_count += 1
3031
+ self.console.print(f"[red]✗ Failed: {agent.name}[/red]")
3032
+ except Exception as e:
3033
+ fail_count += 1
3034
+ self.console.print(f"[red]✗ Failed: {agent.name} - {e}[/red]")
3035
+
3036
+ # Show summary
3037
+ self.console.print()
3038
+ if success_count > 0:
3039
+ self.console.print(
3040
+ f"[green]✓ Successfully installed {success_count} agent(s)[/green]"
3041
+ )
3042
+ if fail_count > 0:
3043
+ self.console.print(f"[red]✗ Failed to install {fail_count} agent(s)[/red]")
3044
+
3045
+ Prompt.ask("\nPress Enter to continue")
3046
+
3047
+ def _deploy_single_agent(
3048
+ self, agent: AgentConfig, show_feedback: bool = True
3049
+ ) -> bool:
3050
+ """Install a single agent to the appropriate location."""
3051
+ try:
3052
+ # Check if this is a remote agent with source_dict
3053
+ source_dict = getattr(agent, "source_dict", None)
3054
+ full_agent_id = getattr(agent, "full_agent_id", agent.name)
3055
+
3056
+ if source_dict:
3057
+ # Deploy remote agent using its source file
3058
+ source_file = Path(source_dict.get("source_file", ""))
3059
+ if not source_file.exists():
3060
+ if show_feedback:
3061
+ self.console.print(
3062
+ f"[red]✗ Source file not found: {source_file}[/red]"
3063
+ )
3064
+ return False
3065
+
3066
+ # Determine target file name (use leaf name from hierarchical ID)
3067
+ if "/" in full_agent_id:
3068
+ target_name = full_agent_id.split("/")[-1] + ".md"
3069
+ else:
3070
+ target_name = full_agent_id + ".md"
3071
+
3072
+ # Deploy to project-level agents directory
3073
+ target_dir = self.project_dir / ".claude" / "agents"
3074
+ target_dir.mkdir(parents=True, exist_ok=True)
3075
+ target_file = target_dir / target_name
3076
+
3077
+ if show_feedback:
3078
+ self.console.print(
3079
+ f"\n[white]Installing {full_agent_id}...[/white]"
3080
+ )
3081
+
3082
+ # Copy the agent file
3083
+ import shutil
3084
+
3085
+ shutil.copy2(source_file, target_file)
3086
+
3087
+ if show_feedback:
3088
+ self.console.print(
3089
+ f"[green]✓ Successfully installed {full_agent_id} to {target_file}[/green]"
3090
+ )
3091
+ Prompt.ask("\nPress Enter to continue")
3092
+
3093
+ return True
3094
+ # Legacy local template installation (not implemented here)
3095
+ if show_feedback:
3096
+ self.console.print(
3097
+ "[yellow]Local template installation not yet implemented[/yellow]"
3098
+ )
3099
+ Prompt.ask("\nPress Enter to continue")
3100
+ return False
3101
+
3102
+ except Exception as e:
3103
+ if show_feedback:
3104
+ self.console.print(f"[red]Error installing agent: {e}[/red]")
3105
+ self.logger.error(f"Agent installation failed: {e}", exc_info=True)
3106
+ Prompt.ask("\nPress Enter to continue")
3107
+ return False
3108
+
3109
+ def _remove_agents(self, agents: List[AgentConfig]) -> None:
3110
+ """Remove installed agents."""
3111
+ # Filter to installed agents only
3112
+ installed = [a for a in agents if getattr(a, "is_deployed", False)]
3113
+
3114
+ if not installed:
3115
+ self.console.print("[yellow]No agents are currently installed[/yellow]")
3116
+ Prompt.ask("\nPress Enter to continue")
3117
+ return
3118
+
3119
+ self.console.print(f"\n[bold]Installed agents ({len(installed)}):[/bold]")
3120
+ for idx, agent in enumerate(installed, 1):
3121
+ raw_display_name = getattr(agent, "display_name", agent.name)
3122
+ display_name = self._format_display_name(raw_display_name)
3123
+ self.console.print(f" {idx}. {agent.name} - {display_name}")
3124
+
3125
+ selection = Prompt.ask("\nEnter agent number to remove (or 'c' to cancel)")
3126
+ if selection.lower() == "c":
3127
+ return
3128
+
3129
+ try:
3130
+ idx = int(selection) - 1
3131
+ if 0 <= idx < len(installed):
3132
+ agent = installed[idx]
3133
+ full_agent_id = getattr(agent, "full_agent_id", agent.name)
3134
+
3135
+ # Determine possible file names (hierarchical and leaf)
3136
+ file_names = [f"{full_agent_id}.md"]
3137
+ if "/" in full_agent_id:
3138
+ leaf_name = full_agent_id.split("/")[-1]
3139
+ file_names.append(f"{leaf_name}.md")
3140
+
3141
+ # Remove from both project and user directories
3142
+ removed = False
3143
+ project_agent_dir = Path.cwd() / ".claude-mpm" / "agents"
3144
+ user_agent_dir = Path.home() / ".claude" / "agents"
3145
+
3146
+ for file_name in file_names:
3147
+ project_file = project_agent_dir / file_name
3148
+ user_file = user_agent_dir / file_name
3149
+
3150
+ if project_file.exists():
3151
+ project_file.unlink()
3152
+ removed = True
3153
+ self.console.print(f"[green]✓ Removed {project_file}[/green]")
3154
+
3155
+ if user_file.exists():
3156
+ user_file.unlink()
3157
+ removed = True
3158
+ self.console.print(f"[green]✓ Removed {user_file}[/green]")
3159
+
3160
+ if removed:
3161
+ self.console.print(
3162
+ f"[green]✓ Successfully removed {full_agent_id}[/green]"
3163
+ )
3164
+ else:
3165
+ self.console.print("[yellow]Agent files not found[/yellow]")
3166
+
3167
+ Prompt.ask("\nPress Enter to continue")
3168
+ else:
3169
+ self.console.print("[red]Invalid selection[/red]")
3170
+ Prompt.ask("\nPress Enter to continue")
3171
+
3172
+ except (ValueError, IndexError):
3173
+ self.console.print("[red]Invalid selection[/red]")
3174
+ Prompt.ask("\nPress Enter to continue")
3175
+
3176
+ def _view_agent_details_enhanced(self, agents: List[AgentConfig]) -> None:
3177
+ """View detailed agent information with enhanced remote agent details."""
3178
+ if not agents:
3179
+ self.console.print("[yellow]No agents available[/yellow]")
3180
+ Prompt.ask("\nPress Enter to continue")
3181
+ return
3182
+
3183
+ self.console.print(f"\n[bold]Available agents ({len(agents)}):[/bold]")
3184
+ for idx, agent in enumerate(agents, 1):
3185
+ raw_display_name = getattr(agent, "display_name", agent.name)
3186
+ display_name = self._format_display_name(raw_display_name)
3187
+ self.console.print(f" {idx}. {agent.name} - {display_name}")
3188
+
3189
+ selection = Prompt.ask("\nEnter agent number to view (or 'c' to cancel)")
3190
+ if selection.lower() == "c":
3191
+ return
3192
+
3193
+ try:
3194
+ idx = int(selection) - 1
3195
+ if 0 <= idx < len(agents):
3196
+ agent = agents[idx]
3197
+
3198
+ self.console.clear()
3199
+ self.console.print("\n[bold white]═══ Agent Details ═══[/bold white]\n")
3200
+
3201
+ # Basic info
3202
+ self.console.print(f"[bold]ID:[/bold] {agent.name}")
3203
+ raw_display_name = getattr(agent, "display_name", "N/A")
3204
+ display_name = (
3205
+ self._format_display_name(raw_display_name)
3206
+ if raw_display_name != "N/A"
3207
+ else "N/A"
3208
+ )
3209
+ self.console.print(f"[bold]Name:[/bold] {display_name}")
3210
+ self.console.print(f"[bold]Description:[/bold] {agent.description}")
3211
+
3212
+ # Source info
3213
+ source_type = getattr(agent, "source_type", "local")
3214
+ self.console.print(f"[bold]Source Type:[/bold] {source_type}")
3215
+
3216
+ if source_type == "remote":
3217
+ source_dict = getattr(agent, "source_dict", {})
3218
+ category = source_dict.get("category", "N/A")
3219
+ source = source_dict.get("source", "N/A")
3220
+ version = source_dict.get("version", "N/A")
3221
+
3222
+ self.console.print(f"[bold]Category:[/bold] {category}")
3223
+ self.console.print(f"[bold]Source:[/bold] {source}")
3224
+ self.console.print(f"[bold]Version:[/bold] {version[:16]}...")
3225
+
3226
+ # Installation status
3227
+ is_installed = getattr(agent, "is_deployed", False)
3228
+ status = "Installed" if is_installed else "Available"
3229
+ self.console.print(f"[bold]Status:[/bold] {status}")
3230
+
3231
+ Prompt.ask("\nPress Enter to continue")
3232
+ else:
3233
+ self.console.print("[red]Invalid selection[/red]")
3234
+ Prompt.ask("\nPress Enter to continue")
3235
+
3236
+ except (ValueError, IndexError):
3237
+ self.console.print("[red]Invalid selection[/red]")
3238
+ Prompt.ask("\nPress Enter to continue")
3239
+
3240
+
3241
+ def manage_configure(args) -> int:
3242
+ """Main entry point for configuration management command.
3243
+
3244
+ This function maintains backward compatibility while using the new BaseCommand pattern.
3245
+ """
3246
+ command = ConfigureCommand()
3247
+ result = command.execute(args)
3248
+
3249
+ # Print result if needed
3250
+ if hasattr(args, "format") and args.format in ["json", "yaml"]:
3251
+ command.print_result(result, args)
3252
+
3253
+ return result.exit_code