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.
- claude_mpm/VERSION +1 -1
- claude_mpm/agents/PM_INSTRUCTIONS.md +8 -3
- claude_mpm/auth/__init__.py +35 -0
- claude_mpm/auth/callback_server.py +328 -0
- claude_mpm/auth/models.py +104 -0
- claude_mpm/auth/oauth_manager.py +266 -0
- claude_mpm/auth/providers/__init__.py +12 -0
- claude_mpm/auth/providers/base.py +165 -0
- claude_mpm/auth/providers/google.py +261 -0
- claude_mpm/auth/token_storage.py +252 -0
- claude_mpm/cli/commands/commander.py +174 -4
- claude_mpm/cli/commands/mcp.py +29 -17
- claude_mpm/cli/commands/mcp_command_router.py +39 -0
- claude_mpm/cli/commands/mcp_service_commands.py +304 -0
- claude_mpm/cli/commands/oauth.py +481 -0
- claude_mpm/cli/commands/skill_source.py +51 -2
- claude_mpm/cli/commands/skills.py +5 -3
- claude_mpm/cli/executor.py +9 -0
- claude_mpm/cli/helpers.py +1 -1
- claude_mpm/cli/parsers/base_parser.py +13 -0
- claude_mpm/cli/parsers/commander_parser.py +43 -10
- claude_mpm/cli/parsers/mcp_parser.py +79 -0
- claude_mpm/cli/parsers/oauth_parser.py +165 -0
- claude_mpm/cli/parsers/skill_source_parser.py +4 -0
- claude_mpm/cli/parsers/skills_parser.py +5 -0
- claude_mpm/cli/startup.py +300 -33
- claude_mpm/cli/startup_display.py +4 -2
- claude_mpm/cli/startup_migrations.py +236 -0
- claude_mpm/commander/__init__.py +6 -0
- claude_mpm/commander/adapters/__init__.py +32 -3
- claude_mpm/commander/adapters/auggie.py +260 -0
- claude_mpm/commander/adapters/base.py +98 -1
- claude_mpm/commander/adapters/claude_code.py +32 -1
- claude_mpm/commander/adapters/codex.py +237 -0
- claude_mpm/commander/adapters/example_usage.py +310 -0
- claude_mpm/commander/adapters/mpm.py +389 -0
- claude_mpm/commander/adapters/registry.py +204 -0
- claude_mpm/commander/api/app.py +32 -16
- claude_mpm/commander/api/errors.py +21 -0
- claude_mpm/commander/api/routes/messages.py +11 -11
- claude_mpm/commander/api/routes/projects.py +20 -20
- claude_mpm/commander/api/routes/sessions.py +37 -26
- claude_mpm/commander/api/routes/work.py +86 -50
- claude_mpm/commander/api/schemas.py +4 -0
- claude_mpm/commander/chat/cli.py +47 -5
- claude_mpm/commander/chat/commands.py +44 -16
- claude_mpm/commander/chat/repl.py +1729 -82
- claude_mpm/commander/config.py +5 -3
- claude_mpm/commander/core/__init__.py +10 -0
- claude_mpm/commander/core/block_manager.py +325 -0
- claude_mpm/commander/core/response_manager.py +323 -0
- claude_mpm/commander/daemon.py +215 -10
- claude_mpm/commander/env_loader.py +59 -0
- claude_mpm/commander/events/manager.py +61 -1
- claude_mpm/commander/frameworks/base.py +91 -1
- claude_mpm/commander/frameworks/mpm.py +9 -14
- claude_mpm/commander/git/__init__.py +5 -0
- claude_mpm/commander/git/worktree_manager.py +212 -0
- claude_mpm/commander/instance_manager.py +546 -15
- claude_mpm/commander/memory/__init__.py +45 -0
- claude_mpm/commander/memory/compression.py +347 -0
- claude_mpm/commander/memory/embeddings.py +230 -0
- claude_mpm/commander/memory/entities.py +310 -0
- claude_mpm/commander/memory/example_usage.py +290 -0
- claude_mpm/commander/memory/integration.py +325 -0
- claude_mpm/commander/memory/search.py +381 -0
- claude_mpm/commander/memory/store.py +657 -0
- claude_mpm/commander/models/events.py +6 -0
- claude_mpm/commander/persistence/state_store.py +95 -1
- claude_mpm/commander/registry.py +10 -4
- claude_mpm/commander/runtime/monitor.py +32 -2
- claude_mpm/commander/tmux_orchestrator.py +3 -2
- claude_mpm/commander/work/executor.py +38 -20
- claude_mpm/commander/workflow/event_handler.py +25 -3
- claude_mpm/config/skill_sources.py +16 -0
- claude_mpm/constants.py +5 -0
- claude_mpm/core/claude_runner.py +152 -0
- claude_mpm/core/config.py +30 -22
- claude_mpm/core/config_constants.py +74 -9
- claude_mpm/core/constants.py +56 -12
- claude_mpm/core/hook_manager.py +2 -1
- claude_mpm/core/interactive_session.py +5 -4
- claude_mpm/core/logger.py +16 -2
- claude_mpm/core/logging_utils.py +40 -16
- claude_mpm/core/network_config.py +148 -0
- claude_mpm/core/oneshot_session.py +7 -6
- claude_mpm/core/output_style_manager.py +37 -7
- claude_mpm/core/socketio_pool.py +47 -15
- claude_mpm/core/unified_paths.py +68 -80
- claude_mpm/hooks/claude_hooks/auto_pause_handler.py +30 -31
- claude_mpm/hooks/claude_hooks/event_handlers.py +285 -194
- claude_mpm/hooks/claude_hooks/hook_handler.py +115 -32
- claude_mpm/hooks/claude_hooks/installer.py +222 -54
- claude_mpm/hooks/claude_hooks/memory_integration.py +52 -32
- claude_mpm/hooks/claude_hooks/response_tracking.py +40 -59
- claude_mpm/hooks/claude_hooks/services/__init__.py +21 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +25 -30
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +24 -28
- claude_mpm/hooks/claude_hooks/services/container.py +326 -0
- claude_mpm/hooks/claude_hooks/services/protocols.py +328 -0
- claude_mpm/hooks/claude_hooks/services/state_manager.py +25 -38
- claude_mpm/hooks/claude_hooks/services/subagent_processor.py +49 -75
- claude_mpm/hooks/session_resume_hook.py +22 -18
- claude_mpm/hooks/templates/pre_tool_use_simple.py +6 -6
- claude_mpm/hooks/templates/pre_tool_use_template.py +16 -8
- claude_mpm/init.py +21 -14
- claude_mpm/mcp/__init__.py +9 -0
- claude_mpm/mcp/google_workspace_server.py +610 -0
- claude_mpm/scripts/claude-hook-handler.sh +10 -9
- claude_mpm/services/agents/agent_selection_service.py +2 -2
- claude_mpm/services/agents/single_tier_deployment_service.py +4 -4
- claude_mpm/services/command_deployment_service.py +44 -26
- claude_mpm/services/hook_installer_service.py +77 -8
- claude_mpm/services/mcp_config_manager.py +99 -19
- claude_mpm/services/mcp_service_registry.py +294 -0
- claude_mpm/services/monitor/server.py +6 -1
- claude_mpm/services/pm_skills_deployer.py +5 -3
- claude_mpm/services/skills/git_skill_source_manager.py +79 -8
- claude_mpm/services/skills/selective_skill_deployer.py +28 -0
- claude_mpm/services/skills/skill_discovery_service.py +17 -1
- claude_mpm/services/skills_deployer.py +31 -5
- claude_mpm/skills/__init__.py +2 -1
- claude_mpm/skills/bundled/pm/mpm-session-pause/SKILL.md +170 -0
- claude_mpm/skills/registry.py +295 -90
- {claude_mpm-5.6.1.dist-info → claude_mpm-5.6.76.dist-info}/METADATA +28 -3
- {claude_mpm-5.6.1.dist-info → claude_mpm-5.6.76.dist-info}/RECORD +131 -93
- {claude_mpm-5.6.1.dist-info → claude_mpm-5.6.76.dist-info}/WHEEL +1 -1
- {claude_mpm-5.6.1.dist-info → claude_mpm-5.6.76.dist-info}/entry_points.txt +2 -0
- {claude_mpm-5.6.1.dist-info → claude_mpm-5.6.76.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.6.1.dist-info → claude_mpm-5.6.76.dist-info}/licenses/LICENSE-FAQ.md +0 -0
- {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
|
claude_mpm/commander/__init__.py
CHANGED
|
@@ -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 .
|
|
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
|
-
|
|
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
|