claude-mpm 5.4.96__py3-none-any.whl → 5.6.10__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (191) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/{CLAUDE_MPM_FOUNDERS_OUTPUT_STYLE.md → CLAUDE_MPM_RESEARCH_OUTPUT_STYLE.md} +14 -6
  3. claude_mpm/agents/PM_INSTRUCTIONS.md +44 -10
  4. claude_mpm/agents/WORKFLOW.md +2 -0
  5. claude_mpm/agents/templates/circuit-breakers.md +26 -17
  6. claude_mpm/cli/commands/autotodos.py +45 -5
  7. claude_mpm/cli/commands/commander.py +46 -0
  8. claude_mpm/cli/commands/hook_errors.py +60 -60
  9. claude_mpm/cli/commands/run.py +35 -3
  10. claude_mpm/cli/commands/skill_source.py +51 -2
  11. claude_mpm/cli/commands/skills.py +5 -3
  12. claude_mpm/cli/executor.py +32 -17
  13. claude_mpm/cli/parsers/base_parser.py +17 -0
  14. claude_mpm/cli/parsers/commander_parser.py +83 -0
  15. claude_mpm/cli/parsers/run_parser.py +10 -0
  16. claude_mpm/cli/parsers/skill_source_parser.py +4 -0
  17. claude_mpm/cli/parsers/skills_parser.py +5 -0
  18. claude_mpm/cli/startup.py +20 -2
  19. claude_mpm/cli/utils.py +7 -3
  20. claude_mpm/commander/__init__.py +72 -0
  21. claude_mpm/commander/adapters/__init__.py +31 -0
  22. claude_mpm/commander/adapters/base.py +191 -0
  23. claude_mpm/commander/adapters/claude_code.py +361 -0
  24. claude_mpm/commander/adapters/communication.py +366 -0
  25. claude_mpm/commander/api/__init__.py +16 -0
  26. claude_mpm/commander/api/app.py +105 -0
  27. claude_mpm/commander/api/errors.py +133 -0
  28. claude_mpm/commander/api/routes/__init__.py +8 -0
  29. claude_mpm/commander/api/routes/events.py +184 -0
  30. claude_mpm/commander/api/routes/inbox.py +171 -0
  31. claude_mpm/commander/api/routes/messages.py +148 -0
  32. claude_mpm/commander/api/routes/projects.py +271 -0
  33. claude_mpm/commander/api/routes/sessions.py +228 -0
  34. claude_mpm/commander/api/routes/work.py +260 -0
  35. claude_mpm/commander/api/schemas.py +182 -0
  36. claude_mpm/commander/chat/__init__.py +7 -0
  37. claude_mpm/commander/chat/cli.py +107 -0
  38. claude_mpm/commander/chat/commands.py +96 -0
  39. claude_mpm/commander/chat/repl.py +310 -0
  40. claude_mpm/commander/config.py +49 -0
  41. claude_mpm/commander/config_loader.py +115 -0
  42. claude_mpm/commander/daemon.py +398 -0
  43. claude_mpm/commander/events/__init__.py +26 -0
  44. claude_mpm/commander/events/manager.py +332 -0
  45. claude_mpm/commander/frameworks/__init__.py +12 -0
  46. claude_mpm/commander/frameworks/base.py +143 -0
  47. claude_mpm/commander/frameworks/claude_code.py +58 -0
  48. claude_mpm/commander/frameworks/mpm.py +62 -0
  49. claude_mpm/commander/inbox/__init__.py +16 -0
  50. claude_mpm/commander/inbox/dedup.py +128 -0
  51. claude_mpm/commander/inbox/inbox.py +224 -0
  52. claude_mpm/commander/inbox/models.py +70 -0
  53. claude_mpm/commander/instance_manager.py +337 -0
  54. claude_mpm/commander/llm/__init__.py +6 -0
  55. claude_mpm/commander/llm/openrouter_client.py +167 -0
  56. claude_mpm/commander/llm/summarizer.py +70 -0
  57. claude_mpm/commander/models/__init__.py +18 -0
  58. claude_mpm/commander/models/events.py +121 -0
  59. claude_mpm/commander/models/project.py +162 -0
  60. claude_mpm/commander/models/work.py +214 -0
  61. claude_mpm/commander/parsing/__init__.py +20 -0
  62. claude_mpm/commander/parsing/extractor.py +132 -0
  63. claude_mpm/commander/parsing/output_parser.py +270 -0
  64. claude_mpm/commander/parsing/patterns.py +100 -0
  65. claude_mpm/commander/persistence/__init__.py +11 -0
  66. claude_mpm/commander/persistence/event_store.py +274 -0
  67. claude_mpm/commander/persistence/state_store.py +309 -0
  68. claude_mpm/commander/persistence/work_store.py +164 -0
  69. claude_mpm/commander/polling/__init__.py +13 -0
  70. claude_mpm/commander/polling/event_detector.py +104 -0
  71. claude_mpm/commander/polling/output_buffer.py +49 -0
  72. claude_mpm/commander/polling/output_poller.py +153 -0
  73. claude_mpm/commander/project_session.py +268 -0
  74. claude_mpm/commander/proxy/__init__.py +12 -0
  75. claude_mpm/commander/proxy/formatter.py +89 -0
  76. claude_mpm/commander/proxy/output_handler.py +191 -0
  77. claude_mpm/commander/proxy/relay.py +155 -0
  78. claude_mpm/commander/registry.py +404 -0
  79. claude_mpm/commander/runtime/__init__.py +10 -0
  80. claude_mpm/commander/runtime/executor.py +191 -0
  81. claude_mpm/commander/runtime/monitor.py +316 -0
  82. claude_mpm/commander/session/__init__.py +6 -0
  83. claude_mpm/commander/session/context.py +81 -0
  84. claude_mpm/commander/session/manager.py +59 -0
  85. claude_mpm/commander/tmux_orchestrator.py +361 -0
  86. claude_mpm/commander/web/__init__.py +1 -0
  87. claude_mpm/commander/work/__init__.py +30 -0
  88. claude_mpm/commander/work/executor.py +189 -0
  89. claude_mpm/commander/work/queue.py +405 -0
  90. claude_mpm/commander/workflow/__init__.py +27 -0
  91. claude_mpm/commander/workflow/event_handler.py +219 -0
  92. claude_mpm/commander/workflow/notifier.py +146 -0
  93. claude_mpm/commands/mpm-config.md +8 -0
  94. claude_mpm/commands/mpm-doctor.md +8 -0
  95. claude_mpm/commands/mpm-help.md +8 -0
  96. claude_mpm/commands/mpm-init.md +8 -0
  97. claude_mpm/commands/mpm-monitor.md +8 -0
  98. claude_mpm/commands/mpm-organize.md +8 -0
  99. claude_mpm/commands/mpm-postmortem.md +8 -0
  100. claude_mpm/commands/mpm-session-resume.md +8 -0
  101. claude_mpm/commands/mpm-status.md +8 -0
  102. claude_mpm/commands/mpm-ticket-view.md +8 -0
  103. claude_mpm/commands/mpm-version.md +8 -0
  104. claude_mpm/commands/mpm.md +8 -0
  105. claude_mpm/config/agent_presets.py +8 -7
  106. claude_mpm/config/skill_sources.py +16 -0
  107. claude_mpm/core/config.py +32 -19
  108. claude_mpm/core/logger.py +26 -9
  109. claude_mpm/core/logging_utils.py +35 -11
  110. claude_mpm/core/output_style_manager.py +15 -5
  111. claude_mpm/core/unified_config.py +10 -6
  112. claude_mpm/core/unified_paths.py +68 -80
  113. claude_mpm/experimental/cli_enhancements.py +2 -1
  114. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-312.pyc +0 -0
  115. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-314.pyc +0 -0
  116. claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-311.pyc +0 -0
  117. claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-312.pyc +0 -0
  118. claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-314.pyc +0 -0
  119. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
  120. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-312.pyc +0 -0
  121. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-314.pyc +0 -0
  122. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
  123. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-312.pyc +0 -0
  124. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-314.pyc +0 -0
  125. claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
  126. claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-314.pyc +0 -0
  127. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
  128. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-312.pyc +0 -0
  129. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-314.pyc +0 -0
  130. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
  131. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-312.pyc +0 -0
  132. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-314.pyc +0 -0
  133. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-312.pyc +0 -0
  134. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-314.pyc +0 -0
  135. claude_mpm/hooks/claude_hooks/auto_pause_handler.py +29 -30
  136. claude_mpm/hooks/claude_hooks/event_handlers.py +90 -99
  137. claude_mpm/hooks/claude_hooks/hook_handler.py +81 -88
  138. claude_mpm/hooks/claude_hooks/hook_wrapper.sh +6 -11
  139. claude_mpm/hooks/claude_hooks/installer.py +116 -8
  140. claude_mpm/hooks/claude_hooks/memory_integration.py +51 -31
  141. claude_mpm/hooks/claude_hooks/response_tracking.py +39 -58
  142. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-312.pyc +0 -0
  143. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-314.pyc +0 -0
  144. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
  145. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-312.pyc +0 -0
  146. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-314.pyc +0 -0
  147. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-312.pyc +0 -0
  148. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-314.pyc +0 -0
  149. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
  150. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-312.pyc +0 -0
  151. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-314.pyc +0 -0
  152. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
  153. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-312.pyc +0 -0
  154. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-314.pyc +0 -0
  155. claude_mpm/hooks/claude_hooks/services/connection_manager.py +23 -28
  156. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +36 -103
  157. claude_mpm/hooks/claude_hooks/services/state_manager.py +23 -36
  158. claude_mpm/hooks/claude_hooks/services/subagent_processor.py +47 -73
  159. claude_mpm/hooks/session_resume_hook.py +22 -18
  160. claude_mpm/hooks/templates/pre_tool_use_template.py +10 -2
  161. claude_mpm/scripts/claude-hook-handler.sh +43 -16
  162. claude_mpm/services/agents/agent_recommendation_service.py +8 -8
  163. claude_mpm/services/agents/agent_selection_service.py +2 -2
  164. claude_mpm/services/agents/loading/framework_agent_loader.py +75 -2
  165. claude_mpm/services/agents/single_tier_deployment_service.py +4 -4
  166. claude_mpm/services/event_log.py +8 -0
  167. claude_mpm/services/pm_skills_deployer.py +84 -6
  168. claude_mpm/services/skills/git_skill_source_manager.py +130 -10
  169. claude_mpm/services/skills/selective_skill_deployer.py +28 -0
  170. claude_mpm/services/skills/skill_discovery_service.py +74 -4
  171. claude_mpm/services/skills_deployer.py +31 -5
  172. claude_mpm/skills/bundled/pm/mpm/SKILL.md +38 -0
  173. claude_mpm/skills/bundled/pm/mpm-config/SKILL.md +29 -0
  174. claude_mpm/skills/bundled/pm/mpm-doctor/SKILL.md +53 -0
  175. claude_mpm/skills/bundled/pm/mpm-help/SKILL.md +35 -0
  176. claude_mpm/skills/bundled/pm/mpm-init/SKILL.md +125 -0
  177. claude_mpm/skills/bundled/pm/mpm-monitor/SKILL.md +32 -0
  178. claude_mpm/skills/bundled/pm/mpm-organize/SKILL.md +121 -0
  179. claude_mpm/skills/bundled/pm/mpm-postmortem/SKILL.md +22 -0
  180. claude_mpm/skills/bundled/pm/mpm-session-resume/SKILL.md +31 -0
  181. claude_mpm/skills/bundled/pm/mpm-status/SKILL.md +37 -0
  182. claude_mpm/skills/bundled/pm/mpm-ticket-view/SKILL.md +110 -0
  183. claude_mpm/skills/bundled/pm/mpm-version/SKILL.md +21 -0
  184. {claude_mpm-5.4.96.dist-info → claude_mpm-5.6.10.dist-info}/METADATA +18 -4
  185. {claude_mpm-5.4.96.dist-info → claude_mpm-5.6.10.dist-info}/RECORD +190 -79
  186. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager.cpython-311.pyc +0 -0
  187. {claude_mpm-5.4.96.dist-info → claude_mpm-5.6.10.dist-info}/WHEEL +0 -0
  188. {claude_mpm-5.4.96.dist-info → claude_mpm-5.6.10.dist-info}/entry_points.txt +0 -0
  189. {claude_mpm-5.4.96.dist-info → claude_mpm-5.6.10.dist-info}/licenses/LICENSE +0 -0
  190. {claude_mpm-5.4.96.dist-info → claude_mpm-5.6.10.dist-info}/licenses/LICENSE-FAQ.md +0 -0
  191. {claude_mpm-5.4.96.dist-info → claude_mpm-5.6.10.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,191 @@
1
+ """Base runtime adapter interface for MPM Commander.
2
+
3
+ This module defines the abstract interface for runtime adapters that bridge
4
+ between the TmuxOrchestrator and various AI coding tools (Claude Code, Cursor, etc.).
5
+ """
6
+
7
+ from abc import ABC, abstractmethod
8
+ from dataclasses import dataclass
9
+ from enum import Enum
10
+ from typing import List, Optional, Set
11
+
12
+
13
+ class Capability(Enum):
14
+ """Capabilities that a runtime adapter can provide."""
15
+
16
+ TOOL_USE = "tool_use"
17
+ FILE_EDIT = "file_edit"
18
+ FILE_CREATE = "file_create"
19
+ GIT_OPERATIONS = "git_operations"
20
+ SHELL_COMMANDS = "shell_commands"
21
+ WEB_SEARCH = "web_search"
22
+ COMPLEX_REASONING = "complex_reasoning"
23
+
24
+
25
+ @dataclass
26
+ class ParsedResponse:
27
+ """Parsed output from a runtime tool.
28
+
29
+ Attributes:
30
+ content: The extracted text content, with ANSI codes removed
31
+ is_complete: True if tool is waiting for input (idle state)
32
+ is_error: True if an error was detected in the output
33
+ error_message: The error message if is_error is True
34
+ is_question: True if tool is asking a question
35
+ question_text: The question text if is_question is True
36
+ options: List of options if presenting a choice
37
+
38
+ Example:
39
+ >>> response = ParsedResponse(
40
+ ... content="File created successfully",
41
+ ... is_complete=True,
42
+ ... is_error=False,
43
+ ... error_message=None,
44
+ ... is_question=False,
45
+ ... question_text=None,
46
+ ... options=None
47
+ ... )
48
+ """
49
+
50
+ content: str
51
+ is_complete: bool
52
+ is_error: bool
53
+ error_message: Optional[str] = None
54
+ is_question: bool = False
55
+ question_text: Optional[str] = None
56
+ options: Optional[List[str]] = None
57
+
58
+
59
+ class RuntimeAdapter(ABC):
60
+ """Abstract base class for runtime adapters.
61
+
62
+ A runtime adapter provides the interface between the TmuxOrchestrator
63
+ and a specific AI coding tool. It handles:
64
+ - Launching the tool with appropriate settings
65
+ - Formatting input messages
66
+ - Detecting tool states (idle, error, questioning)
67
+ - Parsing tool output into structured responses
68
+
69
+ Example:
70
+ >>> class MyAdapter(RuntimeAdapter):
71
+ ... @property
72
+ ... def name(self) -> str:
73
+ ... return "my-tool"
74
+ ...
75
+ ... def build_launch_command(self, project_path: str) -> str:
76
+ ... return f"cd {project_path} && my-tool --interactive"
77
+ """
78
+
79
+ @abstractmethod
80
+ def build_launch_command(
81
+ self, project_path: str, agent_prompt: Optional[str] = None
82
+ ) -> str:
83
+ """Generate shell command to start the tool.
84
+
85
+ Args:
86
+ project_path: Absolute path to the project directory
87
+ agent_prompt: Optional system prompt to configure the agent
88
+
89
+ Returns:
90
+ Shell command string ready to execute
91
+
92
+ Example:
93
+ >>> adapter.build_launch_command("/home/user/project", "You are a Python expert")
94
+ 'cd /home/user/project && claude --system-prompt "You are a Python expert"'
95
+ """
96
+
97
+ @abstractmethod
98
+ def format_input(self, message: str) -> str:
99
+ """Prepare message for tool's input format.
100
+
101
+ Args:
102
+ message: The user message to send
103
+
104
+ Returns:
105
+ Formatted message ready to send to the tool
106
+
107
+ Example:
108
+ >>> adapter.format_input("Fix the bug in main.py")
109
+ 'Fix the bug in main.py'
110
+ """
111
+
112
+ @abstractmethod
113
+ def detect_idle(self, output: str) -> bool:
114
+ """Recognize when tool is waiting for input.
115
+
116
+ Args:
117
+ output: Raw output from the tool (may contain ANSI codes)
118
+
119
+ Returns:
120
+ True if the tool is in an idle state awaiting input
121
+
122
+ Example:
123
+ >>> adapter.detect_idle("Done editing file.\\n> ")
124
+ True
125
+ >>> adapter.detect_idle("Processing request...")
126
+ False
127
+ """
128
+
129
+ @abstractmethod
130
+ def detect_error(self, output: str) -> Optional[str]:
131
+ """Recognize error states, return error message if found.
132
+
133
+ Args:
134
+ output: Raw output from the tool
135
+
136
+ Returns:
137
+ Error message string if error detected, None otherwise
138
+
139
+ Example:
140
+ >>> adapter.detect_error("Error: File not found: config.py")
141
+ 'Error: File not found: config.py'
142
+ >>> adapter.detect_error("File edited successfully")
143
+ None
144
+ """
145
+
146
+ @abstractmethod
147
+ def parse_response(self, output: str) -> ParsedResponse:
148
+ """Extract meaningful content from output.
149
+
150
+ This method combines all detection logic (idle, error, questions)
151
+ into a single structured response.
152
+
153
+ Args:
154
+ output: Raw output from the tool
155
+
156
+ Returns:
157
+ ParsedResponse with all detected states and content
158
+
159
+ Example:
160
+ >>> response = adapter.parse_response("Error: Invalid input\\n> ")
161
+ >>> response.is_error
162
+ True
163
+ >>> response.is_complete
164
+ True
165
+ """
166
+
167
+ @property
168
+ @abstractmethod
169
+ def capabilities(self) -> Set[Capability]:
170
+ """What this tool can do.
171
+
172
+ Returns:
173
+ Set of Capability enums indicating supported features
174
+
175
+ Example:
176
+ >>> adapter.capabilities
177
+ {Capability.FILE_EDIT, Capability.TOOL_USE}
178
+ """
179
+
180
+ @property
181
+ @abstractmethod
182
+ def name(self) -> str:
183
+ """Runtime identifier.
184
+
185
+ Returns:
186
+ Unique name for this runtime adapter
187
+
188
+ Example:
189
+ >>> adapter.name
190
+ 'claude-code'
191
+ """
@@ -0,0 +1,361 @@
1
+ """Claude Code CLI runtime adapter.
2
+
3
+ This module implements the RuntimeAdapter interface for the Claude Code CLI tool.
4
+ It handles launching Claude Code, detecting its various states, and parsing its output.
5
+ """
6
+
7
+ import logging
8
+ import re
9
+ import shlex
10
+ from typing import List, Optional, Set
11
+
12
+ from .base import Capability, ParsedResponse, RuntimeAdapter
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class ClaudeCodeAdapter(RuntimeAdapter):
18
+ """Adapter for Claude Code CLI.
19
+
20
+ This adapter provides integration with the Claude Code command-line interface,
21
+ handling its unique prompt formats, error messages, and interactive behaviors.
22
+
23
+ Example:
24
+ >>> adapter = ClaudeCodeAdapter()
25
+ >>> cmd = adapter.build_launch_command("/home/user/project")
26
+ >>> print(cmd)
27
+ cd '/home/user/project' && claude --dangerously-skip-permissions
28
+ """
29
+
30
+ # Idle detection patterns (Claude Code prompt indicators)
31
+ IDLE_PATTERNS = [
32
+ r"^>\s*$", # Simple prompt
33
+ r"claude>\s*$", # Named prompt
34
+ r"╭─+╮", # Box drawing (Claude's UI)
35
+ r"What would you like", # Claude asking for input
36
+ r"How can I help", # Alternative greeting
37
+ ]
38
+
39
+ # Error patterns - detect various error conditions
40
+ ERROR_PATTERNS = [
41
+ r"Error:",
42
+ r"Failed:",
43
+ r"Exception:",
44
+ r"Permission denied",
45
+ r"not found",
46
+ r"Traceback \(most recent call last\)",
47
+ r"FATAL:",
48
+ r"✗", # Claude's error indicator
49
+ r"command not found",
50
+ r"cannot access",
51
+ ]
52
+
53
+ # Question patterns - detect when Claude is asking for confirmation
54
+ QUESTION_PATTERNS = [
55
+ r"Which option",
56
+ r"Should I proceed",
57
+ r"Please choose",
58
+ r"\(y/n\)\?",
59
+ r"Are you sure",
60
+ r"Do you want",
61
+ r"\[Y/n\]",
62
+ r"\[yes/no\]",
63
+ r"Select an option",
64
+ r"Choose from",
65
+ ]
66
+
67
+ # ANSI escape code pattern for stripping color/formatting codes
68
+ ANSI_ESCAPE = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
69
+
70
+ @property
71
+ def name(self) -> str:
72
+ """Return the runtime identifier.
73
+
74
+ Returns:
75
+ The string "claude-code"
76
+ """
77
+ return "claude-code"
78
+
79
+ @property
80
+ def capabilities(self) -> Set[Capability]:
81
+ """Return the set of capabilities supported by Claude Code.
82
+
83
+ Claude Code is a full-featured AI coding assistant with comprehensive
84
+ tool use, file operations, git integration, and reasoning capabilities.
85
+
86
+ Returns:
87
+ Set of all supported Capability enums
88
+ """
89
+ return {
90
+ Capability.TOOL_USE,
91
+ Capability.FILE_EDIT,
92
+ Capability.FILE_CREATE,
93
+ Capability.GIT_OPERATIONS,
94
+ Capability.SHELL_COMMANDS,
95
+ Capability.WEB_SEARCH,
96
+ Capability.COMPLEX_REASONING,
97
+ }
98
+
99
+ def build_launch_command(
100
+ self, project_path: str, agent_prompt: Optional[str] = None
101
+ ) -> str:
102
+ """Generate shell command to start Claude Code.
103
+
104
+ Args:
105
+ project_path: Absolute path to the project directory
106
+ agent_prompt: Optional system prompt to configure Claude's behavior
107
+
108
+ Returns:
109
+ Shell command string ready to execute in bash
110
+
111
+ Example:
112
+ >>> adapter = ClaudeCodeAdapter()
113
+ >>> adapter.build_launch_command("/home/user/project")
114
+ "cd '/home/user/project' && claude --dangerously-skip-permissions"
115
+ >>> adapter.build_launch_command("/home/user/project", "You are a Python expert")
116
+ "cd '/home/user/project' && claude --system-prompt 'You are a Python expert' --dangerously-skip-permissions"
117
+
118
+ Note:
119
+ Uses --dangerously-skip-permissions for automated operation.
120
+ This is appropriate for MPM Commander's controlled environment.
121
+ """
122
+ # shlex.quote prevents shell injection
123
+ quoted_path = shlex.quote(project_path) # nosec B604
124
+ cmd = f"cd {quoted_path} && claude"
125
+
126
+ if agent_prompt:
127
+ quoted_prompt = shlex.quote(agent_prompt) # nosec B604
128
+ cmd += f" --system-prompt {quoted_prompt}"
129
+
130
+ # Skip permissions for automated operation
131
+ cmd += " --dangerously-skip-permissions"
132
+
133
+ logger.debug(f"Built launch command: {cmd}")
134
+ return cmd
135
+
136
+ def format_input(self, message: str) -> str:
137
+ """Prepare message for Claude Code's input format.
138
+
139
+ Claude Code accepts plain text input, so this method simply
140
+ strips leading/trailing whitespace.
141
+
142
+ Args:
143
+ message: The user message to send
144
+
145
+ Returns:
146
+ Formatted message (whitespace-trimmed)
147
+
148
+ Example:
149
+ >>> adapter = ClaudeCodeAdapter()
150
+ >>> adapter.format_input(" Fix the bug in main.py ")
151
+ 'Fix the bug in main.py'
152
+ """
153
+ formatted = message.strip()
154
+ logger.debug(f"Formatted input: {formatted[:100]}...")
155
+ return formatted
156
+
157
+ def strip_ansi(self, text: str) -> str:
158
+ """Remove ANSI escape codes from text.
159
+
160
+ Args:
161
+ text: Text potentially containing ANSI escape sequences
162
+
163
+ Returns:
164
+ Clean text with ANSI codes removed
165
+
166
+ Example:
167
+ >>> adapter = ClaudeCodeAdapter()
168
+ >>> adapter.strip_ansi("\\x1b[32mSuccess\\x1b[0m")
169
+ 'Success'
170
+ """
171
+ return self.ANSI_ESCAPE.sub("", text)
172
+
173
+ def detect_idle(self, output: str) -> bool:
174
+ """Recognize when Claude Code is waiting for input.
175
+
176
+ Checks the last line of output against known idle patterns
177
+ such as the prompt indicator or greeting messages.
178
+
179
+ Args:
180
+ output: Raw output from Claude Code (may contain ANSI codes)
181
+
182
+ Returns:
183
+ True if Claude is in an idle state awaiting input
184
+
185
+ Example:
186
+ >>> adapter = ClaudeCodeAdapter()
187
+ >>> adapter.detect_idle("Done editing file.\\n> ")
188
+ True
189
+ >>> adapter.detect_idle("Processing request...")
190
+ False
191
+ >>> adapter.detect_idle("╭─────────────────╮\\nWhat would you like me to help with?")
192
+ True
193
+ """
194
+ if not output:
195
+ return False
196
+
197
+ clean = self.strip_ansi(output)
198
+ lines = clean.strip().split("\n")
199
+
200
+ if not lines:
201
+ return False
202
+
203
+ last_line = lines[-1].strip()
204
+
205
+ # Check against all idle patterns
206
+ for pattern in self.IDLE_PATTERNS:
207
+ if re.search(pattern, last_line):
208
+ logger.debug(f"Detected idle state with pattern: {pattern}")
209
+ return True
210
+
211
+ return False
212
+
213
+ def detect_error(self, output: str) -> Optional[str]:
214
+ """Recognize error states and extract error message.
215
+
216
+ Searches the output for known error patterns and returns
217
+ the line containing the error for context.
218
+
219
+ Args:
220
+ output: Raw output from Claude Code
221
+
222
+ Returns:
223
+ Error message string if error detected, None otherwise
224
+
225
+ Example:
226
+ >>> adapter = ClaudeCodeAdapter()
227
+ >>> adapter.detect_error("Error: File not found: config.py")
228
+ 'Error: File not found: config.py'
229
+ >>> adapter.detect_error("File edited successfully")
230
+ None
231
+ >>> adapter.detect_error("Traceback (most recent call last):\\n File...")
232
+ 'Traceback (most recent call last):'
233
+ """
234
+ clean = self.strip_ansi(output)
235
+
236
+ for pattern in self.ERROR_PATTERNS:
237
+ match = re.search(pattern, clean, re.IGNORECASE)
238
+ if match:
239
+ # Extract the line containing the error for context
240
+ for line in clean.split("\n"):
241
+ if re.search(pattern, line, re.IGNORECASE):
242
+ error_msg = line.strip()
243
+ logger.warning(f"Detected error: {error_msg}")
244
+ return error_msg
245
+
246
+ return None
247
+
248
+ def detect_question(
249
+ self, output: str
250
+ ) -> tuple[bool, Optional[str], Optional[List[str]]]:
251
+ """Detect if Claude is asking a question and extract options.
252
+
253
+ Searches for question patterns and attempts to extract the question
254
+ text along with any numbered options presented.
255
+
256
+ Args:
257
+ output: Raw output from Claude Code
258
+
259
+ Returns:
260
+ Tuple of (is_question, question_text, options)
261
+ - is_question: True if a question was detected
262
+ - question_text: The question text if found
263
+ - options: List of option strings if numbered options found
264
+
265
+ Example:
266
+ >>> adapter = ClaudeCodeAdapter()
267
+ >>> is_q, text, opts = adapter.detect_question("Should I proceed? (y/n)?")
268
+ >>> is_q
269
+ True
270
+ >>> text
271
+ 'Should I proceed? (y/n)?'
272
+ >>> is_q, text, opts = adapter.detect_question(
273
+ ... "Which option:\\n1. Create new file\\n2. Edit existing"
274
+ ... )
275
+ >>> opts
276
+ ['Create new file', 'Edit existing']
277
+ """
278
+ clean = self.strip_ansi(output)
279
+
280
+ for pattern in self.QUESTION_PATTERNS:
281
+ if re.search(pattern, clean, re.IGNORECASE):
282
+ # Try to extract question and options
283
+ lines = clean.strip().split("\n")
284
+ question = None
285
+ options = []
286
+
287
+ for line in lines:
288
+ if re.search(pattern, line, re.IGNORECASE):
289
+ question = line.strip()
290
+
291
+ # Look for numbered options (1., 2., 1), 2), etc.)
292
+ opt_match = re.match(r"^\s*(\d+)[.):]\s*(.+)$", line)
293
+ if opt_match:
294
+ options.append(opt_match.group(2).strip())
295
+
296
+ logger.debug(
297
+ f"Detected question: {question}, options: {options if options else 'none'}"
298
+ )
299
+ return True, question, options if options else None
300
+
301
+ return False, None, None
302
+
303
+ def parse_response(self, output: str) -> ParsedResponse:
304
+ """Extract meaningful content from Claude Code output.
305
+
306
+ Combines all detection logic (idle, error, questions) into a
307
+ single structured response object.
308
+
309
+ Args:
310
+ output: Raw output from Claude Code
311
+
312
+ Returns:
313
+ ParsedResponse with all detected states and content
314
+
315
+ Example:
316
+ >>> adapter = ClaudeCodeAdapter()
317
+ >>> response = adapter.parse_response("Error: Invalid input\\n> ")
318
+ >>> response.is_error
319
+ True
320
+ >>> response.is_complete
321
+ True
322
+ >>> response.error_message
323
+ 'Error: Invalid input'
324
+
325
+ >>> response = adapter.parse_response("File created: test.py\\n> ")
326
+ >>> response.content
327
+ 'File created: test.py\\n> '
328
+ >>> response.is_complete
329
+ True
330
+ >>> response.is_error
331
+ False
332
+ """
333
+ if not output:
334
+ return ParsedResponse(
335
+ content="",
336
+ is_complete=False,
337
+ is_error=False,
338
+ is_question=False,
339
+ )
340
+
341
+ clean = self.strip_ansi(output)
342
+ error_msg = self.detect_error(output)
343
+ is_question, question_text, options = self.detect_question(output)
344
+ is_complete = self.detect_idle(output)
345
+
346
+ response = ParsedResponse(
347
+ content=clean,
348
+ is_complete=is_complete,
349
+ is_error=error_msg is not None,
350
+ error_message=error_msg,
351
+ is_question=is_question,
352
+ question_text=question_text,
353
+ options=options,
354
+ )
355
+
356
+ logger.debug(
357
+ f"Parsed response: complete={is_complete}, error={error_msg is not None}, "
358
+ f"question={is_question}"
359
+ )
360
+
361
+ return response