Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 114 additions & 37 deletions lib/skills.sh
Original file line number Diff line number Diff line change
Expand Up @@ -109,58 +109,135 @@ install_skills_to_persistent_source() {
fi
}

# Resolve the skills dir for a given runtime without mutating the currently
# sourced runtime functions permanently. We source the runtime file in a
# subshell, call its runtime_skills_dir(), and echo the result.
_resolve_skills_dir_for_runtime() {
local rt="$1"
local rt_file="$SCRIPT_DIR/runtimes/${rt}.sh"
[ -f "$rt_file" ] || { echo ""; return 1; }
(
# shellcheck disable=SC1090
source "$rt_file"
runtime_skills_dir
)
}

install_skills() {
# Primary skills dir — set from the currently sourced runtime (drives the
# summary output and the kimaki mirror source). Multi-runtime installs
# populate every detected runtime's skills dir below, but the primary
# stays the canonical one the rest of the script refers to.
SKILLS_DIR="$(runtime_skills_dir)"

if [ "$INSTALL_SKILLS" = true ]; then
log "Phase 8.5: Installing agent skills..."
if [ "$INSTALL_SKILLS" != true ]; then
log "Phase 8.5: Skipping agent skills (--no-skills)"
return
fi

log "Phase 8.5: Installing agent skills..."

# Build the unique list of skills dirs to populate. claude-code and
# studio-code both resolve to $SITE_PATH/.claude/skills, so de-dupe.
local -a runtimes=("${DETECTED_RUNTIMES[@]:-$RUNTIME}")
local -a skills_dirs=()
local seen_dir rt dir
for rt in "${runtimes[@]}"; do
dir="$(_resolve_skills_dir_for_runtime "$rt")"
[ -n "$dir" ] || continue
local already=false
for seen_dir in "${skills_dirs[@]}"; do
[ "$seen_dir" = "$dir" ] && { already=true; break; }
done
[ "$already" = true ] || skills_dirs+=("$dir")
done

# Always guarantee the primary is in the list (for belt-and-braces when
# RUNTIME was set explicitly but somehow isn't in DETECTED_RUNTIMES).
local already=false
for seen_dir in "${skills_dirs[@]}"; do
[ "$seen_dir" = "$SKILLS_DIR" ] && { already=true; break; }
done
[ "$already" = true ] || skills_dirs+=("$SKILLS_DIR")

if [ ${#skills_dirs[@]} -gt 1 ]; then
log " Detected ${#runtimes[@]} runtime(s): ${runtimes[*]}"
log " Populating ${#skills_dirs[@]} unique skills dir(s)"
fi

# Install into every detected runtime's skills dir.
local target_dir
for target_dir in "${skills_dirs[@]}"; do
if [ ${#skills_dirs[@]} -gt 1 ]; then
log "→ Installing skills into $target_dir"
fi
SKILLS_DIR="$target_dir"
run_cmd mkdir -p "$SKILLS_DIR"

install_skills_from_local_repo

install_skills_from_repo "https://github.com/WordPress/agent-skills.git" "WordPress agent skills"

install_skills_from_repo "https://github.com/Extra-Chill/data-machine-skills.git" "Data Machine skills"
done

# Copy skills to Kimaki's directory if Kimaki is the chat bridge.
# Kimaki overrides OpenCode's skill discovery to only look in its
# own bundled skills dir, so the runtime skills dir alone isn't enough.
if [ "$CHAT_BRIDGE" = "kimaki" ]; then
if [ "$DRY_RUN" = true ]; then
KIMAKI_SKILLS_DIR="/usr/lib/node_modules/kimaki/skills"
echo -e "${BLUE}[dry-run]${NC} Would copy skills to $KIMAKI_SKILLS_DIR/ (if Kimaki installed)"
elif command -v kimaki &> /dev/null; then
KIMAKI_SKILLS_DIR="$(npm root -g 2>/dev/null)/kimaki/skills"
if [ -d "$KIMAKI_SKILLS_DIR" ]; then
for skill_dir in "$SKILLS_DIR"/*/; do
skill_name=$(basename "$skill_dir")
if [ -f "$skill_dir/SKILL.md" ]; then
cp -r "$skill_dir" "$KIMAKI_SKILLS_DIR/$skill_name"
fi
done
log "Skills also copied to Kimaki: $KIMAKI_SKILLS_DIR/"
fi
fi
# Reset SKILLS_DIR back to the primary for downstream consumers
# (kimaki mirror source, print_skills_summary, summary.sh).
SKILLS_DIR="$(runtime_skills_dir)"

# Mirror skills into the persistent kimaki-config/skills/ dir so
# post-upgrade.sh can restore them on every kimaki restart after
# `npm update -g kimaki` wipes $(npm root -g)/kimaki/skills/.
# Path mirrors the plugin-persistence pattern:
# Local: $KIMAKI_DATA_DIR/kimaki-config/skills/ (defaults to ~/.kimaki/kimaki-config/skills/)
# VPS: /opt/kimaki-config/skills/
install_skills_to_persistent_source
# Copy skills to Kimaki's directory if Kimaki is the chat bridge.
# Kimaki overrides OpenCode's skill discovery to only look in its
# own bundled skills dir, so the runtime skills dir alone isn't enough.
if [ "$CHAT_BRIDGE" = "kimaki" ]; then
if [ "$DRY_RUN" = true ]; then
KIMAKI_SKILLS_DIR="/usr/lib/node_modules/kimaki/skills"
echo -e "${BLUE}[dry-run]${NC} Would copy skills to $KIMAKI_SKILLS_DIR/ (if Kimaki installed)"
elif command -v kimaki &> /dev/null; then
KIMAKI_SKILLS_DIR="$(npm root -g 2>/dev/null)/kimaki/skills"
if [ -d "$KIMAKI_SKILLS_DIR" ]; then
for skill_dir in "$SKILLS_DIR"/*/; do
skill_name=$(basename "$skill_dir")
if [ -f "$skill_dir/SKILL.md" ]; then
cp -r "$skill_dir" "$KIMAKI_SKILLS_DIR/$skill_name"
fi
done
log "Skills also copied to Kimaki: $KIMAKI_SKILLS_DIR/"
fi
fi
else
log "Phase 8.5: Skipping agent skills (--no-skills)"

# Mirror skills into the persistent kimaki-config/skills/ dir so
# post-upgrade.sh can restore them on every kimaki restart after
# `npm update -g kimaki` wipes $(npm root -g)/kimaki/skills/.
# Path mirrors the plugin-persistence pattern:
# Local: $KIMAKI_DATA_DIR/kimaki-config/skills/ (defaults to ~/.kimaki/kimaki-config/skills/)
# VPS: /opt/kimaki-config/skills/
install_skills_to_persistent_source
fi
}

print_skills_summary() {
echo ""
log "Skills installed to $SKILLS_DIR/"
if [ "$DRY_RUN" = false ]; then
ls -1 "$SKILLS_DIR" 2>/dev/null | while read -r skill; do
log " - $skill"

# Collect unique skills dirs across detected runtimes, same logic as
# install_skills. Falls back to SKILLS_DIR if DETECTED_RUNTIMES is empty.
local -a runtimes=("${DETECTED_RUNTIMES[@]:-$RUNTIME}")
local -a skills_dirs=()
local seen_dir rt dir
for rt in "${runtimes[@]}"; do
dir="$(_resolve_skills_dir_for_runtime "$rt")"
[ -n "$dir" ] || continue
local already=false
for seen_dir in "${skills_dirs[@]}"; do
[ "$seen_dir" = "$dir" ] && { already=true; break; }
done
fi
[ "$already" = true ] || skills_dirs+=("$dir")
done
[ ${#skills_dirs[@]} -gt 0 ] || skills_dirs=("$SKILLS_DIR")

for dir in "${skills_dirs[@]}"; do
log "Skills installed to $dir/"
if [ "$DRY_RUN" = false ]; then
ls -1 "$dir" 2>/dev/null | while read -r skill; do
log " - $skill"
done
fi
done
}
38 changes: 28 additions & 10 deletions setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ INSTALL_SKILLS=true
SKILLS_ONLY=false
RUNTIME_ONLY=false
RUNTIME=""
DETECTED_RUNTIMES=()
IS_STUDIO=false

while [[ $# -gt 0 ]]; do
Expand Down Expand Up @@ -223,18 +224,35 @@ fi
# Runtime resolution
# ============================================================================

# Auto-detect runtime if not specified
if [ -z "$RUNTIME" ]; then
# Auto-detect runtime(s).
#
# RUNTIME is the "primary" runtime — the one that drives runtime_install,
# runtime_generate_config, runtime_install_hooks, and the chat-bridge default.
# First-match cascade: studio-code > claude-code > opencode.
#
# DETECTED_RUNTIMES is the list of ALL runtimes whose binary is present. On a
# machine with both claude and opencode installed, skills get installed into
# every detected runtime's skills dir (see install_skills in lib/skills.sh).
# Explicit --runtime <name> narrows both lists to that single runtime.
if [ -n "$RUNTIME" ]; then
# User passed --runtime explicitly — respect it, single-runtime mode.
DETECTED_RUNTIMES=("$RUNTIME")
else
if command -v studio &>/dev/null && [ "${IS_STUDIO:-false}" = true ]; then
RUNTIME="studio-code"
elif command -v claude &>/dev/null; then
RUNTIME="claude-code"
elif command -v opencode &>/dev/null; then
RUNTIME="opencode"
else
# Default to opencode (will be installed)
RUNTIME="opencode"
DETECTED_RUNTIMES+=("studio-code")
fi
if command -v claude &>/dev/null; then
DETECTED_RUNTIMES+=("claude-code")
fi
if command -v opencode &>/dev/null; then
DETECTED_RUNTIMES+=("opencode")
fi
if [ ${#DETECTED_RUNTIMES[@]} -eq 0 ]; then
# Nothing installed yet — default to opencode (will be installed).
DETECTED_RUNTIMES=("opencode")
fi
# Primary = first match in the cascade above.
RUNTIME="${DETECTED_RUNTIMES[0]}"
fi

# Source the selected runtime
Expand Down
27 changes: 18 additions & 9 deletions upgrade.sh
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ MULTISITE=false
MULTISITE_TYPE="subdirectory"
MODE="existing"
RUNTIME=""
DETECTED_RUNTIMES=()
IS_STUDIO=false
CHAT_BRIDGE=""

Expand Down Expand Up @@ -180,18 +181,26 @@ if [ -z "$EXISTING_WP" ]; then
fi
fi

# Auto-detect runtime (same logic as setup.sh)
if [ -z "$RUNTIME" ]; then
# Auto-detect runtime(s). Same model as setup.sh: DETECTED_RUNTIMES is the
# full list (drives multi-runtime skills install); RUNTIME is the primary
# (first-match cascade). Explicit --runtime narrows to a single runtime.
if [ -n "$RUNTIME" ]; then
DETECTED_RUNTIMES=("$RUNTIME")
else
if command -v studio &>/dev/null && [ -f "$EXISTING_WP/STUDIO.md" ]; then
RUNTIME="studio-code"
elif command -v opencode &>/dev/null; then
RUNTIME="opencode"
elif command -v claude &>/dev/null; then
RUNTIME="claude-code"
else
DETECTED_RUNTIMES+=("studio-code")
fi
if command -v claude &>/dev/null; then
DETECTED_RUNTIMES+=("claude-code")
fi
if command -v opencode &>/dev/null; then
DETECTED_RUNTIMES+=("opencode")
fi
if [ ${#DETECTED_RUNTIMES[@]} -eq 0 ]; then
warn "No runtime binary found — defaulting to opencode"
RUNTIME="opencode"
DETECTED_RUNTIMES=("opencode")
fi
RUNTIME="${DETECTED_RUNTIMES[0]}"
fi

RUNTIME_FILE="$SCRIPT_DIR/runtimes/${RUNTIME}.sh"
Expand Down
Loading