claude-mpm 5.4.85__py3-none-any.whl → 5.6.1__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 (254) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/CLAUDE_MPM_OUTPUT_STYLE.md +8 -5
  3. claude_mpm/agents/{CLAUDE_MPM_FOUNDERS_OUTPUT_STYLE.md → CLAUDE_MPM_RESEARCH_OUTPUT_STYLE.md} +14 -6
  4. claude_mpm/agents/PM_INSTRUCTIONS.md +101 -703
  5. claude_mpm/agents/WORKFLOW.md +2 -0
  6. claude_mpm/agents/templates/circuit-breakers.md +26 -17
  7. claude_mpm/cli/commands/autotodos.py +566 -0
  8. claude_mpm/cli/commands/commander.py +46 -0
  9. claude_mpm/cli/commands/hook_errors.py +60 -60
  10. claude_mpm/cli/commands/monitor.py +2 -2
  11. claude_mpm/cli/commands/mpm_init/core.py +2 -2
  12. claude_mpm/cli/commands/run.py +35 -3
  13. claude_mpm/cli/executor.py +119 -16
  14. claude_mpm/cli/parsers/base_parser.py +71 -1
  15. claude_mpm/cli/parsers/commander_parser.py +83 -0
  16. claude_mpm/cli/parsers/run_parser.py +10 -0
  17. claude_mpm/cli/startup.py +54 -16
  18. claude_mpm/cli/startup_display.py +72 -5
  19. claude_mpm/cli/startup_logging.py +2 -2
  20. claude_mpm/cli/utils.py +7 -3
  21. claude_mpm/commander/__init__.py +72 -0
  22. claude_mpm/commander/adapters/__init__.py +31 -0
  23. claude_mpm/commander/adapters/base.py +191 -0
  24. claude_mpm/commander/adapters/claude_code.py +361 -0
  25. claude_mpm/commander/adapters/communication.py +366 -0
  26. claude_mpm/commander/api/__init__.py +16 -0
  27. claude_mpm/commander/api/app.py +105 -0
  28. claude_mpm/commander/api/errors.py +112 -0
  29. claude_mpm/commander/api/routes/__init__.py +8 -0
  30. claude_mpm/commander/api/routes/events.py +184 -0
  31. claude_mpm/commander/api/routes/inbox.py +171 -0
  32. claude_mpm/commander/api/routes/messages.py +148 -0
  33. claude_mpm/commander/api/routes/projects.py +271 -0
  34. claude_mpm/commander/api/routes/sessions.py +215 -0
  35. claude_mpm/commander/api/routes/work.py +260 -0
  36. claude_mpm/commander/api/schemas.py +182 -0
  37. claude_mpm/commander/chat/__init__.py +7 -0
  38. claude_mpm/commander/chat/cli.py +107 -0
  39. claude_mpm/commander/chat/commands.py +96 -0
  40. claude_mpm/commander/chat/repl.py +310 -0
  41. claude_mpm/commander/config.py +49 -0
  42. claude_mpm/commander/config_loader.py +115 -0
  43. claude_mpm/commander/daemon.py +398 -0
  44. claude_mpm/commander/events/__init__.py +26 -0
  45. claude_mpm/commander/events/manager.py +332 -0
  46. claude_mpm/commander/frameworks/__init__.py +12 -0
  47. claude_mpm/commander/frameworks/base.py +143 -0
  48. claude_mpm/commander/frameworks/claude_code.py +58 -0
  49. claude_mpm/commander/frameworks/mpm.py +62 -0
  50. claude_mpm/commander/inbox/__init__.py +16 -0
  51. claude_mpm/commander/inbox/dedup.py +128 -0
  52. claude_mpm/commander/inbox/inbox.py +224 -0
  53. claude_mpm/commander/inbox/models.py +70 -0
  54. claude_mpm/commander/instance_manager.py +337 -0
  55. claude_mpm/commander/llm/__init__.py +6 -0
  56. claude_mpm/commander/llm/openrouter_client.py +167 -0
  57. claude_mpm/commander/llm/summarizer.py +70 -0
  58. claude_mpm/commander/models/__init__.py +18 -0
  59. claude_mpm/commander/models/events.py +121 -0
  60. claude_mpm/commander/models/project.py +162 -0
  61. claude_mpm/commander/models/work.py +214 -0
  62. claude_mpm/commander/parsing/__init__.py +20 -0
  63. claude_mpm/commander/parsing/extractor.py +132 -0
  64. claude_mpm/commander/parsing/output_parser.py +270 -0
  65. claude_mpm/commander/parsing/patterns.py +100 -0
  66. claude_mpm/commander/persistence/__init__.py +11 -0
  67. claude_mpm/commander/persistence/event_store.py +274 -0
  68. claude_mpm/commander/persistence/state_store.py +309 -0
  69. claude_mpm/commander/persistence/work_store.py +164 -0
  70. claude_mpm/commander/polling/__init__.py +13 -0
  71. claude_mpm/commander/polling/event_detector.py +104 -0
  72. claude_mpm/commander/polling/output_buffer.py +49 -0
  73. claude_mpm/commander/polling/output_poller.py +153 -0
  74. claude_mpm/commander/project_session.py +268 -0
  75. claude_mpm/commander/proxy/__init__.py +12 -0
  76. claude_mpm/commander/proxy/formatter.py +89 -0
  77. claude_mpm/commander/proxy/output_handler.py +191 -0
  78. claude_mpm/commander/proxy/relay.py +155 -0
  79. claude_mpm/commander/registry.py +404 -0
  80. claude_mpm/commander/runtime/__init__.py +10 -0
  81. claude_mpm/commander/runtime/executor.py +191 -0
  82. claude_mpm/commander/runtime/monitor.py +316 -0
  83. claude_mpm/commander/session/__init__.py +6 -0
  84. claude_mpm/commander/session/context.py +81 -0
  85. claude_mpm/commander/session/manager.py +59 -0
  86. claude_mpm/commander/tmux_orchestrator.py +361 -0
  87. claude_mpm/commander/web/__init__.py +1 -0
  88. claude_mpm/commander/work/__init__.py +30 -0
  89. claude_mpm/commander/work/executor.py +189 -0
  90. claude_mpm/commander/work/queue.py +405 -0
  91. claude_mpm/commander/workflow/__init__.py +27 -0
  92. claude_mpm/commander/workflow/event_handler.py +219 -0
  93. claude_mpm/commander/workflow/notifier.py +146 -0
  94. claude_mpm/commands/mpm-config.md +8 -0
  95. claude_mpm/commands/mpm-doctor.md +8 -0
  96. claude_mpm/commands/mpm-help.md +8 -0
  97. claude_mpm/commands/mpm-init.md +8 -0
  98. claude_mpm/commands/mpm-monitor.md +8 -0
  99. claude_mpm/commands/mpm-organize.md +8 -0
  100. claude_mpm/commands/mpm-postmortem.md +8 -0
  101. claude_mpm/commands/mpm-session-resume.md +9 -1
  102. claude_mpm/commands/mpm-status.md +8 -0
  103. claude_mpm/commands/mpm-ticket-view.md +8 -0
  104. claude_mpm/commands/mpm-version.md +8 -0
  105. claude_mpm/commands/mpm.md +8 -0
  106. claude_mpm/config/agent_presets.py +8 -7
  107. claude_mpm/core/config.py +5 -0
  108. claude_mpm/core/hook_manager.py +51 -3
  109. claude_mpm/core/logger.py +10 -7
  110. claude_mpm/core/logging_utils.py +4 -2
  111. claude_mpm/core/output_style_manager.py +15 -5
  112. claude_mpm/core/unified_config.py +10 -6
  113. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.C33zOoyM.css +1 -0
  114. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.CW1J-YuA.css +1 -0
  115. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Cs_tUR18.js → 1WZnGYqX.js} +1 -1
  116. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CDuw-vjf.js → 67pF3qNn.js} +1 -1
  117. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{bTOqqlTd.js → 6RxdMKe4.js} +1 -1
  118. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DwBR2MJi.js → 8cZrfX0h.js} +1 -1
  119. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{ZGh7QtNv.js → 9a6T2nm-.js} +1 -1
  120. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D9lljYKQ.js → B443AUzu.js} +1 -1
  121. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{RJiighC3.js → B8AwtY2H.js} +1 -1
  122. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{uuIeMWc-.js → BF15LAsF.js} +1 -1
  123. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{D3k0OPJN.js → BRcwIQNr.js} +1 -1
  124. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CyWMqx4W.js → BV6nKitt.js} +1 -1
  125. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CiIAseT4.js → BViJ8lZt.js} +5 -5
  126. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CBBdVcY8.js → BcQ-Q0FE.js} +1 -1
  127. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BovzEFCE.js → Bpyvgze_.js} +1 -1
  128. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BzTRqg-z.js +1 -0
  129. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/C0Fr8dve.js +1 -0
  130. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{eNVUfhuA.js → C3rbW_a-.js} +1 -1
  131. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{GYwsonyD.js → C8WYN38h.js} +1 -1
  132. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BIF9m_hv.js → C9I8FlXH.js} +1 -1
  133. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B0uc0UOD.js → CIQcWgO2.js} +3 -3
  134. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Be7GpZd6.js → CIctN7YN.js} +1 -1
  135. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Bh0LDWpI.js → CKrS_JZW.js} +2 -2
  136. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DUrLdbGD.js → CR6P9C4A.js} +1 -1
  137. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B7xVLGWV.js → CRRR9MD_.js} +1 -1
  138. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/CRcR2DqT.js +334 -0
  139. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Dhb8PKl3.js → CSXtMOf0.js} +1 -1
  140. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BPYeabCQ.js → CT-sbxSk.js} +1 -1
  141. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{sQeU3Y1z.js → CWm6DJsp.js} +1 -1
  142. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CnA0NrzZ.js → CpqQ1Kzn.js} +1 -1
  143. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C4B-KCzX.js → D2nGpDRe.js} +1 -1
  144. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DGkLK5U1.js → D9iCMida.js} +1 -1
  145. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{BofRWZRR.js → D9ykgMoY.js} +1 -1
  146. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DmxopI1J.js → DL2Ldur1.js} +1 -1
  147. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C30mlcqg.js → DPfltzjH.js} +1 -1
  148. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Vzk33B_K.js → DR8nis88.js} +2 -2
  149. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DI7hHRFL.js → DUliQN2b.js} +1 -1
  150. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C4JcI4KD.js → DXlhR01x.js} +1 -1
  151. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{bT1r9zLR.js → D_lyTybS.js} +1 -1
  152. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DZX00Y4g.js → DngoTTgh.js} +1 -1
  153. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CzZX-COe.js → DqkmHtDC.js} +1 -1
  154. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{B7RN905-.js → DsDh8EYs.js} +1 -1
  155. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DLVjFsZ3.js → DypDmXgd.js} +1 -1
  156. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{iEWssX7S.js → IPYC-LnN.js} +1 -1
  157. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/JTLiF7dt.js +24 -0
  158. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DaimHw_p.js → JpevfAFt.js} +1 -1
  159. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{DY1XQ8fi.js → R8CEIRAd.js} +1 -1
  160. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Dle-35c7.js → Zxy7qc-l.js} +2 -2
  161. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/q9Hm6zAU.js +1 -0
  162. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{C_Usid8X.js → qtd3IeO4.js} +2 -2
  163. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{CzeYkLYB.js → ulBFON_C.js} +2 -2
  164. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/{Cfqx1Qun.js → wQVh1CoA.js} +1 -1
  165. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/{app.D6-I5TpK.js → app.Dr7t0z2J.js} +2 -2
  166. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.BGhZHUS3.js +1 -0
  167. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{0.m1gL8KXf.js → 0.RgBboRvH.js} +1 -1
  168. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/{1.CgNOuw-d.js → 1.DG-KkbDf.js} +1 -1
  169. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.D_jnf-x6.js +1 -0
  170. claude_mpm/dashboard/static/svelte-build/_app/version.json +1 -1
  171. claude_mpm/dashboard/static/svelte-build/index.html +9 -9
  172. claude_mpm/experimental/cli_enhancements.py +2 -1
  173. claude_mpm/hooks/claude_hooks/INTEGRATION_EXAMPLE.md +243 -0
  174. claude_mpm/hooks/claude_hooks/README_AUTO_PAUSE.md +403 -0
  175. claude_mpm/hooks/claude_hooks/auto_pause_handler.py +486 -0
  176. claude_mpm/hooks/claude_hooks/event_handlers.py +250 -11
  177. claude_mpm/hooks/claude_hooks/hook_handler.py +106 -89
  178. claude_mpm/hooks/claude_hooks/hook_wrapper.sh +6 -11
  179. claude_mpm/hooks/claude_hooks/installer.py +69 -5
  180. claude_mpm/hooks/claude_hooks/response_tracking.py +3 -1
  181. claude_mpm/hooks/claude_hooks/services/connection_manager.py +20 -0
  182. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +14 -77
  183. claude_mpm/hooks/claude_hooks/services/subagent_processor.py +30 -6
  184. claude_mpm/hooks/session_resume_hook.py +85 -1
  185. claude_mpm/init.py +1 -1
  186. claude_mpm/scripts/claude-hook-handler.sh +36 -10
  187. claude_mpm/services/agents/agent_recommendation_service.py +8 -8
  188. claude_mpm/services/agents/cache_git_manager.py +1 -1
  189. claude_mpm/services/agents/deployment/remote_agent_discovery_service.py +3 -0
  190. claude_mpm/services/agents/loading/framework_agent_loader.py +75 -2
  191. claude_mpm/services/cli/__init__.py +3 -0
  192. claude_mpm/services/cli/incremental_pause_manager.py +561 -0
  193. claude_mpm/services/cli/session_resume_helper.py +10 -2
  194. claude_mpm/services/delegation_detector.py +175 -0
  195. claude_mpm/services/diagnostics/checks/agent_sources_check.py +30 -0
  196. claude_mpm/services/diagnostics/checks/configuration_check.py +24 -0
  197. claude_mpm/services/diagnostics/checks/installation_check.py +22 -0
  198. claude_mpm/services/diagnostics/checks/mcp_services_check.py +23 -0
  199. claude_mpm/services/diagnostics/doctor_reporter.py +31 -1
  200. claude_mpm/services/diagnostics/models.py +14 -1
  201. claude_mpm/services/event_log.py +325 -0
  202. claude_mpm/services/infrastructure/__init__.py +4 -0
  203. claude_mpm/services/infrastructure/context_usage_tracker.py +291 -0
  204. claude_mpm/services/infrastructure/resume_log_generator.py +24 -5
  205. claude_mpm/services/monitor/daemon_manager.py +15 -4
  206. claude_mpm/services/monitor/management/lifecycle.py +8 -2
  207. claude_mpm/services/monitor/server.py +106 -16
  208. claude_mpm/services/pm_skills_deployer.py +259 -87
  209. claude_mpm/services/skills/git_skill_source_manager.py +51 -2
  210. claude_mpm/services/skills/selective_skill_deployer.py +114 -16
  211. claude_mpm/services/skills/skill_discovery_service.py +57 -3
  212. claude_mpm/services/socketio/handlers/hook.py +14 -7
  213. claude_mpm/services/socketio/server/main.py +12 -4
  214. claude_mpm/skills/bundled/pm/mpm/SKILL.md +38 -0
  215. claude_mpm/skills/bundled/pm/mpm-agent-update-workflow/SKILL.md +75 -0
  216. claude_mpm/skills/bundled/pm/mpm-circuit-breaker-enforcement/SKILL.md +476 -0
  217. claude_mpm/skills/bundled/pm/mpm-config/SKILL.md +29 -0
  218. claude_mpm/skills/bundled/pm/mpm-doctor/SKILL.md +53 -0
  219. claude_mpm/skills/bundled/pm/mpm-help/SKILL.md +35 -0
  220. claude_mpm/skills/bundled/pm/mpm-init/SKILL.md +125 -0
  221. claude_mpm/skills/bundled/pm/mpm-monitor/SKILL.md +32 -0
  222. claude_mpm/skills/bundled/pm/mpm-organize/SKILL.md +121 -0
  223. claude_mpm/skills/bundled/pm/mpm-postmortem/SKILL.md +22 -0
  224. claude_mpm/skills/bundled/pm/mpm-session-management/SKILL.md +312 -0
  225. claude_mpm/skills/bundled/pm/mpm-session-resume/SKILL.md +31 -0
  226. claude_mpm/skills/bundled/pm/mpm-status/SKILL.md +37 -0
  227. claude_mpm/skills/bundled/pm/{pm-teaching-mode → mpm-teaching-mode}/SKILL.md +2 -2
  228. claude_mpm/skills/bundled/pm/mpm-ticket-view/SKILL.md +110 -0
  229. claude_mpm/skills/bundled/pm/mpm-tool-usage-guide/SKILL.md +386 -0
  230. claude_mpm/skills/bundled/pm/mpm-version/SKILL.md +21 -0
  231. claude_mpm/skills/skill_manager.py +4 -4
  232. claude_mpm-5.6.1.dist-info/METADATA +391 -0
  233. {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/RECORD +244 -145
  234. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/0.DWzvg0-y.css +0 -1
  235. claude_mpm/dashboard/static/svelte-build/_app/immutable/assets/2.ThTw9_ym.css +0 -1
  236. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/4TdZjIqw.js +0 -1
  237. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/5shd3_w0.js +0 -24
  238. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/BKjSRqUr.js +0 -1
  239. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Da0KfYnO.js +0 -1
  240. claude_mpm/dashboard/static/svelte-build/_app/immutable/chunks/Dfy6j1xT.js +0 -323
  241. claude_mpm/dashboard/static/svelte-build/_app/immutable/entry/start.NWzMBYRp.js +0 -1
  242. claude_mpm/dashboard/static/svelte-build/_app/immutable/nodes/2.C0GcWctS.js +0 -1
  243. claude_mpm-5.4.85.dist-info/METADATA +0 -1023
  244. /claude_mpm/skills/bundled/pm/{pm-bug-reporting/pm-bug-reporting.md → mpm-bug-reporting/SKILL.md} +0 -0
  245. /claude_mpm/skills/bundled/pm/{pm-delegation-patterns → mpm-delegation-patterns}/SKILL.md +0 -0
  246. /claude_mpm/skills/bundled/pm/{pm-git-file-tracking → mpm-git-file-tracking}/SKILL.md +0 -0
  247. /claude_mpm/skills/bundled/pm/{pm-pr-workflow → mpm-pr-workflow}/SKILL.md +0 -0
  248. /claude_mpm/skills/bundled/pm/{pm-ticketing-integration → mpm-ticketing-integration}/SKILL.md +0 -0
  249. /claude_mpm/skills/bundled/pm/{pm-verification-protocols → mpm-verification-protocols}/SKILL.md +0 -0
  250. {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/WHEEL +0 -0
  251. {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/entry_points.txt +0 -0
  252. {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/licenses/LICENSE +0 -0
  253. {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/licenses/LICENSE-FAQ.md +0 -0
  254. {claude_mpm-5.4.85.dist-info → claude_mpm-5.6.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,337 @@
1
+ """Manages running Claude Code/MPM instances."""
2
+
3
+ import logging
4
+ from pathlib import Path
5
+ from typing import Optional
6
+
7
+ from claude_mpm.commander.adapters import (
8
+ AdapterResponse,
9
+ ClaudeCodeAdapter,
10
+ ClaudeCodeCommunicationAdapter,
11
+ )
12
+ from claude_mpm.commander.frameworks.base import BaseFramework, InstanceInfo
13
+ from claude_mpm.commander.frameworks.claude_code import ClaudeCodeFramework
14
+ from claude_mpm.commander.frameworks.mpm import MPMFramework
15
+ from claude_mpm.commander.tmux_orchestrator import TmuxOrchestrator
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ class InstanceNotFoundError(Exception):
21
+ """Raised when an instance is not found."""
22
+
23
+ def __init__(self, name: str):
24
+ super().__init__(f"Instance not found: {name}")
25
+ self.name = name
26
+
27
+
28
+ class FrameworkNotFoundError(Exception):
29
+ """Raised when a framework is not found or not available."""
30
+
31
+ def __init__(self, framework: str):
32
+ super().__init__(f"Framework not found or not available: {framework}")
33
+ self.framework = framework
34
+
35
+
36
+ class InstanceAlreadyExistsError(Exception):
37
+ """Raised when trying to start an instance that already exists."""
38
+
39
+ def __init__(self, name: str):
40
+ super().__init__(f"Instance already exists: {name}")
41
+ self.name = name
42
+
43
+
44
+ class InstanceManager:
45
+ """Manages lifecycle of Claude instances.
46
+
47
+ The InstanceManager coordinates framework selection, instance startup,
48
+ and tracking of running instances across the TmuxOrchestrator.
49
+
50
+ Attributes:
51
+ orchestrator: TmuxOrchestrator for managing tmux sessions/panes
52
+
53
+ Example:
54
+ >>> orchestrator = TmuxOrchestrator()
55
+ >>> manager = InstanceManager(orchestrator)
56
+ >>> frameworks = manager.list_frameworks()
57
+ >>> print(frameworks)
58
+ ['cc', 'mpm']
59
+ >>> instance = await manager.start_instance(
60
+ ... "myapp",
61
+ ... Path("/Users/user/myapp"),
62
+ ... framework="cc"
63
+ ... )
64
+ >>> print(instance.name, instance.framework)
65
+ myapp cc
66
+ """
67
+
68
+ def __init__(self, orchestrator: TmuxOrchestrator):
69
+ """Initialize the instance manager.
70
+
71
+ Args:
72
+ orchestrator: TmuxOrchestrator for managing tmux sessions/panes
73
+ """
74
+ self.orchestrator = orchestrator
75
+ self._instances: dict[str, InstanceInfo] = {}
76
+ self._frameworks = self._load_frameworks()
77
+ self._adapters: dict[str, ClaudeCodeCommunicationAdapter] = {}
78
+
79
+ def _load_frameworks(self) -> dict[str, BaseFramework]:
80
+ """Load available frameworks.
81
+
82
+ Returns:
83
+ Dict mapping framework name to framework instance
84
+
85
+ Example:
86
+ >>> manager = InstanceManager(orchestrator)
87
+ >>> frameworks = manager._load_frameworks()
88
+ >>> print(frameworks.keys())
89
+ dict_keys(['cc', 'mpm'])
90
+ """
91
+ frameworks = {}
92
+ for framework_class in [ClaudeCodeFramework, MPMFramework]:
93
+ framework = framework_class()
94
+ if framework.is_available():
95
+ frameworks[framework.name] = framework
96
+ logger.info(
97
+ f"Loaded framework: {framework.name} ({framework.display_name})"
98
+ )
99
+ else:
100
+ logger.warning(
101
+ f"Framework not available: {framework.name} ({framework.display_name})"
102
+ )
103
+
104
+ return frameworks
105
+
106
+ def list_frameworks(self) -> list[str]:
107
+ """List available framework names.
108
+
109
+ Returns:
110
+ List of framework names (e.g., ["cc", "mpm"])
111
+
112
+ Example:
113
+ >>> manager = InstanceManager(orchestrator)
114
+ >>> frameworks = manager.list_frameworks()
115
+ >>> print(frameworks)
116
+ ['cc', 'mpm']
117
+ """
118
+ return list(self._frameworks.keys())
119
+
120
+ async def start_instance(
121
+ self, name: str, project_path: Path, framework: str = "cc"
122
+ ) -> InstanceInfo:
123
+ """Start a new instance.
124
+
125
+ Args:
126
+ name: Instance name (e.g., "myapp")
127
+ project_path: Path to project directory
128
+ framework: Framework to use ("cc" or "mpm")
129
+
130
+ Returns:
131
+ InstanceInfo with tmux session details
132
+
133
+ Raises:
134
+ FrameworkNotFoundError: If framework is not available
135
+ InstanceAlreadyExistsError: If instance already exists
136
+
137
+ Example:
138
+ >>> manager = InstanceManager(orchestrator)
139
+ >>> instance = await manager.start_instance(
140
+ ... "myapp",
141
+ ... Path("/Users/user/myapp"),
142
+ ... framework="cc"
143
+ ... )
144
+ >>> print(instance.name, instance.framework)
145
+ myapp cc
146
+ """
147
+ # Check if instance already exists
148
+ if name in self._instances:
149
+ raise InstanceAlreadyExistsError(name)
150
+
151
+ # Get framework
152
+ if framework not in self._frameworks:
153
+ raise FrameworkNotFoundError(framework)
154
+
155
+ framework_obj = self._frameworks[framework]
156
+
157
+ # Get git info
158
+ git_branch, git_status = framework_obj.get_git_info(project_path)
159
+
160
+ # Ensure tmux session exists
161
+ self.orchestrator.create_session()
162
+
163
+ # Create pane
164
+ pane_target = self.orchestrator.create_pane(name, str(project_path))
165
+
166
+ # Start framework in pane
167
+ startup_cmd = framework_obj.get_startup_command(project_path)
168
+ self.orchestrator.send_keys(pane_target, startup_cmd)
169
+
170
+ # Create instance info
171
+ instance = InstanceInfo(
172
+ name=name,
173
+ project_path=project_path,
174
+ framework=framework,
175
+ tmux_session=self.orchestrator.session_name,
176
+ pane_target=pane_target,
177
+ git_branch=git_branch,
178
+ git_status=git_status,
179
+ )
180
+
181
+ # Track instance
182
+ self._instances[name] = instance
183
+
184
+ # Create communication adapter for the instance (only for Claude Code for now)
185
+ if framework == "cc":
186
+ runtime_adapter = ClaudeCodeAdapter()
187
+ comm_adapter = ClaudeCodeCommunicationAdapter(
188
+ orchestrator=self.orchestrator,
189
+ pane_target=pane_target,
190
+ runtime_adapter=runtime_adapter,
191
+ )
192
+ self._adapters[name] = comm_adapter
193
+ logger.debug(f"Created communication adapter for instance '{name}'")
194
+
195
+ logger.info(
196
+ f"Started instance '{name}' with framework '{framework}' at {project_path}"
197
+ )
198
+
199
+ return instance
200
+
201
+ async def stop_instance(self, name: str) -> bool:
202
+ """Stop an instance.
203
+
204
+ Args:
205
+ name: Instance name
206
+
207
+ Returns:
208
+ True if instance was stopped
209
+
210
+ Raises:
211
+ InstanceNotFoundError: If instance not found
212
+
213
+ Example:
214
+ >>> manager = InstanceManager(orchestrator)
215
+ >>> await manager.stop_instance("myapp")
216
+ True
217
+ """
218
+ if name not in self._instances:
219
+ raise InstanceNotFoundError(name)
220
+
221
+ instance = self._instances[name]
222
+
223
+ # Kill tmux pane
224
+ self.orchestrator.kill_pane(instance.pane_target)
225
+
226
+ # Remove adapter if exists
227
+ if name in self._adapters:
228
+ del self._adapters[name]
229
+ logger.debug(f"Removed adapter for instance '{name}'")
230
+
231
+ # Remove from tracking
232
+ del self._instances[name]
233
+
234
+ logger.info(f"Stopped instance '{name}'")
235
+
236
+ return True
237
+
238
+ def get_instance(self, name: str) -> Optional[InstanceInfo]:
239
+ """Get instance by name.
240
+
241
+ Args:
242
+ name: Instance name
243
+
244
+ Returns:
245
+ InstanceInfo if found, None otherwise
246
+
247
+ Example:
248
+ >>> manager = InstanceManager(orchestrator)
249
+ >>> instance = manager.get_instance("myapp")
250
+ >>> if instance:
251
+ ... print(instance.name, instance.framework)
252
+ myapp cc
253
+ """
254
+ return self._instances.get(name)
255
+
256
+ def list_instances(self) -> list[InstanceInfo]:
257
+ """List all running instances.
258
+
259
+ Returns:
260
+ List of InstanceInfo for all running instances
261
+
262
+ Example:
263
+ >>> manager = InstanceManager(orchestrator)
264
+ >>> instances = manager.list_instances()
265
+ >>> for instance in instances:
266
+ ... print(instance.name, instance.framework)
267
+ myapp cc
268
+ otherapp mpm
269
+ """
270
+ return list(self._instances.values())
271
+
272
+ async def send_to_instance(
273
+ self, name: str, message: str, wait_for_response: bool = False
274
+ ) -> Optional[AdapterResponse]:
275
+ """Send a message/command to an instance.
276
+
277
+ Args:
278
+ name: Instance name
279
+ message: Message to send
280
+ wait_for_response: If True, wait for and return response
281
+
282
+ Returns:
283
+ AdapterResponse if wait_for_response=True, None otherwise
284
+
285
+ Raises:
286
+ InstanceNotFoundError: If instance not found
287
+
288
+ Example:
289
+ >>> manager = InstanceManager(orchestrator)
290
+ >>> # Send without waiting
291
+ >>> await manager.send_to_instance("myapp", "Fix the bug in main.py")
292
+ >>> # Send and wait for response
293
+ >>> response = await manager.send_to_instance(
294
+ ... "myapp", "Fix the bug", wait_for_response=True
295
+ ... )
296
+ >>> print(response.content)
297
+ """
298
+ if name not in self._instances:
299
+ raise InstanceNotFoundError(name)
300
+
301
+ instance = self._instances[name]
302
+
303
+ # Use adapter if available
304
+ if name in self._adapters:
305
+ adapter = self._adapters[name]
306
+ await adapter.send(message)
307
+ logger.info(
308
+ f"Sent message via adapter to instance '{name}': {message[:50]}..."
309
+ )
310
+
311
+ if wait_for_response:
312
+ return await adapter.receive()
313
+ return None
314
+
315
+ # Fallback to direct tmux if no adapter
316
+ self.orchestrator.send_keys(instance.pane_target, message)
317
+ logger.info(f"Sent message to instance '{name}': {message[:50]}...")
318
+ return None
319
+
320
+ def get_adapter(self, name: str) -> Optional[ClaudeCodeCommunicationAdapter]:
321
+ """Get communication adapter for an instance.
322
+
323
+ Args:
324
+ name: Instance name
325
+
326
+ Returns:
327
+ ClaudeCodeCommunicationAdapter if exists, None otherwise
328
+
329
+ Example:
330
+ >>> manager = InstanceManager(orchestrator)
331
+ >>> adapter = manager.get_adapter("myapp")
332
+ >>> if adapter:
333
+ ... await adapter.send("Create a new file")
334
+ ... async for chunk in adapter.stream_response():
335
+ ... print(chunk, end='')
336
+ """
337
+ return self._adapters.get(name)
@@ -0,0 +1,6 @@
1
+ """LLM integration for Commander chat interface and summarization."""
2
+
3
+ from .openrouter_client import OpenRouterClient, OpenRouterConfig
4
+ from .summarizer import OutputSummarizer
5
+
6
+ __all__ = ["OpenRouterClient", "OpenRouterConfig", "OutputSummarizer"]
@@ -0,0 +1,167 @@
1
+ """OpenRouter LLM client for Commander chat interface."""
2
+
3
+ import os
4
+ from dataclasses import dataclass
5
+ from typing import AsyncIterator
6
+
7
+ import httpx
8
+
9
+
10
+ @dataclass
11
+ class OpenRouterConfig:
12
+ """Configuration for OpenRouter API client."""
13
+
14
+ api_key: str | None = None # Falls back to OPENROUTER_API_KEY env
15
+ model: str = "anthropic/claude-3.5-sonnet"
16
+ base_url: str = "https://openrouter.ai/api/v1"
17
+ max_tokens: int = 4096
18
+ temperature: float = 0.7
19
+
20
+
21
+ class OpenRouterClient:
22
+ """Async client for OpenRouter API."""
23
+
24
+ def __init__(self, config: OpenRouterConfig | None = None):
25
+ """Initialize client with config.
26
+
27
+ Args:
28
+ config: OpenRouter configuration. Defaults to OpenRouterConfig().
29
+
30
+ Raises:
31
+ ValueError: If OPENROUTER_API_KEY is not set.
32
+ """
33
+ self.config = config or OpenRouterConfig()
34
+ self._api_key = self.config.api_key or os.getenv("OPENROUTER_API_KEY")
35
+ if not self._api_key:
36
+ raise ValueError("OPENROUTER_API_KEY not set")
37
+
38
+ async def chat(self, messages: list[dict], system: str | None = None) -> str:
39
+ """Send chat completion request, return response content.
40
+
41
+ Args:
42
+ messages: List of message dicts with 'role' and 'content' keys.
43
+ system: Optional system prompt.
44
+
45
+ Returns:
46
+ Response content from the model.
47
+
48
+ Raises:
49
+ httpx.HTTPStatusError: If API request fails.
50
+ """
51
+ async with httpx.AsyncClient() as client:
52
+ # Build request payload
53
+ payload = {
54
+ "model": self.config.model,
55
+ "messages": messages,
56
+ "max_tokens": self.config.max_tokens,
57
+ "temperature": self.config.temperature,
58
+ }
59
+
60
+ if system:
61
+ payload["system"] = system
62
+
63
+ # Send request
64
+ response = await client.post(
65
+ f"{self.config.base_url}/chat/completions",
66
+ headers={
67
+ "Authorization": f"Bearer {self._api_key}",
68
+ "Content-Type": "application/json",
69
+ },
70
+ json=payload,
71
+ timeout=30.0,
72
+ )
73
+ response.raise_for_status()
74
+
75
+ # Extract content from response
76
+ data = response.json()
77
+ return data["choices"][0]["message"]["content"]
78
+
79
+ async def chat_stream(
80
+ self, messages: list[dict], system: str | None = None
81
+ ) -> AsyncIterator[str]:
82
+ """Stream chat completion, yield chunks.
83
+
84
+ Args:
85
+ messages: List of message dicts with 'role' and 'content' keys.
86
+ system: Optional system prompt.
87
+
88
+ Yields:
89
+ Content chunks from the streaming response.
90
+
91
+ Raises:
92
+ httpx.HTTPStatusError: If API request fails.
93
+ """
94
+ async with httpx.AsyncClient() as client:
95
+ # Build request payload
96
+ payload = {
97
+ "model": self.config.model,
98
+ "messages": messages,
99
+ "max_tokens": self.config.max_tokens,
100
+ "temperature": self.config.temperature,
101
+ "stream": True,
102
+ }
103
+
104
+ if system:
105
+ payload["system"] = system
106
+
107
+ # Send streaming request
108
+ async with client.stream(
109
+ "POST",
110
+ f"{self.config.base_url}/chat/completions",
111
+ headers={
112
+ "Authorization": f"Bearer {self._api_key}",
113
+ "Content-Type": "application/json",
114
+ },
115
+ json=payload,
116
+ timeout=30.0,
117
+ ) as response:
118
+ response.raise_for_status()
119
+
120
+ # Parse SSE stream
121
+ async for line in response.aiter_lines():
122
+ if not line.strip():
123
+ continue
124
+
125
+ # SSE format: "data: {json}"
126
+ if line.startswith("data: "):
127
+ data_str = line[6:] # Remove "data: " prefix
128
+
129
+ # Check for stream end
130
+ if data_str == "[DONE]":
131
+ break
132
+
133
+ # Parse JSON chunk
134
+ import json
135
+
136
+ try:
137
+ data = json.loads(data_str)
138
+ delta = data["choices"][0].get("delta", {})
139
+ if "content" in delta:
140
+ yield delta["content"]
141
+ except (json.JSONDecodeError, KeyError, IndexError):
142
+ # Skip malformed chunks
143
+ continue
144
+
145
+ async def summarize(self, text: str, max_length: int = 500) -> str:
146
+ """Summarize text to max_length characters.
147
+
148
+ Args:
149
+ text: Text to summarize.
150
+ max_length: Maximum length of summary in characters.
151
+
152
+ Returns:
153
+ Summarized text.
154
+ """
155
+ messages = [
156
+ {
157
+ "role": "user",
158
+ "content": f"Summarize the following text in approximately {max_length} characters or less:\n\n{text}",
159
+ }
160
+ ]
161
+
162
+ system = (
163
+ "You are a concise summarization assistant. "
164
+ "Provide clear, accurate summaries that capture the key points."
165
+ )
166
+
167
+ return await self.chat(messages, system=system)
@@ -0,0 +1,70 @@
1
+ """Summarizes Claude Code output for user display."""
2
+
3
+ from .openrouter_client import OpenRouterClient
4
+
5
+
6
+ class OutputSummarizer:
7
+ """Summarizes long output from Claude Code instances."""
8
+
9
+ def __init__(self, client: OpenRouterClient):
10
+ """Initialize with OpenRouter client.
11
+
12
+ Args:
13
+ client: OpenRouterClient instance for LLM requests.
14
+ """
15
+ self.client = client
16
+
17
+ async def summarize(self, output: str, context: str | None = None) -> str:
18
+ """Summarize Claude Code output.
19
+
20
+ Args:
21
+ output: Raw output from Claude Code.
22
+ context: Optional context about what was requested.
23
+
24
+ Returns:
25
+ Concise summary of the output.
26
+ """
27
+ # Build summarization prompt
28
+ if context:
29
+ prompt = f"""Summarize the following Claude Code output.
30
+
31
+ Context: {context}
32
+
33
+ Output:
34
+ {output}
35
+
36
+ Provide a concise summary (2-3 sentences) that captures:
37
+ 1. What action was taken
38
+ 2. The key result or outcome
39
+ 3. Any important warnings or next steps
40
+
41
+ Summary:"""
42
+ else:
43
+ prompt = f"""Summarize the following Claude Code output in 2-3 sentences.
44
+
45
+ Output:
46
+ {output}
47
+
48
+ Summary:"""
49
+
50
+ messages = [{"role": "user", "content": prompt}]
51
+
52
+ system = (
53
+ "You are a technical summarization assistant. "
54
+ "Provide clear, concise summaries of command output and code execution results. "
55
+ "Focus on actionable information and key outcomes."
56
+ )
57
+
58
+ return await self.client.chat(messages, system=system)
59
+
60
+ def needs_summarization(self, output: str, threshold: int = 500) -> bool:
61
+ """Check if output is long enough to warrant summarization.
62
+
63
+ Args:
64
+ output: Output text to check.
65
+ threshold: Character count threshold for summarization.
66
+
67
+ Returns:
68
+ True if output exceeds threshold, False otherwise.
69
+ """
70
+ return len(output) > threshold
@@ -0,0 +1,18 @@
1
+ """Data models for MPM Commander.
2
+
3
+ This module exports core data structures for project management,
4
+ sessions, conversation threads, and work items.
5
+ """
6
+
7
+ from .project import Project, ProjectState, ThreadMessage, ToolSession
8
+ from .work import WorkItem, WorkPriority, WorkState
9
+
10
+ __all__ = [
11
+ "Project",
12
+ "ProjectState",
13
+ "ThreadMessage",
14
+ "ToolSession",
15
+ "WorkItem",
16
+ "WorkPriority",
17
+ "WorkState",
18
+ ]