agent-skill-creator/scripts/install-template.sh
francylisboacharuto bac2b27bb8 feat: v4.0 Cross-Platform Modernization — Agent Skills Open Standard compliance
BREAKING CHANGES:
- Remove -cskill suffix from all skill names (use standard kebab-case)
- Simplify marketplace.json to only official fields (fixes Issue #5)
- SKILL.md body must be <500 lines (progressive disclosure via references/)

New features:
- Cross-platform support for 8+ platforms (Claude Code, Copilot, Cursor, Windsurf, Cline, Codex CLI, Gemini CLI)
- scripts/install-template.sh: Auto-detect platform installer with --dry-run
- scripts/validate.py: Spec compliance checker for generated skills
- scripts/security_scan.py: Security scanner for hardcoded keys and dangerous patterns
- MIGRATION.md: v3.x to v4.0 migration guide
- 6 new reference files for progressive disclosure from lean SKILL.md

Key changes:
- SKILL.md: 4,116 → 272 lines with spec-compliant YAML frontmatter
- marketplace.json: Stripped to {name, plugins} only
- article-to-prototype-cskill/ → article-to-prototype/
- stock-analyzer-cskill/ → stock-analyzer/
- Export system integrates validation + security scanning
- README.md rewritten for all supported platforms
- Phase 5 pipeline outputs SKILL.md-first, spec-compliant skills

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 14:52:09 -03:00

434 lines
15 KiB
Bash
Executable file

#!/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 -euo pipefail
# ---------------------------------------------------------------------------
# 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 <<EOF
${BOLD}install.sh${NC} — Install the ${BOLD}${SKILL_NAME}${NC} skill (v${VERSION})
USAGE
./install.sh [OPTIONS]
OPTIONS
--platform PLATFORM Explicit platform selection. One of:
claude-code, copilot, cursor, windsurf,
cline, codex, gemini
--project Install at project level (current directory)
--path PATH Custom install path (overrides detection)
--dry-run Show what would happen without making changes
-h, --help Show this help message
EXAMPLES
./install.sh # Auto-detect platform, user-level
./install.sh --project # Auto-detect platform, project-level
./install.sh --platform cursor # Force Cursor, user-level
./install.sh --path ~/my-skills/ # Custom destination
./install.sh --dry-run # Preview without installing
EOF
}
# ---------------------------------------------------------------------------
# Argument parsing
# ---------------------------------------------------------------------------
PLATFORM=""
PROJECT_LEVEL=false
CUSTOM_PATH=""
DRY_RUN=false
parse_args() {
while [ $# -gt 0 ]; do
case "$1" in
--platform)
[ $# -ge 2 ] || { error "Missing value for --platform"; exit 1; }
PLATFORM="$2"
shift 2
;;
--project)
PROJECT_LEVEL=true
shift
;;
--path)
[ $# -ge 2 ] || { error "Missing value for --path"; exit 1; }
CUSTOM_PATH="$2"
shift 2
;;
--dry-run)
DRY_RUN=true
shift
;;
-h|--help)
show_help
exit 0
;;
*)
error "Unknown option: $1"
show_help
exit 1
;;
esac
done
}
# ---------------------------------------------------------------------------
# SKILL.md validation
# ---------------------------------------------------------------------------
validate_skill_md() {
local skill_md="${SCRIPT_DIR}/SKILL.md"
if [ ! -f "$skill_md" ]; then
error "SKILL.md not found in ${SCRIPT_DIR}"
error "Every skill package must contain a valid SKILL.md file."
exit 1
fi
# Check that the file starts with YAML frontmatter delimiter
local first_line
first_line="$(head -n 1 "$skill_md")"
if [ "$first_line" != "---" ]; then
error "SKILL.md must start with YAML frontmatter (---)"
exit 1
fi
# Verify required frontmatter fields: name and description
# We look between the opening --- and closing --- for these fields.
local in_frontmatter=false
local found_name=false
local found_description=false
local line_num=0
while IFS= read -r line; do
line_num=$((line_num + 1))
if [ "$line_num" -eq 1 ]; then
in_frontmatter=true
continue
fi
if $in_frontmatter && [ "$line" = "---" ]; then
# End of frontmatter
break
fi
if $in_frontmatter; then
case "$line" in
name:*) found_name=true ;;
description:*) found_description=true ;;
esac
fi
done < "$skill_md"
if ! $found_name; then
error "SKILL.md frontmatter is missing required field: name"
exit 1
fi
if ! $found_description; then
error "SKILL.md frontmatter is missing required field: description"
exit 1
fi
success "SKILL.md validated (name and description present)"
}
# ---------------------------------------------------------------------------
# Platform detection
# ---------------------------------------------------------------------------
# Returns the detected platform slug or exits with code 2.
detect_platform() {
# If explicitly provided, validate and return it.
if [ -n "$PLATFORM" ]; then
case "$PLATFORM" in
claude-code|copilot|cursor|windsurf|cline|codex|gemini)
info "Platform explicitly set to: ${PLATFORM}"
return 0
;;
*)
error "Unknown platform: ${PLATFORM}"
error "Supported: claude-code, copilot, cursor, windsurf, cline, codex, gemini"
exit 2
;;
esac
fi
# Auto-detection: check user-level config directories.
# Order matters — check most specific / least ambiguous first.
if [ -d "${HOME}/.claude" ]; then
PLATFORM="claude-code"
elif [ -d "${HOME}/.cursor" ] || [ -d ".cursor" ]; then
PLATFORM="cursor"
elif [ -d "${HOME}/.windsurf" ]; then
PLATFORM="windsurf"
elif [ -d "${HOME}/.cline" ] || [ -d ".clinerules" ]; then
PLATFORM="cline"
elif [ -d "${HOME}/.codex" ]; then
PLATFORM="codex"
elif [ -d "${HOME}/.gemini" ]; then
PLATFORM="gemini"
elif [ -d "${HOME}/.copilot" ] || [ -d ".github" ]; then
PLATFORM="copilot"
else
error "Could not auto-detect any supported AI coding platform."
error "Use --platform PLATFORM to specify one explicitly."
error "Supported: claude-code, copilot, cursor, windsurf, cline, codex, gemini"
exit 2
fi
info "Auto-detected platform: ${PLATFORM}"
}
# ---------------------------------------------------------------------------
# Install path resolution
# ---------------------------------------------------------------------------
# Sets INSTALL_DIR based on platform, project-level flag, or custom path.
resolve_install_path() {
# Custom path takes precedence over everything.
if [ -n "$CUSTOM_PATH" ]; then
INSTALL_DIR="${CUSTOM_PATH}"
info "Using custom install path: ${INSTALL_DIR}"
return 0
fi
local base=""
if $PROJECT_LEVEL; then
# Project-level: paths are relative to the current working directory.
case "$PLATFORM" in
claude-code) base=".claude/skills" ;;
copilot) base=".github/skills" ;;
cursor) base=".cursor/rules" ;;
windsurf) base=".windsurf/skills" ;;
cline) base=".clinerules" ;;
codex) base=".codex/skills" ;;
gemini) base=".gemini/skills" ;;
esac
INSTALL_DIR="$(pwd)/${base}/${SKILL_NAME}"
else
# User-level: paths are under the home directory.
case "$PLATFORM" in
claude-code) base="${HOME}/.claude/skills" ;;
copilot) base="${HOME}/.copilot/skills" ;;
cursor) base="${HOME}/.cursor/rules" ;;
windsurf) base="${HOME}/.windsurf/skills" ;;
cline) base="${HOME}/.cline/rules" ;;
codex) base="${HOME}/.codex/skills" ;;
gemini) base="${HOME}/.gemini/skills" ;;
esac
INSTALL_DIR="${base}/${SKILL_NAME}"
fi
info "Install directory: ${INSTALL_DIR}"
}
# ---------------------------------------------------------------------------
# File installation
# ---------------------------------------------------------------------------
install_files() {
# Collect the list of files to install.
# We copy everything in SCRIPT_DIR except the install script itself.
local file_count=0
local install_script_name
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
local fname
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
local fname
fname="$(basename "$file")"
[ "$fname" = "." ] || [ "$fname" = ".." ] && continue
info "Would copy: ${fname}"
file_count=$((file_count + 1))
done
printf "\n"
info "Total files: ${file_count}"
return 0
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
local fname
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
local fname
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}"
}
# ---------------------------------------------------------------------------
# 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.md${NC}\n"
printf " 3. Use trigger phrases to invoke the skill.\n"
;;
windsurf)
printf "To activate the skill in Windsurf:\n"
printf " 1. Open your project in Windsurf.\n"
printf " 2. The skill is available at:\n"
printf " ${BOLD}${INSTALL_DIR}/SKILL.md${NC}\n"
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.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. Reference the skill in your instructions.\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"
;;
esac
printf "\n"
}
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
main() {
printf "${BOLD}Installing skill: ${SKILL_NAME}${NC}\n"
printf "%-40s\n" "----------------------------------------"
parse_args "$@"
validate_skill_md
detect_platform
resolve_install_path
install_files
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
exit 0
}
main "$@"