#!/usr/bin/env bash # # Hyperspace Network — Binary Install Script # # Downloads a pre-compiled binary — no Node.js, pnpm, or git required. # On desktop environments (macOS, Linux with display), also installs the # system tray app with auto-update and crash recovery. # # Usage: # curl -fsSL https://agents.hyper.space/cli | bash # # or # bash install.sh [--no-start] [--key ] # # Options: # --no-start Don't auto-start the node after install # --no-tray Skip tray app installation on desktop # --sudo Use sudo to install to /usr/local/bin (fixes PATH issues) # --key Use existing Ed25519 private key # --api-port N OpenAI API server port (default: 8080) # # Environment: # HYPERSPACE_PRIVATE_KEY Ed25519 private key (alternative to --key) # set -euo pipefail # ─── Colors ─── RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' BOLD='\033[1m' NC='\033[0m' info() { echo -e "${BLUE}[*]${NC} $1"; } ok() { echo -e "${GREEN}[+]${NC} $1"; } warn() { echo -e "${YELLOW}[!]${NC} $1"; } err() { echo -e "${RED}[-]${NC} $1"; } step() { echo -e "${CYAN}==>${NC} ${BOLD}$1${NC}"; } # ─── Parse args ─── NO_START=false NO_TRAY=false USE_SUDO=false PRIVATE_KEY="" API_PORT=8080 while [[ $# -gt 0 ]]; do case $1 in --no-start) NO_START=true; shift ;; --no-tray) NO_TRAY=true; shift ;; --sudo) USE_SUDO=true; shift ;; --key) PRIVATE_KEY="$2"; shift 2 ;; --api-port) API_PORT="$2"; shift 2 ;; *) shift ;; esac done # ─── Banner ─── echo "" echo -e "${BOLD}┌─────────────────────────────────────────────────────────┐${NC}" echo -e "${BOLD}│ hyperspace │${NC}" echo -e "${BOLD}│ network of agents │${NC}" echo -e "${BOLD}└─────────────────────────────────────────────────────────┘${NC}" echo "" # ─── Detect system ─── step "Detecting system..." OS=$(uname -s | tr '[:upper:]' '[:lower:]') ARCH=$(uname -m) # Map to GitHub release asset names case "$OS-$ARCH" in linux-x86_64) ASSET_NAME="aios-cli-x86_64-unknown-linux-gnu.tar.gz" ;; darwin-arm64) ASSET_NAME="aios-cli-aarch64-apple-darwin.tar.gz" ;; darwin-x86_64) ASSET_NAME="aios-cli-x86_64-apple-darwin.tar.gz" ;; *) err "Unsupported platform: $OS $ARCH" err "Supported: Linux x86_64, macOS ARM64, macOS x86_64" exit 1 ;; esac ok "OS: $OS ($ARCH)" # ─── Detect desktop environment ─── IS_DESKTOP=false if [[ "$OS" == "darwin" ]]; then # macOS is always a desktop environment IS_DESKTOP=true elif [[ "$OS" == "linux" ]]; then # Check for display server (X11 or Wayland) if [[ -n "${DISPLAY:-}" ]] || [[ -n "${WAYLAND_DISPLAY:-}" ]]; then IS_DESKTOP=true elif [[ "${XDG_SESSION_TYPE:-}" == "x11" ]] || [[ "${XDG_SESSION_TYPE:-}" == "wayland" ]]; then IS_DESKTOP=true fi fi if [[ "$IS_DESKTOP" == "true" ]]; then ok "Desktop environment detected" else info "Headless server detected (CLI-only install)" fi # Check for GPU if command -v nvidia-smi &>/dev/null; then GPU_NAME=$(nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null | head -1) GPU_VRAM=$(nvidia-smi --query-gpu=memory.total --format=csv,noheader,nounits 2>/dev/null | head -1) ok "GPU: $GPU_NAME (${GPU_VRAM}MB VRAM)" elif command -v rocm-smi &>/dev/null; then ok "GPU: AMD ROCm detected" elif [[ "$OS" == "darwin" ]]; then ok "GPU: Apple Silicon (unified memory)" else warn "No GPU detected — node will participate as relay only" fi # ─── Detect v1 installation ─── V1_MIGRATED=false # Check for running hyperspace/aios-cli processes (v1 or v2) V1_PIDS=$(pgrep -f 'aios-cli|_aios-cli' 2>/dev/null || true) V2_PIDS=$(pgrep -xf '.*hyperspace (start|daemon).*' 2>/dev/null || pgrep -x hyperspace 2>/dev/null || true) if [[ -n "$V1_PIDS" ]]; then warn "Found running v1 aios-cli process(es): $V1_PIDS" warn "Stopping v1 processes to upgrade to v2..." kill $V1_PIDS 2>/dev/null || true sleep 2 kill -9 $V1_PIDS 2>/dev/null || true ok "Stopped v1 processes" V1_MIGRATED=true fi if [[ -n "$V2_PIDS" ]]; then warn "Found running hyperspace process(es): $V2_PIDS" warn "Stopping to upgrade..." kill $V2_PIDS 2>/dev/null || true sleep 2 kill -9 $V2_PIDS 2>/dev/null || true ok "Stopped running hyperspace" fi # Check for v1 key files and inform user they'll be auto-migrated V1_KEY_PATHS=( "${HOME}/.config/hyperspace/key.pem" "${HOME}/Library/Application Support/hyperspace/key.pem" "${HOME}/.hyperspace/key.pem" ) for kp in "${V1_KEY_PATHS[@]}"; do if [[ -f "$kp" ]]; then ok "Found v1 identity at $kp — will auto-migrate on first run" V1_MIGRATED=true break fi done # ─── Install directory ─── INSTALL_DIR="${HOME}/.hyperspace" BIN_DIR="${HOME}/.local/bin" mkdir -p "$INSTALL_DIR" "$BIN_DIR" # Detect shell profile SHELL_RC="${HOME}/.bashrc" [[ -f "${HOME}/.zshrc" ]] && SHELL_RC="${HOME}/.zshrc" # Ensure BIN_DIR is in PATH if [[ ":$PATH:" != *":$BIN_DIR:"* ]]; then warn "$BIN_DIR not in PATH — adding to shell profile" echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$SHELL_RC" export PATH="$BIN_DIR:$PATH" fi # Ensure XDG_RUNTIME_DIR is set (required for systemctl --user on headless servers) if [[ "$OS" == "linux" ]] && [[ -z "${XDG_RUNTIME_DIR:-}" ]]; then export XDG_RUNTIME_DIR="/run/user/$(id -u)" if ! grep -q 'XDG_RUNTIME_DIR' "$SHELL_RC" 2>/dev/null; then echo 'export XDG_RUNTIME_DIR="/run/user/$(id -u)"' >> "$SHELL_RC" fi fi # ─── Download binary ─── step "Downloading Hyperspace CLI..." # Download via agents.hyper.space proxy (handles auth for private repo) DOWNLOAD_URL="https://agents.hyper.space/api/download?asset=${ASSET_NAME}" # Verify the asset exists before downloading if ! curl -sfI --max-time 10 "$DOWNLOAD_URL" >/dev/null 2>&1; then # Fallback: try public hyperspace-node repo directly FALLBACK_API="https://api.github.com/repos/hyperspaceai/hyperspace-node/releases?per_page=20" DOWNLOAD_URL=$(curl -fsSL "$FALLBACK_API" 2>/dev/null | grep -o "\"browser_download_url\": *\"[^\"]*${ASSET_NAME}\"" | head -1 | cut -d'"' -f4) fi if [[ -z "$DOWNLOAD_URL" ]]; then err "Could not find release asset: $ASSET_NAME" err "Try again later or check https://agents.hyper.space" exit 1 fi info "Downloading from: $DOWNLOAD_URL" TMP_DIR=$(mktemp -d) trap 'rm -rf "$TMP_DIR"' EXIT curl -fSL --progress-bar -o "$TMP_DIR/$ASSET_NAME" "$DOWNLOAD_URL" ok "Download complete" # ─── Extract and install ─── step "Installing CLI..." tar -xzf "$TMP_DIR/$ASSET_NAME" -C "$TMP_DIR" # Install as both 'aios-cli' and 'hyperspace' # Archive contains both 'aios-cli' and '_aios-cli' (v1 compat name) BINARY="" if [[ -f "$TMP_DIR/aios-cli" ]]; then BINARY="$TMP_DIR/aios-cli" elif [[ -f "$TMP_DIR/_aios-cli" ]]; then BINARY="$TMP_DIR/_aios-cli" else err "Binary not found in archive (expected 'aios-cli' or '_aios-cli')" exit 1 fi cp "$BINARY" "$BIN_DIR/aios-cli" chmod +x "$BIN_DIR/aios-cli" cp "$BINARY" "$BIN_DIR/hyperspace" chmod +x "$BIN_DIR/hyperspace" ok "Installed to $BIN_DIR/hyperspace" ok "Installed to $BIN_DIR/aios-cli (v1 compat)" # CRDT WASM — must be next to the binary for loro-crdt to find it if [[ -f "$TMP_DIR/loro_wasm_bg.wasm" ]]; then cp "$TMP_DIR/loro_wasm_bg.wasm" "$BIN_DIR/loro_wasm_bg.wasm" ok "Installed CRDT engine (loro_wasm_bg.wasm)" fi # SQLite native addon — must be next to the binary for better-sqlite3 to find it if [[ -f "$TMP_DIR/better_sqlite3.node" ]]; then cp "$TMP_DIR/better_sqlite3.node" "$BIN_DIR/better_sqlite3.node" ok "Installed SQLite engine (better_sqlite3.node)" fi # ORT-Web WASM — search sidecar uses transformers.js + onnxruntime-web for # semantic embeddings. The .wasm + .mjs files must be next to the search # sidecar binary so dynamic imports resolve at runtime. ORT_INSTALLED=0 for f in ort-wasm-simd-threaded.jsep.wasm ort-wasm-simd-threaded.jsep.mjs ort-wasm-simd-threaded.wasm ort-wasm-simd-threaded.mjs; do if [[ -f "$TMP_DIR/$f" ]]; then cp "$TMP_DIR/$f" "$BIN_DIR/$f" ORT_INSTALLED=$((ORT_INSTALLED + 1)) fi done [[ $ORT_INSTALLED -gt 0 ]] && ok "Installed ORT-Web WASM ($ORT_INSTALLED files — semantic search)" # Out-of-process auto-updater — runs every 5min via systemd timer (Linux) # or launchd (macOS), polls agents.hyper.space, signature-verifies the # tarball with a baked-in Ed25519 public key, runs a no-TTY canary, and # atomic-swaps the main daemon's binary. Independent of main daemon's # state — survives crashes, backoff windows, frozen processes. THIS is # what makes auto-update reach 100% reliability across the fleet. # # Two implementations ship in the same release tarball: # - hyperspace-updater[.exe] — Go binary, single static, signature-aware (PRIMARY) # - hyperspace-updater.sh — bash fallback (used if Go binary fails) HSP_BIN_DIR="${HOME}/.hyperspace/bin" mkdir -p "$HSP_BIN_DIR" # Install Go updater binary (primary) GO_UPDATER_NAME="hyperspace-updater" [[ "$OS" == "windows" ]] && GO_UPDATER_NAME="hyperspace-updater.exe" if [[ -f "$TMP_DIR/$GO_UPDATER_NAME" ]]; then cp "$TMP_DIR/$GO_UPDATER_NAME" "$HSP_BIN_DIR/$GO_UPDATER_NAME" chmod +x "$HSP_BIN_DIR/$GO_UPDATER_NAME" ok "Installed out-of-process auto-updater (Go binary, signature-verifying)" fi # Install supervisor binary (v5.46.0+) — staged but NOT activated. # Activated via `hyperspace supervisor install` once you're ready to switch # from the existing daemon plist to the supervisor-managed model. v5.46.1 # will make activation default. if [[ -f "$TMP_DIR/hyperspace-supervisor" ]]; then cp "$TMP_DIR/hyperspace-supervisor" "$HSP_BIN_DIR/hyperspace-supervisor" chmod +x "$HSP_BIN_DIR/hyperspace-supervisor" ok "Installed hyperspace-supervisor (run 'hyperspace supervisor install' to activate)" fi # Install bash fallback (so the systemd timer can fall back if the Go binary fails for any reason) if [[ -f "$TMP_DIR/hyperspace-updater.sh" ]]; then cp "$TMP_DIR/hyperspace-updater.sh" "$HSP_BIN_DIR/hyperspace-updater.sh" chmod +x "$HSP_BIN_DIR/hyperspace-updater.sh" ok "Installed bash fallback updater (hyperspace-updater.sh)" if [[ "$OS" == "linux" ]] && command -v systemctl &>/dev/null; then SYSTEMD_USER_DIR="${HOME}/.config/systemd/user" mkdir -p "$SYSTEMD_USER_DIR" if [[ -f "$TMP_DIR/hyperspace-updater.service" ]]; then cp "$TMP_DIR/hyperspace-updater.service" "$SYSTEMD_USER_DIR/hyperspace-updater.service" fi if [[ -f "$TMP_DIR/hyperspace-updater.timer" ]]; then cp "$TMP_DIR/hyperspace-updater.timer" "$SYSTEMD_USER_DIR/hyperspace-updater.timer" fi systemctl --user daemon-reload 2>/dev/null || true systemctl --user enable --now hyperspace-updater.timer 2>/dev/null \ && ok "hyperspace-updater.timer enabled (every 5min, independent of main daemon)" \ || warn "Could not enable hyperspace-updater.timer — auto-update will rely on main daemon's checkForUpdate" elif [[ "$OS" == "darwin" ]]; then LAUNCHAGENTS_DIR="${HOME}/Library/LaunchAgents" mkdir -p "$LAUNCHAGENTS_DIR" if [[ -f "$TMP_DIR/com.hyperspace.updater.plist" ]]; then sed "s|HOME_PLACEHOLDER|${HOME}|g" "$TMP_DIR/com.hyperspace.updater.plist" \ > "$LAUNCHAGENTS_DIR/com.hyperspace.updater.plist" launchctl unload "$LAUNCHAGENTS_DIR/com.hyperspace.updater.plist" 2>/dev/null || true launchctl load "$LAUNCHAGENTS_DIR/com.hyperspace.updater.plist" 2>/dev/null \ && ok "com.hyperspace.updater LaunchAgent loaded (every 5min)" \ || warn "Could not load com.hyperspace.updater LaunchAgent" fi fi fi # Pod-raft coordinator — Go binary for Raft-backed pod state (BoltDB, no SQLite) # Installed to ~/.hyperspace/bin/pod-raft where the CLI auto-launches it on `hyperspace start` if [[ -f "$TMP_DIR/pod-raft" ]]; then cp "$TMP_DIR/pod-raft" "$BIN_DIR/pod-raft" chmod +x "$BIN_DIR/pod-raft" ok "Installed pod coordinator (pod-raft)" elif [[ -f "$TMP_DIR/pod-raft.exe" ]]; then cp "$TMP_DIR/pod-raft.exe" "$BIN_DIR/pod-raft.exe" ok "Installed pod coordinator (pod-raft.exe)" fi # Sidecars — 20 Tier-1 AgenticOS services. # Each sidecar is opt-in via its own HYPERSPACE_*_SIDECAR=1 env var. # Binaries live in ~/.hyperspace/bin and are auto-launched by the CLI. # Pre-existing: network inference pulse matrix # AgenticOS: vectors cache storage search context proxy wallet training # workflows thor-runtime thor-bridge catalog # plugin-runtime mcp-host skill-matrix # Blockchain: agentic-blockchain (A1 fullnode, auto-updates independently) for SIDECAR in \ network inference pulse matrix \ vectors cache storage search context proxy wallet training \ workflows thor-runtime thor-bridge catalog \ plugin-runtime mcp-host skill-matrix \ agentic-blockchain; do BIN="hyperspace-$SIDECAR" if [[ -f "$TMP_DIR/$BIN" ]]; then cp "$TMP_DIR/$BIN" "$BIN_DIR/$BIN" chmod +x "$BIN_DIR/$BIN" ok "Installed $SIDECAR sidecar ($BIN)" elif [[ -f "$TMP_DIR/$BIN.exe" ]]; then cp "$TMP_DIR/$BIN.exe" "$BIN_DIR/$BIN.exe" ok "Installed $SIDECAR sidecar ($BIN.exe)" fi done # ─── avmd sidecar (security gate) ──────────────────────────────────────────── # avmd is the system-wide AVM gate. Bundled in the CLI tarball when the # release-cli workflow could fetch it from releases.hyper.space. Lands at # ~/.hyperspace/bin/avmd; the CLI's AvmBinaryManager picks it up at # `hyperspace start`. If not bundled (e.g. Intel Mac, where the avmd # release matrix has no asset), the CLI falls back to its own download # path on first start, or politely declines to enable AVM gating. AVMD_BIN_DIR="${HOME}/.hyperspace/bin" mkdir -p "$AVMD_BIN_DIR" if [[ -f "$TMP_DIR/avmd" ]]; then cp "$TMP_DIR/avmd" "$AVMD_BIN_DIR/avmd" chmod +x "$AVMD_BIN_DIR/avmd" ok "Installed avmd sidecar" fi if [[ -f "$TMP_DIR/avmctl" ]]; then cp "$TMP_DIR/avmctl" "$AVMD_BIN_DIR/avmctl" chmod +x "$AVMD_BIN_DIR/avmctl" ok "Installed avmctl operator CLI" fi if [[ -f "$TMP_DIR/.avmd-version" ]]; then cp "$TMP_DIR/.avmd-version" "$AVMD_BIN_DIR/.avmd-version" fi # ─── Python shard scripts (distributed inference) ──────────────────────────── # mlx_shard_server.py (Apple Silicon) and torch_shard_server.py (NVIDIA CUDA) # are spawned by the CLI as child processes for cross-node shard inference. PYTHON_DIR="${HOME}/.hyperspace/python" mkdir -p "$PYTHON_DIR" if [[ -d "$TMP_DIR/python" ]]; then cp "$TMP_DIR/python/"*.py "$PYTHON_DIR/" 2>/dev/null && \ ok "Installed shard inference scripts" || true fi # ─── Skills (Pi coding agent) ───────────────────────────────────────────── SKILLS_DIR="${HOME}/.hyperspace/skills" mkdir -p "$SKILLS_DIR" if [[ -d "$TMP_DIR/skills" ]]; then cp "$TMP_DIR/skills/"*.md "$SKILLS_DIR/" 2>/dev/null && \ ok "Installed Hyperspace skill for Pi coding agent" || true fi # ─── Install A1 Blockchain Node ────────────────────────────────────────────── # If the blockchain binary wasn't in the CLI tar (pre-bundle transition), # download it from GitHub releases. Once installed, the binary auto-updates # itself every 5 minutes from agi releases — independent of the CLI. CHAIN_BIN="$BIN_DIR/hyperspace-agentic-blockchain" if [[ ! -f "$CHAIN_BIN" ]]; then step "Downloading A1 blockchain node..." CHAIN_ARCH="$ARCH" [[ "$CHAIN_ARCH" == "arm64" || "$CHAIN_ARCH" == "aarch64" ]] && CHAIN_ARCH="arm64" [[ "$CHAIN_ARCH" == "x86_64" ]] && CHAIN_ARCH="amd64" CHAIN_ASSET="hyperspace-agentic-blockchain-${OS}-${CHAIN_ARCH}.tar.gz" CHAIN_API="https://api.github.com/repos/hyperspaceai/agi/releases/latest" CHAIN_DL=$(curl -sfL "$CHAIN_API" 2>/dev/null | grep -o "\"browser_download_url\": *\"[^\"]*${CHAIN_ASSET}\"" | head -1 | cut -d'"' -f4) if [[ -n "$CHAIN_DL" ]]; then if curl -sfL "$CHAIN_DL" -o "$TMP_DIR/$CHAIN_ASSET" 2>/dev/null; then tar xzf "$TMP_DIR/$CHAIN_ASSET" -C "$BIN_DIR/" 2>/dev/null && \ chmod +x "$CHAIN_BIN" 2>/dev/null && \ ok "Installed A1 blockchain node (auto-updates from GitHub independently)" || \ warn "Blockchain binary extraction failed" else warn "Blockchain download failed — will retry on first 'hyperspace start'" fi else warn "No blockchain release for $OS-$CHAIN_ARCH — will download later" fi else ok "A1 blockchain node already installed (auto-updates independently)" fi # ─── Install Prometheus Cognitive Engine ───────────────────────────────────── # Downloaded ONLY if not already present. Prometheus auto-updates itself # independently — install.sh NEVER overwrites an existing bundle. RUNTIME_DIR="${HOME}/.hyperspace/runtime" mkdir -p "$RUNTIME_DIR" if [[ ! -f "$RUNTIME_DIR/prometheus.bundle.js" ]]; then step "Downloading Prometheus cognitive engine..." PROM_MANIFEST=$(curl -sfL "https://agents.hyper.space/api/prometheus/latest" 2>/dev/null || echo "") if [[ -n "$PROM_MANIFEST" ]]; then PROM_URL=$(echo "$PROM_MANIFEST" | grep -o '"url":"[^"]*"' | head -1 | cut -d'"' -f4) PROM_CHECKSUM=$(echo "$PROM_MANIFEST" | grep -o '"checksum":"[^"]*"' | head -1 | cut -d'"' -f4) PROM_VERSION=$(echo "$PROM_MANIFEST" | grep -o '"version":"[^"]*"' | head -1 | cut -d'"' -f4) if [[ -n "$PROM_URL" ]]; then curl -sfL "$PROM_URL" -o "$RUNTIME_DIR/prometheus.bundle.js.tmp" 2>/dev/null if [[ -f "$RUNTIME_DIR/prometheus.bundle.js.tmp" ]]; then # Verify checksum if provided ACTUAL_SUM=$(sha256sum "$RUNTIME_DIR/prometheus.bundle.js.tmp" 2>/dev/null | cut -d' ' -f1 || shasum -a 256 "$RUNTIME_DIR/prometheus.bundle.js.tmp" 2>/dev/null | cut -d' ' -f1) if [[ -z "$PROM_CHECKSUM" || "$ACTUAL_SUM" == "$PROM_CHECKSUM" ]]; then mv "$RUNTIME_DIR/prometheus.bundle.js.tmp" "$RUNTIME_DIR/prometheus.bundle.js" echo "{\"version\":\"$PROM_VERSION\",\"checksum\":\"$ACTUAL_SUM\",\"updatedAt\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}" > "$RUNTIME_DIR/version.json" ok "Prometheus $PROM_VERSION installed" else rm -f "$RUNTIME_DIR/prometheus.bundle.js.tmp" warn "Prometheus checksum mismatch — will download on first 'hyperspace start'" fi else warn "Prometheus download failed — will retry on first 'hyperspace start'" fi fi else warn "Could not reach Prometheus manifest — will download on first 'hyperspace start'" fi else ok "Prometheus already installed — skipping (auto-updates independently)" fi # Install to /usr/local/bin/ — critical to avoid stale-binary PATH conflicts. # Previous installs may have placed an older version there, and /usr/local/bin # typically comes before ~/.local/bin in PATH. If we skip this, `hyperspace` # resolves to the OLD binary while ~/.local/bin/hyperspace has the new one. if [[ -d "/usr/local/bin" ]]; then if cp "$BINARY" /usr/local/bin/hyperspace 2>/dev/null && chmod +x /usr/local/bin/hyperspace 2>/dev/null; then ok "Installed to /usr/local/bin/hyperspace" cp "$BINARY" /usr/local/bin/aios-cli 2>/dev/null && chmod +x /usr/local/bin/aios-cli 2>/dev/null || true elif [[ "$(id -u)" == "0" ]]; then # We're root but cp failed (shouldn't happen) — try explicit paths install -m 755 "$BINARY" /usr/local/bin/hyperspace install -m 755 "$BINARY" /usr/local/bin/aios-cli 2>/dev/null || true ok "Installed to /usr/local/bin/hyperspace (root)" elif [[ "$USE_SUDO" == "true" ]]; then sudo cp "$BINARY" /usr/local/bin/hyperspace && sudo chmod +x /usr/local/bin/hyperspace ok "Installed to /usr/local/bin/hyperspace (sudo)" sudo cp "$BINARY" /usr/local/bin/aios-cli 2>/dev/null && sudo chmod +x /usr/local/bin/aios-cli 2>/dev/null || true else # Check if there's an OLD binary at /usr/local/bin that will shadow us if [[ -f "/usr/local/bin/hyperspace" ]]; then OLD_VER=$(/usr/local/bin/hyperspace version 2>/dev/null | head -1 || echo 'unknown') NEW_VER=$("$BIN_DIR/hyperspace" version 2>/dev/null | head -1 || echo 'unknown') if [[ "$OLD_VER" != "$NEW_VER" ]]; then warn "Stale binary at /usr/local/bin/hyperspace ($OLD_VER) shadows new install ($NEW_VER)" warn "Fix: sudo cp $BIN_DIR/hyperspace /usr/local/bin/hyperspace" fi fi fi fi # Also install to ~/.hyperspace for backward compat cp "$BIN_DIR/aios-cli" "$INSTALL_DIR/aios-cli" # Write install-method marker (CLI reports this in analytics sync) echo "install-script" > "$INSTALL_DIR/install-method" # v1 compat: install to common v1 locations if they exist if [[ -d "${HOME}/.aios" ]]; then cp "$BIN_DIR/aios-cli" "${HOME}/.aios/aios-cli" 2>/dev/null || true ok "Updated v1 binary at ~/.aios/aios-cli" fi # ─── Install llama-server (inference engine) ─── step "Installing llama-server (AI inference engine)..." LLAMA_BUILD="b8148" LLAMA_BIN_DIR="${HOME}/.hyperspace/bin" LLAMA_SERVER_PATH="${LLAMA_BIN_DIR}/llama-server" LLAMA_VERSION_FILE="${LLAMA_BIN_DIR}/.llama-server-version" # Check if already installed at the right version if [[ -f "$LLAMA_SERVER_PATH" ]] && [[ -f "$LLAMA_VERSION_FILE" ]] && [[ "$(cat "$LLAMA_VERSION_FILE")" == "$LLAMA_BUILD" ]]; then ok "llama-server $LLAMA_BUILD already installed" else # Determine archive name HAS_NVIDIA=false command -v nvidia-smi &>/dev/null && HAS_NVIDIA=true case "$OS-$ARCH" in linux-x86_64) LLAMA_ARCHIVE="llama-${LLAMA_BUILD}-bin-ubuntu-x64.tar.gz" ;; darwin-arm64) LLAMA_ARCHIVE="llama-${LLAMA_BUILD}-bin-macos-arm64.tar.gz" ;; darwin-x86_64) LLAMA_ARCHIVE="llama-${LLAMA_BUILD}-bin-macos-x64.tar.gz" ;; esac LLAMA_URL="https://github.com/ggml-org/llama.cpp/releases/download/${LLAMA_BUILD}/${LLAMA_ARCHIVE}" info "Downloading llama-server from $LLAMA_URL" mkdir -p "$LLAMA_BIN_DIR" if curl -fSL --progress-bar -o "$TMP_DIR/$LLAMA_ARCHIVE" "$LLAMA_URL"; then # Extract just the llama-server binary tar xzf "$TMP_DIR/$LLAMA_ARCHIVE" -C "$TMP_DIR" "llama-${LLAMA_BUILD}/llama-server" 2>/dev/null || \ tar xzf "$TMP_DIR/$LLAMA_ARCHIVE" -C "$TMP_DIR" if [[ -f "$TMP_DIR/llama-${LLAMA_BUILD}/llama-server" ]]; then mv "$TMP_DIR/llama-${LLAMA_BUILD}/llama-server" "$LLAMA_SERVER_PATH" elif [[ -f "$TMP_DIR/llama-server" ]]; then mv "$TMP_DIR/llama-server" "$LLAMA_SERVER_PATH" fi if [[ -f "$LLAMA_SERVER_PATH" ]]; then chmod +x "$LLAMA_SERVER_PATH" echo "$LLAMA_BUILD" > "$LLAMA_VERSION_FILE" ok "Installed llama-server to $LLAMA_SERVER_PATH" else warn "llama-server binary not found in archive — will download on first run" fi else warn "Failed to download llama-server — will download on first run" fi fi # ─── Install uv (Python package manager for autoresearch) ─── step "Installing uv (Python package manager)..." UV_PATH="${HOME}/.local/bin/uv" if command -v uv &>/dev/null; then UV_VER=$(uv --version 2>/dev/null | head -1) ok "uv already installed: $UV_VER" elif [[ -f "$UV_PATH" ]]; then UV_VER=$("$UV_PATH" --version 2>/dev/null | head -1) ok "uv already installed: $UV_VER" else # Install uv via the official installer (single binary, ~30MB) if curl -fsSL https://astral.sh/uv/install.sh | sh 2>/dev/null; then UV_VER=$("$UV_PATH" --version 2>/dev/null || uv --version 2>/dev/null || echo "installed") ok "uv installed: $UV_VER" else warn "uv install failed — GPU training will use TypeScript fallback" fi fi # ─── Kill existing processes ─── # Stop any running node or tray so the new install starts clean pkill -f "hyperspace start" 2>/dev/null || true pkill -f "hyperspace-tray" 2>/dev/null || true pkill -f "Hyperspace.app" 2>/dev/null || true sleep 1 # ─── Install tray app on desktop (first install only) ─── # The tray app requires manual auth (sudo, Gatekeeper) which blocks auto-updates. # Only install on first run; after that, the tray app auto-updates itself. # Skip entirely on headless servers or if already installed. TRAY_ALREADY_INSTALLED=false [[ -d "/Applications/Hyperspace.app" ]] && TRAY_ALREADY_INSTALLED=true command -v hyperspace-tray &>/dev/null && TRAY_ALREADY_INSTALLED=true if [[ "$IS_DESKTOP" == "true" ]] && [[ "$NO_TRAY" == "false" ]] && [[ "$TRAY_ALREADY_INSTALLED" == "false" ]]; then step "Installing Hyperspace Tray App (first time only)..." # Determine tray asset name for this platform TRAY_ASSET="" TRAY_INSTALL_METHOD="" case "$OS-$ARCH" in darwin-arm64) TRAY_ASSET="Hyperspace_.*_aarch64.dmg" TRAY_INSTALL_METHOD="dmg" ;; darwin-x86_64) TRAY_ASSET="Hyperspace_.*_x64.dmg" TRAY_INSTALL_METHOD="dmg" ;; linux-x86_64) TRAY_ASSET="Hyperspace_.*_amd64.deb" TRAY_INSTALL_METHOD="deb" ;; esac if [[ -n "$TRAY_ASSET" ]]; then # Get tray app from hyperspace-node releases (public repo, tray-v* tags) TRAY_RELEASE_API="https://api.github.com/repos/hyperspaceai/hyperspace-node/releases?per_page=10" TRAY_URL=$(curl -fsSL --connect-timeout 10 "$TRAY_RELEASE_API" 2>/dev/null | grep -oE "\"browser_download_url\": *\"[^\"]*${TRAY_ASSET}\"" | head -1 | cut -d'"' -f4 || true) # Tray install is best-effort — never block the CLI from starting if [[ -n "$TRAY_URL" ]]; then ( set +e # disable errexit in subshell so failures don't kill the script TRAY_FILE=$(basename "$TRAY_URL") info "Downloading tray app: $TRAY_FILE" curl -fSL --connect-timeout 15 --progress-bar -o "$TMP_DIR/$TRAY_FILE" "$TRAY_URL" || { warn "Tray download failed — skipping"; exit 0; } case "$TRAY_INSTALL_METHOD" in dmg) MOUNT_DIR=$(hdiutil attach "$TMP_DIR/$TRAY_FILE" -nobrowse -noverify -noautoopen 2>/dev/null | tail -1 | awk '{print $3}') if [[ -n "$MOUNT_DIR" ]] && [[ -d "$MOUNT_DIR" ]]; then APP_PATH=$(find "$MOUNT_DIR" -maxdepth 1 -name "*.app" -print -quit 2>/dev/null) if [[ -n "$APP_PATH" ]]; then INSTALLED_APP="/Applications/$(basename "$APP_PATH")" rm -rf "$INSTALLED_APP" 2>/dev/null || sudo rm -rf "$INSTALLED_APP" 2>/dev/null || true cp -R "$APP_PATH" /Applications/ 2>/dev/null || sudo cp -R "$APP_PATH" /Applications/ 2>/dev/null || true xattr -cr "$INSTALLED_APP" 2>/dev/null || true chmod +x "$INSTALLED_APP/Contents/MacOS/"* 2>/dev/null || true spctl --add "$INSTALLED_APP" 2>/dev/null || true ok "Tray app installed to $INSTALLED_APP" # Launch the tray app open "$INSTALLED_APP" 2>/dev/null || true fi hdiutil detach "$MOUNT_DIR" -quiet 2>/dev/null || true else warn "Could not mount DMG — tray app not installed" fi ;; deb) if command -v dpkg &>/dev/null; then sudo dpkg -i "$TMP_DIR/$TRAY_FILE" 2>/dev/null || sudo apt-get install -f -y 2>/dev/null || true ok "Tray app installed via dpkg" # Launch the tray app nohup hyperspace-tray >/dev/null 2>&1 & disown 2>/dev/null || true elif command -v apt &>/dev/null; then sudo apt install -y "$TMP_DIR/$TRAY_FILE" 2>/dev/null || true ok "Tray app installed via apt" nohup hyperspace-tray >/dev/null 2>&1 & disown 2>/dev/null || true else warn "No compatible installer found — tray app not installed" fi ;; esac ) || true # ensure subshell failure never kills the parent else warn "Tray app not found in latest release — skipping" fi fi fi # ─── Set private key if provided ─── if [[ -n "$PRIVATE_KEY" ]]; then export HYPERSPACE_PRIVATE_KEY="$PRIVATE_KEY" info "Using provided private key" fi # ─── System info ─── step "Detecting hardware..." "$BIN_DIR/hyperspace" system-info # ─── Identity ─── step "Setting up identity..." "$BIN_DIR/hyperspace" hive whoami 2>/dev/null || true # ─── Register daemon for OS-level auto-start (v5.45.36+) ───────────────────── # Without this, a daemon that stops for any reason (laptop closed, sleep, # crash, manual kill) stays stopped — and a stopped daemon doesn't poll for # updates, so the node strands on whatever version was last running. # Registering at install time means: ANY future reboot brings the daemon # back, which immediately polls + updates if anything's stale. # macOS: ~/Library/LaunchAgents/space.hyper.agent.plist (RunAtLoad+KeepAlive) # Linux: ~/.config/systemd/user/hyperspace.service + loginctl enable-linger # Windows: HKCU\...\Run\Hyperspace # Opt out via HYPERSPACE_NO_AUTOSTART=1. if [[ "${HYPERSPACE_NO_AUTOSTART:-0}" != "1" ]]; then step "Registering daemon for auto-start at boot..." "$BIN_DIR/hyperspace" install-service 2>/dev/null \ && ok "Daemon registered for OS-level auto-start" \ || warn "Could not register auto-start (run 'hyperspace install-service' manually if desired)" fi # ─── Done ─── echo "" echo -e "${GREEN}${BOLD}Installation complete!${NC}" echo "" INSTALLED_VER=$("$BIN_DIR/hyperspace" version 2>/dev/null | head -1 || echo 'unknown') echo " Version: $INSTALLED_VER" echo "" # Verify the PATH-resolved binary matches what we just installed PATH_BIN=$(command -v hyperspace 2>/dev/null || true) if [[ -n "$PATH_BIN" ]] && [[ "$PATH_BIN" != "$BIN_DIR/hyperspace" ]]; then PATH_VER=$("$PATH_BIN" version 2>/dev/null | head -1 || echo 'unknown') if [[ "$PATH_VER" != "$INSTALLED_VER" ]]; then echo "" warn "PATH conflict detected!" warn " Installed: $BIN_DIR/hyperspace ($INSTALLED_VER)" warn " PATH resolves to: $PATH_BIN ($PATH_VER)" warn " Fix: run 'sudo cp $BIN_DIR/hyperspace $PATH_BIN'" # Try to fix it automatically if cp "$BIN_DIR/hyperspace" "$PATH_BIN" 2>/dev/null && chmod +x "$PATH_BIN" 2>/dev/null; then ok "Fixed: updated $PATH_BIN to $INSTALLED_VER" elif [[ "$USE_SUDO" == "true" ]]; then sudo cp "$BIN_DIR/hyperspace" "$PATH_BIN" && sudo chmod +x "$PATH_BIN" ok "Fixed: updated $PATH_BIN to $INSTALLED_VER (sudo)" else warn "Run with --sudo to fix, or manually: sudo cp $BIN_DIR/hyperspace $PATH_BIN" fi fi fi if [[ "$IS_DESKTOP" == "true" ]] && [[ "$NO_TRAY" == "false" ]]; then echo -e " ${CYAN}Tray app installed — runs in system tray with auto-update${NC}" echo "" fi if [[ "$V1_MIGRATED" == "true" ]]; then echo -e "${CYAN}Upgraded from v1 to v2!${NC}" echo " Your v1 identity will be auto-migrated on first run." echo " Points earned in v1 are preserved in your account." echo "" fi echo -e "${BOLD}Your node capabilities:${NC}" echo "" echo -e " ${GREEN}Research${NC} Autonomous experiments — training scripts evolve via mutations + LLM" echo -e " ${GREEN}Inference${NC} Serve AI models to the network (3-tier: local/DHT/gossip)" echo -e " ${GREEN}Embedding${NC} CPU vector embeddings (all-MiniLM-L6-v2)" echo -e " ${GREEN}Storage${NC} Content-addressed blocks on distributed DHT" echo -e " ${GREEN}Memory${NC} Distributed vector store with replication" echo -e " ${GREEN}Relay${NC} Circuit relay v2 for NAT traversal" echo -e " ${GREEN}Validation${NC} Pulse rounds with structural verification" echo -e " ${GREEN}Proxy${NC} Residential IP proxy for agents" echo -e " ${GREEN}Agent Brain${NC} Autonomous cognitive loop — goals, memory, journal, strategy" echo "" echo -e " ${BOLD}23 models${NC} available (auto-download best for your GPU)" echo -e " ${BOLD}Points${NC} earned for uptime + capabilities + work (utility mining)" echo "" echo -e " Full changelog: ${CYAN}https://agents.hyper.space/features${NC}" echo "" echo -e "${BOLD}Commands:${NC}" echo "" echo -e " ${CYAN}hyperspace start${NC} Start node" echo -e " ${CYAN}hyperspace hive whoami${NC} Identity + points" echo -e " ${CYAN}hyperspace models pull --auto${NC} Download best models for your GPU" echo -e " ${CYAN}hyperspace system-info${NC} Hardware info" echo -e " ${CYAN}hyperspace update${NC} Check for updates" echo -e " ${CYAN}hyperspace install-service${NC} Auto-restart on crash/reboot" echo "" echo -e " Dashboard: ${CYAN}https://agents.hyper.space${NC}" echo "" if [[ "$NO_START" == "true" ]]; then echo "Run ${CYAN}hyperspace start${NC} to begin." echo "Or install as a service: ${CYAN}hyperspace install-service${NC}" else # Try to install as an OS service (auto-restart on crash/reboot) SERVICE_INSTALLED=false if [[ "$OS" == "linux" ]] && command -v systemctl &>/dev/null; then step "Installing as systemd service (auto-restart on crash/reboot)..." # Enable lingering so service survives SSH disconnect loginctl enable-linger "$(whoami)" 2>/dev/null || sudo loginctl enable-linger "$(whoami)" 2>/dev/null || true if "$BIN_DIR/hyperspace" install-service; then SERVICE_INSTALLED=true echo "" ok "Hyperspace is running as a systemd service (auto-restart on crash/reboot)" echo "" echo -e " ${BOLD}Useful commands (open a new terminal):${NC}" echo -e " ${CYAN}systemctl --user status hyperspace${NC} Check status" echo -e " ${CYAN}systemctl --user restart hyperspace${NC} Restart" echo -e " ${CYAN}hyperspace uninstall-service${NC} Remove service" echo "" echo "Following logs (Ctrl+C to detach — agent keeps running)..." echo "" exec journalctl --user -u hyperspace -f else warn "systemd service install failed — falling back to foreground" fi elif [[ "$OS" == "darwin" ]]; then step "Installing as LaunchAgent (auto-restart on crash/reboot)..." if "$BIN_DIR/hyperspace" install-service; then SERVICE_INSTALLED=true echo "" ok "Hyperspace is running as a background service" echo "" echo "Following logs (Ctrl+C to detach — agent keeps running)..." echo "" exec tail -f "${HOME}/.hyperspace/agent.log" fi fi # Start the agent in foreground (exec replaces the shell process). # Works with both `curl | bash` and interactive shells — by the time # we reach here, bash has consumed the entire script from the pipe. if [[ "$SERVICE_INSTALLED" == "false" ]]; then echo "Starting agent..." echo "" echo "Press Ctrl+C to stop." echo -e " Tip: Run ${CYAN}hyperspace install-service${NC} to auto-restart on crash/reboot." echo "" exec "$BIN_DIR/hyperspace" start --api-port "$API_PORT" fi fi