#!/usr/bin/env bash # ═══════════════════════════════════════════════════════════════════════════════ # AI Terminal Kickstart - macOS Edition # Supports: macOS 12 Monterey and newer, Apple Silicon (arm64) + Intel (x86_64) # # One script to prep a Mac for AI-powered terminal work: # 1. Claude Code (Anthropic) # 2. OpenAI Codex CLI # 3. ChatGPT CLI (OpenAI) # 4. GitHub Copilot CLI (GitHub) # + Netlify CLI for direct site deployment # + AI frameworks & conversion toolkit (aider, llm, repomix, gemini-cli, # files-to-prompt, markitdown, pandoc, poppler, tesseract, yt-dlp, jc, glow ...) # # Usage: # chmod +x start-ai-terminal-kickstart-macos.sh # ./start-ai-terminal-kickstart-macos.sh # interactive # ./start-ai-terminal-kickstart-macos.sh --auto # unattended (yes to all) # ./start-ai-terminal-kickstart-macos.sh --with-ollama # also install local-model runner # ./start-ai-terminal-kickstart-macos.sh --with-office # also install LibreOffice (doc conversions) # ./start-ai-terminal-kickstart-macos.sh --with-kimi # also install Kimi Code CLI (Moonshot AI, China-operated; OFF by default) # # IMPORTANT: Do NOT run with sudo. Homebrew refuses to run as root and will # abort. The script installs into your user-owned Homebrew prefix and never # needs elevation (Homebrew itself may prompt once for your password when it # first creates its directories — that is expected). # # Author : AI Terminal ops # Date : 2026-06-15 # ═══════════════════════════════════════════════════════════════════════════════ set -uo pipefail # NOTE: -e (errexit) is intentionally omitted. This script uses manual error # handling with || true and explicit checks. set -e causes false exits on # arithmetic returning 0 and on optional package installs failing. # # NOTE: written to run on the bash that ships with macOS (3.2). No bash-4+ # features (associative arrays, mapfile, ${var^^}) are used anywhere. # ─── Configuration ──────────────────────────────────────────────────────────── SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # Private, unpredictable temp dir (created below). Installer output can be # verbose, so keep it out of a world-writable, predictable /tmp path. TEMP_DIR="" LOG_FILE="" AUTO_MODE=false WITH_OLLAMA=false WITH_OFFICE=false WITH_KIMI=false ACTUAL_USER="$USER" ACTUAL_HOME="$HOME" ARCH="$(uname -m)" # Parse arguments for arg in "$@"; do case "$arg" in --auto|-a|--all|--everything) AUTO_MODE=true ;; --with-ollama) WITH_OLLAMA=true ;; --with-office) WITH_OFFICE=true ;; --with-kimi) WITH_KIMI=true ;; --help|-h) echo "Usage: $0 [--auto|--all] [--with-ollama] [--with-office] [--with-kimi]" echo " --auto, --all Install the full recommended stack, no prompts" echo " --with-ollama Also install Ollama + a small local model" echo " --with-office Also install LibreOffice (for document conversions)" echo " --with-kimi Also install Kimi Code CLI (Moonshot AI, China-operated; OFF by default)" exit 0 ;; esac done TEMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/ai-kickstart-macos.XXXXXX" 2>/dev/null)" || TEMP_DIR="/tmp/ai-kickstart-macos-$$" mkdir -p "$TEMP_DIR" 2>/dev/null || true chmod 700 "$TEMP_DIR" 2>/dev/null || true LOG_FILE="$TEMP_DIR/kickstart.log" ( umask 077; : > "$LOG_FILE" ) # Node.js LTS major — the ONLY pinned version in this script (a ~yearly stability # knob). Every AI tool below installs its latest published release, so the script # never needs editing when those tools ship new versions. NODE_MAJOR="22" # ─── Helper Functions ──────────────────────────────────────────────────────── RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[0;33m' CYAN='\033[0;36m' MAGENTA='\033[0;35m' WHITE='\033[1;37m' GRAY='\033[0;37m' NC='\033[0m' # No Color log() { echo "[$(date +%H:%M:%S)] $*" >> "$LOG_FILE"; } section() { echo -e "\n ${CYAN}=================================================================${NC}"; echo -e " ${CYAN}$1${NC}"; echo -e " ${CYAN}=================================================================${NC}"; log "SECTION: $1"; } step() { echo -e "\n ${CYAN}>> $1${NC}"; log "STEP: $1"; } ok() { echo -e " ${GREEN}[OK]${NC} $1"; log "OK: $1"; } warn() { echo -e " ${YELLOW}[WARN]${NC} $1"; log "WARN: $1"; } fail() { echo -e " ${RED}[FAIL]${NC} $1"; log "FAIL: $1"; } info() { echo -e " ${GRAY}$1${NC}"; } tip() { echo -e " ${YELLOW}[TIP]${NC} $1"; } cmd_exists() { command -v "$1" >/dev/null 2>&1; } # True if ANY of the space-separated candidate commands exists (handles tools # whose binary name differs across installers, e.g. "fd" vs "fdfind"). cmd_exists_any() { local c for c in $1; do command -v "$c" >/dev/null 2>&1 && return 0 done return 1 } # ─── Progress bar ──────────────────────────────────────────────────────────── TOTAL_PHASES=6 CURRENT_PHASE=0 show_progress() { local label="$1" CURRENT_PHASE=$((CURRENT_PHASE + 1)) local pct=$((CURRENT_PHASE * 100 / TOTAL_PHASES)) local filled=$((30 * CURRENT_PHASE / TOTAL_PHASES)) local empty=$((30 - filled)) local bar=""; local space=""; local i=0 while [ "$i" -lt "$filled" ]; do bar="${bar}█"; i=$((i + 1)); done i=0 while [ "$i" -lt "$empty" ]; do space="${space}░"; i=$((i + 1)); done printf "\r ${CYAN}[%s%s] %3d%% - Phase %d/%d: %s${NC} \n" \ "$bar" "$space" "$pct" "$CURRENT_PHASE" "$TOTAL_PHASES" "$label" } # ─── Prompt helper ─────────────────────────────────────────────────────────── ask_yn() { local prompt="$1" local default="${2:-y}" if $AUTO_MODE; then [ "$default" = "y" ] && return 0 || return 1 fi local hint="Y/n" [ "$default" = "n" ] && hint="y/N" echo -ne " ${YELLOW}$prompt ($hint): ${NC}" read -r answer answer="${answer:-$default}" [[ "$answer" =~ ^[Yy] ]] } # Center text inside the banner's inner width (ASCII text only). BANNER_W=67 center() { local s="$1" local len=${#s} local total=$((BANNER_W - len)) [ "$total" -lt 0 ] && total=0 local left=$((total / 2)) local right=$((total - left)) printf "%*s%s%*s" "$left" "" "$s" "$right" "" } # Append a line to the shell profile once (idempotent on a marker). add_to_profile() { local line="$1" local marker="$2" touch "$PROFILE_FILE" 2>/dev/null || true if ! grep -qF "$marker" "$PROFILE_FILE" 2>/dev/null; then printf '\n%s\n' "$line" >> "$PROFILE_FILE" fi } # Write (or REPLACE) an `export VAR="value"` line, anchored to the assignment so # a rotated key actually takes effect on a re-run (a plain marker check would # keep the stale key). The profile now holds a secret, so lock it to mode 600. add_to_profile_kv() { local var="$1" local val="$2" touch "$PROFILE_FILE" 2>/dev/null || true if grep -q "^export ${var}=" "$PROFILE_FILE" 2>/dev/null; then local tmp tmp="$(mktemp)" grep -v "^export ${var}=" "$PROFILE_FILE" > "$tmp" 2>/dev/null && mv "$tmp" "$PROFILE_FILE" fi printf '\nexport %s="%s"\n' "$var" "$val" >> "$PROFILE_FILE" chmod 600 "$PROFILE_FILE" 2>/dev/null || true } # Homebrew install wrapper (formulae). Never elevated. brew_install() { HOMEBREW_NO_AUTO_UPDATE=1 brew install "$@" >> "$LOG_FILE" 2>&1 || true } brew_install_cask() { HOMEBREW_NO_AUTO_UPDATE=1 brew install --cask "$@" >> "$LOG_FILE" 2>&1 || true } # pip install into the brew Python. Modern Python is "externally managed" # (PEP 668), so --break-system-packages is required; fall back to --user. pip_install() { [ -z "${PYTHON_CMD:-}" ] && return 0 # Homebrew/Apple Python are PEP 668 "externally managed" AND built with # ENABLE_USER_SITE=False, so --user is rejected here; --break-system-packages # is the working path. (Standalone CLI apps go through pipx instead.) "$PYTHON_CMD" -m pip install --break-system-packages --upgrade --quiet "$@" >> "$LOG_FILE" 2>&1 || true } # pipx install a CLI app (isolated venv, the correct tool for standalone CLIs). pipx_install() { cmd_exists pipx || return 1 pipx install "$@" >> "$LOG_FILE" 2>&1 || pipx install --force "$@" >> "$LOG_FILE" 2>&1 || true } # Track results for the final summary (plain indexed arrays — bash 3.2 safe). RESULT_NAMES=() RESULT_STATUS=() RESULT_DETAIL=() add_result() { RESULT_NAMES[${#RESULT_NAMES[@]}]="$1" RESULT_STATUS[${#RESULT_STATUS[@]}]="$2" RESULT_DETAIL[${#RESULT_DETAIL[@]}]="${3:-}" } # ─── Shell profile target (zsh is the macOS default since Catalina) ────────── case "${SHELL:-/bin/zsh}" in */bash) PROFILE_FILE="$ACTUAL_HOME/.bash_profile" ;; */zsh) PROFILE_FILE="$ACTUAL_HOME/.zprofile" ;; *) PROFILE_FILE="$ACTUAL_HOME/.zprofile" ;; esac # ─── Banner ─────────────────────────────────────────────────────────────────── echo -e "${MAGENTA}" printf " ╔"; i=0; while [ "$i" -lt "$BANNER_W" ]; do printf "═"; i=$((i + 1)); done; printf "╗\n" printf " ║%s║\n" "$(center "")" printf " ║%s║\n" "$(center "A I T E R M I N A L K I C K S T A R T")" printf " ║%s║\n" "$(center "macOS Edition (Apple Silicon + Intel)")" printf " ║%s║\n" "$(center "")" printf " ║%s║\n" "$(center "Claude Code | Codex | ChatGPT CLI | Copilot CLI")" printf " ║%s║\n" "$(center "")" printf " ╚"; i=0; while [ "$i" -lt "$BANNER_W" ]; do printf "═"; i=$((i + 1)); done; printf "╝\n" echo -e "${NC}" # ─── Guards: must be macOS, must NOT be root ───────────────────────────────── if [ "$(uname -s)" != "Darwin" ]; then fail "This is the macOS edition. You're not on macOS ($(uname -s))." info "Use start-ai-terminal-kickstart.sh (Linux) or Start-AITerminalKickstart.ps1 (Windows)." exit 1 fi if [ "$(id -u)" -eq 0 ]; then fail "Do NOT run this script with sudo / as root." info "Homebrew refuses to run as root and the install will fail." info "Re-run as your normal user: ./start-ai-terminal-kickstart-macos.sh" exit 1 fi # ─── System Info ────────────────────────────────────────────────────────────── MACOS_VER="$(sw_vers -productVersion 2>/dev/null || echo 'unknown')" MACOS_NAME="$(sw_vers -productName 2>/dev/null || echo 'macOS')" if [ "$ARCH" = "arm64" ]; then CHIP_LABEL="Apple Silicon (arm64)" BREW_PREFIX_DEFAULT="/opt/homebrew" else CHIP_LABEL="Intel (x86_64)" BREW_PREFIX_DEFAULT="/usr/local" fi echo -e " ${WHITE}System Info:${NC}" echo -e " ${GRAY}OS : $MACOS_NAME $MACOS_VER${NC}" echo -e " ${GRAY}Chip : $CHIP_LABEL${NC}" echo -e " ${GRAY}User : $ACTUAL_USER${NC}" echo -e " ${GRAY}Shell : ${SHELL:-unknown}${NC}" echo -e " ${GRAY}Profile : $PROFILE_FILE${NC}" echo -e " ${GRAY}Bash : ${BASH_VERSION:-?}${NC}" echo -e " ${GRAY}Log File : $LOG_FILE${NC}" echo "" # ─── Install mode (Everything vs Choose) ───────────────────────────────────── if ! $AUTO_MODE; then echo -e " ${WHITE}Install mode:${NC}" echo -e " ${GRAY} 1) Everything — install the full recommended stack, unattended${NC}" echo -e " ${GRAY} 2) Choose — ask before each component${NC}" echo -ne " ${YELLOW}Pick [1/2] (default 2): ${NC}" read -r INSTALL_MODE if [ "${INSTALL_MODE:-2}" = "1" ]; then AUTO_MODE=true info "Mode: install everything. (API keys, Ollama and LibreOffice stay opt-in via flags.)" else info "Mode: choose each component." fi echo "" fi # ─── Pre-flight: Xcode CLT, Homebrew, Network, Disk ────────────────────────── show_progress "Pre-flight Checks" section "PRE-FLIGHT CHECKS" { echo "" echo "======================================================================" echo "Session started: $(date '+%Y-%m-%d %H:%M:%S')" echo "OS: $MACOS_NAME $MACOS_VER | Chip: $CHIP_LABEL | User: $ACTUAL_USER" echo "======================================================================" } >> "$LOG_FILE" # --- Xcode Command Line Tools (provides git, clang, make) --- step "Checking Xcode Command Line Tools" if xcode-select -p >/dev/null 2>&1; then ok "Xcode Command Line Tools present ($(xcode-select -p))" add_result "Xcode CLT" "PRESENT" "" elif command -v brew >/dev/null 2>&1 || [ -x /opt/homebrew/bin/brew ] || [ -x /usr/local/bin/brew ]; then # Homebrew is already here but CLT is somehow missing — let Apple fix it. warn "Xcode Command Line Tools not installed (needed for git + compilers)." info "Triggering Apple's installer — a GUI dialog will appear. Click Install." xcode-select --install >/dev/null 2>&1 || true info "When the install finishes, re-run this script if git is missing." add_result "Xcode CLT" "TRIGGERED" "complete GUI dialog" else # No Homebrew yet: the Homebrew installer (next step) installs the Command # Line Tools itself. Don't fire a second, racing xcode-select install. warn "Xcode Command Line Tools not installed." info "The Homebrew installer below will set them up for you." add_result "Xcode CLT" "VIA-BREW" "Homebrew installs it" fi # --- Homebrew --- step "Checking Homebrew" if ! cmd_exists brew; then # Try known install locations before deciding to install if [ -x "$BREW_PREFIX_DEFAULT/bin/brew" ]; then eval "$("$BREW_PREFIX_DEFAULT/bin/brew" shellenv)" elif [ -x /opt/homebrew/bin/brew ]; then eval "$(/opt/homebrew/bin/brew shellenv)" elif [ -x /usr/local/bin/brew ]; then eval "$(/usr/local/bin/brew shellenv)" fi fi if ! cmd_exists brew; then info "Homebrew is the macOS package manager — it installs everything else." if ask_yn "Install Homebrew now?"; then step "Installing Homebrew (this may prompt once for your password)" # Run visibly (not redirected) so the sudo password prompt is shown. NONINTERACTIVE=1 /bin/bash -c \ "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" || true # Load brew into this shell from whichever prefix it landed in if [ -x "$BREW_PREFIX_DEFAULT/bin/brew" ]; then eval "$("$BREW_PREFIX_DEFAULT/bin/brew" shellenv)" elif [ -x /opt/homebrew/bin/brew ]; then eval "$(/opt/homebrew/bin/brew shellenv)" elif [ -x /usr/local/bin/brew ]; then eval "$(/usr/local/bin/brew shellenv)" fi fi fi if cmd_exists brew; then BREW_BIN="$(command -v brew)" ok "Homebrew ready ($(brew --version 2>/dev/null | head -1))" # Persist brew into the login shell so future terminals find it add_to_profile "eval \"\$($BREW_BIN shellenv)\"" "brew shellenv" add_result "Homebrew" "OK" "$BREW_BIN" else fail "Homebrew is not available. Cannot continue without it." info "Install manually from https://brew.sh then re-run this script." add_result "Homebrew" "FAILED" "" exit 1 fi # --- Network connectivity --- step "Testing internet connectivity to download sources" REACHABLE=0 UNREACHABLE=0 NET_ENDPOINTS=" GitHub API|https://api.github.com Homebrew|https://formulae.brew.sh Claude installer|https://claude.ai npm registry|https://registry.npmjs.org PyPI|https://pypi.org " while IFS='|' read -r ep_name ep_url; do [ -z "$ep_name" ] && continue if curl -sf --connect-timeout 8 --max-time 12 -o /dev/null "$ep_url" 2>/dev/null; then ok "$ep_name reachable" REACHABLE=$((REACHABLE + 1)) else warn "$ep_name ($ep_url) - UNREACHABLE" UNREACHABLE=$((UNREACHABLE + 1)) fi done <<< "$NET_ENDPOINTS" TOTAL_EP=$((REACHABLE + UNREACHABLE)) if [ "$UNREACHABLE" -eq 0 ]; then ok "All $REACHABLE download sources reachable" add_result "Network" "OK" "$REACHABLE/$TOTAL_EP reachable" elif [ "$UNREACHABLE" -lt 3 ]; then warn "$UNREACHABLE of $TOTAL_EP sources unreachable. Some installs may fail." add_result "Network" "PARTIAL" "$REACHABLE/$TOTAL_EP reachable" else fail "Most download sources unreachable. Script will likely fail." info "Check: DNS, proxy, firewall, or VPN settings." add_result "Network" "FAILED" "$REACHABLE/$TOTAL_EP reachable" fi # --- Disk space (BSD df: -g reports GB) --- step "Checking available disk space" FREE_GB="$(df -g / 2>/dev/null | awk 'NR==2{print $4}')" TOTAL_GB="$(df -g / 2>/dev/null | awk 'NR==2{print $2}')" if [ "${FREE_GB:-0}" -lt 3 ]; then fail "CRITICAL: only ${FREE_GB}GB free on / (${TOTAL_GB}GB total). Need at least 3GB." add_result "Disk Space" "FAILED" "${FREE_GB}GB free" elif [ "${FREE_GB:-0}" -lt 8 ]; then warn "Low: ${FREE_GB}GB free on / (${TOTAL_GB}GB total). Recommend 8+ GB." add_result "Disk Space" "WARN" "${FREE_GB}GB free" else ok "Disk space: ${FREE_GB}GB free on / (${TOTAL_GB}GB total)" add_result "Disk Space" "OK" "${FREE_GB}GB free" fi # ═══════════════════════════════════════════════════════════════════════════════ # PHASE 1: CORE RUNTIMES & CLI UTILITIES # ═══════════════════════════════════════════════════════════════════════════════ show_progress "Core Runtimes & Utilities" section "PHASE 1: Core Runtimes & CLI Utilities" step "Updating Homebrew" brew update >> "$LOG_FILE" 2>&1 || true ok "Homebrew updated" # ─── Git ────────────────────────────────────────────────────────────────────── step "Checking Git" if cmd_exists git; then ok "Git already installed ($(git --version))" add_result "Git" "PRESENT" "$(git --version)" else info "Git is REQUIRED for Claude Code and version control." if ask_yn "Install Git (Homebrew, newer than the Xcode one)?"; then brew_install git cmd_exists git && ok "Git installed ($(git --version))" && add_result "Git" "INSTALLED" "$(git --version)" else add_result "Git" "SKIPPED" "" fi fi # Git identity check if cmd_exists git; then step "Checking Git configuration" GIT_USER="$(git config --global user.name 2>/dev/null || true)" GIT_EMAIL="$(git config --global user.email 2>/dev/null || true)" if [ -z "$GIT_USER" ] || [ -z "$GIT_EMAIL" ]; then warn "Git user.name or user.email not set. Commits will fail without these." tip "git config --global user.name 'Your Name'" tip "git config --global user.email 'you@example.com'" else ok "Git user: $GIT_USER <$GIT_EMAIL>" fi git config --global init.defaultBranch main 2>/dev/null || true fi # ─── Node.js ────────────────────────────────────────────────────────────────── step "Checking Node.js" if cmd_exists node; then ok "Node.js already installed ($(node --version))" cmd_exists npm && ok "npm $(npm --version)" cmd_exists npx && ok "npx available" add_result "Node.js" "PRESENT" "$(node --version)" else info "Node.js is required for Claude Code, npm tools, and MCP servers." if ask_yn "Install Node.js ${NODE_MAJOR} LTS?"; then brew_install "node@${NODE_MAJOR}" # node@NN is keg-only; link it onto PATH brew link --overwrite --force "node@${NODE_MAJOR}" >> "$LOG_FILE" 2>&1 || true if ! cmd_exists node; then brew_install node; fi if cmd_exists node; then ok "Node.js $(node --version) installed" cmd_exists npm && ok "npm $(npm --version)" add_result "Node.js" "INSTALLED" "$(node --version)" else fail "Node.js installation failed" add_result "Node.js" "FAILED" "" fi else add_result "Node.js" "SKIPPED" "" fi fi # npm global prefix (user-owned; brew node already installs globals without sudo, # but a dedicated prefix keeps things tidy and survives node upgrades). if cmd_exists npm; then step "Configuring npm global directory" NPM_GLOBAL="$ACTUAL_HOME/.npm-global" mkdir -p "$NPM_GLOBAL" npm config set prefix "$NPM_GLOBAL" >> "$LOG_FILE" 2>&1 || true add_to_profile 'export PATH="$HOME/.npm-global/bin:$PATH"' 'npm-global' export PATH="$NPM_GLOBAL/bin:$PATH" ok "npm global dir: $NPM_GLOBAL" fi # ─── Python (prefer Homebrew Python, not Apple's externally-managed one) ───── step "Checking Python" # Apple's /usr/bin/python3 is externally-managed and its user-site bin is not on # PATH, so we deliberately ignore it and prefer a Homebrew python3.x. PYTHON_CMD="" pick_brew_python() { local cand p for cand in python3.13 python3.12 python3.11 python3; do if command -v "$cand" >/dev/null 2>&1; then p="$(command -v "$cand")" case "$p" in /usr/bin/python3|/usr/bin/python3.*) continue ;; esac PYTHON_CMD="$cand"; return 0 fi done return 1 } pick_brew_python || true if [ -n "$PYTHON_CMD" ]; then ok "Python: $("$PYTHON_CMD" --version 2>&1) ($PYTHON_CMD)" add_result "Python" "PRESENT" "$("$PYTHON_CMD" --version 2>&1)" else info "Python is used for MCP servers, AI SDKs, and data processing." info "(Apple's system python3 is externally-managed; installing Homebrew Python.)" if ask_yn "Install Python 3 (Homebrew)?"; then brew_install python # unversioned formula = latest stable Python; no version pin pick_brew_python || PYTHON_CMD="python3" if "$PYTHON_CMD" --version >/dev/null 2>&1; then ok "Python $("$PYTHON_CMD" --version 2>&1) installed ($PYTHON_CMD)" add_result "Python" "INSTALLED" "$("$PYTHON_CMD" --version 2>&1)" else fail "Python installation failed" add_result "Python" "FAILED" "" fi else add_result "Python" "SKIPPED" "" fi fi # pipx — the right tool for installing standalone Python CLI apps in isolation. if [ -n "$PYTHON_CMD" ]; then step "Checking pipx (isolated installs for Python CLI apps)" if cmd_exists pipx; then ok "pipx already installed" add_result "pipx" "PRESENT" "" else brew_install pipx pipx ensurepath >> "$LOG_FILE" 2>&1 || true add_to_profile 'export PATH="$HOME/.local/bin:$PATH"' '.local/bin' export PATH="$ACTUAL_HOME/.local/bin:$PATH" cmd_exists pipx && ok "pipx installed" && add_result "pipx" "INSTALLED" "" fi fi # uv — ultra-fast Python package/venv manager. step "Checking uv (fast Python package manager)" if cmd_exists uv; then ok "uv already installed ($(uv --version 2>/dev/null))" add_result "uv" "PRESENT" "" else if ask_yn "Install uv?"; then brew_install uv cmd_exists uv && ok "uv installed" && add_result "uv" "INSTALLED" "" || add_result "uv" "SKIPPED" "" else add_result "uv" "SKIPPED" "" fi fi # ─── CLI Utilities + Conversion Toolkit (single bash-3.2-safe table) ───────── step "Installing CLI utilities + conversion toolkit" # key | candidate-commands | brew-formula | description CLI_TABLE=" ripgrep|rg|ripgrep|Ultra-fast code search (used by Claude Code) jq|jq|jq|JSON processor for API responses fd|fd fdfind|fd|Fast file finder imagemagick|magick convert|imagemagick|Image processing (convert, resize) ghostscript|gs|ghostscript|PDF/PostScript engine (needed by ImageMagick for PDFs) ffmpeg|ffmpeg|ffmpeg|Video/audio processing and media conversion p7zip|7z 7zz 7za|p7zip|Archive compression bat|bat batcat|bat|Syntax-highlighted file viewer (better cat) fzf|fzf|fzf|Fuzzy finder for files, history, and commands yq|yq|yq|YAML processor (like jq but for YAML) tree|tree|tree|Directory structure viewer delta|delta|git-delta|Enhanced git diff viewer with syntax highlighting shellcheck|shellcheck|shellcheck|Shell script linter and validator tmux|tmux|tmux|Terminal multiplexer for persistent sessions sqlite3|sqlite3|sqlite|Lightweight database engine and client wget|wget|wget|Classic downloader (curl alternative) gnupg|gpg|gnupg|GnuPG for signing and encryption pandoc|pandoc|pandoc|Universal document converter (docx/md/pdf/html ...) poppler|pdftotext pdfinfo|poppler|PDF text + image extraction (feed PDFs to AI) tesseract|tesseract|tesseract|OCR engine (turn images/scans into text) yt-dlp|yt-dlp|yt-dlp|Media downloader (pairs with ffmpeg) jc|jc|jc|Convert command output to JSON for AI parsing glow|glow|glow|Render Markdown beautifully in the terminal gh|gh|gh|GitHub CLI (also powers Copilot CLI) gron|gron|gron|Greppable JSON (gron / gron -u) for AI + jq workflows just|just|just|Command/task runner (justfile recipes) " # macOS GNU userland + quality-of-life (binaries are g-prefixed; check formula) MAC_TABLE=" coreutils|gdate gtimeout|coreutils|GNU core utilities (gdate, gtimeout, gln ...) gnu-sed|gsed|gnu-sed|GNU sed (gsed) — matches Linux sed behavior findutils|gfind|findutils|GNU find/xargs (gfind, gxargs) gawk|gawk|gawk|GNU awk grep|ggrep|grep|GNU grep (ggrep) watch|watch|watch|Re-run a command on an interval trash|trash|trash|Move files to Trash instead of rm terminal-notifier|terminal-notifier|terminal-notifier|macOS notifications from the CLI mas|mas|mas|Mac App Store command-line installer " install_table() { local table="$1" local key cmds formula desc pretty while IFS='|' read -r key cmds formula desc; do [ -z "$key" ] && continue if cmd_exists_any "$cmds"; then ok "$key is installed" add_result "$key" "PRESENT" "" else info "$key: $desc" if ask_yn "Install $key?"; then brew_install "$formula" if cmd_exists_any "$cmds"; then ok "$key installed" add_result "$key" "INSTALLED" "" else warn "$key may need a new shell to appear on PATH" add_result "$key" "INSTALLED" "restart shell" fi else add_result "$key" "SKIPPED" "" fi fi done <<< "$table" } install_table "$CLI_TABLE" step "Installing macOS power tools (GNU userland + quality-of-life)" install_table "$MAC_TABLE" # ─── Python packages for AI development ────────────────────────────────────── if [ -n "$PYTHON_CMD" ]; then step "Python packages for AI development" info "These libraries enhance what Claude Code and AI tools can do:" info " Data: numpy, pandas, polars, matplotlib, openpyxl" info " Web : requests, httpx, beautifulsoup4" info " AI : anthropic, openai, fastmcp, mcp" info " Dev : pydantic, rich, pyyaml, python-dotenv" if ask_yn "Install recommended Python packages?"; then pip_install numpy pandas polars matplotlib openpyxl requests httpx \ beautifulsoup4 lxml anthropic openai fastmcp mcp pydantic rich \ pyyaml python-dotenv Pillow chardet tabulate jsonlines ok "Python packages installed (into Homebrew Python via --break-system-packages)" add_result "Python Packages" "INSTALLED" "AI + data libs" else add_result "Python Packages" "SKIPPED" "" fi fi # ═══════════════════════════════════════════════════════════════════════════════ # PHASE 2: AI TERMINALS, FRAMEWORKS & CONVERSIONS # ═══════════════════════════════════════════════════════════════════════════════ show_progress "AI Terminals & Frameworks" section "PHASE 2: AI Terminal Solutions" echo "" echo -e " ${WHITE}Which AI terminal solutions would you like to set up?${NC}" echo "" echo -e " ${CYAN}┌──────────────────────────────────────────────────────────────┐${NC}" echo -e " ${CYAN}│ 1. Claude Code (Anthropic) │${NC}" echo -e " ${GRAY}│ Best for: deep code understanding, multi-file edits, │${NC}" echo -e " ${GRAY}│ agentic coding, MCP servers, security research │${NC}" echo -e " ${CYAN}│ 2. OpenAI Codex CLI │${NC}" echo -e " ${GRAY}│ Best for: OpenAI-model agentic coding in the terminal │${NC}" echo -e " ${CYAN}│ 3. ChatGPT CLI (OpenAI) │${NC}" echo -e " ${GRAY}│ Best for: quick questions, brainstorming, general AI │${NC}" echo -e " ${CYAN}│ 4. GitHub Copilot CLI (GitHub) │${NC}" echo -e " ${GRAY}│ Best for: shell command suggestions, git operations │${NC}" echo -e " ${CYAN}└──────────────────────────────────────────────────────────────┘${NC}" echo "" INSTALL_CLAUDE=false INSTALL_CODEX=false INSTALL_CHATGPT=false INSTALL_COPILOT=false ask_yn "Install Claude Code?" && INSTALL_CLAUDE=true ask_yn "Install OpenAI Codex CLI?" && INSTALL_CODEX=true ask_yn "Install ChatGPT CLI tools?" && INSTALL_CHATGPT=true ask_yn "Install GitHub Copilot CLI?" && INSTALL_COPILOT=true # ─── Claude Code ────────────────────────────────────────────────────────────── if $INSTALL_CLAUDE; then section "Installing Claude Code" if cmd_exists claude; then ok "Claude Code already installed ($(claude --version 2>/dev/null || echo installed))" add_result "Claude Code" "PRESENT" "" else step "Installing Claude Code via official installer..." curl -fsSL https://claude.ai/install.sh | sh >> "$LOG_FILE" 2>&1 || true add_to_profile 'export PATH="$HOME/.local/bin:$PATH"' '.local/bin' export PATH="$ACTUAL_HOME/.local/bin:$PATH" if cmd_exists claude; then ok "Claude Code installed!" add_result "Claude Code" "INSTALLED" "" elif cmd_exists npm; then warn "Official installer may need a new shell. Trying npm fallback..." npm install -g @anthropic-ai/claude-code >> "$LOG_FILE" 2>&1 || true if cmd_exists claude; then ok "Claude Code installed via npm" add_result "Claude Code" "INSTALLED" "via npm" else fail "Claude Code install needs a terminal restart" tip "Open a new terminal, then run: claude" add_result "Claude Code" "INSTALLED" "restart terminal" fi else fail "npm not available for fallback install" add_result "Claude Code" "FAILED" "npm missing" fi fi echo "" echo -e " ${CYAN}┌──────────────────────────────────────────────────────────────┐${NC}" echo -e " ${CYAN}│ CLAUDE CODE AUTHENTICATION │${NC}" echo -e " ${CYAN}├──────────────────────────────────────────────────────────────┤${NC}" echo -e " ${GRAY}│ Option A - Browser login (easiest): │${NC}" echo -e " ${WHITE}│ 1. Run: claude │${NC}" echo -e " ${WHITE}│ 2. Browser opens -> log in at claude.ai -> Authorize │${NC}" echo -e " ${GRAY}│ Option B - API key: │${NC}" echo -e " ${WHITE}│ export ANTHROPIC_API_KEY=\"sk-ant-your-key\" │${NC}" echo -e " ${CYAN}└──────────────────────────────────────────────────────────────┘${NC}" if ask_yn "Set ANTHROPIC_API_KEY now?" "n"; then echo -ne " ${YELLOW}Enter your Anthropic API key (sk-ant-...): ${NC}" read -r ANTHROPIC_KEY if [[ "$ANTHROPIC_KEY" == sk-ant-* ]]; then add_to_profile_kv ANTHROPIC_API_KEY "$ANTHROPIC_KEY" export ANTHROPIC_API_KEY="$ANTHROPIC_KEY" ok "ANTHROPIC_API_KEY saved to $PROFILE_FILE (mode 600)" else warn "Key doesn't start with sk-ant- . Skipped. Use browser login instead." fi fi fi # ─── OpenAI Codex CLI ───────────────────────────────────────────────────────── if $INSTALL_CODEX; then section "Installing OpenAI Codex CLI" if cmd_exists codex; then ok "Codex already installed ($(codex --version 2>/dev/null || echo installed))" add_result "OpenAI Codex" "PRESENT" "" else step "Installing Codex..." if cmd_exists npm; then npm install -g @openai/codex >> "$LOG_FILE" 2>&1 || true fi if ! cmd_exists codex; then brew_install_cask codex # Codex ships as a Homebrew cask, not a formula fi if cmd_exists codex; then ok "Codex installed (run: codex)" add_result "OpenAI Codex" "INSTALLED" "" else warn "Codex install needs a terminal restart" add_result "OpenAI Codex" "INSTALLED" "restart terminal" fi fi info "Auth: run 'codex' and sign in with ChatGPT, or set OPENAI_API_KEY." fi # ─── ChatGPT CLI ────────────────────────────────────────────────────────────── if $INSTALL_CHATGPT; then section "Installing ChatGPT CLI Tools" if cmd_exists npm; then npm install -g chatgpt-cli >> "$LOG_FILE" 2>&1 || true ok "chatgpt-cli installed (run: chatgpt)" add_result "ChatGPT CLI" "INSTALLED" "chatgpt-cli" else warn "npm not available - cannot install ChatGPT CLI" add_result "ChatGPT CLI" "FAILED" "npm missing" fi [ -n "${PYTHON_CMD:-}" ] && pip_install openai && ok "OpenAI Python SDK installed" echo "" echo -e " ${CYAN}┌──────────────────────────────────────────────────────────────┐${NC}" echo -e " ${CYAN}│ CHATGPT / OPENAI AUTHENTICATION │${NC}" echo -e " ${WHITE}│ 1. Get a key: https://platform.openai.com/api-keys │${NC}" echo -e " ${WHITE}│ 2. export OPENAI_API_KEY=\"sk-your-key\" │${NC}" echo -e " ${CYAN}└──────────────────────────────────────────────────────────────┘${NC}" if ask_yn "Set OPENAI_API_KEY now?" "n"; then echo -ne " ${YELLOW}Enter your OpenAI API key (sk-...): ${NC}" read -r OPENAI_KEY if [[ "$OPENAI_KEY" == sk-* ]]; then add_to_profile_kv OPENAI_API_KEY "$OPENAI_KEY" export OPENAI_API_KEY="$OPENAI_KEY" ok "OPENAI_API_KEY saved to $PROFILE_FILE (mode 600)" else warn "Key format not recognized. Set it manually later." fi fi fi # ─── GitHub Copilot CLI ─────────────────────────────────────────────────────── if $INSTALL_COPILOT; then section "Installing GitHub Copilot CLI" if ! cmd_exists gh; then info "GitHub CLI is required for Copilot CLI." if ask_yn "Install GitHub CLI?"; then brew_install gh; fi fi if cmd_exists gh; then ok "GitHub CLI: $(gh --version 2>/dev/null | head -1)" add_result "GitHub CLI" "PRESENT" "" step "Installing Copilot CLI extension..." gh extension install github/gh-copilot >> "$LOG_FILE" 2>&1 || true ok "Copilot CLI extension installed" add_result "GitHub Copilot CLI" "INSTALLED" "" echo "" echo -e " ${CYAN}┌──────────────────────────────────────────────────────────────┐${NC}" echo -e " ${CYAN}│ GITHUB COPILOT AUTHENTICATION │${NC}" echo -e " ${WHITE}│ 1. gh auth login -> GitHub.com -> web browser │${NC}" echo -e " ${WHITE}│ 2. Copy the one-time code, authorize in the browser │${NC}" echo -e " ${GRAY}│ Requires a GitHub Copilot subscription. │${NC}" echo -e " ${CYAN}└──────────────────────────────────────────────────────────────┘${NC}" if ask_yn "Log in to GitHub now?" "n"; then gh auth login; fi else fail "GitHub CLI not available" add_result "GitHub Copilot CLI" "FAILED" "gh CLI missing" fi fi # ─── AI frameworks & conversion CLIs (code generation + data conversions) ──── section "AI Frameworks & Conversion CLIs" info "These power code generation, repo-to-prompt packing, and feeding" info "documents/media into any AI model." echo "" if ask_yn "Install the AI framework + conversion bundle?"; then # npm-based agentic / repo tooling if cmd_exists npm; then step "repomix, ast-grep, tldr (npm)" npm install -g repomix @ast-grep/cli tldr >> "$LOG_FILE" 2>&1 || true cmd_exists repomix && ok "repomix installed" && add_result "repomix" "INSTALLED" "" || add_result "repomix" "SKIPPED" "restart shell" cmd_exists ast-grep && ok "ast-grep installed (structural code search/rewrite)" && add_result "ast-grep" "INSTALLED" "" || add_result "ast-grep" "SKIPPED" "restart shell" cmd_exists tldr && ok "tldr installed" && add_result "tldr" "INSTALLED" "" || add_result "tldr" "SKIPPED" "restart shell" # Google Gemini CLI installs the '@google/gemini-cli' package (command: # 'gemini'). NOTE: Google is sunsetting Gemini CLI ~2026-06-18, so it is # opt-in here and skipped under --auto. if $AUTO_MODE; then info "Skipping Google Gemini CLI under --auto (Google is sunsetting it ~2026-06-18)." elif ask_yn "Install Google Gemini CLI? (Google is sunsetting it ~2026-06-18)" "n"; then npm install -g @google/gemini-cli >> "$LOG_FILE" 2>&1 || true cmd_exists gemini && ok "gemini installed (command: gemini)" && add_result "gemini-cli" "INSTALLED" "" || add_result "gemini-cli" "SKIPPED" "restart shell" fi fi # pipx-based standalone Python AI CLIs (isolated, no PEP 668 conflicts) if cmd_exists pipx; then step "aider (AI pair programmer in the terminal)" pipx_install aider-chat cmd_exists aider && ok "aider installed" && add_result "aider" "INSTALLED" "" || add_result "aider" "SKIPPED" "restart shell" step "llm (Simon Willison's pluggable LLM CLI)" pipx_install llm cmd_exists llm && ok "llm installed" && add_result "llm" "INSTALLED" "" || add_result "llm" "SKIPPED" "restart shell" step "files-to-prompt (turn a tree of files into one prompt)" pipx_install files-to-prompt cmd_exists files-to-prompt && ok "files-to-prompt installed" && add_result "files-to-prompt" "INSTALLED" "" || add_result "files-to-prompt" "SKIPPED" "restart shell" step "markitdown (Office/PDF/images -> Markdown for LLMs)" pipx_install "markitdown[all]" cmd_exists markitdown && ok "markitdown installed" && add_result "markitdown" "INSTALLED" "" || add_result "markitdown" "SKIPPED" "restart shell" step "httpie (friendly HTTP client; the command is 'http')" pipx_install httpie cmd_exists http && ok "httpie installed (command: http)" && add_result "httpie" "INSTALLED" "" || add_result "httpie" "SKIPPED" "restart shell" # Refresh any already-installed pipx apps to their latest release (so a # re-run keeps the toolchain current without editing the script). pipx upgrade-all >> "$LOG_FILE" 2>&1 || true else warn "pipx unavailable - skipping aider/llm/files-to-prompt/markitdown" fi # aichat — all-in-one Rust LLM CLI (brew formula) step "aichat (all-in-one LLM CLI: chat, REPL, RAG, shell assistant)" if cmd_exists aichat; then ok "aichat already installed" add_result "aichat" "PRESENT" "" else brew_install aichat cmd_exists aichat && ok "aichat installed" && add_result "aichat" "INSTALLED" "" || add_result "aichat" "SKIPPED" "" fi else info "Skipped AI framework bundle. Re-run anytime to add them." fi # ─── Optional: Ollama (local models) ───────────────────────────────────────── if $WITH_OLLAMA || ask_yn "Install Ollama for offline/local models?" "n"; then section "Installing Ollama (local model runner)" if cmd_exists ollama; then ok "Ollama already installed" add_result "Ollama" "PRESENT" "" else brew_install ollama cmd_exists ollama && ok "Ollama installed" && add_result "Ollama" "INSTALLED" "" || add_result "Ollama" "SKIPPED" "" fi if cmd_exists ollama; then info "Start the server with: ollama serve (or it runs as a brew service)" if ask_yn "Pull a small starter model now (llama3.2, ~2GB)?" "n"; then brew services start ollama >> "$LOG_FILE" 2>&1 || (ollama serve >> "$LOG_FILE" 2>&1 &) || true sleep 2 ollama pull llama3.2 || warn "Pull failed - run 'ollama pull llama3.2' later" fi fi fi # ─── Optional: Kimi Code CLI (Moonshot AI — China-operated, OFF by default) ── # Gated EXACTLY like Ollama: opt-in via --with-kimi, otherwise prompted with a # default of "n". Under --auto/--all/--everything, ask_yn returns the "n" # default, so the full-stack run does NOT install Kimi. US-only models stay the # default; a user must explicitly choose this China-operated tool. if $WITH_KIMI || ask_yn "Install Kimi Code CLI (Moonshot AI — China-operated, off by default)?" "n"; then section "Installing Kimi Code CLI (Moonshot AI)" if cmd_exists kimi; then ok "Kimi Code CLI already installed ($(kimi --version 2>/dev/null || echo installed))" add_result "Kimi Code CLI" "PRESENT" "" else step "Installing Kimi Code CLI via official installer (run as your user, no sudo)..." curl -LsSf https://code.kimi.com/install.sh | bash >> "$LOG_FILE" 2>&1 || true add_to_profile 'export PATH="$HOME/.local/bin:$PATH"' '.local/bin' export PATH="$ACTUAL_HOME/.local/bin:$PATH" if cmd_exists kimi; then ok "Kimi Code CLI installed ($(kimi --version 2>/dev/null || echo installed))" add_result "Kimi Code CLI" "INSTALLED" "" else warn "Kimi Code CLI install may need a new shell to appear on PATH" add_result "Kimi Code CLI" "INSTALLED" "restart shell" fi fi echo "" info "Kimi Code CLI auth: run 'kimi' then '/login' (browser OAuth: \"Kimi Code\")," info "or paste an API key from https://platform.kimi.ai (Console -> create key)." info "Account needs a >= \$1 recharge to activate; the \$1 starter tier is only" info "3 RPM, so for real agentic loops budget the \$10 tier." echo "" info "OPTIONAL — use Kimi as a drop-in model INSIDE Claude Code (no separate CLI)" info "via Moonshot's Anthropic-compatible endpoint. Set these in your shell ONLY" info "when you want Claude Code to talk to Kimi (do NOT set them to keep US models):" info " export ANTHROPIC_BASE_URL=https://api.moonshot.ai/anthropic" info " export ANTHROPIC_AUTH_TOKEN=" info " export ANTHROPIC_MODEL=kimi-k2.7-code" info "Leaving those three unset keeps Claude Code on US (Anthropic) models." warn "Moonshot AI is China-operated (both api.moonshot.ai and api.moonshot.cn) — do NOT send confidential client data through it." fi # ─── Optional: LibreOffice (document conversions) ──────────────────────────── if $WITH_OFFICE; then section "Installing LibreOffice (document conversions)" if [ -d "/Applications/LibreOffice.app" ] || cmd_exists soffice; then ok "LibreOffice already installed" add_result "LibreOffice" "PRESENT" "" else brew_install_cask libreoffice add_result "LibreOffice" "INSTALLED" "" info "Convert via: soffice --headless --convert-to pdf file.docx" fi fi # ═══════════════════════════════════════════════════════════════════════════════ # AI ECOSYSTEM: MCP SERVERS & ALIGNED REPOS # ═══════════════════════════════════════════════════════════════════════════════ section "AI Ecosystem: MCP Servers & Aligned Repos" if cmd_exists claude; then if ask_yn "Register common MCP servers with Claude Code (user scope)?"; then mkdir -p "$ACTUAL_HOME/.ai-tools" # npm/npx servers (need only Node) — flags BEFORE the name, command after `--` claude mcp add --scope user filesystem -- npx -y @modelcontextprotocol/server-filesystem "$ACTUAL_HOME" >> "$LOG_FILE" 2>&1 || true claude mcp add --scope user memory -e MEMORY_FILE_PATH="$ACTUAL_HOME/.ai-tools/memory.json" -- npx -y @modelcontextprotocol/server-memory >> "$LOG_FILE" 2>&1 || true claude mcp add --scope user sequential-thinking -- npx -y @modelcontextprotocol/server-sequential-thinking >> "$LOG_FILE" 2>&1 || true # python/uvx servers (need uv) — fetch + time live on PyPI, not npm if cmd_exists uvx; then claude mcp add --scope user fetch -- uvx mcp-server-fetch >> "$LOG_FILE" 2>&1 || true claude mcp add --scope user time -- uvx mcp-server-time >> "$LOG_FILE" 2>&1 || true else info "uvx not found — skipping Python MCP servers (fetch, time). 'brew install uv' to add them." fi claude mcp add --scope user playwright -- npx -y @playwright/mcp@latest >> "$LOG_FILE" 2>&1 || true ok "MCP servers registered (run 'claude mcp list' to verify)" add_result "MCP servers" "INSTALLED" "fs,memory,seq,fetch,time,pw" else add_result "MCP servers" "SKIPPED" "" fi else info "Claude Code not installed — skipping MCP server registration." fi echo "" info "Aligned GitHub repos worth bookmarking:" info " awesome-claude-code https://github.com/hesreallyhim/awesome-claude-code" info " awesome-mcp-servers https://github.com/punkpeye/awesome-mcp-servers" info " MCP reference servers https://github.com/modelcontextprotocol/servers" info " OpenAI Codex CLI https://github.com/openai/codex" info " Aider https://github.com/Aider-AI/aider" if cmd_exists git && ask_yn "Clone these reference repos into ~/ai-tools?" "n"; then AI_TOOLS_DIR="$ACTUAL_HOME/ai-tools" mkdir -p "$AI_TOOLS_DIR" for repo in \ "https://github.com/hesreallyhim/awesome-claude-code.git" \ "https://github.com/punkpeye/awesome-mcp-servers.git" \ "https://github.com/modelcontextprotocol/servers.git" \ "https://github.com/openai/codex.git" \ "https://github.com/Aider-AI/aider.git"; do rname="$(basename "$repo" .git)" if [ -d "$AI_TOOLS_DIR/$rname/.git" ]; then ok "$rname already cloned" else git clone --depth 1 "$repo" "$AI_TOOLS_DIR/$rname" >> "$LOG_FILE" 2>&1 && ok "cloned $rname" || warn "clone failed: $rname" fi done add_result "Aligned repos" "INSTALLED" "$AI_TOOLS_DIR" fi # ═══════════════════════════════════════════════════════════════════════════════ # PHASE 3: DEPLOYMENT TOOLS # ═══════════════════════════════════════════════════════════════════════════════ show_progress "Deployment & Extras" section "PHASE 3: Netlify CLI & Deployment" step "Checking Netlify CLI" if cmd_exists netlify; then ok "Netlify CLI already installed ($(netlify --version 2>/dev/null))" add_result "Netlify CLI" "PRESENT" "" else info "Netlify CLI deploys websites directly from your terminal." if ask_yn "Install Netlify CLI?"; then if cmd_exists npm; then npm install -g netlify-cli >> "$LOG_FILE" 2>&1 || true cmd_exists netlify && ok "Netlify CLI installed" && add_result "Netlify CLI" "INSTALLED" "" \ || { warn "Netlify CLI installed but needs terminal restart for PATH"; add_result "Netlify CLI" "INSTALLED" "restart terminal"; } else fail "npm not available - install Node.js first" add_result "Netlify CLI" "FAILED" "npm missing" fi else add_result "Netlify CLI" "SKIPPED" "" fi fi echo "" echo -e " ${CYAN}┌──────────────────────────────────────────────────────────────┐${NC}" echo -e " ${CYAN}│ NETLIFY: LOGIN & DEPLOY │${NC}" echo -e " ${WHITE}│ netlify login │${NC}" echo -e " ${WHITE}│ cd /path/to/site && netlify init │${NC}" echo -e " ${WHITE}│ netlify deploy # preview draft │${NC}" echo -e " ${WHITE}│ netlify deploy --prod # push live │${NC}" echo -e " ${CYAN}└──────────────────────────────────────────────────────────────┘${NC}" if cmd_exists netlify; then if ask_yn "Log in to Netlify now?" "n"; then netlify login; fi fi # ─── Cloudflare Wrangler (Pages + Workers) ─────────────────────────────────── step "Checking Cloudflare Wrangler" if cmd_exists wrangler; then ok "Wrangler already installed ($(wrangler --version 2>/dev/null | head -1))" add_result "Cloudflare Wrangler" "PRESENT" "" else info "Wrangler deploys to Cloudflare Pages (static sites) and Workers (edge)." info "Free tier: unlimited static requests, generous Workers quota, custom domains." if ask_yn "Install Cloudflare Wrangler?"; then if cmd_exists npm; then npm install -g wrangler >> "$LOG_FILE" 2>&1 || true if cmd_exists wrangler; then ok "Wrangler installed" add_result "Cloudflare Wrangler" "INSTALLED" "" else warn "Wrangler installed but needs terminal restart for PATH" add_result "Cloudflare Wrangler" "INSTALLED" "restart terminal" fi else fail "npm not available - install Node.js first" add_result "Cloudflare Wrangler" "FAILED" "npm missing" fi else add_result "Cloudflare Wrangler" "SKIPPED" "" fi fi info "Cloudflare deploy quickstart:" tip "wrangler login # browser OAuth" tip "wrangler pages deploy ./dist --project-name=NAME # static site -> Pages" tip "wrangler deploy # deploy a Worker (edge)" tip "Headless/CI: export CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID" if cmd_exists wrangler; then if ask_yn "Log in to Cloudflare now?" "n"; then wrangler login; fi fi step "Checking Vercel CLI" if cmd_exists vercel; then ok "Vercel CLI already installed" add_result "Vercel CLI" "PRESENT" "" else if ask_yn "Install Vercel CLI?" "n"; then if cmd_exists npm; then npm install -g vercel >> "$LOG_FILE" 2>&1 || true cmd_exists vercel && ok "Vercel CLI installed" && add_result "Vercel CLI" "INSTALLED" "" || add_result "Vercel CLI" "FAILED" "" else fail "npm not available" add_result "Vercel CLI" "FAILED" "npm missing" fi else add_result "Vercel CLI" "SKIPPED" "" fi fi # ═══════════════════════════════════════════════════════════════════════════════ # PHASE 4: ENVIRONMENT HEALTH CHECK # ═══════════════════════════════════════════════════════════════════════════════ show_progress "Validation & Health Check" section "PHASE 4: Environment Health Check" echo "" step "Running functional validation tests" if cmd_exists node; then NODE_TEST="$(node -e "console.log('node-ok')" 2>/dev/null || true)" [[ "$NODE_TEST" == *node-ok* ]] && ok "Node.js exec: PASS" || warn "Node.js exec: FAIL" fi if [ -n "${PYTHON_CMD:-}" ]; then PY_SSL="$($PYTHON_CMD -c "import ssl; print('py-ssl-ok:', ssl.OPENSSL_VERSION)" 2>/dev/null || true)" [[ "$PY_SSL" == *py-ssl-ok* ]] && ok "Python SSL: PASS (${PY_SSL#*: })" || warn "Python SSL: FAIL" fi if cmd_exists git; then GIT_LS="$(git ls-remote --heads https://github.com/anthropics/claude-code.git 2>/dev/null || true)" [ -n "$GIT_LS" ] && ok "Git HTTPS: PASS" || warn "Git HTTPS: FAIL" fi echo "" printf " ${WHITE}%-18s %-10s %-35s${NC}\n" "Component" "Status" "Version / Details" printf " %-18s %-10s %-35s\n" "──────────────────" "──────────" "───────────────────────────────────" # Name|command (the command may contain `||` fallbacks for alt binary names) CHECKS=" Homebrew|brew --version Git|git --version Node.js|node --version npm|npm --version Python|python3 --version pipx|pipx --version uv|uv --version Claude Code|claude --version OpenAI Codex|codex --version ChatGPT CLI|chatgpt --version GitHub CLI|gh --version Netlify CLI|netlify --version Cloudflare Wrangler|wrangler --version aider|aider --version llm|llm --version repomix|repomix --version ast-grep|ast-grep --version tldr|tldr --version httpie|http --version gron|gron --version just|just --version gemini-cli|gemini --version aichat|aichat --version ripgrep|rg --version jq|jq --version fd|fd --version || fdfind --version ImageMagick|magick --version || convert --version ffmpeg|ffmpeg -version pandoc|pandoc --version pdftotext|pdftotext -v tesseract|tesseract --version yt-dlp|yt-dlp --version markitdown|markitdown --version jc|jc -v bat|bat --version || batcat --version fzf|fzf --version shellcheck|shellcheck --version tmux|tmux -V sqlite3|sqlite3 --version " PASS_COUNT=0 FAIL_COUNT=0 while IFS='|' read -r name cmd; do [ -z "$name" ] && continue # Capture stderr too (tesseract/pdftotext print version there) but drop shell # "command not found" noise (forced English via LC_ALL=C) and runtime errors, # so a missing/broken tool reads as empty instead of a false PASS. ver="$(LC_ALL=C bash -c "$cmd" 2>&1 | grep -viE 'command not found|no such file|dyld|library not loaded|symbol not found|abort trap|illegal instruction|killed|traceback' | head -1 || true)" padded_name="$(printf "%-18s" "$name")" if [ -n "$ver" ]; then printf " %s ${GREEN}%-10s${NC} ${GRAY}%s${NC}\n" "$padded_name" "PASS" "${ver:0:35}" PASS_COUNT=$((PASS_COUNT + 1)) else printf " %s ${YELLOW}%-10s${NC} ${GRAY}%s${NC}\n" "$padded_name" "—" "not installed / skipped" FAIL_COUNT=$((FAIL_COUNT + 1)) fi done <<< "$CHECKS" echo "" echo -e " ─────────────────────────────────────────────────────────────────" echo -e " ${GREEN}Installed/Present: $PASS_COUNT${NC} ${GRAY}Not installed: $FAIL_COUNT${NC}" # ═══════════════════════════════════════════════════════════════════════════════ # PHASE 5: QUICK REFERENCE GUIDE # ═══════════════════════════════════════════════════════════════════════════════ show_progress "Complete" section "PHASE 5: Quick Reference Guide" echo -e "${CYAN}" cat << 'GUIDE' ┌─────────────────────────────────────────────────────────────────┐ │ HOW TO USE YOUR AI TERMINALS (macOS) │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ CLAUDE CODE claude start a session │ │ claude "fix bug" one-shot │ │ claude doctor diagnose config │ │ OPENAI CODEX codex OpenAI agentic coding │ │ CHATGPT CLI chatgpt quick Q&A │ │ COPILOT CLI gh copilot suggest shell command help │ │ │ │ AI FRAMEWORKS │ │ ───────────── │ │ aider AI pair programmer over your git repo │ │ llm "prompt" one-shot prompt; llm -c to continue │ │ repomix pack a repo into one file for any model │ │ files-to-prompt . turn a folder into a single prompt │ │ gemini Google Gemini in the terminal │ │ aichat chat / REPL / shell assistant │ │ │ │ CONVERSIONS (feed anything to an AI) │ │ ─────────── │ │ markitdown f.docx Office/PDF/images -> Markdown │ │ pdftotext f.pdf - PDF -> text on stdout │ │ tesseract img out OCR an image/scan to text │ │ pandoc a.md -o b.pdf convert between doc formats │ │ jc -p df command output -> JSON │ │ │ │ DEPLOY netlify deploy --prod │ │ │ │ THE MAGIC COMBO │ │ cd /path/to/site && claude │ │ > improve the hero section, then deploy a draft to Netlify │ │ │ │ MCP CONNECTORS (extend Claude Code) │ │ claude mcp add playwright -- npx @playwright/mcp@latest │ │ claude mcp add filesystem -- npx -y \ │ │ @modelcontextprotocol/server-filesystem ~/ │ │ claude mcp list │ │ │ │ TIPS │ │ - cd into your project BEFORE starting claude │ │ - Open a NEW terminal after install so PATH updates load │ │ - Homebrew on Apple Silicon lives in /opt/homebrew │ │ │ └─────────────────────────────────────────────────────────────────┘ GUIDE echo -e "${NC}" # ═══════════════════════════════════════════════════════════════════════════════ # FINAL SUMMARY # ═══════════════════════════════════════════════════════════════════════════════ ELAPSED="$((SECONDS / 60))m$((SECONDS % 60))s" echo -e " ${GREEN}╔═══════════════════════════════════════════════════════════════════╗${NC}" echo -e " ${GREEN}║ KICKSTART COMPLETE ║${NC}" echo -e " ${GREEN}╠═══════════════════════════════════════════════════════════════════╣${NC}" printf " ${GREEN}║${NC} ${WHITE}%-22s %-12s %-25s${NC} ${GREEN}║${NC}\n" "Component" "Status" "Detail" printf " ${GREEN}║${NC} %-22s %-12s %-25s ${GREEN}║${NC}\n" "──────────────────────" "────────────" "─────────────────────────" idx=0 while [ "$idx" -lt "${#RESULT_NAMES[@]}" ]; do name="${RESULT_NAMES[$idx]}" status="${RESULT_STATUS[$idx]}" detail="${RESULT_DETAIL[$idx]:0:25}" case "$status" in INSTALLED|PRESENT|OK) color="$GREEN" ;; SKIPPED|PARTIAL|TRIGGERED) color="$YELLOW" ;; FAILED|ERROR) color="$RED" ;; *) color="$GRAY" ;; esac printf " ${GREEN}║${NC} ${color}%-22s %-12s${NC} ${GRAY}%-25s${NC} ${GREEN}║${NC}\n" "$name" "$status" "$detail" idx=$((idx + 1)) done echo -e " ${GREEN}║ ║${NC}" echo -e " ${GREEN}║ Time elapsed: $ELAPSED$(printf '%*s' $((52 - ${#ELAPSED})) '')║${NC}" echo -e " ${GREEN}║ ║${NC}" echo -e " ${GREEN}║ NEXT STEPS: ║${NC}" echo -e " ${GREEN}║ 1. Open a NEW terminal (or: source your shell profile) ║${NC}" echo -e " ${GREEN}║ 2. Run 'claude' to start Claude Code (browser auth first time) ║${NC}" echo -e " ${GREEN}║ 3. Run 'gh auth login' for GitHub Copilot ║${NC}" echo -e " ${GREEN}║ 4. Run 'netlify login' to authenticate Netlify ║${NC}" echo -e " ${GREEN}║ 5. cd into your project folder, then 'claude' to start! ║${NC}" echo -e " ${GREEN}╚═══════════════════════════════════════════════════════════════════╝${NC}" echo "" echo -e " ${GRAY}Profile updated: $PROFILE_FILE · Log: $LOG_FILE${NC}" echo ""