multica/skills/skill-creator/scripts/init_skill.py
Jiang Bohan f19e51da31 feat(skill-creator): add init_skill.py script for reliable skill creation
- Add init_skill.py that always creates skills in ~/.super-multica/skills/
- Restructure SKILL.md with step-by-step creation process
- Make script usage mandatory to prevent skills in wrong directories
- Support --description, --emoji, --tag, --resources options
2026-01-30 17:39:03 +08:00

248 lines
7.1 KiB
Python
Executable file

#!/usr/bin/env python3
"""
Skill Initializer - Creates a new skill in the managed skills directory
Usage:
init_skill.py <skill-name> [--resources scripts,references]
Examples:
init_skill.py my-translator
init_skill.py code-formatter --resources scripts
init_skill.py api-helper --resources scripts,references
The skill will be created at: ~/.super-multica/skills/<skill-name>/
"""
import argparse
import os
import re
import sys
from pathlib import Path
# Fixed output directory - always use managed skills directory
MANAGED_SKILLS_DIR = Path.home() / ".super-multica" / "skills"
MAX_SKILL_NAME_LENGTH = 64
ALLOWED_RESOURCES = {"scripts", "references"}
SKILL_TEMPLATE = """---
name: {skill_name}
description: {description}
version: 1.0.0
metadata:
emoji: "{emoji}"
tags:
- {tag}
---
## Instructions
{instructions}
"""
EXAMPLE_SCRIPT = '''#!/usr/bin/env python3
"""
Helper script for {skill_name}
Replace this with your actual implementation.
"""
def main():
print("Hello from {skill_name}!")
# TODO: Add your script logic here
if __name__ == "__main__":
main()
'''
EXAMPLE_REFERENCE = """# Reference Documentation for {skill_title}
Add detailed reference documentation here.
## Overview
[Describe what this reference covers]
## Details
[Add detailed information]
"""
def normalize_skill_name(skill_name: str) -> str:
"""Normalize a skill name to lowercase hyphen-case."""
normalized = skill_name.strip().lower()
normalized = re.sub(r"[^a-z0-9]+", "-", normalized)
normalized = normalized.strip("-")
normalized = re.sub(r"-{2,}", "-", normalized)
return normalized
def title_case_skill_name(skill_name: str) -> str:
"""Convert hyphenated skill name to Title Case for display."""
return " ".join(word.capitalize() for word in skill_name.split("-"))
def parse_resources(raw_resources: str) -> list[str]:
if not raw_resources:
return []
resources = [item.strip() for item in raw_resources.split(",") if item.strip()]
invalid = sorted({item for item in resources if item not in ALLOWED_RESOURCES})
if invalid:
allowed = ", ".join(sorted(ALLOWED_RESOURCES))
print(f"[ERROR] Unknown resource type(s): {', '.join(invalid)}")
print(f" Allowed: {allowed}")
sys.exit(1)
return list(dict.fromkeys(resources)) # dedupe while preserving order
def create_resource_dirs(skill_dir: Path, skill_name: str, skill_title: str, resources: list[str]):
for resource in resources:
resource_dir = skill_dir / resource
resource_dir.mkdir(exist_ok=True)
if resource == "scripts":
example_script = resource_dir / "helper.py"
example_script.write_text(EXAMPLE_SCRIPT.format(skill_name=skill_name))
example_script.chmod(0o755)
print(f" [OK] Created {resource}/helper.py")
elif resource == "references":
example_ref = resource_dir / "reference.md"
example_ref.write_text(EXAMPLE_REFERENCE.format(skill_title=skill_title))
print(f" [OK] Created {resource}/reference.md")
def init_skill(skill_name: str, description: str, emoji: str, tag: str,
instructions: str, resources: list[str]) -> Path | None:
"""
Initialize a new skill directory with SKILL.md.
Returns:
Path to created skill directory, or None if error
"""
# Ensure managed skills directory exists
MANAGED_SKILLS_DIR.mkdir(parents=True, exist_ok=True)
# Determine skill directory path
skill_dir = MANAGED_SKILLS_DIR / skill_name
# Check if directory already exists
if skill_dir.exists():
print(f"[ERROR] Skill directory already exists: {skill_dir}")
print(f" To edit, modify files directly in {skill_dir}")
print(f" To recreate, first run: rm -rf {skill_dir}")
return None
# Create skill directory
try:
skill_dir.mkdir(parents=True, exist_ok=False)
print(f"[OK] Created skill directory: {skill_dir}")
except Exception as e:
print(f"[ERROR] Error creating directory: {e}")
return None
# Create SKILL.md from template
skill_content = SKILL_TEMPLATE.format(
skill_name=skill_name,
description=description,
emoji=emoji,
tag=tag,
instructions=instructions
)
skill_md_path = skill_dir / "SKILL.md"
try:
skill_md_path.write_text(skill_content)
print(" [OK] Created SKILL.md")
except Exception as e:
print(f"[ERROR] Error creating SKILL.md: {e}")
return None
# Create resource directories if requested
if resources:
skill_title = title_case_skill_name(skill_name)
try:
create_resource_dirs(skill_dir, skill_name, skill_title, resources)
except Exception as e:
print(f"[ERROR] Error creating resource directories: {e}")
return None
# Print summary
print(f"\n[SUCCESS] Skill '{skill_name}' created at {skill_dir}")
print("\nThe skill is now active (hot-reload enabled).")
print("Test it by running: pnpm skills:cli list")
return skill_dir
def main():
parser = argparse.ArgumentParser(
description="Create a new skill in ~/.super-multica/skills/",
)
parser.add_argument("skill_name", help="Skill name (will be normalized to hyphen-case)")
parser.add_argument(
"--description", "-d",
default="[TODO: Add description]",
help="Skill description"
)
parser.add_argument(
"--emoji", "-e",
default="🔧",
help="Emoji for the skill (default: 🔧)"
)
parser.add_argument(
"--tag", "-t",
default="custom",
help="Primary tag for the skill (default: custom)"
)
parser.add_argument(
"--instructions", "-i",
default="[TODO: Add instructions for when and how to use this skill]",
help="Instructions content"
)
parser.add_argument(
"--resources", "-r",
default="",
help="Comma-separated list: scripts,references"
)
args = parser.parse_args()
raw_skill_name = args.skill_name
skill_name = normalize_skill_name(raw_skill_name)
if not skill_name:
print("[ERROR] Skill name must include at least one letter or digit.")
sys.exit(1)
if len(skill_name) > MAX_SKILL_NAME_LENGTH:
print(
f"[ERROR] Skill name '{skill_name}' is too long ({len(skill_name)} characters). "
f"Maximum is {MAX_SKILL_NAME_LENGTH} characters."
)
sys.exit(1)
if skill_name != raw_skill_name:
print(f"Note: Normalized skill name from '{raw_skill_name}' to '{skill_name}'")
resources = parse_resources(args.resources)
print(f"Creating skill: {skill_name}")
print(f" Location: {MANAGED_SKILLS_DIR / skill_name}")
if resources:
print(f" Resources: {', '.join(resources)}")
print()
result = init_skill(
skill_name=skill_name,
description=args.description,
emoji=args.emoji,
tag=args.tag,
instructions=args.instructions,
resources=resources
)
sys.exit(0 if result else 1)
if __name__ == "__main__":
main()