mirror of
https://github.com/bobmatnyc/claude-mpm-skills.git
synced 2026-09-14 19:59:53 +08:00
ead26503ce
- init_skill.py: Initialize new skills with proper structure (interactive + CLI modes) - package_skill.py: Validate and package skills for deployment - Update scripts/README.md with comprehensive documentation Version: 1.0.0 → 1.0.1 (PATCH) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
617 lines
17 KiB
Python
Executable File
617 lines
17 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Initialize a new skill with proper directory structure and templates.
|
|
|
|
This script creates a new skill following the claude-mpm-skills conventions:
|
|
1. Creates directory structure: {category}/{toolchain}/{subcategory}/{framework}/
|
|
2. Generates SKILL.md with proper frontmatter and progressive_disclosure
|
|
3. Generates metadata.json with proper structure
|
|
4. Validates the skill name and path
|
|
|
|
Usage:
|
|
# Interactive mode
|
|
python scripts/init_skill.py
|
|
|
|
# With arguments
|
|
python scripts/init_skill.py --category toolchains --toolchain python --framework fastapi
|
|
python scripts/init_skill.py --category universal --subcategory testing --name tdd-patterns
|
|
|
|
# Quick mode with full path
|
|
python scripts/init_skill.py --path toolchains/python/frameworks/fastapi
|
|
|
|
Examples:
|
|
# Create a new framework skill
|
|
python scripts/init_skill.py --category toolchains --toolchain python --subcategory frameworks --framework django
|
|
|
|
# Create a universal skill
|
|
python scripts/init_skill.py --category universal --subcategory debugging --name systematic-debugging
|
|
|
|
# Create with description
|
|
python scripts/init_skill.py --path toolchains/rust/testing/integration --description "Rust integration testing patterns"
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
|
|
# Template for SKILL.md
|
|
SKILL_MD_TEMPLATE = """---
|
|
name: {name}
|
|
description: "{description}"
|
|
---
|
|
|
|
# {title}
|
|
|
|
---
|
|
progressive_disclosure:
|
|
entry_point:
|
|
summary: "{summary}"
|
|
when_to_use:
|
|
- "{use_case_1}"
|
|
- "{use_case_2}"
|
|
- "{use_case_3}"
|
|
quick_start:
|
|
- "{quick_start_1}"
|
|
- "{quick_start_2}"
|
|
- "{quick_start_3}"
|
|
token_estimate:
|
|
entry: 75-100
|
|
full: 3000-5000
|
|
---
|
|
|
|
## Overview
|
|
|
|
{overview_placeholder}
|
|
|
|
## Core Concepts
|
|
|
|
### Concept 1
|
|
|
|
[Describe the first core concept]
|
|
|
|
### Concept 2
|
|
|
|
[Describe the second core concept]
|
|
|
|
## Implementation Patterns
|
|
|
|
### Pattern 1: Basic Usage
|
|
|
|
```{code_lang}
|
|
# Example code here
|
|
```
|
|
|
|
### Pattern 2: Advanced Usage
|
|
|
|
```{code_lang}
|
|
# Advanced example code here
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
1. **Practice 1**: [Description]
|
|
2. **Practice 2**: [Description]
|
|
3. **Practice 3**: [Description]
|
|
|
|
## Anti-Patterns
|
|
|
|
- **Anti-pattern 1**: [What to avoid and why]
|
|
- **Anti-pattern 2**: [What to avoid and why]
|
|
|
|
## Decision Trees
|
|
|
|
### When to Use This Skill
|
|
|
|
```
|
|
Is your use case X?
|
|
├── Yes → Use Pattern A
|
|
└── No
|
|
├── Is it Y? → Use Pattern B
|
|
└── Otherwise → Consider Alternative Z
|
|
```
|
|
|
|
## Examples
|
|
|
|
### Example 1: [Title]
|
|
|
|
```{code_lang}
|
|
# Complete working example
|
|
```
|
|
|
|
### Example 2: [Title]
|
|
|
|
```{code_lang}
|
|
# Another complete example
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### Common Issue 1
|
|
|
|
**Problem**: [Description]
|
|
**Solution**: [How to fix]
|
|
|
|
### Common Issue 2
|
|
|
|
**Problem**: [Description]
|
|
**Solution**: [How to fix]
|
|
|
|
## Resources
|
|
|
|
- [Official Documentation](https://example.com)
|
|
- [Tutorial](https://example.com/tutorial)
|
|
- [Best Practices Guide](https://example.com/best-practices)
|
|
|
|
## Related Skills
|
|
|
|
- **related-skill-1**: [Brief description of relationship]
|
|
- **related-skill-2**: [Brief description of relationship]
|
|
"""
|
|
|
|
|
|
def get_code_language(toolchain: str) -> str:
|
|
"""Map toolchain to code language for syntax highlighting."""
|
|
language_map = {
|
|
"python": "python",
|
|
"javascript": "javascript",
|
|
"typescript": "typescript",
|
|
"rust": "rust",
|
|
"golang": "go",
|
|
"go": "go",
|
|
"java": "java",
|
|
"ruby": "ruby",
|
|
"php": "php",
|
|
"elixir": "elixir",
|
|
"swift": "swift",
|
|
"kotlin": "kotlin",
|
|
"csharp": "csharp",
|
|
"cpp": "cpp",
|
|
"c": "c",
|
|
}
|
|
return language_map.get(toolchain.lower(), "text")
|
|
|
|
|
|
def slugify(text: str) -> str:
|
|
"""Convert text to a URL-friendly slug."""
|
|
text = text.lower()
|
|
text = re.sub(r"[^\w\s-]", "", text)
|
|
text = re.sub(r"[-\s]+", "-", text).strip("-")
|
|
return text
|
|
|
|
|
|
def title_case(text: str) -> str:
|
|
"""Convert slug or text to title case."""
|
|
return " ".join(
|
|
word.capitalize() for word in text.replace("-", " ").replace("_", " ").split()
|
|
)
|
|
|
|
|
|
def create_metadata_json(
|
|
name: str,
|
|
category: str,
|
|
toolchain: Optional[str],
|
|
framework: Optional[str],
|
|
tags: list[str],
|
|
author: str = "Claude MPM",
|
|
) -> dict:
|
|
"""Create metadata.json content."""
|
|
metadata = {
|
|
"name": name,
|
|
"version": "1.0.0",
|
|
"category": category,
|
|
"toolchain": toolchain,
|
|
"framework": framework,
|
|
"tags": tags,
|
|
"entry_point_tokens": 85,
|
|
"full_tokens": 4000,
|
|
"related_skills": [],
|
|
"author": author,
|
|
"license": "MIT",
|
|
}
|
|
|
|
# Remove None values
|
|
return {k: v for k, v in metadata.items() if v is not None}
|
|
|
|
|
|
def create_skill_md(
|
|
name: str,
|
|
title: str,
|
|
description: str,
|
|
toolchain: Optional[str] = None,
|
|
framework: Optional[str] = None,
|
|
) -> str:
|
|
"""Create SKILL.md content from template."""
|
|
code_lang = get_code_language(toolchain) if toolchain else "text"
|
|
|
|
# Generate placeholder content
|
|
summary = description if description else f"{title} patterns and best practices"
|
|
use_case_1 = f"When working with {framework or toolchain or 'this technology'}"
|
|
use_case_2 = "When implementing common patterns"
|
|
use_case_3 = "When troubleshooting issues"
|
|
|
|
quick_start_1 = "Step 1: [Installation or setup]"
|
|
quick_start_2 = "Step 2: [Basic configuration]"
|
|
quick_start_3 = "Step 3: [First implementation]"
|
|
|
|
overview = f"[Provide an overview of {title}. Explain what it is, why it's useful, and key concepts.]"
|
|
|
|
return SKILL_MD_TEMPLATE.format(
|
|
name=name,
|
|
description=description or f"{title} skill",
|
|
title=title,
|
|
summary=summary,
|
|
use_case_1=use_case_1,
|
|
use_case_2=use_case_2,
|
|
use_case_3=use_case_3,
|
|
quick_start_1=quick_start_1,
|
|
quick_start_2=quick_start_2,
|
|
quick_start_3=quick_start_3,
|
|
overview_placeholder=overview,
|
|
code_lang=code_lang,
|
|
)
|
|
|
|
|
|
def parse_path(path: str) -> dict:
|
|
"""Parse a skill path into components."""
|
|
parts = path.strip("/").split("/")
|
|
|
|
result = {
|
|
"category": None,
|
|
"toolchain": None,
|
|
"subcategory": None,
|
|
"framework": None,
|
|
"name": None,
|
|
}
|
|
|
|
if not parts:
|
|
return result
|
|
|
|
# First part is category (toolchains, universal, examples)
|
|
result["category"] = parts[0]
|
|
|
|
if result["category"] == "toolchains" and len(parts) > 1:
|
|
result["toolchain"] = parts[1]
|
|
if len(parts) > 2:
|
|
result["subcategory"] = parts[2]
|
|
if len(parts) > 3:
|
|
result["framework"] = parts[3]
|
|
result["name"] = parts[3]
|
|
elif result["category"] == "universal" and len(parts) > 1:
|
|
result["subcategory"] = parts[1]
|
|
if len(parts) > 2:
|
|
result["name"] = parts[2]
|
|
elif result["category"] == "examples" and len(parts) > 1:
|
|
result["name"] = parts[1]
|
|
|
|
return result
|
|
|
|
|
|
def build_skill_path(
|
|
category: str,
|
|
toolchain: Optional[str] = None,
|
|
subcategory: Optional[str] = None,
|
|
framework: Optional[str] = None,
|
|
name: Optional[str] = None,
|
|
) -> Path:
|
|
"""Build the skill directory path from components."""
|
|
parts = [category]
|
|
|
|
if category == "toolchains":
|
|
if toolchain:
|
|
parts.append(toolchain)
|
|
if subcategory:
|
|
parts.append(subcategory)
|
|
if framework:
|
|
parts.append(framework)
|
|
elif category == "universal":
|
|
if subcategory:
|
|
parts.append(subcategory)
|
|
if name:
|
|
parts.append(name)
|
|
elif category == "examples":
|
|
if name:
|
|
parts.append(name)
|
|
|
|
return Path("/".join(parts))
|
|
|
|
|
|
def interactive_mode(repo_root: Path) -> dict:
|
|
"""Run interactive mode to gather skill parameters."""
|
|
print("\n🔧 Claude MPM Skill Initializer\n")
|
|
print("This wizard will help you create a new skill.\n")
|
|
|
|
# Category
|
|
print("Categories:")
|
|
print(" 1. toolchains - Language/framework specific skills")
|
|
print(" 2. universal - Cross-language patterns and practices")
|
|
print(" 3. examples - Example skills for reference")
|
|
category_choice = input("\nSelect category [1/2/3] (default: 1): ").strip() or "1"
|
|
category_map = {"1": "toolchains", "2": "universal", "3": "examples"}
|
|
category = category_map.get(category_choice, "toolchains")
|
|
|
|
toolchain = None
|
|
subcategory = None
|
|
framework = None
|
|
name = None
|
|
|
|
if category == "toolchains":
|
|
# Toolchain
|
|
print(
|
|
"\nCommon toolchains: python, javascript, typescript, rust, golang, java, php, ruby, elixir"
|
|
)
|
|
toolchain = input("Enter toolchain: ").strip().lower()
|
|
|
|
# Subcategory
|
|
print(
|
|
"\nCommon subcategories: frameworks, testing, tooling, data, async, validation, state, api, build"
|
|
)
|
|
subcategory = input("Enter subcategory: ").strip().lower()
|
|
|
|
# Framework/name
|
|
framework = input("Enter framework/skill name: ").strip().lower()
|
|
framework = slugify(framework)
|
|
name = framework
|
|
|
|
elif category == "universal":
|
|
# Subcategory
|
|
print(
|
|
"\nCommon subcategories: testing, debugging, architecture, collaboration, infrastructure, main"
|
|
)
|
|
subcategory = input("Enter subcategory: ").strip().lower()
|
|
|
|
# Name
|
|
name = input("Enter skill name: ").strip().lower()
|
|
name = slugify(name)
|
|
|
|
elif category == "examples":
|
|
name = input("Enter example name: ").strip().lower()
|
|
name = slugify(name)
|
|
|
|
# Description
|
|
print("\n")
|
|
description = input("Enter skill description (one line): ").strip()
|
|
|
|
# Tags
|
|
tags_input = input("Enter tags (comma-separated): ").strip()
|
|
tags = [t.strip().lower() for t in tags_input.split(",") if t.strip()]
|
|
|
|
return {
|
|
"category": category,
|
|
"toolchain": toolchain,
|
|
"subcategory": subcategory,
|
|
"framework": framework,
|
|
"name": name or framework,
|
|
"description": description,
|
|
"tags": tags,
|
|
}
|
|
|
|
|
|
def create_skill(
|
|
repo_root: Path,
|
|
category: str,
|
|
toolchain: Optional[str] = None,
|
|
subcategory: Optional[str] = None,
|
|
framework: Optional[str] = None,
|
|
name: Optional[str] = None,
|
|
description: str = "",
|
|
tags: Optional[list[str]] = None,
|
|
force: bool = False,
|
|
dry_run: bool = False,
|
|
) -> bool:
|
|
"""Create a new skill with all required files."""
|
|
|
|
# Determine skill name
|
|
skill_name = name or framework
|
|
if not skill_name:
|
|
print("Error: Skill name is required", file=sys.stderr)
|
|
return False
|
|
|
|
# Build path
|
|
skill_path = build_skill_path(category, toolchain, subcategory, framework, name)
|
|
full_path = repo_root / skill_path
|
|
|
|
# Check if already exists
|
|
if full_path.exists() and not force:
|
|
print(f"Error: Skill already exists at {skill_path}", file=sys.stderr)
|
|
print("Use --force to overwrite", file=sys.stderr)
|
|
return False
|
|
|
|
# Generate title
|
|
title = title_case(skill_name)
|
|
if framework and toolchain:
|
|
title = f"{title_case(framework)} ({title_case(toolchain)})"
|
|
|
|
# Generate tags if not provided
|
|
if not tags:
|
|
tags = []
|
|
if toolchain:
|
|
tags.append(toolchain)
|
|
if framework:
|
|
tags.append(framework)
|
|
if subcategory:
|
|
tags.append(subcategory)
|
|
|
|
# Create content
|
|
skill_md_content = create_skill_md(
|
|
name=skill_name,
|
|
title=title,
|
|
description=description,
|
|
toolchain=toolchain,
|
|
framework=framework,
|
|
)
|
|
|
|
metadata_content = create_metadata_json(
|
|
name=skill_name,
|
|
category="toolchain" if category == "toolchains" else category,
|
|
toolchain=toolchain,
|
|
framework=framework,
|
|
tags=tags,
|
|
)
|
|
|
|
if dry_run:
|
|
print(f"\n[DRY RUN] Would create skill at: {skill_path}")
|
|
print("\nDirectory structure:")
|
|
print(f" {skill_path}/")
|
|
print(f" SKILL.md ({len(skill_md_content)} chars)")
|
|
print(" metadata.json")
|
|
print("\nmetadata.json content:")
|
|
print(json.dumps(metadata_content, indent=2))
|
|
return True
|
|
|
|
# Create directory
|
|
full_path.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Write SKILL.md
|
|
skill_md_path = full_path / "SKILL.md"
|
|
skill_md_path.write_text(skill_md_content, encoding="utf-8")
|
|
|
|
# Write metadata.json
|
|
metadata_path = full_path / "metadata.json"
|
|
metadata_path.write_text(
|
|
json.dumps(metadata_content, indent=2, ensure_ascii=False) + "\n",
|
|
encoding="utf-8",
|
|
)
|
|
|
|
print("\n✅ Skill created successfully!")
|
|
print(f"\n📁 Location: {skill_path}/")
|
|
print(" - SKILL.md")
|
|
print(" - metadata.json")
|
|
print("\n📝 Next steps:")
|
|
print(f" 1. Edit {skill_path}/SKILL.md with actual content")
|
|
print(" 2. Update metadata.json with accurate token counts")
|
|
print(" 3. Add related_skills references")
|
|
print(" 4. Run: python scripts/generate_manifest.py --validate")
|
|
print(" 5. Deploy: ./scripts/flatten_skills.sh")
|
|
|
|
return True
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="Initialize a new skill with proper structure",
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
epilog=__doc__,
|
|
)
|
|
|
|
parser.add_argument(
|
|
"--path",
|
|
"-p",
|
|
type=str,
|
|
help="Full skill path (e.g., toolchains/python/frameworks/django)",
|
|
)
|
|
|
|
parser.add_argument(
|
|
"--category",
|
|
"-c",
|
|
choices=["toolchains", "universal", "examples"],
|
|
help="Skill category",
|
|
)
|
|
|
|
parser.add_argument(
|
|
"--toolchain", "-t", type=str, help="Toolchain (e.g., python, javascript, rust)"
|
|
)
|
|
|
|
parser.add_argument(
|
|
"--subcategory",
|
|
"-s",
|
|
type=str,
|
|
help="Subcategory (e.g., frameworks, testing, tooling)",
|
|
)
|
|
|
|
parser.add_argument("--framework", "-f", type=str, help="Framework or skill name")
|
|
|
|
parser.add_argument(
|
|
"--name", "-n", type=str, help="Skill name (for universal/examples)"
|
|
)
|
|
|
|
parser.add_argument(
|
|
"--description", "-d", type=str, default="", help="Skill description"
|
|
)
|
|
|
|
parser.add_argument("--tags", type=str, help="Comma-separated tags")
|
|
|
|
parser.add_argument("--force", action="store_true", help="Overwrite existing skill")
|
|
|
|
parser.add_argument(
|
|
"--dry-run", action="store_true", help="Preview without creating files"
|
|
)
|
|
|
|
parser.add_argument(
|
|
"--interactive", "-i", action="store_true", help="Run in interactive mode"
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
|
|
# Find repository root
|
|
script_dir = Path(__file__).parent
|
|
repo_root = script_dir.parent
|
|
|
|
# Validate repository
|
|
if (
|
|
not (repo_root / "toolchains").exists()
|
|
and not (repo_root / "universal").exists()
|
|
):
|
|
print(
|
|
f"Error: Not a valid claude-mpm-skills repository: {repo_root}",
|
|
file=sys.stderr,
|
|
)
|
|
sys.exit(1)
|
|
|
|
# Determine mode
|
|
if args.interactive or (not args.path and not args.category):
|
|
# Interactive mode
|
|
params = interactive_mode(repo_root)
|
|
success = create_skill(
|
|
repo_root=repo_root,
|
|
category=params["category"],
|
|
toolchain=params["toolchain"],
|
|
subcategory=params["subcategory"],
|
|
framework=params["framework"],
|
|
name=params["name"],
|
|
description=params["description"],
|
|
tags=params["tags"],
|
|
force=args.force,
|
|
dry_run=args.dry_run,
|
|
)
|
|
elif args.path:
|
|
# Path mode
|
|
parsed = parse_path(args.path)
|
|
tags = [t.strip() for t in args.tags.split(",")] if args.tags else None
|
|
success = create_skill(
|
|
repo_root=repo_root,
|
|
category=parsed["category"] or "toolchains",
|
|
toolchain=parsed["toolchain"],
|
|
subcategory=parsed["subcategory"],
|
|
framework=parsed["framework"],
|
|
name=parsed["name"] or args.name,
|
|
description=args.description,
|
|
tags=tags,
|
|
force=args.force,
|
|
dry_run=args.dry_run,
|
|
)
|
|
else:
|
|
# Argument mode
|
|
tags = [t.strip() for t in args.tags.split(",")] if args.tags else None
|
|
success = create_skill(
|
|
repo_root=repo_root,
|
|
category=args.category,
|
|
toolchain=args.toolchain,
|
|
subcategory=args.subcategory,
|
|
framework=args.framework,
|
|
name=args.name,
|
|
description=args.description,
|
|
tags=tags,
|
|
force=args.force,
|
|
dry_run=args.dry_run,
|
|
)
|
|
|
|
sys.exit(0 if success else 1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|