diff --git a/README-FreeBSD.md b/README-FreeBSD.md new file mode 100644 index 000000000..ea15795df --- /dev/null +++ b/README-FreeBSD.md @@ -0,0 +1,26 @@ +# Hermes Agent — FreeBSD + +This is a clean-room FreeBSD compatibility layer for [Hermes Agent](https://github.com/NousResearch/hermes-agent), built from the MIT-licensed upstream. No LGPL code, no Autolycus dependency. + +## What's patched + +Six targeted changes for FreeBSD native support: + +| File | Change | +|------|--------| +| `hermes_cli/setup.py` | FreeBSD in platform detection, `pkg` for espeak-ng, rc.d service support | +| `hermes_cli/uninstall.py` | `/usr/local/bin` symlink removal on FreeBSD | +| `tools/voice_mode.py` | Audio playback on FreeBSD (aplay/ffplay) | +| `scripts/install-freebsd.sh` | Native FreeBSD installer (POSIX sh, pkg, uv) | + +Clipboard (xclip) and voice (ffplay) work on FreeBSD without code changes — xclip and ffmpeg are available via `pkg`. + +## Install + +```sh +sh scripts/install-freebsd.sh +``` + +## License + +MIT — same as upstream NousResearch Hermes Agent. No LGPL encumbrance. diff --git a/hermes_cli/setup.py b/hermes_cli/setup.py index 266eb9eaa..62d824075 100644 --- a/hermes_cli/setup.py +++ b/hermes_cli/setup.py @@ -771,6 +771,8 @@ def _install_neutts_deps() -> bool: print_info("Install with: brew install espeak-ng") elif sys.platform == "win32": print_info("Install with: choco install espeak-ng") + elif sys.platform.startswith("freebsd"): + print_info("Install with: sudo pkg install espeak-ng") else: print_info("Install with: sudo apt install espeak-ng") print() @@ -780,6 +782,8 @@ def _install_neutts_deps() -> bool: subprocess.run(["brew", "install", "espeak-ng"], check=True) elif sys.platform == "win32": subprocess.run(["choco", "install", "espeak-ng", "-y"], check=True) + elif sys.platform.startswith("freebsd"): + subprocess.run(["sudo", "pkg", "install", "-y", "espeak-ng"], check=True) else: subprocess.run(["sudo", "apt", "install", "-y", "espeak-ng"], check=True) print_success("espeak-ng installed") @@ -1141,7 +1145,7 @@ def setup_terminal_backend(config: dict): print() current_backend = cfg_get(config, "terminal", "backend", default="local") - is_linux = _platform.system() == "Linux" + is_linux = _platform.system() in ("Linux", "FreeBSD") # Build backend choices with descriptions terminal_choices = [ @@ -2225,6 +2229,7 @@ def setup_gateway(config: dict): _is_linux = _platform.system() == "Linux" _is_macos = _platform.system() == "Darwin" _is_windows = _platform.system() == "Windows" + _is_freebsd = _platform.system() == "FreeBSD" from hermes_cli.gateway import ( _is_service_installed, @@ -2249,7 +2254,7 @@ def setup_gateway(config: dict): service_installed = _is_service_installed() service_running = _is_service_running() supports_systemd = supports_systemd_services() - supports_service_manager = supports_systemd or _is_macos or _is_windows + supports_service_manager = supports_systemd or _is_macos or _is_windows or _is_freebsd print() if supports_systemd and has_conflicting_systemd_units(): diff --git a/hermes_cli/uninstall.py b/hermes_cli/uninstall.py index 9b5350073..79a6a6e24 100644 --- a/hermes_cli/uninstall.py +++ b/hermes_cli/uninstall.py @@ -122,7 +122,7 @@ def _node_symlink_candidate_dirs() -> "list[Path]": """Directories where the installer may have placed node/npm/npx symlinks.""" dirs: list[Path] = [Path.home() / ".local" / "bin"] # Root FHS installs put links in /usr/local/bin. - if sys.platform == "linux": + if sys.platform in ("linux", "freebsd"): dirs.append(Path("/usr/local/bin")) # Termux installs put links in $PREFIX/bin. prefix = os.environ.get("PREFIX", "") diff --git a/scripts/install-freebsd.sh b/scripts/install-freebsd.sh new file mode 100755 index 000000000..50a333da6 --- /dev/null +++ b/scripts/install-freebsd.sh @@ -0,0 +1,116 @@ +#!/bin/sh +# ============================================================================ +# Hermes Agent Installer — FreeBSD +# ============================================================================ +# MIT Licensed — clean-room implementation for FreeBSD 14/15. +# +# Prerequisites: FreeBSD 14+ base system, internet access. +# Installs: uv (Python package manager), Hermes agent, pip dependencies. +# +# Usage: sh scripts/install-freebsd.sh +# ============================================================================ + +set -e + +echo "=== Hermes Agent FreeBSD Installer ===" +echo "" + +# ── Ensure /usr/local/bin in PATH ────────────────────── +case ":${PATH}:" in + *:/usr/local/bin:*) ;; + *) PATH="/usr/local/bin:${PATH}"; export PATH ;; +esac + +# ── Check FreeBSD ────────────────────────────────────── +if [ "$(uname -s)" != "FreeBSD" ]; then + echo "ERROR: This script is for FreeBSD only." + echo "For Linux/macOS, use the standard Hermes installer." + exit 1 +fi + +echo "Detected: $(uname -sr)" + +# ── Install uv (Python package manager) ──────────────── +if ! command -v uv >/dev/null 2>&1; then + echo "" + echo "[1/4] Installing uv package manager..." + if command -v pkg >/dev/null 2>&1; then + sudo pkg install -y uv 2>/dev/null || { + echo "uv not in pkg yet — installing via pip..." + pip install --break-system-packages uv + } + else + pip install --break-system-packages uv + fi + echo "uv installed: $(uv --version)" +else + echo "[1/4] uv already installed: $(uv --version)" +fi + +# ── Install Hermes via pip ───────────────────────────── +echo "" +echo "[2/4] Installing Hermes Agent..." + +# Use uv for fast resolution, fall back to pip +if command -v uv >/dev/null 2>&1; then + uv pip install --system hermes-agent 2>/dev/null || \ + uv pip install --system --break-system-packages hermes-agent 2>/dev/null || \ + pip install --break-system-packages hermes-agent +else + pip install --break-system-packages hermes-agent +fi + +echo "Hermes installed." + +# ── Verify installation ──────────────────────────────── +echo "" +echo "[3/4] Verifying installation..." +HERMES_BIN=$(command -v hermes 2>/dev/null || echo "/usr/local/bin/hermes") + +if [ -x "$HERMES_BIN" ]; then + echo "Hermes binary: $HERMES_BIN" + hermes --version 2>/dev/null || echo "(version check skipped — first run needed)" +else + echo "WARNING: hermes not found on PATH." + echo "Check: ls -la ~/.local/bin/hermes /usr/local/bin/hermes" +fi + +# ── Ensure pip dependencies ──────────────────────────── +echo "" +echo "[4/4] Checking dependencies..." + +# Packages commonly needed on FreeBSD +MISSING="" +for pkg in espeak-ng xclip; do + if ! pkg info "$pkg" >/dev/null 2>&1; then + MISSING="$MISSING $pkg" + fi +done + +if [ -n "$MISSING" ]; then + echo "" + echo "Optional packages for voice/clipboard support:" + echo " sudo pkg install$MISSING" + echo "" + echo "Install now? [y/N]" + read -r answer + case "$answer" in + [Yy]*) sudo pkg install -y$MISSING ;; + *) echo "Skipping optional packages." ;; + esac +fi + +echo "" +echo "=== Installation complete ===" +echo "" +echo "Next steps:" +echo " 1. hermes setup # configure providers, tools, gateway" +echo " 2. hermes model # pick a model (OpenRouter, DeepSeek, etc.)" +echo " 3. hermes config # review configuration" +echo " 4. hermes run # start using Hermes" +echo "" +echo "FreeBSD notes:" +echo " - Packages live in /usr/local/bin (ensure it's in PATH)" +echo " - Voice mode: install espeak-ng + ffmpeg (pkg install espeak-ng ffmpeg)" +echo " - Clipboard: install xclip (pkg install xclip)" +echo " - No systemd — use rc.d or run hermes gateway as a foreground service"