#!/usr/bin/env bash # Install VillageSQL skills for all detected AI coding agents. # Supports: Claude Code, OpenAI Codex, Cursor, Amp, Gemini CLI, Antigravity (agy), Kiro, OpenCode, OpenClaw # # Usage: # curl -sSL https://villagesql.com/skills | bash # # Env vars (optional): # VILLAGESQL_SKILLS_SRC Local clone of villagesql-skills repo # (default: ~/.local/share/villagesql-skills) # CLAUDE_SKILLS_DIR Override Claude Code skills path # (default: ~/.claude/skills) set -euo pipefail REPO_URL="https://github.com/villagesql/villagesql-skills.git" SRC_DIR="${VILLAGESQL_SKILLS_SRC:-$HOME/.local/share/villagesql-skills}" # Anonymous install ID — stable per machine, stored in ~/.villagesql/.install_id # To opt out of tracking, write an empty file: touch ~/.villagesql/.install_id _VSQL_ID_FILE="${HOME}/.villagesql/.install_id" if [ -f "$_VSQL_ID_FILE" ] && [ ! -s "$_VSQL_ID_FILE" ]; then _VSQL_ANON_ID="" # empty file = opted out elif [ -f "$_VSQL_ID_FILE" ]; then _VSQL_ANON_ID=$(cat "$_VSQL_ID_FILE" 2>/dev/null || echo "anon") else if [ -f /proc/sys/kernel/random/uuid ]; then _VSQL_ANON_ID=$(tr -d '-' < /proc/sys/kernel/random/uuid) elif command -v uuidgen >/dev/null 2>&1; then _VSQL_ANON_ID=$(uuidgen | tr -d '-' | tr '[:upper:]' '[:lower:]') else _VSQL_ANON_ID="$(date +%s)${RANDOM}${RANDOM}" fi mkdir -p "${HOME}/.villagesql" 2>/dev/null || true printf '%s' "$_VSQL_ANON_ID" > "$_VSQL_ID_FILE" 2>/dev/null || true fi _IS_UPDATE=false [ -d "$SRC_DIR/.git" ] && _IS_UPDATE=true _track() { local _event="$1" _extra="${2:-}" [ -n "$_VSQL_ANON_ID" ] || return 0 command -v curl >/dev/null 2>&1 || return 0 local _ts _json _b64 _ts=$(date +%s) _json=$(printf '[{"event":"%s","properties":{"token":"86f474ad75c034a2e021b35fd3d4b5f1","distinct_id":"%s","time":%d,"os":"%s","arch":"%s","script":"skills","method":"source","is_update":%s%s}}]' \ "$_event" "$_VSQL_ANON_ID" "$_ts" "$(uname -s)" "$(uname -m)" "$_IS_UPDATE" "$_extra") _b64=$(printf '%s' "$_json" | base64 | tr -d '\n') curl --silent --max-time 5 --output /dev/null \ --request POST \ --url "https://api.mixpanel.com/track" \ --data "data=$_b64" 2>/dev/null & } _track "Skills Install Started" if ! command -v git >/dev/null 2>&1; then echo "Error: git is required but not installed." >&2 exit 1 fi if [ -d "$SRC_DIR/.git" ]; then echo "Updating $SRC_DIR..." git -C "$SRC_DIR" fetch origin git -C "$SRC_DIR" checkout main git -C "$SRC_DIR" reset --hard origin/main else echo "Cloning into $SRC_DIR..." mkdir -p "$(dirname "$SRC_DIR")" git clone "$REPO_URL" "$SRC_DIR" fi # Install all skills into a target directory via symlinks. # $1 = target skills dir, $2 = agent label for output install_to() { local dir="$1" label="$2" local n_installed=0 n_skipped=0 mkdir -p "$dir" for skill_dir in "$SRC_DIR"/skills/*/; do skill_name=$(basename "${skill_dir%/}") [ -f "$skill_dir/SKILL.md" ] || continue target="$dir/$skill_name" if [ -e "$target" ] && [ ! -L "$target" ]; then n_skipped=$((n_skipped + 1)) continue fi ln -sfn "${skill_dir%/}" "$target" n_installed=$((n_installed + 1)) done if [ "$n_skipped" -gt 0 ]; then printf " %-28s installed, %d skipped (non-symlink exists)\n" "$label" "$n_skipped" else printf " %-28s installed\n" "$label" fi } # Install the repo as an extension/plugin by symlinking the repo root. # $1 = target path (the symlink to create), $2 = agent label for output install_extension_to() { local target="$1" label="$2" mkdir -p "$(dirname "$target")" if [ -e "$target" ] && [ ! -L "$target" ]; then printf " %-28s skipped (non-symlink exists)\n" "$label" return fi ln -sfn "$SRC_DIR" "$target" printf " %-28s installed\n" "$label" } has_cmd() { command -v "$1" >/dev/null 2>&1; } has_dir() { [ -d "$1" ]; } echo echo "Detecting agents..." echo agents_found=0 # Claude Code if has_cmd claude || has_dir "$HOME/.claude"; then install_to "${CLAUDE_SKILLS_DIR:-$HOME/.claude/skills}" "Claude Code" agents_found=$((agents_found + 1)) fi # OpenAI Codex + Cursor (share ~/.agents/skills/) if has_cmd codex || has_dir "$HOME/.codex" || has_cmd cursor || has_dir "$HOME/.cursor"; then install_to "$HOME/.agents/skills" "Codex / Cursor" agents_found=$((agents_found + 1)) fi # Amp if has_cmd amp || has_dir "$HOME/.config/agents"; then install_to "$HOME/.config/agents/skills" "Amp" agents_found=$((agents_found + 1)) fi # Gemini CLI (detect by command; dir alone is ambiguous with Antigravity) if has_cmd gemini; then install_extension_to "$HOME/.gemini/extensions/villagesql" "Gemini CLI" agents_found=$((agents_found + 1)) fi # Antigravity (agy) — replaced Gemini CLI; uses ~/.gemini/antigravity-cli/plugins/ if has_cmd agy; then install_extension_to "$HOME/.gemini/antigravity-cli/plugins/villagesql" "Antigravity (agy)" agents_found=$((agents_found + 1)) fi # Kiro if has_cmd kiro || has_dir "$HOME/.kiro"; then install_to "$HOME/.kiro/skills" "Kiro" agents_found=$((agents_found + 1)) fi # OpenCode if has_cmd opencode || has_dir "$HOME/.config/opencode"; then install_to "$HOME/.config/opencode/skills" "OpenCode" agents_found=$((agents_found + 1)) fi # OpenClaw if has_cmd openclaw || has_dir "$HOME/.openclaw"; then install_to "$HOME/.openclaw/skills" "OpenClaw" agents_found=$((agents_found + 1)) fi echo if [ "$agents_found" -eq 0 ]; then echo "No supported agents detected." echo "Supported: Claude Code, OpenAI Codex, Cursor, Amp, Gemini CLI, Antigravity (agy), Kiro, OpenCode, OpenClaw" exit 1 fi echo "Done. Restart any running agents to pick up new skills." _track "Skills Install Completed"