Last active
March 2, 2026 22:53
-
-
Save ASRagab/2d1eb00babcc0f10adecb507eefb8f39 to your computer and use it in GitHub Desktop.
HAI Operators — Prerequisite Bootstrap (test)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # HAI Operators — Prerequisite Bootstrap | |
| # Installs system-level prerequisites, provisions Claude Code settings, | |
| # and optionally installs plugins. | |
| # Safe to run multiple times (idempotent). | |
| # | |
| # Usage: | |
| # bash bootstrap.sh (interactive) | |
| # curl -fsSL <url> | bash (remote) | |
| set -euo pipefail | |
| # --- Colors & formatting --- | |
| RED='\033[0;31m' | |
| GREEN='\033[0;32m' | |
| YELLOW='\033[1;33m' | |
| BLUE='\033[0;34m' | |
| BOLD='\033[1m' | |
| NC='\033[0m' | |
| info() { printf "${BLUE}==>${NC} ${BOLD}%s${NC}\n" "$*"; } | |
| ok() { printf "${GREEN} ✓${NC} %s\n" "$*"; } | |
| warn() { printf "${YELLOW} !${NC} %s\n" "$*"; } | |
| fail() { printf "${RED} ✗${NC} %s\n" "$*"; } | |
| # --- Pipe mode detection --- | |
| # When run via curl|bash, stdin is the script — read prompts won't work. | |
| # Detect this and auto-proceed with installs. | |
| INTERACTIVE=true | |
| if [ ! -t 0 ]; then | |
| INTERACTIVE=false | |
| info "Running in non-interactive mode (pipe detected). Will auto-install missing tools." | |
| fi | |
| # Helper: prompt user or auto-accept in pipe mode | |
| confirm() { | |
| local prompt="$1" default="${2:-Y}" | |
| if [ "$INTERACTIVE" = false ]; then | |
| return 0 # auto-yes | |
| fi | |
| read -rp "$prompt" answer | |
| case "${answer:-$default}" in | |
| [Yy]*) return 0 ;; | |
| *) return 1 ;; | |
| esac | |
| } | |
| # --- State tracking --- | |
| declare -a MISSING=() | |
| declare -a INSTALLED=() | |
| declare -a FAILED=() | |
| declare -a SKIPPED=() | |
| # --- Detection functions --- | |
| detect_brew() { | |
| if command -v brew &>/dev/null; then | |
| ok "Homebrew $(brew --version 2>/dev/null | head -1 | awk '{print $2}')" | |
| return 0 | |
| fi | |
| MISSING+=("brew") | |
| return 1 | |
| } | |
| detect_python() { | |
| if command -v python3 &>/dev/null; then | |
| local ver major minor | |
| ver=$(python3 --version 2>/dev/null | awk '{print $2}') | |
| major=$(echo "$ver" | cut -d. -f1) | |
| minor=$(echo "$ver" | cut -d. -f2) | |
| if [ "$major" -ge 3 ] && [ "$minor" -ge 12 ]; then | |
| ok "Python $ver" | |
| return 0 | |
| else | |
| warn "Python $ver found (need 3.12+)" | |
| MISSING+=("python") | |
| return 1 | |
| fi | |
| fi | |
| MISSING+=("python") | |
| return 1 | |
| } | |
| detect_node() { | |
| if command -v node &>/dev/null; then | |
| local ver major | |
| ver=$(node --version 2>/dev/null | sed 's/^v//') | |
| major=$(echo "$ver" | cut -d. -f1) | |
| if [ "$major" -ge 22 ]; then | |
| ok "Node.js v$ver" | |
| return 0 | |
| else | |
| warn "Node.js v$ver found (need 22+)" | |
| MISSING+=("node") | |
| return 1 | |
| fi | |
| fi | |
| MISSING+=("node") | |
| return 1 | |
| } | |
| detect_gh() { | |
| if command -v gh &>/dev/null; then | |
| ok "GitHub CLI $(gh --version 2>/dev/null | head -1 | awk '{print $3}')" | |
| return 0 | |
| fi | |
| MISSING+=("gh") | |
| return 1 | |
| } | |
| detect_gh_auth() { | |
| if gh auth status &>/dev/null 2>&1; then | |
| ok "GitHub CLI authenticated" | |
| return 0 | |
| fi | |
| MISSING+=("gh_auth") | |
| return 1 | |
| } | |
| detect_gcloud() { | |
| if command -v gcloud &>/dev/null; then | |
| ok "gcloud CLI $(gcloud --version 2>/dev/null | head -1 | awk '{print $NF}')" | |
| return 0 | |
| fi | |
| MISSING+=("gcloud") | |
| return 1 | |
| } | |
| # --- Install functions --- | |
| install_brew() { | |
| info "Installing Homebrew..." | |
| /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" | |
| if command -v brew &>/dev/null; then | |
| INSTALLED+=("Homebrew") | |
| ok "Homebrew installed" | |
| elif [ -f /opt/homebrew/bin/brew ]; then | |
| eval "$(/opt/homebrew/bin/brew shellenv)" | |
| INSTALLED+=("Homebrew") | |
| ok "Homebrew installed (run 'eval \$(/opt/homebrew/bin/brew shellenv)' in new terminals)" | |
| else | |
| FAILED+=("Homebrew — see https://brew.sh for manual install") | |
| fail "Homebrew install failed" | |
| fi | |
| } | |
| install_python() { | |
| info "Installing Python 3.12 via Homebrew..." | |
| if brew install python@3.12; then | |
| if python3 --version 2>/dev/null | grep -q "3\.1[2-9]"; then | |
| INSTALLED+=("Python 3.12") | |
| ok "Python 3.12 installed" | |
| else | |
| warn "Python installed but not on PATH. You may need to restart your terminal." | |
| INSTALLED+=("Python 3.12 (restart terminal)") | |
| fi | |
| else | |
| FAILED+=("Python 3.12 — try: brew install python@3.12") | |
| fail "Python 3.12 install failed" | |
| fi | |
| } | |
| install_node() { | |
| info "Installing Node.js 22 via Homebrew..." | |
| if brew install node@22; then | |
| INSTALLED+=("Node.js 22") | |
| ok "Node.js 22 installed" | |
| else | |
| FAILED+=("Node.js 22 — try: brew install node@22") | |
| fail "Node.js 22 install failed" | |
| fi | |
| } | |
| install_gh() { | |
| info "Installing GitHub CLI via Homebrew..." | |
| if brew install gh; then | |
| INSTALLED+=("GitHub CLI") | |
| ok "GitHub CLI installed" | |
| else | |
| FAILED+=("GitHub CLI — try: brew install gh") | |
| fail "GitHub CLI install failed" | |
| fi | |
| } | |
| run_gh_auth() { | |
| info "Authenticating GitHub CLI..." | |
| echo "A browser window will open. Sign in with your GitHub account and authorize the CLI." | |
| echo "" | |
| if gh auth login --web; then | |
| INSTALLED+=("GitHub auth") | |
| ok "GitHub CLI authenticated" | |
| else | |
| FAILED+=("GitHub auth — try: gh auth login --web") | |
| fail "GitHub CLI auth failed" | |
| fi | |
| } | |
| install_gcloud() { | |
| info "Installing gcloud CLI via Homebrew..." | |
| if brew install google-cloud-sdk; then | |
| INSTALLED+=("gcloud CLI") | |
| ok "gcloud CLI installed" | |
| else | |
| FAILED+=("gcloud CLI — try: brew install google-cloud-sdk") | |
| fail "gcloud CLI install failed" | |
| fi | |
| } | |
| # --- Config provisioning --- | |
| # Template files ship at scripts/configs/ inside the plugin repo. | |
| # Resolve SCRIPT_DIR so we can find them relative to this script. | |
| # When piped via curl|bash, BASH_SOURCE is unset — fall back gracefully. | |
| if [ -n "${BASH_SOURCE[0]:-}" ]; then | |
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | |
| else | |
| SCRIPT_DIR="" | |
| fi | |
| provision_configs() { | |
| local config_dir="${CLAUDE_CONFIG_DIR:-$HOME/.claude}" | |
| mkdir -p "$config_dir" | |
| if [ -z "$SCRIPT_DIR" ]; then | |
| warn "Running from pipe — config templates not available. Run /hai-setup inside Claude Code to provision settings." | |
| SKIPPED+=("settings.json (pipe mode)" "CLAUDE.md (pipe mode)") | |
| return | |
| fi | |
| # --- settings.json --- | |
| local settings_src="$SCRIPT_DIR/configs/settings.json" | |
| local settings_dst="$config_dir/settings.json" | |
| if [ -f "$settings_src" ]; then | |
| if [ -f "$settings_dst" ]; then | |
| info "settings.json already exists at $settings_dst" | |
| if confirm "Overwrite with HAI recommended settings? [y/N] " "N"; then | |
| cp "$settings_src" "$settings_dst" | |
| INSTALLED+=("settings.json") | |
| ok "Settings provisioned at $settings_dst" | |
| else | |
| SKIPPED+=("settings.json") | |
| fi | |
| else | |
| cp "$settings_src" "$settings_dst" | |
| INSTALLED+=("settings.json") | |
| ok "Settings provisioned at $settings_dst" | |
| fi | |
| else | |
| warn "Template settings.json not found at $settings_src — skipping" | |
| fi | |
| # --- CLAUDE.md --- | |
| local claude_src="$SCRIPT_DIR/configs/CLAUDE.md" | |
| local claude_dst="$config_dir/CLAUDE.md" | |
| if [ -f "$claude_src" ]; then | |
| if [ -f "$claude_dst" ]; then | |
| info "CLAUDE.md already exists at $claude_dst" | |
| if confirm "Overwrite with HAI team instructions? [y/N] " "N"; then | |
| cp "$claude_src" "$claude_dst" | |
| INSTALLED+=("CLAUDE.md") | |
| ok "CLAUDE.md provisioned at $claude_dst" | |
| else | |
| SKIPPED+=("CLAUDE.md") | |
| fi | |
| else | |
| cp "$claude_src" "$claude_dst" | |
| INSTALLED+=("CLAUDE.md") | |
| ok "CLAUDE.md provisioned at $claude_dst" | |
| fi | |
| else | |
| warn "Template CLAUDE.md not found at $claude_src — skipping" | |
| fi | |
| } | |
| # --- Plugin installation --- | |
| install_plugins() { | |
| if ! command -v claude &>/dev/null; then | |
| warn "Claude Code CLI not found. Install plugins manually after launching Claude Code." | |
| return | |
| fi | |
| echo "" | |
| if ! confirm "Install HAI plugins via Claude CLI now? [Y/n] "; then | |
| SKIPPED+=("Plugin installation") | |
| return | |
| fi | |
| info "Adding marketplace..." | |
| claude plugin marketplace add git@github.com:joinhandshake/claude-operators.git 2>/dev/null || true | |
| info "Installing plugins..." | |
| if claude plugin install hai-operators@claude-operators 2>/dev/null; then ok "hai-operators installed"; else warn "hai-operators install failed"; fi | |
| if claude plugin install hai-database@claude-operators 2>/dev/null; then ok "hai-database installed"; else warn "hai-database install failed"; fi | |
| if claude plugin install hai-playbook@claude-operators 2>/dev/null; then ok "hai-playbook installed"; else warn "hai-playbook install failed"; fi | |
| if claude plugin install slack@claude-plugins-official 2>/dev/null; then ok "slack installed"; else warn "slack install failed"; fi | |
| INSTALLED+=("HAI plugins") | |
| } | |
| # --- Summary --- | |
| print_summary() { | |
| echo "" | |
| printf "${BOLD}%s${NC}\n" "═══════════════════════════════════════" | |
| printf "${BOLD}%s${NC}\n" " HAI Operators — Bootstrap Summary" | |
| printf "${BOLD}%s${NC}\n" "═══════════════════════════════════════" | |
| echo "" | |
| if [ ${#INSTALLED[@]} -gt 0 ]; then | |
| printf '%b\n' "${GREEN}Installed/Configured:${NC}" | |
| for item in "${INSTALLED[@]}"; do printf ' %b %s\n' "${GREEN}✓${NC}" "$item"; done | |
| echo "" | |
| fi | |
| if [ ${#SKIPPED[@]} -gt 0 ]; then | |
| printf '%b\n' "${BLUE}Already installed / Skipped:${NC}" | |
| for item in "${SKIPPED[@]}"; do printf ' %b %s\n' "${BLUE}—${NC}" "$item"; done | |
| echo "" | |
| fi | |
| if [ ${#FAILED[@]} -gt 0 ]; then | |
| printf '%b\n' "${RED}Failed:${NC}" | |
| for item in "${FAILED[@]}"; do printf ' %b %s\n' "${RED}✗${NC}" "$item"; done | |
| echo "" | |
| fi | |
| echo "" | |
| if [ ${#FAILED[@]} -gt 0 ]; then | |
| printf '%b\n' "${RED}${BOLD}Some items failed (see above).${NC}" | |
| elif [ ${#SKIPPED[@]} -gt 0 ]; then | |
| printf '%b\n' "${YELLOW}${BOLD}Some items were skipped. Re-run this script when ready.${NC}" | |
| else | |
| printf '%b\n' "${GREEN}${BOLD}All prerequisites installed!${NC}" | |
| fi | |
| echo "" | |
| printf '%b\n' "${BOLD}Next step:${NC} Open Claude Code and run ${BOLD}/hai-setup${NC}" | |
| echo "" | |
| } | |
| # --- Main --- | |
| main() { | |
| echo "" | |
| printf "${BOLD}%s${NC}\n" "HAI Operators — Prerequisite Bootstrap" | |
| printf "%s\n" "Checking your system for required tools..." | |
| echo "" | |
| # Phase 1: Detect everything | |
| detect_brew || true | |
| detect_python || true | |
| detect_node || true | |
| detect_gh || true | |
| detect_gcloud || true | |
| echo "" | |
| # If nothing missing, just check gh auth | |
| if [ ${#MISSING[@]} -eq 0 ]; then | |
| info "All tools are installed!" | |
| SKIPPED+=("Homebrew" "Python 3.12+" "Node.js 22+" "GitHub CLI" "gcloud CLI") | |
| detect_gh_auth || MISSING+=("gh_auth") | |
| fi | |
| # Phase 2: Confirm and install missing items | |
| if [ ${#MISSING[@]} -gt 0 ]; then | |
| echo "" | |
| info "The following items need to be installed or configured:" | |
| for item in "${MISSING[@]}"; do | |
| case "$item" in | |
| brew) printf " • Homebrew (macOS package manager)\n" ;; | |
| python) printf " • Python 3.12+ (for Drive scripts)\n" ;; | |
| node) printf " • Node.js 22+ (for gcloud MCP)\n" ;; | |
| gh) printf " • GitHub CLI (for plugin installation)\n" ;; | |
| gh_auth) printf " • GitHub CLI authentication\n" ;; | |
| gcloud) printf " • gcloud CLI (for BigQuery and Drive)\n" ;; | |
| esac | |
| done | |
| echo "" | |
| if confirm "Install/configure these now? [Y/n] "; then | |
| for item in "${MISSING[@]}"; do | |
| case "$item" in | |
| brew) install_brew ;; | |
| python) install_python ;; | |
| node) install_node ;; | |
| gh) install_gh ;; | |
| gh_auth) run_gh_auth ;; | |
| gcloud) install_gcloud ;; | |
| esac | |
| done | |
| else | |
| info "Skipped. You can re-run this script anytime." | |
| for item in "${MISSING[@]}"; do SKIPPED+=("$item"); done | |
| fi | |
| fi | |
| # Phase 3: Check gh auth if gh was just installed | |
| if command -v gh &>/dev/null; then | |
| if ! gh auth status &>/dev/null 2>&1; then | |
| if [[ ! " ${MISSING[*]} " =~ " gh_auth " ]] && [[ ! " ${FAILED[*]} " == *"GitHub auth"* ]]; then | |
| echo "" | |
| if confirm "GitHub CLI is not authenticated. Log in now? [Y/n] "; then | |
| run_gh_auth | |
| fi | |
| fi | |
| fi | |
| fi | |
| # Phase 4: Provision Claude Code configs (settings.json + CLAUDE.md) | |
| echo "" | |
| provision_configs | |
| # Phase 5: Install plugins (if claude CLI available) | |
| install_plugins | |
| print_summary | |
| } | |
| main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment