Strict Developer Environment Installer
A robust, idempotent, and interactive bash script to bootstrap your macOS, Linux, or WSL development environment.
Unlike standard setup scripts that rely heavily on package managers like Homebrew or Apt (which can introduce bloat, dependency hell, or outdated packages), this script is designed with a "Strict Official Source" philosophy. It dynamically fetches the absolute latest binaries and pre-compiled releases directly from the official maintainers (e.g., GitHub API, Python.org, Amazon).
✨ Key Features
- Zero-Brew Dependency: By default, it bypasses Homebrew entirely, fetching tools directly from official APIs and release pages (Homebrew is available as an optional install).
- Idempotent: Safe to run multiple times. It intelligently checks if a tool is already installed before attempting to download it.
- Dynamic Versioning: Pings official APIs (like GitHub Releases or
go.dev/VERSION) to ensure you are downloading the exact latest version at the time of execution. - Auto-Path Management: Intelligently injects paths into a dedicated
~/.pathrc(or your.bashrc/.zshrc) without mangling your config files. - Architecture Aware: Automatically detects if you are running on Intel (
x86_64) or Apple Silicon/ARM (aarch64/arm64) and downloads the correct binaries.
🚀 Usage
You can launch the interactive installer directly from your terminal with a single command:
bash -c "$(curl -fsSL https://opengist.rmrf.online/weehong/af1c64c143a44ffbb0a1632dd6a32af1/raw/HEAD/menu.sh)"
Once the menu loads, simply follow the interactive prompts. You can select multiple tools at once by entering space-separated numbers (e.g., 1 3 8 12).
🛠️ Included Tools
Core Runtimes & Managers
- Build Tools: GCC, Make, Git, Curl, Unzip, and core libraries.
- Homebrew: (Optional) Installed officially.
- NVM (Node Version Manager): Includes interactive prompt to install LTS or Latest Node.js immediately.
- Python 3: Fetches the latest stable
.pkgfor Mac or compiles strictly from source for Linux. - Go: Fetches the latest tarball and installs system-wide to
/usr/local/go. - SDKMAN: For managing Java, Kotlin, and Gradle.
- .NET: Installed via Microsoft's official script.
Cloud & Infrastructure
- Docker: Downloads the official
.dmg(macOS) or runs the officialget.docker.comscript (Linux) with auto-group assignment. - AWS CLI: Fetches the official binaries directly from Amazon.
- Google Cloud CLI (gcloud): Official Google setup script.
- Firebase CLI: Standalone binary (no Node.js required natively).
Dev Tools & Security
- GitHub CLI (gh): Downloaded directly from GitHub Releases to
/usr/local/bin. - Infisical CLI: Downloaded directly from GitHub Releases for secret management.
AI & Agentic Tools
- Claude Code CLI: Anthropic's official agent.
- OpenCode: AI agent orchestration.
- OpenAI Codex CLI: Required NVM/Node.js to run.
⚠️ Important Notes & Troubleshooting
- Restart Your Shell: After running the script for the first time, you should restart your terminal or run
source ~/.bashrc(or~/.zshrc) to ensure all new PATH variables (like NVM, Go, or .NET) take effect. - Docker Permissions (Linux): The script automatically adds your user to the
dockergroup. You will need to log out and log back in for this to take effect. - AWS CLI on Linux: Ensure you install Build Tools (Option 1) first if you are on a fresh Linux install, as the AWS installer requires
unzipto extract the payload.
Built for developers who want total control over their toolchain.
| 1 | #!/usr/bin/env bash |
| 2 | |
| 3 | # ======================================================= |
| 4 | # 1. BOOTSTRAPPER: PREFER ZSH, FALLBACK TO BASH |
| 5 | # ======================================================= |
| 6 | if [ -z "${_PREFER_ZSH_BOOTSTRAPPED:-}" ]; then |
| 7 | export _PREFER_ZSH_BOOTSTRAPPED=1 |
| 8 | if command -v zsh >/dev/null 2>&1; then |
| 9 | exec zsh "$0" "$@" |
| 10 | elif command -v bash >/dev/null 2>&1; then |
| 11 | exec bash "$0" "$@" |
| 12 | else |
| 13 | echo "Error: Neither Zsh nor Bash is installed. Exiting." |
| 14 | exit 1 |
| 15 | fi |
| 16 | fi |
| 17 | |
| 18 | set -u |
| 19 | |
| 20 | # ======================================================= |
| 21 | # 2. CROSS-SHELL NORMALIZATION |
| 22 | # ======================================================= |
| 23 | if [ -n "${ZSH_VERSION:-}" ]; then |
| 24 | # Zsh: Enable Bash-like word splitting for unquoted variables (menu input) |
| 25 | setopt shwordsplit 2>/dev/null || true |
| 26 | CURRENT_SHELL="zsh" |
| 27 | PROFILE_FILE="$HOME/.zshrc" |
| 28 | elif [ -n "${BASH_VERSION:-}" ]; then |
| 29 | # Bash |
| 30 | CURRENT_SHELL="bash" |
| 31 | PROFILE_FILE="$HOME/.bashrc" |
| 32 | else |
| 33 | # Fallback POSIX |
| 34 | CURRENT_SHELL="sh" |
| 35 | PROFILE_FILE="$HOME/.profile" |
| 36 | fi |
| 37 | |
| 38 | # ======================================================= |
| 39 | # COLORS & LOGGING |
| 40 | # ======================================================= |
| 41 | GREEN="\033[0;32m" |
| 42 | RED="\033[0;31m" |
| 43 | YELLOW="\033[1;33m" |
| 44 | BLUE="\033[0;34m" |
| 45 | NC="\033[0m" |
| 46 | |
| 47 | log() { printf "${GREEN}▶ %s${NC}\n" "$*"; } |
| 48 | warn() { printf "${YELLOW}⚠ %s${NC}\n" "$*"; } |
| 49 | err() { printf "${RED}✖ %s${NC}\n" "$*"; } |
| 50 | info() { printf "${BLUE}ℹ %s${NC}\n" "$*"; } |
| 51 | |
| 52 | # ======================================================= |
| 53 | # HELPERS & SYSTEM DETECTION |
| 54 | # ======================================================= |
| 55 | require_cmd() { command -v "$1" >/dev/null 2>&1; } |
| 56 | |
| 57 | add_to_path_config() { |
| 58 | local label=$1 |
| 59 | local path_line=$2 |
| 60 | local target_file="${3:-$PROFILE_FILE}" |
| 61 | |
| 62 | if [[ -f "$target_file" ]]; then |
| 63 | if ! grep -q "$label" "$target_file"; then |
| 64 | printf "\n# %s\n%s\n" "$label" "$path_line" >> "$target_file" |
| 65 | log "Added $label to $target_file" |
| 66 | fi |
| 67 | else |
| 68 | printf "\n# %s\n%s\n" "$label" "$path_line" >> "$PROFILE_FILE" |
| 69 | log "Added $label to shell profile ($PROFILE_FILE)." |
| 70 | fi |
| 71 | } |
| 72 | |
| 73 | detect_os_arch() { |
| 74 | ARCH=$(uname -m) |
| 75 | case "$ARCH" in |
| 76 | x86_64|amd64) SYS_ARCH="amd64"; MAC_ARCH="x86_64"; AWS_ARCH="x86_64" ;; |
| 77 | aarch64|arm64) SYS_ARCH="arm64"; MAC_ARCH="arm64"; AWS_ARCH="aarch64" ;; |
| 78 | *) err "Unsupported architecture: $ARCH"; exit 1 ;; |
| 79 | esac |
| 80 | |
| 81 | if [[ "$OSTYPE" == "darwin"* ]]; then |
| 82 | OS="macOS" |
| 83 | OS_LOWER="darwin" |
| 84 | elif grep -qi microsoft /proc/version 2>/dev/null; then |
| 85 | OS="WSL" |
| 86 | OS_LOWER="linux" |
| 87 | elif [[ -f /etc/os-release ]]; then |
| 88 | . /etc/os-release |
| 89 | [[ "$ID" == "ubuntu" || "$ID_LIKE" == *"ubuntu"* ]] && OS="Ubuntu" || OS="Linux" |
| 90 | OS_LOWER="linux" |
| 91 | else |
| 92 | OS="Unknown" |
| 93 | OS_LOWER="unknown" |
| 94 | fi |
| 95 | log "Detected: $OS ($ARCH) running $CURRENT_SHELL" |
| 96 | } |
| 97 | |
| 98 | detect_os_arch |
| 99 | |
| 100 | # ======================================================= |
| 101 | # CORE RUNTIMES & MANAGERS |
| 102 | # ======================================================= |
| 103 | |
| 104 | install_build_tools() { |
| 105 | log "Installing Build Essentials & Core Dependencies..." |
| 106 | case "$OS" in |
| 107 | Ubuntu|WSL) sudo apt-get update && sudo apt-get install -y build-essential curl wget git jq unzip libssl-dev zlib1g-dev libffi-dev libsqlite3-dev ;; |
| 108 | macOS) xcode-select --install || warn "Xcode tools already installed" ;; |
| 109 | *) warn "Manual installation required for $OS." ;; |
| 110 | esac |
| 111 | } |
| 112 | |
| 113 | install_brew() { |
| 114 | if require_cmd brew; then warn "Homebrew already installed"; return; fi |
| 115 | log "Installing Homebrew from Official Source..." |
| 116 | |
| 117 | # Homebrew's installer specifically requires Bash execution, regardless of current shell |
| 118 | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" |
| 119 | |
| 120 | if [[ "$OS_LOWER" == "linux" ]]; then |
| 121 | add_to_path_config "HOMEBREW" 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"' |
| 122 | elif [[ "$MAC_ARCH" == "arm64" ]]; then |
| 123 | add_to_path_config "HOMEBREW" 'eval "$(/opt/homebrew/bin/brew shellenv)"' |
| 124 | else |
| 125 | add_to_path_config "HOMEBREW" 'eval "$(/usr/local/bin/brew shellenv)"' |
| 126 | fi |
| 127 | } |
| 128 | |
| 129 | install_nvm() { |
| 130 | export NVM_DIR="$HOME/.nvm" |
| 131 | |
| 132 | if [ -d "$NVM_DIR" ]; then |
| 133 | warn "NVM is already installed." |
| 134 | else |
| 135 | log "Installing NVM via $CURRENT_SHELL..." |
| 136 | curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | $CURRENT_SHELL |
| 137 | fi |
| 138 | |
| 139 | [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" |
| 140 | |
| 141 | printf "\n${YELLOW}Which version of Node.js would you like to install?${NC}\n" |
| 142 | printf " 1) LTS (Long Term Support - Recommended for stability)\n" |
| 143 | printf " 2) Latest (Current features - Recommended for testing new APIs)\n" |
| 144 | printf " 3) Skip installing Node.js right now\n" |
| 145 | |
| 146 | printf "Select (1/2/3): " |
| 147 | read node_choice |
| 148 | |
| 149 | case "$node_choice" in |
| 150 | 2) log "Installing Latest Node.js..."; nvm install node; nvm alias default node; nvm use node ;; |
| 151 | 3) log "Skipping Node.js installation." ;; |
| 152 | *) log "Installing Latest LTS Node.js..."; nvm install --lts; nvm alias default 'lts/*'; nvm use --lts ;; |
| 153 | esac |
| 154 | } |
| 155 | |
| 156 | install_python() { |
| 157 | log "Fetching latest stable Python version..." |
| 158 | |
| 159 | LATEST_PY=$(curl -s https://www.python.org/downloads/ | grep -o 'Download Python 3\.[0-9]\{1,2\}\.[0-9]\{1,2\}' | head -1 | grep -o '3\.[0-9]\{1,2\}\.[0-9]\{1,2\}') |
| 160 | [[ -z "$LATEST_PY" ]] && LATEST_PY="3.12.3" |
| 161 | |
| 162 | log "Detected Stable Version: $LATEST_PY" |
| 163 | |
| 164 | if [[ "$OS" == "macOS" ]]; then |
| 165 | log "Downloading official macOS package..." |
| 166 | PKG_URL="https://www.python.org/ftp/python/${LATEST_PY}/python-${LATEST_PY}-macos11.pkg" |
| 167 | curl -fsSL -o /tmp/python.pkg "$PKG_URL" |
| 168 | |
| 169 | log "Running installer..." |
| 170 | sudo installer -pkg /tmp/python.pkg -target / |
| 171 | rm /tmp/python.pkg |
| 172 | else |
| 173 | log "Installing build dependencies (zlib, ssl, etc)..." |
| 174 | sudo apt-get update && sudo apt-get install -y build-essential zlib1g-dev libssl-dev libffi-dev libsqlite3-dev |
| 175 | |
| 176 | SRC_URL="https://www.python.org/ftp/python/${LATEST_PY}/Python-${LATEST_PY}.tgz" |
| 177 | |
| 178 | log "Downloading $SRC_URL..." |
| 179 | curl -fSL -o /tmp/Python.tgz "$SRC_URL" || err "Failed to download Python source from $SRC_URL" |
| 180 | |
| 181 | cd /tmp && tar -xzf Python.tgz && cd "Python-${LATEST_PY}" |
| 182 | |
| 183 | log "Configuring build (with ensurepip)..." |
| 184 | ./configure --enable-optimizations --with-ensurepip=install |
| 185 | |
| 186 | log "Compiling Python (this may take a few minutes)..." |
| 187 | sudo make altinstall |
| 188 | |
| 189 | PY_MINOR=$(echo "$LATEST_PY" | cut -d. -f1,2) |
| 190 | |
| 191 | log "Setting up symlinks..." |
| 192 | sudo ln -sf /usr/local/bin/python${PY_MINOR} /usr/local/bin/python3 |
| 193 | |
| 194 | if [[ -f "/usr/local/bin/pip${PY_MINOR}" ]]; then |
| 195 | sudo ln -sf /usr/local/bin/pip${PY_MINOR} /usr/local/bin/pip3 |
| 196 | log "Successfully linked pip3!" |
| 197 | else |
| 198 | err "pip${PY_MINOR} was not generated during the build!" |
| 199 | fi |
| 200 | |
| 201 | cd ~ && sudo rm -rf /tmp/Python* |
| 202 | fi |
| 203 | |
| 204 | log "Python $LATEST_PY installation sequence finished!" |
| 205 | } |
| 206 | |
| 207 | install_go() { |
| 208 | log "Fetching latest Go version from official source..." |
| 209 | LATEST_GO=$(curl -sL https://go.dev/VERSION?m=text | head -n 1) |
| 210 | TAR_FILE="${LATEST_GO}.${OS_LOWER}-${SYS_ARCH}.tar.gz" |
| 211 | |
| 212 | log "Downloading $TAR_FILE..." |
| 213 | curl -fsSL -o /tmp/go.tar.gz "https://go.dev/dl/$TAR_FILE" |
| 214 | |
| 215 | log "Installing to /usr/local/go..." |
| 216 | sudo rm -rf /usr/local/go |
| 217 | sudo tar -C /usr/local -xzf /tmp/go.tar.gz |
| 218 | rm /tmp/go.tar.gz |
| 219 | add_to_path_config "GO_BIN" 'export PATH="$PATH:/usr/local/go/bin"' |
| 220 | } |
| 221 | |
| 222 | install_sdkman() { |
| 223 | if [ -d "$HOME/.sdkman" ]; then warn "SDKMAN already exists"; return; fi |
| 224 | log "Installing SDKMAN via $CURRENT_SHELL..." |
| 225 | curl -s "https://get.sdkman.io" | $CURRENT_SHELL |
| 226 | } |
| 227 | |
| 228 | install_dotnet() { |
| 229 | log "Installing .NET from official script..." |
| 230 | curl -fsSL https://dot.net/v1/dotnet-install.sh | $CURRENT_SHELL |
| 231 | add_to_path_config "DOTNET_TOOLS" 'export PATH="$PATH:$HOME/.dotnet/tools"' |
| 232 | } |
| 233 | |
| 234 | # ======================================================= |
| 235 | # CLOUD & INFRASTRUCTURE |
| 236 | # ======================================================= |
| 237 | |
| 238 | install_docker() { |
| 239 | if require_cmd docker; then log "Docker exists"; else |
| 240 | log "Installing Docker from official source..." |
| 241 | |
| 242 | if [[ "$OS" == "macOS" ]]; then |
| 243 | [[ "$SYS_ARCH" == "arm64" ]] && DOCKER_MAC_ARCH="arm64" || DOCKER_MAC_ARCH="amd64" |
| 244 | DMG_URL="https://desktop.docker.com/mac/main/${DOCKER_MAC_ARCH}/Docker.dmg" |
| 245 | curl -fsSL -o /tmp/Docker.dmg "$DMG_URL" |
| 246 | hdiutil attach /tmp/Docker.dmg -nobrowse -mountpoint /Volumes/Docker |
| 247 | sudo cp -a /Volumes/Docker/Docker.app /Applications/ |
| 248 | hdiutil detach /Volumes/Docker |
| 249 | rm /tmp/Docker.dmg |
| 250 | |
| 251 | log "Starting Docker Desktop..." |
| 252 | open /Applications/Docker.app |
| 253 | log "Please complete the setup in the Docker Desktop UI." |
| 254 | else |
| 255 | curl -fsSL https://get.docker.com | sudo sh |
| 256 | fi |
| 257 | fi |
| 258 | |
| 259 | if [[ "$OS" != "macOS" ]]; then |
| 260 | log "Applying Linux post-install actions..." |
| 261 | |
| 262 | sudo groupadd -f docker |
| 263 | sudo usermod -aG docker "$USER" |
| 264 | |
| 265 | if command -v systemctl >/dev/null 2>&1; then |
| 266 | sudo systemctl enable --now docker.service |
| 267 | sudo systemctl enable --now containerd.service |
| 268 | elif [[ "$OS" == "WSL" ]]; then |
| 269 | sudo service docker start |
| 270 | fi |
| 271 | |
| 272 | if [[ -S /var/run/docker.sock ]]; then |
| 273 | log "Fixing ownership of /var/run/docker.sock..." |
| 274 | sudo chown root:docker /var/run/docker.sock |
| 275 | sudo chmod 660 /var/run/docker.sock |
| 276 | fi |
| 277 | |
| 278 | log "Added $USER to the docker group." |
| 279 | log "CRITICAL: To apply group changes immediately, run: newgrp docker" |
| 280 | fi |
| 281 | } |
| 282 | |
| 283 | install_aws() { |
| 284 | if require_cmd aws; then warn "AWS CLI exists"; return; fi |
| 285 | log "Installing AWS CLI directly from Amazon..." |
| 286 | |
| 287 | if [[ "$OS" == "macOS" ]]; then |
| 288 | curl -fsSL -o /tmp/AWSCLIV2.pkg "https://awscli.amazonaws.com/AWSCLIV2.pkg" |
| 289 | sudo installer -pkg /tmp/AWSCLIV2.pkg -target / |
| 290 | rm /tmp/AWSCLIV2.pkg |
| 291 | else |
| 292 | if ! require_cmd unzip; then |
| 293 | err "unzip is required. Please install 'Build Tools' (Option 1) first." |
| 294 | return 1 |
| 295 | fi |
| 296 | curl -fsSL -o /tmp/awscliv2.zip "https://awscli.amazonaws.com/awscli-exe-linux-${AWS_ARCH}.zip" |
| 297 | cd /tmp && unzip -q awscliv2.zip && sudo ./aws/install |
| 298 | rm -rf /tmp/awscliv2.zip /tmp/aws |
| 299 | fi |
| 300 | } |
| 301 | |
| 302 | install_gcloud() { |
| 303 | if require_cmd gcloud; then warn "Google Cloud CLI exists"; return; fi |
| 304 | log "Installing Google Cloud CLI..." |
| 305 | curl -fsSL https://sdk.cloud.google.com | $CURRENT_SHELL -s -- --disable-prompts |
| 306 | |
| 307 | if [[ "$CURRENT_SHELL" == "zsh" ]]; then |
| 308 | add_to_path_config "GCLOUD" '[ -f "$HOME/google-cloud-sdk/path.zsh.inc" ] && source "$HOME/google-cloud-sdk/path.zsh.inc"' |
| 309 | else |
| 310 | add_to_path_config "GCLOUD" '[ -f "$HOME/google-cloud-sdk/path.bash.inc" ] && source "$HOME/google-cloud-sdk/path.bash.inc"' |
| 311 | fi |
| 312 | } |
| 313 | |
| 314 | install_firebase() { |
| 315 | if require_cmd firebase; then warn "Firebase CLI exists"; return; fi |
| 316 | log "Installing Firebase CLI via NPM to ensure ARM64 compatibility..." |
| 317 | |
| 318 | if ! require_cmd npm; then |
| 319 | err "NPM not found. Please install NVM (Option 3) first." |
| 320 | return 1 |
| 321 | fi |
| 322 | |
| 323 | npm install -g firebase-tools |
| 324 | } |
| 325 | |
| 326 | # ======================================================= |
| 327 | # DEV TOOLS & SECURITY |
| 328 | # ======================================================= |
| 329 | |
| 330 | install_gh() { |
| 331 | if require_cmd gh; then warn "GitHub CLI exists"; return; fi |
| 332 | log "Fetching latest GitHub CLI version..." |
| 333 | LATEST_GH=$(curl -s https://api.github.com/repos/cli/cli/releases/latest | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/') |
| 334 | TAR_NAME="gh_${LATEST_GH}_${OS_LOWER}_${SYS_ARCH}" |
| 335 | |
| 336 | curl -fsSL -o /tmp/gh.tar.gz "https://github.com/cli/cli/releases/download/v${LATEST_GH}/${TAR_NAME}.tar.gz" |
| 337 | tar -xzf /tmp/gh.tar.gz -C /tmp |
| 338 | sudo mv "/tmp/${TAR_NAME}/bin/gh" /usr/local/bin/ |
| 339 | sudo rm -rf "/tmp/${TAR_NAME}" /tmp/gh.tar.gz |
| 340 | } |
| 341 | |
| 342 | install_infisical() { |
| 343 | if require_cmd infisical; then warn "Infisical CLI exists"; return; fi |
| 344 | log "Installing Infisical CLI via NPM..." |
| 345 | |
| 346 | if ! require_cmd npm; then |
| 347 | err "NPM not found. Please install NVM (Option 3) first." |
| 348 | return 1 |
| 349 | fi |
| 350 | |
| 351 | npm install -g @infisical/cli |
| 352 | } |
| 353 | |
| 354 | # ======================================================= |
| 355 | # AI & AGENTIC TOOLS |
| 356 | # ======================================================= |
| 357 | |
| 358 | install_claude() { log "Installing Claude Code..."; curl -fsSL https://claude.ai/install.sh | $CURRENT_SHELL; } |
| 359 | install_opencode() { log "Installing OpenCode..."; curl -fsSL https://opencode.ai/install | $CURRENT_SHELL; } |
| 360 | |
| 361 | install_codex() { |
| 362 | log "Installing @openai/codex..." |
| 363 | if ! require_cmd npm; then err "Node.js/NPM is required. Install NVM (3) first."; return 1; fi |
| 364 | npm i -g @openai/codex |
| 365 | } |
| 366 | |
| 367 | # ======================================================= |
| 368 | # MENU LOGIC |
| 369 | # ======================================================= |
| 370 | show_menu() { |
| 371 | clear |
| 372 | printf "${BLUE}==================================================${NC}\n" |
| 373 | printf "${GREEN} DEVELOPER ENVIRONMENT INSTALLER (Polyglot) ${NC}\n" |
| 374 | printf "${BLUE}==================================================${NC}\n" |
| 375 | printf "${YELLOW}--- Core Runtimes & Managers ---${NC}\n" |
| 376 | printf " 1) Build Tools (GCC/Make)\n" |
| 377 | printf " 2) Homebrew (Optional)\n" |
| 378 | printf " 3) NVM (Node.js)\n" |
| 379 | printf " 4) Python 3 (Official API)\n" |
| 380 | printf " 5) Go (Official -> /usr/local)\n" |
| 381 | printf " 6) SDKMAN (Java/Kotlin)\n" |
| 382 | printf " 7) .NET (Official Script)\n" |
| 383 | printf "${YELLOW}--- Cloud & Infrastructure ---${NC}\n" |
| 384 | printf " 8) Docker (Official DMG/sh)\n" |
| 385 | printf " 9) AWS CLI (Official Bin)\n" |
| 386 | printf " 10) Google Cloud CLI (gcloud)\n" |
| 387 | printf " 11) Firebase CLI (NPM/ARM64 Safe)\n" |
| 388 | printf "${YELLOW}--- Dev Tools & Security ---${NC}\n" |
| 389 | printf " 12) GitHub CLI (Official Bin)\n" |
| 390 | printf " 13) Infisical (Official Bin)\n" |
| 391 | printf "${YELLOW}--- AI & Agentic Tools ---${NC}\n" |
| 392 | printf " 14) Claude Code CLI\n" |
| 393 | printf " 15) OpenCode (opencode.ai)\n" |
| 394 | printf " 16) OpenAI Codex CLI\n" |
| 395 | printf "${BLUE}==================================================${NC}\n" |
| 396 | printf " 99) Quit\n" |
| 397 | printf "${BLUE}==================================================${NC}\n" |
| 398 | } |
| 399 | |
| 400 | while true; do |
| 401 | show_menu |
| 402 | |
| 403 | # Bulletproof prompt for both Bash and Zsh |
| 404 | printf "Select options (space-separated): " |
| 405 | read input |
| 406 | |
| 407 | # Thanks to 'setopt shwordsplit' in Zsh, this behaves perfectly across both shells |
| 408 | for choice in $input; do |
| 409 | case "$choice" in |
| 410 | 1) install_build_tools ;; |
| 411 | 2) install_brew ;; |
| 412 | 3) install_nvm ;; |
| 413 | 4) install_python ;; |
| 414 | 5) install_go ;; |
| 415 | 6) install_sdkman ;; |
| 416 | 7) install_dotnet ;; |
| 417 | 8) install_docker ;; |
| 418 | 9) install_aws ;; |
| 419 | 10) install_gcloud ;; |
| 420 | 11) install_firebase ;; |
| 421 | 12) install_gh ;; |
| 422 | 13) install_infisical ;; |
| 423 | 14) install_claude ;; |
| 424 | 15) install_opencode ;; |
| 425 | 16) install_codex ;; |
| 426 | 99) log "Exiting..."; exit 0 ;; |
| 427 | *) warn "Option $choice not valid." ;; |
| 428 | esac |
| 429 | done |
| 430 | |
| 431 | printf "Press Enter to continue..." |
| 432 | read dummy |
| 433 | done |