#!/usr/bin/env bash # ───────────────────────────────────────────────────────────── # BrainJack Service — One-line installer for macOS & Linux # # curl -fsSL https://brainjack.ai/get | bash # # Downloads the service, sets up everything automatically, # and opens a QR code to pair with the BrainJack iOS app. # ───────────────────────────────────────────────────────────── set -euo pipefail # ── Branding ───────────────────────────────────────────────── BOLD='\033[1m' DIM='\033[2m' GREEN='\033[0;32m' BLUE='\033[0;34m' YELLOW='\033[1;33m' RED='\033[0;31m' NC='\033[0m' INSTALL_DIR="$HOME/.brainjack" REPO="scrappylabsai/brainjack-service" BRANCH="main" MIN_PYTHON="3.10" banner() { echo "" echo -e "${BOLD} ██████╗ ██████╗ █████╗ ██╗███╗ ██╗ ██╗ █████╗ ██████╗██╗ ██╗${NC}" echo -e "${BOLD} ██╔══██╗██╔══██╗██╔══██╗██║████╗ ██║ ██║██╔══██╗██╔════╝██║ ██╔╝${NC}" echo -e "${BOLD} ██████╔╝██████╔╝███████║██║██╔██╗ ██║ ██║███████║██║ █████╔╝ ${NC}" echo -e "${BOLD} ██╔══██╗██╔══██╗██╔══██║██║██║╚██╗██║██ ██║██╔══██║██║ ██╔═██╗ ${NC}" echo -e "${BOLD} ██████╔╝██║ ██║██║ ██║██║██║ ╚████║╚█████╔╝██║ ██║╚██████╗██║ ██╗${NC}" echo -e "${BOLD} ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝ ╚════╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝${NC}" echo -e "${DIM} Voice goes in, keystrokes come out.${NC}" echo "" } step() { echo -e " ${GREEN}▸${NC} ${BOLD}$1${NC}"; } info() { echo -e " ${DIM} $1${NC}"; } warn() { echo -e " ${YELLOW}!${NC} $1"; } fail() { echo -e " ${RED}✗${NC} $1"; exit 1; } ok() { echo -e " ${GREEN}✓${NC} $1"; } # ── Pre-flight checks ─────────────────────────────────────── banner OS="$(uname -s)" ARCH="$(uname -m)" if [ "$OS" = "Darwin" ]; then PLATFORM="macOS" elif [ "$OS" = "Linux" ]; then PLATFORM="Linux" else fail "Unsupported OS: $OS. For Windows, use: irm brainjack.ai/install.ps1 | iex" fi step "Detected $PLATFORM ($ARCH)" # Check Python — auto-install on macOS if missing find_python() { for cmd in python3 python; do if command -v "$cmd" &>/dev/null; then VER=$("$cmd" -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')" 2>/dev/null || true) if [ -n "$VER" ]; then MAJOR=$(echo "$VER" | cut -d. -f1) MINOR=$(echo "$VER" | cut -d. -f2) if [ "$MAJOR" -ge 3 ] && [ "$MINOR" -ge 10 ]; then echo "$cmd" return 0 fi fi fi done return 1 } PYTHON=$(find_python || true) if [ -z "$PYTHON" ] && [ "$OS" = "Darwin" ]; then step "Python not found — installing via Homebrew" if ! command -v brew &>/dev/null; then info "Installing Homebrew first (this is normal on a fresh Mac)..." /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" /dev/null PYTHON=$(find_python || true) fi if [ -z "$PYTHON" ]; then fail "Python 3.10+ required. Install it: macOS: brew install python Linux: sudo apt install python3" fi ok "Python: $($PYTHON --version 2>&1)" # Check curl or wget DOWNLOADER="" if command -v curl &>/dev/null; then DOWNLOADER="curl" elif command -v wget &>/dev/null; then DOWNLOADER="wget" else fail "curl or wget required. Install one first." fi # ── Download agent ─────────────────────────────────────────── step "Installing to $INSTALL_DIR" mkdir -p "$INSTALL_DIR" # Files we need from the repo FILES="agent.py requirements.txt .env.template com.brainjack.agent.plist brainjack-agent.service .gitignore" BASE_URL="https://raw.githubusercontent.com/$REPO/$BRANCH" download() { local url="$1" local dest="$2" if [ "$DOWNLOADER" = "curl" ]; then curl -fsSL "$url" -o "$dest" else wget -q "$url" -O "$dest" fi } for file in $FILES; do download "$BASE_URL/$file" "$INSTALL_DIR/$file" 2>/dev/null || true done # Verify the critical file downloaded if [ ! -f "$INSTALL_DIR/agent.py" ]; then fail "Download failed. Check your internet connection and try again." fi ok "Downloaded" # ── Python venv ────────────────────────────────────────────── step "Setting up Python environment" "$PYTHON" -m venv "$INSTALL_DIR/.venv" 2>/dev/null || { # Some systems need ensurepip "$PYTHON" -m venv --without-pip "$INSTALL_DIR/.venv" download "https://bootstrap.pypa.io/get-pip.py" "/tmp/get-pip.py" "$INSTALL_DIR/.venv/bin/python" /tmp/get-pip.py -q rm -f /tmp/get-pip.py } "$INSTALL_DIR/.venv/bin/pip" install -q -r "$INSTALL_DIR/requirements.txt" 2>/dev/null ok "Dependencies installed (websockets)" # ── Configuration ──────────────────────────────────────────── step "Generating configuration" if [ ! -f "$INSTALL_DIR/.env" ]; then cp "$INSTALL_DIR/.env.template" "$INSTALL_DIR/.env" fi # Generate token if not set CURRENT_TOKEN=$(grep '^BRAINJACK_TOKEN=' "$INSTALL_DIR/.env" 2>/dev/null | sed 's/^BRAINJACK_TOKEN=//' || true) if [ -z "$CURRENT_TOKEN" ]; then TOKEN=$("$PYTHON" -c "import secrets; print(secrets.token_urlsafe(32))") if [ "$OS" = "Darwin" ]; then sed -i '' "s|^BRAINJACK_TOKEN=.*|BRAINJACK_TOKEN=$TOKEN|" "$INSTALL_DIR/.env" else sed -i "s|^BRAINJACK_TOKEN=.*|BRAINJACK_TOKEN=$TOKEN|" "$INSTALL_DIR/.env" fi else TOKEN="$CURRENT_TOKEN" fi ok "Auth token generated" # ── Input injection tools (Linux only) ─────────────────────── if [ "$OS" = "Linux" ]; then if [ -n "${WAYLAND_DISPLAY:-}" ]; then if ! command -v ydotool &>/dev/null; then warn "ydotool not found — install it for Wayland keystroke injection:" info "sudo apt install ydotool OR sudo pacman -S ydotool" fi else if ! command -v xdotool &>/dev/null; then warn "xdotool not found — install it for X11 keystroke injection:" info "sudo apt install xdotool OR sudo pacman -S xdotool" fi fi fi # ── Install background service ─────────────────────────────── step "Installing background service" if [ "$OS" = "Darwin" ]; then PLIST_DST="$HOME/Library/LaunchAgents/com.brainjack.agent.plist" sed "s|AGENT_DIR_PLACEHOLDER|$INSTALL_DIR|g" "$INSTALL_DIR/com.brainjack.agent.plist" > "$PLIST_DST" launchctl unload "$PLIST_DST" 2>/dev/null || true launchctl load "$PLIST_DST" ok "macOS LaunchAgent installed (auto-starts on login)" else mkdir -p ~/.config/systemd/user # Rewrite the service file for the install location cat > ~/.config/systemd/user/brainjack-agent.service </dev/null ok "systemd user service installed (auto-starts on login)" fi # ── Verify it's running ────────────────────────────────────── sleep 1 PORT=$(grep '^BRAINJACK_PORT=' "$INSTALL_DIR/.env" | sed 's/^BRAINJACK_PORT=//' || echo "9898") PORT="${PORT:-9898}" RUNNING=false for i in 1 2 3; do if "$INSTALL_DIR/.venv/bin/python" -c " import asyncio, websockets, json async def t(): async with websockets.connect('ws://127.0.0.1:$PORT?token=$TOKEN') as ws: await ws.send(json.dumps({'cmd':'status'})) r = json.loads(await ws.recv()) print(r.get('hostname','')) asyncio.run(t()) " &>/dev/null; then RUNNING=true break fi sleep 1 done if [ "$RUNNING" = true ]; then ok "Service running on port $PORT" else warn "Service may still be starting. Check logs:" if [ "$OS" = "Darwin" ]; then info "tail -f $INSTALL_DIR/brainjack.log" else info "journalctl --user -u brainjack-agent -f" fi fi # ── macOS: Accessibility permission ────────────────────────── if [ "$OS" = "Darwin" ]; then echo "" step "One more thing — Accessibility Permission" echo "" echo -e " ${YELLOW}macOS needs your permission for BrainJack to type keystrokes.${NC}" echo -e " ${YELLOW}System Settings is opening now — just follow these steps:${NC}" echo "" echo -e " ${BOLD}1.${NC} Click the ${BOLD}+${NC} button (enter your password if asked)" echo -e " ${BOLD}2.${NC} Navigate to: ${GREEN}$INSTALL_DIR${NC}" echo -e " ${BOLD}3.${NC} Select ${GREEN}BrainJack.app${NC}" echo -e " ${BOLD}4.${NC} Toggle it ${GREEN}ON${NC}" echo "" echo -e " ${DIM} Skip this if you only want clipboard mode (no key combos).${NC}" echo "" # Open System Settings + reveal BrainJack.app open "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility" 2>/dev/null || true sleep 1 open -R "$INSTALL_DIR/BrainJack.app" 2>/dev/null || true fi # ── Get local IP ───────────────────────────────────────────── if [ "$OS" = "Darwin" ]; then LOCAL_IP=$(ipconfig getifaddr en0 2>/dev/null || ipconfig getifaddr en1 2>/dev/null || echo "YOUR_IP") else LOCAL_IP=$(hostname -I 2>/dev/null | awk '{print $1}' || echo "YOUR_IP") fi # ── Generate QR code page ──────────────────────────────────── HOSTNAME_SHORT=$(hostname -s 2>/dev/null || hostname) QR_HTML="/tmp/brainjack-setup-$HOSTNAME_SHORT.html" cat > "$QR_HTML" << 'QREOF' BrainJack — Scan to Connect

BrainJack

Scan with the BrainJack app to connect

Device:
Address:
Token:
Next: Open BrainJack on your iPhone → tap +Scan QR Code
Service installed   Service running  
QREOF2 # Open QR page if [ "$OS" = "Darwin" ]; then open "$QR_HTML" 2>/dev/null || true elif command -v xdg-open &>/dev/null; then xdg-open "$QR_HTML" 2>/dev/null || true fi # ── Summary ────────────────────────────────────────────────── echo "" echo -e " ─────────────────────────────────────────────────" echo -e " ${GREEN}${BOLD}BrainJack installed successfully.${NC}" echo -e " ─────────────────────────────────────────────────" echo "" echo -e " ${BOLD}Location:${NC} $INSTALL_DIR" echo -e " ${BOLD}Address:${NC} ws://$LOCAL_IP:$PORT" echo -e " ${BOLD}Auth Token:${NC} $TOKEN" echo "" echo -e " ${BOLD}Connect from your phone:${NC}" echo -e " 1. Open the BrainJack app" echo -e " 2. Tap ${BOLD}+${NC} → ${BOLD}Scan QR Code${NC}" echo -e " 3. Point at the QR code in your browser" echo "" if [ "$OS" = "Darwin" ]; then echo -e " ${BOLD}Manage:${NC}" echo -e " Stop: launchctl unload ~/Library/LaunchAgents/com.brainjack.agent.plist" echo -e " Start: launchctl load ~/Library/LaunchAgents/com.brainjack.agent.plist" echo -e " Logs: tail -f ~/.brainjack/brainjack.log" echo -e " Uninstall: curl -fsSL brainjack.ai/uninstall | bash" else echo -e " ${BOLD}Manage:${NC}" echo -e " Status: systemctl --user status brainjack-agent" echo -e " Restart: systemctl --user restart brainjack-agent" echo -e " Logs: journalctl --user -u brainjack-agent -f" fi echo "" echo -e " ${DIM}brainjack.ai${NC}" echo ""