275 lines
8.4 KiB
Bash
Executable File
275 lines
8.4 KiB
Bash
Executable File
#!/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 <official-agy-install-url> | 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" <<EOF
|
|
# CLI install report (WSL)
|
|
Generated: $(date)
|
|
OS: $(lsb_release -d -s 2>/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" <<EOF
|
|
|
|
Antigravity (agy): Manual verification required. Official page: $ANTIGRAVITY_PAGE
|
|
|
|
PATH changes:
|
|
- If npm prefix was adjusted, ~/.npm-global/bin was added to ~/.profile
|
|
|
|
Notes:
|
|
- No auth performed by this script. Use each CLI's documented login command (do not put API keys in shell profiles).
|
|
- Backups saved to: $BACKUP_DIR
|
|
Log file: $LOG
|
|
EOF
|
|
|
|
echo
|
|
echo "DONE. Report: $REPORT"
|
|
echo "Tail of log:"
|
|
tail -n 50 "$LOG" || true
|
|
echo
|
|
echo "If any install failed, paste the log or report and I will help debug."
|
|
|
|
# End of script
|