claude-mpm 5.6.4__py3-none-any.whl → 5.6.30__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 (103) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/PM_INSTRUCTIONS.md +8 -3
  3. claude_mpm/cli/commands/commander.py +174 -4
  4. claude_mpm/cli/commands/skill_source.py +51 -2
  5. claude_mpm/cli/commands/skills.py +5 -3
  6. claude_mpm/cli/parsers/commander_parser.py +43 -10
  7. claude_mpm/cli/parsers/skill_source_parser.py +4 -0
  8. claude_mpm/cli/parsers/skills_parser.py +5 -0
  9. claude_mpm/cli/startup.py +140 -20
  10. claude_mpm/cli/startup_display.py +2 -1
  11. claude_mpm/commander/__init__.py +6 -0
  12. claude_mpm/commander/adapters/__init__.py +32 -3
  13. claude_mpm/commander/adapters/auggie.py +260 -0
  14. claude_mpm/commander/adapters/base.py +98 -1
  15. claude_mpm/commander/adapters/claude_code.py +32 -1
  16. claude_mpm/commander/adapters/codex.py +237 -0
  17. claude_mpm/commander/adapters/example_usage.py +310 -0
  18. claude_mpm/commander/adapters/mpm.py +389 -0
  19. claude_mpm/commander/adapters/registry.py +204 -0
  20. claude_mpm/commander/api/app.py +32 -16
  21. claude_mpm/commander/api/errors.py +21 -0
  22. claude_mpm/commander/api/routes/messages.py +11 -11
  23. claude_mpm/commander/api/routes/projects.py +20 -20
  24. claude_mpm/commander/api/routes/sessions.py +37 -26
  25. claude_mpm/commander/api/routes/work.py +86 -50
  26. claude_mpm/commander/api/schemas.py +4 -0
  27. claude_mpm/commander/chat/cli.py +42 -3
  28. claude_mpm/commander/config.py +5 -3
  29. claude_mpm/commander/core/__init__.py +10 -0
  30. claude_mpm/commander/core/block_manager.py +325 -0
  31. claude_mpm/commander/core/response_manager.py +323 -0
  32. claude_mpm/commander/daemon.py +215 -10
  33. claude_mpm/commander/env_loader.py +59 -0
  34. claude_mpm/commander/frameworks/base.py +4 -1
  35. claude_mpm/commander/instance_manager.py +124 -11
  36. claude_mpm/commander/memory/__init__.py +45 -0
  37. claude_mpm/commander/memory/compression.py +347 -0
  38. claude_mpm/commander/memory/embeddings.py +230 -0
  39. claude_mpm/commander/memory/entities.py +310 -0
  40. claude_mpm/commander/memory/example_usage.py +290 -0
  41. claude_mpm/commander/memory/integration.py +325 -0
  42. claude_mpm/commander/memory/search.py +381 -0
  43. claude_mpm/commander/memory/store.py +657 -0
  44. claude_mpm/commander/registry.py +10 -4
  45. claude_mpm/commander/runtime/monitor.py +32 -2
  46. claude_mpm/commander/work/executor.py +38 -20
  47. claude_mpm/commander/workflow/event_handler.py +25 -3
  48. claude_mpm/config/skill_sources.py +16 -0
  49. claude_mpm/core/claude_runner.py +152 -0
  50. claude_mpm/core/config.py +30 -22
  51. claude_mpm/core/config_constants.py +74 -9
  52. claude_mpm/core/constants.py +56 -12
  53. claude_mpm/core/interactive_session.py +5 -4
  54. claude_mpm/core/logging_utils.py +4 -2
  55. claude_mpm/core/network_config.py +148 -0
  56. claude_mpm/core/oneshot_session.py +7 -6
  57. claude_mpm/core/output_style_manager.py +37 -7
  58. claude_mpm/core/socketio_pool.py +13 -5
  59. claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-311.pyc +0 -0
  60. claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-311.pyc +0 -0
  61. claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-311.pyc +0 -0
  62. claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
  63. claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
  64. claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
  65. claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
  66. claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-311.pyc +0 -0
  67. claude_mpm/hooks/claude_hooks/auto_pause_handler.py +30 -31
  68. claude_mpm/hooks/claude_hooks/event_handlers.py +22 -0
  69. claude_mpm/hooks/claude_hooks/hook_handler.py +6 -6
  70. claude_mpm/hooks/claude_hooks/installer.py +43 -2
  71. claude_mpm/hooks/claude_hooks/memory_integration.py +31 -22
  72. claude_mpm/hooks/claude_hooks/response_tracking.py +40 -59
  73. claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-311.pyc +0 -0
  74. claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
  75. claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-311.pyc +0 -0
  76. claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
  77. claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
  78. claude_mpm/hooks/claude_hooks/services/connection_manager.py +25 -30
  79. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +24 -28
  80. claude_mpm/hooks/claude_hooks/services/state_manager.py +25 -38
  81. claude_mpm/hooks/claude_hooks/services/subagent_processor.py +49 -75
  82. claude_mpm/hooks/session_resume_hook.py +22 -18
  83. claude_mpm/hooks/templates/pre_tool_use_simple.py +6 -6
  84. claude_mpm/hooks/templates/pre_tool_use_template.py +16 -8
  85. claude_mpm/scripts/claude-hook-handler.sh +8 -8
  86. claude_mpm/services/agents/agent_selection_service.py +2 -2
  87. claude_mpm/services/agents/single_tier_deployment_service.py +4 -4
  88. claude_mpm/services/command_deployment_service.py +44 -26
  89. claude_mpm/services/pm_skills_deployer.py +3 -2
  90. claude_mpm/services/skills/git_skill_source_manager.py +79 -8
  91. claude_mpm/services/skills/selective_skill_deployer.py +28 -0
  92. claude_mpm/services/skills/skill_discovery_service.py +17 -1
  93. claude_mpm/services/skills_deployer.py +31 -5
  94. claude_mpm/skills/__init__.py +2 -1
  95. claude_mpm/skills/bundled/pm/mpm-session-pause/SKILL.md +170 -0
  96. claude_mpm/skills/registry.py +295 -90
  97. {claude_mpm-5.6.4.dist-info → claude_mpm-5.6.30.dist-info}/METADATA +5 -3
  98. {claude_mpm-5.6.4.dist-info → claude_mpm-5.6.30.dist-info}/RECORD +103 -71
  99. {claude_mpm-5.6.4.dist-info → claude_mpm-5.6.30.dist-info}/WHEEL +0 -0
  100. {claude_mpm-5.6.4.dist-info → claude_mpm-5.6.30.dist-info}/entry_points.txt +0 -0
  101. {claude_mpm-5.6.4.dist-info → claude_mpm-5.6.30.dist-info}/licenses/LICENSE +0 -0
  102. {claude_mpm-5.6.4.dist-info → claude_mpm-5.6.30.dist-info}/licenses/LICENSE-FAQ.md +0 -0
  103. {claude_mpm-5.6.4.dist-info → claude_mpm-5.6.30.dist-info}/top_level.txt +0 -0
claude_mpm/cli/startup.py CHANGED
@@ -34,13 +34,13 @@ def sync_hooks_on_startup(quiet: bool = False) -> bool:
34
34
  installer = HookInstaller()
35
35
 
36
36
  # Show brief status (hooks sync is fast)
37
- if not quiet:
37
+ if not quiet and sys.stdout.isatty():
38
38
  print("Syncing Claude Code hooks...", end=" ", flush=True)
39
39
 
40
40
  # Reinstall hooks (force=True ensures update)
41
41
  success = installer.install_hooks(force=True)
42
42
 
43
- if not quiet:
43
+ if not quiet and sys.stdout.isatty():
44
44
  if success:
45
45
  print("✓")
46
46
  else:
@@ -49,7 +49,7 @@ def sync_hooks_on_startup(quiet: bool = False) -> bool:
49
49
  return success
50
50
 
51
51
  except Exception as e:
52
- if not quiet:
52
+ if not quiet and sys.stdout.isatty():
53
53
  print("(error)")
54
54
  # Log but don't fail startup
55
55
  from ..core.logger import get_logger
@@ -210,7 +210,16 @@ def should_skip_background_services(args, processed_argv):
210
210
  return any(cmd in (processed_argv or sys.argv[1:]) for cmd in skip_commands) or (
211
211
  hasattr(args, "command")
212
212
  and args.command
213
- in ["info", "doctor", "config", "mcp", "configure", "hook-errors", "autotodos"]
213
+ in [
214
+ "info",
215
+ "doctor",
216
+ "config",
217
+ "mcp",
218
+ "configure",
219
+ "hook-errors",
220
+ "autotodos",
221
+ "commander",
222
+ ]
214
223
  )
215
224
 
216
225
 
@@ -271,11 +280,13 @@ def deploy_bundled_skills():
271
280
  if deployment_result.get("deployed"):
272
281
  # Show simple feedback for deployed skills
273
282
  deployed_count = len(deployment_result["deployed"])
274
- print(f"✓ Bundled skills ready ({deployed_count} deployed)", flush=True)
283
+ if sys.stdout.isatty():
284
+ print(f"✓ Bundled skills ready ({deployed_count} deployed)", flush=True)
275
285
  logger.info(f"Skills: Deployed {deployed_count} skill(s)")
276
286
  elif not deployment_result.get("errors"):
277
287
  # No deployment needed, skills already present
278
- print("✓ Bundled skills ready", flush=True)
288
+ if sys.stdout.isatty():
289
+ print("✓ Bundled skills ready", flush=True)
279
290
 
280
291
  if deployment_result.get("errors"):
281
292
  logger.warning(
@@ -309,7 +320,8 @@ def discover_and_link_runtime_skills():
309
320
 
310
321
  discover_skills()
311
322
  # Show simple success feedback
312
- print("✓ Runtime skills linked", flush=True)
323
+ if sys.stdout.isatty():
324
+ print("✓ Runtime skills linked", flush=True)
313
325
  except Exception as e:
314
326
  # Import logger here to avoid circular imports
315
327
  from ..core.logger import get_logger
@@ -364,7 +376,8 @@ def deploy_output_style_on_startup():
364
376
 
365
377
  if all_up_to_date:
366
378
  # Show feedback that output styles are ready
367
- print("✓ Output styles ready", flush=True)
379
+ if sys.stdout.isatty():
380
+ print("✓ Output styles ready", flush=True)
368
381
  return
369
382
 
370
383
  # Deploy all styles using the manager
@@ -374,7 +387,8 @@ def deploy_output_style_on_startup():
374
387
  deployed_count = sum(1 for success in results.values() if success)
375
388
 
376
389
  if deployed_count > 0:
377
- print(f"✓ Output styles deployed ({deployed_count} styles)", flush=True)
390
+ if sys.stdout.isatty():
391
+ print(f"✓ Output styles deployed ({deployed_count} styles)", flush=True)
378
392
  else:
379
393
  # Deployment failed - log but don't fail startup
380
394
  from ..core.logger import get_logger
@@ -469,6 +483,94 @@ def _cleanup_orphaned_agents(deploy_target: Path, deployed_agents: list[str]) ->
469
483
  return removed_count
470
484
 
471
485
 
486
+ def _save_deployment_state_after_reconciliation(
487
+ agent_result, project_path: Path
488
+ ) -> None:
489
+ """Save deployment state after reconciliation to prevent duplicate deployment.
490
+
491
+ WHY: After perform_startup_reconciliation() deploys agents to .claude/agents/,
492
+ we need to save a deployment state file so that ClaudeRunner.setup_agents()
493
+ can detect agents are already deployed and skip redundant deployment.
494
+
495
+ This prevents the "✓ Deployed 31 native agents" duplicate deployment that
496
+ occurs when setup_agents() doesn't know reconciliation already ran.
497
+
498
+ Args:
499
+ agent_result: DeploymentResult from perform_startup_reconciliation()
500
+ project_path: Project root directory
501
+
502
+ DESIGN DECISION: Use same state file format as ClaudeRunner._save_deployment_state()
503
+ Located at: .claude-mpm/cache/deployment_state.json
504
+
505
+ State file format:
506
+ {
507
+ "version": "5.6.13",
508
+ "agent_count": 15,
509
+ "deployment_hash": "sha256:...",
510
+ "deployed_at": 1234567890.123
511
+ }
512
+ """
513
+ import hashlib
514
+ import json
515
+ import time
516
+
517
+ from ..core.logger import get_logger
518
+
519
+ logger = get_logger("cli")
520
+
521
+ try:
522
+ # Get version from package
523
+ from claude_mpm import __version__
524
+
525
+ # Path to state file (matches ClaudeRunner._get_deployment_state_path())
526
+ state_file = project_path / ".claude-mpm" / "cache" / "deployment_state.json"
527
+ agents_dir = project_path / ".claude" / "agents"
528
+
529
+ # Count deployed agents
530
+ if agents_dir.exists():
531
+ agent_count = len(list(agents_dir.glob("*.md")))
532
+ else:
533
+ agent_count = 0
534
+
535
+ # Calculate deployment hash (matches ClaudeRunner._calculate_deployment_hash())
536
+ # CRITICAL: Must match exact hash algorithm used in ClaudeRunner
537
+ # Hashes filename + file content (not mtime) for consistency
538
+ deployment_hash = ""
539
+ if agents_dir.exists():
540
+ agent_files = sorted(agents_dir.glob("*.md"))
541
+ hash_obj = hashlib.sha256()
542
+ for agent_file in agent_files:
543
+ # Include filename and content in hash (matches ClaudeRunner)
544
+ hash_obj.update(agent_file.name.encode())
545
+ try:
546
+ hash_obj.update(agent_file.read_bytes())
547
+ except Exception as e:
548
+ logger.debug(f"Error reading {agent_file} for hash: {e}")
549
+
550
+ deployment_hash = hash_obj.hexdigest()
551
+
552
+ # Create state data
553
+ state_data = {
554
+ "version": __version__,
555
+ "agent_count": agent_count,
556
+ "deployment_hash": deployment_hash,
557
+ "deployed_at": time.time(),
558
+ }
559
+
560
+ # Ensure directory exists
561
+ state_file.parent.mkdir(parents=True, exist_ok=True)
562
+
563
+ # Write state file
564
+ state_file.write_text(json.dumps(state_data, indent=2))
565
+ logger.debug(
566
+ f"Saved deployment state after reconciliation: {agent_count} agents"
567
+ )
568
+
569
+ except Exception as e:
570
+ # Non-critical error - log but don't fail startup
571
+ logger.debug(f"Failed to save deployment state: {e}")
572
+
573
+
472
574
  def sync_remote_agents_on_startup(force_sync: bool = False):
473
575
  """
474
576
  Synchronize agent templates from remote sources on startup.
@@ -631,6 +733,12 @@ def sync_remote_agents_on_startup(force_sync: bool = False):
631
733
  )
632
734
  print(" Run with --verbose for detailed error information.\n")
633
735
 
736
+ # Save deployment state to prevent duplicate deployment in ClaudeRunner
737
+ # This ensures setup_agents() skips deployment since we already reconciled
738
+ _save_deployment_state_after_reconciliation(
739
+ agent_result=agent_result, project_path=project_path
740
+ )
741
+
634
742
  except Exception as e:
635
743
  # Deployment failure shouldn't block startup
636
744
  from ..core.logger import get_logger
@@ -1187,9 +1295,11 @@ def verify_and_show_pm_skills():
1187
1295
  if result.verified:
1188
1296
  # Show verified status with count
1189
1297
  total_required = len(REQUIRED_PM_SKILLS)
1190
- print(
1191
- f"✓ PM skills: {total_required}/{total_required} verified", flush=True
1192
- )
1298
+ if sys.stdout.isatty():
1299
+ print(
1300
+ f"✓ PM skills: {total_required}/{total_required} verified",
1301
+ flush=True,
1302
+ )
1193
1303
  else:
1194
1304
  # Show warning with details
1195
1305
  missing_count = len(result.missing_skills)
@@ -1208,13 +1318,15 @@ def verify_and_show_pm_skills():
1208
1318
  if "Auto-repaired" in result.message:
1209
1319
  # Auto-repair succeeded
1210
1320
  total_required = len(REQUIRED_PM_SKILLS)
1211
- print(
1212
- f"✓ PM skills: {total_required}/{total_required} verified (auto-repaired)",
1213
- flush=True,
1214
- )
1321
+ if sys.stdout.isatty():
1322
+ print(
1323
+ f"✓ PM skills: {total_required}/{total_required} verified (auto-repaired)",
1324
+ flush=True,
1325
+ )
1215
1326
  else:
1216
1327
  # Auto-repair failed or not attempted
1217
- print(f"⚠ PM skills: {status}", flush=True)
1328
+ if sys.stdout.isatty():
1329
+ print(f"⚠ PM skills: {status}", flush=True)
1218
1330
 
1219
1331
  # Log warnings for debugging
1220
1332
  from ..core.logger import get_logger
@@ -1413,7 +1525,9 @@ def check_mcp_auto_configuration():
1413
1525
  from ..services.mcp_gateway.auto_configure import check_and_configure_mcp
1414
1526
 
1415
1527
  # Show progress feedback - this operation can take 10+ seconds
1416
- print("Checking MCP configuration...", end="", flush=True)
1528
+ # Only show progress message in TTY mode to avoid interfering with Claude Code's status display
1529
+ if sys.stdout.isatty():
1530
+ print("Checking MCP configuration...", end="", flush=True)
1417
1531
 
1418
1532
  # This function handles all the logic:
1419
1533
  # - Checks if already configured
@@ -1424,11 +1538,17 @@ def check_mcp_auto_configuration():
1424
1538
  check_and_configure_mcp()
1425
1539
 
1426
1540
  # Clear the "Checking..." message by overwriting with spaces
1427
- print("\r" + " " * 30 + "\r", end="", flush=True)
1541
+ # Only use carriage return clearing if stdout is a real TTY
1542
+ if sys.stdout.isatty():
1543
+ print("\r" + " " * 30 + "\r", end="", flush=True)
1544
+ # In non-TTY mode, don't print anything - the "Checking..." message will just remain on its line
1428
1545
 
1429
1546
  except Exception as e:
1430
1547
  # Clear progress message on error
1431
- print("\r" + " " * 30 + "\r", end="", flush=True)
1548
+ # Only use carriage return clearing if stdout is a real TTY
1549
+ if sys.stdout.isatty():
1550
+ print("\r" + " " * 30 + "\r", end="", flush=True)
1551
+ # In non-TTY mode, don't print anything - the "Checking..." message will just remain on its line
1432
1552
 
1433
1553
  # Non-critical - log but don't fail
1434
1554
  from ..core.logger import get_logger
@@ -540,7 +540,8 @@ def should_show_banner(args) -> bool:
540
540
  return False
541
541
 
542
542
  # Check for commands that should skip banner
543
- skip_commands = {"info", "doctor", "config", "configure"}
543
+ # Commander has its own banner, so skip the main MPM banner
544
+ skip_commands = {"info", "doctor", "config", "configure", "commander"}
544
545
  if hasattr(args, "command") and args.command in skip_commands:
545
546
  return False
546
547
 
@@ -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"