#!/bin/sh # install-template.sh — Cross-platform skill installation script # This file is a template. During skill generation, {{SKILL_NAME}} is replaced # with the actual skill name and the result is shipped as install.sh inside # every generated skill package. # # POSIX-compatible (works in bash, dash, zsh, ash, etc.) # Exit codes: # 0 — Success # 1 — Validation failed (missing or malformed SKILL.md) # 2 — Platform not detected # 3 — Permission denied set -eu # --------------------------------------------------------------------------- # Constants # --------------------------------------------------------------------------- SKILL_NAME="{{SKILL_NAME}}" VERSION="1.0.0" SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" # --------------------------------------------------------------------------- # Colors (disabled when stdout is not a terminal) # --------------------------------------------------------------------------- if [ -t 1 ]; then RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' BOLD='\033[1m' NC='\033[0m' else RED='' GREEN='' YELLOW='' BLUE='' BOLD='' NC='' fi # --------------------------------------------------------------------------- # Logging helpers # --------------------------------------------------------------------------- info() { printf "${BLUE}[INFO]${NC} %s\n" "$1"; } success() { printf "${GREEN}[OK]${NC} %s\n" "$1"; } warn() { printf "${YELLOW}[WARN]${NC} %s\n" "$1"; } error() { printf "${RED}[ERROR]${NC} %s\n" "$1" >&2; } # --------------------------------------------------------------------------- # Usage / help # --------------------------------------------------------------------------- show_help() { cat < "$mdc_file" < "${global_file}.tmp" mv "${global_file}.tmp" "$global_file" fi # Append new block cat >> "$global_file" < ${body} WSEOF success "Appended to Windsurf global_rules.md" else # Project-level: create a .md rule file rule_file="${target_dir}/${SKILL_NAME}.md" if $DRY_RUN; then info "Would generate Windsurf rule: ${rule_file}" return 0 fi mkdir -p "$target_dir" printf '%s\n' "$body" > "$rule_file" success "Generated Windsurf rule: ${rule_file}" fi } # Generate plain markdown (strip YAML frontmatter) for Cline/Roo/Trae generate_plain_rule() { target_dir="$1" filename="$2" skill_md="${SCRIPT_DIR}/SKILL.md" plain_file="${target_dir}/${filename}" if $DRY_RUN; then info "Would generate plain rule: ${plain_file}" return 0 fi mkdir -p "$target_dir" awk 'BEGIN{c=0} /^---$/{c++;next} c>=2{print}' "$skill_md" > "$plain_file" success "Generated plain rule: ${plain_file}" } # --------------------------------------------------------------------------- # Universal .agents/skills/ secondary install (symlink or copy) # --------------------------------------------------------------------------- install_universal_secondary() { # Skip if primary target is already .agents/ case "$PLATFORM" in codex|antigravity|universal) return 0 ;; esac universal_dir="${HOME}/.agents/skills/${SKILL_NAME}" if $DRY_RUN; then info "Would create universal symlink: ${universal_dir} -> ${INSTALL_DIR}" return 0 fi mkdir -p "${HOME}/.agents/skills" # Remove existing entry if present if [ -e "$universal_dir" ] || [ -L "$universal_dir" ]; then rm -rf "$universal_dir" fi # Try symlink first, fallback to copy if ln -s "$INSTALL_DIR" "$universal_dir" 2>/dev/null; then success "Universal symlink: ${universal_dir} -> ${INSTALL_DIR}" elif cp -R "$INSTALL_DIR" "$universal_dir" 2>/dev/null; then success "Universal copy: ${universal_dir}" else warn "Could not create universal path at ${universal_dir}" fi } # --------------------------------------------------------------------------- # File installation # --------------------------------------------------------------------------- install_files() { # Collect the list of files to install. # We copy everything in SCRIPT_DIR except the install script itself. file_count=0 install_script_name="$(basename "$0")" if $DRY_RUN; then printf "\n${BOLD}Dry-run mode — no files will be copied.${NC}\n\n" info "Would create directory: ${INSTALL_DIR}" for file in "${SCRIPT_DIR}"/*; do [ -e "$file" ] || continue fname="$(basename "$file")" # Skip the install script itself [ "$fname" = "$install_script_name" ] && continue info "Would copy: ${fname}" file_count=$((file_count + 1)) done # Also handle dotfiles for file in "${SCRIPT_DIR}"/.*; do [ -e "$file" ] || continue fname="$(basename "$file")" if [ "$fname" = "." ] || [ "$fname" = ".." ]; then continue; fi info "Would copy: ${fname}" file_count=$((file_count + 1)) done printf "\n" info "Total files: ${file_count}" return 0 fi # Clean existing install for idempotency (remove stale files from prior installs). if [ -d "$INSTALL_DIR" ]; then rm -rf "$INSTALL_DIR" fi # Create destination directory. if ! mkdir -p "$INSTALL_DIR" 2>/dev/null; then error "Cannot create directory: ${INSTALL_DIR}" error "Check file permissions or run with appropriate privileges." exit 3 fi # Copy files. for file in "${SCRIPT_DIR}"/*; do [ -e "$file" ] || continue fname="$(basename "$file")" [ "$fname" = "$install_script_name" ] && continue if ! cp -R "$file" "${INSTALL_DIR}/" 2>/dev/null; then error "Failed to copy ${fname} to ${INSTALL_DIR}/" error "Check file permissions." exit 3 fi file_count=$((file_count + 1)) done # Copy dotfiles (if any). for file in "${SCRIPT_DIR}"/.*; do [ -e "$file" ] || continue fname="$(basename "$file")" [ "$fname" = "." ] || [ "$fname" = ".." ] && continue if ! cp -R "$file" "${INSTALL_DIR}/" 2>/dev/null; then error "Failed to copy ${fname} to ${INSTALL_DIR}/" error "Check file permissions." exit 3 fi file_count=$((file_count + 1)) done success "Copied ${file_count} file(s) to ${INSTALL_DIR}" } # --------------------------------------------------------------------------- # Run format adapters based on platform # --------------------------------------------------------------------------- run_adapters() { case "$PLATFORM" in cursor) generate_cursor_mdc "$INSTALL_DIR" ;; windsurf) if $PROJECT_LEVEL; then generate_windsurf_rule "$(pwd)/.windsurf/rules" "false" else generate_windsurf_rule "" "true" fi ;; cline) generate_plain_rule "$INSTALL_DIR" "${SKILL_NAME}.md" ;; roo-code) generate_plain_rule "$INSTALL_DIR" "${SKILL_NAME}.md" ;; trae) generate_plain_rule "$INSTALL_DIR" "${SKILL_NAME}.md" ;; esac } # --------------------------------------------------------------------------- # Activation instructions # --------------------------------------------------------------------------- print_activation_instructions() { if $DRY_RUN; then return 0 fi printf "\n${GREEN}${BOLD}Installation complete!${NC}\n\n" case "$PLATFORM" in claude-code) printf "To activate the skill in Claude Code:\n" printf " 1. Start a new Claude Code session.\n" printf " 2. The skill will be loaded automatically from:\n" printf " ${BOLD}${INSTALL_DIR}/SKILL.md${NC}\n" printf " 3. Use trigger phrases defined in the skill's description.\n" ;; copilot) printf "To activate the skill in GitHub Copilot:\n" printf " 1. Open your project in VS Code or the GitHub CLI.\n" printf " 2. The skill is available at:\n" printf " ${BOLD}${INSTALL_DIR}/SKILL.md${NC}\n" printf " 3. Reference the skill in your Copilot instructions.\n" ;; cursor) printf "To activate the skill in Cursor:\n" printf " 1. Open your project in Cursor.\n" printf " 2. The rule is loaded automatically from:\n" printf " ${BOLD}${INSTALL_DIR}/${SKILL_NAME}.mdc${NC}\n" printf " 3. Use trigger phrases to invoke the skill.\n" ;; windsurf) printf "To activate the skill in Windsurf:\n" if $PROJECT_LEVEL; then printf " 1. Open your project in Windsurf.\n" printf " 2. The rule is loaded from .windsurf/rules/\n" else printf " 1. Open Windsurf.\n" printf " 2. The skill was added to global_rules.md.\n" fi printf " 3. Use trigger phrases to invoke the skill.\n" ;; cline) printf "To activate the skill in Cline:\n" printf " 1. Open your project in VS Code with Cline.\n" printf " 2. The rule is loaded from:\n" printf " ${BOLD}${INSTALL_DIR}/${SKILL_NAME}.md${NC}\n" printf " 3. Cline will pick up the rule automatically.\n" ;; codex) printf "To activate the skill in OpenAI Codex CLI:\n" printf " 1. Start a new Codex CLI session.\n" printf " 2. The skill is available at:\n" printf " ${BOLD}${INSTALL_DIR}/SKILL.md${NC}\n" printf " 3. Codex reads from ~/.agents/skills/ automatically.\n" ;; gemini) printf "To activate the skill in Gemini CLI:\n" printf " 1. Start a new Gemini CLI session.\n" printf " 2. The skill is available at:\n" printf " ${BOLD}${INSTALL_DIR}/SKILL.md${NC}\n" printf " 3. The skill will be loaded automatically.\n" ;; kiro) printf "To activate the skill in Kiro:\n" printf " 1. Open your project in Kiro.\n" printf " 2. The skill is available at:\n" printf " ${BOLD}${INSTALL_DIR}/SKILL.md${NC}\n" printf " 3. Kiro reads from .kiro/skills/ automatically.\n" ;; trae) printf "To activate the skill in Trae:\n" printf " 1. Open your project in Trae.\n" printf " 2. The rule is loaded from:\n" printf " ${BOLD}${INSTALL_DIR}/${SKILL_NAME}.md${NC}\n" printf " 3. Use trigger phrases to invoke the skill.\n" ;; goose) printf "To activate the skill in Goose:\n" printf " 1. Start a new Goose session.\n" printf " 2. The skill is available at:\n" printf " ${BOLD}${INSTALL_DIR}/SKILL.md${NC}\n" printf " 3. Goose reads from ~/.config/goose/skills/ automatically.\n" ;; opencode) printf "To activate the skill in OpenCode:\n" printf " 1. Start a new OpenCode session.\n" printf " 2. The skill is available at:\n" printf " ${BOLD}${INSTALL_DIR}/SKILL.md${NC}\n" printf " 3. OpenCode reads from ~/.config/opencode/skills/ automatically.\n" ;; roo-code) printf "To activate the skill in Roo Code:\n" printf " 1. Open your project in VS Code with Roo Code.\n" printf " 2. The rule is loaded from:\n" printf " ${BOLD}${INSTALL_DIR}/${SKILL_NAME}.md${NC}\n" printf " 3. Roo Code will pick up the rule automatically.\n" ;; antigravity) printf "To activate the skill in Antigravity:\n" printf " 1. Open your project.\n" printf " 2. The skill is available at:\n" printf " ${BOLD}${INSTALL_DIR}/SKILL.md${NC}\n" printf " 3. Antigravity reads from .agents/skills/ automatically.\n" ;; universal) printf "The skill is installed at the universal path:\n" printf " ${BOLD}${INSTALL_DIR}/SKILL.md${NC}\n\n" printf "Tools that read ~/.agents/skills/ (Codex CLI, Gemini CLI,\n" printf "Kiro, Antigravity, and others) will discover it automatically.\n" ;; esac printf "\n" } # --------------------------------------------------------------------------- # Install for a single platform # --------------------------------------------------------------------------- install_single() { detect_platform resolve_install_path install_files run_adapters install_universal_secondary print_activation_instructions if $DRY_RUN; then info "Dry run complete. No changes were made." else success "Skill '${SKILL_NAME}' installed successfully for ${PLATFORM}." fi } # --------------------------------------------------------------------------- # Install for all detected platforms (--all) # --------------------------------------------------------------------------- install_all() { detect_all_platforms info "Installing to all detected platforms: ${ALL_PLATFORMS}" printf "%-40s\n" "----------------------------------------" installed_count=0 first_non_agents_dir="" for plat in $ALL_PLATFORMS; do printf "\n" info "--- Installing for: ${plat} ---" PLATFORM="$plat" resolve_install_path install_files run_adapters installed_count=$((installed_count + 1)) # Remember the first non-.agents/ install dir for universal symlink if [ -z "$first_non_agents_dir" ]; then case "$plat" in codex|antigravity|universal) ;; *) first_non_agents_dir="$INSTALL_DIR" ;; esac fi done # Create universal symlink from the first non-.agents/ install if [ -n "$first_non_agents_dir" ]; then INSTALL_DIR="$first_non_agents_dir" install_universal_secondary fi printf "\n" if $DRY_RUN; then info "Dry run complete. No changes were made." else success "Skill '${SKILL_NAME}' installed to ${installed_count} platform(s)." fi } # --------------------------------------------------------------------------- # Main # --------------------------------------------------------------------------- main() { printf "${BOLD}Installing skill: ${SKILL_NAME}${NC}\n" printf "%-40s\n" "----------------------------------------" parse_args "$@" validate_skill_md if $INSTALL_ALL; then install_all else install_single fi exit 0 } main "$@"