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,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 "$@"