Created
April 27, 2026 16:33
-
-
Save as3k/935d39622e37356ef49f93887cf00d42 to your computer and use it in GitHub Desktop.
alpine hermes agent
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/sh | |
| # ============================================================================ | |
| # Hermes Agent Installer — Alpine Linux | |
| # ============================================================================ | |
| # Alpine uses musl libc, busybox sh, and apk. Adapted accordingly. | |
| # Usage: | |
| # wget -qO- https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | sh | |
| # Or: sh install.sh [--no-venv] [--skip-setup] [--branch NAME] [--dir PATH] | |
| # ============================================================================ | |
| set -e | |
| # Colors | |
| RED='\033[0;31m' | |
| GREEN='\033[0;32m' | |
| YELLOW='\033[0;33m' | |
| CYAN='\033[0;36m' | |
| MAGENTA='\033[0;35m' | |
| NC='\033[0m' | |
| BOLD='\033[1m' | |
| # Configuration | |
| REPO_URL_SSH="git@github.com:NousResearch/hermes-agent.git" | |
| REPO_URL_HTTPS="https://github.com/NousResearch/hermes-agent.git" | |
| HERMES_HOME="$HOME/.hermes" | |
| INSTALL_DIR="${HERMES_INSTALL_DIR:-$HERMES_HOME/hermes-agent}" | |
| PYTHON_VERSION="3.11" | |
| NODE_VERSION="22" | |
| # Options | |
| USE_VENV=true | |
| RUN_SETUP=true | |
| BRANCH="main" | |
| # Parse arguments (busybox sh compatible — no [[ ]]) | |
| while [ $# -gt 0 ]; do | |
| case "$1" in | |
| --no-venv) USE_VENV=false; shift ;; | |
| --skip-setup) RUN_SETUP=false; shift ;; | |
| --branch) BRANCH="$2"; shift 2 ;; | |
| --dir) INSTALL_DIR="$2"; shift 2 ;; | |
| -h|--help) | |
| printf "Hermes Agent Installer\n\nUsage: install.sh [OPTIONS]\n\n" | |
| printf " --no-venv Don't create virtual environment\n" | |
| printf " --skip-setup Skip interactive setup wizard\n" | |
| printf " --branch NAME Git branch to install (default: main)\n" | |
| printf " --dir PATH Installation directory (default: ~/.hermes/hermes-agent)\n" | |
| exit 0 | |
| ;; | |
| *) printf "Unknown option: %s\n" "$1"; exit 1 ;; | |
| esac | |
| done | |
| # ============================================================================ | |
| # Helpers | |
| # ============================================================================ | |
| print_banner() { | |
| printf "\n${MAGENTA}${BOLD}" | |
| printf "┌─────────────────────────────────────────────────────────┐\n" | |
| printf "│ ⚕ Hermes Agent Installer (Alpine) │\n" | |
| printf "├─────────────────────────────────────────────────────────┤\n" | |
| printf "│ An open source AI agent by Nous Research. │\n" | |
| printf "└─────────────────────────────────────────────────────────┘\n" | |
| printf "${NC}\n" | |
| } | |
| log_info() { printf "${CYAN}→${NC} %s\n" "$1"; } | |
| log_success() { printf "${GREEN}✓${NC} %s\n" "$1"; } | |
| log_warn() { printf "${YELLOW}⚠${NC} %s\n" "$1"; } | |
| log_error() { printf "${RED}✗${NC} %s\n" "$1"; } | |
| # ============================================================================ | |
| # System detection | |
| # ============================================================================ | |
| detect_os() { | |
| OS="linux" | |
| DISTRO="alpine" | |
| if [ -f /etc/os-release ]; then | |
| # shellcheck disable=SC1091 | |
| . /etc/os-release | |
| DISTRO="${ID:-alpine}" | |
| fi | |
| log_success "Detected: $OS ($DISTRO)" | |
| if [ "$DISTRO" != "alpine" ]; then | |
| log_warn "This script is optimised for Alpine. Proceeding anyway — some steps may fail." | |
| fi | |
| } | |
| # ============================================================================ | |
| # Dependency checks | |
| # ============================================================================ | |
| require_apk_pkg() { | |
| PKG="$1" | |
| if ! command -v "$2" > /dev/null 2>&1; then | |
| log_info "Installing $PKG via apk..." | |
| if [ "$(id -u)" -eq 0 ]; then | |
| apk add --no-cache "$PKG" | |
| elif command -v sudo > /dev/null 2>&1; then | |
| sudo apk add --no-cache "$PKG" | |
| else | |
| log_error "Cannot install $PKG — run as root or install sudo." | |
| exit 1 | |
| fi | |
| fi | |
| } | |
| install_uv() { | |
| log_info "Checking for uv package manager..." | |
| UV_CMD="" | |
| for candidate in uv "$HOME/.local/bin/uv" "$HOME/.cargo/bin/uv"; do | |
| if [ -x "$candidate" ] || command -v "$candidate" > /dev/null 2>&1; then | |
| UV_CMD="$candidate" | |
| break | |
| fi | |
| done | |
| if [ -n "$UV_CMD" ]; then | |
| UV_VERSION=$("$UV_CMD" --version 2>/dev/null) | |
| log_success "uv found ($UV_VERSION)" | |
| return 0 | |
| fi | |
| # uv's installer needs curl and sh; Alpine needs libgcc for the uv binary | |
| log_info "Installing uv (fast Python package manager)..." | |
| require_apk_pkg curl curl | |
| # uv on musl needs libgcc (it ships a glibc binary; use musl-compatible static build via installer) | |
| apk add --no-cache libgcc 2>/dev/null || true | |
| if curl -LsSf https://astral.sh/uv/install.sh | sh; then | |
| for candidate in "$HOME/.local/bin/uv" "$HOME/.cargo/bin/uv" uv; do | |
| if [ -x "$candidate" ] || command -v "$candidate" > /dev/null 2>&1; then | |
| UV_CMD="$candidate" | |
| break | |
| fi | |
| done | |
| if [ -z "$UV_CMD" ]; then | |
| log_error "uv installed but not found on PATH. Add ~/.local/bin to PATH and retry." | |
| exit 1 | |
| fi | |
| UV_VERSION=$("$UV_CMD" --version 2>/dev/null) | |
| log_success "uv installed ($UV_VERSION)" | |
| else | |
| log_error "Failed to install uv." | |
| log_info "Install manually: https://docs.astral.sh/uv/getting-started/installation/" | |
| exit 1 | |
| fi | |
| } | |
| check_python() { | |
| log_info "Checking Python $PYTHON_VERSION..." | |
| if "$UV_CMD" python find "$PYTHON_VERSION" > /dev/null 2>&1; then | |
| PYTHON_PATH=$("$UV_CMD" python find "$PYTHON_VERSION") | |
| PYTHON_FOUND_VERSION=$("$PYTHON_PATH" --version 2>/dev/null) | |
| log_success "Python found: $PYTHON_FOUND_VERSION" | |
| return 0 | |
| fi | |
| # uv can download a standalone Python build (musl-compatible for Alpine) | |
| log_info "Python $PYTHON_VERSION not found, installing via uv..." | |
| if "$UV_CMD" python install "$PYTHON_VERSION"; then | |
| PYTHON_PATH=$("$UV_CMD" python find "$PYTHON_VERSION") | |
| PYTHON_FOUND_VERSION=$("$PYTHON_PATH" --version 2>/dev/null) | |
| log_success "Python installed: $PYTHON_FOUND_VERSION" | |
| else | |
| # Fallback: system python3 from apk | |
| log_warn "uv python install failed, trying apk python3..." | |
| require_apk_pkg python3 python3 | |
| apk add --no-cache py3-pip 2>/dev/null || true | |
| PYTHON_PATH="$(command -v python3)" | |
| log_success "Using system Python: $("$PYTHON_PATH" --version 2>/dev/null)" | |
| fi | |
| } | |
| check_git() { | |
| log_info "Checking Git..." | |
| if command -v git > /dev/null 2>&1; then | |
| log_success "Git $(git --version | awk '{print $3}') found" | |
| return 0 | |
| fi | |
| log_info "Git not found — installing via apk..." | |
| require_apk_pkg git git | |
| log_success "Git installed" | |
| } | |
| check_node() { | |
| log_info "Checking Node.js (for browser tools)..." | |
| HAS_NODE=false | |
| if command -v node > /dev/null 2>&1; then | |
| log_success "Node.js $(node --version) found" | |
| HAS_NODE=true | |
| return 0 | |
| fi | |
| if [ -x "$HERMES_HOME/node/bin/node" ]; then | |
| export PATH="$HERMES_HOME/node/bin:$PATH" | |
| log_success "Node.js $("$HERMES_HOME/node/bin/node" --version) found (Hermes-managed)" | |
| HAS_NODE=true | |
| return 0 | |
| fi | |
| log_info "Node.js not found — attempting install..." | |
| install_node | |
| } | |
| install_node() { | |
| # Prefer apk on Alpine — much simpler and musl-native | |
| if apk info nodejs > /dev/null 2>&1 || apk add --no-cache nodejs npm 2>/dev/null; then | |
| log_success "Node.js $(node --version) installed via apk" | |
| HAS_NODE=true | |
| return 0 | |
| fi | |
| # Fallback: official tarball (Alpine needs the musl build, which Node doesn't ship; | |
| # the glibc binary works if gcompat is installed) | |
| ARCH=$(uname -m) | |
| case "$ARCH" in | |
| x86_64) NODE_ARCH="x64" ;; | |
| aarch64|arm64) NODE_ARCH="arm64" ;; | |
| *) log_warn "Unsupported arch ($ARCH) for Node tarball install"; HAS_NODE=false; return 0 ;; | |
| esac | |
| log_info "Installing gcompat for glibc compatibility..." | |
| apk add --no-cache gcompat 2>/dev/null || true | |
| INDEX_URL="https://nodejs.org/dist/latest-v${NODE_VERSION}.x/" | |
| TARBALL=$(wget -qO- "$INDEX_URL" \ | |
| | grep -oE "node-v${NODE_VERSION}\.[0-9]+\.[0-9]+-linux-${NODE_ARCH}\.tar\.xz" \ | |
| | head -1) | |
| if [ -z "$TARBALL" ]; then | |
| log_warn "Could not resolve Node.js tarball. Install manually: https://nodejs.org/en/download/" | |
| HAS_NODE=false | |
| return 0 | |
| fi | |
| TMP=$(mktemp -d) | |
| log_info "Downloading $TARBALL..." | |
| wget -qO "$TMP/$TARBALL" "${INDEX_URL}${TARBALL}" || { log_warn "Download failed"; HAS_NODE=false; rm -rf "$TMP"; return 0; } | |
| tar xf "$TMP/$TARBALL" -C "$TMP" | |
| EXTRACTED=$(find "$TMP" -maxdepth 1 -name "node-v*" -type d | head -1) | |
| [ -d "$EXTRACTED" ] || { log_warn "Extraction failed"; HAS_NODE=false; rm -rf "$TMP"; return 0; } | |
| rm -rf "$HERMES_HOME/node" | |
| mv "$EXTRACTED" "$HERMES_HOME/node" | |
| rm -rf "$TMP" | |
| mkdir -p "$HOME/.local/bin" | |
| ln -sf "$HERMES_HOME/node/bin/node" "$HOME/.local/bin/node" | |
| ln -sf "$HERMES_HOME/node/bin/npm" "$HOME/.local/bin/npm" | |
| ln -sf "$HERMES_HOME/node/bin/npx" "$HOME/.local/bin/npx" | |
| export PATH="$HERMES_HOME/node/bin:$PATH" | |
| log_success "Node.js $("$HERMES_HOME/node/bin/node" --version) installed to ~/.hermes/node/" | |
| HAS_NODE=true | |
| } | |
| install_system_packages() { | |
| HAS_RIPGREP=false | |
| HAS_FFMPEG=false | |
| log_info "Checking ripgrep..." | |
| if command -v rg > /dev/null 2>&1; then | |
| log_success "$(rg --version | head -1) found" | |
| HAS_RIPGREP=true | |
| else | |
| if apk add --no-cache ripgrep 2>/dev/null; then | |
| log_success "ripgrep installed" | |
| HAS_RIPGREP=true | |
| else | |
| log_warn "ripgrep not available — file search will fall back to grep" | |
| fi | |
| fi | |
| log_info "Checking ffmpeg..." | |
| if command -v ffmpeg > /dev/null 2>&1; then | |
| log_success "ffmpeg $(ffmpeg -version 2>/dev/null | head -1 | awk '{print $3}') found" | |
| HAS_FFMPEG=true | |
| else | |
| if apk add --no-cache ffmpeg 2>/dev/null; then | |
| log_success "ffmpeg installed" | |
| HAS_FFMPEG=true | |
| else | |
| log_warn "ffmpeg not available — TTS voice messages will be limited" | |
| fi | |
| fi | |
| } | |
| # ============================================================================ | |
| # Installation | |
| # ============================================================================ | |
| clone_repo() { | |
| log_info "Installing to $INSTALL_DIR..." | |
| if [ -d "$INSTALL_DIR" ]; then | |
| if [ -d "$INSTALL_DIR/.git" ]; then | |
| log_info "Existing install found, updating..." | |
| cd "$INSTALL_DIR" | |
| git fetch origin | |
| git checkout "$BRANCH" | |
| git pull origin "$BRANCH" | |
| else | |
| log_error "Directory exists but is not a git repo: $INSTALL_DIR" | |
| log_info "Remove it or use --dir to choose another path." | |
| exit 1 | |
| fi | |
| else | |
| log_info "Trying SSH clone..." | |
| if git clone --branch "$BRANCH" --recurse-submodules "$REPO_URL_SSH" "$INSTALL_DIR" 2>/dev/null; then | |
| log_success "Cloned via SSH" | |
| else | |
| log_info "SSH failed, trying HTTPS..." | |
| if git clone --branch "$BRANCH" --recurse-submodules "$REPO_URL_HTTPS" "$INSTALL_DIR"; then | |
| log_success "Cloned via HTTPS" | |
| else | |
| log_error "Failed to clone repository." | |
| exit 1 | |
| fi | |
| fi | |
| fi | |
| cd "$INSTALL_DIR" | |
| log_info "Initializing submodules..." | |
| git submodule update --init --recursive | |
| log_success "Repository ready" | |
| } | |
| setup_venv() { | |
| if [ "$USE_VENV" = "false" ]; then | |
| log_info "Skipping virtual environment (--no-venv)" | |
| return 0 | |
| fi | |
| log_info "Creating virtual environment with Python $PYTHON_VERSION..." | |
| [ -d "venv" ] && rm -rf venv | |
| "$UV_CMD" venv venv --python "$PYTHON_VERSION" | |
| log_success "Virtual environment ready" | |
| } | |
| install_deps() { | |
| log_info "Installing dependencies..." | |
| if [ "$USE_VENV" = "true" ]; then | |
| export VIRTUAL_ENV="$INSTALL_DIR/venv" | |
| fi | |
| "$UV_CMD" pip install -e ".[all]" 2>/dev/null || "$UV_CMD" pip install -e "." | |
| log_success "Main package installed" | |
| log_info "Installing mini-swe-agent..." | |
| if [ -f "mini-swe-agent/pyproject.toml" ]; then | |
| "$UV_CMD" pip install -e "./mini-swe-agent" || log_warn "mini-swe-agent install failed" | |
| log_success "mini-swe-agent installed" | |
| else | |
| log_warn "mini-swe-agent not found (run: git submodule update --init)" | |
| fi | |
| log_info "Installing tinker-atropos..." | |
| if [ -f "tinker-atropos/pyproject.toml" ]; then | |
| "$UV_CMD" pip install -e "./tinker-atropos" || log_warn "tinker-atropos install failed" | |
| log_success "tinker-atropos installed" | |
| else | |
| log_warn "tinker-atropos not found (run: git submodule update --init)" | |
| fi | |
| log_success "All dependencies installed" | |
| } | |
| setup_path() { | |
| log_info "Setting up hermes command..." | |
| if [ "$USE_VENV" = "true" ]; then | |
| HERMES_BIN="$INSTALL_DIR/venv/bin/hermes" | |
| else | |
| HERMES_BIN="$(command -v hermes 2>/dev/null || true)" | |
| if [ -z "$HERMES_BIN" ]; then | |
| log_warn "hermes not found on PATH after install" | |
| return 0 | |
| fi | |
| fi | |
| mkdir -p "$HOME/.local/bin" | |
| ln -sf "$HERMES_BIN" "$HOME/.local/bin/hermes" | |
| log_success "Symlinked hermes → ~/.local/bin/hermes" | |
| PATH_LINE='export PATH="$HOME/.local/bin:$PATH"' | |
| # Detect shell config — Alpine default shell is ash/sh but users may have bash/zsh | |
| SHELL_CONFIG="" | |
| if [ -f "$HOME/.bashrc" ]; then | |
| SHELL_CONFIG="$HOME/.bashrc" | |
| elif [ -f "$HOME/.bash_profile" ]; then | |
| SHELL_CONFIG="$HOME/.bash_profile" | |
| elif [ -f "$HOME/.zshrc" ]; then | |
| SHELL_CONFIG="$HOME/.zshrc" | |
| elif [ -f "$HOME/.profile" ]; then | |
| SHELL_CONFIG="$HOME/.profile" | |
| fi | |
| if ! printf '%s' "$PATH" | tr ':' '\n' | grep -qx "$HOME/.local/bin"; then | |
| if [ -n "$SHELL_CONFIG" ]; then | |
| if ! grep -q '\.local/bin' "$SHELL_CONFIG" 2>/dev/null; then | |
| printf '\n# Hermes Agent — ensure ~/.local/bin is on PATH\n%s\n' "$PATH_LINE" >> "$SHELL_CONFIG" | |
| log_success "Added ~/.local/bin to PATH in $SHELL_CONFIG" | |
| else | |
| log_info "~/.local/bin already referenced in $SHELL_CONFIG" | |
| fi | |
| fi | |
| else | |
| log_info "~/.local/bin already on PATH" | |
| fi | |
| export PATH="$HOME/.local/bin:$PATH" | |
| log_success "hermes command ready" | |
| } | |
| copy_config_templates() { | |
| log_info "Setting up configuration files..." | |
| mkdir -p "$HERMES_HOME/cron" "$HERMES_HOME/sessions" "$HERMES_HOME/logs" \ | |
| "$HERMES_HOME/pairing" "$HERMES_HOME/hooks" "$HERMES_HOME/image_cache" \ | |
| "$HERMES_HOME/audio_cache" "$HERMES_HOME/memories" "$HERMES_HOME/skills" | |
| if [ ! -f "$HERMES_HOME/.env" ]; then | |
| if [ -f "$INSTALL_DIR/.env.example" ]; then | |
| cp "$INSTALL_DIR/.env.example" "$HERMES_HOME/.env" | |
| else | |
| touch "$HERMES_HOME/.env" | |
| fi | |
| log_success "Created ~/.hermes/.env" | |
| else | |
| log_info "~/.hermes/.env already exists, keeping it" | |
| fi | |
| if [ ! -f "$HERMES_HOME/config.yaml" ] && [ -f "$INSTALL_DIR/cli-config.yaml.example" ]; then | |
| cp "$INSTALL_DIR/cli-config.yaml.example" "$HERMES_HOME/config.yaml" | |
| log_success "Created ~/.hermes/config.yaml" | |
| fi | |
| if [ ! -f "$HERMES_HOME/SOUL.md" ]; then | |
| cat > "$HERMES_HOME/SOUL.md" << 'SOUL_EOF' | |
| # Hermes Agent Persona | |
| <!-- | |
| Edit this to customise how Hermes communicates with you. | |
| Loaded fresh each message — no restart needed. | |
| --> | |
| SOUL_EOF | |
| log_success "Created ~/.hermes/SOUL.md" | |
| fi | |
| log_info "Syncing bundled skills..." | |
| if "$INSTALL_DIR/venv/bin/python" "$INSTALL_DIR/tools/skills_sync.py" 2>/dev/null; then | |
| log_success "Skills synced" | |
| elif [ -d "$INSTALL_DIR/skills" ]; then | |
| cp -r "$INSTALL_DIR/skills/"* "$HERMES_HOME/skills/" 2>/dev/null || true | |
| log_success "Skills copied" | |
| fi | |
| } | |
| install_node_deps() { | |
| if [ "$HAS_NODE" = "false" ]; then | |
| log_info "Skipping Node.js dependencies (Node not installed)" | |
| return 0 | |
| fi | |
| if [ -f "$INSTALL_DIR/package.json" ]; then | |
| log_info "Installing Node.js dependencies..." | |
| cd "$INSTALL_DIR" | |
| npm install --silent 2>/dev/null || log_warn "npm install failed (browser tools may not work)" | |
| log_success "Node.js dependencies installed" | |
| fi | |
| } | |
| run_setup_wizard() { | |
| if [ "$RUN_SETUP" = "false" ]; then | |
| log_info "Skipping setup wizard (--skip-setup)" | |
| return 0 | |
| fi | |
| printf "\n" | |
| log_info "Starting setup wizard..." | |
| printf "\n" | |
| cd "$INSTALL_DIR" | |
| if [ "$USE_VENV" = "true" ]; then | |
| "$INSTALL_DIR/venv/bin/python" -m hermes_cli.main setup | |
| else | |
| python3 -m hermes_cli.main setup | |
| fi | |
| } | |
| maybe_start_gateway() { | |
| ENV_FILE="$HERMES_HOME/.env" | |
| [ -f "$ENV_FILE" ] || return 0 | |
| HAS_MESSAGING=false | |
| for VAR in TELEGRAM_BOT_TOKEN DISCORD_BOT_TOKEN SLACK_BOT_TOKEN SLACK_APP_TOKEN WHATSAPP_ENABLED; do | |
| VAL=$(grep "^${VAR}=" "$ENV_FILE" 2>/dev/null | cut -d'=' -f2-) | |
| if [ -n "$VAL" ] && [ "$VAL" != "your-token-here" ]; then | |
| HAS_MESSAGING=true | |
| break | |
| fi | |
| done | |
| [ "$HAS_MESSAGING" = "true" ] || return 0 | |
| printf "\n" | |
| log_info "Messaging platform token detected!" | |
| log_info "The gateway must be running for Hermes to send/receive messages." | |
| printf "\n" | |
| printf "Install gateway as a background service? [Y/n] " | |
| read -r REPLY | |
| case "$REPLY" in | |
| ""|y|Y) | |
| HERMES_CMD="$HOME/.local/bin/hermes" | |
| command -v "$HERMES_CMD" > /dev/null 2>&1 || HERMES_CMD="hermes" | |
| # Alpine uses OpenRC, not systemd | |
| if command -v rc-service > /dev/null 2>&1 || command -v openrc > /dev/null 2>&1; then | |
| log_info "OpenRC detected — installing service..." | |
| if "$HERMES_CMD" gateway install 2>/dev/null; then | |
| log_success "Gateway service installed" | |
| "$HERMES_CMD" gateway start 2>/dev/null && log_success "Gateway started!" \ | |
| || log_warn "Service installed but failed to start. Try: hermes gateway start" | |
| else | |
| log_warn "OpenRC install failed. Starting gateway in background..." | |
| nohup "$HERMES_CMD" gateway > "$HERMES_HOME/logs/gateway.log" 2>&1 & | |
| log_success "Gateway started (PID $!). Logs: ~/.hermes/logs/gateway.log" | |
| fi | |
| elif command -v systemctl > /dev/null 2>&1; then | |
| # Some Alpine setups run systemd inside containers | |
| log_info "systemd detected — installing service..." | |
| if "$HERMES_CMD" gateway install 2>/dev/null; then | |
| log_success "Gateway service installed" | |
| "$HERMES_CMD" gateway start 2>/dev/null && log_success "Gateway started!" \ | |
| || log_warn "Try: hermes gateway start" | |
| else | |
| log_warn "systemd install failed. Starting in background..." | |
| nohup "$HERMES_CMD" gateway > "$HERMES_HOME/logs/gateway.log" 2>&1 & | |
| log_success "Gateway started (PID $!)" | |
| fi | |
| else | |
| log_info "No init system detected — starting gateway in background..." | |
| nohup "$HERMES_CMD" gateway > "$HERMES_HOME/logs/gateway.log" 2>&1 & | |
| log_success "Gateway started (PID $!). Logs: ~/.hermes/logs/gateway.log" | |
| log_info "To stop: kill $!" | |
| log_info "To restart: hermes gateway" | |
| fi | |
| ;; | |
| *) | |
| log_info "Skipped. Start later with: hermes gateway" | |
| ;; | |
| esac | |
| } | |
| print_success() { | |
| printf "\n${GREEN}${BOLD}" | |
| printf "┌─────────────────────────────────────────────────────────┐\n" | |
| printf "│ ✓ Installation Complete! │\n" | |
| printf "└─────────────────────────────────────────────────────────┘\n" | |
| printf "${NC}\n\n" | |
| printf "${CYAN}${BOLD}📁 Your files (all in ~/.hermes/):${NC}\n\n" | |
| printf " ${YELLOW}Config:${NC} ~/.hermes/config.yaml\n" | |
| printf " ${YELLOW}API Keys:${NC} ~/.hermes/.env\n" | |
| printf " ${YELLOW}Data:${NC} ~/.hermes/cron/, sessions/, logs/\n" | |
| printf " ${YELLOW}Code:${NC} ~/.hermes/hermes-agent/\n\n" | |
| printf "${CYAN}${BOLD}🚀 Commands:${NC}\n\n" | |
| printf " ${GREEN}hermes${NC} Start chatting\n" | |
| printf " ${GREEN}hermes setup${NC} Configure API keys & settings\n" | |
| printf " ${GREEN}hermes config${NC} View/edit configuration\n" | |
| printf " ${GREEN}hermes gateway install${NC} Install gateway service\n" | |
| printf " ${GREEN}hermes update${NC} Update to latest version\n\n" | |
| printf "${YELLOW}⚡ Reload your shell to use 'hermes':${NC}\n\n" | |
| printf " source ~/.profile # or ~/.bashrc / ~/.zshrc\n\n" | |
| [ "$HAS_NODE" = "false" ] && printf "${YELLOW}Note: Node.js not installed. Browser tools need it: https://nodejs.org/en/download/${NC}\n\n" | |
| [ "$HAS_RIPGREP" = "false" ] && printf "${YELLOW}Note: ripgrep not found. File search will use grep. Install: apk add ripgrep${NC}\n\n" | |
| } | |
| # ============================================================================ | |
| # Main | |
| # ============================================================================ | |
| main() { | |
| print_banner | |
| detect_os | |
| install_uv | |
| check_python | |
| check_git | |
| check_node | |
| install_system_packages | |
| clone_repo | |
| setup_venv | |
| install_deps | |
| install_node_deps | |
| setup_path | |
| copy_config_templates | |
| run_setup_wizard | |
| maybe_start_gateway | |
| print_success | |
| } | |
| main |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment