From a8809bbd3e4ba9671ce524525ae8a7ad10df1870 Mon Sep 17 00:00:00 2001 From: teknium Date: Sat, 7 Feb 2026 23:54:53 +0000 Subject: [PATCH] Transition installation to uv for py version and speed to be easier to streamline - Integrated `uv` as a fast Python package manager for automatic Python provisioning and dependency management. - Updated installation scripts (`setup-hermes.sh`, `install.sh`, `install.ps1`) to utilize `uv` for installing Python and packages, streamlining the setup process. - Revised `README.md` to reflect changes in installation steps, including symlinking `hermes` for global access and clarifying Python version requirements. - Adjusted commands in `doctor.py` and other scripts to recommend `uv` for package installations, ensuring consistency across the project. --- README.md | 124 ++++++++++----------- hermes_cli/doctor.py | 10 +- hermes_cli/main.py | 19 +++- hermes_cli/setup.py | 40 +++++-- scripts/install.ps1 | 220 ++++++++++++++++++++++--------------- scripts/install.sh | 253 +++++++++++++++++++++++-------------------- setup-hermes.sh | 143 +++++++++++++++--------- 7 files changed, 471 insertions(+), 338 deletions(-) diff --git a/README.md b/README.md index 45340e8f..18b016c4 100644 --- a/README.md +++ b/README.md @@ -15,11 +15,13 @@ irm https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/ins ``` The installer will: +- Install [uv](https://docs.astral.sh/uv/) (fast Python package manager) if not present +- Install Python 3.11 via uv if not already available (no sudo needed) - Clone to `~/.hermes/hermes-agent` (with submodules: mini-swe-agent, tinker-atropos) -- Create a virtual environment (Python 3.11+ recommended) +- Create a virtual environment with Python 3.11 - Install all dependencies and submodule packages +- Symlink `hermes` into `~/.local/bin` so it works globally (no venv activation needed) - Run the interactive setup wizard -- Add `hermes` to your PATH After installation, reload your shell and run: ```bash @@ -179,7 +181,7 @@ hermes config set terminal.singularity_image ~/python.sif **Modal** (serverless cloud): ```bash -pip install "swe-rex[modal]" # Installs swe-rex + modal + boto3 +uv pip install "swe-rex[modal]" # Installs swe-rex + modal + boto3 modal setup # Authenticate with Modal hermes config set terminal.backend modal ``` @@ -522,26 +524,25 @@ If you prefer full control over the installation process (or the quick-install s | Requirement | Minimum Version | Check Command | Notes | |-------------|----------------|---------------|-------| -| **Python** | 3.11+ recommended (3.10 minimum) | `python3 --version` | Required. 3.11+ needed for RL training tools | | **Git** | Any recent | `git --version` | Required | -| **pip** | 21+ | `pip --version` | Comes with Python | | **Node.js** | 18+ | `node --version` | Optional — needed for browser automation tools | | **ripgrep** | Any | `rg --version` | Optional — faster file search in terminal tool (falls back to grep) | +> **Note:** Python and pip are **not** prerequisites. The installer uses [uv](https://docs.astral.sh/uv/) to provision Python 3.11 automatically (no sudo needed). If you already have Python 3.11+ installed, uv will use it. +
Installing prerequisites by platform **Ubuntu / Debian:** ```bash -sudo apt update -sudo apt install python3.11 python3.11-venv python3-pip git +sudo apt update && sudo apt install git # Optional: sudo apt install ripgrep nodejs npm ``` **macOS (Homebrew):** ```bash -brew install python@3.11 git +brew install git # Optional: brew install ripgrep node ``` @@ -569,34 +570,37 @@ git submodule update --init --recursive --- -### Step 2: Create & Activate a Virtual Environment +### Step 2: Install uv & Create Virtual Environment -A virtual environment keeps Hermes dependencies isolated from your system Python: +[uv](https://docs.astral.sh/uv/) is a fast Python package manager that can also provision Python itself. Install it and create the venv in one go: ```bash -python3 -m venv venv -source venv/bin/activate +# Install uv (if not already installed) +curl -LsSf https://astral.sh/uv/install.sh | sh -# Upgrade core packaging tools -pip install --upgrade pip wheel setuptools +# Create venv with Python 3.11 (uv downloads it if not present — no sudo needed) +uv venv venv --python 3.11 ``` -> **Tip:** Every time you open a new terminal to use Hermes, activate the venv first: -> `source /path/to/hermes-agent/venv/bin/activate` +> **Tip:** You do **not** need to activate the venv to use `hermes`. The entry point has a hardcoded shebang pointing to the venv Python, so it works globally once symlinked (see Step 8). For installing packages, uv can target the venv directly via `VIRTUAL_ENV`. --- ### Step 3: Install Python Dependencies -Install the main package in editable mode with all optional extras (messaging, cron, CLI menus): +Install the main package in editable mode with all optional extras (messaging, cron, CLI menus, modal): ```bash -pip install -e ".[all]" +# Tell uv which venv to install into +export VIRTUAL_ENV="$(pwd)/venv" + +# Install with all extras +uv pip install -e ".[all]" ``` If you only want the core agent (no Telegram/Discord/cron support): ```bash -pip install -e "." +uv pip install -e "." ```
@@ -604,14 +608,14 @@ pip install -e "." | Extra | What it adds | Install command | |-------|-------------|-----------------| -| `all` | Everything below | `pip install -e ".[all]"` | -| `messaging` | Telegram & Discord gateway | `pip install -e ".[messaging]"` | -| `cron` | Cron expression parsing for scheduled tasks | `pip install -e ".[cron]"` | -| `cli` | Terminal menu UI for setup wizard | `pip install -e ".[cli]"` | -| `modal` | Modal cloud execution backend (swe-rex + modal + boto3) | `pip install -e ".[modal]"` | -| `dev` | pytest & test utilities | `pip install -e ".[dev]"` | +| `all` | Everything below | `uv pip install -e ".[all]"` | +| `messaging` | Telegram & Discord gateway | `uv pip install -e ".[messaging]"` | +| `cron` | Cron expression parsing for scheduled tasks | `uv pip install -e ".[cron]"` | +| `cli` | Terminal menu UI for setup wizard | `uv pip install -e ".[cli]"` | +| `modal` | Modal cloud execution backend (swe-rex + modal + boto3) | `uv pip install -e ".[modal]"` | +| `dev` | pytest & test utilities | `uv pip install -e ".[dev]"` | -You can combine extras: `pip install -e ".[messaging,cron]"` +You can combine extras: `uv pip install -e ".[messaging,cron]"`
@@ -623,16 +627,14 @@ These are local packages checked out as Git submodules. Install them in editable ```bash # Terminal tool backend (required for the terminal/command-execution tool) -pip install -e "./mini-swe-agent" +uv pip install -e "./mini-swe-agent" -# RL training backend (requires Python 3.11+) -pip install -e "./tinker-atropos" +# RL training backend +uv pip install -e "./tinker-atropos" ``` Both are optional — if you skip them, the corresponding toolsets simply won't be available. -> **Note:** `tinker-atropos` requires Python 3.11+ (the upstream `tinker` package has this constraint). On Python 3.10, skip this line — RL tools will be disabled but everything else works. - --- ### Step 5: Install Node.js Dependencies (Optional) @@ -706,13 +708,20 @@ hermes config set OPENROUTER_API_KEY sk-or-v1-your-key-here ### Step 8: Add `hermes` to Your PATH -The `hermes` command is installed into the virtual environment's `bin/` directory. Add it to your shell PATH so you can run `hermes` from anywhere: +The `hermes` entry point at `venv/bin/hermes` has a hardcoded shebang pointing to the venv's Python, so it works **without activating the venv**. The recommended approach is a symlink into `~/.local/bin` (most distributions already have this on PATH): + +```bash +mkdir -p ~/.local/bin +ln -sf "$(pwd)/venv/bin/hermes" ~/.local/bin/hermes +``` + +If `~/.local/bin` isn't on your PATH yet, add it: **Bash** (`~/.bashrc`): ```bash echo '' >> ~/.bashrc echo '# Hermes Agent' >> ~/.bashrc -echo 'export PATH="$HOME/hermes-agent/venv/bin:$PATH"' >> ~/.bashrc +echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc source ~/.bashrc ``` @@ -720,24 +729,15 @@ source ~/.bashrc ```bash echo '' >> ~/.zshrc echo '# Hermes Agent' >> ~/.zshrc -echo 'export PATH="$HOME/hermes-agent/venv/bin:$PATH"' >> ~/.zshrc +echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc source ~/.zshrc ``` **Fish** (`~/.config/fish/config.fish`): ```fish -fish_add_path $HOME/hermes-agent/venv/bin +fish_add_path $HOME/.local/bin ``` -> **Note:** Adjust the path if you cloned to a different location. The key is to add the `venv/bin` directory inside your clone to your PATH. - -Alternatively, if you don't want to modify your PATH, you can create a symlink: -```bash -mkdir -p ~/.local/bin -ln -sf "$(pwd)/venv/bin/hermes" ~/.local/bin/hermes -``` -(Most distributions already have `~/.local/bin` on the PATH.) - --- ### Step 9: Run the Setup Wizard (Optional) @@ -777,19 +777,21 @@ If `hermes doctor` reports issues, it will tell you exactly what's missing and h For those who just want the commands without the explanations: ```bash +# Install uv (if not already installed) +curl -LsSf https://astral.sh/uv/install.sh | sh + # Clone & enter git clone --recurse-submodules https://github.com/NousResearch/hermes-agent.git cd hermes-agent -# Virtual environment -python3 -m venv venv -source venv/bin/activate -pip install --upgrade pip wheel setuptools +# Create venv with Python 3.11 (uv downloads it if needed) +uv venv venv --python 3.11 +export VIRTUAL_ENV="$(pwd)/venv" # Install everything -pip install -e ".[all]" -pip install -e "./mini-swe-agent" -pip install -e "./tinker-atropos" +uv pip install -e ".[all]" +uv pip install -e "./mini-swe-agent" +uv pip install -e "./tinker-atropos" npm install # optional, for browser tools # Configure @@ -798,9 +800,9 @@ cp cli-config.yaml.example ~/.hermes/config.yaml touch ~/.hermes/.env echo 'OPENROUTER_API_KEY=sk-or-v1-your-key' >> ~/.hermes/.env -# Add to PATH (adjust for your shell) -echo 'export PATH="'$(pwd)'/venv/bin:$PATH"' >> ~/.bashrc -source ~/.bashrc +# Make hermes available globally (no venv activation needed) +mkdir -p ~/.local/bin +ln -sf "$(pwd)/venv/bin/hermes" ~/.local/bin/hermes # Verify hermes doctor @@ -815,16 +817,16 @@ To update an existing manual install to the latest version: ```bash cd /path/to/hermes-agent -source venv/bin/activate +export VIRTUAL_ENV="$(pwd)/venv" # Pull latest code and submodules git pull origin main git submodule update --init --recursive # Reinstall (picks up new dependencies) -pip install -e ".[all]" -pip install -e "./mini-swe-agent" -pip install -e "./tinker-atropos" +uv pip install -e ".[all]" +uv pip install -e "./mini-swe-agent" +uv pip install -e "./tinker-atropos" # Check for new config options added since your last update hermes config check @@ -834,14 +836,14 @@ hermes config migrate # Interactively add any missing options ### Uninstalling a Manual Installation ```bash +# Remove the hermes symlink +rm -f ~/.local/bin/hermes + # Remove the cloned repository rm -rf /path/to/hermes-agent # Remove user configuration (optional — keep if you plan to reinstall) rm -rf ~/.hermes - -# Remove the PATH line from your shell config (~/.bashrc or ~/.zshrc) -# Look for the "# Hermes Agent" comment and remove that block ``` --- diff --git a/hermes_cli/doctor.py b/hermes_cli/doctor.py index 7c770cf8..de9b721e 100644 --- a/hermes_cli/doctor.py +++ b/hermes_cli/doctor.py @@ -103,7 +103,7 @@ def run_doctor(args): check_ok(name) except ImportError: check_fail(name, "(missing)") - issues.append(f"Install {name}: pip install {module}") + issues.append(f"Install {name}: uv pip install {module}") for module, name in optional_packages: try: @@ -279,8 +279,8 @@ def run_doctor(args): __import__("minisweagent") check_ok("mini-swe-agent", "(terminal backend)") except ImportError: - check_warn("mini-swe-agent found but not installed", "(run: pip install -e ./mini-swe-agent)") - issues.append("Install mini-swe-agent: pip install -e ./mini-swe-agent") + check_warn("mini-swe-agent found but not installed", "(run: uv pip install -e ./mini-swe-agent)") + issues.append("Install mini-swe-agent: uv pip install -e ./mini-swe-agent") else: check_warn("mini-swe-agent not found", "(run: git submodule update --init --recursive)") @@ -292,8 +292,8 @@ def run_doctor(args): __import__("tinker_atropos") check_ok("tinker-atropos", "(RL training backend)") except ImportError: - check_warn("tinker-atropos found but not installed", "(run: pip install -e ./tinker-atropos)") - issues.append("Install tinker-atropos: pip install -e ./tinker-atropos") + check_warn("tinker-atropos found but not installed", "(run: uv pip install -e ./tinker-atropos)") + issues.append("Install tinker-atropos: uv pip install -e ./tinker-atropos") else: check_warn("tinker-atropos requires Python 3.11+", f"(current: {py_version.major}.{py_version.minor})") else: diff --git a/hermes_cli/main.py b/hermes_cli/main.py index a3100279..a4c4f644 100644 --- a/hermes_cli/main.py +++ b/hermes_cli/main.py @@ -119,6 +119,7 @@ def cmd_uninstall(args): def cmd_update(args): """Update Hermes Agent to the latest version.""" import subprocess + import shutil print("🦋 Updating Hermes Agent...") print() @@ -163,13 +164,21 @@ def cmd_update(args): print("→ Pulling updates...") subprocess.run(["git", "pull", "origin", branch], cwd=PROJECT_ROOT, check=True) - # Reinstall Python dependencies + # Reinstall Python dependencies (prefer uv for speed, fall back to pip) print("→ Updating Python dependencies...") - venv_pip = PROJECT_ROOT / "venv" / "bin" / "pip" - if venv_pip.exists(): - subprocess.run([str(venv_pip), "install", "-e", ".", "--quiet"], cwd=PROJECT_ROOT, check=True) + uv_bin = shutil.which("uv") + if uv_bin: + subprocess.run( + [uv_bin, "pip", "install", "-e", ".", "--quiet"], + cwd=PROJECT_ROOT, check=True, + env={**os.environ, "VIRTUAL_ENV": str(PROJECT_ROOT / "venv")} + ) else: - subprocess.run(["pip", "install", "-e", ".", "--quiet"], cwd=PROJECT_ROOT, check=True) + venv_pip = PROJECT_ROOT / "venv" / "bin" / "pip" + if venv_pip.exists(): + subprocess.run([str(venv_pip), "install", "-e", ".", "--quiet"], cwd=PROJECT_ROOT, check=True) + else: + subprocess.run(["pip", "install", "-e", ".", "--quiet"], cwd=PROJECT_ROOT, check=True) # Check for Node.js deps if (PROJECT_ROOT / "package.json").exists(): diff --git a/hermes_cli/setup.py b/hermes_cli/setup.py index 5f9f045a..75e019d9 100644 --- a/hermes_cli/setup.py +++ b/hermes_cli/setup.py @@ -659,15 +659,24 @@ def run_setup_wizard(args): except ImportError: print_info("Installing required package: swe-rex[modal]...") import subprocess - result = subprocess.run( - [sys.executable, "-m", "pip", "install", "swe-rex[modal]>=1.4.0"], - capture_output=True, text=True - ) + import shutil + # Prefer uv for speed, fall back to pip + uv_bin = shutil.which("uv") + if uv_bin: + result = subprocess.run( + [uv_bin, "pip", "install", "swe-rex[modal]>=1.4.0"], + capture_output=True, text=True + ) + else: + result = subprocess.run( + [sys.executable, "-m", "pip", "install", "swe-rex[modal]>=1.4.0"], + capture_output=True, text=True + ) if result.returncode == 0: print_success("swe-rex[modal] installed (includes modal + boto3)") else: print_warning("Failed to install swe-rex[modal] — install manually:") - print_info(' pip install "swe-rex[modal]>=1.4.0"') + print_info(' uv pip install "swe-rex[modal]>=1.4.0"') # Always show current status and allow reconfiguration current_token = get_env_value('MODAL_TOKEN_ID') @@ -1031,19 +1040,28 @@ def run_setup_wizard(args): if tinker_dir.exists() and (tinker_dir / "pyproject.toml").exists(): print_info(" Installing tinker-atropos submodule...") import subprocess - result = subprocess.run( - [sys.executable, "-m", "pip", "install", "-e", str(tinker_dir)], - capture_output=True, text=True - ) + import shutil + # Prefer uv for speed, fall back to pip + uv_bin = shutil.which("uv") + if uv_bin: + result = subprocess.run( + [uv_bin, "pip", "install", "-e", str(tinker_dir)], + capture_output=True, text=True + ) + else: + result = subprocess.run( + [sys.executable, "-m", "pip", "install", "-e", str(tinker_dir)], + capture_output=True, text=True + ) if result.returncode == 0: print_success(" tinker-atropos installed") else: print_warning(" tinker-atropos install failed — run manually:") - print_info(' pip install -e "./tinker-atropos"') + print_info(' uv pip install -e "./tinker-atropos"') else: print_warning(" tinker-atropos submodule not found — run:") print_info(" git submodule update --init --recursive") - print_info(' pip install -e "./tinker-atropos"') + print_info(' uv pip install -e "./tinker-atropos"') if api_key and wandb_key: print_success(" Configured ✓") diff --git a/scripts/install.ps1 b/scripts/install.ps1 index 86b914d3..25a4159e 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -2,6 +2,7 @@ # Hermes Agent Installer for Windows # ============================================================================ # Installation script for Windows (PowerShell). +# Uses uv for fast Python provisioning and package management. # # Usage: # irm https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.ps1 | iex @@ -27,6 +28,7 @@ $ErrorActionPreference = "Stop" $RepoUrlSsh = "git@github.com:NousResearch/hermes-agent.git" $RepoUrlHttps = "https://github.com/NousResearch/hermes-agent.git" +$PythonVersion = "3.11" # ============================================================================ # Helper functions @@ -52,12 +54,12 @@ function Write-Success { Write-Host "✓ $Message" -ForegroundColor Green } -function Write-Warning { +function Write-Warn { param([string]$Message) Write-Host "⚠ $Message" -ForegroundColor Yellow } -function Write-Error { +function Write-Err { param([string]$Message) Write-Host "✗ $Message" -ForegroundColor Red } @@ -66,41 +68,93 @@ function Write-Error { # Dependency checks # ============================================================================ -function Test-Python { - Write-Info "Checking Python..." +function Install-Uv { + Write-Info "Checking for uv package manager..." - # Try different python commands (prefer 3.11+ for full feature support) - $pythonCmds = @("python3", "python", "py -3") + # Check if uv is already available + if (Get-Command uv -ErrorAction SilentlyContinue) { + $version = uv --version + $script:UvCmd = "uv" + Write-Success "uv found ($version)" + return $true + } - foreach ($cmd in $pythonCmds) { - try { - $version = & $cmd.Split()[0] $cmd.Split()[1..99] -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')" 2>$null - if ($version) { - $major, $minor = $version.Split('.') - if ([int]$major -ge 3 -and [int]$minor -ge 10) { - $script:PythonCmd = $cmd - $script:PythonVersion = $version - Write-Success "Python $version found" - - # Warn if < 3.11 (RL training tools require 3.11+) - if ([int]$minor -lt 11) { - Write-Warning "Python 3.11+ recommended — RL Training tools (tinker-atropos) require >= 3.11" - Write-Info "Core agent features will work fine on $version" - } - - return $true - } - } - } catch { - # Try next command + # Check common install locations + $uvPaths = @( + "$env:USERPROFILE\.local\bin\uv.exe", + "$env:USERPROFILE\.cargo\bin\uv.exe" + ) + foreach ($uvPath in $uvPaths) { + if (Test-Path $uvPath) { + $script:UvCmd = $uvPath + $version = & $uvPath --version + Write-Success "uv found at $uvPath ($version)" + return $true } } - Write-Error "Python 3.10+ not found" - Write-Info "Please install Python 3.11 or newer (recommended) from:" - Write-Info " https://www.python.org/downloads/" - Write-Info "" - Write-Info "Make sure to check 'Add Python to PATH' during installation" + # Install uv + Write-Info "Installing uv (fast Python package manager)..." + try { + powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" 2>&1 | Out-Null + + # Find the installed binary + $uvExe = "$env:USERPROFILE\.local\bin\uv.exe" + if (-not (Test-Path $uvExe)) { + $uvExe = "$env:USERPROFILE\.cargo\bin\uv.exe" + } + if (-not (Test-Path $uvExe)) { + # Refresh PATH and try again + $env:Path = [Environment]::GetEnvironmentVariable("Path", "User") + ";" + [Environment]::GetEnvironmentVariable("Path", "Machine") + if (Get-Command uv -ErrorAction SilentlyContinue) { + $uvExe = (Get-Command uv).Source + } + } + + if (Test-Path $uvExe) { + $script:UvCmd = $uvExe + $version = & $uvExe --version + Write-Success "uv installed ($version)" + return $true + } + + Write-Err "uv installed but not found on PATH" + Write-Info "Try restarting your terminal and re-running" + return $false + } catch { + Write-Err "Failed to install uv" + Write-Info "Install manually: https://docs.astral.sh/uv/getting-started/installation/" + return $false + } +} + +function Test-Python { + Write-Info "Checking Python $PythonVersion..." + + # Let uv find or install Python + try { + $pythonPath = & $UvCmd python find $PythonVersion 2>$null + if ($pythonPath) { + $ver = & $pythonPath --version 2>$null + Write-Success "Python found: $ver" + return $true + } + } catch { } + + # Python not found — use uv to install it (no admin needed!) + Write-Info "Python $PythonVersion not found, installing via uv..." + try { + & $UvCmd python install $PythonVersion 2>&1 | Out-Null + $pythonPath = & $UvCmd python find $PythonVersion 2>$null + if ($pythonPath) { + $ver = & $pythonPath --version 2>$null + Write-Success "Python installed: $ver" + return $true + } + } catch { } + + Write-Err "Failed to install Python $PythonVersion" + Write-Info "Install Python $PythonVersion manually, then re-run this script" return $false } @@ -113,7 +167,7 @@ function Test-Git { return $true } - Write-Error "Git not found" + Write-Err "Git not found" Write-Info "Please install Git from:" Write-Info " https://git-scm.com/download/win" return $false @@ -129,7 +183,7 @@ function Test-Node { return $true } - Write-Warning "Node.js not found (browser tools will be limited)" + Write-Warn "Node.js not found (browser tools will be limited)" Write-Info "To install Node.js (optional):" Write-Info " https://nodejs.org/en/download/" $script:HasNode = $false @@ -146,7 +200,7 @@ function Test-Ripgrep { return $true } - Write-Warning "ripgrep not found (file search will use findstr fallback)" + Write-Warn "ripgrep not found (file search will use findstr fallback)" # Check what package managers are available $hasWinget = Get-Command winget -ErrorAction SilentlyContinue @@ -193,7 +247,7 @@ function Test-Ripgrep { } catch { } } - Write-Warning "Auto-install failed. You can install manually:" + Write-Warn "Auto-install failed. You can install manually:" } else { Write-Info "Skipping ripgrep installation. To install manually:" } @@ -224,13 +278,12 @@ function Install-Repository { git pull origin $Branch Pop-Location } else { - Write-Error "Directory exists but is not a git repository: $InstallDir" + Write-Err "Directory exists but is not a git repository: $InstallDir" Write-Info "Remove it or choose a different directory with -InstallDir" exit 1 } } else { # Try SSH first (for private repo access), fall back to HTTPS - # Use --recurse-submodules to also clone mini-swe-agent and tinker-atropos Write-Info "Trying SSH clone..." $sshResult = git clone --branch $Branch --recurse-submodules $RepoUrlSsh $InstallDir 2>&1 @@ -243,7 +296,7 @@ function Install-Repository { if ($LASTEXITCODE -eq 0) { Write-Success "Cloned via HTTPS" } else { - Write-Error "Failed to clone repository" + Write-Err "Failed to clone repository" Write-Info "For private repo access, ensure your SSH key is added to GitHub:" Write-Info " ssh-add ~/.ssh/id_rsa" Write-Info " ssh -T git@github.com # Test connection" @@ -252,7 +305,7 @@ function Install-Repository { } } - # Ensure submodules are initialized and updated (for existing installs or if --recurse failed) + # Ensure submodules are initialized and updated Write-Info "Initializing submodules (mini-swe-agent, tinker-atropos)..." Push-Location $InstallDir git submodule update --init --recursive @@ -268,23 +321,21 @@ function Install-Venv { return } - Write-Info "Creating virtual environment..." + Write-Info "Creating virtual environment with Python $PythonVersion..." Push-Location $InstallDir - if (-not (Test-Path "venv")) { - & $PythonCmd -m venv venv + if (Test-Path "venv") { + Write-Info "Virtual environment already exists, recreating..." + Remove-Item -Recurse -Force "venv" } - # Activate - & .\venv\Scripts\Activate.ps1 - - # Upgrade pip - pip install --upgrade pip wheel setuptools | Out-Null + # uv creates the venv and pins the Python version in one step + & $UvCmd venv venv --python $PythonVersion Pop-Location - Write-Success "Virtual environment ready" + Write-Success "Virtual environment ready (Python $PythonVersion)" } function Install-Dependencies { @@ -293,14 +344,15 @@ function Install-Dependencies { Push-Location $InstallDir if (-not $NoVenv) { - & .\venv\Scripts\Activate.ps1 + # Tell uv to install into our venv (no activation needed) + $env:VIRTUAL_ENV = "$InstallDir\venv" } - # Install main package + # Install main package with all extras try { - pip install -e ".[all]" 2>&1 | Out-Null + & $UvCmd pip install -e ".[all]" 2>&1 | Out-Null } catch { - pip install -e "." | Out-Null + & $UvCmd pip install -e "." | Out-Null } Write-Success "Main package installed" @@ -309,32 +361,25 @@ function Install-Dependencies { Write-Info "Installing mini-swe-agent (terminal tool backend)..." if (Test-Path "mini-swe-agent\pyproject.toml") { try { - pip install -e ".\mini-swe-agent" 2>&1 | Out-Null + & $UvCmd pip install -e ".\mini-swe-agent" 2>&1 | Out-Null Write-Success "mini-swe-agent installed" } catch { - Write-Warning "mini-swe-agent install failed (terminal tools may not work)" + Write-Warn "mini-swe-agent install failed (terminal tools may not work)" } } else { - Write-Warning "mini-swe-agent not found (run: git submodule update --init)" + Write-Warn "mini-swe-agent not found (run: git submodule update --init)" } Write-Info "Installing tinker-atropos (RL training backend)..." if (Test-Path "tinker-atropos\pyproject.toml") { - # tinker-atropos depends on the 'tinker' package which requires Python >= 3.11 - $major, $minor = $PythonVersion.Split('.') - if ([int]$minor -ge 11) { - try { - pip install -e ".\tinker-atropos" 2>&1 | Out-Null - Write-Success "tinker-atropos installed" - } catch { - Write-Warning "tinker-atropos install failed (RL tools may not work)" - } - } else { - Write-Warning "tinker-atropos requires Python 3.11+ (skipping — RL training tools won't be available)" - Write-Info "Upgrade to Python 3.11+ to enable RL training features" + try { + & $UvCmd pip install -e ".\tinker-atropos" 2>&1 | Out-Null + Write-Success "tinker-atropos installed" + } catch { + Write-Warn "tinker-atropos install failed (RL tools may not work)" } } else { - Write-Warning "tinker-atropos not found (run: git submodule update --init)" + Write-Warn "tinker-atropos not found (run: git submodule update --init)" } Pop-Location @@ -343,41 +388,44 @@ function Install-Dependencies { } function Set-PathVariable { - Write-Info "Setting up PATH..." + Write-Info "Setting up hermes command..." if ($NoVenv) { - $binDir = "$InstallDir" + $hermesBin = "$InstallDir" } else { - $binDir = "$InstallDir\venv\Scripts" + $hermesBin = "$InstallDir\venv\Scripts" } - # Add to user PATH + # Add the venv Scripts dir to user PATH so hermes is globally available + # On Windows, the hermes.exe in venv\Scripts\ has the venv Python baked in $currentPath = [Environment]::GetEnvironmentVariable("Path", "User") - if ($currentPath -notlike "*$binDir*") { + if ($currentPath -notlike "*$hermesBin*") { [Environment]::SetEnvironmentVariable( "Path", - "$binDir;$currentPath", + "$hermesBin;$currentPath", "User" ) - Write-Success "Added to user PATH" + Write-Success "Added to user PATH: $hermesBin" } else { Write-Info "PATH already configured" } # Update current session - $env:Path = "$binDir;$env:Path" + $env:Path = "$hermesBin;$env:Path" + + Write-Success "hermes command ready" } function Copy-ConfigTemplates { Write-Info "Setting up configuration files..." - # Create ~/.hermes directory structure (config at top level, code in subdir) + # Create ~/.hermes directory structure New-Item -ItemType Directory -Force -Path "$HermesHome\cron" | Out-Null New-Item -ItemType Directory -Force -Path "$HermesHome\sessions" | Out-Null New-Item -ItemType Directory -Force -Path "$HermesHome\logs" | Out-Null - # Create .env at ~/.hermes/.env (top level, easy to find) + # Create .env $envPath = "$HermesHome\.env" if (-not (Test-Path $envPath)) { $examplePath = "$InstallDir\.env.example" @@ -385,7 +433,6 @@ function Copy-ConfigTemplates { Copy-Item $examplePath $envPath Write-Success "Created ~/.hermes/.env from template" } else { - # Create empty .env if no example exists New-Item -ItemType File -Force -Path $envPath | Out-Null Write-Success "Created ~/.hermes/.env" } @@ -393,7 +440,7 @@ function Copy-ConfigTemplates { Write-Info "~/.hermes/.env already exists, keeping it" } - # Create config.yaml at ~/.hermes/config.yaml (top level, easy to find) + # Create config.yaml $configPath = "$HermesHome\config.yaml" if (-not (Test-Path $configPath)) { $examplePath = "$InstallDir\cli-config.yaml.example" @@ -422,7 +469,7 @@ function Install-NodeDeps { npm install --silent 2>&1 | Out-Null Write-Success "Node.js dependencies installed" } catch { - Write-Warning "npm install failed (browser tools may not work)" + Write-Warn "npm install failed (browser tools may not work)" } } @@ -441,12 +488,13 @@ function Invoke-SetupWizard { Push-Location $InstallDir + # Run hermes setup using the venv Python directly (no activation needed) if (-not $NoVenv) { - & .\venv\Scripts\Activate.ps1 + & ".\venv\Scripts\python.exe" -m hermes_cli.main setup + } else { + python -m hermes_cli.main setup } - python -m hermes_cli.main setup - Pop-Location } @@ -493,7 +541,6 @@ function Write-Completion { Write-Host "⚡ Restart your terminal for PATH changes to take effect" -ForegroundColor Yellow Write-Host "" - # Show notes about optional tools if (-not $HasNode) { Write-Host "Note: Node.js was not found. Browser automation tools" -ForegroundColor Yellow Write-Host "will have limited functionality." -ForegroundColor Yellow @@ -515,6 +562,7 @@ function Write-Completion { function Main { Write-Banner + if (-not (Install-Uv)) { exit 1 } if (-not (Test-Python)) { exit 1 } if (-not (Test-Git)) { exit 1 } Test-Node # Optional, doesn't fail diff --git a/scripts/install.sh b/scripts/install.sh index c97cbc8a..09f93cb7 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -3,6 +3,7 @@ # Hermes Agent Installer # ============================================================================ # Installation script for Linux and macOS. +# Uses uv for fast Python provisioning and package management. # # Usage: # curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash @@ -29,7 +30,7 @@ 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_MIN_VERSION="3.10" +PYTHON_VERSION="3.11" # Options USE_VENV=true @@ -64,7 +65,7 @@ while [[ $# -gt 0 ]]; do echo " --no-venv Don't create virtual environment" echo " --skip-setup Skip interactive setup wizard" echo " --branch NAME Git branch to install (default: main)" - echo " --dir PATH Installation directory (default: ~/.hermes-agent)" + echo " --dir PATH Installation directory (default: ~/.hermes/hermes-agent)" echo " -h, --help Show this help" exit 0 ;; @@ -146,57 +147,80 @@ detect_os() { # Dependency checks # ============================================================================ -check_python() { - log_info "Checking Python..." +install_uv() { + log_info "Checking for uv package manager..." - # Try different python commands (prefer 3.11+ for full feature support) - for cmd in python3.12 python3.11 python3.10 python3 python; do - if command -v $cmd &> /dev/null; then - PYTHON_CMD=$cmd - PYTHON_VERSION=$($cmd -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")') - - # Check minimum version (3.10) - if $cmd -c "import sys; exit(0 if sys.version_info >= (3, 10) else 1)" 2>/dev/null; then - log_success "Python $PYTHON_VERSION found" - - # Warn if < 3.11 (RL training tools require 3.11+) - if ! $cmd -c "import sys; exit(0 if sys.version_info >= (3, 11) else 1)" 2>/dev/null; then - log_warn "Python 3.11+ recommended — RL Training tools (tinker-atropos) require >= 3.11" - log_info "Core agent features will work fine on $PYTHON_VERSION" - fi - - return 0 - fi + # Check common locations for uv + if command -v uv &> /dev/null; then + UV_CMD="uv" + UV_VERSION=$($UV_CMD --version 2>/dev/null) + log_success "uv found ($UV_VERSION)" + return 0 + fi + + # Check ~/.local/bin (default uv install location) even if not on PATH yet + if [ -x "$HOME/.local/bin/uv" ]; then + UV_CMD="$HOME/.local/bin/uv" + UV_VERSION=$($UV_CMD --version 2>/dev/null) + log_success "uv found at ~/.local/bin ($UV_VERSION)" + return 0 + fi + + # Check ~/.cargo/bin (alternative uv install location) + if [ -x "$HOME/.cargo/bin/uv" ]; then + UV_CMD="$HOME/.cargo/bin/uv" + UV_VERSION=$($UV_CMD --version 2>/dev/null) + log_success "uv found at ~/.cargo/bin ($UV_VERSION)" + return 0 + fi + + # Install uv + log_info "Installing uv (fast Python package manager)..." + if curl -LsSf https://astral.sh/uv/install.sh | sh 2>/dev/null; then + # uv installs to ~/.local/bin by default + 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" + elif command -v uv &> /dev/null; then + UV_CMD="uv" + else + log_error "uv installed but not found on PATH" + log_info "Try adding ~/.local/bin to your PATH and re-running" + exit 1 fi - done + 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..." - log_error "Python 3.10+ not found" - log_info "Please install Python 3.11 or newer (recommended):" + # Let uv handle Python — it can download and manage Python versions + # First check if a suitable Python is already available + 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) + log_success "Python found: $PYTHON_FOUND_VERSION" + return 0 + fi - case "$OS" in - linux) - case "$DISTRO" in - ubuntu|debian) - log_info " sudo apt update && sudo apt install python3.11 python3.11-venv" - ;; - fedora) - log_info " sudo dnf install python3.11" - ;; - arch) - log_info " sudo pacman -S python" - ;; - *) - log_info " Use your package manager to install Python 3.11+" - ;; - esac - ;; - macos) - log_info " brew install python@3.11" - log_info " Or download from https://www.python.org/downloads/" - ;; - esac - - exit 1 + # Python not found — use uv to install it (no sudo needed!) + 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 + log_error "Failed to install Python $PYTHON_VERSION" + log_info "Install Python $PYTHON_VERSION manually, then re-run this script" + exit 1 + fi } check_git() { @@ -301,7 +325,6 @@ check_ripgrep() { # Check if we can use sudo CAN_SUDO=false if command -v sudo &> /dev/null; then - # Check if user has sudo access (without actually running sudo) if sudo -n true 2>/dev/null || sudo -v 2>/dev/null; then CAN_SUDO=true fi @@ -335,7 +358,6 @@ check_ripgrep() { esac else log_warn "sudo not available - cannot auto-install system packages" - # Try cargo as fallback if available if command -v cargo &> /dev/null; then log_info "Trying cargo install (no sudo required)..." if cargo install ripgrep 2>/dev/null; then @@ -378,7 +400,6 @@ check_ripgrep() { log_info " https://github.com/BurntSushi/ripgrep#installation" ;; esac - # Show cargo alternative for users without sudo if command -v cargo &> /dev/null; then log_info " Or without sudo: cargo install ripgrep" fi @@ -447,39 +468,36 @@ setup_venv() { return 0 fi - log_info "Creating virtual environment..." + log_info "Creating virtual environment with Python $PYTHON_VERSION..." if [ -d "venv" ]; then - log_info "Virtual environment already exists" - else - $PYTHON_CMD -m venv venv + log_info "Virtual environment already exists, recreating..." + rm -rf venv fi - # Activate - source venv/bin/activate + # uv creates the venv and pins the Python version in one step + $UV_CMD venv venv --python "$PYTHON_VERSION" - # Upgrade pip - pip install --upgrade pip wheel setuptools > /dev/null - - log_success "Virtual environment ready" + log_success "Virtual environment ready (Python $PYTHON_VERSION)" } install_deps() { log_info "Installing dependencies..." if [ "$USE_VENV" = true ]; then - source venv/bin/activate + # Tell uv to install into our venv (no need to activate) + export VIRTUAL_ENV="$INSTALL_DIR/venv" fi # Install the main package in editable mode with all extras - pip install -e ".[all]" > /dev/null 2>&1 || pip install -e "." > /dev/null + $UV_CMD pip install -e ".[all]" || $UV_CMD pip install -e "." log_success "Main package installed" # Install submodules log_info "Installing mini-swe-agent (terminal tool backend)..." if [ -d "mini-swe-agent" ] && [ -f "mini-swe-agent/pyproject.toml" ]; then - pip install -e "./mini-swe-agent" > /dev/null 2>&1 || log_warn "mini-swe-agent install failed (terminal tools may not work)" + $UV_CMD pip install -e "./mini-swe-agent" || log_warn "mini-swe-agent install failed (terminal tools may not work)" log_success "mini-swe-agent installed" else log_warn "mini-swe-agent not found (run: git submodule update --init)" @@ -487,14 +505,8 @@ install_deps() { log_info "Installing tinker-atropos (RL training backend)..." if [ -d "tinker-atropos" ] && [ -f "tinker-atropos/pyproject.toml" ]; then - # tinker-atropos depends on the 'tinker' package which requires Python >= 3.11 - if $PYTHON_CMD -c "import sys; exit(0 if sys.version_info >= (3, 11) else 1)" 2>/dev/null; then - pip install -e "./tinker-atropos" > /dev/null 2>&1 || log_warn "tinker-atropos install failed (RL tools may not work)" - log_success "tinker-atropos installed" - else - log_warn "tinker-atropos requires Python 3.11+ (skipping — RL training tools won't be available)" - log_info "Upgrade to Python 3.11+ to enable RL training features" - fi + $UV_CMD pip install -e "./tinker-atropos" || log_warn "tinker-atropos install failed (RL tools may not work)" + log_success "tinker-atropos installed" else log_warn "tinker-atropos not found (run: git submodule update --init)" fi @@ -503,53 +515,56 @@ install_deps() { } setup_path() { - log_info "Setting up PATH..." + log_info "Setting up hermes command..." - # Determine the bin directory if [ "$USE_VENV" = true ]; then - BIN_DIR="$INSTALL_DIR/venv/bin" + HERMES_BIN="$INSTALL_DIR/venv/bin/hermes" else - BIN_DIR="$HOME/.local/bin" - mkdir -p "$BIN_DIR" + HERMES_BIN="$(which hermes 2>/dev/null || echo "")" + if [ -z "$HERMES_BIN" ]; then + log_warn "hermes not found on PATH after install" + return 0 + fi + fi + + # Create symlink in ~/.local/bin (standard user binary location, usually on PATH) + mkdir -p "$HOME/.local/bin" + ln -sf "$HERMES_BIN" "$HOME/.local/bin/hermes" + log_success "Symlinked hermes → ~/.local/bin/hermes" + + # Check if ~/.local/bin is on PATH; if not, add it to shell config + if ! echo "$PATH" | tr ':' '\n' | grep -q "^$HOME/.local/bin$"; then + SHELL_CONFIG="" + if [ -n "$BASH_VERSION" ]; then + if [ -f "$HOME/.bashrc" ]; then + SHELL_CONFIG="$HOME/.bashrc" + elif [ -f "$HOME/.bash_profile" ]; then + SHELL_CONFIG="$HOME/.bash_profile" + fi + elif [ -n "$ZSH_VERSION" ] || [ -f "$HOME/.zshrc" ]; then + SHELL_CONFIG="$HOME/.zshrc" + fi - # Create a wrapper script - cat > "$BIN_DIR/hermes" << EOF -#!/bin/bash -cd "$INSTALL_DIR" -exec python -m hermes_cli.main "\$@" -EOF - chmod +x "$BIN_DIR/hermes" - fi - - # Add to PATH in shell config - SHELL_CONFIG="" - if [ -n "$BASH_VERSION" ]; then - if [ -f "$HOME/.bashrc" ]; then - SHELL_CONFIG="$HOME/.bashrc" - elif [ -f "$HOME/.bash_profile" ]; then - SHELL_CONFIG="$HOME/.bash_profile" + PATH_LINE='export PATH="$HOME/.local/bin:$PATH"' + + if [ -n "$SHELL_CONFIG" ]; 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 "$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 - elif [ -n "$ZSH_VERSION" ] || [ -f "$HOME/.zshrc" ]; then - SHELL_CONFIG="$HOME/.zshrc" + else + log_info "~/.local/bin already on PATH" fi - PATH_LINE="export PATH=\"$BIN_DIR:\$PATH\"" + # Export for current session so hermes works immediately + export PATH="$HOME/.local/bin:$PATH" - if [ -n "$SHELL_CONFIG" ]; then - if ! grep -q "hermes-agent" "$SHELL_CONFIG" 2>/dev/null; then - echo "" >> "$SHELL_CONFIG" - echo "# Hermes Agent" >> "$SHELL_CONFIG" - echo "$PATH_LINE" >> "$SHELL_CONFIG" - log_success "Added to $SHELL_CONFIG" - else - log_info "PATH already configured in $SHELL_CONFIG" - fi - fi - - # Also export for current session - export PATH="$BIN_DIR:$PATH" - - log_success "PATH configured" + log_success "hermes command ready" } copy_config_templates() { @@ -566,7 +581,6 @@ copy_config_templates() { cp "$INSTALL_DIR/.env.example" "$HERMES_HOME/.env" log_success "Created ~/.hermes/.env from template" else - # Create empty .env if no example exists touch "$HERMES_HOME/.env" log_success "Created ~/.hermes/.env" fi @@ -614,12 +628,14 @@ run_setup_wizard() { log_info "Starting setup wizard..." echo "" - if [ "$USE_VENV" = true ]; then - source "$INSTALL_DIR/venv/bin/activate" - fi - cd "$INSTALL_DIR" - python -m hermes_cli.main setup + + # Run hermes setup using the venv Python directly (no activation needed) + if [ "$USE_VENV" = true ]; then + "$INSTALL_DIR/venv/bin/python" -m hermes_cli.main setup + else + python -m hermes_cli.main setup + fi } print_success() { @@ -686,6 +702,7 @@ main() { print_banner detect_os + install_uv check_python check_git check_node diff --git a/setup-hermes.sh b/setup-hermes.sh index e1a9dcb4..a1c9b723 100755 --- a/setup-hermes.sh +++ b/setup-hermes.sh @@ -3,16 +3,18 @@ # 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. Creates a virtual environment (if not exists) -# 2. Installs dependencies -# 3. Creates .env from template (if not exists) -# 4. Installs the 'hermes' CLI command -# 5. Runs the setup wizard (optional) +# 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 @@ -21,42 +23,74 @@ set -e 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 "" # ============================================================================ -# Python check +# Install / locate uv # ============================================================================ -echo -e "${CYAN}→${NC} Checking Python..." +echo -e "${CYAN}→${NC} Checking for uv..." -PYTHON_CMD="" -for cmd in python3.12 python3.11 python3.10 python3 python; do - if command -v $cmd &> /dev/null; then - if $cmd -c "import sys; exit(0 if sys.version_info >= (3, 10) else 1)" 2>/dev/null; then - PYTHON_CMD=$cmd - break - fi - fi -done - -if [ -z "$PYTHON_CMD" ]; then - echo -e "${YELLOW}✗${NC} Python 3.10+ required" - exit 1 +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 -PYTHON_VERSION=$($PYTHON_CMD -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")') -echo -e "${GREEN}✓${NC} Python $PYTHON_VERSION found" +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 -# Warn if < 3.11 (RL training tools require 3.11+) -if ! $PYTHON_CMD -c "import sys; exit(0 if sys.version_info >= (3, 11) else 1)" 2>/dev/null; then - echo -e "${YELLOW}⚠${NC} Python 3.11+ recommended — RL Training tools (tinker-atropos) require >= 3.11" +# ============================================================================ +# 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 # ============================================================================ @@ -65,15 +99,16 @@ fi echo -e "${CYAN}→${NC} Setting up virtual environment..." -if [ ! -d "venv" ]; then - $PYTHON_CMD -m venv venv - echo -e "${GREEN}✓${NC} Created venv" -else - echo -e "${GREEN}✓${NC} venv exists" +if [ -d "venv" ]; then + echo -e "${CYAN}→${NC} Removing old venv..." + rm -rf venv fi -source venv/bin/activate -pip install --upgrade pip wheel setuptools > /dev/null +$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 @@ -81,7 +116,7 @@ pip install --upgrade pip wheel setuptools > /dev/null echo -e "${CYAN}→${NC} Installing dependencies..." -pip install -e ".[all]" > /dev/null 2>&1 || pip install -e "." > /dev/null +$UV_CMD pip install -e ".[all]" || $UV_CMD pip install -e "." echo -e "${GREEN}✓${NC} Dependencies installed" @@ -93,22 +128,18 @@ echo -e "${CYAN}→${NC} Installing submodules..." # mini-swe-agent (terminal tool backend) if [ -d "mini-swe-agent" ] && [ -f "mini-swe-agent/pyproject.toml" ]; then - pip install -e "./mini-swe-agent" > /dev/null 2>&1 && \ + $UV_CMD pip install -e "./mini-swe-agent" && \ echo -e "${GREEN}✓${NC} mini-swe-agent installed" || \ echo -e "${YELLOW}⚠${NC} mini-swe-agent install failed (terminal tools may not work)" else echo -e "${YELLOW}⚠${NC} mini-swe-agent not found (run: git submodule update --init --recursive)" fi -# tinker-atropos (RL training backend — requires Python 3.11+) +# tinker-atropos (RL training backend) if [ -d "tinker-atropos" ] && [ -f "tinker-atropos/pyproject.toml" ]; then - if $PYTHON_CMD -c "import sys; exit(0 if sys.version_info >= (3, 11) else 1)" 2>/dev/null; then - pip install -e "./tinker-atropos" > /dev/null 2>&1 && \ - 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 requires Python 3.11+ (skipping — RL training tools won't be available)" - fi + $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 @@ -174,14 +205,17 @@ else fi # ============================================================================ -# PATH setup +# PATH setup — symlink hermes into ~/.local/bin # ============================================================================ echo -e "${CYAN}→${NC} Setting up hermes command..." -BIN_DIR="$SCRIPT_DIR/venv/bin" +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" -# Add to shell config if not already there +# Ensure ~/.local/bin is on PATH in shell config SHELL_CONFIG="" if [ -f "$HOME/.zshrc" ]; then SHELL_CONFIG="$HOME/.zshrc" @@ -192,13 +226,17 @@ elif [ -f "$HOME/.bash_profile" ]; then fi if [ -n "$SHELL_CONFIG" ]; then - if ! grep -q "hermes-agent" "$SHELL_CONFIG" 2>/dev/null; then - echo "" >> "$SHELL_CONFIG" - echo "# Hermes Agent" >> "$SHELL_CONFIG" - echo "export PATH=\"$BIN_DIR:\$PATH\"" >> "$SHELL_CONFIG" - echo -e "${GREEN}✓${NC} Added to $SHELL_CONFIG" + 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} PATH already in $SHELL_CONFIG" + echo -e "${GREEN}✓${NC} ~/.local/bin already on PATH" fi fi @@ -232,5 +270,6 @@ read -p "Would you like to run the setup wizard now? [Y/n] " -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]] || [[ -z $REPLY ]]; then echo "" - python -m hermes_cli.main setup + # Run directly with venv Python (no activation needed) + "$SCRIPT_DIR/venv/bin/python" -m hermes_cli.main setup fi