Add skills
This commit is contained in:
527
.config/opencode/skills/skill-migrator/scripts/migrate.sh
Executable file
527
.config/opencode/skills/skill-migrator/scripts/migrate.sh
Executable 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 "$@"
|
||||
Reference in New Issue
Block a user