mirror of
https://github.com/alirezarezvani/claude-skills.git
synced 2026-11-15 09:11:54 +00:00
- Gate G13 (scripts/check_harness_mirrors.py): regenerate the .codex, .gemini, .vibe and .hermes mirrors from scratch in a temp copy and fail on any difference in link names, link targets or index bytes. --fix rewrites them, replacing hand-run syncs. - harness-compat.yml + scripts/check_harness_installs.py: install the real Codex, Gemini CLI, Hermes Agent and Mistral Vibe CLIs (pinned on PRs, latest weekly), run the documented install path into an empty HOME and fail unless the harness itself lists every installed skill. Offline listings only, no model calls or API keys. - Fix: Codex skipped 6 single-skill plugins because their installed folder held .claude-plugin/; the Codex installers now drop it. - Fix: Gemini skipped 8 command entries without a frontmatter name; added. - Regenerated 6 Vibe/Hermes links that a fresh sync points at the bundle copy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L8KYFHtMEP35nSKLy8fRkj
429 lines
11 KiB
Bash
Executable file
429 lines
11 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 (--category <name>[,<name>] | --skill <name> | --all)
|
|
#
|
|
# Options:
|
|
# --category <name> Install one or more categories (comma-separated)
|
|
# --skill <name> Install a single skill by name
|
|
# --all Install every skill (not recommended, see below)
|
|
# --list List available skills and categories
|
|
# --dry-run Show what would be installed without making changes
|
|
# --help Show this help message
|
|
#
|
|
# Codex fits all skill descriptions into a fixed prompt budget. Around 100
|
|
# skills it starts cutting descriptions short; with the full library (~380)
|
|
# it drops them entirely and Codex can no longer tell which skill fits a task.
|
|
# Install the categories you use. Run without arguments to see them.
|
|
#
|
|
# Examples:
|
|
# ./scripts/codex-install.sh --category engineering,product
|
|
# ./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() {
|
|
sed -n '2,32p' "$0" | sed 's/^# \{0,1\}//'
|
|
exit "${1:-0}"
|
|
}
|
|
|
|
# Codex's skill list budget: beyond this many installed skills, descriptions
|
|
# are truncated, and with the full library they disappear.
|
|
CODEX_SKILL_WARN_AT=100
|
|
|
|
warn_if_crowded() {
|
|
[[ -d $CODEX_SKILLS_DIR ]] || return 0
|
|
local total
|
|
total=$(find "$CODEX_SKILLS_DIR" -mindepth 2 -maxdepth 2 -name SKILL.md | wc -l | tr -d ' ')
|
|
if ((total > CODEX_SKILL_WARN_AT)); then
|
|
echo ""
|
|
print_warning "$total skills are now installed in $CODEX_SKILLS_DIR."
|
|
print_warning "Above about $CODEX_SKILL_WARN_AT, Codex shortens skill descriptions; with the full library it shows names only."
|
|
print_warning "Remove categories you don't use from $CODEX_SKILLS_DIR to keep skill selection working."
|
|
fi
|
|
}
|
|
|
|
# Shown when no mode is given: installing everything is no longer the default.
|
|
choose_hint() {
|
|
print_error "Choose what to install: --category <name>[,<name>], --skill <name>, or --all."
|
|
echo ""
|
|
if [[ -f $CODEX_INDEX ]] && command -v python3 &> /dev/null; then
|
|
CODEX_INDEX="$CODEX_INDEX" python3 << 'EOF'
|
|
import json, os
|
|
cats = json.load(open(os.environ["CODEX_INDEX"], encoding="utf-8")).get("categories", {})
|
|
print("Categories:")
|
|
for name, info in sorted(cats.items()):
|
|
print(f" {name:22} {info['count']:>3} skills")
|
|
EOF
|
|
fi
|
|
echo ""
|
|
print_info "Example: $0 --category engineering,product"
|
|
print_info "--all installs every skill, which leaves Codex with skill names but no descriptions."
|
|
exit 2
|
|
}
|
|
|
|
# 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"
|
|
|
|
# Codex scans installed skill folders recursively, so a fixture such as
|
|
# skill-tester/assets/sample-skill/SKILL.md would load as a real skill.
|
|
find "$skill_dest" -mindepth 2 -path '*/assets/*' -name SKILL.md \
|
|
-exec sh -c 'mv "$1" "$1.fixture"' _ {} \;
|
|
|
|
# A single-skill plugin keeps its Claude Code manifest next to SKILL.md.
|
|
# Codex skips any skill folder holding a .claude-plugin/ directory (verified
|
|
# on codex 0.166), so drop it: the manifest means nothing to Codex.
|
|
rm -rf "$skill_dest/.claude-plugin"
|
|
|
|
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') in os.environ["CATEGORY"].split(",") 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"
|
|
[[ $dry_run == "true" ]] || warn_if_crowded
|
|
}
|
|
|
|
# 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"
|
|
print_warning "With every skill installed, Codex drops skill descriptions from its prompt."
|
|
print_warning "Prefer --category <name>[,<name>] for the domains you use."
|
|
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"
|
|
warn_if_crowded
|
|
fi
|
|
}
|
|
|
|
# Main
|
|
main() {
|
|
local mode=""
|
|
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"
|
|
;;
|
|
"")
|
|
choose_hint
|
|
;;
|
|
esac
|
|
}
|
|
|
|
main "$@"
|