claude-mpm 5.6.3__py3-none-any.whl → 5.6.16__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/cli/commands/commander.py +173 -3
- claude_mpm/cli/commands/skill_source.py +51 -2
- claude_mpm/cli/commands/skills.py +5 -3
- claude_mpm/cli/parsers/commander_parser.py +41 -8
- claude_mpm/cli/parsers/skill_source_parser.py +4 -0
- claude_mpm/cli/parsers/skills_parser.py +5 -0
- claude_mpm/cli/startup.py +10 -1
- claude_mpm/cli/startup_display.py +2 -1
- 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 +4 -0
- 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 +206 -10
- claude_mpm/commander/env_loader.py +59 -0
- 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/registry.py +10 -4
- claude_mpm/commander/runtime/monitor.py +32 -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/core/claude_runner.py +143 -0
- claude_mpm/core/config.py +27 -19
- claude_mpm/core/output_style_manager.py +34 -7
- claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/__init__.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/auto_pause_handler.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/correlation_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/event_handlers.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/hook_handler.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/installer.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/memory_integration.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/response_tracking.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/__pycache__/tool_analysis.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/auto_pause_handler.py +29 -30
- claude_mpm/hooks/claude_hooks/event_handlers.py +80 -105
- claude_mpm/hooks/claude_hooks/hook_handler.py +0 -0
- claude_mpm/hooks/claude_hooks/installer.py +41 -0
- claude_mpm/hooks/claude_hooks/memory_integration.py +30 -21
- claude_mpm/hooks/claude_hooks/response_tracking.py +39 -58
- claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/__init__.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/connection_manager_http.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/duplicate_detector.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/state_manager.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-311.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-312.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/__pycache__/subagent_processor.cpython-314.pyc +0 -0
- claude_mpm/hooks/claude_hooks/services/connection_manager.py +23 -28
- claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +22 -26
- claude_mpm/hooks/claude_hooks/services/state_manager.py +23 -36
- claude_mpm/hooks/claude_hooks/services/subagent_processor.py +47 -73
- claude_mpm/hooks/session_resume_hook.py +22 -18
- claude_mpm/hooks/templates/pre_tool_use_template.py +10 -2
- claude_mpm/scripts/claude-hook-handler.sh +5 -5
- claude_mpm/scripts/start_activity_logging.py +0 -0
- claude_mpm/services/agents/agent_selection_service.py +2 -2
- claude_mpm/services/agents/single_tier_deployment_service.py +4 -4
- 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.3.dist-info → claude_mpm-5.6.16.dist-info}/METADATA +5 -3
- {claude_mpm-5.6.3.dist-info → claude_mpm-5.6.16.dist-info}/RECORD +116 -58
- {claude_mpm-5.6.3.dist-info → claude_mpm-5.6.16.dist-info}/WHEEL +0 -0
- {claude_mpm-5.6.3.dist-info → claude_mpm-5.6.16.dist-info}/entry_points.txt +0 -0
- {claude_mpm-5.6.3.dist-info → claude_mpm-5.6.16.dist-info}/licenses/LICENSE +0 -0
- {claude_mpm-5.6.3.dist-info → claude_mpm-5.6.16.dist-info}/licenses/LICENSE-FAQ.md +0 -0
- {claude_mpm-5.6.3.dist-info → claude_mpm-5.6.16.dist-info}/top_level.txt +0 -0
claude_mpm/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
5.6.
|
|
1
|
+
5.6.16
|
|
@@ -294,6 +294,8 @@ If you're about to run ANY other command, stop and delegate instead.
|
|
|
294
294
|
- Grep (>1), Glob (investigation) → Delegate to research
|
|
295
295
|
- `mcp__mcp-ticketer__*` → Delegate to ticketing
|
|
296
296
|
- `mcp__chrome-devtools__*` → Delegate to web-qa
|
|
297
|
+
- `mcp__claude-in-chrome__*` → Delegate to web-qa
|
|
298
|
+
- `mcp__playwright__*` → Delegate to web-qa
|
|
297
299
|
|
|
298
300
|
## Agent Deployment Architecture
|
|
299
301
|
|
|
@@ -358,7 +360,7 @@ These are EXAMPLES of routing, not an exhaustive list. **Default to delegation f
|
|
|
358
360
|
| **Research** | Understanding codebase, investigating approaches, analyzing files | Grep, Glob, Read multiple files, WebSearch | Investigation tools |
|
|
359
361
|
| **Engineer** | Writing/modifying code, implementing features, refactoring | Edit, Write, codebase knowledge, testing workflows | - |
|
|
360
362
|
| **Ops** (local-ops) | Deploying apps, managing infrastructure, starting servers, port/process management | Environment config, deployment procedures | Use `local-ops` for localhost/PM2/docker |
|
|
361
|
-
| **QA** (web-qa, api-qa) | Testing implementations, verifying deployments, regression tests, browser testing | Playwright (web), fetch (APIs), verification protocols | For browser: use **web-qa** (never use chrome-devtools directly) |
|
|
363
|
+
| **QA** (web-qa, api-qa) | Testing implementations, verifying deployments, regression tests, browser testing | Playwright (web), fetch (APIs), verification protocols | For browser: use **web-qa** (never use chrome-devtools, claude-in-chrome, or playwright directly) |
|
|
362
364
|
| **Documentation** | Creating/updating docs, README, API docs, guides | Style consistency, organization standards | - |
|
|
363
365
|
| **Ticketing** | ALL ticket operations (CRUD, search, hierarchy, comments) | Direct mcp-ticketer access | PM never uses `mcp__mcp-ticketer__*` directly |
|
|
364
366
|
| **Version Control** | Creating PRs, managing branches, complex git ops | PR workflows, branch management | Check git user for main branch access (bobmatnyc@users.noreply.github.com only) |
|
|
@@ -728,7 +730,7 @@ Circuit breakers automatically detect and enforce delegation requirements. All c
|
|
|
728
730
|
| 3 | Unverified Assertions | PM claiming status without agent evidence | Require verification evidence | [Details](#circuit-breaker-3-unverified-assertions) |
|
|
729
731
|
| 4 | File Tracking | PM marking task complete without tracking new files | Run git tracking sequence | [Details](#circuit-breaker-4-file-tracking-enforcement) |
|
|
730
732
|
| 5 | Delegation Chain | PM claiming completion without full workflow delegation | Execute missing phases | [Details](#circuit-breaker-5-delegation-chain) |
|
|
731
|
-
| 6 | Forbidden Tool Usage | PM using ticketing/browser MCP tools directly | Delegate to specialist agent | [Details](#circuit-breaker-6-forbidden-tool-usage) |
|
|
733
|
+
| 6 | Forbidden Tool Usage | PM using ticketing/browser MCP tools (ticketer, chrome-devtools, claude-in-chrome, playwright) directly | Delegate to specialist agent | [Details](#circuit-breaker-6-forbidden-tool-usage) |
|
|
732
734
|
| 7 | Verification Commands | PM using curl/lsof/ps/wget/nc | Delegate to local-ops or QA | [Details](#circuit-breaker-7-verification-command-detection) |
|
|
733
735
|
| 8 | QA Verification Gate | PM claiming work complete without QA delegation | BLOCK - Delegate to QA now | [Details](#circuit-breaker-8-qa-verification-gate) |
|
|
734
736
|
| 9 | User Delegation | PM instructing user to run commands | Delegate to appropriate agent | [Details](#circuit-breaker-9-user-delegation-detection) |
|
|
@@ -747,6 +749,9 @@ Circuit breakers automatically detect and enforce delegation requirements. All c
|
|
|
747
749
|
- "It works" / "It's deployed" → Circuit Breaker #3
|
|
748
750
|
- Marks todo complete without `git status` → Circuit Breaker #4
|
|
749
751
|
- Uses `mcp__mcp-ticketer__*` → Circuit Breaker #6
|
|
752
|
+
- Uses `mcp__chrome-devtools__*` → Circuit Breaker #6
|
|
753
|
+
- Uses `mcp__claude-in-chrome__*` → Circuit Breaker #6
|
|
754
|
+
- Uses `mcp__playwright__*` → Circuit Breaker #6
|
|
750
755
|
- Uses curl/lsof directly → Circuit Breaker #7
|
|
751
756
|
- Claims complete without QA → Circuit Breaker #8
|
|
752
757
|
- "You'll need to run..." → Circuit Breaker #9
|
|
@@ -782,7 +787,7 @@ When the user says "just do it" or "handle it", delegate to the full workflow pi
|
|
|
782
787
|
|
|
783
788
|
When the user says "verify", "check", or "test", delegate to the QA agent with specific verification criteria.
|
|
784
789
|
|
|
785
|
-
When the user mentions "browser", "screenshot", "click", "navigate", "DOM", "console errors", delegate to web-qa agent for browser testing (NEVER use chrome-devtools tools directly).
|
|
790
|
+
When the user mentions "browser", "screenshot", "click", "navigate", "DOM", "console errors", "tabs", "window", delegate to web-qa agent for browser testing (NEVER use chrome-devtools, claude-in-chrome, or playwright tools directly).
|
|
786
791
|
|
|
787
792
|
When the user mentions "localhost", "local server", or "PM2", delegate to **local-ops** as the primary choice for local development operations.
|
|
788
793
|
|
|
@@ -2,25 +2,129 @@
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import logging
|
|
5
|
+
import shutil
|
|
6
|
+
import threading
|
|
7
|
+
import time
|
|
8
|
+
from pathlib import Path
|
|
5
9
|
|
|
6
10
|
logger = logging.getLogger(__name__)
|
|
7
11
|
|
|
12
|
+
# ANSI colors
|
|
13
|
+
CYAN = "\033[36m"
|
|
14
|
+
DIM = "\033[2m"
|
|
15
|
+
BOLD = "\033[1m"
|
|
16
|
+
YELLOW = "\033[33m"
|
|
17
|
+
GREEN = "\033[32m"
|
|
18
|
+
RED = "\033[31m"
|
|
19
|
+
RESET = "\033[0m"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _get_terminal_width() -> int:
|
|
23
|
+
"""Get terminal width with reasonable bounds."""
|
|
24
|
+
try:
|
|
25
|
+
width = shutil.get_terminal_size().columns
|
|
26
|
+
return max(80, min(width, 120))
|
|
27
|
+
except Exception:
|
|
28
|
+
return 100
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _get_version() -> str:
|
|
32
|
+
"""Get Commander version."""
|
|
33
|
+
version_file = Path(__file__).parent.parent.parent / "VERSION"
|
|
34
|
+
if version_file.exists():
|
|
35
|
+
return version_file.read_text().strip()
|
|
36
|
+
return "unknown"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def display_commander_banner():
|
|
40
|
+
"""Display Commander-specific startup banner."""
|
|
41
|
+
width = _get_terminal_width()
|
|
42
|
+
version = _get_version()
|
|
43
|
+
|
|
44
|
+
# Commander ASCII art banner
|
|
45
|
+
banner = f"""
|
|
46
|
+
{CYAN}╭{'─' * (width - 2)}╮{RESET}
|
|
47
|
+
{CYAN}│{RESET}{BOLD} ⚡ MPM Commander {RESET}{DIM}v{version}{RESET}{' ' * (width - 24 - len(version))}│
|
|
48
|
+
{CYAN}│{RESET}{DIM} Multi-Project AI Orchestration{RESET}{' ' * (width - 36)}│
|
|
49
|
+
{CYAN}├{'─' * (width - 2)}┤{RESET}
|
|
50
|
+
{CYAN}│{RESET} {YELLOW}ALPHA{RESET} - APIs may change {' ' * (width - 55)}│
|
|
51
|
+
{CYAN}╰{'─' * (width - 2)}╯{RESET}
|
|
52
|
+
"""
|
|
53
|
+
print(banner)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _count_cached_agents() -> int:
|
|
57
|
+
"""Count cached agents from ~/.claude-mpm/cache/agents/."""
|
|
58
|
+
try:
|
|
59
|
+
cache_agents_dir = Path.home() / ".claude-mpm" / "cache" / "agents"
|
|
60
|
+
if not cache_agents_dir.exists():
|
|
61
|
+
return 0
|
|
62
|
+
# Recursively find all .md files excluding base/README files
|
|
63
|
+
agent_files = [
|
|
64
|
+
f
|
|
65
|
+
for f in cache_agents_dir.rglob("*.md")
|
|
66
|
+
if f.is_file()
|
|
67
|
+
and not f.name.startswith(".")
|
|
68
|
+
and f.name not in ("README.md", "BASE-AGENT.md", "INSTRUCTIONS.md")
|
|
69
|
+
]
|
|
70
|
+
return len(agent_files)
|
|
71
|
+
except Exception:
|
|
72
|
+
return 0
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _count_cached_skills() -> int:
|
|
76
|
+
"""Count cached skills from ~/.claude-mpm/cache/skills/."""
|
|
77
|
+
try:
|
|
78
|
+
cache_skills_dir = Path.home() / ".claude-mpm" / "cache" / "skills"
|
|
79
|
+
if not cache_skills_dir.exists():
|
|
80
|
+
return 0
|
|
81
|
+
# Recursively find all directories containing SKILL.md
|
|
82
|
+
skill_files = list(cache_skills_dir.rglob("SKILL.md"))
|
|
83
|
+
return len(skill_files)
|
|
84
|
+
except Exception:
|
|
85
|
+
return 0
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def load_agents_and_skills():
|
|
89
|
+
"""Load agents and skills for Commander sessions."""
|
|
90
|
+
try:
|
|
91
|
+
print(f"{DIM}Loading agents...{RESET}", end=" ", flush=True)
|
|
92
|
+
agent_count = _count_cached_agents()
|
|
93
|
+
print(f"{GREEN}✓{RESET} {agent_count} agents")
|
|
94
|
+
|
|
95
|
+
print(f"{DIM}Loading skills...{RESET}", end=" ", flush=True)
|
|
96
|
+
skill_count = _count_cached_skills()
|
|
97
|
+
print(f"{GREEN}✓{RESET} {skill_count} skills")
|
|
98
|
+
|
|
99
|
+
return agent_count, skill_count
|
|
100
|
+
except Exception as e:
|
|
101
|
+
logger.warning(f"Could not load agents/skills: {e}")
|
|
102
|
+
print(f"{YELLOW}⚠{RESET} Could not load agents/skills")
|
|
103
|
+
return 0, 0
|
|
104
|
+
|
|
8
105
|
|
|
9
106
|
def handle_commander_command(args) -> int:
|
|
10
|
-
"""Handle the commander command.
|
|
107
|
+
"""Handle the commander command with auto-starting daemon.
|
|
11
108
|
|
|
12
109
|
Args:
|
|
13
110
|
args: Parsed command line arguments with:
|
|
14
|
-
- port: Port for
|
|
111
|
+
- port: Port for daemon (default: 8765)
|
|
112
|
+
- host: Host for daemon (default: 127.0.0.1)
|
|
15
113
|
- state_dir: Optional state directory path
|
|
16
114
|
- debug: Enable debug logging
|
|
115
|
+
- no_chat: Start daemon only without interactive chat
|
|
116
|
+
- daemon_only: Alias for no_chat
|
|
17
117
|
|
|
18
118
|
Returns:
|
|
19
119
|
Exit code (0 for success, 1 for error)
|
|
20
120
|
"""
|
|
21
121
|
try:
|
|
22
122
|
# Import here to avoid circular dependencies
|
|
123
|
+
import requests
|
|
124
|
+
|
|
23
125
|
from claude_mpm.commander.chat.cli import run_commander
|
|
126
|
+
from claude_mpm.commander.config import DaemonConfig
|
|
127
|
+
from claude_mpm.commander.daemon import main as daemon_main
|
|
24
128
|
|
|
25
129
|
# Setup debug logging if requested
|
|
26
130
|
if getattr(args, "debug", False):
|
|
@@ -29,11 +133,76 @@ def handle_commander_command(args) -> int:
|
|
|
29
133
|
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
30
134
|
)
|
|
31
135
|
|
|
136
|
+
# Display Commander banner
|
|
137
|
+
display_commander_banner()
|
|
138
|
+
|
|
139
|
+
# Load agents and skills
|
|
140
|
+
load_agents_and_skills()
|
|
141
|
+
|
|
142
|
+
print() # Blank line after loading
|
|
143
|
+
|
|
32
144
|
# Get arguments
|
|
33
145
|
port = getattr(args, "port", 8765)
|
|
146
|
+
host = getattr(args, "host", "127.0.0.1")
|
|
34
147
|
state_dir = getattr(args, "state_dir", None)
|
|
148
|
+
no_chat = getattr(args, "no_chat", False) or getattr(args, "daemon_only", False)
|
|
149
|
+
|
|
150
|
+
# Check if daemon already running
|
|
151
|
+
daemon_running = False
|
|
152
|
+
try:
|
|
153
|
+
resp = requests.get(f"http://{host}:{port}/api/health", timeout=1)
|
|
154
|
+
if resp.status_code == 200:
|
|
155
|
+
print(f"{GREEN}✓{RESET} Daemon already running on {host}:{port}")
|
|
156
|
+
daemon_running = True
|
|
157
|
+
except (requests.RequestException, requests.ConnectionError):
|
|
158
|
+
pass
|
|
159
|
+
|
|
160
|
+
# Start daemon if not running
|
|
161
|
+
if not daemon_running:
|
|
162
|
+
print(
|
|
163
|
+
f"{DIM}Starting daemon on {host}:{port}...{RESET}", end=" ", flush=True
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# Create daemon config
|
|
167
|
+
config_kwargs = {"host": host, "port": port}
|
|
168
|
+
if state_dir:
|
|
169
|
+
config_kwargs["state_dir"] = state_dir
|
|
170
|
+
config = DaemonConfig(**config_kwargs)
|
|
171
|
+
|
|
172
|
+
# Start daemon in background thread
|
|
173
|
+
daemon_thread = threading.Thread(
|
|
174
|
+
target=lambda: asyncio.run(daemon_main(config)), daemon=True
|
|
175
|
+
)
|
|
176
|
+
daemon_thread.start()
|
|
177
|
+
|
|
178
|
+
# Wait for daemon to be ready (max 3 seconds)
|
|
179
|
+
for _ in range(30):
|
|
180
|
+
time.sleep(0.1)
|
|
181
|
+
try:
|
|
182
|
+
resp = requests.get(f"http://{host}:{port}/api/health", timeout=1)
|
|
183
|
+
if resp.status_code == 200:
|
|
184
|
+
print(f"{GREEN}✓{RESET}")
|
|
185
|
+
daemon_running = True
|
|
186
|
+
break
|
|
187
|
+
except (requests.RequestException, requests.ConnectionError):
|
|
188
|
+
pass
|
|
189
|
+
else:
|
|
190
|
+
print(f"{RED}✗{RESET} Failed (timeout)")
|
|
191
|
+
return 1
|
|
192
|
+
|
|
193
|
+
# If daemon-only mode, keep running until interrupted
|
|
194
|
+
if no_chat:
|
|
195
|
+
print(f"\n{CYAN}Daemon running.{RESET} API at http://{host}:{port}")
|
|
196
|
+
print(f"{DIM}Press Ctrl+C to stop{RESET}\n")
|
|
197
|
+
try:
|
|
198
|
+
while True:
|
|
199
|
+
time.sleep(1)
|
|
200
|
+
except KeyboardInterrupt:
|
|
201
|
+
print(f"\n{DIM}Shutting down...{RESET}")
|
|
202
|
+
return 0
|
|
35
203
|
|
|
36
|
-
#
|
|
204
|
+
# Launch interactive chat
|
|
205
|
+
print(f"\n{CYAN}Entering Commander chat...{RESET}\n")
|
|
37
206
|
asyncio.run(run_commander(port=port, state_dir=state_dir))
|
|
38
207
|
|
|
39
208
|
return 0
|
|
@@ -43,4 +212,5 @@ def handle_commander_command(args) -> int:
|
|
|
43
212
|
return 0
|
|
44
213
|
except Exception as e:
|
|
45
214
|
logger.error(f"Commander error: {e}", exc_info=True)
|
|
215
|
+
print(f"{RED}Error:{RESET} {e}")
|
|
46
216
|
return 1
|
|
@@ -11,6 +11,7 @@ for better UX. Handles errors gracefully with actionable messages.
|
|
|
11
11
|
|
|
12
12
|
import json
|
|
13
13
|
import logging
|
|
14
|
+
import os
|
|
14
15
|
import re
|
|
15
16
|
|
|
16
17
|
from ...config.skill_sources import SkillSource, SkillSourceConfiguration
|
|
@@ -20,6 +21,33 @@ from ...services.skills.skill_discovery_service import SkillDiscoveryService
|
|
|
20
21
|
logger = logging.getLogger(__name__)
|
|
21
22
|
|
|
22
23
|
|
|
24
|
+
def _get_github_token(source: SkillSource | None = None) -> str | None:
|
|
25
|
+
"""Get GitHub token with source-specific override support.
|
|
26
|
+
|
|
27
|
+
Priority: source.token > GITHUB_TOKEN > GH_TOKEN
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
source: Optional SkillSource to check for per-source token
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
GitHub token if found, None otherwise
|
|
34
|
+
|
|
35
|
+
Security Note:
|
|
36
|
+
Token is never logged or printed to avoid exposure.
|
|
37
|
+
"""
|
|
38
|
+
# Priority 1: Per-source token (env var reference or direct)
|
|
39
|
+
if source and source.token:
|
|
40
|
+
if source.token.startswith("$"):
|
|
41
|
+
# Env var reference: $VAR_NAME -> os.environ.get("VAR_NAME")
|
|
42
|
+
env_var_name = source.token[1:]
|
|
43
|
+
return os.environ.get(env_var_name)
|
|
44
|
+
# Direct token (not recommended but supported)
|
|
45
|
+
return source.token
|
|
46
|
+
|
|
47
|
+
# Priority 2-3: Global environment variables
|
|
48
|
+
return os.environ.get("GITHUB_TOKEN") or os.environ.get("GH_TOKEN")
|
|
49
|
+
|
|
50
|
+
|
|
23
51
|
def _test_skill_repository_access(source: SkillSource) -> dict:
|
|
24
52
|
"""Test if skill repository is accessible via GitHub API.
|
|
25
53
|
|
|
@@ -58,7 +86,13 @@ def _test_skill_repository_access(source: SkillSource) -> dict:
|
|
|
58
86
|
# Test GitHub API access
|
|
59
87
|
api_url = f"https://api.github.com/repos/{owner_repo}"
|
|
60
88
|
|
|
61
|
-
|
|
89
|
+
# Build headers with authentication if token available
|
|
90
|
+
headers = {"Accept": "application/vnd.github+json"}
|
|
91
|
+
token = _get_github_token(source)
|
|
92
|
+
if token:
|
|
93
|
+
headers["Authorization"] = f"token {token}"
|
|
94
|
+
|
|
95
|
+
response = requests.get(api_url, headers=headers, timeout=10)
|
|
62
96
|
|
|
63
97
|
if response.status_code == 200:
|
|
64
98
|
return {"accessible": True, "error": None}
|
|
@@ -68,9 +102,14 @@ def _test_skill_repository_access(source: SkillSource) -> dict:
|
|
|
68
102
|
"error": f"Repository not found: {owner_repo}",
|
|
69
103
|
}
|
|
70
104
|
if response.status_code == 403:
|
|
105
|
+
error_msg = "Access denied (private repository or rate limit)"
|
|
106
|
+
if not token:
|
|
107
|
+
error_msg += (
|
|
108
|
+
". Try setting GITHUB_TOKEN environment variable for private repos"
|
|
109
|
+
)
|
|
71
110
|
return {
|
|
72
111
|
"accessible": False,
|
|
73
|
-
"error":
|
|
112
|
+
"error": error_msg,
|
|
74
113
|
}
|
|
75
114
|
return {
|
|
76
115
|
"accessible": False,
|
|
@@ -263,6 +302,15 @@ def handle_add_skill_source(args) -> int:
|
|
|
263
302
|
|
|
264
303
|
# Create new source
|
|
265
304
|
enabled = not args.disabled
|
|
305
|
+
token = getattr(args, "token", None)
|
|
306
|
+
|
|
307
|
+
# Security warning for direct tokens
|
|
308
|
+
if token and not token.startswith("$"):
|
|
309
|
+
print("⚠️ Warning: Direct token values in config are not recommended")
|
|
310
|
+
print(" Consider using environment variable reference instead:")
|
|
311
|
+
print(" --token $MY_PRIVATE_TOKEN")
|
|
312
|
+
print()
|
|
313
|
+
|
|
266
314
|
source = SkillSource(
|
|
267
315
|
id=source_id,
|
|
268
316
|
type="git",
|
|
@@ -270,6 +318,7 @@ def handle_add_skill_source(args) -> int:
|
|
|
270
318
|
branch=args.branch,
|
|
271
319
|
priority=args.priority,
|
|
272
320
|
enabled=enabled,
|
|
321
|
+
token=token,
|
|
273
322
|
)
|
|
274
323
|
|
|
275
324
|
# Determine if we should test
|
|
@@ -538,6 +538,7 @@ class SkillsManagementCommand(BaseCommand):
|
|
|
538
538
|
toolchain = getattr(args, "toolchain", None)
|
|
539
539
|
categories = getattr(args, "categories", None)
|
|
540
540
|
force = getattr(args, "force", False)
|
|
541
|
+
deploy_all = getattr(args, "all", False)
|
|
541
542
|
|
|
542
543
|
if collection:
|
|
543
544
|
console.print(
|
|
@@ -548,14 +549,15 @@ class SkillsManagementCommand(BaseCommand):
|
|
|
548
549
|
"\n[bold cyan]Deploying skills from default collection...[/bold cyan]\n"
|
|
549
550
|
)
|
|
550
551
|
|
|
551
|
-
#
|
|
552
|
-
#
|
|
552
|
+
# Use selective deployment unless --all flag is provided
|
|
553
|
+
# Selective mode deploys only agent-referenced skills
|
|
554
|
+
# --all mode deploys all available skills from the collection
|
|
553
555
|
result = self.skills_deployer.deploy_skills(
|
|
554
556
|
collection=collection,
|
|
555
557
|
toolchain=toolchain,
|
|
556
558
|
categories=categories,
|
|
557
559
|
force=force,
|
|
558
|
-
selective=
|
|
560
|
+
selective=not deploy_all,
|
|
559
561
|
)
|
|
560
562
|
|
|
561
563
|
# Display results
|
|
@@ -23,17 +23,21 @@ def add_commander_subparser(subparsers: argparse._SubParsersAction) -> None:
|
|
|
23
23
|
"""
|
|
24
24
|
commander_parser = subparsers.add_parser(
|
|
25
25
|
"commander",
|
|
26
|
-
help="
|
|
26
|
+
help="Launch Commander multi-project orchestration (ALPHA)",
|
|
27
27
|
description="""
|
|
28
|
-
Commander Mode -
|
|
28
|
+
Commander Mode - Multi-Project Orchestration (ALPHA)
|
|
29
29
|
|
|
30
|
-
Commander
|
|
31
|
-
|
|
32
|
-
- Connecting to instances and sending natural language commands
|
|
33
|
-
- Managing multiple concurrent projects
|
|
34
|
-
- Viewing instance status and output
|
|
30
|
+
The commander subcommand auto-starts the Commander daemon (if not already running)
|
|
31
|
+
and launches an interactive REPL for managing multiple Claude Code instances.
|
|
35
32
|
|
|
36
|
-
|
|
33
|
+
Commander provides:
|
|
34
|
+
- Auto-starting daemon that manages project lifecycles
|
|
35
|
+
- Interactive REPL for controlling instances
|
|
36
|
+
- Tmux-based session management
|
|
37
|
+
- Real-time output monitoring
|
|
38
|
+
- REST API for external control (http://127.0.0.1:8765)
|
|
39
|
+
|
|
40
|
+
REPL Commands:
|
|
37
41
|
list, ls, instances List active instances
|
|
38
42
|
start <path> Start new instance at path
|
|
39
43
|
--framework <cc|mpm> Specify framework (default: cc)
|
|
@@ -50,7 +54,16 @@ Natural Language:
|
|
|
50
54
|
command will be sent to the connected instance as a message.
|
|
51
55
|
|
|
52
56
|
Examples:
|
|
57
|
+
# Start daemon and launch interactive chat
|
|
53
58
|
claude-mpm commander
|
|
59
|
+
|
|
60
|
+
# Start daemon only (no chat interface)
|
|
61
|
+
claude-mpm commander --daemon-only
|
|
62
|
+
|
|
63
|
+
# Use custom port
|
|
64
|
+
claude-mpm commander --port 9000
|
|
65
|
+
|
|
66
|
+
# In REPL:
|
|
54
67
|
> start ~/myproject --framework cc --name myapp
|
|
55
68
|
> connect myapp
|
|
56
69
|
> Fix the authentication bug in login.py
|
|
@@ -81,3 +94,23 @@ Examples:
|
|
|
81
94
|
action="store_true",
|
|
82
95
|
help="Enable debug logging",
|
|
83
96
|
)
|
|
97
|
+
|
|
98
|
+
# Daemon auto-start options
|
|
99
|
+
commander_parser.add_argument(
|
|
100
|
+
"--host",
|
|
101
|
+
type=str,
|
|
102
|
+
default="127.0.0.1",
|
|
103
|
+
help="Daemon host (default: 127.0.0.1)",
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
commander_parser.add_argument(
|
|
107
|
+
"--no-chat",
|
|
108
|
+
action="store_true",
|
|
109
|
+
help="Start daemon only without interactive chat",
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
commander_parser.add_argument(
|
|
113
|
+
"--daemon-only",
|
|
114
|
+
action="store_true",
|
|
115
|
+
help="Alias for --no-chat (start daemon only)",
|
|
116
|
+
)
|
|
@@ -76,6 +76,10 @@ def add_skill_source_subparser(subparsers) -> argparse.ArgumentParser:
|
|
|
76
76
|
dest="skip_test",
|
|
77
77
|
help="Skip immediate testing (not recommended)",
|
|
78
78
|
)
|
|
79
|
+
add_parser.add_argument(
|
|
80
|
+
"--token",
|
|
81
|
+
help="GitHub token or env var reference (e.g., ghp_xxx or $PRIVATE_TOKEN)",
|
|
82
|
+
)
|
|
79
83
|
|
|
80
84
|
# Remove repository
|
|
81
85
|
remove_parser = skill_source_subparsers.add_parser(
|
|
@@ -167,6 +167,11 @@ def add_skills_subparser(subparsers) -> argparse.ArgumentParser:
|
|
|
167
167
|
action="store_true",
|
|
168
168
|
help="Force redeployment of already deployed skills",
|
|
169
169
|
)
|
|
170
|
+
deploy_github_parser.add_argument(
|
|
171
|
+
"--all",
|
|
172
|
+
action="store_true",
|
|
173
|
+
help="Deploy all available skills, not just agent-referenced ones",
|
|
174
|
+
)
|
|
170
175
|
|
|
171
176
|
# List available GitHub skills
|
|
172
177
|
list_available_parser = skills_subparsers.add_parser(
|
claude_mpm/cli/startup.py
CHANGED
|
@@ -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 [
|
|
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
|
|
|
@@ -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
|
-
|
|
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
|
|
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
|
]
|