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
This commit is contained in:
parent
b2d281c29d
commit
f19e51da31
2 changed files with 349 additions and 178 deletions
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
name: Skill Creator
|
||||
description: Create, edit, and manage custom skills to extend agent capabilities
|
||||
version: 1.0.0
|
||||
description: Create, edit, and manage custom skills to extend agent capabilities. Use when the user asks to create a new skill, build a custom capability, or extend the agent's functionality.
|
||||
version: 1.1.0
|
||||
metadata:
|
||||
emoji: "🛠️"
|
||||
always: true
|
||||
|
|
@ -15,23 +15,78 @@ metadata:
|
|||
|
||||
You can create, edit, and manage skills to extend your own capabilities or help users build custom skills.
|
||||
|
||||
### Skills Directory
|
||||
## Skill Creation Process
|
||||
|
||||
Custom skills are stored in `~/.super-multica/skills/`. Each skill is a directory containing a `SKILL.md` file.
|
||||
**ALWAYS follow these steps in order when creating a new skill:**
|
||||
|
||||
```
|
||||
~/.super-multica/skills/
|
||||
├── my-skill/
|
||||
│ └── SKILL.md
|
||||
├── another-skill/
|
||||
│ ├── SKILL.md
|
||||
│ └── scripts/
|
||||
│ └── helper.py
|
||||
1. Understand what the skill should do
|
||||
2. Initialize the skill using `init_skill.py`
|
||||
3. Edit the generated SKILL.md
|
||||
4. Test the skill
|
||||
|
||||
### Step 1: Understand the Skill
|
||||
|
||||
Before creating, clarify:
|
||||
- What functionality should the skill provide?
|
||||
- When should it be triggered?
|
||||
- Does it need helper scripts?
|
||||
|
||||
### Step 2: Initialize the Skill
|
||||
|
||||
**CRITICAL: Always use the init_skill.py script to create skills.** This ensures the skill is created in the correct location (`~/.super-multica/skills/`).
|
||||
|
||||
```bash
|
||||
# Basic usage
|
||||
python3 ~/.super-multica/skills/skill-creator/scripts/init_skill.py <skill-name>
|
||||
|
||||
# With description and resources
|
||||
python3 ~/.super-multica/skills/skill-creator/scripts/init_skill.py my-skill \
|
||||
--description "What this skill does" \
|
||||
--resources scripts
|
||||
```
|
||||
|
||||
### SKILL.md Format
|
||||
**Script options:**
|
||||
- `--description, -d` - Skill description
|
||||
- `--emoji, -e` - Emoji for display (default: 🔧)
|
||||
- `--tag, -t` - Primary tag (default: custom)
|
||||
- `--resources, -r` - Create directories: `scripts`, `references`, or both
|
||||
|
||||
Every skill must have a `SKILL.md` file with YAML frontmatter and Markdown instructions:
|
||||
**Examples:**
|
||||
```bash
|
||||
# Simple skill
|
||||
python3 scripts/init_skill.py translator -d "Translate text between languages"
|
||||
|
||||
# Skill with helper script
|
||||
python3 scripts/init_skill.py pdf-rotator -d "Rotate PDF pages" -r scripts
|
||||
|
||||
# Skill with references
|
||||
python3 scripts/init_skill.py api-helper -d "Help with API calls" -r scripts,references
|
||||
```
|
||||
|
||||
The script path when running from the skill-creator directory:
|
||||
```bash
|
||||
python3 ~/.super-multica/skills/skill-creator/scripts/init_skill.py <skill-name>
|
||||
```
|
||||
|
||||
### Step 3: Edit the Skill
|
||||
|
||||
After initialization, edit `~/.super-multica/skills/<skill-name>/SKILL.md`:
|
||||
|
||||
1. Update the `description` - This is the primary trigger mechanism
|
||||
2. Write clear `## Instructions` - What the agent should do
|
||||
3. Add helper scripts to `scripts/` if needed
|
||||
4. Add reference docs to `references/` if needed
|
||||
|
||||
### Step 4: Test the Skill
|
||||
|
||||
The skill is automatically loaded (hot-reload). Verify with:
|
||||
```bash
|
||||
pnpm skills:cli list | grep <skill-name>
|
||||
```
|
||||
|
||||
## SKILL.md Format
|
||||
|
||||
Every skill must have a `SKILL.md` file with YAML frontmatter:
|
||||
|
||||
```markdown
|
||||
---
|
||||
|
|
@ -42,16 +97,14 @@ metadata:
|
|||
emoji: "🔧"
|
||||
tags:
|
||||
- category1
|
||||
- category2
|
||||
requires:
|
||||
bins: [required-binary]
|
||||
env: [REQUIRED_ENV_VAR]
|
||||
os: [darwin, linux]
|
||||
---
|
||||
|
||||
## Instructions
|
||||
|
||||
Detailed instructions that will be injected into your system prompt...
|
||||
Detailed instructions for using this skill...
|
||||
```
|
||||
|
||||
### Frontmatter Fields
|
||||
|
|
@ -59,195 +112,65 @@ Detailed instructions that will be injected into your system prompt...
|
|||
| Field | Required | Description |
|
||||
|-------|----------|-------------|
|
||||
| `name` | Yes | Display name for the skill |
|
||||
| `description` | No | Short description |
|
||||
| `description` | Yes | Short description (triggers skill selection) |
|
||||
| `version` | No | Semantic version |
|
||||
| `metadata.emoji` | No | Emoji for display |
|
||||
| `metadata.tags` | No | Categorization tags |
|
||||
| `metadata.always` | No | If true, always include (skip eligibility) |
|
||||
| `metadata.requires.bins` | No | Required binaries (all must exist) |
|
||||
| `metadata.requires.anyBins` | No | Alternative binaries (one must exist) |
|
||||
| `metadata.requires.env` | No | Required environment variables |
|
||||
| `metadata.os` | No | Supported platforms: darwin, linux, win32 |
|
||||
|
||||
### Creating a Skill
|
||||
## Directory Structure
|
||||
|
||||
**IMPORTANT**: Always create skills in `~/.super-multica/skills/`, NOT in the current working directory.
|
||||
Skills are stored in `~/.super-multica/skills/`:
|
||||
|
||||
To create a new skill:
|
||||
|
||||
1. Create the skill directory (use absolute path):
|
||||
```bash
|
||||
mkdir -p ~/.super-multica/skills/<skill-name>
|
||||
```
|
||||
|
||||
2. Write the SKILL.md file at `~/.super-multica/skills/<skill-name>/SKILL.md`
|
||||
|
||||
3. If including scripts, create them at `~/.super-multica/skills/<skill-name>/scripts/`
|
||||
|
||||
4. The skill will be automatically loaded (hot-reload is enabled)
|
||||
|
||||
### Example: Creating a Translation Skill
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: Translator
|
||||
description: Translate text between languages
|
||||
version: 1.0.0
|
||||
metadata:
|
||||
emoji: "🌐"
|
||||
tags:
|
||||
- language
|
||||
- translation
|
||||
---
|
||||
|
||||
## Instructions
|
||||
|
||||
When asked to translate text:
|
||||
|
||||
1. Identify the source and target languages
|
||||
2. If source language is not specified, detect it from the text
|
||||
3. Provide accurate, natural-sounding translations
|
||||
4. For ambiguous terms, offer alternative translations with context
|
||||
5. Preserve formatting (bullet points, paragraphs, etc.)
|
||||
|
||||
### Supported Languages
|
||||
|
||||
You can translate between any common languages including:
|
||||
English, Chinese, Japanese, Korean, Spanish, French, German, etc.
|
||||
|
||||
### Example Usage
|
||||
|
||||
User: "Translate 'Hello, how are you?' to Chinese"
|
||||
Response: "你好,你好吗?" (Nǐ hǎo, nǐ hǎo ma?)
|
||||
```
|
||||
~/.super-multica/skills/
|
||||
├── my-skill/
|
||||
│ └── SKILL.md
|
||||
├── another-skill/
|
||||
│ ├── SKILL.md
|
||||
│ ├── scripts/
|
||||
│ │ └── helper.py
|
||||
│ └── references/
|
||||
│ └── api-docs.md
|
||||
```
|
||||
|
||||
### Example: Creating a Code Formatter Skill
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: Code Formatter
|
||||
description: Format and beautify code in various languages
|
||||
version: 1.0.0
|
||||
metadata:
|
||||
emoji: "✨"
|
||||
requires:
|
||||
anyBins: [prettier, black, gofmt]
|
||||
tags:
|
||||
- code
|
||||
- formatting
|
||||
---
|
||||
|
||||
## Instructions
|
||||
|
||||
When asked to format code:
|
||||
|
||||
1. Detect the programming language
|
||||
2. Apply language-specific formatting rules
|
||||
3. Use available formatters when possible:
|
||||
- JavaScript/TypeScript: prettier
|
||||
- Python: black
|
||||
- Go: gofmt
|
||||
4. If no formatter is available, apply standard conventions
|
||||
```
|
||||
|
||||
### Editing a Skill
|
||||
## Editing Existing Skills
|
||||
|
||||
To modify an existing skill:
|
||||
|
||||
1. Read the current SKILL.md file
|
||||
2. Make your changes to the frontmatter or instructions
|
||||
3. Save the file - changes take effect immediately
|
||||
2. Make changes to frontmatter or instructions
|
||||
3. Save - changes take effect immediately (hot-reload)
|
||||
|
||||
### Listing Skills
|
||||
## Listing and Removing Skills
|
||||
|
||||
To see available skills, check the skills directory:
|
||||
```bash
|
||||
ls ~/.super-multica/skills/
|
||||
```
|
||||
|
||||
Or use the CLI:
|
||||
```bash
|
||||
# List all skills
|
||||
pnpm skills:cli list
|
||||
```
|
||||
|
||||
### Removing a Skill
|
||||
# Check skill status
|
||||
pnpm skills:cli status <skill-name>
|
||||
|
||||
To remove a skill:
|
||||
```bash
|
||||
# Remove a skill
|
||||
pnpm skills:cli remove <skill-name>
|
||||
# or
|
||||
rm -rf ~/.super-multica/skills/<skill-name>
|
||||
```
|
||||
|
||||
Or use the CLI:
|
||||
```bash
|
||||
pnpm skills:cli remove <skill-name>
|
||||
```
|
||||
## Best Practices
|
||||
|
||||
### Progressive Disclosure
|
||||
1. **Use init_skill.py** - Never create skills manually in random directories
|
||||
2. **Clear description** - Include "when to use" triggers in the description
|
||||
3. **Concise instructions** - Keep SKILL.md under 500 lines
|
||||
4. **Test scripts** - Run helper scripts to verify they work
|
||||
5. **Single responsibility** - Each skill should do one thing well
|
||||
|
||||
Skills use a multi-level loading system to manage context efficiently:
|
||||
## Skill Precedence
|
||||
|
||||
1. **Metadata** (name + description) - Always loaded for skill selection (~50-100 tokens)
|
||||
2. **SKILL.md body** - Loaded when skill is invoked or relevant
|
||||
3. **Bundled resources** - Loaded on-demand when referenced
|
||||
|
||||
**Design principle**: Keep SKILL.md concise. The context window is shared with conversation history and other skills. Only include information that the agent doesn't already know.
|
||||
|
||||
**For large skills**, split content into separate files:
|
||||
|
||||
```
|
||||
my-skill/
|
||||
├── SKILL.md # Core workflow and navigation
|
||||
├── references/
|
||||
│ ├── api-docs.md # Detailed API documentation
|
||||
│ └── examples.md # Extended examples
|
||||
└── scripts/
|
||||
└── helper.py # Reusable scripts
|
||||
```
|
||||
|
||||
In SKILL.md, reference them with clear guidance:
|
||||
```markdown
|
||||
## Advanced Features
|
||||
|
||||
- **API Reference**: See `references/api-docs.md` for complete method documentation
|
||||
- **Examples**: See `references/examples.md` for common patterns
|
||||
```
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Concise Instructions**: Keep SKILL.md under 500 lines; split larger content into references
|
||||
2. **Clear Triggers**: Write descriptions that help the agent know when to use the skill
|
||||
3. **Examples**: Include usage examples in your skill
|
||||
4. **Requirements**: Specify binaries/env vars if needed
|
||||
5. **Versioning**: Update version when making changes
|
||||
6. **Tags**: Use descriptive tags for organization
|
||||
7. **Single Responsibility**: Each skill should do one thing well
|
||||
|
||||
### Including Scripts
|
||||
|
||||
Skills can include helper scripts in a `scripts/` subdirectory:
|
||||
|
||||
```
|
||||
my-skill/
|
||||
├── SKILL.md
|
||||
└── scripts/
|
||||
├── process.py
|
||||
└── helper.sh
|
||||
```
|
||||
|
||||
Reference them in your instructions:
|
||||
```markdown
|
||||
To process data, run the helper script:
|
||||
\`\`\`bash
|
||||
python ~/.super-multica/skills/my-skill/scripts/process.py
|
||||
\`\`\`
|
||||
```
|
||||
|
||||
### Skill Precedence
|
||||
|
||||
Skills from different sources have different priorities (highest wins):
|
||||
Skills from different sources (highest priority wins):
|
||||
1. Profile-specific skills (`~/.super-multica/agent-profiles/<id>/skills/`)
|
||||
2. User-installed skills (`~/.super-multica/skills/`)
|
||||
3. Plugin skills (from npm packages)
|
||||
4. Bundled skills (built into the application)
|
||||
|
||||
A user skill with the same ID as a bundled skill will override it.
|
||||
|
|
|
|||
248
skills/skill-creator/scripts/init_skill.py
Executable file
248
skills/skill-creator/scripts/init_skill.py
Executable file
|
|
@ -0,0 +1,248 @@
|
|||
#!/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()
|
||||
Loading…
Add table
Add a link
Reference in a new issue