#!/usr/bin/env bash set -euo pipefail IFS=$'\n\t' LOG="$HOME/cli-install-wsl.log" REPORT="$HOME/cli-install-report.md" BACKUP_DIR="$HOME/cli-install-backups-wsl-$(date +%Y%m%d-%H%M%S)" mkdir -p "$BACKUP_DIR" exec > >(tee -a "$LOG") 2>&1 echo "=== CLI install script (interactive) ===" echo "Log: $LOG" echo # Helpers prompt_yesno() { local msg="$1" local ans while true; do read -rp "$msg [y/n]: " ans case "$ans" in [Yy]*) return 0 ;; [Nn]*) return 1 ;; *) echo "Please answer y or n." ;; esac done } preview_url() { local url="$1" echo echo "Previewing first 200 lines of $url" echo "---- BEGIN PREVIEW ----" curl -fsSL "$url" 2>/dev/null | sed -n '1,200p' echo "---- END PREVIEW ----" echo } safe_run_pipe_installer() { # $1 = url, $2 = shell (bash|sh) local url="$1"; local shellcmd="${2:-bash}" echo "About to run installer from: $url" if ! prompt_yesno "Have you reviewed the preview and want to execute it now?"; then echo "Skipping $url" return 1 fi # Execute installer (interactive; sudo prompts will show) curl -fsSL "$url" | $shellcmd } # 0) WSL check if grep -qi microsoft /proc/version 2>/dev/null || grep -qi microsoft /proc/sys/kernel/osrelease 2>/dev/null; then echo "Running inside WSL (or WSL2). Proceeding." else echo "Warning: This script is intended for WSL/Ubuntu. Continue only if you know what you are doing." if ! prompt_yesno "Continue anyway?"; then echo "Aborting." exit 1 fi fi # 1) Detect environment echo "== Environment ==" uname -a if command -v lsb_release >/dev/null 2>&1; then lsb_release -a || true; else cat /etc/os-release || true; fi echo "Shell: $SHELL" echo "User: $USER" echo "Home: $HOME" echo "PATH: $PATH" echo "Arch: $(uname -m)" echo # 2) Back up shell configs echo "Backing up shell configs to $BACKUP_DIR" for f in "$HOME/.bashrc" "$HOME/.profile" "$HOME/.zshrc"; do if [ -f "$f" ]; then cp -a "$f" "$BACKUP_DIR/$(basename "$f").bak" || true echo "Backed up $f" else echo "No file: $f" fi done echo # 3) Install prerequisites if prompt_yesno "Install apt prerequisites (curl ca-certificates git unzip gnupg lsb-release build-essential)?"; then sudo apt-get update sudo apt-get install -y curl ca-certificates git unzip gnupg lsb-release build-essential apt-transport-https else echo "Skipping prerequisites install. Ensure required tools exist." fi echo # 4) Node check and optional install (Node 20 recommended) NEED_NODE_INSTALL=0 if command -v node >/dev/null 2>&1; then NV="$(node -v 2>/dev/null || true)" echo "Found node: $NV" MAJ="$(echo "$NV" | sed -E 's/v([0-9]+).*/\1/')" || MAJ=0 if [ -z "$MAJ" ] || [ "$MAJ" -lt 18 ]; then NEED_NODE_INSTALL=1 echo "Node major version $MAJ is older than 18; Node 20 is recommended." fi else NEED_NODE_INSTALL=1 echo "Node not found." fi if [ "$NEED_NODE_INSTALL" -eq 1 ]; then if prompt_yesno "Install Node 20 via NodeSource (recommended) now?"; then curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - sudo apt-get install -y nodejs else echo "Skipping Node install. Codex npm installer may require Node >=18." fi fi echo "Node version: $(node -v 2>/dev/null || echo 'none')" echo "npm version: $(npm -v 2>/dev/null || echo 'none')" echo # 5) Ensure npm global prefix writable (user-level) CUR_PREFIX="$(npm config get prefix 2>/dev/null || echo '')" echo "Current npm prefix: $CUR_PREFIX" if [ -z "$CUR_PREFIX" ] || [ ! -w "$CUR_PREFIX" ] || [ "$CUR_PREFIX" = "/usr" ]; then echo "Configuring user npm global prefix at ~/.npm-global" mkdir -p "$HOME/.npm-global" npm config set prefix "$HOME/.npm-global" if ! grep -q '\.npm-global' "$HOME/.profile" 2>/dev/null; then printf '\n# npm global bin\nexport PATH="$HOME/.npm-global/bin:$HOME/.local/bin:$PATH"\n' >> "$HOME/.profile" echo "Appended npm PATH to ~/.profile" fi export PATH="$HOME/.npm-global/bin:$HOME/.local/bin:$PATH" fi echo "Effective PATH now contains: $HOME/.npm-global/bin (if present)" echo # 6) Claude Code CLI (official) CLAUDE_URL="https://claude.ai/install.sh" echo "Claude Code installer (official): $CLAUDE_URL" preview_url "$CLAUDE_URL" if prompt_yesno "Install Claude Code CLI now?"; then safe_run_pipe_installer "$CLAUDE_URL" bash || echo "Claude installer failed or was skipped." else echo "Skipped Claude install." fi echo # 7) OpenAI Codex CLI (official) echo "OpenAI Codex CLI preferred method: npm i -g @openai/codex" if prompt_yesno "Attempt npm global install @openai/codex now?"; then if npm i -g @openai/codex --no-progress; then echo "Codex npm install succeeded." else echo "npm global install failed. You can choose to run the official installer fallback." if prompt_yesno "Attempt official fallback installer (curl https://chatgpt.com/codex/install.sh | sh)?"; then preview_url "https://chatgpt.com/codex/install.sh" safe_run_pipe_installer "https://chatgpt.com/codex/install.sh" sh || echo "Codex fallback failed or skipped." fi fi else echo "Skipped Codex npm install." fi echo # 8) Devin CLI (official) DEVIN_URL="https://cli.devin.ai/install.sh" echo "Devin CLI official installer: $DEVIN_URL" preview_url "$DEVIN_URL" if prompt_yesno "Install Devin CLI now?"; then safe_run_pipe_installer "$DEVIN_URL" bash || echo "Devin installer failed or was skipped." else echo "Skipped Devin install." fi echo # 9) Antigravity (agy) — manual only ANTIGRAVITY_PAGE="https://antigravity.google/product/antigravity-cli" echo "Antigravity CLI official docs page: $ANTIGRAVITY_PAGE" echo "This script will not auto-run installers from Antigravity. Review the official page and follow their instructions." if prompt_yesno "Fetch and preview the Antigravity docs page snippet?"; then echo "Previewing page snippet (first 300 lines):" curl -fsSL "$ANTIGRAVITY_PAGE" | sed -n '1,300p' || echo "Failed to fetch Antigravity page." echo echo "If the page specifies an official installer URL you trust, run it manually (example placeholder):" echo " curl -fsSL | bash" fi echo # 10) Reload profile and PATH echo "Reloading ~/.profile (if present) to pick PATH changes" if [ -f "$HOME/.profile" ]; then # shellcheck disable=SC1090 source "$HOME/.profile" || true fi export PATH="$HOME/.npm-global/bin:$HOME/.local/bin:$PATH" echo "PATH now: $PATH" echo # 11) Verification echo "=== Verification ===" declare -A RESULTS for cmd in claude codex agy devin; do echo "---- $cmd ----" if command -v "$cmd" >/dev/null 2>&1; then p="$(command -v "$cmd")" out="$($cmd --version 2>&1 || $cmd --help 2>&1 || echo 'no-version-output')" echo "Found: $p" echo "Output: $out" RESULTS["$cmd"]="installed|$out|$p" else echo "Not found in PATH." RESULTS["$cmd"]="not-installed||" fi echo done # Also verify in a clean non-login shell echo "=== Clean shell checks ===" bash -lc 'command -v claude && claude --version 2>&1 || echo "claude: not-found"' bash -lc 'command -v codex && codex --version 2>&1 || echo "codex: not-found"' bash -lc 'command -v devin && (devin --version 2>&1 || devin --help 2>&1) || echo "devin: not-found"' bash -lc 'command -v agy && agy --version 2>&1 || echo "agy: not-found"' # 12) Create report echo "Writing report to $REPORT" cat > "$REPORT" </dev/null || grep PRETTY_NAME /etc/os-release | cut -d= -f2-) User: $USER Home: $HOME Install summary: EOF for key in claude codex devin agy; do val="${RESULTS[$key]:-not-installed||}" IFS='|' read -r inst ver path <<< "$val" if [ "$inst" = "installed" ] || [ "$inst" = "installed_npm" ] || [ "$inst" = "installed_fallback" ]; then status="yes" elif [ "$inst" = "not-installed" ]; then status="no" else status="$inst" fi { echo echo "## $key" echo "Installed?: $status" echo "Path: ${path:-}" echo "Version/Output: ${ver:-}" } >> "$REPORT" done cat >> "$REPORT" <