-
-
Save tylergermain/c8392860f7b82d048e69db1c9efef53d to your computer and use it in GitHub Desktop.
| #!/bin/bash | |
| set -e | |
| # ── Config ─────────────────────────────────────────────────────────── | |
| REPO="https://github.com/AI-Essentials/instagram-skills.git" | |
| # Skills available in this repo (name|description) | |
| SKILLS=( | |
| "instagram-audit|Full account audit - content, performance, voice, competitors" | |
| "instagram-analysis|Analytics dashboards and competitor research via Metricool" | |
| "instagram-thread-carousel|Turn text threads into tweet-style carousel slides" | |
| "instagram-thumbnail|Generate reel covers and post thumbnails with Gemini" | |
| "instagram-reel-script|Script reels with hooks, beats, CTAs, and visual notes" | |
| "instagram-carousel-preview|Stitch carousel slides into a single preview image" | |
| "instagram-sponsor-outreach|Find and pitch AI companies for sponsored posts" | |
| ) | |
| # ── Helpers ────────────────────────────────────────────────────────── | |
| BOLD='\033[1m' | |
| DIM='\033[2m' | |
| RED='\033[0;31m' | |
| GREEN='\033[0;32m' | |
| YELLOW='\033[1;33m' | |
| CYAN='\033[0;36m' | |
| NC='\033[0m' | |
| info() { echo -e " ${GREEN}>${NC} $1"; } | |
| warn() { echo -e " ${YELLOW}>${NC} $1"; } | |
| fail() { echo -e " ${RED}x${NC} $1"; exit 1; } | |
| step() { echo -e "\n ${BOLD}$1${NC}"; } | |
| cleanup() { | |
| if [ -n "$TMPDIR_CREATED" ] && [ -d "$TMPDIR_CREATED" ]; then | |
| rm -rf "$TMPDIR_CREATED" | |
| fi | |
| } | |
| trap cleanup EXIT | |
| TARGET_DIR="$(pwd)" | |
| echo "" | |
| echo -e " ${CYAN}██╗███╗ ██╗███████╗████████╗ █████╗ ${NC}" | |
| echo -e " ${CYAN}██║████╗ ██║██╔════╝╚══██╔══╝██╔══██╗${NC}" | |
| echo -e " ${CYAN}██║██╔██╗ ██║███████╗ ██║ ███████║${NC}" | |
| echo -e " ${CYAN}██║██║╚██╗██║╚════██║ ██║ ██╔══██║${NC}" | |
| echo -e " ${CYAN}██║██║ ╚████║███████║ ██║ ██║ ██║${NC}" | |
| echo -e " ${CYAN}╚═╝╚═╝ ╚═══╝╚══════╝ ╚═╝ ╚═╝ ╚═╝${NC}" | |
| echo "" | |
| echo -e " ${CYAN} ██████╗ ██████╗ █████╗ ███╗ ███╗${NC}" | |
| echo -e " ${CYAN}██╔════╝ ██╔══██╗██╔══██╗████╗ ████║${NC}" | |
| echo -e " ${CYAN}██║ ███╗██████╔╝███████║██╔████╔██║${NC}" | |
| echo -e " ${CYAN}██║ ██║██╔══██╗██╔══██║██║╚██╔╝██║${NC}" | |
| echo -e " ${CYAN}╚██████╔╝██║ ██║██║ ██║██║ ╚═╝ ██║${NC}" | |
| echo -e " ${CYAN} ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝${NC}" | |
| echo "" | |
| # ── Platform selection ─────────────────────────────────────────────── | |
| if [ "$1" = "claude" ] || [ "$1" = "claude-code" ]; then | |
| PLATFORM="claude"; shift | |
| elif [ "$1" = "openclaw" ]; then | |
| PLATFORM="openclaw"; shift | |
| else | |
| echo -e " ${BOLD}Where are you installing?${NC}" | |
| echo "" | |
| echo -e " ${CYAN}1)${NC} Claude Code ${DIM}(default)${NC}" | |
| echo -e " ${CYAN}2)${NC} OpenClaw" | |
| echo "" | |
| printf " Choice [1]: " | |
| read -r CHOICE < /dev/tty | |
| CHOICE="${CHOICE:-1}" | |
| case "$CHOICE" in | |
| 2|openclaw) PLATFORM="openclaw" ;; | |
| *) PLATFORM="claude" ;; | |
| esac | |
| fi | |
| if [ "$PLATFORM" = "openclaw" ]; then | |
| SKILLS_DIR=".openclaw/skills" | |
| PLATFORM_LABEL="OpenClaw" | |
| else | |
| SKILLS_DIR=".claude/skills" | |
| PLATFORM_LABEL="Claude Code" | |
| fi | |
| # Detect install vs update | |
| FIRST_SKILL=$(echo "${SKILLS[0]}" | cut -d'|' -f1) | |
| if [ -d "$TARGET_DIR/$SKILLS_DIR/$FIRST_SKILL" ]; then | |
| MODE="update" | |
| else | |
| MODE="install" | |
| fi | |
| if [ "$MODE" = "update" ]; then | |
| echo -e " ${DIM}Updating for ${PLATFORM_LABEL}${NC}" | |
| else | |
| echo -e " ${DIM}Installing for ${PLATFORM_LABEL}${NC}" | |
| fi | |
| # ── Resolve source ─────────────────────────────────────────────────── | |
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}" 2>/dev/null || echo ".")" && pwd)" | |
| if [ -f "$SCRIPT_DIR/.claude/skills/$FIRST_SKILL/SKILL.md" ]; then | |
| SOURCE_DIR="$SCRIPT_DIR" | |
| else | |
| step "Fetching" | |
| TMPDIR_CREATED="$(mktemp -d)" | |
| git clone --depth 1 "$REPO" "$TMPDIR_CREATED" 2>/dev/null || fail "Clone failed. Do you have access to the repo?" | |
| SOURCE_DIR="$TMPDIR_CREATED" | |
| info "done" | |
| fi | |
| # ── Skill selection ────────────────────────────────────────────────── | |
| step "Skills" | |
| # Check for command-line argument (e.g., bash -s -- instagram-audit) | |
| if [ -n "$1" ]; then | |
| MATCH_FOUND=0 | |
| for i in "${!SKILLS[@]}"; do | |
| NAME=$(echo "${SKILLS[$i]}" | cut -d'|' -f1) | |
| if [ "$NAME" = "$1" ]; then | |
| SELECTED=("$i") | |
| MATCH_FOUND=1 | |
| info "$NAME" | |
| break | |
| fi | |
| done | |
| if [ "$MATCH_FOUND" -eq 0 ]; then | |
| fail "Unknown skill: $1. Available: $(printf '%s ' "${SKILLS[@]}" | sed 's/|[^ ]* */ /g')" | |
| fi | |
| else | |
| # Initialize all skills as selected | |
| for i in "${!SKILLS[@]}"; do | |
| TOGGLED[$i]=1 | |
| done | |
| CURSOR=0 | |
| TOTAL=${#SKILLS[@]} | |
| draw_menu() { | |
| if [ "$1" = "redraw" ]; then | |
| for ((i = 0; i < TOTAL; i++)); do | |
| echo -ne "\033[2A" | |
| done | |
| fi | |
| for i in "${!SKILLS[@]}"; do | |
| NAME=$(echo "${SKILLS[$i]}" | cut -d'|' -f1) | |
| DESC=$(echo "${SKILLS[$i]}" | cut -d'|' -f2) | |
| if [ "${TOGGLED[$i]}" -eq 1 ]; then | |
| CHECK="${GREEN}*${NC}" | |
| else | |
| CHECK="${DIM}-${NC}" | |
| fi | |
| if [ "$i" -eq "$CURSOR" ]; then | |
| POINTER="${CYAN}>${NC}" | |
| else | |
| POINTER=" " | |
| fi | |
| echo -e " ${POINTER} ${CHECK} ${BOLD}${NAME}${NC} ${DIM}${DESC}${NC}" | |
| echo -e "" | |
| done | |
| } | |
| echo "" | |
| echo -e " ${DIM}arrows = move, space = toggle, enter = confirm${NC}" | |
| echo "" | |
| draw_menu | |
| while true; do | |
| IFS= read -rsn1 KEY < /dev/tty | |
| if [ "$KEY" = "" ]; then | |
| break | |
| elif [ "$KEY" = " " ]; then | |
| if [ "${TOGGLED[$CURSOR]}" -eq 1 ]; then | |
| TOGGLED[$CURSOR]=0 | |
| else | |
| TOGGLED[$CURSOR]=1 | |
| fi | |
| draw_menu redraw | |
| elif [ "$KEY" = $'\x1b' ]; then | |
| read -rsn1 SEQ1 < /dev/tty | |
| read -rsn1 SEQ2 < /dev/tty | |
| if [ "$SEQ1" = "[" ]; then | |
| case "$SEQ2" in | |
| A) CURSOR=$(( (CURSOR - 1 + TOTAL) % TOTAL )); draw_menu redraw ;; | |
| B) CURSOR=$(( (CURSOR + 1) % TOTAL )); draw_menu redraw ;; | |
| esac | |
| fi | |
| fi | |
| done | |
| SELECTED=() | |
| for i in "${!SKILLS[@]}"; do | |
| if [ "${TOGGLED[$i]}" -eq 1 ]; then | |
| SELECTED+=("$i") | |
| fi | |
| done | |
| if [ "${#SELECTED[@]}" -eq 0 ]; then | |
| fail "No skills selected" | |
| fi | |
| echo "" | |
| for IDX in "${SELECTED[@]}"; do | |
| NAME=$(echo "${SKILLS[$IDX]}" | cut -d'|' -f1) | |
| info "$NAME" | |
| done | |
| fi # end of interactive menu | |
| # ── Prerequisites ──────────────────────────────────────────────────── | |
| step "Prerequisites" | |
| # Python check | |
| if command -v python3 >/dev/null 2>&1; then | |
| PYTHON_VERSION=$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")') | |
| PYTHON_MAJOR=$(echo "$PYTHON_VERSION" | cut -d. -f1) | |
| PYTHON_MINOR=$(echo "$PYTHON_VERSION" | cut -d. -f2) | |
| if [ "$PYTHON_MAJOR" -lt 3 ] || { [ "$PYTHON_MAJOR" -eq 3 ] && [ "$PYTHON_MINOR" -lt 10 ]; }; then | |
| warn "Python 3.10+ recommended (found $PYTHON_VERSION) - carousel and dashboard skills won't work" | |
| else | |
| info "Python ${DIM}$PYTHON_VERSION${NC}" | |
| fi | |
| else | |
| warn "Python 3 not found - carousel and dashboard skills won't work without it" | |
| fi | |
| # ffmpeg check | |
| if command -v ffmpeg >/dev/null 2>&1; then | |
| info "ffmpeg ${DIM}found${NC}" | |
| else | |
| warn "ffmpeg not found - animated carousel slides will fall back to GIF" | |
| echo -e " ${DIM} Install with: brew install ffmpeg${NC}" | |
| fi | |
| # ── Copy skill files ───────────────────────────────────────────────── | |
| if [ "$SOURCE_DIR" != "$TARGET_DIR" ]; then | |
| step "Installing" | |
| mkdir -p "$TARGET_DIR/$SKILLS_DIR" | |
| for IDX in "${SELECTED[@]}"; do | |
| NAME=$(echo "${SKILLS[$IDX]}" | cut -d'|' -f1) | |
| if [ -d "$TARGET_DIR/$SKILLS_DIR/$NAME" ]; then | |
| rm -rf "$TARGET_DIR/$SKILLS_DIR/$NAME" | |
| fi | |
| cp -r "$SOURCE_DIR/.claude/skills/$NAME" "$TARGET_DIR/$SKILLS_DIR/$NAME" | |
| info "$SKILLS_DIR/$NAME/" | |
| done | |
| # Supporting files | |
| mkdir -p "$TARGET_DIR/assets/headshots" | |
| if [ -d "$SOURCE_DIR/assets/headshots" ]; then | |
| cp -n "$SOURCE_DIR/assets/headshots/"* "$TARGET_DIR/assets/headshots/" 2>/dev/null || true | |
| fi | |
| # CLAUDE.md.example | |
| if [ -f "$SOURCE_DIR/CLAUDE.md.example" ] && [ ! -f "$TARGET_DIR/CLAUDE.md" ]; then | |
| cp "$SOURCE_DIR/CLAUDE.md.example" "$TARGET_DIR/CLAUDE.md.example" | |
| info "CLAUDE.md.example" | |
| fi | |
| # Requirements | |
| if [ -f "$SOURCE_DIR/requirements.txt" ]; then | |
| if [ -f "$TARGET_DIR/requirements.txt" ]; then | |
| while IFS= read -r pkg; do | |
| [ -z "$pkg" ] && continue | |
| PKG_NAME=$(echo "$pkg" | sed 's/[>=<].*//') | |
| if ! grep -q "$PKG_NAME" "$TARGET_DIR/requirements.txt" 2>/dev/null; then | |
| echo "$pkg" >> "$TARGET_DIR/requirements.txt" | |
| fi | |
| done < "$SOURCE_DIR/requirements.txt" | |
| else | |
| cp "$SOURCE_DIR/requirements.txt" "$TARGET_DIR/requirements.txt" | |
| fi | |
| info "requirements.txt" | |
| fi | |
| # .env.example | |
| if [ ! -f "$TARGET_DIR/.env.example" ]; then | |
| cp "$SOURCE_DIR/.env.example" "$TARGET_DIR/.env.example" | |
| info ".env.example" | |
| fi | |
| fi | |
| # ── Environment ────────────────────────────────────────────────────── | |
| cd "$TARGET_DIR" | |
| step "Environment" | |
| if [ ! -f .env ]; then | |
| if [ -f .env.example ]; then | |
| cp .env.example .env | |
| info "Created .env from template" | |
| fi | |
| fi | |
| # Walk through each API key | |
| declare -A ENV_KEYS | |
| ENV_KEYS=( | |
| ["GEMINI_API_KEY"]="Gemini API (image generation)|https://aistudio.google.com/apikey" | |
| ["TAVILY_API_KEY"]="Tavily (image search)|https://tavily.com" | |
| ["GIPHY_API_KEY"]="Giphy (GIF search)|https://developers.giphy.com" | |
| ["SCRAPECREATORS_API_KEY"]="ScrapeCreators (reel scraping)|https://scrapecreators.com" | |
| ) | |
| if [ -f .env ]; then | |
| HAS_EMPTY=0 | |
| for KEY in GEMINI_API_KEY TAVILY_API_KEY GIPHY_API_KEY SCRAPECREATORS_API_KEY; do | |
| CURRENT=$(grep "^${KEY}=" .env 2>/dev/null | cut -d'=' -f2) | |
| if [ -z "$CURRENT" ] || echo "$CURRENT" | grep -q "your_"; then | |
| DESC=$(echo "${ENV_KEYS[$KEY]}" | cut -d'|' -f1) | |
| URL=$(echo "${ENV_KEYS[$KEY]}" | cut -d'|' -f2) | |
| echo "" | |
| echo -e " ${BOLD}${KEY}${NC} ${DIM}- ${DESC}${NC}" | |
| echo -e " ${DIM}Sign up: ${URL}${NC}" | |
| printf " Paste key (or press enter to skip): " | |
| read -r VALUE < /dev/tty | |
| if [ -n "$VALUE" ]; then | |
| if grep -q "^${KEY}=" .env 2>/dev/null; then | |
| sed -i.bak "s|^${KEY}=.*|${KEY}=${VALUE}|" .env && rm -f .env.bak | |
| else | |
| echo "${KEY}=${VALUE}" >> .env | |
| fi | |
| info "${KEY} saved" | |
| else | |
| HAS_EMPTY=1 | |
| fi | |
| else | |
| info "${KEY} ${DIM}already set${NC}" | |
| fi | |
| done | |
| if [ "$HAS_EMPTY" -eq 1 ]; then | |
| echo "" | |
| warn "Some keys skipped - add them to .env later" | |
| fi | |
| else | |
| warn "No .env.example found - create .env manually with your API keys" | |
| fi | |
| # ── MCP Server (Metricool) ─────────────────────────────────────────── | |
| step "MCP Server" | |
| echo "" | |
| echo -e " ${BOLD}Metricool${NC} ${DIM}- powers analytics and audit skills${NC}" | |
| echo -e " ${DIM}Sign up: https://metricool.com${NC}" | |
| echo "" | |
| printf " Set up Metricool MCP now? [y/N]: " | |
| read -r SETUP_MCP < /dev/tty | |
| if [ "$SETUP_MCP" = "y" ] || [ "$SETUP_MCP" = "Y" ]; then | |
| printf " Metricool User Token: " | |
| read -r MC_TOKEN < /dev/tty | |
| printf " Metricool User ID: " | |
| read -r MC_ID < /dev/tty | |
| if [ -n "$MC_TOKEN" ] && [ -n "$MC_ID" ]; then | |
| if command -v claude >/dev/null 2>&1; then | |
| claude mcp add-json mcp-metricool "{ | |
| \"command\": \"uvx\", | |
| \"args\": [\"--upgrade\", \"mcp-metricool\"], | |
| \"env\": { | |
| \"METRICOOL_USER_TOKEN\": \"${MC_TOKEN}\", | |
| \"METRICOOL_USER_ID\": \"${MC_ID}\" | |
| } | |
| }" 2>/dev/null && info "Metricool MCP configured" || warn "MCP setup failed - configure manually" | |
| else | |
| warn "claude CLI not found - add Metricool MCP manually (see install.md)" | |
| fi | |
| else | |
| warn "Skipped - add Metricool MCP manually later" | |
| fi | |
| else | |
| info "Skipped ${DIM}- add later with: claude mcp add-json mcp-metricool ...${NC}" | |
| fi | |
| # ── Python ─────────────────────────────────────────────────────────── | |
| if command -v python3 >/dev/null 2>&1 && [ -f requirements.txt ]; then | |
| step "Python" | |
| if [ ! -d .venv ]; then | |
| python3 -m venv .venv | |
| fi | |
| source .venv/bin/activate 2>/dev/null || . .venv/bin/activate | |
| pip install -q -r requirements.txt 2>/dev/null | |
| info "Packages installed" | |
| # Install Playwright browsers for website screenshots | |
| if python3 -c "import playwright" 2>/dev/null; then | |
| playwright install chromium 2>/dev/null && info "Playwright chromium installed" || warn "Playwright browser install failed - run: playwright install chromium" | |
| fi | |
| fi | |
| # ── CLAUDE.md setup ────────────────────────────────────────────────── | |
| if [ ! -f "$TARGET_DIR/CLAUDE.md" ] && [ -f "$TARGET_DIR/CLAUDE.md.example" ]; then | |
| step "Config" | |
| cp "$TARGET_DIR/CLAUDE.md.example" "$TARGET_DIR/CLAUDE.md" | |
| info "Created CLAUDE.md from template" | |
| warn "Run /instagram-audit to personalize it" | |
| fi | |
| # ── Done ───────────────────────────────────────────────────────────── | |
| echo "" | |
| echo "" | |
| if [ "$MODE" = "update" ]; then | |
| echo -e " ${GREEN}${BOLD}Updated!${NC}" | |
| echo "" | |
| echo -e " ${DIM}Restart ${PLATFORM_LABEL} to pick up changes.${NC}" | |
| else | |
| echo -e " ${GREEN}${BOLD}Installed!${NC}" | |
| echo "" | |
| echo -e " ${DIM}1.${NC} Add any missing API keys to .env" | |
| echo -e " ${DIM}2.${NC} Open ${PLATFORM_LABEL} in this directory" | |
| echo -e " ${DIM}3.${NC} Run ${CYAN}/instagram-audit${NC} to set up your account" | |
| echo "" | |
| echo -e " ${DIM}All skills:${NC}" | |
| for IDX in "${SELECTED[@]}"; do | |
| NAME=$(echo "${SKILLS[$IDX]}" | cut -d'|' -f1) | |
| echo -e " ${CYAN}/${NAME}${NC}" | |
| done | |
| fi | |
| echo "" |
shadmanishama93-ui
commented
May 9, 2026
Skip to content
tylergermain/install.sh
Last active now • Report abuse
Clone this repository at <script src="https://gist.github.com/tylergermain/c8392860f7b82d048e69db1c9efef53d.js"></script>
Code
Revisions
2
Instagram Skills installer for Claude Code
install.sh
#!/bin/bash
set -e
── Config ───────────────────────────────────────────────────────────
REPO="https://github.com/AI-Essentials/instagram-skills.git"
Skills available in this repo (name|description)
SKILLS=(
"instagram-audit|Full account audit - content, performance, voice, competitors"
"instagram-analysis|Analytics dashboards and competitor research via Metricool"
"instagram-thread-carousel|Turn text threads into tweet-style carousel slides"
"instagram-thumbnail|Generate reel covers and post thumbnails with Gemini"
"instagram-reel-script|Script reels with hooks, beats, CTAs, and visual notes"
"instagram-carousel-preview|Stitch carousel slides into a single preview image"
"instagram-sponsor-outreach|Find and pitch AI companies for sponsored posts"
)
── Helpers ──────────────────────────────────────────────────────────
BOLD='\033[1m'
DIM='\033[2m'
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'
info() { echo -e " ${GREEN}>${NC} $1"; }
warn() { echo -e " ${YELLOW}>${NC} $1"; }
fail() { echo -e " ${RED}x${NC} $1"; exit 1; }
step() { echo -e "\n ${BOLD}$1${NC}"; }
cleanup() {
if [ -n "$TMPDIR_CREATED" ] && [ -d "$TMPDIR_CREATED" ]; then
rm -rf "$TMPDIR_CREATED"
fi
}
trap cleanup EXIT
TARGET_DIR="$(pwd)"
echo ""
echo -e " ${CYAN}██╗███╗ ██╗███████╗████████╗ █████╗ ${NC}"
echo -e " ${CYAN}██║████╗ ██║██╔════╝╚══██╔══╝██╔══██╗${NC}"
echo -e " ${CYAN}██║██╔██╗ ██║███████╗ ██║ ███████║${NC}"
echo -e " ${CYAN}██║██║╚██╗██║╚════██║ ██║ ██╔══██║${NC}"
echo -e " ${CYAN}██║██║ ╚████║███████║ ██║ ██║ ██║${NC}"
echo -e " ${CYAN}╚═╝╚═╝ ╚═══╝╚══════╝ ╚═╝ ╚═╝ ╚═╝${NC}"
echo ""
echo -e " ${CYAN} ██████╗ ██████╗ █████╗ ███╗ ███╗${NC}"
echo -e " ${CYAN}██╔════╝ ██╔══██╗██╔══██╗████╗ ████║${NC}"
echo -e " ${CYAN}██║ ███╗██████╔╝███████║██╔████╔██║${NC}"
echo -e " ${CYAN}██║ ██║██╔══██╗██╔══██║██║╚██╔╝██║${NC}"
echo -e " ${CYAN}╚██████╔╝██║ ██║██║ ██║██║ ╚═╝ ██║${NC}"
echo -e " ${CYAN} ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝${NC}"
echo ""
── Platform selection ───────────────────────────────────────────────
if [ "$1" = "claude" ] || [ "$1" = "claude-code" ]; then
PLATFORM="claude"; shift
elif [ "$1" = "openclaw" ]; then
PLATFORM="openclaw"; shift
else
echo -e " ${BOLD}Where are you installing?${NC}"
echo ""
echo -e " ${CYAN}1)${NC} Claude Code ${DIM}(default)${NC}"
echo -e " ${CYAN}2)${NC} OpenClaw"
echo ""
printf " Choice [1]: "
read -r CHOICE < /dev/tty
CHOICE="${CHOICE:-1}"
case "$CHOICE" in
2|openclaw) PLATFORM="openclaw" ;;
*) PLATFORM="claude" ;;
esac
fi
if [ "$PLATFORM" = "openclaw" ]; then
SKILLS_DIR=".openclaw/skills"
PLATFORM_LABEL="OpenClaw"
else
SKILLS_DIR=".claude/skills"
PLATFORM_LABEL="Claude Code"
fi
Detect install vs update
FIRST_SKILL=$(echo "${SKILLS[0]}" | cut -d'|' -f1)
if [ -d "$TARGET_DIR/$SKILLS_DIR/$FIRST_SKILL" ]; then
MODE="update"
else
MODE="install"
fi
if [ "$MODE" = "update" ]; then
echo -e " ${DIM}Updating for ${PLATFORM_LABEL}${NC}"
else
echo -e " ${DIM}Installing for ${PLATFORM_LABEL}${NC}"
fi
── Resolve source ───────────────────────────────────────────────────
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}" 2>/dev/null || echo ".")" && pwd)"
if [ -f "$SCRIPT_DIR/.claude/skills/$FIRST_SKILL/SKILL.md" ]; then
SOURCE_DIR="$SCRIPT_DIR"
else
step "Fetching"
TMPDIR_CREATED="$(mktemp -d)"
git clone --depth 1 "$REPO" "$TMPDIR_CREATED" 2>/dev/null || fail "Clone failed. Do you have access to the repo?"
SOURCE_DIR="$TMPDIR_CREATED"
info "done"
fi
── Skill selection ──────────────────────────────────────────────────
step "Skills"
Check for command-line argument (e.g., bash -s -- instagram-audit)
if [ -n "$1" ]; then
MATCH_FOUND=0
for i in "${!SKILLS[@]}"; do
NAME=$(echo "${SKILLS[$i]}" | cut -d'|' -f1)
if [ "$NAME" = "$1" ]; then
SELECTED=("$i")
MATCH_FOUND=1
info "$NAME"
break
fi
done
if [ "$MATCH_FOUND" -eq 0 ]; then
fail "Unknown skill:
fi
else
Initialize all skills as selected
for i in "${!SKILLS[@]}"; do
TOGGLED[$i]=1
done
CURSOR=0
TOTAL=${#SKILLS[@]}
draw_menu() {
if [ "$1" = "redraw" ]; then
for ((i = 0; i < TOTAL; i++)); do
echo -ne "\033[2A"
done
fi
for i in "${!SKILLS[@]}"; do
NAME=$(echo "${SKILLS[$i]}" | cut -d'|' -f1)
DESC=$(echo "${SKILLS[$i]}" | cut -d'|' -f2)
if [ "${TOGGLED[$i]}" -eq 1 ]; then
CHECK="${GREEN}*${NC}"
else
CHECK="${DIM}-${NC}"
fi
if [ "$i" -eq "$CURSOR" ]; then
POINTER="${CYAN}>${NC}"
else
POINTER=" "
fi
echo -e " ${POINTER} ${CHECK} ${BOLD}${NAME}${NC} ${DIM}${DESC}${NC}"
echo -e ""
done
}
echo ""
echo -e " ${DIM}arrows = move, space = toggle, enter = confirm${NC}"
echo ""
draw_menu
while true; do
IFS= read -rsn1 KEY < /dev/tty
if [ "$KEY" = "" ]; then
break
elif [ "$KEY" = " " ]; then
if [ "${TOGGLED[$CURSOR]}" -eq 1 ]; then
TOGGLED[$CURSOR]=0
else
TOGGLED[$CURSOR]=1
fi
draw_menu redraw
elif [ "$KEY" = $'\x1b' ]; then
read -rsn1 SEQ1 < /dev/tty
read -rsn1 SEQ2 < /dev/tty
if [ "$SEQ1" = "[" ]; then
case "$SEQ2" in
A) CURSOR=$(( (CURSOR - 1 + TOTAL) % TOTAL )); draw_menu redraw ;;
B) CURSOR=$(( (CURSOR + 1) % TOTAL )); draw_menu redraw ;;
esac
fi
fi
done
SELECTED=()
for i in "${!SKILLS[@]}"; do
if [ "${TOGGLED[$i]}" -eq 1 ]; then
SELECTED+=("$i")
fi
done
if [ "${#SELECTED[@]}" -eq 0 ]; then
fail "No skills selected"
fi
echo ""
for IDX in "${SELECTED[@]}"; do
NAME=$(echo "${SKILLS[$IDX]}" | cut -d'|' -f1)
info "$NAME"
done
fi # end of interactive menu
── Prerequisites ────────────────────────────────────────────────────
step "Prerequisites"
Python check
if command -v python3 >/dev/null 2>&1; then
PYTHON_VERSION=$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')
PYTHON_MAJOR=$(echo "$PYTHON_VERSION" | cut -d. -f1)
PYTHON_MINOR=$(echo "$PYTHON_VERSION" | cut -d. -f2)
if [ "$PYTHON_MAJOR" -lt 3 ] || { [ "$PYTHON_MAJOR" -eq 3 ] && [ "$PYTHON_MINOR" -lt 10 ]; }; then
warn "Python 3.10+ recommended (found $PYTHON_VERSION) - carousel and dashboard skills won't work"
else
info "Python ${DIM}$PYTHON_VERSION${NC}"
fi
else
warn "Python 3 not found - carousel and dashboard skills won't work without it"
fi
ffmpeg check
if command -v ffmpeg >/dev/null 2>&1; then
info "ffmpeg ${DIM}found${NC}"
else
warn "ffmpeg not found - animated carousel slides will fall back to GIF"
echo -e " ${DIM} Install with: brew install ffmpeg${NC}"
fi
── Copy skill files ─────────────────────────────────────────────────
if [ "$SOURCE_DIR" != "$TARGET_DIR" ]; then
step "Installing"
mkdir -p "$TARGET_DIR/$SKILLS_DIR"
for IDX in "${SELECTED[@]}"; do
NAME=$(echo "${SKILLS[$IDX]}" | cut -d'|' -f1)
if [ -d "$TARGET_DIR/$SKILLS_DIR/$NAME" ]; then
rm -rf "$TARGET_DIR/$SKILLS_DIR/$NAME"
fi
cp -r "$SOURCE_DIR/.claude/skills/$NAME" "$TARGET_DIR/$SKILLS_DIR/$NAME"
info "$SKILLS_DIR/$NAME/"
done
Supporting files
mkdir -p "$TARGET_DIR/assets/headshots"
if [ -d "$SOURCE_DIR/assets/headshots" ]; then
cp -n "$SOURCE_DIR/assets/headshots/"* "$TARGET_DIR/assets/headshots/" 2>/dev/null || true
fi
CLAUDE.md.example
if [ -f "$SOURCE_DIR/CLAUDE.md.example" ] && [ ! -f "$TARGET_DIR/CLAUDE.md" ]; then
cp "$SOURCE_DIR/CLAUDE.md.example" "$TARGET_DIR/CLAUDE.md.example"
info "CLAUDE.md.example"
fi
Requirements
if [ -f "$SOURCE_DIR/requirements.txt" ]; then
if [ -f "$TARGET_DIR/requirements.txt" ]; then
while IFS= read -r pkg; do
[ -z "$pkg" ] && continue
PKG_NAME=$(echo "$pkg" | sed 's/[>=<].*//')
if ! grep -q "$PKG_NAME" "$TARGET_DIR/requirements.txt" 2>/dev/null; then
echo "$pkg" >> "$TARGET_DIR/requirements.txt"
fi
done < "$SOURCE_DIR/requirements.txt"
else
cp "$SOURCE_DIR/requirements.txt" "$TARGET_DIR/requirements.txt"
fi
info "requirements.txt"
fi
.env.example
if [ ! -f "$TARGET_DIR/.env.example" ]; then
cp "$SOURCE_DIR/.env.example" "$TARGET_DIR/.env.example"
info ".env.example"
fi
fi
── Environment ──────────────────────────────────────────────────────
cd "$TARGET_DIR"
step "Environment"
if [ ! -f .env ]; then
if [ -f .env.example ]; then
cp .env.example .env
info "Created .env from template"
fi
fi
Walk through each API key
declare -A ENV_KEYS
ENV_KEYS=(
["GEMINI_API_KEY"]="Gemini API (image generation)|https://aistudio.google.com/apikey"
["TAVILY_API_KEY"]="Tavily (image search)|https://tavily.com"
["GIPHY_API_KEY"]="Giphy (GIF search)|https://developers.giphy.com"
["SCRAPECREATORS_API_KEY"]="ScrapeCreators (reel scraping)|https://scrapecreators.com"
)
if [ -f .env ]; then
HAS_EMPTY=0
for KEY in GEMINI_API_KEY TAVILY_API_KEY GIPHY_API_KEY SCRAPECREATORS_API_KEY; do
CURRENT=$(grep "^${KEY}=" .env 2>/dev/null | cut -d'=' -f2)
if [ -z "$CURRENT" ] || echo "$CURRENT" | grep -q "your_"; then
DESC=$(echo "${ENV_KEYS[$KEY]}" | cut -d'|' -f1)
URL=$(echo "${ENV_KEYS[$KEY]}" | cut -d'|' -f2)
echo ""
echo -e " ${BOLD}${KEY}${NC} ${DIM}- ${DESC}${NC}"
echo -e " ${DIM}Sign up: ${URL}${NC}"
printf " Paste key (or press enter to skip): "
read -r VALUE < /dev/tty
if [ -n "$VALUE" ]; then
if grep -q "^${KEY}=" .env 2>/dev/null; then
sed -i.bak "s|^${KEY}=.*|${KEY}=${VALUE}|" .env && rm -f .env.bak
else
echo "${KEY}=${VALUE}" >> .env
fi
info "${KEY} saved"
else
HAS_EMPTY=1
fi
else
info "${KEY} ${DIM}already set${NC}"
fi
done
if [ "$HAS_EMPTY" -eq 1 ]; then
echo ""
warn "Some keys skipped - add them to .env later"
fi
else
warn "No .env.example found - create .env manually with your API keys"
fi
── MCP Server (Metricool) ───────────────────────────────────────────
step "MCP Server"
echo ""
echo -e " ${BOLD}Metricool${NC} ${DIM}- powers analytics and audit skills${NC}"
echo -e " ${DIM}Sign up: https://metricool.com${NC}"
echo ""
printf " Set up Metricool MCP now? [y/N]: "
read -r SETUP_MCP < /dev/tty
if [ "$SETUP_MCP" = "y" ] || [ "$SETUP_MCP" = "Y" ]; then
printf " Metricool User Token: "
read -r MC_TOKEN < /dev/tty
printf " Metricool User ID: "
read -r MC_ID < /dev/tty
if [ -n "$MC_TOKEN" ] && [ -n "$MC_ID" ]; then
if command -v claude >/dev/null 2>&1; then
claude mcp add-json mcp-metricool "{
"command": "uvx",
"args": ["--upgrade", "mcp-metricool"],
"env": {
"METRICOOL_USER_TOKEN": "${MC_TOKEN}",
"METRICOOL_USER_ID": "${MC_ID}"
}
}" 2>/dev/null && info "Metricool MCP configured" || warn "MCP setup failed - configure manually"
else
warn "claude CLI not found - add Metricool MCP manually (see install.md)"
fi
else
warn "Skipped - add Metricool MCP manually later"
fi
else
info "Skipped ${DIM}- add later with: claude mcp add-json mcp-metricool ...${NC}"
fi
── Python ───────────────────────────────────────────────────────────
if command -v python3 >/dev/null 2>&1 && [ -f requirements.txt ]; then
step "Python"
if [ ! -d .venv ]; then
python3 -m venv .venv
fi
source .venv/bin/activate 2>/dev/null || . .venv/bin/activate
pip install -q -r requirements.txt 2>/dev/null
info "Packages installed"
Install Playwright browsers for website screenshots
if python3 -c "import playwright" 2>/dev/null; then
playwright install chromium 2>/dev/null && info "Playwright chromium installed" || warn "Playwright browser install failed - run: playwright install chromium"
fi
fi
── CLAUDE.md setup ──────────────────────────────────────────────────
if [ ! -f "$TARGET_DIR/CLAUDE.md" ] && [ -f "$TARGET_DIR/CLAUDE.md.example" ]; then
step "Config"
cp "$TARGET_DIR/CLAUDE.md.example" "$TARGET_DIR/CLAUDE.md"
info "Created CLAUDE.md from template"
warn "Run /instagram-audit to personalize it"
fi
── Done ─────────────────────────────────────────────────────────────
echo ""
echo ""
if [ "$MODE" = "update" ]; then
echo -e " ${GREEN}${BOLD}Updated!${NC}"
echo ""
echo -e " ${DIM}Restart ${PLATFORM_LABEL} to pick up changes.${NC}"
else
echo -e " ${GREEN}${BOLD}Installed!${NC}"
echo ""
echo -e " ${DIM}1.${NC} Add any missing API keys to .env"
echo -e " ${DIM}2.${NC} Open ${PLATFORM_LABEL} in this directory"
echo -e " ${DIM}3.${NC} Run ${CYAN}/instagram-audit${NC} to set up your account"
echo ""
echo -e " ${DIM}All skills:${NC}"
for IDX in "${SELECTED[@]}"; do
NAME=$(echo "${SKILLS[$IDX]}" | cut -d'|' -f1)
echo -e " ${CYAN}/${NAME}${NC}"
done
fi
echo ""
shadmanishama93-ui
commented
now
Comment
Leave a comment
Footer
© 2026 GitHub, Inc.
Footer navigation
Terms
Privacy
Security
Status
Community
Docs
Contact
Manage cookies
Do not share my personal information