#!/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 # Try to install to /usr/local/bin/ too (uses sudo only with --sudo flag) 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 [[ "$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 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 ─── if [[ "$IS_DESKTOP" == "true" ]] && [[ "$NO_TRAY" == "false" ]]; then step "Installing Hyperspace Tray App..." # 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 # ─── 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