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,170 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mpm-session-pause
|
|
3
|
+
description: Pause session and save current work state for later resume
|
|
4
|
+
user-invocable: true
|
|
5
|
+
version: "1.0.0"
|
|
6
|
+
category: mpm-command
|
|
7
|
+
tags: [mpm-command, session, pm-recommended]
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# /mpm-pause
|
|
11
|
+
|
|
12
|
+
Pause the current session and save all work state for later resume.
|
|
13
|
+
|
|
14
|
+
## What This Does
|
|
15
|
+
|
|
16
|
+
When invoked, this skill:
|
|
17
|
+
1. Captures current work state (todos, git status, context summary)
|
|
18
|
+
2. Creates session file at `.claude-mpm/sessions/session-{timestamp}.md`
|
|
19
|
+
3. Updates `.claude-mpm/sessions/LATEST-SESSION.txt` pointer
|
|
20
|
+
4. Optionally commits session state to git
|
|
21
|
+
5. Shows user the session file path for later resume
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
/mpm-pause [optional message describing current work]
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Examples:**
|
|
30
|
+
```
|
|
31
|
+
/mpm-pause
|
|
32
|
+
/mpm-pause Working on authentication refactor, about to test login flow
|
|
33
|
+
/mpm-pause Need to context switch to urgent bug fix
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Implementation
|
|
37
|
+
|
|
38
|
+
**Execute the following Python code to pause the session:**
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from pathlib import Path
|
|
42
|
+
from claude_mpm.services.cli.session_pause_manager import SessionPauseManager
|
|
43
|
+
|
|
44
|
+
# Optional: Get message from user's command
|
|
45
|
+
# If user provided message after /mpm-pause, extract it
|
|
46
|
+
# Otherwise, message = None
|
|
47
|
+
|
|
48
|
+
# Create session pause manager
|
|
49
|
+
manager = SessionPauseManager(project_path=Path.cwd())
|
|
50
|
+
|
|
51
|
+
# Create pause session
|
|
52
|
+
session_id = manager.create_pause_session(
|
|
53
|
+
message=message, # Optional context message
|
|
54
|
+
skip_commit=False, # Will commit to git if in a repo
|
|
55
|
+
export_path=None, # No additional export needed
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Report success to user
|
|
59
|
+
print(f"✅ Session paused successfully!")
|
|
60
|
+
print(f"")
|
|
61
|
+
print(f"Session ID: {session_id}")
|
|
62
|
+
print(f"Session files:")
|
|
63
|
+
print(f" - .claude-mpm/sessions/{session_id}.md (human-readable)")
|
|
64
|
+
print(f" - .claude-mpm/sessions/{session_id}.json (machine-readable)")
|
|
65
|
+
print(f" - .claude-mpm/sessions/{session_id}.yaml (config format)")
|
|
66
|
+
print(f"")
|
|
67
|
+
print(f"Quick resume:")
|
|
68
|
+
print(f" /mpm-resume")
|
|
69
|
+
print(f"")
|
|
70
|
+
print(f"View session context:")
|
|
71
|
+
print(f" cat .claude-mpm/sessions/LATEST-SESSION.txt")
|
|
72
|
+
print(f" cat .claude-mpm/sessions/{session_id}.md")
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## What Gets Saved
|
|
76
|
+
|
|
77
|
+
**Session State:**
|
|
78
|
+
- Session ID and timestamp
|
|
79
|
+
- Current working directory
|
|
80
|
+
- Git branch, recent commits, and file status
|
|
81
|
+
- Primary task and current phase
|
|
82
|
+
- Context message (if provided)
|
|
83
|
+
|
|
84
|
+
**Resume Instructions:**
|
|
85
|
+
- Quick-start commands
|
|
86
|
+
- Validation commands
|
|
87
|
+
- Files to review
|
|
88
|
+
|
|
89
|
+
**File Formats:**
|
|
90
|
+
- `.md` - Human-readable markdown (for reading)
|
|
91
|
+
- `.json` - Machine-readable (for tooling)
|
|
92
|
+
- `.yaml` - Human-readable config (for editing)
|
|
93
|
+
|
|
94
|
+
## Session File Location
|
|
95
|
+
|
|
96
|
+
All session files are stored in:
|
|
97
|
+
```
|
|
98
|
+
.claude-mpm/sessions/
|
|
99
|
+
├── LATEST-SESSION.txt # Pointer to most recent session
|
|
100
|
+
├── session-YYYYMMDD-HHMMSS.md
|
|
101
|
+
├── session-YYYYMMDD-HHMMSS.json
|
|
102
|
+
└── session-YYYYMMDD-HHMMSS.yaml
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Token Budget
|
|
106
|
+
|
|
107
|
+
**Token usage:** ~5-10k tokens to execute (2-5% of context budget)
|
|
108
|
+
|
|
109
|
+
**Benefit:** Saves all remaining context for future resume, allowing you to:
|
|
110
|
+
- Context switch to urgent tasks
|
|
111
|
+
- Take a break and resume later
|
|
112
|
+
- Archive current work state before major changes
|
|
113
|
+
|
|
114
|
+
## Resume Later
|
|
115
|
+
|
|
116
|
+
To resume this session:
|
|
117
|
+
```
|
|
118
|
+
/mpm-resume
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Or manually:
|
|
122
|
+
```bash
|
|
123
|
+
cat .claude-mpm/sessions/LATEST-SESSION.txt
|
|
124
|
+
cat .claude-mpm/sessions/session-YYYYMMDD-HHMMSS.md
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Git Integration
|
|
128
|
+
|
|
129
|
+
If in a git repository, the session will be automatically committed with message:
|
|
130
|
+
```
|
|
131
|
+
session: pause at YYYY-MM-DD HH:MM:SS
|
|
132
|
+
|
|
133
|
+
Session ID: session-YYYYMMDD-HHMMSS
|
|
134
|
+
Context: [your optional message]
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Use Cases
|
|
138
|
+
|
|
139
|
+
**Context switching:**
|
|
140
|
+
```
|
|
141
|
+
/mpm-pause Switching to urgent production bug
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**End of work session:**
|
|
145
|
+
```
|
|
146
|
+
/mpm-pause Completed API refactor, ready for testing tomorrow
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**Before major changes:**
|
|
150
|
+
```
|
|
151
|
+
/mpm-pause Saving state before attempting risky refactor
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**When approaching context limit:**
|
|
155
|
+
```
|
|
156
|
+
/mpm-pause Hit 150k tokens, starting fresh session
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Related Commands
|
|
160
|
+
|
|
161
|
+
- `/mpm-resume` - Resume from most recent paused session
|
|
162
|
+
- `/mpm-init resume` - Alternative resume command
|
|
163
|
+
- See `docs/features/session-auto-resume.md` for auto-pause behavior
|
|
164
|
+
|
|
165
|
+
## Notes
|
|
166
|
+
|
|
167
|
+
- Session files are project-local (not synced across machines)
|
|
168
|
+
- Git commit is optional (automatically skipped if not a repo)
|
|
169
|
+
- LATEST-SESSION.txt always points to most recent session
|
|
170
|
+
- Session format compatible with auto-pause feature (70% context trigger)
|
claude_mpm/skills/registry.py
CHANGED
|
@@ -14,35 +14,144 @@ logger = get_logger(__name__)
|
|
|
14
14
|
|
|
15
15
|
@dataclass
|
|
16
16
|
class Skill:
|
|
17
|
-
"""Represents a skill that can be used by agents.
|
|
18
|
-
|
|
17
|
+
"""Represents a skill that can be used by agents.
|
|
18
|
+
|
|
19
|
+
Supports agentskills.io specification with backward compatibility for legacy claude-mpm format.
|
|
20
|
+
|
|
21
|
+
Spec fields (agentskills.io):
|
|
22
|
+
- name: Required, 1-64 chars, lowercase alphanumeric + hyphens
|
|
23
|
+
- description: Required, 1-1024 chars
|
|
24
|
+
- license: Optional, license name or reference
|
|
25
|
+
- compatibility: Optional, max 500 chars, environment requirements
|
|
26
|
+
- metadata: Optional, key-value mapping for arbitrary data
|
|
27
|
+
- allowed_tools: Optional, list of pre-approved tools
|
|
28
|
+
|
|
29
|
+
Internal fields:
|
|
30
|
+
- path: Path to skill file
|
|
31
|
+
- content: Skill content (markdown)
|
|
32
|
+
- source: Origin of skill ('bundled', 'user', 'project', 'pm')
|
|
33
|
+
- version: Skill version (from metadata.version or top-level)
|
|
34
|
+
- skill_id: Internal ID (defaults to name)
|
|
35
|
+
- agent_types: Which agent types can use this skill
|
|
36
|
+
- updated_at: Last update timestamp (from metadata.updated)
|
|
37
|
+
- tags: Tags for discovery (from metadata.tags or top-level)
|
|
38
|
+
|
|
39
|
+
Claude-mpm extensions (preserved for backward compat):
|
|
40
|
+
- category: Skill category for organization
|
|
41
|
+
- toolchain: Associated toolchain (python, javascript, etc.)
|
|
42
|
+
- progressive_disclosure: Progressive disclosure configuration
|
|
43
|
+
- user_invocable: Whether skill can be manually invoked
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
# Core spec fields (agentskills.io)
|
|
19
47
|
name: str
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
48
|
+
description: str
|
|
49
|
+
license: Optional[str] = None
|
|
50
|
+
compatibility: Optional[str] = None
|
|
51
|
+
metadata: Dict[str, Any] = None
|
|
52
|
+
allowed_tools: List[str] = None
|
|
53
|
+
|
|
54
|
+
# Internal fields (not in frontmatter spec)
|
|
55
|
+
path: Path = None
|
|
56
|
+
content: str = ""
|
|
57
|
+
source: str = "bundled" # 'bundled', 'user', 'project', 'pm'
|
|
58
|
+
|
|
59
|
+
# Derived fields (from metadata or fallback)
|
|
60
|
+
version: str = "0.1.0" # From metadata.version or top-level
|
|
61
|
+
skill_id: str = "" # Internal ID (defaults to name)
|
|
30
62
|
agent_types: List[str] = None # Which agent types can use this skill
|
|
63
|
+
updated_at: Optional[str] = None # From metadata.updated
|
|
64
|
+
tags: List[str] = None # From metadata.tags or top-level
|
|
31
65
|
|
|
32
|
-
#
|
|
33
|
-
|
|
34
|
-
|
|
66
|
+
# Claude-mpm extensions (preserved for backward compat)
|
|
67
|
+
category: Optional[str] = None
|
|
68
|
+
toolchain: Optional[str] = None
|
|
69
|
+
progressive_disclosure: Optional[Dict[str, Any]] = None
|
|
70
|
+
user_invocable: bool = False
|
|
35
71
|
|
|
36
72
|
def __post_init__(self):
|
|
37
73
|
"""Initialize default values if not provided."""
|
|
74
|
+
if self.metadata is None:
|
|
75
|
+
self.metadata = {}
|
|
38
76
|
if self.agent_types is None:
|
|
39
77
|
self.agent_types = []
|
|
40
78
|
if self.tags is None:
|
|
41
79
|
self.tags = []
|
|
80
|
+
if self.allowed_tools is None:
|
|
81
|
+
self.allowed_tools = []
|
|
42
82
|
if not self.skill_id:
|
|
43
83
|
self.skill_id = self.name
|
|
44
84
|
|
|
45
85
|
|
|
86
|
+
def validate_agentskills_spec(skill: Skill) -> tuple[bool, List[str]]:
|
|
87
|
+
"""Validate skill against agentskills.io specification.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
skill: Skill object to validate
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Tuple of (is_valid, list_of_warnings)
|
|
94
|
+
- is_valid: True if skill meets spec requirements
|
|
95
|
+
- warnings: List of warning messages for spec violations
|
|
96
|
+
"""
|
|
97
|
+
warnings = []
|
|
98
|
+
|
|
99
|
+
# Validate name (required field)
|
|
100
|
+
if not skill.name:
|
|
101
|
+
warnings.append("Missing required field: name")
|
|
102
|
+
return False, warnings
|
|
103
|
+
|
|
104
|
+
# Validate name format: lowercase alphanumeric + hyphens, no leading/trailing hyphens
|
|
105
|
+
if not re.match(r"^[a-z0-9]+(-[a-z0-9]+)*$", skill.name):
|
|
106
|
+
warnings.append(
|
|
107
|
+
f"Invalid name format: '{skill.name}' (must be lowercase alphanumeric with hyphens, "
|
|
108
|
+
"no leading/trailing hyphens, no consecutive hyphens)"
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# Validate name length (max 64 chars)
|
|
112
|
+
if len(skill.name) > 64:
|
|
113
|
+
warnings.append(
|
|
114
|
+
f"Name too long: {len(skill.name)} chars (max 64 per agentskills.io spec)"
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# Validate description (required field)
|
|
118
|
+
if not skill.description:
|
|
119
|
+
warnings.append("Missing required field: description")
|
|
120
|
+
return False, warnings
|
|
121
|
+
|
|
122
|
+
# Validate description length (1-1024 chars)
|
|
123
|
+
desc_len = len(skill.description)
|
|
124
|
+
if desc_len < 1 or desc_len > 1024:
|
|
125
|
+
warnings.append(
|
|
126
|
+
f"Description length {desc_len} chars is outside spec range (1-1024 chars)"
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
# Validate compatibility length (max 500 chars)
|
|
130
|
+
if skill.compatibility and len(skill.compatibility) > 500:
|
|
131
|
+
warnings.append(
|
|
132
|
+
f"Compatibility field too long: {len(skill.compatibility)} chars (max 500)"
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# Validate metadata is dict
|
|
136
|
+
if skill.metadata is not None and not isinstance(skill.metadata, dict):
|
|
137
|
+
warnings.append("Metadata must be a key-value mapping (dict)")
|
|
138
|
+
|
|
139
|
+
# Check for spec-compliant metadata structure
|
|
140
|
+
spec_metadata_fields = ["version", "author", "updated", "tags"]
|
|
141
|
+
for field in spec_metadata_fields:
|
|
142
|
+
if hasattr(skill, field):
|
|
143
|
+
field_value = getattr(skill, field)
|
|
144
|
+
if field_value and field not in skill.metadata:
|
|
145
|
+
warnings.append(
|
|
146
|
+
f"Field '{field}' should be in metadata block per agentskills.io spec "
|
|
147
|
+
f"(found as top-level field)"
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
# Valid if no errors (only warnings allowed)
|
|
151
|
+
is_valid = len(warnings) == 0 or all("should be in metadata" in w for w in warnings)
|
|
152
|
+
return is_valid, warnings
|
|
153
|
+
|
|
154
|
+
|
|
46
155
|
class SkillsRegistry:
|
|
47
156
|
"""Registry for managing skills across all tiers."""
|
|
48
157
|
|
|
@@ -54,7 +163,10 @@ class SkillsRegistry:
|
|
|
54
163
|
self._load_project_skills()
|
|
55
164
|
|
|
56
165
|
def _parse_skill_frontmatter(self, content: str) -> Dict[str, Any]:
|
|
57
|
-
"""Parse YAML frontmatter from skill markdown file.
|
|
166
|
+
"""Parse YAML frontmatter from skill markdown file with spec validation.
|
|
167
|
+
|
|
168
|
+
Supports both agentskills.io spec format and legacy claude-mpm format
|
|
169
|
+
with automatic migration.
|
|
58
170
|
|
|
59
171
|
Returns:
|
|
60
172
|
Dict with frontmatter fields or empty dict if no frontmatter
|
|
@@ -70,11 +182,150 @@ class SkillsRegistry:
|
|
|
70
182
|
|
|
71
183
|
try:
|
|
72
184
|
frontmatter = yaml.safe_load(match.group(1))
|
|
73
|
-
|
|
185
|
+
if not frontmatter:
|
|
186
|
+
return {}
|
|
187
|
+
|
|
188
|
+
# Apply backward compatibility migration
|
|
189
|
+
return self._apply_backward_compatibility(frontmatter)
|
|
74
190
|
except yaml.YAMLError as e:
|
|
75
191
|
logger.warning(f"Failed to parse skill frontmatter: {e}")
|
|
76
192
|
return {}
|
|
77
193
|
|
|
194
|
+
def _apply_backward_compatibility(
|
|
195
|
+
self, frontmatter: Dict[str, Any]
|
|
196
|
+
) -> Dict[str, Any]:
|
|
197
|
+
"""Apply backward compatibility transformations to legacy frontmatter.
|
|
198
|
+
|
|
199
|
+
Auto-migrates legacy claude-mpm fields to agentskills.io spec format:
|
|
200
|
+
- version → metadata.version
|
|
201
|
+
- author → metadata.author
|
|
202
|
+
- updated → metadata.updated
|
|
203
|
+
- tags → metadata.tags (if not already present)
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
frontmatter: Parsed frontmatter dict
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
Transformed frontmatter with spec-compliant structure
|
|
210
|
+
"""
|
|
211
|
+
# Initialize metadata if not present
|
|
212
|
+
if "metadata" not in frontmatter:
|
|
213
|
+
frontmatter["metadata"] = {}
|
|
214
|
+
|
|
215
|
+
metadata = frontmatter["metadata"]
|
|
216
|
+
|
|
217
|
+
# Auto-migrate version (top-level → metadata.version)
|
|
218
|
+
if "version" in frontmatter and "version" not in metadata:
|
|
219
|
+
metadata["version"] = frontmatter["version"]
|
|
220
|
+
logger.debug(
|
|
221
|
+
f"Auto-migrated 'version' to metadata for skill '{frontmatter.get('name', 'unknown')}'"
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
# Auto-migrate author (top-level → metadata.author)
|
|
225
|
+
if "author" in frontmatter and "author" not in metadata:
|
|
226
|
+
metadata["author"] = frontmatter["author"]
|
|
227
|
+
logger.debug(
|
|
228
|
+
f"Auto-migrated 'author' to metadata for skill '{frontmatter.get('name', 'unknown')}'"
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
# Auto-migrate updated (top-level → metadata.updated)
|
|
232
|
+
if "updated" in frontmatter and "updated" not in metadata:
|
|
233
|
+
metadata["updated"] = frontmatter["updated"]
|
|
234
|
+
logger.debug(
|
|
235
|
+
f"Auto-migrated 'updated' to metadata for skill '{frontmatter.get('name', 'unknown')}'"
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
# Auto-migrate tags (top-level → metadata.tags)
|
|
239
|
+
if "tags" in frontmatter and "tags" not in metadata:
|
|
240
|
+
metadata["tags"] = frontmatter["tags"]
|
|
241
|
+
logger.debug(
|
|
242
|
+
f"Auto-migrated 'tags' to metadata for skill '{frontmatter.get('name', 'unknown')}'"
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
# Parse allowed-tools from space-delimited string to list
|
|
246
|
+
if "allowed-tools" in frontmatter:
|
|
247
|
+
allowed_tools = frontmatter["allowed-tools"]
|
|
248
|
+
if isinstance(allowed_tools, str):
|
|
249
|
+
frontmatter["allowed-tools"] = allowed_tools.split()
|
|
250
|
+
|
|
251
|
+
# Set default compatibility for claude-code if not present
|
|
252
|
+
if "compatibility" not in frontmatter:
|
|
253
|
+
frontmatter["compatibility"] = "claude-code"
|
|
254
|
+
|
|
255
|
+
return frontmatter
|
|
256
|
+
|
|
257
|
+
def _create_skill_from_frontmatter(
|
|
258
|
+
self, frontmatter: Dict[str, Any], path: Path, content: str, source: str
|
|
259
|
+
) -> Optional[Skill]:
|
|
260
|
+
"""Create Skill object from frontmatter with spec compliance.
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
frontmatter: Parsed and migrated frontmatter dict
|
|
264
|
+
path: Path to skill file
|
|
265
|
+
content: Full skill content
|
|
266
|
+
source: Source type ('bundled', 'user', 'project', 'pm')
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
Skill object or None if required fields missing
|
|
270
|
+
"""
|
|
271
|
+
# Extract spec fields (required)
|
|
272
|
+
name = frontmatter.get("name")
|
|
273
|
+
description = frontmatter.get("description", "")
|
|
274
|
+
|
|
275
|
+
# If name not in frontmatter, use filename stem
|
|
276
|
+
if not name:
|
|
277
|
+
name = path.stem
|
|
278
|
+
|
|
279
|
+
# If description not in frontmatter, extract from content
|
|
280
|
+
if not description:
|
|
281
|
+
description = self._extract_description(content)
|
|
282
|
+
|
|
283
|
+
# Validate required fields
|
|
284
|
+
if not name or not description:
|
|
285
|
+
logger.warning(
|
|
286
|
+
f"Skipping skill at {path}: missing required field (name or description)"
|
|
287
|
+
)
|
|
288
|
+
return None
|
|
289
|
+
|
|
290
|
+
# Extract spec fields (optional)
|
|
291
|
+
license_field = frontmatter.get("license")
|
|
292
|
+
compatibility = frontmatter.get("compatibility", "claude-code")
|
|
293
|
+
metadata = frontmatter.get("metadata", {})
|
|
294
|
+
allowed_tools = frontmatter.get("allowed-tools", [])
|
|
295
|
+
|
|
296
|
+
# Extract derived fields from metadata or top-level
|
|
297
|
+
version = frontmatter.get("version") or metadata.get("version", "0.1.0")
|
|
298
|
+
skill_id = frontmatter.get("skill_id", name)
|
|
299
|
+
updated_at = frontmatter.get("updated_at") or metadata.get("updated")
|
|
300
|
+
tags = frontmatter.get("tags", []) or metadata.get("tags", [])
|
|
301
|
+
|
|
302
|
+
# Extract claude-mpm extensions
|
|
303
|
+
category = frontmatter.get("category")
|
|
304
|
+
toolchain = frontmatter.get("toolchain")
|
|
305
|
+
progressive_disclosure = frontmatter.get("progressive_disclosure")
|
|
306
|
+
user_invocable = frontmatter.get("user-invocable", False)
|
|
307
|
+
|
|
308
|
+
# Create skill object
|
|
309
|
+
return Skill(
|
|
310
|
+
name=name,
|
|
311
|
+
description=description,
|
|
312
|
+
license=license_field,
|
|
313
|
+
compatibility=compatibility,
|
|
314
|
+
metadata=metadata,
|
|
315
|
+
allowed_tools=allowed_tools,
|
|
316
|
+
path=path,
|
|
317
|
+
content=content,
|
|
318
|
+
source=source,
|
|
319
|
+
version=version,
|
|
320
|
+
skill_id=skill_id,
|
|
321
|
+
updated_at=updated_at,
|
|
322
|
+
tags=tags,
|
|
323
|
+
category=category,
|
|
324
|
+
toolchain=toolchain,
|
|
325
|
+
progressive_disclosure=progressive_disclosure,
|
|
326
|
+
user_invocable=user_invocable,
|
|
327
|
+
)
|
|
328
|
+
|
|
78
329
|
def _load_bundled_skills(self):
|
|
79
330
|
"""Load skills bundled with MPM."""
|
|
80
331
|
bundled_dir = Path(__file__).parent / "bundled"
|
|
@@ -88,32 +339,16 @@ class SkillsRegistry:
|
|
|
88
339
|
skill_name = skill_file.stem
|
|
89
340
|
content = skill_file.read_text(encoding="utf-8")
|
|
90
341
|
|
|
91
|
-
# Parse frontmatter
|
|
342
|
+
# Parse frontmatter with backward compatibility
|
|
92
343
|
frontmatter = self._parse_skill_frontmatter(content)
|
|
93
344
|
|
|
94
|
-
#
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
updated_at = frontmatter.get("updated_at")
|
|
98
|
-
tags = frontmatter.get("tags", [])
|
|
99
|
-
|
|
100
|
-
# Extract description (from frontmatter or fallback to content parsing)
|
|
101
|
-
description = frontmatter.get("description", "")
|
|
102
|
-
if not description:
|
|
103
|
-
description = self._extract_description(content)
|
|
104
|
-
|
|
105
|
-
self.skills[skill_name] = Skill(
|
|
106
|
-
name=skill_name,
|
|
107
|
-
path=skill_file,
|
|
108
|
-
content=content,
|
|
109
|
-
source="bundled",
|
|
110
|
-
version=version,
|
|
111
|
-
skill_id=skill_id,
|
|
112
|
-
description=description,
|
|
113
|
-
updated_at=updated_at,
|
|
114
|
-
tags=tags,
|
|
345
|
+
# Create skill from frontmatter
|
|
346
|
+
skill = self._create_skill_from_frontmatter(
|
|
347
|
+
frontmatter, skill_file, content, "bundled"
|
|
115
348
|
)
|
|
116
|
-
|
|
349
|
+
if skill:
|
|
350
|
+
self.skills[skill_name] = skill
|
|
351
|
+
skill_count += 1
|
|
117
352
|
except Exception as e:
|
|
118
353
|
logger.error(f"Error loading bundled skill {skill_file}: {e}")
|
|
119
354
|
|
|
@@ -130,36 +365,20 @@ class SkillsRegistry:
|
|
|
130
365
|
for skill_file in user_skills_dir.glob("*.md"):
|
|
131
366
|
try:
|
|
132
367
|
skill_name = skill_file.stem
|
|
133
|
-
# User skills override bundled skills
|
|
134
368
|
content = skill_file.read_text(encoding="utf-8")
|
|
135
369
|
|
|
136
|
-
# Parse frontmatter
|
|
370
|
+
# Parse frontmatter with backward compatibility
|
|
137
371
|
frontmatter = self._parse_skill_frontmatter(content)
|
|
138
372
|
|
|
139
|
-
#
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
updated_at = frontmatter.get("updated_at")
|
|
143
|
-
tags = frontmatter.get("tags", [])
|
|
144
|
-
|
|
145
|
-
# Extract description (from frontmatter or fallback to content parsing)
|
|
146
|
-
description = frontmatter.get("description", "")
|
|
147
|
-
if not description:
|
|
148
|
-
description = self._extract_description(content)
|
|
149
|
-
|
|
150
|
-
self.skills[skill_name] = Skill(
|
|
151
|
-
name=skill_name,
|
|
152
|
-
path=skill_file,
|
|
153
|
-
content=content,
|
|
154
|
-
source="user",
|
|
155
|
-
version=version,
|
|
156
|
-
skill_id=skill_id,
|
|
157
|
-
description=description,
|
|
158
|
-
updated_at=updated_at,
|
|
159
|
-
tags=tags,
|
|
373
|
+
# Create skill from frontmatter
|
|
374
|
+
skill = self._create_skill_from_frontmatter(
|
|
375
|
+
frontmatter, skill_file, content, "user"
|
|
160
376
|
)
|
|
161
|
-
|
|
162
|
-
|
|
377
|
+
if skill:
|
|
378
|
+
# User skills override bundled skills
|
|
379
|
+
self.skills[skill_name] = skill
|
|
380
|
+
skill_count += 1
|
|
381
|
+
logger.debug(f"User skill '{skill_name}' overrides bundled version")
|
|
163
382
|
except Exception as e:
|
|
164
383
|
logger.error(f"Error loading user skill {skill_file}: {e}")
|
|
165
384
|
|
|
@@ -177,36 +396,22 @@ class SkillsRegistry:
|
|
|
177
396
|
for skill_file in project_skills_dir.glob("*.md"):
|
|
178
397
|
try:
|
|
179
398
|
skill_name = skill_file.stem
|
|
180
|
-
# Project skills override both user and bundled skills
|
|
181
399
|
content = skill_file.read_text(encoding="utf-8")
|
|
182
400
|
|
|
183
|
-
# Parse frontmatter
|
|
401
|
+
# Parse frontmatter with backward compatibility
|
|
184
402
|
frontmatter = self._parse_skill_frontmatter(content)
|
|
185
403
|
|
|
186
|
-
#
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
updated_at = frontmatter.get("updated_at")
|
|
190
|
-
tags = frontmatter.get("tags", [])
|
|
191
|
-
|
|
192
|
-
# Extract description (from frontmatter or fallback to content parsing)
|
|
193
|
-
description = frontmatter.get("description", "")
|
|
194
|
-
if not description:
|
|
195
|
-
description = self._extract_description(content)
|
|
196
|
-
|
|
197
|
-
self.skills[skill_name] = Skill(
|
|
198
|
-
name=skill_name,
|
|
199
|
-
path=skill_file,
|
|
200
|
-
content=content,
|
|
201
|
-
source="project",
|
|
202
|
-
version=version,
|
|
203
|
-
skill_id=skill_id,
|
|
204
|
-
description=description,
|
|
205
|
-
updated_at=updated_at,
|
|
206
|
-
tags=tags,
|
|
404
|
+
# Create skill from frontmatter
|
|
405
|
+
skill = self._create_skill_from_frontmatter(
|
|
406
|
+
frontmatter, skill_file, content, "project"
|
|
207
407
|
)
|
|
208
|
-
|
|
209
|
-
|
|
408
|
+
if skill:
|
|
409
|
+
# Project skills override both user and bundled skills
|
|
410
|
+
self.skills[skill_name] = skill
|
|
411
|
+
skill_count += 1
|
|
412
|
+
logger.debug(
|
|
413
|
+
f"Project skill '{skill_name}' overrides other versions"
|
|
414
|
+
)
|
|
210
415
|
except Exception as e:
|
|
211
416
|
logger.error(f"Error loading project skill {skill_file}: {e}")
|
|
212
417
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: claude-mpm
|
|
3
|
-
Version: 5.6.
|
|
3
|
+
Version: 5.6.76
|
|
4
4
|
Summary: Claude Multi-Agent Project Manager - Orchestrate Claude with agent delegation and ticket tracking
|
|
5
5
|
Author-email: Bob Matsuoka <bob@matsuoka.com>
|
|
6
6
|
Maintainer: Claude MPM Team
|
|
@@ -23,7 +23,7 @@ License-File: LICENSE
|
|
|
23
23
|
License-File: LICENSE-FAQ.md
|
|
24
24
|
Requires-Dist: ai-trackdown-pytools>=1.4.0
|
|
25
25
|
Requires-Dist: pyyaml>=6.0
|
|
26
|
-
Requires-Dist: python-dotenv>=0.
|
|
26
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
27
27
|
Requires-Dist: click>=8.0.0
|
|
28
28
|
Requires-Dist: pexpect>=4.8.0
|
|
29
29
|
Requires-Dist: psutil>=5.9.0
|
|
@@ -51,6 +51,10 @@ Requires-Dist: pyee>=13.0.0
|
|
|
51
51
|
Requires-Dist: pathspec>=0.11.0
|
|
52
52
|
Requires-Dist: fastapi>=0.100.0
|
|
53
53
|
Requires-Dist: uvicorn>=0.20.0
|
|
54
|
+
Requires-Dist: httpx>=0.24.0
|
|
55
|
+
Requires-Dist: keyring>=24.0.0
|
|
56
|
+
Requires-Dist: cryptography>=41.0.0
|
|
57
|
+
Requires-Dist: mcp>=1.0.0
|
|
54
58
|
Provides-Extra: mcp
|
|
55
59
|
Requires-Dist: mcp>=0.1.0; extra == "mcp"
|
|
56
60
|
Requires-Dist: mcp-vector-search>=0.1.0; extra == "mcp"
|
|
@@ -143,9 +147,11 @@ Claude MPM transforms Claude Code into a **multi-agent orchestration platform**
|
|
|
143
147
|
|
|
144
148
|
### Prerequisites
|
|
145
149
|
|
|
146
|
-
1. **Python 3.
|
|
150
|
+
1. **Python 3.11+** (required - older versions will install outdated claude-mpm)
|
|
147
151
|
2. **Claude Code CLI v2.1.3+** (required!)
|
|
148
152
|
|
|
153
|
+
> ⚠️ **Python Version Note**: Claude MPM requires Python 3.11 or higher. If you have Python 3.9 or 3.10, you'll get an old version (4.x) that lacks current features. Check with `python3 --version` before installing.
|
|
154
|
+
|
|
149
155
|
```bash
|
|
150
156
|
# Verify Claude Code is installed
|
|
151
157
|
claude --version
|
|
@@ -253,6 +259,25 @@ ls ~/.claude/agents/ # Should show 47+ agents
|
|
|
253
259
|
|
|
254
260
|
[→ Learn more: MCP Gateway](docs/developer/13-mcp-gateway/README.md)
|
|
255
261
|
|
|
262
|
+
### 🔐 OAuth & Google Workspace Integration
|
|
263
|
+
- **Browser-Based OAuth** for secure authentication with MCP services
|
|
264
|
+
- **Google Workspace MCP** built-in server for Gmail, Calendar, and Drive
|
|
265
|
+
- **Encrypted Token Storage** using Fernet encryption with system keychain
|
|
266
|
+
- **Automatic Token Refresh** handles expiration seamlessly
|
|
267
|
+
|
|
268
|
+
```bash
|
|
269
|
+
# Set up Google Workspace OAuth
|
|
270
|
+
claude-mpm oauth setup workspace-mcp
|
|
271
|
+
|
|
272
|
+
# Check token status
|
|
273
|
+
claude-mpm oauth status workspace-mcp
|
|
274
|
+
|
|
275
|
+
# List OAuth-capable services
|
|
276
|
+
claude-mpm oauth list
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
[→ Learn more: OAuth Setup Guide](docs/guides/oauth-setup.md)
|
|
280
|
+
|
|
256
281
|
### ⚡ Performance & Security
|
|
257
282
|
- **Simplified Architecture** with ~3,700 lines removed for better performance
|
|
258
283
|
- **Enhanced Security** with comprehensive input validation
|