#!/bin/bash # ============================================================================ # Hermes Agent Setup Script # ============================================================================ # Quick setup for developers who cloned the repo manually. # Uses uv for desktop/server setup and Python's stdlib venv + pip on Termux. # # Usage: # ./setup-hermes.sh # # This script: # 1. Detects desktop/server vs Android/Termux setup path # 2. Creates a Python 3.11 virtual environment # 3. Installs the appropriate dependency set for the platform # 4. Creates .env from template (if not exists) # 5. Symlinks the 'hermes' CLI command into a user-facing bin dir # 6. Runs the setup wizard (optional) # ============================================================================ set -e # Colors GREEN='\033[0;32m' YELLOW='\033[0;33m' CYAN='\033[0;36m' RED='\033[0;31m' NC='\033[0m' SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$SCRIPT_DIR" PYTHON_VERSION="3.11" is_termux() { [ -n "${TERMUX_VERSION:-}" ] || [[ "${PREFIX:-}" == *"com.termux/files/usr"* ]] } get_command_link_dir() { if is_termux && [ -n "${PREFIX:-}" ]; then echo "$PREFIX/bin" else echo "$HOME/.local/bin" fi } get_command_link_display_dir() { if is_termux && [ -n "${PREFIX:-}" ]; then echo '$PREFIX/bin' else echo '~/.local/bin' fi } echo "" echo -e "${CYAN}⚕ Hermes Agent Setup${NC}" echo "" # ============================================================================ # Install / locate uv # ============================================================================ echo -e "${CYAN}→${NC} Checking for uv..." UV_CMD="" if is_termux; then echo -e "${CYAN}→${NC} Termux detected — using Python's stdlib venv + pip instead of uv" else if command -v uv &> /dev/null; then UV_CMD="uv" elif [ -x "$HOME/.local/bin/uv" ]; then UV_CMD="$HOME/.local/bin/uv" elif [ -x "$HOME/.cargo/bin/uv" ]; then UV_CMD="$HOME/.cargo/bin/uv" fi if [ -n "$UV_CMD" ]; then UV_VERSION=$($UV_CMD --version 2>/dev/null) echo -e "${GREEN}✓${NC} uv found ($UV_VERSION)" else echo -e "${CYAN}→${NC} Installing uv..." if curl -LsSf https://astral.sh/uv/install.sh | sh 2>/dev/null; then if [ -x "$HOME/.local/bin/uv" ]; then UV_CMD="$HOME/.local/bin/uv" elif [ -x "$HOME/.cargo/bin/uv" ]; then UV_CMD="$HOME/.cargo/bin/uv" fi if [ -n "$UV_CMD" ]; then UV_VERSION=$($UV_CMD --version 2>/dev/null) echo -e "${GREEN}✓${NC} uv installed ($UV_VERSION)" else echo -e "${RED}✗${NC} uv installed but not found. Add ~/.local/bin to PATH and retry." exit 1 fi else echo -e "${RED}✗${NC} Failed to install uv. Visit https://docs.astral.sh/uv/" exit 1 fi fi fi # ============================================================================ # Python check (uv can provision it automatically) # ============================================================================ echo -e "${CYAN}→${NC} Checking Python $PYTHON_VERSION..." if is_termux; then if command -v python >/dev/null 2>&1; then PYTHON_PATH="$(command -v python)" if "$PYTHON_PATH" -c 'import sys; raise SystemExit(0 if sys.version_info >= (3, 11) else 1)' 2>/dev/null; then PYTHON_FOUND_VERSION=$($PYTHON_PATH --version 2>/dev/null) echo -e "${GREEN}✓${NC} $PYTHON_FOUND_VERSION found" else echo -e "${RED}✗${NC} Termux Python must be 3.11+" echo " Run: pkg install python" exit 1 fi else echo -e "${RED}✗${NC} Python not found in Termux" echo " Run: pkg install python" exit 1 fi else if $UV_CMD python find "$PYTHON_VERSION" &> /dev/null; then PYTHON_PATH=$($UV_CMD python find "$PYTHON_VERSION") PYTHON_FOUND_VERSION=$($PYTHON_PATH --version 2>/dev/null) echo -e "${GREEN}✓${NC} $PYTHON_FOUND_VERSION found" else echo -e "${CYAN}→${NC} Python $PYTHON_VERSION not found, installing via uv..." $UV_CMD python install "$PYTHON_VERSION" PYTHON_PATH=$($UV_CMD python find "$PYTHON_VERSION") PYTHON_FOUND_VERSION=$($PYTHON_PATH --version 2>/dev/null) echo -e "${GREEN}✓${NC} $PYTHON_FOUND_VERSION installed" fi fi # ============================================================================ # Virtual environment # ============================================================================ echo -e "${CYAN}→${NC} Setting up virtual environment..." if [ -d "venv" ]; then echo -e "${CYAN}→${NC} Removing old venv..." rm -rf venv fi if is_termux; then "$PYTHON_PATH" -m venv venv echo -e "${GREEN}✓${NC} venv created with stdlib venv" else $UV_CMD venv venv --python "$PYTHON_VERSION" echo -e "${GREEN}✓${NC} venv created (Python $PYTHON_VERSION)" fi export VIRTUAL_ENV="$SCRIPT_DIR/venv" SETUP_PYTHON="$SCRIPT_DIR/venv/bin/python" # ============================================================================ # Dependencies # ============================================================================ echo -e "${CYAN}→${NC} Installing dependencies..." if is_termux; then export ANDROID_API_LEVEL="$(getprop ro.build.version.sdk 2>/dev/null || printf '%s' "${ANDROID_API_LEVEL:-}")" echo -e "${CYAN}→${NC} Termux detected — installing the tested Android bundle" "$SETUP_PYTHON" -m pip install --upgrade pip setuptools wheel if [ -f "constraints-termux.txt" ]; then "$SETUP_PYTHON" -m pip install -e ".[termux]" -c constraints-termux.txt || { echo -e "${YELLOW}⚠${NC} Termux bundle install failed, falling back to base install..." "$SETUP_PYTHON" -m pip install -e "." -c constraints-termux.txt } else "$SETUP_PYTHON" -m pip install -e ".[termux]" || "$SETUP_PYTHON" -m pip install -e "." fi echo -e "${GREEN}✓${NC} Dependencies installed" else # Prefer uv sync with lockfile (hash-verified installs) when available, # fall back to pip install for compatibility or when lockfile is stale. if [ -f "uv.lock" ]; then echo -e "${CYAN}→${NC} Using uv.lock for hash-verified installation..." UV_PROJECT_ENVIRONMENT="$SCRIPT_DIR/venv" $UV_CMD sync --all-extras --locked 2>/dev/null && \ echo -e "${GREEN}✓${NC} Dependencies installed (lockfile verified)" || { echo -e "${YELLOW}⚠${NC} Lockfile install failed (may be outdated), falling back to pip install..." $UV_CMD pip install -e ".[all]" || $UV_CMD pip install -e "." echo -e "${GREEN}✓${NC} Dependencies installed" } else $UV_CMD pip install -e ".[all]" || $UV_CMD pip install -e "." echo -e "${GREEN}✓${NC} Dependencies installed" fi fi # ============================================================================ # Submodules (terminal backend + RL training) # ============================================================================ echo -e "${CYAN}→${NC} Installing optional submodules..." # tinker-atropos (RL training backend) if is_termux; then echo -e "${CYAN}→${NC} Skipping tinker-atropos on Termux (not part of the tested Android path)" elif [ -d "tinker-atropos" ] && [ -f "tinker-atropos/pyproject.toml" ]; then $UV_CMD pip install -e "./tinker-atropos" && \ echo -e "${GREEN}✓${NC} tinker-atropos installed" || \ echo -e "${YELLOW}⚠${NC} tinker-atropos install failed (RL tools may not work)" else echo -e "${YELLOW}⚠${NC} tinker-atropos not found (run: git submodule update --init --recursive)" fi # ============================================================================ # Optional: ripgrep (for faster file search) # ============================================================================ echo -e "${CYAN}→${NC} Checking ripgrep (optional, for faster search)..." if command -v rg &> /dev/null; then echo -e "${GREEN}✓${NC} ripgrep found" else echo -e "${YELLOW}⚠${NC} ripgrep not found (file search will use grep fallback)" read -p "Install ripgrep for faster search? [Y/n] " -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]] || [[ -z $REPLY ]]; then INSTALLED=false if is_termux; then pkg install -y ripgrep && INSTALLED=true else # Check if sudo is available if command -v sudo &> /dev/null && sudo -n true 2>/dev/null; then if command -v apt &> /dev/null; then sudo apt install -y ripgrep && INSTALLED=true elif command -v dnf &> /dev/null; then sudo dnf install -y ripgrep && INSTALLED=true fi fi # Try brew (no sudo needed) if [ "$INSTALLED" = false ] && command -v brew &> /dev/null; then brew install ripgrep && INSTALLED=true fi # Try cargo (no sudo needed) if [ "$INSTALLED" = false ] && command -v cargo &> /dev/null; then echo -e "${CYAN}→${NC} Trying cargo install (no sudo required)..." cargo install ripgrep && INSTALLED=true fi fi if [ "$INSTALLED" = true ]; then echo -e "${GREEN}✓${NC} ripgrep installed" else echo -e "${YELLOW}⚠${NC} Auto-install failed. Install options:" if is_termux; then echo " pkg install ripgrep # Termux / Android" else echo " sudo apt install ripgrep # Debian/Ubuntu" echo " brew install ripgrep # macOS" echo " cargo install ripgrep # With Rust (no sudo)" fi echo " https://github.com/BurntSushi/ripgrep#installation" fi fi fi # ============================================================================ # Environment file # ============================================================================ if [ ! -f ".env" ]; then if [ -f ".env.example" ]; then cp .env.example .env echo -e "${GREEN}✓${NC} Created .env from template" fi else echo -e "${GREEN}✓${NC} .env exists" fi # ============================================================================ # PATH setup — symlink hermes into a user-facing bin dir # ============================================================================ echo -e "${CYAN}→${NC} Setting up hermes command..." HERMES_BIN="$SCRIPT_DIR/venv/bin/hermes" COMMAND_LINK_DIR="$(get_command_link_dir)" COMMAND_LINK_DISPLAY_DIR="$(get_command_link_display_dir)" mkdir -p "$COMMAND_LINK_DIR" ln -sf "$HERMES_BIN" "$COMMAND_LINK_DIR/hermes" echo -e "${GREEN}✓${NC} Symlinked hermes → $COMMAND_LINK_DISPLAY_DIR/hermes" if is_termux; then export PATH="$COMMAND_LINK_DIR:$PATH" echo -e "${GREEN}✓${NC} $COMMAND_LINK_DISPLAY_DIR is already on PATH in Termux" else # Determine the appropriate shell config file SHELL_CONFIG="" if [[ "$SHELL" == *"zsh"* ]]; then SHELL_CONFIG="$HOME/.zshrc" elif [[ "$SHELL" == *"bash"* ]]; then SHELL_CONFIG="$HOME/.bashrc" [ ! -f "$SHELL_CONFIG" ] && SHELL_CONFIG="$HOME/.bash_profile" else # Fallback to checking existing files if [ -f "$HOME/.zshrc" ]; then SHELL_CONFIG="$HOME/.zshrc" elif [ -f "$HOME/.bashrc" ]; then SHELL_CONFIG="$HOME/.bashrc" elif [ -f "$HOME/.bash_profile" ]; then SHELL_CONFIG="$HOME/.bash_profile" fi fi if [ -n "$SHELL_CONFIG" ]; then # Touch the file just in case it doesn't exist yet but was selected touch "$SHELL_CONFIG" 2>/dev/null || true if ! echo "$PATH" | tr ':' '\n' | grep -q "^$HOME/.local/bin$"; then if ! grep -q '\.local/bin' "$SHELL_CONFIG" 2>/dev/null; then echo "" >> "$SHELL_CONFIG" echo "# Hermes Agent — ensure ~/.local/bin is on PATH" >> "$SHELL_CONFIG" echo 'export PATH="$HOME/.local/bin:$PATH"' >> "$SHELL_CONFIG" echo -e "${GREEN}✓${NC} Added ~/.local/bin to PATH in $SHELL_CONFIG" else echo -e "${GREEN}✓${NC} ~/.local/bin already in $SHELL_CONFIG" fi else echo -e "${GREEN}✓${NC} ~/.local/bin already on PATH" fi fi fi # ============================================================================ # Seed bundled skills into ~/.hermes/skills/ # ============================================================================ HERMES_SKILLS_DIR="${HERMES_HOME:-$HOME/.hermes}/skills" mkdir -p "$HERMES_SKILLS_DIR" echo "" echo "Syncing bundled skills to ~/.hermes/skills/ ..." if "$SCRIPT_DIR/venv/bin/python" "$SCRIPT_DIR/tools/skills_sync.py" 2>/dev/null; then echo -e "${GREEN}✓${NC} Skills synced" else # Fallback: copy if sync script fails (missing deps, etc.) if [ -d "$SCRIPT_DIR/skills" ]; then cp -rn "$SCRIPT_DIR/skills/"* "$HERMES_SKILLS_DIR/" 2>/dev/null || true echo -e "${GREEN}✓${NC} Skills copied" fi fi # ============================================================================ # Done # ============================================================================ echo "" echo -e "${GREEN}✓ Setup complete!${NC}" echo "" echo "Next steps:" echo "" if is_termux; then echo " 1. Run the setup wizard to configure API keys:" echo " hermes setup" echo "" echo " 2. Start chatting:" echo " hermes" echo "" else echo " 1. Reload your shell:" echo " source $SHELL_CONFIG" echo "" echo " 2. Run the setup wizard to configure API keys:" echo " hermes setup" echo "" echo " 3. Start chatting:" echo " hermes" echo "" fi echo "Other commands:" echo " hermes status # Check configuration" if is_termux; then echo " hermes gateway # Run gateway in foreground" else echo " hermes gateway install # Install gateway service (messaging + cron)" fi echo " hermes cron list # View scheduled jobs" echo " hermes doctor # Diagnose issues" echo "" # Ask if they want to run setup wizard now read -p "Would you like to run the setup wizard now? [Y/n] " -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]] || [[ -z $REPLY ]]; then echo "" # Run directly with venv Python (no activation needed) "$SCRIPT_DIR/venv/bin/python" -m hermes_cli.main setup fi