#!/bin/bash # Nobox Agents · instalador # # curl -fsSL https://agentes.nobox.tech/instalar | bash # # Instala tudo na pasta da pessoa, sem senha de administrador: # - um Node só para o Nobox (não mexe no resto do Mac); # - o motor do agente, com o Claude Code oficial da Anthropic; # - as pastas em ~/Nobox Agents; # - o comando "nobox" e o serviço que liga junto com o Mac; # e abre o instalador no navegador. # # Opções: --atualizar (troca a versão e religa o serviço, sem abrir o navegador) # --sem-servico --sem-abrir (usadas nos testes) # # Tudo fica dentro de funções e só roda na última linha: assim, com "curl | bash", # nenhum comando do meio consegue ler o resto do script pela entrada padrão. set -euo pipefail principal() { local BASE="${NOBOX_URL:-https://agentes.nobox.tech}" local CASA="${NOBOX_HOME:-$HOME/Nobox Agents}" local PORTA="${NOBOX_PORTA:-4870}" local PASTA_BIN="${NOBOX_BIN_DIR:-$HOME/.local/bin}" local NODE_VERSAO="24.13.1" local ATUALIZAR=0 SEM_SERVICO=0 SEM_ABRIR=0 for a in "$@"; do case "$a" in --atualizar) ATUALIZAR=1; SEM_ABRIR=1 ;; --sem-servico) SEM_SERVICO=1 ;; --sem-abrir) SEM_ABRIR=1 ;; esac done if [ -t 1 ]; then VERDE=$'\033[32m'; AZUL=$'\033[36m'; CINZA=$'\033[2m'; NEGRITO=$'\033[1m'; VERMELHO=$'\033[31m'; FIM=$'\033[0m' else VERDE=""; AZUL=""; CINZA=""; NEGRITO=""; VERMELHO=""; FIM="" fi printf "\n %s◆%s %sNobox Agents%s %s· instalador%s\n %sSeu primeiro agente, rodando no seu Mac.%s\n\n" \ "$AZUL" "$FIM" "$NEGRITO" "$FIM" "$CINZA" "$FIM" "$CINZA" "$FIM" TEMP="$(mktemp -d)" trap 'rm -rf "$TEMP"' EXIT # ── 1. Mac compatível ────────────────────────────────────────────────────── [ "$(uname -s)" = "Darwin" ] || falha "Este instalador é para Mac." local ARQ NARQ CHIP MAC MAIOR LIVRE ARQ="$(uname -m)" case "$ARQ" in arm64) NARQ="arm64"; CHIP="Apple Silicon" ;; x86_64) NARQ="x64"; CHIP="Intel" ;; *) falha "Processador não suportado ($ARQ)." ;; esac MAC="$(sw_vers -productVersion)" MAIOR="${MAC%%.*}" [ "$MAIOR" -ge 13 ] || falha "Precisa do macOS 13 ou mais novo. Este Mac está no $MAC." LIVRE=$(( $(df -k "$HOME" | awk 'NR==2 {print $4}') / 1024 / 1024 )) [ "$LIVRE" -ge 2 ] || falha "Precisa de pelo menos 2 GB livres. Há $LIVRE GB." ok "Mac compatível" "macOS $MAC · $CHIP · $LIVRE GB livres" mkdir -p "$CASA"/{agentes,skills,navegador,config,dados,logs,.runtime,.motor} # ── 2. Node só para o Nobox ──────────────────────────────────────────────── local PASTA_NODE="$CASA/.runtime/node-v$NODE_VERSAO" if [ ! -x "$PASTA_NODE/bin/node" ]; then local NOME_NODE="node-v$NODE_VERSAO-darwin-$NARQ" esperar "Baixando o Node" baixar "https://nodejs.org/dist/v$NODE_VERSAO/SHASUMS256.txt" "$TEMP/somas" \ || falha "Não consegui baixar o Node. Confira a internet e tente de novo." esperar "Baixando o Node" baixar "https://nodejs.org/dist/v$NODE_VERSAO/$NOME_NODE.tar.gz" "$TEMP/node.tgz" \ || falha "Não consegui baixar o Node. Confira a internet e tente de novo." local ESPERADO ESPERADO="$(grep " $NOME_NODE.tar.gz\$" "$TEMP/somas" | awk '{print $1}')" [ -n "$ESPERADO" ] && [ "$(shasum -a 256 "$TEMP/node.tgz" | awk '{print $1}')" = "$ESPERADO" ] \ || falha "O Node veio corrompido no download. Tente de novo." mkdir -p "$PASTA_NODE" tar -xzf "$TEMP/node.tgz" -C "$PASTA_NODE" --strip-components 1 fi ln -sfn "$PASTA_NODE" "$CASA/.runtime/node" local d for d in "$CASA/.runtime"/node-v*; do if [ -d "$d" ] && [ ! -L "$d" ] && [ "$d" != "$PASTA_NODE" ]; then rm -rf "$d"; fi done local NODE="$CASA/.runtime/node/bin/node" local NPM="$CASA/.runtime/node/bin/npm" ok "Node" "v$NODE_VERSAO, só para o Nobox" # ── 3. Motor do agente ───────────────────────────────────────────────────── baixar "$BASE/pacotes/ultima.json" "$TEMP/ultima.json" || falha "Não consegui falar com $BASE." local VERSAO ARQUIVO SHA VERSAO="$(lerJson "$NODE" "$TEMP/ultima.json" versao)" ARQUIVO="$(lerJson "$NODE" "$TEMP/ultima.json" arquivo)" SHA="$(lerJson "$NODE" "$TEMP/ultima.json" sha256)" [[ "$VERSAO" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || falha "Versão inválida no servidor." local DESTINO="$CASA/.motor/$VERSAO" if [ ! -f "$DESTINO/.instalado" ]; then esperar "Baixando o motor" baixar "$BASE/pacotes/$ARQUIVO" "$TEMP/motor.tgz" || falha "Não consegui baixar o motor." [ "$(shasum -a 256 "$TEMP/motor.tgz" | awk '{print $1}')" = "$SHA" ] || falha "O motor veio corrompido no download. Tente de novo." rm -rf "$DESTINO" mkdir -p "$DESTINO" tar -xzf "$TEMP/motor.tgz" -C "$DESTINO" esperar "Instalando o motor e o Claude Code oficial" instalarDependencias "$DESTINO" "$NPM" "$CASA" \ || falha "Não consegui instalar as peças do motor. Detalhes em $CASA/logs/instalacao.log" touch "$DESTINO/.instalado" fi local ANTERIOR="" [ -L "$CASA/.motor/atual" ] && ANTERIOR="$(readlink "$CASA/.motor/atual")" ln -sfn "$DESTINO" "$CASA/.motor/atual" # Guarda só a versão nova e a anterior (cada uma ocupa umas centenas de MB). for d in "$CASA/.motor"/*; do [ -d "$d" ] && [ ! -L "$d" ] || continue [ "$d" = "$DESTINO" ] || [ "$d" = "$ANTERIOR" ] || rm -rf "$d" done ok "Motor do agente" "v$VERSAO" local CLAUDE_BIN="$DESTINO/node_modules/@anthropic-ai/claude-agent-sdk-darwin-$NARQ/claude" local CLAUDE_VERSAO CLAUDE_VERSAO="$("$CLAUDE_BIN" --version /dev/null | awk '{print $1}')" || CLAUDE_VERSAO="" [ -n "$CLAUDE_VERSAO" ] || falha "O Claude Code não ficou pronto. Detalhes em $CASA/logs/instalacao.log" ok "Claude Code oficial" "$CLAUDE_VERSAO, da Anthropic" # ── 4. Pastas ────────────────────────────────────────────────────────────── ok "Pastas do agente" "$CASA" printf " %s├─%s %s%-11s%s %scada agente mora aqui%s\n" "$CINZA" "$FIM" "$AZUL" "agentes/" "$FIM" "$CINZA" "$FIM" printf " %s├─%s %s%-11s%s %so que eles sabem fazer%s\n" "$CINZA" "$FIM" "$AZUL" "skills/" "$FIM" "$CINZA" "$FIM" printf " %s├─%s %s%-11s%s %so navegador dos agentes%s\n" "$CINZA" "$FIM" "$AZUL" "navegador/" "$FIM" "$CINZA" "$FIM" printf " %s├─%s %s%-11s%s %sajustes, sem senhas%s\n" "$CINZA" "$FIM" "$AZUL" "config/" "$FIM" "$CINZA" "$FIM" printf " %s├─%s %s%-11s%s %smemória e conversas%s\n" "$CINZA" "$FIM" "$AZUL" "dados/" "$FIM" "$CINZA" "$FIM" printf " %s└─%s %s%-11s%s %shistórico para o diagnóstico%s\n" "$CINZA" "$FIM" "$AZUL" "logs/" "$FIM" "$CINZA" "$FIM" # ── 5. Comando nobox ─────────────────────────────────────────────────────── mkdir -p "$PASTA_BIN" { printf '#!/bin/bash\n' printf 'export NOBOX_HOME=%q\n' "$CASA" [ -z "${NOBOX_PORTA:-}" ] || printf 'export NOBOX_PORTA=%q\n' "$PORTA" [ -z "${NOBOX_ROTULO:-}" ] || printf 'export NOBOX_ROTULO=%q\n' "$NOBOX_ROTULO" printf 'exec %q --no-deprecation %q "$@"\n' "$NODE" "$CASA/.motor/atual/dist/cli.js" } > "$PASTA_BIN/nobox" chmod +x "$PASTA_BIN/nobox" if [ -z "${NOBOX_BIN_DIR:-}" ]; then local PERFIL="$HOME/.zprofile" grep -qs '.local/bin' "$PERFIL" || printf '\n# Nobox Agents\nexport PATH="$HOME/.local/bin:$PATH"\n' >> "$PERFIL" fi ok "Comando nobox" "nobox abrir · nobox status · nobox doctor" # ── 6. Serviço do Mac ────────────────────────────────────────────────────── if [ "$SEM_SERVICO" = 0 ]; then NOBOX_HOME="$CASA" NOBOX_PORTA="$PORTA" "$NODE" --no-deprecation "$CASA/.motor/atual/dist/cli.js" servico instalar >"$CASA/logs/instalacao.log" 2>&1 \ || falha "Não consegui ligar o serviço do agente. Detalhes em $CASA/logs/instalacao.log" local i for i in $(seq 1 60); do curl -fsS "http://127.0.0.1:$PORTA/api/saude" >/dev/null 2>&1 && break sleep 0.5 done curl -fsS "http://127.0.0.1:$PORTA/api/saude" >/dev/null 2>&1 \ || falha "O serviço ligou, mas o painel não respondeu. Detalhes em $CASA/logs/servico.log" ok "Serviço do agente" "liga sozinho junto com o Mac" fi # ── 7. Pronto ────────────────────────────────────────────────────────────── if [ "$ATUALIZAR" = 1 ]; then printf "\n %s%sAtualizado para a v%s.%s\n\n" "$VERDE" "$NEGRITO" "$VERSAO" "$FIM" return 0 fi printf "\n %s%sTudo instalado.%s\n" "$VERDE" "$NEGRITO" "$FIM" printf " %s→%s Abrindo o instalador no seu navegador…\n" "$AZUL" "$FIM" printf " %shttp://localhost:%s%s\n" "$NEGRITO" "$PORTA" "$FIM" printf " %sSe não abrir sozinho, copie o endereço acima.%s\n" "$CINZA" "$FIM" printf " %sPara voltar depois, abra uma janela nova do Terminal e digite: nobox abrir%s\n\n" "$CINZA" "$FIM" if [ "$SEM_ABRIR" = 0 ]; then open "http://localhost:$PORTA/instalar" || true; fi } ok() { printf " %s✓%s %-24s %s%s%s\n" "$VERDE" "$FIM" "$1" "$CINZA" "${2:-}" "$FIM"; } falha() { printf "\n %s✗ %s%s\n\n" "$VERMELHO" "$1" "$FIM" >&2 exit 1 } baixar() { curl -fsSL --retry 2 --connect-timeout 20 "$1" -o "$2" >"$casa/logs/instalacao.log" 2>&1 } # Mostra um indicador girando enquanto um comando roda; devolve o código do comando. esperar() { local rotulo="$1"; shift "$@" & local pid=$! i=0 local quadros=(⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏) if [ -t 1 ]; then while kill -0 "$pid" 2>/dev/null; do printf " %s%s%s %s…\r" "$AZUL" "${quadros[i++ % 10]}" "$FIM" "$rotulo" sleep 0.1 done printf "\r\033[K" fi wait "$pid" } principal "$@"