claude-mpm 5.6.1__py3-none-any.whl → 5.6.76__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 (131) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/PM_INSTRUCTIONS.md +8 -3
  3. claude_mpm/auth/__init__.py +35 -0
  4. claude_mpm/auth/callback_server.py +328 -0
  5. claude_mpm/auth/models.py +104 -0
  6. claude_mpm/auth/oauth_manager.py +266 -0
  7. claude_mpm/auth/providers/__init__.py +12 -0
  8. claude_mpm/auth/providers/base.py +165 -0
  9. claude_mpm/auth/providers/google.py +261 -0
  10. claude_mpm/auth/token_storage.py +252 -0
  11. claude_mpm/cli/commands/commander.py +174 -4
  12. claude_mpm/cli/commands/mcp.py +29 -17
  13. claude_mpm/cli/commands/mcp_command_router.py +39 -0
  14. claude_mpm/cli/commands/mcp_service_commands.py +304 -0
  15. claude_mpm/cli/commands/oauth.py +481 -0
  16. claude_mpm/cli/commands/skill_source.py +51 -2
  17. claude_mpm/cli/commands/skills.py +5 -3
  18. claude_mpm/cli/executor.py +9 -0
  19. claude_mpm/cli/helpers.py +1 -1
  20. claude_mpm/cli/parsers/base_parser.py +13 -0
  21. claude_mpm/cli/parsers/commander_parser.py +43 -10
  22. claude_mpm/cli/parsers/mcp_parser.py +79 -0
  23. claude_mpm/cli/parsers/oauth_parser.py +165 -0
  24. claude_mpm/cli/parsers/skill_source_parser.py +4 -0
  25. claude_mpm/cli/parsers/skills_parser.py +5 -0
  26. claude_mpm/cli/startup.py +300 -33
  27. claude_mpm/cli/startup_display.py +4 -2
  28. claude_mpm/cli/startup_migrations.py +236 -0
  29. claude_mpm/commander/__init__.py +6 -0
  30. claude_mpm/commander/adapters/__init__.py +32 -3
  31. claude_mpm/commander/adapters/auggie.py +260 -0
  32. claude_mpm/commander/adapters/base.py +98 -1
  33. claude_mpm/commander/adapters/claude_code.py +32 -1
  34. claude_mpm/commander/adapters/codex.py +237 -0
  35. claude_mpm/commander/adapters/example_usage.py +310 -0
  36. claude_mpm/commander/adapters/mpm.py +389 -0
  37. claude_mpm/commander/adapters/registry.py +204 -0
  38. claude_mpm/commander/api/app.py +32 -16
  39. claude_mpm/commander/api/errors.py +21 -0
  40. claude_mpm/commander/api/routes/messages.py +11 -11
  41. claude_mpm/commander/api/routes/projects.py +20 -20
  42. claude_mpm/commander/api/routes/sessions.py +37 -26
  43. claude_mpm/commander/api/routes/work.py +86 -50
  44. claude_mpm/commander/api/schemas.py +4 -0
  45. claude_mpm/commander/chat/cli.py +47 -5
  46. claude_mpm/commander/chat/commands.py +44 -16
  47. claude_mpm/commander/chat/repl.py +1729 -82
  48. claude_mpm/commander/config.py +5 -3
  49. claude_mpm/commander/core/__init__.py +10 -0
  50. claude_mpm/commander/core/block_manager.py +325 -0
  51. claude_mpm/commander/core/response_manager.py +323 -0
  52. claude_mpm/commander/daemon.py +215 -10
  53. claude_mpm/commander/env_loader.py +59 -0
  54. claude_mpm/commander/events/manager.py +61 -1
  55. claude_mpm/commander/frameworks/base.py +91 -1
  56. claude_mpm/commander/frameworks/mpm.py +9 -14
  57. claude_mpm/commander/git/__init__.py +5 -0
  58. claude_mpm/commander/git/worktree_manager.py +212 -0
  59. claude_mpm/commander/instance_manager.py +546 -15
  60. claude_mpm/commander/memory/__init__.py +45 -0
  61. claude_mpm/commander/memory/compression.py +347 -0
  62. claude_mpm/commander/memory/embeddings.py +230 -0
  63. claude_mpm/commander/memory/entities.py +310 -0
  64. claude_mpm/commander/memory/example_usage.py +290 -0
  65. claude_mpm/commander/memory/integration.py +325 -0
  66. claude_mpm/commander/memory/search.py +381 -0
  67. claude_mpm/commander/memory/store.py +657 -0
  68. claude_mpm/commander/models/events.py +6 -0
  69. claude_mpm/commander/persistence/state_store.py +95 -1
  70. claude_mpm/commander/registry.py +10 -4
  71. claude_mpm/commander/runtime/monitor.py +32 -2
  72. claude_mpm/commander/tmux_orchestrator.py +3 -2
  73. claude_mpm/commander/work/executor.py +38 -20
  74. claude_mpm/commander/workflow/event_handler.py +25 -3
  75. claude_mpm/config/skill_sources.py +16 -0
  76. claude_mpm/constants.py +5 -0
  77. claude_mpm/core/claude_runner.py +152 -0
  78. claude_mpm/core/config.py +30 -22
  79. claude_mpm/core/config_constants.py +74 -9
  80. claude_mpm/core/constants.py +56 -12
  81. claude_mpm/core/hook_manager.py +2 -1
  82. claude_mpm/core/interactive_session.py +5 -4
  83. claude_mpm/core/logger.py +16 -2
  84. claude_mpm/core/logging_utils.py +40 -16
  85. claude_mpm/core/network_config.py +148 -0
  86. claude_mpm/core/oneshot_session.py +7 -6
  87. claude_mpm/core/output_style_manager.py +37 -7
  88. claude_mpm/core/socketio_pool.py +47 -15
  89. claude_mpm/core/unified_paths.py +68 -80
  90. claude_mpm/hooks/claude_hooks/auto_pause_handler.py +30 -31
  91. claude_mpm/hooks/claude_hooks/event_handlers.py +285 -194
  92. claude_mpm/hooks/claude_hooks/hook_handler.py +115 -32
  93. claude_mpm/hooks/claude_hooks/installer.py +222 -54
  94. claude_mpm/hooks/claude_hooks/memory_integration.py +52 -32
  95. claude_mpm/hooks/claude_hooks/response_tracking.py +40 -59
  96. claude_mpm/hooks/claude_hooks/services/__init__.py +21 -0
  97. claude_mpm/hooks/claude_hooks/services/connection_manager.py +25 -30
  98. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +24 -28
  99. claude_mpm/hooks/claude_hooks/services/container.py +326 -0
  100. claude_mpm/hooks/claude_hooks/services/protocols.py +328 -0
  101. claude_mpm/hooks/claude_hooks/services/state_manager.py +25 -38
  102. claude_mpm/hooks/claude_hooks/services/subagent_processor.py +49 -75
  103. claude_mpm/hooks/session_resume_hook.py +22 -18
  104. claude_mpm/hooks/templates/pre_tool_use_simple.py +6 -6
  105. claude_mpm/hooks/templates/pre_tool_use_template.py +16 -8
  106. claude_mpm/init.py +21 -14
  107. claude_mpm/mcp/__init__.py +9 -0
  108. claude_mpm/mcp/google_workspace_server.py +610 -0
  109. claude_mpm/scripts/claude-hook-handler.sh +10 -9
  110. claude_mpm/services/agents/agent_selection_service.py +2 -2
  111. claude_mpm/services/agents/single_tier_deployment_service.py +4 -4
  112. claude_mpm/services/command_deployment_service.py +44 -26
  113. claude_mpm/services/hook_installer_service.py +77 -8
  114. claude_mpm/services/mcp_config_manager.py +99 -19
  115. claude_mpm/services/mcp_service_registry.py +294 -0
  116. claude_mpm/services/monitor/server.py +6 -1
  117. claude_mpm/services/pm_skills_deployer.py +5 -3
  118. claude_mpm/services/skills/git_skill_source_manager.py +79 -8
  119. claude_mpm/services/skills/selective_skill_deployer.py +28 -0
  120. claude_mpm/services/skills/skill_discovery_service.py +17 -1
  121. claude_mpm/services/skills_deployer.py +31 -5
  122. claude_mpm/skills/__init__.py +2 -1
  123. claude_mpm/skills/bundled/pm/mpm-session-pause/SKILL.md +170 -0
  124. claude_mpm/skills/registry.py +295 -90
  125. {claude_mpm-5.6.1.dist-info → claude_mpm-5.6.76.dist-info}/METADATA +28 -3
  126. {claude_mpm-5.6.1.dist-info → claude_mpm-5.6.76.dist-info}/RECORD +131 -93
  127. {claude_mpm-5.6.1.dist-info → claude_mpm-5.6.76.dist-info}/WHEEL +1 -1
  128. {claude_mpm-5.6.1.dist-info → claude_mpm-5.6.76.dist-info}/entry_points.txt +2 -0
  129. {claude_mpm-5.6.1.dist-info → claude_mpm-5.6.76.dist-info}/licenses/LICENSE +0 -0
  130. {claude_mpm-5.6.1.dist-info → claude_mpm-5.6.76.dist-info}/licenses/LICENSE-FAQ.md +0 -0
  131. {claude_mpm-5.6.1.dist-info → claude_mpm-5.6.76.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,236 @@
1
+ """
2
+ Startup migrations for claude-mpm.
3
+
4
+ This module provides a migration registry pattern for automatically fixing
5
+ configuration issues on first startup after an update. Migrations run once
6
+ and are tracked in ~/.claude-mpm/migrations.yaml.
7
+
8
+ Design Principles:
9
+ - Non-blocking: Failures log warnings but don't stop startup
10
+ - Idempotent: Safe to run multiple times (check before migrate)
11
+ - Tracked: Each migration runs only once per installation
12
+ - Early: Runs before agent sync in startup sequence
13
+ """
14
+
15
+ import shutil
16
+ from dataclasses import dataclass
17
+ from datetime import datetime, timezone
18
+ from pathlib import Path
19
+ from typing import Callable
20
+
21
+ import yaml
22
+
23
+ from ..core.logging_utils import get_logger
24
+
25
+ logger = get_logger(__name__)
26
+
27
+
28
+ @dataclass
29
+ class Migration:
30
+ """Definition of a startup migration."""
31
+
32
+ id: str
33
+ description: str
34
+ check: Callable[[], bool] # Returns True if migration is needed
35
+ migrate: Callable[[], bool] # Returns True if migration succeeded
36
+
37
+
38
+ def _get_migrations_file() -> Path:
39
+ """Get path to migrations tracking file."""
40
+ return Path.home() / ".claude-mpm" / "migrations.yaml"
41
+
42
+
43
+ def _load_completed_migrations() -> dict:
44
+ """Load completed migrations from tracking file.
45
+
46
+ Returns:
47
+ Dictionary with completed migrations data.
48
+ """
49
+ migrations_file = _get_migrations_file()
50
+ if not migrations_file.exists():
51
+ return {"migrations": []}
52
+
53
+ try:
54
+ with open(migrations_file) as f:
55
+ data = yaml.safe_load(f) or {}
56
+ return data if "migrations" in data else {"migrations": []}
57
+ except Exception as e:
58
+ logger.debug(f"Failed to load migrations file: {e}")
59
+ return {"migrations": []}
60
+
61
+
62
+ def _save_completed_migration(migration_id: str) -> None:
63
+ """Save a completed migration to tracking file.
64
+
65
+ Args:
66
+ migration_id: The ID of the completed migration.
67
+ """
68
+ migrations_file = _get_migrations_file()
69
+ migrations_file.parent.mkdir(parents=True, exist_ok=True)
70
+
71
+ data = _load_completed_migrations()
72
+
73
+ # Add new migration entry
74
+ data["migrations"].append(
75
+ {"id": migration_id, "completed_at": datetime.now(timezone.utc).isoformat()}
76
+ )
77
+
78
+ try:
79
+ with open(migrations_file, "w") as f:
80
+ yaml.safe_dump(data, f, default_flow_style=False)
81
+ except Exception as e:
82
+ logger.warning(f"Failed to save migration tracking: {e}")
83
+
84
+
85
+ def _is_migration_completed(migration_id: str) -> bool:
86
+ """Check if a migration has already been completed.
87
+
88
+ Args:
89
+ migration_id: The ID of the migration to check.
90
+
91
+ Returns:
92
+ True if the migration has been completed.
93
+ """
94
+ data = _load_completed_migrations()
95
+ completed_ids = [m.get("id") for m in data.get("migrations", [])]
96
+ return migration_id in completed_ids
97
+
98
+
99
+ # =============================================================================
100
+ # Migration: v5.6.76-cache-dir-rename
101
+ # =============================================================================
102
+
103
+
104
+ def _check_cache_dir_rename_needed() -> bool:
105
+ """Check if cache directory rename is needed.
106
+
107
+ Returns:
108
+ True if ~/.claude-mpm/cache/remote-agents/ exists.
109
+ """
110
+ old_cache_dir = Path.home() / ".claude-mpm" / "cache" / "remote-agents"
111
+ return old_cache_dir.exists()
112
+
113
+
114
+ def _migrate_cache_dir_rename() -> bool:
115
+ """Rename remote-agents cache directory to agents.
116
+
117
+ This migration:
118
+ 1. Moves ~/.claude-mpm/cache/remote-agents/ contents to ~/.claude-mpm/cache/agents/
119
+ 2. Removes the old remote-agents directory
120
+ 3. Updates configuration.yaml if it references the old path
121
+
122
+ Returns:
123
+ True if migration succeeded.
124
+ """
125
+ old_cache_dir = Path.home() / ".claude-mpm" / "cache" / "remote-agents"
126
+ new_cache_dir = Path.home() / ".claude-mpm" / "cache" / "agents"
127
+
128
+ try:
129
+ # Step 1: Move directory contents
130
+ if old_cache_dir.exists():
131
+ # Ensure parent directory exists
132
+ new_cache_dir.parent.mkdir(parents=True, exist_ok=True)
133
+
134
+ if new_cache_dir.exists():
135
+ # Merge: move contents from old to new
136
+ for item in old_cache_dir.iterdir():
137
+ dest = new_cache_dir / item.name
138
+ if not dest.exists():
139
+ shutil.move(str(item), str(dest))
140
+ # Remove old directory after moving contents
141
+ shutil.rmtree(old_cache_dir, ignore_errors=True)
142
+ else:
143
+ # Simple rename if new dir doesn't exist
144
+ shutil.move(str(old_cache_dir), str(new_cache_dir))
145
+
146
+ logger.debug(
147
+ f"Moved cache directory from {old_cache_dir} to {new_cache_dir}"
148
+ )
149
+
150
+ # Step 2: Update configuration.yaml if needed
151
+ _update_configuration_cache_path()
152
+
153
+ return True
154
+
155
+ except Exception as e:
156
+ logger.warning(f"Cache directory migration failed: {e}")
157
+ return False
158
+
159
+
160
+ def _update_configuration_cache_path() -> None:
161
+ """Update configuration.yaml to use new cache path if it references old path."""
162
+ config_file = Path.home() / ".claude-mpm" / "configuration.yaml"
163
+ if not config_file.exists():
164
+ return
165
+
166
+ try:
167
+ with open(config_file) as f:
168
+ content = f.read()
169
+
170
+ old_path_pattern = "/.claude-mpm/cache/remote-agents"
171
+ new_path_pattern = "/.claude-mpm/cache/agents"
172
+
173
+ if old_path_pattern in content:
174
+ updated_content = content.replace(old_path_pattern, new_path_pattern)
175
+ with open(config_file, "w") as f:
176
+ f.write(updated_content)
177
+ logger.debug("Updated configuration.yaml cache_dir path")
178
+
179
+ except Exception as e:
180
+ logger.debug(f"Failed to update configuration.yaml: {e}")
181
+
182
+
183
+ # =============================================================================
184
+ # Migration Registry
185
+ # =============================================================================
186
+
187
+ MIGRATIONS: list[Migration] = [
188
+ Migration(
189
+ id="v5.6.76-cache-dir-rename",
190
+ description="Rename remote-agents cache dir to agents",
191
+ check=_check_cache_dir_rename_needed,
192
+ migrate=_migrate_cache_dir_rename,
193
+ ),
194
+ ]
195
+
196
+
197
+ def run_migrations() -> None:
198
+ """Run all pending startup migrations.
199
+
200
+ This function:
201
+ 1. Iterates through the migration registry
202
+ 2. Skips already-completed migrations
203
+ 3. Checks if each migration is needed
204
+ 4. Runs the migration if needed
205
+ 5. Tracks completed migrations
206
+
207
+ Errors are logged but do not stop startup.
208
+ """
209
+ for migration in MIGRATIONS:
210
+ try:
211
+ # Skip if already completed
212
+ if _is_migration_completed(migration.id):
213
+ continue
214
+
215
+ # Check if migration is needed
216
+ if not migration.check():
217
+ # Mark as completed even if not needed (condition doesn't apply)
218
+ _save_completed_migration(migration.id)
219
+ continue
220
+
221
+ # Run the migration
222
+ print(f"⚙️ Running startup migration: {migration.description}")
223
+ logger.info(f"Running startup migration: {migration.id}")
224
+
225
+ success = migration.migrate()
226
+
227
+ if success:
228
+ _save_completed_migration(migration.id)
229
+ logger.info(f"Migration {migration.id} completed successfully")
230
+ else:
231
+ logger.warning(f"Migration {migration.id} failed")
232
+
233
+ except Exception as e:
234
+ # Non-blocking: log and continue
235
+ logger.warning(f"Migration {migration.id} error: {e}")
236
+ continue
@@ -12,12 +12,18 @@ Key Components:
12
12
  - ProjectSession: Per-project lifecycle management
13
13
  - InstanceManager: Framework selection and instance lifecycle
14
14
  - Frameworks: Claude Code, MPM framework abstractions
15
+ - Memory: Conversation storage, semantic search, context compression
15
16
 
16
17
  Example:
17
18
  >>> from claude_mpm.commander import ProjectRegistry
18
19
  >>> registry = ProjectRegistry()
19
20
  >>> project = registry.register("/path/to/project")
20
21
  >>> registry.update_state(project.id, ProjectState.WORKING)
22
+
23
+ >>> # Memory integration
24
+ >>> from claude_mpm.commander.memory import MemoryIntegration
25
+ >>> memory = MemoryIntegration.create()
26
+ >>> await memory.capture_project_conversation(project)
21
27
  """
22
28
 
23
29
  from claude_mpm.commander.config import DaemonConfig
@@ -6,26 +6,55 @@ the TmuxOrchestrator to interface with various runtimes in a uniform way.
6
6
  Two types of adapters:
7
7
  - RuntimeAdapter: Synchronous parsing and state detection
8
8
  - CommunicationAdapter: Async I/O and state management
9
+
10
+ Available Runtime Adapters:
11
+ - ClaudeCodeAdapter: Vanilla Claude Code CLI
12
+ - AuggieAdapter: Auggie with MCP support
13
+ - CodexAdapter: Codex (limited features)
14
+ - MPMAdapter: Full MPM with agents, hooks, skills, monitoring
15
+
16
+ Registry:
17
+ - AdapterRegistry: Centralized adapter management with auto-detection
9
18
  """
10
19
 
11
- from .base import Capability, ParsedResponse, RuntimeAdapter
20
+ from .auggie import AuggieAdapter
21
+ from .base import (
22
+ Capability,
23
+ ParsedResponse,
24
+ RuntimeAdapter,
25
+ RuntimeCapability,
26
+ RuntimeInfo,
27
+ )
12
28
  from .claude_code import ClaudeCodeAdapter
29
+ from .codex import CodexAdapter
13
30
  from .communication import (
14
31
  AdapterResponse,
15
32
  AdapterState,
16
33
  BaseCommunicationAdapter,
17
34
  ClaudeCodeCommunicationAdapter,
18
35
  )
36
+ from .mpm import MPMAdapter
37
+ from .registry import AdapterRegistry
38
+
39
+ # Auto-register all adapters
40
+ AdapterRegistry.register("claude-code", ClaudeCodeAdapter)
41
+ AdapterRegistry.register("auggie", AuggieAdapter)
42
+ AdapterRegistry.register("codex", CodexAdapter)
43
+ AdapterRegistry.register("mpm", MPMAdapter)
19
44
 
20
45
  __all__ = [
21
- # Communication adapters (async I/O)
46
+ "AdapterRegistry",
22
47
  "AdapterResponse",
23
48
  "AdapterState",
49
+ "AuggieAdapter",
24
50
  "BaseCommunicationAdapter",
25
- # Runtime adapters (parsing)
26
51
  "Capability",
27
52
  "ClaudeCodeAdapter",
28
53
  "ClaudeCodeCommunicationAdapter",
54
+ "CodexAdapter",
55
+ "MPMAdapter",
29
56
  "ParsedResponse",
30
57
  "RuntimeAdapter",
58
+ "RuntimeCapability",
59
+ "RuntimeInfo",
31
60
  ]
@@ -0,0 +1,260 @@
1
+ """Auggie CLI runtime adapter.
2
+
3
+ This module implements the RuntimeAdapter interface for Auggie,
4
+ an AI coding assistant with MCP (Model Context Protocol) support.
5
+ """
6
+
7
+ import logging
8
+ import re
9
+ import shlex
10
+ from typing import List, Optional, Set
11
+
12
+ from .base import (
13
+ Capability,
14
+ ParsedResponse,
15
+ RuntimeAdapter,
16
+ RuntimeCapability,
17
+ RuntimeInfo,
18
+ )
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ class AuggieAdapter(RuntimeAdapter):
24
+ """Adapter for Auggie CLI.
25
+
26
+ Auggie is an AI coding assistant with support for MCP servers,
27
+ custom instructions, and various tool capabilities.
28
+
29
+ Example:
30
+ >>> adapter = AuggieAdapter()
31
+ >>> cmd = adapter.build_launch_command("/home/user/project")
32
+ >>> print(cmd)
33
+ cd '/home/user/project' && auggie
34
+ """
35
+
36
+ # Idle detection patterns
37
+ IDLE_PATTERNS = [
38
+ r"^>\s*$", # Simple prompt
39
+ r"auggie>\s*$", # Named prompt
40
+ r"Ready for input",
41
+ r"What would you like",
42
+ r"How can I assist",
43
+ ]
44
+
45
+ # Error patterns
46
+ ERROR_PATTERNS = [
47
+ r"Error:",
48
+ r"Failed:",
49
+ r"Exception:",
50
+ r"Permission denied",
51
+ r"not found",
52
+ r"Traceback \(most recent call last\)",
53
+ r"FATAL:",
54
+ r"command not found",
55
+ r"cannot access",
56
+ ]
57
+
58
+ # Question patterns
59
+ QUESTION_PATTERNS = [
60
+ r"Which option",
61
+ r"Should I proceed",
62
+ r"Please choose",
63
+ r"\(y/n\)\?",
64
+ r"Are you sure",
65
+ r"Do you want",
66
+ r"\[Y/n\]",
67
+ r"\[yes/no\]",
68
+ ]
69
+
70
+ # ANSI escape code pattern
71
+ ANSI_ESCAPE = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
72
+
73
+ @property
74
+ def name(self) -> str:
75
+ """Return the runtime identifier."""
76
+ return "auggie"
77
+
78
+ @property
79
+ def capabilities(self) -> Set[Capability]:
80
+ """Return the set of capabilities supported by Auggie."""
81
+ return {
82
+ Capability.TOOL_USE,
83
+ Capability.FILE_EDIT,
84
+ Capability.FILE_CREATE,
85
+ Capability.GIT_OPERATIONS,
86
+ Capability.SHELL_COMMANDS,
87
+ Capability.COMPLEX_REASONING,
88
+ }
89
+
90
+ @property
91
+ def runtime_info(self) -> RuntimeInfo:
92
+ """Return detailed runtime information."""
93
+ return RuntimeInfo(
94
+ name="auggie",
95
+ version=None, # Version detection could be added
96
+ capabilities={
97
+ RuntimeCapability.FILE_READ,
98
+ RuntimeCapability.FILE_EDIT,
99
+ RuntimeCapability.FILE_CREATE,
100
+ RuntimeCapability.BASH_EXECUTION,
101
+ RuntimeCapability.GIT_OPERATIONS,
102
+ RuntimeCapability.TOOL_USE,
103
+ RuntimeCapability.MCP_TOOLS, # Auggie supports MCP
104
+ RuntimeCapability.INSTRUCTIONS,
105
+ RuntimeCapability.COMPLEX_REASONING,
106
+ RuntimeCapability.AGENT_DELEGATION, # Auggie now supports agents
107
+ },
108
+ command="auggie",
109
+ supports_agents=True, # Auggie now supports agent delegation
110
+ instruction_file=".augment/instructions.md",
111
+ )
112
+
113
+ def build_launch_command(
114
+ self, project_path: str, agent_prompt: Optional[str] = None
115
+ ) -> str:
116
+ """Generate shell command to start Auggie.
117
+
118
+ Args:
119
+ project_path: Absolute path to the project directory
120
+ agent_prompt: Optional system prompt to configure Auggie
121
+
122
+ Returns:
123
+ Shell command string ready to execute
124
+
125
+ Example:
126
+ >>> adapter = AuggieAdapter()
127
+ >>> adapter.build_launch_command("/home/user/project")
128
+ "cd '/home/user/project' && auggie"
129
+ """
130
+ quoted_path = shlex.quote(project_path)
131
+ cmd = f"cd {quoted_path} && auggie"
132
+
133
+ if agent_prompt:
134
+ # Auggie may support --prompt or similar flag
135
+ # Adjust based on actual Auggie CLI options
136
+ quoted_prompt = shlex.quote(agent_prompt)
137
+ cmd += f" --prompt {quoted_prompt}"
138
+
139
+ logger.debug(f"Built Auggie launch command: {cmd}")
140
+ return cmd
141
+
142
+ def format_input(self, message: str) -> str:
143
+ """Prepare message for Auggie's input format."""
144
+ formatted = message.strip()
145
+ logger.debug(f"Formatted input: {formatted[:100]}...")
146
+ return formatted
147
+
148
+ def strip_ansi(self, text: str) -> str:
149
+ """Remove ANSI escape codes from text."""
150
+ return self.ANSI_ESCAPE.sub("", text)
151
+
152
+ def detect_idle(self, output: str) -> bool:
153
+ """Recognize when Auggie is waiting for input."""
154
+ if not output:
155
+ return False
156
+
157
+ clean = self.strip_ansi(output)
158
+ lines = clean.strip().split("\n")
159
+
160
+ if not lines:
161
+ return False
162
+
163
+ last_line = lines[-1].strip()
164
+
165
+ for pattern in self.IDLE_PATTERNS:
166
+ if re.search(pattern, last_line):
167
+ logger.debug(f"Detected idle state with pattern: {pattern}")
168
+ return True
169
+
170
+ return False
171
+
172
+ def detect_error(self, output: str) -> Optional[str]:
173
+ """Recognize error states and extract error message."""
174
+ clean = self.strip_ansi(output)
175
+
176
+ for pattern in self.ERROR_PATTERNS:
177
+ match = re.search(pattern, clean, re.IGNORECASE)
178
+ if match:
179
+ for line in clean.split("\n"):
180
+ if re.search(pattern, line, re.IGNORECASE):
181
+ error_msg = line.strip()
182
+ logger.warning(f"Detected error: {error_msg}")
183
+ return error_msg
184
+
185
+ return None
186
+
187
+ def detect_question(
188
+ self, output: str
189
+ ) -> tuple[bool, Optional[str], Optional[List[str]]]:
190
+ """Detect if Auggie is asking a question."""
191
+ clean = self.strip_ansi(output)
192
+
193
+ for pattern in self.QUESTION_PATTERNS:
194
+ if re.search(pattern, clean, re.IGNORECASE):
195
+ lines = clean.strip().split("\n")
196
+ question = None
197
+ options = []
198
+
199
+ for line in lines:
200
+ if re.search(pattern, line, re.IGNORECASE):
201
+ question = line.strip()
202
+
203
+ # Look for numbered options
204
+ opt_match = re.match(r"^\s*(\d+)[.):]\s*(.+)$", line)
205
+ if opt_match:
206
+ options.append(opt_match.group(2).strip())
207
+
208
+ logger.debug(
209
+ f"Detected question: {question}, options: {options if options else 'none'}"
210
+ )
211
+ return True, question, options if options else None
212
+
213
+ return False, None, None
214
+
215
+ def parse_response(self, output: str) -> ParsedResponse:
216
+ """Extract meaningful content from Auggie output."""
217
+ if not output:
218
+ return ParsedResponse(
219
+ content="",
220
+ is_complete=False,
221
+ is_error=False,
222
+ is_question=False,
223
+ )
224
+
225
+ clean = self.strip_ansi(output)
226
+ error_msg = self.detect_error(output)
227
+ is_question, question_text, options = self.detect_question(output)
228
+ is_complete = self.detect_idle(output)
229
+
230
+ response = ParsedResponse(
231
+ content=clean,
232
+ is_complete=is_complete,
233
+ is_error=error_msg is not None,
234
+ error_message=error_msg,
235
+ is_question=is_question,
236
+ question_text=question_text,
237
+ options=options,
238
+ )
239
+
240
+ logger.debug(
241
+ f"Parsed response: complete={is_complete}, error={error_msg is not None}, "
242
+ f"question={is_question}"
243
+ )
244
+
245
+ return response
246
+
247
+ def inject_instructions(self, instructions: str) -> Optional[str]:
248
+ """Return command to inject custom instructions.
249
+
250
+ Auggie supports .augment/instructions.md file for custom instructions.
251
+
252
+ Args:
253
+ instructions: Instructions text to inject
254
+
255
+ Returns:
256
+ Command to write instructions file
257
+ """
258
+ # Write to .augment/instructions.md
259
+ escaped = instructions.replace("'", "'\\''")
260
+ return f"mkdir -p .augment && echo '{escaped}' > .augment/instructions.md"
@@ -6,7 +6,7 @@ between the TmuxOrchestrator and various AI coding tools (Claude Code, Cursor, e
6
6
 
7
7
  from abc import ABC, abstractmethod
8
8
  from dataclasses import dataclass
9
- from enum import Enum
9
+ from enum import Enum, auto
10
10
  from typing import List, Optional, Set
11
11
 
12
12
 
@@ -22,6 +22,56 @@ class Capability(Enum):
22
22
  COMPLEX_REASONING = "complex_reasoning"
23
23
 
24
24
 
25
+ class RuntimeCapability(Enum):
26
+ """Extended capabilities a runtime may support (MPM-specific features)."""
27
+
28
+ FILE_READ = auto()
29
+ FILE_EDIT = auto()
30
+ FILE_CREATE = auto()
31
+ BASH_EXECUTION = auto()
32
+ GIT_OPERATIONS = auto()
33
+ TOOL_USE = auto()
34
+ AGENT_DELEGATION = auto() # Sub-agent spawning
35
+ HOOKS = auto() # Lifecycle hooks
36
+ INSTRUCTIONS = auto() # Custom instructions file
37
+ MCP_TOOLS = auto() # MCP server integration
38
+ SKILLS = auto() # Loadable skills
39
+ MONITOR = auto() # Real-time monitoring
40
+ WEB_SEARCH = auto()
41
+ COMPLEX_REASONING = auto()
42
+
43
+
44
+ @dataclass
45
+ class RuntimeInfo:
46
+ """Information about a runtime environment.
47
+
48
+ Attributes:
49
+ name: Runtime identifier (e.g., "claude-code", "auggie")
50
+ version: Optional version string
51
+ capabilities: Set of RuntimeCapability enums
52
+ command: CLI command to invoke runtime
53
+ supports_agents: Whether runtime supports agent delegation
54
+ instruction_file: Path to instructions file (e.g., "CLAUDE.md")
55
+
56
+ Example:
57
+ >>> info = RuntimeInfo(
58
+ ... name="claude-code",
59
+ ... version="1.0.0",
60
+ ... capabilities={RuntimeCapability.FILE_EDIT, RuntimeCapability.TOOL_USE},
61
+ ... command="claude",
62
+ ... supports_agents=False,
63
+ ... instruction_file="CLAUDE.md"
64
+ ... )
65
+ """
66
+
67
+ name: str
68
+ version: Optional[str]
69
+ capabilities: Set[RuntimeCapability]
70
+ command: str
71
+ supports_agents: bool = False
72
+ instruction_file: Optional[str] = None
73
+
74
+
25
75
  @dataclass
26
76
  class ParsedResponse:
27
77
  """Parsed output from a runtime tool.
@@ -189,3 +239,50 @@ class RuntimeAdapter(ABC):
189
239
  >>> adapter.name
190
240
  'claude-code'
191
241
  """
242
+
243
+ @property
244
+ def runtime_info(self) -> Optional[RuntimeInfo]:
245
+ """Return detailed runtime information.
246
+
247
+ Returns:
248
+ RuntimeInfo with capabilities and configuration, or None if not implemented
249
+
250
+ Example:
251
+ >>> info = adapter.runtime_info
252
+ >>> if info and RuntimeCapability.AGENT_DELEGATION in info.capabilities:
253
+ ... print("Supports agent delegation")
254
+ """
255
+ return None
256
+
257
+ def inject_instructions(self, instructions: str) -> Optional[str]:
258
+ """Return command/method to inject custom instructions.
259
+
260
+ Args:
261
+ instructions: Instructions text to inject
262
+
263
+ Returns:
264
+ Command string to inject instructions, or None if not supported
265
+
266
+ Example:
267
+ >>> cmd = adapter.inject_instructions("You are a Python expert")
268
+ >>> if cmd:
269
+ ... # Execute command to inject instructions
270
+ """
271
+ return None
272
+
273
+ def inject_agent_context(self, agent_id: str, context: dict) -> Optional[str]:
274
+ """Return command to inject agent context.
275
+
276
+ Args:
277
+ agent_id: Unique identifier for agent
278
+ context: Context dictionary with agent metadata
279
+
280
+ Returns:
281
+ Command string to inject context, or None if not supported
282
+
283
+ Example:
284
+ >>> cmd = adapter.inject_agent_context("eng-001", {"role": "Engineer"})
285
+ >>> if cmd:
286
+ ... # Execute command to inject context
287
+ """
288
+ return None