#!/bin/bash # VantaBrain installer # curl -fsSL https://vb.darkcode.ai | bash set -u VB_VERSION="3.1.2" REPO="vantagrid/vantabrain" BRAIN_HOME="${BRAIN_HOME:-$HOME/VantaBrain}" # -- styling (matches TUI glyphs + colors) -------------------------------- CYAN='\033[38;2;0;180;220m' GREEN='\033[38;2;0;200;100m' RED='\033[38;2;220;50;30m' DIM='\033[2m' BOLD='\033[1m' WHITE='\033[38;2;255;255;255m' NC='\033[0m' # Spinner frames — forward + reverse for smooth cycle (matches TUI ThinkSpinner) SPIN_FRAMES=('·' '✢' '✳' '✶' '✻' '✽' '✻' '✶' '✳' '✢') SPIN_PID="" # Start background spinner on current line spin_start() { local msg="$1" ( local i=0 while true; do local ch="${SPIN_FRAMES[$((i % ${#SPIN_FRAMES[@]}))]}" printf "\r\033[2K ${CYAN}%s${NC} %b" "$ch" "$msg" >&2 i=$((i + 1)) sleep 0.12 done ) & SPIN_PID=$! } # Stop spinner, print final result on same line spin_ok() { [ -n "$SPIN_PID" ] && kill "$SPIN_PID" 2>/dev/null && wait "$SPIN_PID" 2>/dev/null SPIN_PID="" printf "\r\033[2K ${GREEN}✓${NC} %b\n" "$*" >&2 } spin_fail() { [ -n "$SPIN_PID" ] && kill "$SPIN_PID" 2>/dev/null && wait "$SPIN_PID" 2>/dev/null SPIN_PID="" printf "\r\033[2K ${RED}✗${NC} %b\n" "$*" >&2 } # Cleanup on exit trap '[ -n "$SPIN_PID" ] && kill "$SPIN_PID" 2>/dev/null' EXIT # -- banner ---------------------------------------------------------------- echo "" echo -e " ${BOLD}${WHITE}VantaBrain Installer${NC} ${DIM}v${VB_VERSION}${NC}" echo "" # -- preflight ------------------------------------------------------------- spin_start "Checking dependencies..." missing=0 for dep in gh git node npm; do if ! command -v "$dep" >/dev/null 2>&1; then spin_fail "$dep is required but not installed." missing=1 fi done if [ "$missing" -eq 1 ]; then echo "" echo -e " ${DIM}macOS: brew install gh git node${NC}" echo -e " ${DIM}Linux: apt install gh git nodejs npm${NC}" exit 1 fi if ! gh auth status >/dev/null 2>&1; then spin_fail "gh is not authenticated. Run: ${WHITE}gh auth login${NC}" exit 1 fi NODE_MAJOR=$(node -e "console.log(process.versions.node.split('.')[0])") if [ "$NODE_MAJOR" -lt 20 ]; then spin_fail "Node.js >= 20 required (found $(node -v))" echo -e " ${DIM}brew install node or nvm install 20${NC}" exit 1 fi spin_ok "Dependencies ${DIM}gh $(gh --version | head -1 | awk '{print $3}'), git $(git --version | awk '{print $3}'), node $(node -v | tr -d v)${NC}" # -- clone or update ------------------------------------------------------- if [ -d "$BRAIN_HOME/.git" ]; then spin_start "Updating ${DIM}$BRAIN_HOME${NC}" git -C "$BRAIN_HOME" pull --rebase --autostash >/dev/null 2>&1 || { spin_fail "git pull failed -- continuing with existing version" } spin_ok "Updated ${DIM}$BRAIN_HOME${NC}" elif [ -d "$BRAIN_HOME" ]; then spin_start "Backing up and cloning fresh..." mv "$BRAIN_HOME" "${BRAIN_HOME}.bak.$(date +%s)" gh repo clone "$REPO" "$BRAIN_HOME" -- --quiet spin_ok "Cloned ${DIM}(old dir backed up)${NC}" else spin_start "Cloning VantaBrain..." gh repo clone "$REPO" "$BRAIN_HOME" -- --quiet spin_ok "Cloned to ${DIM}$BRAIN_HOME${NC}" fi # -- install CLI deps ------------------------------------------------------ if [ ! -d "$BRAIN_HOME/cli/node_modules" ]; then spin_start "Installing CLI dependencies..." (cd "$BRAIN_HOME/cli" && npm install --silent 2>/dev/null) spin_ok "CLI dependencies installed" fi # -- hand off to Ink TUI -------------------------------------------------- spin_start "Launching setup..." sleep 0.5 spin_ok "Ready" echo "" # Ensure vantabrain CLI is on PATH for this session and the TUI process export PATH="$BRAIN_HOME/bin:$PATH" # -- database setup (before TUI) ------------------------------------------- if [ ! -f "$BRAIN_HOME/.env" ]; then echo "" echo -e " ${BOLD}${WHITE}Database Setup${NC}" echo -e " ${DIM}VantaBrain uses Neon Postgres for multi-machine state.${NC}" echo -e " ${DIM}Get your connection string from: https://console.neon.tech${NC}" echo "" printf " DATABASE_URL: " read -r db_url "$BRAIN_HOME/.env" spin_ok "Database configured" else echo -e " ${DIM}Skipped. Add later: echo 'DATABASE_URL=...' > $BRAIN_HOME/.env${NC}" fi fi # Detect shell profile for post-install source detect_profile() { local shell_name shell_name="$(basename "${SHELL:-/bin/sh}")" case "$shell_name" in zsh) [ -f "$HOME/.zshrc" ] && echo "$HOME/.zshrc" ;; bash) if [ -f "$HOME/.bashrc" ]; then echo "$HOME/.bashrc" elif [ -f "$HOME/.bash_profile" ]; then echo "$HOME/.bash_profile" fi ;; fish) [ -f "$HOME/.config/fish/config.fish" ] && echo "$HOME/.config/fish/config.fish" ;; *) [ -f "$HOME/.profile" ] && echo "$HOME/.profile" ;; esac } # When run via curl|bash, stdin is the pipe. We need a real TTY for Ink. if [ -t 0 ]; then cd "$BRAIN_HOME/cli" && npx tsx src/cli.tsx onboard else cd "$BRAIN_HOME/cli" && npx tsx src/cli.tsx onboard /dev/null || true fi # -- web dependencies ----------------------------------------------------- if [ ! -d "$BRAIN_HOME/web/node_modules" ]; then spin_start "Installing web dependencies..." (cd "$BRAIN_HOME/web" && npm install --silent 2>/dev/null) spin_ok "Web dependencies installed" fi # Final message echo "" echo -e " ${GREEN}✓${NC} ${BOLD}${WHITE}VantaBrain v${VB_VERSION}${NC} installed" if command -v vantabrain >/dev/null 2>&1; then echo -e " ${DIM}Run ${WHITE}vantabrain${DIM} from any terminal.${NC}" else echo -e " ${DIM}Open a new terminal or run:${NC} ${WHITE}source ${_profile:-~/.zshrc}${NC}" fi echo ""