#!/data/data/com.termux/files/usr/bin/bash # claude-update: Actualizador seguro de Claude Code en Termux # Respeta techo v2.1.112 (v2.1.113+ usa binarios nativos incompatibles con Bionic) # Alternativa para versiones nuevas: github.com/ferrumclaudepilgrim/claude-code-android set -euo pipefail readonly MAX_VERSION="2.1.112" readonly PKG="@anthropic-ai/claude-code" readonly CLAUDE_DIR="$PREFIX/lib/node_modules/${PKG}" readonly BACKUP_ROOT="$HOME/.cache/claude-updates" readonly LOG="$BACKUP_ROOT/history.log" readonly SETTINGS="$HOME/.claude/settings.json" readonly R="\033[0;31m" G="\033[0;32m" Y="\033[1;33m" C="\033[0;36m" N="\033[0m" CHECK_ONLY=0; DRY_RUN=0; FORCE=0; ROLLBACK=0; TARGET="" usage() { cat <> "$LOG"; } die() { echo -e "${R}ERROR:${N} $*" >&2; log "ERROR: $*"; exit 1; } info() { echo -e "${C}==>${N} $*"; } warn() { echo -e "${Y}!${N} $*"; } ok() { echo -e "${G}[OK]${N} $*"; } preflight() { [[ -n "${PREFIX:-}" && -d "$PREFIX" ]] || die "\$PREFIX inválido: no parece Termux" [[ "$(uname -m)" == "aarch64" ]] || die "Solo aarch64 soportado (uname -m: $(uname -m))" command -v npm &>/dev/null || die "npm no encontrado (pkg install nodejs)" npm view "$PKG" version &>/dev/null || die "sin acceso a npm registry (¿sin red?)" if [[ -f "$SETTINGS" ]] && ! grep -q '"DISABLE_AUTOUPDATER"' "$SETTINGS"; then warn "settings.json sin DISABLE_AUTOUPDATER=1" fi mkdir -p "$BACKUP_ROOT" } version_gt() { [[ "$1" != "$2" && "$(printf '%s\n%s' "$1" "$2" | sort -V | tail -1)" == "$1" ]]; } get_current() { claude --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || true; } verify_node_compat() { local ver="$1" files info "Verificando v$ver es Node.js puro (busca cli.js, rechaza cli-wrapper.cjs)..." files=$(npm pack "$PKG@$ver" --dry-run 2>&1 || true) echo "$files" | grep -q 'cli-wrapper.cjs' && die "v$ver es binario nativo → incompatible" echo "$files" | grep -q 'cli.js' || warn "v$ver: no se detectó cli.js explícito" } do_backup() { [[ -d "$CLAUDE_DIR" ]] || return 0 local dest="$BACKUP_ROOT/backup-$(date +%s)-v$(get_current)" info "Backup: $CLAUDE_DIR → $dest" cp -a "$CLAUDE_DIR" "$dest" log "backup $dest" ls -1dt "$BACKUP_ROOT"/backup-* 2>/dev/null | tail -n +4 | xargs -r rm -rf } do_rollback() { local last last=$(ls -1dt "$BACKUP_ROOT"/backup-* 2>/dev/null | head -1) [[ -n "$last" ]] || die "sin backups en $BACKUP_ROOT" info "Rollback: $last → $CLAUDE_DIR" [[ -d "$CLAUDE_DIR" ]] && { chmod -R u+w "$CLAUDE_DIR"; rm -rf "$CLAUDE_DIR"; } cp -a "$last" "$CLAUDE_DIR" chmod -R a-w "$CLAUDE_DIR" ok "Restaurado a v$(get_current)"; log "rollback from $last"; exit 0 } do_install() { local ver="$1" (( DRY_RUN )) && { info "[DRY-RUN] npm install -g $PKG@$ver (con backup + protección)"; exit 0; } do_backup info "Desprotegiendo (chmod u+w)..." [[ -d "$CLAUDE_DIR" ]] && chmod -R u+w "$CLAUDE_DIR" info "Instalando v$ver..." npm install -g "$PKG@$ver" info "Reprotegiendo (chmod a-w) contra auto-updater..." chmod -R a-w "$CLAUDE_DIR" local new; new=$(get_current) [[ "$new" == "$ver" ]] || die "instalación inconsistente: esperaba $ver, obtuvo $new" ok "Claude Code v$new instalado y protegido"; log "installed v$ver" } while [[ $# -gt 0 ]]; do case "$1" in --check) CHECK_ONLY=1 ;; --dry-run) DRY_RUN=1 ;; --force) FORCE=1 ;; --rollback) ROLLBACK=1 ;; --version) shift; TARGET="${1:-}" ;; -h|--help) usage ;; *) die "opción desconocida: $1 (--help para ayuda)" ;; esac; shift done preflight (( ROLLBACK )) && do_rollback CURRENT=$(get_current) LATEST=$(npm view "$PKG" version 2>/dev/null) echo -e "${C}Actual:${N} v${CURRENT:-}" echo -e "${C}Última:${N} v${LATEST}" echo -e "${C}Techo:${N} v${MAX_VERSION}" if [[ -z "$TARGET" ]]; then if version_gt "$LATEST" "$MAX_VERSION"; then TARGET="$MAX_VERSION" warn "Última v$LATEST > techo → objetivo v$MAX_VERSION" else TARGET="$LATEST" fi elif version_gt "$TARGET" "$MAX_VERSION"; then die "v$TARGET excede techo v$MAX_VERSION (ver --help para Path A)" fi echo -e "${C}Objetivo:${N} v${TARGET}" (( CHECK_ONLY )) && exit 0 if [[ "$CURRENT" == "$TARGET" ]] && (( ! FORCE )); then ok "Ya en v$TARGET (usar --force para reinstalar)"; exit 0 fi verify_node_compat "$TARGET" do_install "$TARGET"