Add skills

This commit is contained in:
2026-03-22 23:21:49 +02:00
parent 4cbbbae1ef
commit c09d9151ca
104 changed files with 23879 additions and 0 deletions

View File

@@ -0,0 +1,236 @@
---
name: skill-migrator
description: This skill should be used when the user asks to "migrate skills from a directory", "import tools to opencode", "move llm tools", "copy skills from a path", "transfer skills to opencode", "install skills from a directory", or migrate LLM tools between directories
license: MIT
compatibility: opencode
metadata:
category: tools
version: "1.0.0"
---
# Skill Migrator
Migrate LLM tools and skills from any directory to OpenCode's skills directory. Supports both global (`~/.config/opencode/skills/`) and local project (`.opencode/skills/`) installations.
## Overview
This skill provides a streamlined way to import skills from external sources or backup locations into OpenCode. It handles skill discovery, conflict resolution, and installation to the appropriate location.
## Common Use Cases
### Migrate Skills from a Directory
Import all skills from a source directory:
```bash
scripts/migrate.sh /path/to/source/skills
```
The script will:
1. Recursively discover all skills (directories containing `SKILL.md`)
2. List found skills and ask which to migrate
3. Handle conflicts interactively (overwrite/skip/backup)
4. Install to global skills directory by default
### Non-Interactive Mode (Automation-Friendly)
For use with opencode or automated workflows, use the non-interactive flags:
```bash
# Migrate all skills without prompts
scripts/migrate.sh /path/to/source/skills --all --yes
# Migrate all, skip existing skills (no overwrite)
scripts/migrate.sh /path/to/source/skills --all --yes --conflict-strategy skip
# Migrate all, overwrite existing skills
scripts/migrate.sh /path/to/source/skills --all --yes --conflict-strategy overwrite
# Migrate all, backup existing before overwriting
scripts/migrate.sh /path/to/source/skills --all --yes --conflict-strategy backup
```
### Install to Local Project
Migrate skills to the current project's local skills directory:
```bash
# Interactive mode
scripts/migrate.sh /path/to/source/skills --local
# Non-interactive mode
scripts/migrate.sh /path/to/source/skills --local --all --yes
```
### Dry Run (Preview)
See what would be migrated without making changes:
```bash
# Preview only
scripts/migrate.sh /path/to/source/skills --dry-run
# Preview with non-interactive flags
scripts/migrate.sh /path/to/source/skills --all --dry-run
```
### Migrate Specific Skills
After discovery, the script presents an interactive menu to select which skills to migrate. Choose specific skills or migrate all discovered skills.
## Migration Process
### Step 1: Discovery
The script searches the source directory recursively for skills:
- Looks for directories containing `SKILL.md`
- Lists all discovered skills with their paths
### Step 2: Selection
Interactive selection menu:
- View all discovered skills
- Select individual skills or all
- Confirm before proceeding
Use `--all` flag to skip selection and migrate all discovered skills.
### Step 3: Conflict Resolution
For each skill that already exists at the destination:
- **Overwrite**: Replace existing skill
- **Skip**: Keep existing skill, skip this one
- **Backup**: Create backup with timestamp, then overwrite
Use `--conflict-strategy <strategy>` to set a default strategy and avoid prompts.
### Step 4: Installation
Copies selected skills to destination:
- Preserves directory structure
- Maintains file permissions
- Copies all skill contents as-is
### Step 5: Report
Generates a migration report showing:
- Successfully migrated skills
- Skipped skills (with reason)
- Backed up skills (with backup location)
- Any errors encountered
## Scripts
Use the migration script:
- `scripts/migrate.sh` - Main migration script with interactive and non-interactive workflow
### Script Usage
```bash
scripts/migrate.sh [OPTIONS] <source-directory>
Options:
-a, --all Migrate all discovered skills without prompting
-c, --conflict-strategy Set conflict resolution: skip, overwrite, backup
-y, --yes Auto-confirm all prompts without interaction
-l, --local Install to local project (.opencode/skills/)
-g, --global Install to global directory (~/.config/opencode/skills/)
-d, --dry-run Preview changes without migrating
-h, --help Show help message
Interactive Mode (default):
The script will prompt for skill selection, conflict resolution, and confirmation.
Non-Interactive Mode:
Use -a/--all, -y/--yes, and -c/--conflict-strategy for automated usage.
Examples:
# Interactive mode
scripts/migrate.sh ~/my-skills
# Non-interactive: migrate all
scripts/migrate.sh ~/my-skills --all --yes
# Non-interactive: migrate all, skip existing
scripts/migrate.sh ~/my-skills --all --yes --conflict-strategy skip
# Non-interactive: migrate all, overwrite existing
scripts/migrate.sh ~/my-skills --all --yes --conflict-strategy overwrite
# Dry run
scripts/migrate.sh ~/my-skills --all --dry-run
# Local project installation
scripts/migrate.sh ~/my-skills --local --all --yes
```
## Target Locations
### Global Skills (Default)
Installed to: `~/.config/opencode/skills/`
- Available across all projects
- Shared OpenCode installation
- Best for commonly used skills
### Local Project Skills
Installed to: `./.opencode/skills/`
- Project-specific skills only
- Version controlled with project
- Team-shared within project
## Non-Interactive Mode
For use with opencode and coding agents, the script supports full automation:
### Flags for Automation
- `--all`: Skip skill selection and migrate all discovered skills
- `--yes`: Skip confirmation prompts and proceed automatically
- `--conflict-strategy <strategy>`: Set default conflict handling (skip/overwrite/backup)
### Example Automation Scenarios
**Migrate all skills, skip existing:**
```bash
scripts/migrate.sh ~/my-skills --all --yes --conflict-strategy skip
```
**Migrate all skills, overwrite existing:**
```bash
scripts/migrate.sh ~/my-skills --all --yes --conflict-strategy overwrite
```
**Migrate all skills, backup existing:**
```bash
scripts/migrate.sh ~/my-skills --all --yes --conflict-strategy backup
```
**Dry run to preview changes:**
```bash
scripts/migrate.sh ~/my-skills --all --dry-run
```
### Conflict Strategies
- `skip`: Keep existing skills, skip migrating those that already exist
- `overwrite`: Replace existing skills with the new versions
- `backup`: Create timestamped backups of existing skills before overwriting
## Important Notes
- Skills are copied as-is without validation
- File permissions are preserved during migration
- Backups are stored with timestamp: `skillname.backup.YYYYMMDD_HHMMSS`
- Empty directories and non-skill folders are ignored
- The script requires write permissions to the destination directory
- Use `--all`, `--yes`, and `--conflict-strategy` flags for non-interactive usage with opencode
- Non-interactive mode is ideal for automated workflows and CI/CD pipelines
## Troubleshooting
See `references/migration-guide.md` for detailed troubleshooting and advanced usage.

View File

@@ -0,0 +1,62 @@
{
"skill_name": "skill-migrator",
"evals": [
{
"id": 1,
"name": "basic-non-interactive-migration",
"prompt": "Migrate all skills from ~/.agents to the global opencode skills directory without any interactive prompts. Use the skill-migrator skill.",
"expected_output": "The agent should use the migrate.sh script with --all and --yes flags to perform a non-interactive migration. No prompts should be displayed.",
"assertions": [
"Agent identifies the correct script path",
"Agent uses --all flag to select all skills",
"Agent uses --yes flag to auto-confirm",
"Migration completes without interactive prompts"
]
},
{
"id": 2,
"name": "migration-with-conflict-resolution",
"prompt": "Migrate skills from ~/.agents to the global skills directory. Some skills may already exist at the destination. Use the skill-migrator to handle conflicts by skipping existing skills automatically.",
"expected_output": "The agent should use --conflict-strategy skip to automatically handle conflicts without prompting.",
"assertions": [
"Agent uses --conflict-strategy skip flag",
"Agent includes --all and --yes for non-interactive mode",
"Existing skills are skipped without prompts"
]
},
{
"id": 3,
"name": "dry-run-preview",
"prompt": "Preview what would be migrated from ~/.agents without actually making any changes. Show what skills would be migrated and how conflicts would be handled.",
"expected_output": "The agent should use --dry-run flag to preview changes without making them.",
"assertions": [
"Agent uses --dry-run flag",
"Agent may also use --all to preview all skills",
"No actual file changes are made",
"Preview output shows discovered skills"
]
},
{
"id": 4,
"name": "local-project-installation",
"prompt": "Migrate skills from ~/.agents to the local project directory (.opencode/skills/) instead of the global directory. Make it non-interactive.",
"expected_output": "The agent should use --local flag along with non-interactive flags.",
"assertions": [
"Agent uses --local flag",
"Agent uses --all and --yes for non-interactive mode",
"Target directory is the local .opencode/skills/ directory"
]
},
{
"id": 5,
"name": "backup-conflict-strategy",
"prompt": "Migrate skills from ~/.agents. If any skills already exist, create backups of the existing ones before overwriting them. Do this without any prompts.",
"expected_output": "The agent should use --conflict-strategy backup to create backups before overwriting.",
"assertions": [
"Agent uses --conflict-strategy backup",
"Agent includes --all and --yes",
"Existing skills are backed up with timestamps"
]
}
]
}

View File

@@ -0,0 +1,231 @@
#!/bin/bash
#
# Skill Migrator Test Script
# Tests the non-interactive mode functionality
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
MIGRATE_SCRIPT="$HOME/.config/opencode/skills/skill-migrator/scripts/migrate.sh"
TEST_DIR="$HOME/.agents-test"
echo "========================================"
echo " SKILL MIGRATOR TEST SUITE"
echo "========================================"
echo ""
# Setup: Create test skills
setup_test_env() {
echo "Setting up test environment..."
rm -rf "$TEST_DIR"
mkdir -p "$TEST_DIR"
# Create a test skill
mkdir -p "$TEST_DIR/test-skill-1"
cat > "$TEST_DIR/test-skill-1/SKILL.md" << 'EOF'
---
name: test-skill-1
description: Test skill 1 for migration testing
---
# Test Skill 1
This is a test skill.
EOF
# Create another test skill
mkdir -p "$TEST_DIR/test-skill-2"
cat > "$TEST_DIR/test-skill-2/SKILL.md" << 'EOF'
---
name: test-skill-2
description: Test skill 2 for migration testing
---
# Test Skill 2
This is another test skill.
EOF
echo "✓ Test environment created"
echo ""
}
# Test 1: Basic non-interactive migration
test_basic_migration() {
echo "Test 1: Basic Non-Interactive Migration"
echo "----------------------------------------"
# Clean target
rm -rf "$HOME/.config/opencode/skills/test-skill-1"
rm -rf "$HOME/.config/opencode/skills/test-skill-2"
# Run migration with non-interactive flags
if "$MIGRATE_SCRIPT" "$TEST_DIR" --all --yes; then
echo "✓ Migration completed successfully"
else
echo "✗ Migration failed"
return 1
fi
# Verify skills were migrated
if [[ -d "$HOME/.config/opencode/skills/test-skill-1" && -d "$HOME/.config/opencode/skills/test-skill-2" ]]; then
echo "✓ Skills migrated to correct location"
else
echo "✗ Skills not found in target directory"
return 1
fi
echo ""
}
# Test 2: Skip conflict strategy
test_skip_strategy() {
echo "Test 2: Skip Conflict Strategy"
echo "-------------------------------"
# Modify source skill
echo "# Modified" >> "$TEST_DIR/test-skill-1/SKILL.md"
# Run migration with skip strategy
if "$MIGRATE_SCRIPT" "$TEST_DIR" --all --yes --conflict-strategy skip; then
echo "✓ Migration with skip strategy completed"
else
echo "✗ Migration failed"
return 1
fi
# Verify original skill was NOT overwritten
if ! grep -q "# Modified" "$HOME/.config/opencode/skills/test-skill-1/SKILL.md"; then
echo "✓ Existing skill was skipped (not overwritten)"
else
echo "✗ Existing skill was overwritten (should have been skipped)"
return 1
fi
echo ""
}
# Test 3: Overwrite conflict strategy
test_overwrite_strategy() {
echo "Test 3: Overwrite Conflict Strategy"
echo "------------------------------------"
# Run migration with overwrite strategy
if "$MIGRATE_SCRIPT" "$TEST_DIR" --all --yes --conflict-strategy overwrite; then
echo "✓ Migration with overwrite strategy completed"
else
echo "✗ Migration failed"
return 1
fi
# Verify skill WAS overwritten
if grep -q "# Modified" "$HOME/.config/opencode/skills/test-skill-1/SKILL.md"; then
echo "✓ Existing skill was overwritten"
else
echo "✗ Existing skill was not overwritten"
return 1
fi
echo ""
}
# Test 4: Backup conflict strategy
test_backup_strategy() {
echo "Test 4: Backup Conflict Strategy"
echo "---------------------------------"
# Run migration with backup strategy
if "$MIGRATE_SCRIPT" "$TEST_DIR" --all --yes --conflict-strategy backup; then
echo "✓ Migration with backup strategy completed"
else
echo "✗ Migration failed"
return 1
fi
# Verify backup was created
if ls "$HOME/.config/opencode/skills/test-skill-1.backup."* 1> /dev/null 2>&1; then
echo "✓ Backup was created"
else
echo "✗ Backup not found"
return 1
fi
echo ""
}
# Test 5: Dry run
test_dry_run() {
echo "Test 5: Dry Run Mode"
echo "---------------------"
# Create a new test skill that doesn't exist yet
mkdir -p "$TEST_DIR/test-skill-3"
cat > "$TEST_DIR/test-skill-3/SKILL.md" << 'EOF'
---
name: test-skill-3
description: Test skill 3 for dry run testing
---
# Test Skill 3
This skill should not be migrated in dry run.
EOF
# Run migration with dry-run
if "$MIGRATE_SCRIPT" "$TEST_DIR" --all --dry-run; then
echo "✓ Dry run completed"
else
echo "✗ Dry run failed"
return 1
fi
# Verify skill was NOT actually migrated
if [[ ! -d "$HOME/.config/opencode/skills/test-skill-3" ]]; then
echo "✓ Dry run correctly made no changes"
else
echo "✗ Skill was migrated (should not happen in dry run)"
return 1
fi
echo ""
}
# Cleanup
cleanup() {
echo "Cleaning up..."
rm -rf "$TEST_DIR"
rm -rf "$HOME/.config/opencode/skills/test-skill-1"
rm -rf "$HOME/.config/opencode/skills/test-skill-2"
rm -rf "$HOME/.config/opencode/skills/test-skill-3"
rm -f "$HOME/.config/opencode/skills/test-skill-1.backup."*
echo "✓ Cleanup complete"
echo ""
}
# Run all tests
main() {
setup_test_env
local failed=0
test_basic_migration || ((failed++))
test_skip_strategy || ((failed++))
test_overwrite_strategy || ((failed++))
test_backup_strategy || ((failed++))
test_dry_run || ((failed++))
cleanup
echo "========================================"
echo " TEST RESULTS"
echo "========================================"
if [[ $failed -eq 0 ]]; then
echo "✓ All tests passed!"
exit 0
else
echo "$failed test(s) failed"
exit 1
fi
}
main "$@"

View File

@@ -0,0 +1,438 @@
# Skill Migration Guide
Comprehensive guide for migrating LLM tools and skills to OpenCode.
## Table of Contents
1. [Quick Start](#quick-start)
2. [Usage Examples](#usage-examples)
3. [Interactive Workflow](#interactive-workflow)
4. [Conflict Resolution](#conflict-resolution)
5. [Troubleshooting](#troubleshooting)
6. [Advanced Usage](#advanced-usage)
## Quick Start
### Basic Migration
Migrate all skills from a directory to global OpenCode:
```bash
# Discover and migrate skills
~/.config/opencode/skills/skill-migrator/scripts/migrate.sh /path/to/skills
# Or if you're in the skill-migrator directory
./scripts/migrate.sh /path/to/skills
```
### Migrate to Local Project
```bash
./scripts/migrate.sh /path/to/skills --local
```
## Usage Examples
### Example 1: Migrating from Backup
You have a backup of skills in `~/backups/opencode-skills/`:
```bash
./scripts/migrate.sh ~/backups/opencode-skills
```
**Workflow:**
1. Script discovers all skills containing `SKILL.md`
2. Lists discovered skills with numbers
3. You select which to migrate (or 'a' for all)
4. For existing skills, choose: overwrite/skip/backup
5. Migration completes with a report
### Example 2: Preview Before Migrating
See what would be migrated without making changes:
```bash
./scripts/migrate.sh ~/external-tools --dry-run
```
This shows:
- Which skills would be discovered
- Which already exist at destination
- No actual changes are made
### Example 3: Selective Migration
Migrate only specific skills:
```bash
./scripts/migrate.sh ~/my-tools
# At selection prompt, enter: 1,3,5
# Only skills 1, 3, and 5 will be migrated
```
### Example 4: Local Development
Working on a project that needs specific skills:
```bash
# In your project directory
./scripts/migrate.sh ~/project-skills --local
# Skills installed to ./.opencode/skills/
# Can be version controlled with your project
```
## Interactive Workflow
### Discovery Phase
The script searches recursively for directories containing `SKILL.md`:
```
Discovering skills in: /home/user/external-skills
✓ Found 5 skill(s)
Discovered Skills:
==================
1. docker-helper (/home/user/external-skills/docker-helper)
2. git-workflow (/home/user/external-skills/workflows/git-workflow)
3. api-tester (/home/user/external-skills/devtools/api-tester)
4. doc-generator (/home/user/external-skills/docs/doc-generator)
5. test-validator (/home/user/external-skills/qa/test-validator)
```
### Selection Phase
Choose which skills to migrate:
```
Select skills to migrate:
[a] - Migrate all
[1-5] - Select specific skill(s) by number (comma-separated)
[q] - Quit
Your choice: 1,3,5
```
### Confirmation
Before proceeding:
```
Ready to migrate 3 skill(s).
Continue? [Y/n]: Y
```
### Migration Progress
Live progress as skills are migrated:
```
Migrating: docker-helper
⚠ Skill already exists: docker-helper
Location: ~/.config/opencode/skills/docker-helper
Options:
[o] - Overwrite (replace existing)
[s] - Skip (keep existing)
[b] - Backup (create backup, then overwrite)
Your choice [o/s/b]: b
Backed up to: ~/.config/opencode/skills/docker-helper.backup.20240319_143052
✓ Migrated: docker-helper
Migrating: api-tester
✓ Migrated: api-tester
Migrating: test-validator
✓ Migrated: test-validator
```
### Final Report
```
========================================
MIGRATION REPORT
========================================
✓ Successfully Migrated (3):
• docker-helper
• api-tester
• test-validator
Backed Up (1):
• docker-helper -> ~/.config/opencode/skills/docker-helper.backup.20240319_143052
Target Directory: ~/.config/opencode/skills
========================================
```
## Conflict Resolution
### When Conflicts Occur
A conflict happens when a skill with the same name already exists at the destination.
### Options
#### 1. Overwrite
- **When to use:** The new version is newer/better, or you don't need the old one
- **Result:** Existing skill is completely replaced
- **Risk:** You lose the old version permanently
#### 2. Skip
- **When to use:** You want to keep the existing skill as-is
- **Result:** New skill is not migrated
- **Use case:** The existing skill has customizations you want to keep
#### 3. Backup
- **When to use:** You want both versions (safe option)
- **Result:** Old skill backed up with timestamp, new skill installed
- **Backup location:** `~/.config/opencode/skills/skillname.backup.YYYYMMDD_HHMMSS`
### Best Practices
- **Always choose backup** when unsure - you can manually merge later
- **Use skip** if you've customized the existing skill
- **Use overwrite** if the source is definitely newer/correct
## Troubleshooting
### Issue: "No skills found"
**Symptoms:**
```
⚠ No skills found in: /path/to/source
Looking for directories containing 'SKILL.md'
```
**Solutions:**
1. **Verify source structure:**
```bash
# Check if SKILL.md files exist
find /path/to/source -name "SKILL.md" -type f
```
2. **Check file permissions:**
```bash
# Ensure you can read the directory
ls -la /path/to/source
```
3. **Verify path is correct:**
```bash
# Make sure path exists
ls -d /path/to/source
```
### Issue: "Permission denied"
**Symptoms:**
```
✗ Cannot write to target directory
```
**Solutions:**
1. **Check directory permissions:**
```bash
ls -ld ~/.config/opencode/skills
```
2. **Fix permissions:**
```bash
# For global directory
mkdir -p ~/.config/opencode/skills
chmod 755 ~/.config/opencode/skills
# For local directory
mkdir -p .opencode/skills
chmod 755 .opencode/skills
```
3. **Run with appropriate user:**
Ensure you're running as the user who owns the OpenCode config
### Issue: Script hangs or no prompt
**Symptoms:** Script stops without showing selection menu
**Solutions:**
1. **Ensure terminal is interactive:**
```bash
# Check if stdin is a terminal
[ -t 0 ] && echo "Interactive" || echo "Not interactive"
```
2. **Run in proper terminal:**
Don't run from non-interactive contexts (CI/CD, scripts without TTY)
3. **Use --dry-run first:**
```bash
./scripts/migrate.sh /path --dry-run
```
### Issue: Skills not appearing in OpenCode
**Symptoms:** Migration succeeds but OpenCode doesn't recognize new skills
**Solutions:**
1. **Check skill structure:**
```bash
# Verify SKILL.md exists
ls ~/.config/opencode/skills/new-skill/SKILL.md
```
2. **Check SKILL.md format:**
- Must start with YAML frontmatter (`---`)
- Must have `name:` field
- Must have `description:` field (20-1024 chars)
3. **Validate the skill:**
```bash
~/.config/opencode/skills/skill-builder/scripts/validate-skill.sh \
~/.config/opencode/skills/new-skill
```
4. **Restart OpenCode** if using a GUI/IDE plugin
### Issue: Backup not created
**Symptoms:** Chose 'backup' option but can't find backup
**Solutions:**
1. **Check backup location:**
```bash
ls -la ~/.config/opencode/skills/*.backup.*
```
2. **Check timestamp format:**
Backups use format: `skillname.backup.YYYYMMDD_HHMMSS`
3. **Verify not in dry-run:**
Backups are only created during actual migration, not dry-run
## Advanced Usage
### Batch Migration Script
Create a wrapper for recurring migrations:
```bash
#!/bin/bash
# migrate-from-backup.sh
BACKUP_DIR="${HOME}/backups/opencode-skills"
MIGRATOR="${HOME}/.config/opencode/skills/skill-migrator/scripts/migrate.sh"
# Always backup before overwriting
export MIGRATOR_DEFAULT_ACTION="backup"
# Run migration
"$MIGRATOR" "$BACKUP_DIR" "$@"
```
### Automated Sync
Sync skills from a git repository:
```bash
#!/bin/bash
# sync-skills.sh
SKILLS_REPO="https://github.com/company/shared-opencode-skills.git"
TEMP_DIR="/tmp/opencode-skills-sync"
MIGRATOR="${HOME}/.config/opencode/skills/skill-migrator/scripts/migrate.sh"
# Clone latest
rm -rf "$TEMP_DIR"
git clone "$SKILLS_REPO" "$TEMP_DIR"
# Dry run first
"$MIGRATOR" "$TEMP_DIR/skills" --dry-run
# Ask for confirmation
read -p "Proceed with migration? [y/N]: " confirm
if [[ "$confirm" =~ ^[Yy]$ ]]; then
"$MIGRATOR" "$TEMP_DIR/skills"
fi
# Cleanup
rm -rf "$TEMP_DIR"
```
### Selective Auto-Backup
Always backup certain critical skills:
```bash
#!/bin/bash
# safe-migrate.sh
CRITICAL_SKILLS=("production-deploy" "database-migration" "security-scan")
SOURCE_DIR="$1"
MIGRATOR="${HOME}/.config/opencode/skills/skill-migrator/scripts/migrate.sh"
# Pre-backup critical skills
for skill in "${CRITICAL_SKILLS[@]}"; do
skill_path="${HOME}/.config/opencode/skills/${skill}"
if [[ -d "$skill_path" ]]; then
timestamp=$(date +"%Y%m%d_%H%M%S")
cp -r "$skill_path" "${skill_path}.pre-migration.${timestamp}"
echo "Pre-backed up: $skill"
fi
done
# Run migration
"$MIGRATOR" "$SOURCE_DIR"
```
### Migration with Validation
Validate skills after migration:
```bash
#!/bin/bash
# migrate-and-validate.sh
SOURCE_DIR="$1"
MIGRATOR="${HOME}/.config/opencode/skills/skill-migrator/scripts/migrate.sh"
VALIDATOR="${HOME}/.config/opencode/skills/skill-builder/scripts/validate-skill.sh"
TARGET_DIR="${HOME}/.config/opencode/skills"
# Migrate
"$MIGRATOR" "$SOURCE_DIR"
# Validate all migrated skills
echo ""
echo "Validating migrated skills..."
for skill_dir in "$TARGET_DIR"/*/; do
skill_name=$(basename "$skill_dir")
if [[ -f "$skill_dir/SKILL.md" ]]; then
echo "Validating: $skill_name"
if ! "$VALIDATOR" "$skill_dir" 2>/dev/null; then
echo "⚠ $skill_name has validation issues"
fi
fi
done
```
## Tips and Best Practices
1. **Always use --dry-run first** when migrating from a new source
2. **Keep backups** until you've verified skills work in OpenCode
3. **Use local skills** for project-specific tools
4. **Use global skills** for commonly used utilities
5. **Document your skills** with clear descriptions for better OpenCode integration
6. **Test migrated skills** by asking OpenCode to use them
## See Also
- `SKILL.md` - Main skill documentation
- `skill-builder` skill - For validating and creating skills
- OpenCode documentation for skill development

View File

@@ -0,0 +1,527 @@
#!/bin/bash
#
# Skill Migrator - Migrate LLM tools to OpenCode skills directory
#
set -euo pipefail
# Configuration
GLOBAL_SKILLS_DIR="${HOME}/.config/opencode/skills"
LOCAL_SKILLS_DIR=".opencode/skills"
TARGET_MODE="global"
DRY_RUN=false
# Non-interactive mode flags
MIGRATE_ALL=false
AUTO_CONFIRM=false
CONFLICT_STRATEGY=""
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Data structures
declare -a DISCOVERED_SKILLS=()
declare -a DISCOVERED_PATHS=()
declare -a MIGRATED_SKILLS=()
declare -a SKIPPED_SKILLS=()
declare -a BACKED_UP_SKILLS=()
declare -a ERROR_SKILLS=()
# Show help
show_help() {
cat << EOF
Skill Migrator - Migrate LLM tools to OpenCode
Usage: $(basename "$0") [OPTIONS] <source-directory>
Migrate skills from a source directory to OpenCode's skills directory.
Options:
-a, --all Migrate all discovered skills without prompting
-c, --conflict-strategy Set conflict resolution strategy: skip, overwrite, backup
-y, --yes Auto-confirm all prompts without interaction
-l, --local Install to local project (.opencode/skills/)
-g, --global Install to global directory (default: ~/.config/opencode/skills/)
-d, --dry-run Preview changes without migrating
-h, --help Show this help message
Interactive Options:
By default, the script will prompt you to:
- Select which skills to migrate
- Resolve conflicts (overwrite/skip/backup)
- Confirm before proceeding
Use -a, -y, and -c flags for non-interactive/automated usage.
Examples:
# Interactive mode (default)
$(basename "$0") ~/my-skills
# Non-interactive: migrate all skills
$(basename "$0") ~/my-skills --all --yes
# Non-interactive: migrate all, skip existing
$(basename "$0") ~/my-skills --all --yes --conflict-strategy skip
# Non-interactive: migrate all, overwrite existing
$(basename "$0") ~/my-skills --all --yes --conflict-strategy overwrite
# Dry run (preview only)
$(basename "$0") ~/my-skills --all --dry-run
# Install to local project
$(basename "$0") ~/my-skills --local --all --yes
EOF
}
# Print colored output
print_error() {
echo -e "${RED}${NC} $1"
}
print_success() {
echo -e "${GREEN}${NC} $1"
}
print_warning() {
echo -e "${YELLOW}${NC} $1"
}
print_info() {
echo -e "${BLUE}${NC} $1"
}
# Parse command line arguments
parse_args() {
SOURCE_DIR=""
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_help
exit 0
;;
-a|--all)
MIGRATE_ALL=true
shift
;;
-y|--yes)
AUTO_CONFIRM=true
shift
;;
-c|--conflict-strategy)
if [[ -n "${2:-}" && ! "$2" =~ ^- ]]; then
CONFLICT_STRATEGY="$2"
# Validate conflict strategy
if [[ "$CONFLICT_STRATEGY" != "skip" && "$CONFLICT_STRATEGY" != "overwrite" && "$CONFLICT_STRATEGY" != "backup" ]]; then
print_error "Invalid conflict strategy: $CONFLICT_STRATEGY"
print_error "Valid options: skip, overwrite, backup"
exit 1
fi
shift 2
else
print_error "--conflict-strategy requires a value (skip, overwrite, backup)"
exit 1
fi
;;
-l|--local)
TARGET_MODE="local"
shift
;;
-g|--global)
TARGET_MODE="global"
shift
;;
-d|--dry-run)
DRY_RUN=true
shift
;;
-*)
print_error "Unknown option: $1"
show_help
exit 1
;;
*)
if [[ -z "$SOURCE_DIR" ]]; then
SOURCE_DIR="$1"
else
print_error "Multiple source directories specified"
exit 1
fi
shift
;;
esac
done
if [[ -z "$SOURCE_DIR" ]]; then
print_error "No source directory specified"
show_help
exit 1
fi
}
# Get target directory
get_target_dir() {
if [[ "$TARGET_MODE" == "local" ]]; then
if [[ ! -d "$LOCAL_SKILLS_DIR" ]]; then
if [[ "$DRY_RUN" == false ]]; then
mkdir -p "$LOCAL_SKILLS_DIR"
fi
fi
echo "$LOCAL_SKILLS_DIR"
else
if [[ ! -d "$GLOBAL_SKILLS_DIR" ]]; then
if [[ "$DRY_RUN" == false ]]; then
mkdir -p "$GLOBAL_SKILLS_DIR"
fi
fi
echo "$GLOBAL_SKILLS_DIR"
fi
}
# Discover skills recursively
discover_skills() {
local source_dir="$1"
print_info "Discovering skills in: $source_dir"
# Find all directories containing SKILL.md
local found_skills=0
while IFS= read -r -d '' skill_path; do
local skill_dir
skill_dir=$(dirname "$skill_path")
local skill_name
skill_name=$(basename "$skill_dir")
# Skip the skill-migrator itself
if [[ "$skill_name" == "skill-migrator" ]]; then
continue
fi
DISCOVERED_SKILLS+=("$skill_name")
DISCOVERED_PATHS+=("$skill_dir")
((found_skills++)) || true
done < <(find "$source_dir" -type f -name "SKILL.md" -print0 2>/dev/null)
if [[ ${#DISCOVERED_SKILLS[@]} -eq 0 ]]; then
print_warning "No skills found in: $source_dir"
print_info "Looking for directories containing 'SKILL.md'"
exit 0
fi
print_success "Found $found_skills skill(s)"
}
# Display discovered skills
display_discovered() {
echo ""
echo "Discovered Skills:"
echo "=================="
local i=1
for skill_name in "${DISCOVERED_SKILLS[@]}"; do
printf "%2d. %-30s (%s)\n" "$i" "$skill_name" "${DISCOVERED_PATHS[$((i-1))]}"
((i++)) || true
done
echo ""
}
# Prompt user for skill selection
select_skills() {
# If --all flag is set, select all skills automatically
if [[ "$MIGRATE_ALL" == true ]]; then
SELECTED_INDICES=($(seq 1 ${#DISCOVERED_SKILLS[@]}))
print_info "Auto-selecting all ${#DISCOVERED_SKILLS[@]} skill(s) (--all flag set)"
return
fi
echo "Select skills to migrate:"
echo " [a] - Migrate all"
echo " [1-${#DISCOVERED_SKILLS[@]}] - Select specific skill(s) by number (comma-separated)"
echo " [q] - Quit"
echo ""
read -p "Your choice: " choice
case "$choice" in
a|A)
SELECTED_INDICES=($(seq 1 ${#DISCOVERED_SKILLS[@]}))
;;
q|Q)
print_info "Migration cancelled"
exit 0
;;
*)
SELECTED_INDICES=()
IFS=',' read -ra selections <<< "$choice"
for sel in "${selections[@]}"; do
sel=$(echo "$sel" | tr -d ' ')
if [[ "$sel" =~ ^[0-9]+$ ]] && [[ "$sel" -ge 1 ]] && [[ "$sel" -le ${#DISCOVERED_SKILLS[@]} ]]; then
SELECTED_INDICES+=("$sel")
else
print_warning "Invalid selection: $sel"
fi
done
if [[ ${#SELECTED_INDICES[@]} -eq 0 ]]; then
print_error "No valid skills selected"
exit 1
fi
;;
esac
}
# Resolve conflict for existing skill
resolve_conflict() {
local skill_name="$1"
local target_dir="$2"
local target_path="$target_dir/$skill_name"
if [[ ! -d "$target_path" ]]; then
echo "migrate"
return
fi
# If conflict strategy is set via flag, use it
if [[ -n "$CONFLICT_STRATEGY" ]]; then
# Print to stderr so it doesn't get captured in command substitution
print_info "Conflict for '$skill_name': using --conflict-strategy=$CONFLICT_STRATEGY" >&2
echo "$CONFLICT_STRATEGY"
return
fi
echo ""
print_warning "Skill already exists: $skill_name"
echo " Location: $target_path"
echo ""
echo "Options:"
echo " [o] - Overwrite (replace existing)"
echo " [s] - Skip (keep existing)"
echo " [b] - Backup (create backup, then overwrite)"
echo ""
while true; do
read -p "Your choice [o/s/b]: " conflict_choice
case "$conflict_choice" in
o|O)
echo "overwrite"
return
;;
s|S)
echo "skip"
return
;;
b|B)
echo "backup"
return
;;
*)
print_warning "Invalid choice. Please enter o, s, or b"
;;
esac
done
}
# Backup existing skill
backup_skill() {
local skill_name="$1"
local target_dir="$2"
local target_path="$target_dir/$skill_name"
local timestamp
timestamp=$(date +"%Y%m%d_%H%M%S")
local backup_path="${target_path}.backup.${timestamp}"
if [[ "$DRY_RUN" == false ]]; then
mv "$target_path" "$backup_path"
fi
BACKED_UP_SKILLS+=("$skill_name -> $backup_path")
echo "$backup_path"
}
# Migrate a single skill
migrate_skill() {
local skill_name="$1"
local source_path="$2"
local target_dir="$3"
local target_path="$target_dir/$skill_name"
print_info "Migrating: $skill_name"
# Check for conflicts
local resolution
resolution=$(resolve_conflict "$skill_name" "$target_dir")
case "$resolution" in
skip)
SKIPPED_SKILLS+=("$skill_name (user skipped)")
print_warning "Skipped: $skill_name"
return 0
;;
backup)
local backup_path
backup_path=$(backup_skill "$skill_name" "$target_dir")
print_info "Backed up to: $backup_path"
;;
migrate|overwrite)
# Proceed with migration
;;
esac
# Perform migration
if [[ "$DRY_RUN" == false ]]; then
if [[ -d "$target_path" ]]; then
rm -rf "$target_path"
fi
cp -r "$source_path" "$target_path"
fi
MIGRATED_SKILLS+=("$skill_name")
print_success "Migrated: $skill_name"
return 0
}
# Generate migration report
generate_report() {
echo ""
echo "========================================"
echo " MIGRATION REPORT"
echo "========================================"
echo ""
if [[ ${#MIGRATED_SKILLS[@]} -gt 0 ]]; then
print_success "Successfully Migrated (${#MIGRATED_SKILLS[@]}):"
for skill in "${MIGRATED_SKILLS[@]}"; do
echo "$skill"
done
echo ""
fi
if [[ ${#SKIPPED_SKILLS[@]} -gt 0 ]]; then
print_warning "Skipped (${#SKIPPED_SKILLS[@]}):"
for skill in "${SKIPPED_SKILLS[@]}"; do
echo "$skill"
done
echo ""
fi
if [[ ${#BACKED_UP_SKILLS[@]} -gt 0 ]]; then
print_info "Backed Up (${#BACKED_UP_SKILLS[@]}):"
for backup in "${BACKED_UP_SKILLS[@]}"; do
echo "$backup"
done
echo ""
fi
if [[ ${#ERROR_SKILLS[@]} -gt 0 ]]; then
print_error "Failed (${#ERROR_SKILLS[@]}):"
for skill in "${ERROR_SKILLS[@]}"; do
echo "$skill"
done
echo ""
fi
if [[ "$DRY_RUN" == true ]]; then
echo "Note: This was a DRY RUN. No changes were made."
echo " Run without --dry-run to perform actual migration."
fi
echo ""
echo "Target Directory: $TARGET_DIR"
echo "========================================"
}
# Main function
main() {
# Check for help flag early
for arg in "$@"; do
if [[ "$arg" == "-h" ]] || [[ "$arg" == "--help" ]]; then
show_help
exit 0
fi
done
echo "========================================"
echo " SKILL MIGRATOR"
echo "========================================"
echo ""
# Parse arguments
parse_args "$@"
# Validate source directory
if [[ ! -d "$SOURCE_DIR" ]]; then
print_error "Source directory does not exist: $SOURCE_DIR"
exit 1
fi
# Check dry run
if [[ "$DRY_RUN" == true ]]; then
print_warning "DRY RUN MODE - No changes will be made"
echo ""
fi
# Get target directory
TARGET_DIR=$(get_target_dir)
print_info "Source: $SOURCE_DIR"
print_info "Target: $TARGET_DIR ($TARGET_MODE)"
echo ""
# Discover skills
discover_skills "$SOURCE_DIR"
# Display discovered skills
display_discovered
# Select skills to migrate
SELECTED_INDICES=()
select_skills
# Confirm migration
echo ""
echo "Ready to migrate ${#SELECTED_INDICES[@]} skill(s)."
if [[ "$DRY_RUN" == false ]]; then
if [[ "$AUTO_CONFIRM" == true ]]; then
print_info "Auto-confirming (--yes flag set)"
else
read -p "Continue? [Y/n]: " confirm
if [[ "$confirm" =~ ^[Nn]$ ]]; then
print_info "Migration cancelled"
exit 0
fi
fi
fi
echo ""
# Migrate selected skills
local failed=0
for idx in "${SELECTED_INDICES[@]}"; do
local array_idx=$((idx - 1))
local skill_name="${DISCOVERED_SKILLS[$array_idx]}"
local source_path="${DISCOVERED_PATHS[$array_idx]}"
if ! migrate_skill "$skill_name" "$source_path" "$TARGET_DIR"; then
ERROR_SKILLS+=("$skill_name")
((failed++)) || true
fi
done
# Generate report
generate_report
# Exit with appropriate code
if [[ $failed -gt 0 ]]; then
exit 1
fi
exit 0
}
# Run main function
main "$@"