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