- Regenerate uv.lock with sha256 hashes for all 2965 package artifacts - Add python_version marker to yc-bench (requires >=3.12) - Update setup-hermes.sh to prefer 'uv sync --locked' for hash-verified installs, with fallback to 'uv pip install' when lockfile is stale This completes the supply chain hardening: pyproject.toml bounds the version ranges, and uv.lock pins exact versions with cryptographic hashes so tampered packages are rejected at install time.
308 lines
11 KiB
Bash
Executable File
308 lines
11 KiB
Bash
Executable File
#!/bin/bash
|
|
# ============================================================================
|
|
# Hermes Agent Setup Script
|
|
# ============================================================================
|
|
# Quick setup for developers who cloned the repo manually.
|
|
# Uses uv for fast Python provisioning and package management.
|
|
#
|
|
# Usage:
|
|
# ./setup-hermes.sh
|
|
#
|
|
# This script:
|
|
# 1. Installs uv if not present
|
|
# 2. Creates a virtual environment with Python 3.11 via uv
|
|
# 3. Installs all dependencies (main package + submodules)
|
|
# 4. Creates .env from template (if not exists)
|
|
# 5. Symlinks the 'hermes' CLI command into ~/.local/bin
|
|
# 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"
|
|
|
|
echo ""
|
|
echo -e "${CYAN}⚕ Hermes Agent Setup${NC}"
|
|
echo ""
|
|
|
|
# ============================================================================
|
|
# Install / locate uv
|
|
# ============================================================================
|
|
|
|
echo -e "${CYAN}→${NC} Checking for uv..."
|
|
|
|
UV_CMD=""
|
|
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
|
|
|
|
# ============================================================================
|
|
# Python check (uv can provision it automatically)
|
|
# ============================================================================
|
|
|
|
echo -e "${CYAN}→${NC} Checking Python $PYTHON_VERSION..."
|
|
|
|
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
|
|
|
|
# ============================================================================
|
|
# 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
|
|
|
|
$UV_CMD venv venv --python "$PYTHON_VERSION"
|
|
echo -e "${GREEN}✓${NC} venv created (Python $PYTHON_VERSION)"
|
|
|
|
# Tell uv to install into this venv (no activation needed for uv)
|
|
export VIRTUAL_ENV="$SCRIPT_DIR/venv"
|
|
|
|
# ============================================================================
|
|
# Dependencies
|
|
# ============================================================================
|
|
|
|
echo -e "${CYAN}→${NC} Installing dependencies..."
|
|
|
|
# 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
|
|
|
|
# ============================================================================
|
|
# Submodules (terminal backend + RL training)
|
|
# ============================================================================
|
|
|
|
echo -e "${CYAN}→${NC} Installing optional submodules..."
|
|
|
|
# tinker-atropos (RL training backend)
|
|
if [ -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
|
|
|
|
# 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
|
|
|
|
if [ "$INSTALLED" = true ]; then
|
|
echo -e "${GREEN}✓${NC} ripgrep installed"
|
|
else
|
|
echo -e "${YELLOW}⚠${NC} Auto-install failed. Install options:"
|
|
echo " sudo apt install ripgrep # Debian/Ubuntu"
|
|
echo " brew install ripgrep # macOS"
|
|
echo " cargo install ripgrep # With Rust (no sudo)"
|
|
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 ~/.local/bin
|
|
# ============================================================================
|
|
|
|
echo -e "${CYAN}→${NC} Setting up hermes command..."
|
|
|
|
HERMES_BIN="$SCRIPT_DIR/venv/bin/hermes"
|
|
mkdir -p "$HOME/.local/bin"
|
|
ln -sf "$HERMES_BIN" "$HOME/.local/bin/hermes"
|
|
echo -e "${GREEN}✓${NC} Symlinked hermes → ~/.local/bin/hermes"
|
|
|
|
# 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
|
|
|
|
# ============================================================================
|
|
# 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 ""
|
|
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 ""
|
|
echo "Other commands:"
|
|
echo " hermes status # Check configuration"
|
|
echo " hermes gateway install # Install gateway service (messaging + cron)"
|
|
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
|