374 lines
8.3 KiB
Bash
Executable File
374 lines
8.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Codex Installation Script for Claude Skills Library
|
|
#
|
|
# Installs skills from this repository to your local Codex skills directory.
|
|
# Follows symlinks to copy actual skill contents.
|
|
#
|
|
# Usage:
|
|
# ./scripts/codex-install.sh [--all | --category <name> | --skill <name>]
|
|
#
|
|
# Options:
|
|
# --all Install all skills (default)
|
|
# --category <name> Install skills from a specific category
|
|
# --skill <name> Install a single skill by name
|
|
# --list List available skills and categories
|
|
# --dry-run Show what would be installed without making changes
|
|
# --help Show this help message
|
|
#
|
|
# Examples:
|
|
# ./scripts/codex-install.sh # Install all skills
|
|
# ./scripts/codex-install.sh --category marketing
|
|
# ./scripts/codex-install.sh --skill content-creator
|
|
# ./scripts/codex-install.sh --list
|
|
#
|
|
|
|
set -e
|
|
|
|
# Configuration
|
|
CODEX_SKILLS_DIR="${CODEX_SKILLS_DIR:-$HOME/.codex/skills}"
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
REPO_ROOT="$(dirname "$SCRIPT_DIR")"
|
|
CODEX_SKILLS_SRC="$REPO_ROOT/.codex/skills"
|
|
CODEX_INDEX="$REPO_ROOT/.codex/skills-index.json"
|
|
|
|
# Colors for output
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m' # No Color
|
|
|
|
# Print colored output
|
|
print_info() {
|
|
echo -e "${BLUE}[INFO]${NC} $1"
|
|
}
|
|
|
|
print_success() {
|
|
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
|
}
|
|
|
|
print_warning() {
|
|
echo -e "${YELLOW}[WARNING]${NC} $1"
|
|
}
|
|
|
|
print_error() {
|
|
echo -e "${RED}[ERROR]${NC} $1"
|
|
}
|
|
|
|
# Show help
|
|
show_help() {
|
|
head -35 "$0" | tail -30
|
|
exit 0
|
|
}
|
|
|
|
# Check prerequisites
|
|
check_prerequisites() {
|
|
if [[ ! -d $CODEX_SKILLS_SRC ]]; then
|
|
print_error "Codex skills directory not found: $CODEX_SKILLS_SRC"
|
|
print_info "Run 'python scripts/sync-codex-skills.py' first to generate symlinks."
|
|
exit 1
|
|
fi
|
|
|
|
if [[ ! -f $CODEX_INDEX ]]; then
|
|
print_warning "skills-index.json not found. Some features may be limited."
|
|
fi
|
|
}
|
|
|
|
# List available skills
|
|
list_skills() {
|
|
print_info "Available skills in $CODEX_SKILLS_SRC:"
|
|
echo ""
|
|
|
|
if [[ -f $CODEX_INDEX ]] && command -v python3 &> /dev/null; then
|
|
# Use Python to parse JSON and display nicely
|
|
CODEX_INDEX="$CODEX_INDEX" python3 << 'EOF'
|
|
import json
|
|
import os
|
|
import sys
|
|
|
|
try:
|
|
with open(os.environ["CODEX_INDEX"], "r", encoding="utf-8") as f:
|
|
index = json.load(f)
|
|
|
|
print("Categories:")
|
|
print("-" * 50)
|
|
for cat, info in index.get('categories', {}).items():
|
|
print(f" {cat}: {info['count']} skills")
|
|
|
|
print()
|
|
print("Skills by category:")
|
|
print("-" * 50)
|
|
|
|
current_cat = None
|
|
for skill in index.get('skills', []):
|
|
if skill['category'] != current_cat:
|
|
current_cat = skill['category']
|
|
print(f"\n [{current_cat}]")
|
|
print(f" - {skill['name']}: {skill['description'][:60]}...")
|
|
|
|
except Exception as e:
|
|
print(f"Error parsing index: {e}")
|
|
sys.exit(1)
|
|
EOF
|
|
else
|
|
# Fallback to simple listing
|
|
for skill in "$CODEX_SKILLS_SRC"/*; do
|
|
if [[ -L $skill ]] && [[ -e "$skill/SKILL.md" ]]; then
|
|
echo " - $(basename "$skill")"
|
|
fi
|
|
done
|
|
fi
|
|
|
|
exit 0
|
|
}
|
|
|
|
# Install a single skill
|
|
install_skill() {
|
|
local skill_name="$1"
|
|
local dry_run="$2"
|
|
|
|
if [[ -z $skill_name || $skill_name == */* || $skill_name == *..* ]]; then
|
|
print_error "Invalid skill name: $skill_name"
|
|
return 1
|
|
fi
|
|
|
|
local skill_src="$CODEX_SKILLS_SRC/$skill_name"
|
|
local skill_dest="$CODEX_SKILLS_DIR/$skill_name"
|
|
|
|
# Check if skill exists
|
|
if [[ ! -e $skill_src ]]; then
|
|
print_error "Skill not found: $skill_name"
|
|
return 1
|
|
fi
|
|
|
|
# Check if it's a valid skill (has SKILL.md)
|
|
if [[ ! -e "$skill_src/SKILL.md" ]]; then
|
|
print_error "Invalid skill (no SKILL.md): $skill_name"
|
|
return 1
|
|
fi
|
|
|
|
if [[ $dry_run == "true" ]]; then
|
|
print_info "[DRY RUN] Would install: $skill_name -> $skill_dest"
|
|
return 0
|
|
fi
|
|
|
|
# Create destination directory
|
|
mkdir -p "$CODEX_SKILLS_DIR"
|
|
|
|
# Remove existing installation
|
|
if [[ -e $skill_dest ]]; then
|
|
print_info "Updating existing skill: $skill_name"
|
|
rm -rf "$skill_dest"
|
|
fi
|
|
|
|
# Copy skill (following symlinks with -L)
|
|
cp -rL "$skill_src" "$skill_dest"
|
|
|
|
print_success "Installed: $skill_name"
|
|
return 0
|
|
}
|
|
|
|
# Install skills by category
|
|
install_category() {
|
|
local category="$1"
|
|
local dry_run="$2"
|
|
local installed=0
|
|
local failed=0
|
|
|
|
if [[ ! -f $CODEX_INDEX ]]; then
|
|
print_error "skills-index.json required for category installation"
|
|
exit 1
|
|
fi
|
|
|
|
if ! command -v python3 &> /dev/null; then
|
|
print_error "python3 is required for category installation"
|
|
exit 1
|
|
fi
|
|
|
|
print_info "Installing skills from category: $category"
|
|
|
|
# Get skills for this category from index
|
|
local skills
|
|
skills=$(
|
|
CODEX_INDEX="$CODEX_INDEX" CATEGORY="$category" python3 << 'EOF'
|
|
import json
|
|
import os
|
|
|
|
with open(os.environ["CODEX_INDEX"], "r", encoding="utf-8") as f:
|
|
index = json.load(f)
|
|
|
|
seen = set()
|
|
for skill in index.get('skills', []):
|
|
name = skill.get('name')
|
|
if skill.get('category') == os.environ["CATEGORY"] and name and name not in seen:
|
|
seen.add(name)
|
|
print(name)
|
|
EOF
|
|
)
|
|
|
|
if [[ -z $skills ]]; then
|
|
print_error "No skills found for category: $category"
|
|
exit 1
|
|
fi
|
|
|
|
while IFS= read -r skill; do
|
|
if install_skill "$skill" "$dry_run"; then
|
|
((++installed))
|
|
else
|
|
((++failed))
|
|
fi
|
|
done <<< "$skills"
|
|
|
|
echo ""
|
|
print_info "Category '$category' complete: $installed installed, $failed failed"
|
|
}
|
|
|
|
# Install all skills
|
|
install_all() {
|
|
local dry_run="$1"
|
|
local installed=0
|
|
local failed=0
|
|
local skills=""
|
|
|
|
print_info "Installing all skills to: $CODEX_SKILLS_DIR"
|
|
echo ""
|
|
|
|
if [[ -f $CODEX_INDEX ]] && command -v python3 &> /dev/null; then
|
|
skills=$(
|
|
CODEX_INDEX="$CODEX_INDEX" python3 << 'EOF'
|
|
import json
|
|
import os
|
|
|
|
with open(os.environ["CODEX_INDEX"], "r", encoding="utf-8") as f:
|
|
index = json.load(f)
|
|
|
|
seen = set()
|
|
for skill in index.get("skills", []):
|
|
name = skill.get("name")
|
|
if name and name not in seen:
|
|
seen.add(name)
|
|
print(name)
|
|
EOF
|
|
)
|
|
|
|
while IFS= read -r skill_name; do
|
|
if [[ -z $skill_name ]]; then
|
|
continue
|
|
fi
|
|
|
|
if install_skill "$skill_name" "$dry_run"; then
|
|
((++installed))
|
|
else
|
|
((++failed))
|
|
fi
|
|
done <<< "$skills"
|
|
else
|
|
for skill in "$CODEX_SKILLS_SRC"/*; do
|
|
if [[ -L $skill ]] || [[ -d $skill ]]; then
|
|
local skill_name
|
|
skill_name=$(basename "$skill")
|
|
|
|
if install_skill "$skill_name" "$dry_run"; then
|
|
((++installed))
|
|
else
|
|
((++failed))
|
|
fi
|
|
fi
|
|
done
|
|
fi
|
|
|
|
echo ""
|
|
print_info "Installation complete: $installed installed, $failed failed"
|
|
|
|
if [[ $dry_run != "true" ]]; then
|
|
echo ""
|
|
print_success "Skills installed to: $CODEX_SKILLS_DIR"
|
|
print_info "Verify with: ls $CODEX_SKILLS_DIR"
|
|
fi
|
|
}
|
|
|
|
# Main
|
|
main() {
|
|
local mode="all"
|
|
local target=""
|
|
local dry_run="false"
|
|
|
|
# Parse arguments
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
--all)
|
|
mode="all"
|
|
shift
|
|
;;
|
|
--category)
|
|
if [[ $# -lt 2 || -z ${2:-} || ${2:-} == --* ]]; then
|
|
print_error "Category name required"
|
|
exit 1
|
|
fi
|
|
mode="category"
|
|
target="$2"
|
|
shift 2
|
|
;;
|
|
--skill)
|
|
if [[ $# -lt 2 || -z ${2:-} || ${2:-} == --* ]]; then
|
|
print_error "Skill name required"
|
|
exit 1
|
|
fi
|
|
mode="skill"
|
|
target="$2"
|
|
shift 2
|
|
;;
|
|
--list)
|
|
mode="list"
|
|
shift
|
|
;;
|
|
--dry-run)
|
|
dry_run="true"
|
|
shift
|
|
;;
|
|
--help | -h)
|
|
show_help
|
|
;;
|
|
*)
|
|
print_error "Unknown option: $1"
|
|
show_help
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Banner
|
|
echo ""
|
|
echo "========================================"
|
|
echo " Claude Skills - Codex Installer"
|
|
echo "========================================"
|
|
echo ""
|
|
|
|
check_prerequisites
|
|
|
|
case $mode in
|
|
list)
|
|
list_skills
|
|
;;
|
|
skill)
|
|
if [[ -z $target ]]; then
|
|
print_error "Skill name required"
|
|
exit 1
|
|
fi
|
|
install_skill "$target" "$dry_run"
|
|
;;
|
|
category)
|
|
if [[ -z $target ]]; then
|
|
print_error "Category name required"
|
|
exit 1
|
|
fi
|
|
install_category "$target" "$dry_run"
|
|
;;
|
|
all)
|
|
install_all "$dry_run"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
main "$@"
|