From 795f1846f7557be9a2e326d299b225616ce29eb6 Mon Sep 17 00:00:00 2001 From: Alireza Rezvani <5697919+alirezarezvani@users.noreply.github.com> Date: Wed, 11 Mar 2026 17:29:48 +0100 Subject: [PATCH] feat: multi-tool skill conversion (Cursor, Aider, Windsurf, etc.) (#326) (#327) * feat: add multi-tool conversion scripts (7 tools) - convert.sh: converts 156 skills to Antigravity, Cursor, Aider, Kilo Code, Windsurf, OpenCode, Augment formats - install.sh: installs converted skills to each tool's expected location - Pure bash/awk, no external deps, macOS + Linux compatible - Per-tool README with install/verify/update docs - integrations/ added to .gitignore (generated output) * docs: add multi-tool conversion to README - New section documenting Cursor, Aider, Kilo Code, Windsurf, OpenCode, Augment support - Install commands for each tool - Link to integrations/ for per-tool docs - Quick start: convert.sh + install.sh workflow --------- Co-authored-by: Leo --- .gitignore | 4 +- README.md | 40 +++ scripts/convert.sh | 593 +++++++++++++++++++++++++++++++++++++++++++++ scripts/install.sh | 275 +++++++++++++++++++++ 4 files changed, 911 insertions(+), 1 deletion(-) create mode 100755 scripts/convert.sh create mode 100755 scripts/install.sh diff --git a/.gitignore b/.gitignore index e5b1c02..cc8e47a 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,6 @@ archive/ # MkDocs build output site/ -.playwright-mcp/ \ No newline at end of file +.playwright-mcp/ +# Generated integration files (regenerate with ./scripts/convert.sh) +integrations/ diff --git a/README.md b/README.md index a99fbd3..c101023 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,46 @@ git clone https://github.com/alirezarezvani/claude-skills.git --- +## Multi-Tool Support (New) + +**Convert all 156 skills to 7 AI coding tools** with a single script: + +| Tool | Format | Install | +|------|--------|---------| +| **Cursor** | `.mdc` rules | `./scripts/install.sh --tool cursor --target .` | +| **Aider** | `CONVENTIONS.md` | `./scripts/install.sh --tool aider --target .` | +| **Kilo Code** | `.kilocode/rules/` | `./scripts/install.sh --tool kilocode --target .` | +| **Windsurf** | `.windsurf/skills/` | `./scripts/install.sh --tool windsurf --target .` | +| **OpenCode** | `.opencode/skills/` | `./scripts/install.sh --tool opencode --target .` | +| **Augment** | `.augment/rules/` | `./scripts/install.sh --tool augment --target .` | +| **Antigravity** | `~/.gemini/antigravity/skills/` | `./scripts/install.sh --tool antigravity` | + +**How it works:** + +```bash +# 1. Convert all skills to all tools (takes ~15 seconds) +./scripts/convert.sh --tool all + +# 2. Install into your project (with confirmation) +./scripts/install.sh --tool cursor --target /path/to/project + +# Or use --force to skip confirmation: +./scripts/install.sh --tool aider --target . --force + +# 3. Verify +find .cursor/rules -name "*.mdc" | wc -l # Should show 156 +``` + +**Each tool gets:** +- ✅ All 156 skills converted to native format +- ✅ Per-tool README with install/verify/update steps +- ✅ Support for scripts, references, templates where applicable +- ✅ Zero manual conversion work + +See [integrations/](integrations/) for tool-specific documentation and pre-generated outputs. + +--- + ## Skills Overview **177 skills across 9 domains:** diff --git a/scripts/convert.sh b/scripts/convert.sh new file mode 100755 index 0000000..eff95ea --- /dev/null +++ b/scripts/convert.sh @@ -0,0 +1,593 @@ +#!/usr/bin/env bash +# Usage: +# ./scripts/convert.sh [--tool ] [--out ] [--help] +# +# Tools: antigravity, cursor, aider, kilocode, windsurf, opencode, augment, all +# Default: all + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" + +TOOL="all" +OUT_BASE="${REPO_ROOT}/integrations" +TODAY="$(date +%F)" + +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +BLUE='\033[0;34m' +NC='\033[0m' + +ok() { + echo -e "${GREEN}[OK]${NC} $*" +} + +warn() { + echo -e "${YELLOW}[!!]${NC} $*" +} + +err() { + echo -e "${RED}[ERR]${NC} $*" >&2 +} + +info() { + echo -e "${BLUE}[*]${NC} $*" +} + +usage() { + cat <<'USAGE' +Usage: + ./scripts/convert.sh [--tool ] [--out ] [--help] + +Tools: + antigravity, cursor, aider, kilocode, windsurf, opencode, augment, all + +Defaults: + --tool all + --out /integrations +USAGE +} + +is_valid_tool() { + case "$1" in + antigravity|cursor|aider|kilocode|windsurf|opencode|augment|all) return 0 ;; + *) return 1 ;; + esac +} + +yaml_unquote() { + local value="$1" + value="${value#\"}" + value="${value%\"}" + value="${value#\'}" + value="${value%\'}" + printf '%s' "$value" +} + +yaml_quote() { + local value="$1" + value="${value//\\/\\\\}" + value="${value//\"/\\\"}" + printf '"%s"' "$value" +} + +init_count_vars() { + converted_antigravity=0 + converted_cursor=0 + converted_aider=0 + converted_kilocode=0 + converted_windsurf=0 + converted_opencode=0 + converted_augment=0 + + skipped_antigravity=0 + skipped_cursor=0 + skipped_aider=0 + skipped_kilocode=0 + skipped_windsurf=0 + skipped_opencode=0 + skipped_augment=0 +} + +inc_converted() { + local t="$1" + case "$t" in + antigravity) converted_antigravity=$((converted_antigravity + 1)) ;; + cursor) converted_cursor=$((converted_cursor + 1)) ;; + aider) converted_aider=$((converted_aider + 1)) ;; + kilocode) converted_kilocode=$((converted_kilocode + 1)) ;; + windsurf) converted_windsurf=$((converted_windsurf + 1)) ;; + opencode) converted_opencode=$((converted_opencode + 1)) ;; + augment) converted_augment=$((converted_augment + 1)) ;; + esac +} + +inc_skipped() { + local t="$1" + case "$t" in + antigravity) skipped_antigravity=$((skipped_antigravity + 1)) ;; + cursor) skipped_cursor=$((skipped_cursor + 1)) ;; + aider) skipped_aider=$((skipped_aider + 1)) ;; + kilocode) skipped_kilocode=$((skipped_kilocode + 1)) ;; + windsurf) skipped_windsurf=$((skipped_windsurf + 1)) ;; + opencode) skipped_opencode=$((skipped_opencode + 1)) ;; + augment) skipped_augment=$((skipped_augment + 1)) ;; + esac +} + +get_converted() { + local t="$1" + case "$t" in + antigravity) echo "$converted_antigravity" ;; + cursor) echo "$converted_cursor" ;; + aider) echo "$converted_aider" ;; + kilocode) echo "$converted_kilocode" ;; + windsurf) echo "$converted_windsurf" ;; + opencode) echo "$converted_opencode" ;; + augment) echo "$converted_augment" ;; + esac +} + +get_skipped() { + local t="$1" + case "$t" in + antigravity) echo "$skipped_antigravity" ;; + cursor) echo "$skipped_cursor" ;; + aider) echo "$skipped_aider" ;; + kilocode) echo "$skipped_kilocode" ;; + windsurf) echo "$skipped_windsurf" ;; + opencode) echo "$skipped_opencode" ;; + augment) echo "$skipped_augment" ;; + esac +} + +# Prints frontmatter fields as: namedescription +extract_frontmatter() { + local file="$1" + awk ' + BEGIN { + in_fm = 0 + name = "" + desc = "" + in_desc_block = 0 + } + + function ltrim(s) { + sub(/^[[:space:]]+/, "", s) + return s + } + + function rtrim(s) { + sub(/[[:space:]]+$/, "", s) + return s + } + + function trim(s) { + return rtrim(ltrim(s)) + } + + function dequote(s, first, last) { + s = trim(s) + first = substr(s, 1, 1) + last = substr(s, length(s), 1) + if ((first == "\"" && last == "\"") || (first == "\047" && last == "\047")) { + s = substr(s, 2, length(s) - 2) + if (first == "\"") { + gsub(/\\\\/, "\\", s) + gsub(/\\"/, "\"", s) + } else { + gsub(/\047\047/, "\047", s) + } + } + return s + } + + function append_desc(chunk) { + chunk = trim(chunk) + if (chunk == "") return + if (desc == "") { + desc = chunk + } else { + desc = desc " " chunk + } + } + + NR == 1 { + if ($0 == "---") { + in_fm = 1 + next + } + next + } + + in_fm == 1 { + if ($0 == "---") { + in_fm = 0 + next + } + + if (in_desc_block == 1) { + if ($0 ~ /^[[:space:]]+/ || $0 == "") { + line = $0 + sub(/^[[:space:]]+/, "", line) + append_desc(line) + next + } + in_desc_block = 0 + } + + if ($0 ~ /^name:[[:space:]]*/) { + line = $0 + sub(/^name:[[:space:]]*/, "", line) + name = dequote(line) + next + } + + if ($0 ~ /^description:[[:space:]]*/) { + line = $0 + sub(/^description:[[:space:]]*/, "", line) + line = trim(line) + + if (line ~ /^>[+-]?$/ || line ~ /^\|[+-]?$/) { + in_desc_block = 1 + next + } + + desc = dequote(line) + next + } + + next + } + + END { + printf "%s\t%s\n", trim(name), trim(desc) + } + ' "$file" +} + +extract_body() { + local file="$1" + awk ' + BEGIN { in_fm = 0 } + + NR == 1 { + if ($0 == "---") { + in_fm = 1 + next + } + print + next + } + + in_fm == 1 { + if ($0 == "---") { + in_fm = 0 + next + } + next + } + + { print } + ' "$file" +} + +copy_supporting_dirs() { + local src_dir="$1" + local dst_dir="$2" + local d + for d in scripts references templates; do + if [[ -d "${src_dir}/${d}" ]]; then + cp -R "${src_dir}/${d}" "${dst_dir}/${d}" + fi + done +} + +append_aider_skill() { + local name="$1" + local description="$2" + local body_file="$3" + + { + echo "---" + echo + echo "## ${name}" + echo "> ${description}" + echo + cat "$body_file" + echo + } >> "${AIDER_FILE}" +} + +tool_title() { + case "$1" in + antigravity) echo "Antigravity" ;; + cursor) echo "Cursor" ;; + aider) echo "Aider" ;; + kilocode) echo "Kilo Code" ;; + windsurf) echo "Windsurf" ;; + opencode) echo "OpenCode" ;; + augment) echo "Augment" ;; + esac +} + +write_tool_readme() { + local tool="$1" + local count="$2" + local out_dir="${OUT_BASE}/${tool}" + + local format_line="" + local manual_install="" + local script_install="./scripts/install.sh --tool ${tool}" + local verify_step="" + local update_step="" + + case "$tool" in + antigravity) + format_line='Directory skill bundles: `SKILL.md` with Antigravity frontmatter (`risk`, `source`, `date_added`) plus copied `scripts/`, `references/`, `templates/` when present.' + manual_install='Copy each folder from `integrations/antigravity//` to `~/.gemini/antigravity/skills//`.' + verify_step='Run `find ~/.gemini/antigravity/skills -name "SKILL.md" | wc -l` and confirm the count, then check your Gemini/Antigravity skill list.' + update_step='Re-run `./scripts/convert.sh --tool antigravity` and then reinstall with `./scripts/install.sh --tool antigravity`.' + ;; + cursor) + format_line='Flat Cursor rules: `rules/.mdc` with Cursor-compatible frontmatter (`description`, `globs`, `alwaysApply`).' + manual_install='Copy `integrations/cursor/rules/*.mdc` into your project `.cursor/rules/` directory.' + verify_step='Open Cursor rules panel or run `find .cursor/rules -name "*.mdc" | wc -l` in your project.' + update_step='Re-run `./scripts/convert.sh --tool cursor` and then reinstall with `./scripts/install.sh --tool cursor --target `.' + ;; + aider) + format_line='Single conventions file: `CONVENTIONS.md` concatenating all skills with separators and per-skill sections.' + manual_install='Copy `integrations/aider/CONVENTIONS.md` into your project root.' + verify_step='Run `aider --read CONVENTIONS.md` and confirm sections load (search for `## ` entries).' + update_step='Re-run `./scripts/convert.sh --tool aider` and reinstall with `./scripts/install.sh --tool aider --target `.' + ;; + kilocode) + format_line='Flat markdown rules: `rules/.md` with a title/description header and no frontmatter.' + manual_install='Copy `integrations/kilocode/rules/*.md` into your project `.kilocode/rules/` directory.' + verify_step='Run `find .kilocode/rules -name "*.md" | wc -l` in your project and confirm expected count.' + update_step='Re-run `./scripts/convert.sh --tool kilocode` and reinstall with `./scripts/install.sh --tool kilocode --target `.' + ;; + windsurf) + format_line='Directory skill bundles: `skills//SKILL.md` using Windsurf-compatible SKILL frontmatter (`name`, `description`) plus copied support folders.' + manual_install='Copy each folder from `integrations/windsurf/skills//` to `.windsurf/skills//` in your project.' + verify_step='Run `find .windsurf/skills -name "SKILL.md" | wc -l` and verify skills are listed in Windsurf.' + update_step='Re-run `./scripts/convert.sh --tool windsurf` and reinstall with `./scripts/install.sh --tool windsurf --target `.' + ;; + opencode) + format_line='Directory skill bundles: `skills//SKILL.md` with `compatibility: opencode` plus copied support folders.' + manual_install='Copy each folder from `integrations/opencode/skills//` to `.opencode/skills//` in your project.' + verify_step='Run `find .opencode/skills -name "SKILL.md" | wc -l` and confirm OpenCode shows installed skills.' + update_step='Re-run `./scripts/convert.sh --tool opencode` and reinstall with `./scripts/install.sh --tool opencode --target `.' + ;; + augment) + format_line='Flat Augment rules: `rules/.md` with Augment frontmatter (`type: auto`, `description`).' + manual_install='Copy `integrations/augment/rules/*.md` into your project `.augment/rules/` directory.' + verify_step='Run `find .augment/rules -name "*.md" | wc -l` and check rules appear in Augment.' + update_step='Re-run `./scripts/convert.sh --tool augment` and reinstall with `./scripts/install.sh --tool augment --target `.' + ;; + esac + + { + printf '# %s Integration\n\n' "$(tool_title "$tool")" + printf '![%s](https://img.shields.io/badge/Integration-%s-0A66C2)\n\n' "$tool" "$tool" + printf 'This directory contains converted Claude Skills for **%s**.\n\n' "$(tool_title "$tool")" + printf '## Included Skills\n\n' + printf -- '- **%s** skills generated from this repository.\n\n' "$count" + printf '## Format\n\n' + printf '%s\n\n' "$format_line" + printf '## Install\n\n' + printf '### Manual\n\n' + printf '%s\n\n' "$manual_install" + printf '### Script\n\n' + printf '```bash\n' + printf 'git clone https://github.com/alirezarezvani/claude-skills.git\n' + printf 'cd claude-skills\n' + printf '%s\n' "$script_install" + printf '```\n\n' + printf '## Verify\n\n' + printf '%s\n\n' "$verify_step" + printf '## Update\n\n' + printf '%s\n\n' "$update_step" + printf '## Source Repository\n\n' + printf -- '- https://github.com/alirezarezvani/claude-skills\n' + } > "${out_dir}/README.md" +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --tool) + TOOL="${2:-}" + shift 2 + ;; + --out) + OUT_BASE="${2:-}" + shift 2 + ;; + --help|-h) + usage + exit 0 + ;; + *) + err "Unknown argument: $1" + usage + exit 1 + ;; + esac +done + +if ! is_valid_tool "$TOOL"; then + err "Invalid --tool value: ${TOOL}" + usage + exit 1 +fi + +TOOLS="antigravity cursor aider kilocode windsurf opencode augment" +if [[ "$TOOL" != "all" ]]; then + TOOLS="$TOOL" +fi + +SKILLS_TMP="$(mktemp)" +( + cd "$REPO_ROOT" + find . -mindepth 3 -maxdepth 3 -type f -name 'SKILL.md' -not -path './.git/*' | sort +) > "$SKILLS_TMP" + +TOTAL_CANDIDATES="$(wc -l < "$SKILLS_TMP" | tr -d ' ')" +if [[ "$TOTAL_CANDIDATES" -eq 0 ]]; then + err "No skills found matching */*/SKILL.md" + rm -f "$SKILLS_TMP" + exit 1 +fi + +info "Found ${TOTAL_CANDIDATES} candidate skills" + +for t in $TOOLS; do + rm -rf "${OUT_BASE}/${t}" + mkdir -p "${OUT_BASE}/${t}" + if [[ "$t" == "cursor" || "$t" == "kilocode" || "$t" == "augment" ]]; then + mkdir -p "${OUT_BASE}/${t}/rules" + fi + if [[ "$t" == "windsurf" || "$t" == "opencode" ]]; then + mkdir -p "${OUT_BASE}/${t}/skills" + fi + if [[ "$t" == "aider" ]]; then + AIDER_FILE="${OUT_BASE}/aider/CONVENTIONS.md" + { + echo "# Claude Skills — Aider Conventions" + echo "> Auto-generated from claude-skills. Do not edit manually." + echo "> Generated: ${TODAY}" + echo + } > "$AIDER_FILE" + fi + ok "Prepared output directory: ${OUT_BASE}/${t}" +done + +init_count_vars + +while IFS= read -r rel_path; do + src="${REPO_ROOT}/${rel_path#./}" + src_dir="$(dirname "$src")" + + meta="$(extract_frontmatter "$src")" + name="${meta%%$'\t'*}" + description="${meta#*$'\t'}" + + name="$(yaml_unquote "$name")" + description="$(yaml_unquote "$description")" + + if [[ -z "$name" || -z "$description" ]]; then + for t in $TOOLS; do + inc_skipped "$t" + done + warn "Skipping invalid frontmatter: ${rel_path}" + continue + fi + + body_tmp="$(mktemp)" + extract_body "$src" > "$body_tmp" + + for t in $TOOLS; do + case "$t" in + antigravity) + out_dir="${OUT_BASE}/antigravity/${name}" + mkdir -p "$out_dir" + { + echo "---" + echo "name: $(yaml_quote "$name")" + echo "description: $(yaml_quote "$description")" + echo "risk: low" + echo "source: community" + echo "date_added: '${TODAY}'" + echo "---" + cat "$body_tmp" + } > "${out_dir}/SKILL.md" + copy_supporting_dirs "$src_dir" "$out_dir" + ;; + cursor) + out_file="${OUT_BASE}/cursor/rules/${name}.mdc" + { + echo "---" + echo "description: $(yaml_quote "$description")" + echo "globs:" + echo "alwaysApply: false" + echo "---" + cat "$body_tmp" + } > "$out_file" + ;; + aider) + append_aider_skill "$name" "$description" "$body_tmp" + ;; + kilocode) + out_file="${OUT_BASE}/kilocode/rules/${name}.md" + { + echo "# ${name}" + echo "> ${description}" + echo + cat "$body_tmp" + } > "$out_file" + ;; + windsurf) + out_dir="${OUT_BASE}/windsurf/skills/${name}" + mkdir -p "$out_dir" + { + echo "---" + echo "name: $(yaml_quote "$name")" + echo "description: $(yaml_quote "$description")" + echo "---" + cat "$body_tmp" + } > "${out_dir}/SKILL.md" + copy_supporting_dirs "$src_dir" "$out_dir" + ;; + opencode) + out_dir="${OUT_BASE}/opencode/skills/${name}" + mkdir -p "$out_dir" + { + echo "---" + echo "name: $(yaml_quote "$name")" + echo "description: $(yaml_quote "$description")" + echo "compatibility: opencode" + echo "---" + cat "$body_tmp" + } > "${out_dir}/SKILL.md" + copy_supporting_dirs "$src_dir" "$out_dir" + ;; + augment) + out_file="${OUT_BASE}/augment/rules/${name}.md" + { + echo "---" + echo "type: auto" + echo "description: $(yaml_quote "$description")" + echo "---" + cat "$body_tmp" + } > "$out_file" + ;; + *) + err "Unhandled tool: ${t}" + rm -f "$body_tmp" "$SKILLS_TMP" + exit 1 + ;; + esac + + inc_converted "$t" + done + + rm -f "$body_tmp" + ok "Converted ${name} (${rel_path})" +done < "$SKILLS_TMP" + +rm -f "$SKILLS_TMP" + +for t in $TOOLS; do + write_tool_readme "$t" "$(get_converted "$t")" +done + +echo +info "Conversion summary" +for t in $TOOLS; do + echo " ${t}: $(get_converted "$t") converted, $(get_skipped "$t") skipped" +done + +echo +ok "Done" diff --git a/scripts/install.sh b/scripts/install.sh new file mode 100755 index 0000000..f15bcf9 --- /dev/null +++ b/scripts/install.sh @@ -0,0 +1,275 @@ +#!/usr/bin/env bash +# Usage: +# ./scripts/install.sh --tool [--target ] [--force] [--help] +# +# Installs converted skills into the appropriate location. +# --target overrides the default install path. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" + +TOOL="" +TARGET="" +FORCE=false + +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +NC='\033[0m' + +ok() { + echo -e "${GREEN}[OK]${NC} $*" +} + +warn() { + echo -e "${YELLOW}[!!]${NC} $*" +} + +err() { + echo -e "${RED}[ERR]${NC} $*" >&2 +} + +usage() { + cat <<'USAGE' +Usage: + ./scripts/install.sh --tool [--target ] [--force] [--help] + +Tools: + antigravity, cursor, aider, kilocode, windsurf, opencode, augment + +Examples: + ./scripts/install.sh --tool cursor --target /path/to/project + ./scripts/install.sh --tool antigravity + ./scripts/install.sh --tool aider --force +USAGE +} + +is_valid_tool() { + case "$1" in + antigravity|cursor|aider|kilocode|windsurf|opencode|augment) return 0 ;; + *) return 1 ;; + esac +} + +confirm_overwrite() { + local prompt="$1" + + if $FORCE; then + return 0 + fi + + printf "%s [y/N]: " "$prompt" + read -r reply + case "$reply" in + y|Y|yes|YES) return 0 ;; + *) return 1 ;; + esac +} + +safe_copy_dir_contents() { + local src_dir="$1" + local dst_dir="$2" + + mkdir -p "$dst_dir" + cp -R "${src_dir}/." "$dst_dir/" +} + +install_antigravity() { + local src_root="${REPO_ROOT}/integrations/antigravity" + local dst_root + + if [[ -n "$TARGET" ]]; then + dst_root="$TARGET" + else + dst_root="${HOME}/.gemini/antigravity/skills" + fi + + if [[ ! -d "$src_root" ]]; then + err "Missing source directory: $src_root" + err "Run ./scripts/convert.sh --tool antigravity first." + exit 1 + fi + + mkdir -p "$dst_root" + + local count=0 + local skill_dir + for skill_dir in "$src_root"/*; do + if [[ ! -d "$skill_dir" ]]; then + continue + fi + if [[ "$(basename "$skill_dir")" == "README.md" ]]; then + continue + fi + + local skill_name + skill_name="$(basename "$skill_dir")" + local dst_skill="${dst_root}/${skill_name}" + + if [[ -e "$dst_skill" ]]; then + if ! confirm_overwrite "Overwrite existing ${dst_skill}?"; then + warn "Skipped ${dst_skill}" + continue + fi + rm -rf "$dst_skill" + fi + + cp -R "$skill_dir" "$dst_skill" + count=$((count + 1)) + done + + ok "Installed ${count} skill directories to ${dst_root}" +} + +install_flat_rules_tool() { + local tool="$1" + local subdir="$2" + local ext="$3" + + local src_dir="${REPO_ROOT}/integrations/${tool}/rules" + local base_target="${TARGET:-$PWD}" + local dst_dir="${base_target}/${subdir}" + + if [[ ! -d "$src_dir" ]]; then + err "Missing source directory: $src_dir" + err "Run ./scripts/convert.sh --tool ${tool} first." + exit 1 + fi + + if [[ -d "$dst_dir" ]] && [[ "$(find "$dst_dir" -maxdepth 1 -type f -name "*${ext}" | wc -l | tr -d ' ')" -gt 0 ]]; then + if ! confirm_overwrite "${dst_dir} already contains ${ext} files. Overwrite contents?"; then + warn "Install cancelled for ${tool}" + return + fi + rm -rf "$dst_dir" + fi + + mkdir -p "$dst_dir" + cp -R "${src_dir}/." "$dst_dir/" + + local count + count="$(find "$dst_dir" -maxdepth 1 -type f -name "*${ext}" | wc -l | tr -d ' ')" + ok "Installed ${count} files to ${dst_dir}" +} + +install_aider() { + local src_file="${REPO_ROOT}/integrations/aider/CONVENTIONS.md" + local base_target="${TARGET:-$PWD}" + local dst_file="${base_target}/CONVENTIONS.md" + + if [[ ! -f "$src_file" ]]; then + err "Missing source file: $src_file" + err "Run ./scripts/convert.sh --tool aider first." + exit 1 + fi + + mkdir -p "$base_target" + + if [[ -f "$dst_file" ]]; then + if ! confirm_overwrite "Overwrite existing ${dst_file}?"; then + warn "Skipped ${dst_file}" + return + fi + fi + + cp "$src_file" "$dst_file" + ok "Installed ${dst_file}" +} + +install_skill_bundle_tool() { + local tool="$1" + local src_dir="${REPO_ROOT}/integrations/${tool}/skills" + local base_target="${TARGET:-$PWD}" + local dst_dir="${base_target}/.${tool}/skills" + + if [[ ! -d "$src_dir" ]]; then + err "Missing source directory: $src_dir" + err "Run ./scripts/convert.sh --tool ${tool} first." + exit 1 + fi + + if [[ -d "$dst_dir" ]] && [[ "$(find "$dst_dir" -mindepth 1 -maxdepth 1 -type d | wc -l | tr -d ' ')" -gt 0 ]]; then + if ! confirm_overwrite "${dst_dir} already contains skills. Overwrite contents?"; then + warn "Install cancelled for ${tool}" + return + fi + rm -rf "$dst_dir" + fi + + mkdir -p "$dst_dir" + cp -R "${src_dir}/." "$dst_dir/" + + local count + count="$(find "$dst_dir" -mindepth 1 -maxdepth 1 -type d | wc -l | tr -d ' ')" + ok "Installed ${count} skill directories to ${dst_dir}" +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --tool) + TOOL="${2:-}" + shift 2 + ;; + --target) + TARGET="${2:-}" + shift 2 + ;; + --force) + FORCE=true + shift + ;; + --help|-h) + usage + exit 0 + ;; + *) + err "Unknown argument: $1" + usage + exit 1 + ;; + esac +done + +if [[ -z "$TOOL" ]]; then + err "--tool is required" + usage + exit 1 +fi + +if ! is_valid_tool "$TOOL"; then + err "Invalid --tool value: ${TOOL}" + usage + exit 1 +fi + +case "$TOOL" in + antigravity) + install_antigravity + ;; + cursor) + install_flat_rules_tool "cursor" ".cursor/rules" ".mdc" + ;; + aider) + install_aider + ;; + kilocode) + install_flat_rules_tool "kilocode" ".kilocode/rules" ".md" + ;; + windsurf) + install_skill_bundle_tool "windsurf" + ;; + opencode) + install_skill_bundle_tool "opencode" + ;; + augment) + install_flat_rules_tool "augment" ".augment/rules" ".md" + ;; + *) + err "Unhandled tool: ${TOOL}" + exit 1 + ;; +esac + +ok "Installation complete for ${TOOL}"