#!/usr/bin/env bash # installagent — install CLI AI coding agents inside asandbox # Usage: installagent | installagent list | installagent status set -euo pipefail VERSION="1.1.0" if [[ -t 2 ]]; then C_RED=$'\033[31m'; C_GRN=$'\033[32m'; C_YLW=$'\033[33m' C_BLU=$'\033[34m'; C_DIM=$'\033[2m'; C_RST=$'\033[0m'; C_BLD=$'\033[1m' else C_RED=; C_GRN=; C_YLW=; C_BLU=; C_DIM=; C_RST=; C_BLD= fi die() { echo "${C_RED}installagent:${C_RST} $*" >&2; exit 1; } info() { echo "${C_BLU}installagent:${C_RST} $*" >&2; } ok() { echo "${C_GRN}installagent:${C_RST} $*" >&2; } warn() { echo "${C_YLW}installagent:${C_RST} $*" >&2; } ensure_local_path() { mkdir -p "${HOME}/.local/bin" "${HOME}/.local/lib" "${HOME}/.npm-global" # Prefer user-local npm so installs survive container recreate (via persist mount) if command -v npm >/dev/null 2>&1; then npm config set prefix "${HOME}/.npm-global" >/dev/null 2>&1 || true fi export PATH="${HOME}/.local/bin:${HOME}/.npm-global/bin:${PATH}" } npm_g() { ensure_local_path npm install -g --prefix "${HOME}/.npm-global" "$@" } have_cmd() { command -v "$1" >/dev/null 2>&1; } # ── catalog ───────────────────────────────────────────────────────── # id|aliases|binary|description catalog() { cat <<'EOF' antigravity|agy,antigravity-cli|agy|Google Antigravity CLI cursor|cursor-cli,cursor-agent|agent|Cursor Agent CLI opencode|open-code|opencode|OpenCode open-source coding agent claude|claude-code,claude-cli|claude|Anthropic Claude Code gemini|gemini-cli,google-gemini|gemini|Google Gemini CLI codex|openai-codex|codex|OpenAI Codex CLI aider|aider-chat|aider|Aider Python coding agent qwen|qwen-code,qwen-cli|qwen|Qwen Code CLI amp|sourcegraph-amp|amp|Sourcegraph Amp CLI goose|block-goose|goose|Block Goose agent continue|cn|cn|Continue CLI EOF } normalize_name() { echo "$1" | tr '[:upper:]' '[:lower:]' | tr '_' '-' } resolve_id() { local want want=$(normalize_name "$1") local line id aliases while IFS= read -r line; do [[ -z "$line" || "$line" == \#* ]] && continue id="${line%%|*}" aliases="${line#*|}" aliases="${aliases%%|*}" if [[ "$want" == "$id" ]]; then echo "$id" return 0 fi local a IFS=',' read -ra _als <<< "$aliases" for a in "${_als[@]}"; do a=$(echo "$a" | tr -d ' ') if [[ "$want" == "$a" ]]; then echo "$id" return 0 fi done done < <(catalog) return 1 } agent_binary() { local id="$1" line while IFS= read -r line; do [[ "${line%%|*}" == "$id" ]] || continue # id|aliases|binary|desc local rest="${line#*|}" rest="${rest#*|}" echo "${rest%%|*}" return 0 done < <(catalog) } agent_desc() { local id="$1" line while IFS= read -r line; do [[ "${line%%|*}" == "$id" ]] || continue echo "${line##*|}" return 0 done < <(catalog) } # ── installers ────────────────────────────────────────────────────── install_antigravity() { info "installing Antigravity CLI (agy)..." ensure_local_path curl -fsSL https://antigravity.google/cli/install.sh | bash # Some installers put binary only in a custom dir — ensure on PATH if ! have_cmd agy; then # common locations for p in \ "${HOME}/.local/bin/agy" \ "${HOME}/.antigravity/bin/agy" \ "${HOME}/.agy/bin/agy" \ /usr/local/bin/agy; do if [[ -x "$p" ]]; then mkdir -p "${HOME}/.local/bin" ln -sfn "$p" "${HOME}/.local/bin/agy" break fi done fi have_cmd agy || die "agy not found after install — check installer output" ok "Antigravity CLI ready: $(command -v agy)" agy --version 2>/dev/null || agy version 2>/dev/null || true } install_cursor() { info "installing Cursor Agent CLI..." ensure_local_path curl -fsSL https://cursor.com/install | bash if ! have_cmd agent; then for p in \ "${HOME}/.local/bin/agent" \ "${HOME}/.cursor/bin/agent" \ "${HOME}/.cursor-agent/bin/agent"; do if [[ -x "$p" ]]; then mkdir -p "${HOME}/.local/bin" ln -sfn "$p" "${HOME}/.local/bin/agent" break fi done fi have_cmd agent || die "agent not found after install" ok "Cursor CLI ready: $(command -v agent)" agent --version 2>/dev/null || true } install_opencode() { info "installing OpenCode..." ensure_local_path if have_cmd npm; then npm_g opencode-ai else curl -fsSL https://opencode.ai/install | bash fi if ! have_cmd opencode; then for p in "${HOME}/.npm-global/bin/opencode" "${HOME}/.local/bin/opencode" /usr/local/bin/opencode; do [[ -x "$p" ]] && ln -sfn "$p" "${HOME}/.local/bin/opencode" && break done fi have_cmd opencode || die "opencode not found after install" ok "OpenCode ready: $(command -v opencode)" opencode --version 2>/dev/null || true } install_claude() { info "installing Claude Code..." have_cmd npm || die "npm required (Node.js)" npm_g @anthropic-ai/claude-code have_cmd claude || die "claude not found after install" ok "Claude Code ready: $(command -v claude)" claude --version 2>/dev/null || true } install_gemini() { info "installing Gemini CLI..." have_cmd npm || die "npm required (Node.js)" npm_g @google/gemini-cli have_cmd gemini || die "gemini not found after install" ok "Gemini CLI ready: $(command -v gemini)" gemini --version 2>/dev/null || true } install_codex() { info "installing OpenAI Codex CLI..." have_cmd npm || die "npm required (Node.js)" npm_g @openai/codex have_cmd codex || die "codex not found after install" ok "Codex ready: $(command -v codex)" codex --version 2>/dev/null || true } install_aider() { info "installing Aider..." ensure_local_path if have_cmd uv; then uv tool install aider-chat elif have_cmd pip3; then pip3 install --user --break-system-packages aider-chat \ || pip3 install --user aider-chat else die "need uv or pip3 to install aider" fi # uv tools go to ~/.local/bin have_cmd aider || die "aider not found after install — ensure ~/.local/bin is on PATH" ok "Aider ready: $(command -v aider)" aider --version 2>/dev/null || true } install_qwen() { info "installing Qwen Code..." have_cmd npm || die "npm required (Node.js)" npm_g @qwen-code/qwen-code # binary name may be qwen or qwen-code if ! have_cmd qwen && have_cmd qwen-code; then ln -sfn "$(command -v qwen-code)" "${HOME}/.local/bin/qwen" fi if have_cmd qwen || have_cmd qwen-code; then ok "Qwen Code ready: $(command -v qwen 2>/dev/null || command -v qwen-code)" else die "qwen binary not found after install" fi } install_amp() { info "installing Amp (Sourcegraph)..." ensure_local_path # Official installer when available if curl -fsSL https://ampcode.com/install.sh 2>/dev/null | bash; then : elif have_cmd npm; then npm_g @sourcegraph/amp 2>/dev/null || npm_g amp 2>/dev/null || true fi have_cmd amp || warn "amp may need manual install — see https://ampcode.com" have_cmd amp && ok "Amp ready: $(command -v amp)" } install_goose() { info "installing Goose..." ensure_local_path curl -fsSL https://github.com/block/goose/releases/download/stable/download_cli.sh 2>/dev/null | bash \ || curl -fsSL https://raw.githubusercontent.com/block/goose/main/download_cli.sh 2>/dev/null | bash \ || true if ! have_cmd goose; then # fallback cargo if present if have_cmd cargo; then cargo install goose-cli 2>/dev/null || true fi fi have_cmd goose || die "goose not found after install — see https://github.com/block/goose" ok "Goose ready: $(command -v goose)" } install_continue() { info "installing Continue CLI..." ensure_local_path have_cmd npm || die "npm required" npm_g @continuedev/cli 2>/dev/null || npm_g continue-cli 2>/dev/null || true if have_cmd cn || have_cmd continue; then ok "Continue ready: $(command -v cn 2>/dev/null || command -v continue)" else warn "Continue CLI package name may have changed — try: npm install -g @continuedev/cli" fi } do_install() { local id="$1" case "$id" in antigravity) install_antigravity ;; cursor) install_cursor ;; opencode) install_opencode ;; claude) install_claude ;; gemini) install_gemini ;; codex) install_codex ;; aider) install_aider ;; qwen) install_qwen ;; amp) install_amp ;; goose) install_goose ;; continue) install_continue ;; *) die "no installer for: $id" ;; esac } cmd_list() { echo "${C_BLD}Available agents${C_RST} (installagent )" echo printf " ${C_BLD}%-14s${C_RST} %-12s %s\n" "NAME" "BINARY" "DESCRIPTION" printf " %-14s %-12s %s\n" "----" "------" "-----------" local line id aliases bin desc while IFS= read -r line; do id="${line%%|*}" rest="${line#*|}" aliases="${rest%%|*}" rest="${rest#*|}" bin="${rest%%|*}" desc="${rest#*|}" local mark=" " if have_cmd "$bin"; then mark="${C_GRN}✓${C_RST}" else mark="${C_DIM}·${C_RST}" fi printf " %s %-12s %-12s %s\n" "$mark" "$id" "$bin" "$desc" if [[ -n "$aliases" ]]; then printf " ${C_DIM}aliases: %s${C_RST}\n" "$aliases" fi done < <(catalog) echo echo "Examples:" echo " installagent claude" echo " installagent antigravity" echo " installagent cursor" echo " installagent opencode" echo " installagent all # install core set" } cmd_status() { ensure_local_path echo "${C_BLD}Installed agents${C_RST}" local line id bin while IFS= read -r line; do id="${line%%|*}" rest="${line#*|}" rest="${rest#*|}" bin="${rest%%|*}" if have_cmd "$bin"; then printf " ${C_GRN}✓${C_RST} %-14s %s\n" "$id" "$(command -v "$bin")" else printf " ${C_DIM}·${C_RST} %-14s (not installed)\n" "$id" fi done < <(catalog) echo echo "PATH includes:" echo " ${HOME}/.local/bin" echo " ${HOME}/.npm-global/bin" } cmd_all() { # Core set commonly used local core=(claude gemini codex opencode cursor antigravity aider) local name for name in "${core[@]}"; do info "── $name ──" if do_install "$name"; then ok "$name done" else warn "$name failed (continuing)" fi done cmd_status } usage() { cat < Install agent by name or alias installagent list Show catalog installagent status Show what is installed installagent all Install core agents installagent help This help AGENTS antigravity (agy) Google Antigravity CLI cursor (agent) Cursor Agent CLI opencode OpenCode claude Claude Code gemini Gemini CLI codex OpenAI Codex aider Aider qwen Qwen Code amp Sourcegraph Amp goose Block Goose continue Continue CLI Installs go to ~/.local and ~/.npm-global (persisted by asandbox). EOF } main() { ensure_local_path local cmd="${1:-list}" shift || true case "$cmd" in list|ls|-l) cmd_list ;; status|st) cmd_status ;; all) cmd_all ;; help|-h|--help) usage ;; version|-V|--version) echo "installagent $VERSION" ;; *) local id if ! id=$(resolve_id "$cmd"); then die "unknown agent: $cmd (try: installagent list)" fi info "resolved '$cmd' → $id ($(agent_desc "$id"))" do_install "$id" echo ok "run: $(agent_binary "$id")" ;; esac } main "$@"