mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-29 06:31:32 +00:00
refactor(bootstrap): consolidate ACP browser bootstrap into install.{sh,ps1} (#27851)
* refactor(bootstrap): consolidate ACP browser bootstrap into install.{sh,ps1}
Delete 687 lines of duplicated browser bootstrap code from
acp_adapter/bootstrap/. All browser installation now routes through
dep_ensure -> install.{sh,ps1} --ensure, using agent-browser install
for Chromium. install.sh gains ensure_browser() with macOS app-bundle
detection and per-distro guidance.
Tracking: #27826
* fix(install.sh): add --ignore-scripts to npm install for camofox
@askjo/camofox-browser has a dependency (impit) whose postinstall
script runs `npx only-allow pnpm`, which fails under npm. Adding
--ignore-scripts avoids the spurious failure without affecting
functionality.
Tracking: #27826
* fix: add explicit return in ensure_browser, narrow exception in entry.py
ensure_browser() now returns 0 explicitly on all success paths.
_run_setup_browser() catches OSError instead of broad Exception,
letting ImportError propagate as a real packaging bug.
This commit is contained in:
parent
e3a254d65b
commit
d9b6f75c0b
6 changed files with 140 additions and 831 deletions
|
|
@ -1,288 +0,0 @@
|
|||
# bootstrap_browser_tools.ps1 — install agent-browser + Playwright Chromium
|
||||
# into ~/.hermes/node/ for use by Hermes Agent's browser tools on Windows.
|
||||
#
|
||||
# Targets the registry-install path: users who got Hermes via
|
||||
# `uvx --from 'hermes-agent[acp]==X' hermes-acp` don't have a repo clone,
|
||||
# so the install.ps1 `npm install`-in-repo flow doesn't apply. This script
|
||||
# is a self-contained, idempotent slice of install.ps1's browser block.
|
||||
#
|
||||
# Usage:
|
||||
# .\bootstrap_browser_tools.ps1 # use defaults
|
||||
# .\bootstrap_browser_tools.ps1 -Yes # accept Chromium download
|
||||
# .\bootstrap_browser_tools.ps1 -SkipChromium # Node + agent-browser only
|
||||
#
|
||||
# Idempotent: re-running this is safe and fast.
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[switch]$Yes,
|
||||
[switch]$SkipChromium
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$NodeVersion = "22"
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# Logging
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
function Write-Info { param([string]$msg) Write-Host "[*] $msg" -ForegroundColor Cyan }
|
||||
function Write-Success { param([string]$msg) Write-Host "[+] $msg" -ForegroundColor Green }
|
||||
function Write-Warn { param([string]$msg) Write-Host "[!] $msg" -ForegroundColor Yellow }
|
||||
function Write-Err { param([string]$msg) Write-Host "[x] $msg" -ForegroundColor Red }
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# Paths
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
$HermesHome = $env:HERMES_HOME
|
||||
if (-not $HermesHome) {
|
||||
$HermesHome = Join-Path $env:USERPROFILE ".hermes"
|
||||
}
|
||||
$NodePrefix = Join-Path $HermesHome "node"
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# Step 1: Node.js
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
function Resolve-NpmExe {
|
||||
# Same gotcha as install.ps1: prefer npm.cmd over npm.ps1 so the
|
||||
# PowerShell execution policy doesn't block us.
|
||||
$cmd = Get-Command npm -ErrorAction SilentlyContinue
|
||||
if (-not $cmd) { return $null }
|
||||
$npmExe = $cmd.Source
|
||||
if ($npmExe -like "*.ps1") {
|
||||
$sibling = Join-Path (Split-Path $npmExe -Parent) "npm.cmd"
|
||||
if (Test-Path $sibling) { return $sibling }
|
||||
}
|
||||
return $npmExe
|
||||
}
|
||||
|
||||
function Resolve-NpxExe {
|
||||
$cmd = Get-Command npx -ErrorAction SilentlyContinue
|
||||
if (-not $cmd) { return $null }
|
||||
$npxExe = $cmd.Source
|
||||
if ($npxExe -like "*.ps1") {
|
||||
$sibling = Join-Path (Split-Path $npxExe -Parent) "npx.cmd"
|
||||
if (Test-Path $sibling) { return $sibling }
|
||||
}
|
||||
return $npxExe
|
||||
}
|
||||
|
||||
function Ensure-Node {
|
||||
# System Node on PATH?
|
||||
$sysNode = Get-Command node -ErrorAction SilentlyContinue
|
||||
if ($sysNode) {
|
||||
try {
|
||||
$v = & $sysNode.Source --version
|
||||
$major = [int]($v -replace '^v(\d+).*', '$1')
|
||||
if ($major -ge 20) {
|
||||
Write-Success "Node.js $v found on PATH"
|
||||
return
|
||||
}
|
||||
Write-Warn "Node.js $v is older than v20 — installing managed Node."
|
||||
} catch {
|
||||
Write-Warn "Failed to query Node version: $_"
|
||||
}
|
||||
}
|
||||
|
||||
# Hermes-managed Node?
|
||||
$managedNode = Join-Path $NodePrefix "node.exe"
|
||||
if (Test-Path $managedNode) {
|
||||
$v = & $managedNode --version
|
||||
Write-Success "Node.js $v found (Hermes-managed at $NodePrefix)"
|
||||
# Prepend to current-process PATH so subsequent npm/npx calls find it.
|
||||
$env:PATH = "$NodePrefix;$env:PATH"
|
||||
return
|
||||
}
|
||||
|
||||
Write-Info "Installing Node.js $NodeVersion LTS into $NodePrefix ..."
|
||||
|
||||
$arch = if ([Environment]::Is64BitOperatingSystem) { "x64" } else { "x86" }
|
||||
$indexUrl = "https://nodejs.org/dist/latest-v${NodeVersion}.x/"
|
||||
|
||||
try {
|
||||
$indexPage = Invoke-WebRequest -Uri $indexUrl -UseBasicParsing
|
||||
$matches = [regex]::Matches($indexPage.Content, "node-v${NodeVersion}\.\d+\.\d+-win-${arch}\.zip")
|
||||
if ($matches.Count -eq 0) {
|
||||
Write-Err "Could not locate Node.js $NodeVersion zip for win-$arch"
|
||||
throw "no tarball"
|
||||
}
|
||||
$zipName = $matches[0].Value
|
||||
$zipUrl = "$indexUrl$zipName"
|
||||
|
||||
$tmpDir = Join-Path $env:TEMP "hermes-node-$([guid]::NewGuid().ToString('N'))"
|
||||
New-Item -ItemType Directory -Force -Path $tmpDir | Out-Null
|
||||
$zipPath = Join-Path $tmpDir $zipName
|
||||
|
||||
Write-Info "Downloading $zipName ..."
|
||||
Invoke-WebRequest -Uri $zipUrl -OutFile $zipPath -UseBasicParsing
|
||||
|
||||
Expand-Archive -Path $zipPath -DestinationPath $tmpDir -Force
|
||||
$extracted = Get-ChildItem -Path $tmpDir -Directory | Where-Object { $_.Name -like "node-v*" } | Select-Object -First 1
|
||||
|
||||
if (-not $extracted) { Write-Err "Node.js extraction failed"; throw "extract" }
|
||||
|
||||
if (Test-Path $NodePrefix) { Remove-Item -Recurse -Force $NodePrefix }
|
||||
New-Item -ItemType Directory -Force -Path $HermesHome | Out-Null
|
||||
Move-Item -Path $extracted.FullName -Destination $NodePrefix
|
||||
|
||||
Remove-Item -Recurse -Force $tmpDir -ErrorAction SilentlyContinue
|
||||
|
||||
$env:PATH = "$NodePrefix;$env:PATH"
|
||||
$v = & "$NodePrefix\node.exe" --version
|
||||
Write-Success "Node.js $v installed to $NodePrefix"
|
||||
} catch {
|
||||
Write-Err "Node.js install failed: $_"
|
||||
Write-Info "Install Node 20+ manually from https://nodejs.org/en/download/ and re-run."
|
||||
throw
|
||||
}
|
||||
}
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# Step 2: agent-browser
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
function Ensure-AgentBrowser {
|
||||
$npmExe = Resolve-NpmExe
|
||||
if (-not $npmExe) {
|
||||
Write-Err "npm not on PATH after Node install — aborting"
|
||||
throw "npm missing"
|
||||
}
|
||||
|
||||
# Already installed?
|
||||
$existing = Get-Command agent-browser -ErrorAction SilentlyContinue
|
||||
if ($existing) {
|
||||
Write-Success "agent-browser already installed at $($existing.Source)"
|
||||
return
|
||||
}
|
||||
|
||||
# When the user has system Node (winget / installer-based), `npm install
|
||||
# -g` writes to a directory that may require admin rights. Force the
|
||||
# prefix to the user-writable Hermes-managed Node directory so we never
|
||||
# need elevation and the agent can always find the result. Mirrors the
|
||||
# bash bootstrap's `--prefix $NODE_PREFIX` strategy.
|
||||
New-Item -ItemType Directory -Force -Path $NodePrefix | Out-Null
|
||||
|
||||
Write-Info "Installing agent-browser (npm, prefix=$NodePrefix)..."
|
||||
& $npmExe install -g --prefix $NodePrefix --silent `
|
||||
"agent-browser@^0.26.0" "@askjo/camofox-browser@^1.5.2"
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Err "npm install -g agent-browser failed (exit $LASTEXITCODE)"
|
||||
throw "npm install"
|
||||
}
|
||||
|
||||
# Windows npm global installs drop shims at $NodePrefix\ root (not bin/).
|
||||
# Prepend to PATH so any subsequent npx call resolves them.
|
||||
$env:PATH = "$NodePrefix;$env:PATH"
|
||||
|
||||
Write-Success "agent-browser installed to $NodePrefix"
|
||||
}
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# Step 3: Playwright Chromium
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
function Find-SystemBrowser {
|
||||
$candidates = @(
|
||||
"C:\Program Files\Google\Chrome\Application\chrome.exe",
|
||||
"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe",
|
||||
"C:\Program Files\Chromium\Application\chromium.exe",
|
||||
"${env:LOCALAPPDATA}\Google\Chrome\Application\chrome.exe",
|
||||
"${env:LOCALAPPDATA}\Chromium\Application\chromium.exe"
|
||||
)
|
||||
foreach ($p in $candidates) {
|
||||
if (Test-Path $p) { return $p }
|
||||
}
|
||||
# Edge — Chromium-based, agent-browser can use it
|
||||
foreach ($p in @(
|
||||
"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe",
|
||||
"C:\Program Files\Microsoft\Edge\Application\msedge.exe"
|
||||
)) {
|
||||
if (Test-Path $p) { return $p }
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
function Write-BrowserEnv {
|
||||
param([string]$BrowserPath)
|
||||
$envFile = Join-Path $HermesHome ".env"
|
||||
New-Item -ItemType Directory -Force -Path $HermesHome | Out-Null
|
||||
if (Test-Path $envFile) {
|
||||
$existing = Get-Content $envFile -Raw -ErrorAction SilentlyContinue
|
||||
if ($existing -and ($existing -match "(?m)^AGENT_BROWSER_EXECUTABLE_PATH=")) {
|
||||
return
|
||||
}
|
||||
}
|
||||
Add-Content -Path $envFile -Value ""
|
||||
Add-Content -Path $envFile -Value "# Hermes Agent browser tools — use the system Chrome/Chromium/Edge binary."
|
||||
Add-Content -Path $envFile -Value "AGENT_BROWSER_EXECUTABLE_PATH=$BrowserPath"
|
||||
Write-Success "Configured browser tools to use $BrowserPath"
|
||||
}
|
||||
|
||||
function Confirm-ChromiumDownload {
|
||||
if ($Yes) { return $true }
|
||||
if (-not [Environment]::UserInteractive) {
|
||||
Write-Warn "Non-interactive shell — skipping Chromium prompt."
|
||||
Write-Info "Re-run with -Yes to install Chromium (~400 MB download)."
|
||||
return $false
|
||||
}
|
||||
$reply = Read-Host "Install Playwright Chromium (~400 MB download)? [y/N]"
|
||||
return ($reply -match "^(y|yes)$")
|
||||
}
|
||||
|
||||
function Ensure-Chromium {
|
||||
if ($SkipChromium) {
|
||||
Write-Info "Skipping Chromium install (-SkipChromium)"
|
||||
return
|
||||
}
|
||||
|
||||
# agent-browser on Windows expects a Playwright-managed Chromium under
|
||||
# %LOCALAPPDATA%\ms-playwright. The system-browser shortcut from the
|
||||
# Linux/macOS path doesn't apply the same way on Windows — Playwright's
|
||||
# default launch path won't pick up a stock Chrome install without an
|
||||
# explicit AGENT_BROWSER_EXECUTABLE_PATH. We still offer it as a
|
||||
# fallback when the user doesn't want the download.
|
||||
|
||||
if (-not (Confirm-ChromiumDownload)) {
|
||||
$sys = Find-SystemBrowser
|
||||
if ($sys) {
|
||||
Write-Info "Using system browser at $sys (Chromium download skipped)."
|
||||
Write-BrowserEnv -BrowserPath $sys
|
||||
} else {
|
||||
Write-Info "Chromium install skipped. Browser tools won't launch until"
|
||||
Write-Info "Chromium is installed or AGENT_BROWSER_EXECUTABLE_PATH is set."
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
$npxExe = Resolve-NpxExe
|
||||
if (-not $npxExe) {
|
||||
Write-Err "npx not on PATH — cannot install Playwright Chromium"
|
||||
throw "npx missing"
|
||||
}
|
||||
|
||||
Write-Info "Installing Playwright Chromium (~400 MB) ..."
|
||||
& $npxExe --yes playwright install chromium
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Err "Playwright Chromium install failed (exit $LASTEXITCODE)"
|
||||
Write-Info "Try again later: npx --yes playwright install chromium"
|
||||
throw "playwright"
|
||||
}
|
||||
Write-Success "Playwright Chromium installed"
|
||||
}
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# Main
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
Write-Info "Hermes Agent: bootstrapping browser tools"
|
||||
Write-Info " HERMES_HOME = $HermesHome"
|
||||
Write-Info " OS = Windows"
|
||||
|
||||
Ensure-Node
|
||||
Ensure-AgentBrowser
|
||||
Ensure-Chromium
|
||||
|
||||
Write-Success "Browser tools setup complete."
|
||||
Write-Info "Hermes Agent will pick up agent-browser from $NodePrefix on next launch."
|
||||
|
|
@ -1,399 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# bootstrap_browser_tools.sh — install agent-browser + Playwright Chromium
|
||||
# into ~/.hermes/node/ for use by Hermes Agent's browser tools.
|
||||
#
|
||||
# Targets the registry-install path: users who got Hermes via
|
||||
# `uvx --from 'hermes-agent[acp]==X' hermes-acp` don't have a repo clone,
|
||||
# so the install.sh `npm install`-in-repo flow doesn't apply. This script
|
||||
# is a self-contained, idempotent slice of install.sh's browser block —
|
||||
# safe to run from `hermes-acp --setup-browser`, from a fresh terminal,
|
||||
# or from install.sh itself (it's a no-op when everything is already in place).
|
||||
#
|
||||
# Usage:
|
||||
# bootstrap_browser_tools.sh # use defaults
|
||||
# bootstrap_browser_tools.sh --yes # accept the ~400MB Chromium download
|
||||
# bootstrap_browser_tools.sh --skip-chromium # only install Node + agent-browser
|
||||
# HERMES_HOME=/custom/path bootstrap_browser_tools.sh
|
||||
#
|
||||
# Idempotent: re-running this is safe and fast. Each step checks whether
|
||||
# the work is already done.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# Config
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
NODE_VERSION="22"
|
||||
HERMES_HOME="${HERMES_HOME:-$HOME/.hermes}"
|
||||
NODE_PREFIX="$HERMES_HOME/node"
|
||||
|
||||
SKIP_CHROMIUM=false
|
||||
ASSUME_YES=false
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# Logging
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
if [ -t 1 ]; then
|
||||
C_GREEN='\033[0;32m'
|
||||
C_YELLOW='\033[0;33m'
|
||||
C_BLUE='\033[0;34m'
|
||||
C_RED='\033[0;31m'
|
||||
C_RESET='\033[0m'
|
||||
else
|
||||
C_GREEN='' ; C_YELLOW='' ; C_BLUE='' ; C_RED='' ; C_RESET=''
|
||||
fi
|
||||
|
||||
log_info() { printf "${C_BLUE}[*]${C_RESET} %s\n" "$*"; }
|
||||
log_success() { printf "${C_GREEN}[✓]${C_RESET} %s\n" "$*"; }
|
||||
log_warn() { printf "${C_YELLOW}[!]${C_RESET} %s\n" "$*" >&2; }
|
||||
log_error() { printf "${C_RED}[✗]${C_RESET} %s\n" "$*" >&2; }
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# Arg parsing
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--skip-chromium) SKIP_CHROMIUM=true ;;
|
||||
--yes|-y) ASSUME_YES=true ;;
|
||||
-h|--help)
|
||||
cat <<EOF
|
||||
Bootstrap Hermes Agent browser tools.
|
||||
|
||||
Installs Node.js (into ~/.hermes/node/), the agent-browser npm package,
|
||||
and the Playwright Chromium browser engine.
|
||||
|
||||
Options:
|
||||
--skip-chromium Install Node + agent-browser but skip Chromium download
|
||||
--yes, -y Accept the ~400 MB Chromium download without prompting
|
||||
-h, --help Show this help
|
||||
|
||||
Environment:
|
||||
HERMES_HOME Override Hermes data dir (default: \$HOME/.hermes)
|
||||
EOF
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
log_error "Unknown option: $1"
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# OS / arch detection
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
OS="unknown"
|
||||
case "$(uname -s)" in
|
||||
Linux*) OS="linux" ;;
|
||||
Darwin*) OS="macos" ;;
|
||||
*)
|
||||
log_error "Unsupported OS: $(uname -s)"
|
||||
log_info "Windows users: run scripts/bootstrap_browser_tools.ps1 in PowerShell."
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
NODE_ARCH=""
|
||||
case "$(uname -m)" in
|
||||
x86_64) NODE_ARCH="x64" ;;
|
||||
aarch64|arm64) NODE_ARCH="arm64" ;;
|
||||
armv7l) NODE_ARCH="armv7l" ;;
|
||||
*)
|
||||
log_error "Unsupported architecture: $(uname -m)"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
NODE_OS=""
|
||||
case "$OS" in
|
||||
linux) NODE_OS="linux" ;;
|
||||
macos) NODE_OS="darwin" ;;
|
||||
esac
|
||||
|
||||
DISTRO=""
|
||||
if [ -f /etc/os-release ]; then
|
||||
# shellcheck disable=SC1091
|
||||
. /etc/os-release
|
||||
DISTRO="${ID:-}"
|
||||
fi
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# Step 1: Node.js
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
ensure_node() {
|
||||
# Already on PATH and recent enough?
|
||||
if command -v node >/dev/null 2>&1; then
|
||||
local found_ver major
|
||||
found_ver=$(node --version 2>/dev/null)
|
||||
major=$(echo "$found_ver" | sed -E 's/^v([0-9]+).*/\1/')
|
||||
if [ -n "$major" ] && [ "$major" -ge 20 ]; then
|
||||
log_success "Node.js $found_ver found on PATH"
|
||||
return 0
|
||||
fi
|
||||
log_warn "Node.js $found_ver is older than v20 — installing managed Node."
|
||||
fi
|
||||
|
||||
if [ -x "$NODE_PREFIX/bin/node" ]; then
|
||||
local found_ver
|
||||
found_ver=$("$NODE_PREFIX/bin/node" --version 2>/dev/null || echo "?")
|
||||
export PATH="$NODE_PREFIX/bin:$PATH"
|
||||
log_success "Node.js $found_ver found (Hermes-managed at $NODE_PREFIX)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_info "Installing Node.js $NODE_VERSION LTS into $NODE_PREFIX ..."
|
||||
|
||||
local index_url="https://nodejs.org/dist/latest-v${NODE_VERSION}.x/"
|
||||
local tarball_name
|
||||
tarball_name=$(curl -fsSL "$index_url" \
|
||||
| grep -oE "node-v${NODE_VERSION}\.[0-9]+\.[0-9]+-${NODE_OS}-${NODE_ARCH}\.tar\.xz" \
|
||||
| head -1)
|
||||
|
||||
if [ -z "$tarball_name" ]; then
|
||||
tarball_name=$(curl -fsSL "$index_url" \
|
||||
| grep -oE "node-v${NODE_VERSION}\.[0-9]+\.[0-9]+-${NODE_OS}-${NODE_ARCH}\.tar\.gz" \
|
||||
| head -1)
|
||||
fi
|
||||
|
||||
if [ -z "$tarball_name" ]; then
|
||||
log_error "Could not locate Node.js $NODE_VERSION tarball for $NODE_OS-$NODE_ARCH"
|
||||
log_info "Install Node 20+ manually: https://nodejs.org/en/download/"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local tmp_dir
|
||||
tmp_dir=$(mktemp -d)
|
||||
trap 'rm -rf "$tmp_dir"' RETURN
|
||||
|
||||
log_info "Downloading $tarball_name ..."
|
||||
if ! curl -fsSL "${index_url}${tarball_name}" -o "$tmp_dir/$tarball_name"; then
|
||||
log_error "Node.js download failed"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ "$tarball_name" == *.tar.xz ]]; then
|
||||
tar xf "$tmp_dir/$tarball_name" -C "$tmp_dir"
|
||||
else
|
||||
tar xzf "$tmp_dir/$tarball_name" -C "$tmp_dir"
|
||||
fi
|
||||
|
||||
local extracted_dir
|
||||
extracted_dir=$(ls -d "$tmp_dir"/node-v* 2>/dev/null | head -1)
|
||||
if [ ! -d "$extracted_dir" ]; then
|
||||
log_error "Node.js extraction failed"
|
||||
return 1
|
||||
fi
|
||||
|
||||
mkdir -p "$HERMES_HOME"
|
||||
rm -rf "$NODE_PREFIX"
|
||||
mv "$extracted_dir" "$NODE_PREFIX"
|
||||
|
||||
export PATH="$NODE_PREFIX/bin:$PATH"
|
||||
|
||||
local installed_ver
|
||||
installed_ver=$("$NODE_PREFIX/bin/node" --version 2>/dev/null || echo "?")
|
||||
log_success "Node.js $installed_ver installed to $NODE_PREFIX"
|
||||
}
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# Step 2: agent-browser + @askjo/camofox-browser via global npm install
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
ensure_agent_browser() {
|
||||
if ! command -v npm >/dev/null 2>&1; then
|
||||
log_error "npm not on PATH after Node install — aborting"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# _find_agent_browser() in tools/browser_tool.py walks ~/.hermes/node/bin
|
||||
# plus a few standard prefixes, so installing globally into the managed
|
||||
# Node prefix is enough — no PATH manipulation needed from the agent side.
|
||||
if [ -x "$NODE_PREFIX/bin/agent-browser" ] || command -v agent-browser >/dev/null 2>&1; then
|
||||
log_success "agent-browser already installed"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# When the system's `npm` resolves to a root-owned prefix (e.g.
|
||||
# /usr/lib/node_modules), `npm install -g` fails with EACCES without
|
||||
# sudo. Force the prefix to the user-writable Hermes-managed Node
|
||||
# directory so we never need sudo and the agent can always find the
|
||||
# result. If we installed Node ourselves above, this is a no-op
|
||||
# (managed Node already uses $NODE_PREFIX). If the user has system
|
||||
# Node, we still drop agent-browser under $NODE_PREFIX/bin/ — which
|
||||
# is exactly where _browser_candidate_path_dirs() looks first.
|
||||
mkdir -p "$NODE_PREFIX"
|
||||
|
||||
log_info "Installing agent-browser (npm, prefix=$NODE_PREFIX)..."
|
||||
if ! npm install -g --prefix "$NODE_PREFIX" --silent \
|
||||
agent-browser@^0.26.0 \
|
||||
"@askjo/camofox-browser@^1.5.2"; then
|
||||
log_error "npm install -g agent-browser failed"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# macOS/Linux global installs place the shim into $NODE_PREFIX/bin/.
|
||||
# Add it to PATH for any subsequent steps (npx playwright).
|
||||
export PATH="$NODE_PREFIX/bin:$PATH"
|
||||
|
||||
log_success "agent-browser installed to $NODE_PREFIX/bin/"
|
||||
}
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# Step 3: Playwright Chromium
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
confirm_chromium_download() {
|
||||
if [ "$ASSUME_YES" = true ]; then return 0; fi
|
||||
if [ ! -t 0 ]; then
|
||||
log_warn "Non-interactive shell — skipping Chromium prompt."
|
||||
log_info "Re-run with --yes to install Chromium (~400 MB download)."
|
||||
return 1
|
||||
fi
|
||||
printf "Install Playwright Chromium (~400 MB download)? [y/N] "
|
||||
local reply=""
|
||||
read -r reply || reply=""
|
||||
case "$reply" in
|
||||
y|Y|yes|YES) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Detect a usable system Chrome/Chromium. agent-browser's Chrome engine can
|
||||
# use it instead of downloading Playwright's bundled Chromium, saving the
|
||||
# download cost. Returns the path or empty string.
|
||||
find_system_browser() {
|
||||
local candidate
|
||||
for candidate in google-chrome google-chrome-stable chromium chromium-browser chrome; do
|
||||
if command -v "$candidate" >/dev/null 2>&1; then
|
||||
command -v "$candidate"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
# macOS app-bundle locations
|
||||
if [ "$OS" = "macos" ]; then
|
||||
for candidate in \
|
||||
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
|
||||
"/Applications/Chromium.app/Contents/MacOS/Chromium" ; do
|
||||
if [ -x "$candidate" ]; then
|
||||
echo "$candidate"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
write_browser_env() {
|
||||
local browser_path="$1"
|
||||
local env_file="$HERMES_HOME/.env"
|
||||
mkdir -p "$HERMES_HOME"
|
||||
if [ -f "$env_file" ] && grep -q "^AGENT_BROWSER_EXECUTABLE_PATH=" "$env_file"; then
|
||||
return 0
|
||||
fi
|
||||
{
|
||||
echo ""
|
||||
echo "# Hermes Agent browser tools — use the system Chrome/Chromium binary."
|
||||
echo "AGENT_BROWSER_EXECUTABLE_PATH=$browser_path"
|
||||
} >> "$env_file"
|
||||
log_success "Configured browser tools to use $browser_path"
|
||||
}
|
||||
|
||||
ensure_chromium() {
|
||||
if [ "$SKIP_CHROMIUM" = true ]; then
|
||||
log_info "Skipping Chromium install (--skip-chromium)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local system_browser
|
||||
system_browser="$(find_system_browser 2>/dev/null || true)"
|
||||
if [ -n "$system_browser" ]; then
|
||||
log_success "Found system browser: $system_browser"
|
||||
log_info "Skipping Playwright Chromium download; agent-browser will use it."
|
||||
write_browser_env "$system_browser"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! confirm_chromium_download; then
|
||||
log_info "Chromium install skipped. Browser tools will only work if you"
|
||||
log_info "set AGENT_BROWSER_EXECUTABLE_PATH or install Chromium later."
|
||||
return 0
|
||||
fi
|
||||
|
||||
if ! command -v npx >/dev/null 2>&1; then
|
||||
log_error "npx not on PATH — cannot install Playwright Chromium"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "Installing Playwright Chromium (~400 MB) ..."
|
||||
|
||||
# On apt-based distros, --with-deps requires sudo. Try non-interactively
|
||||
# only — never prompt — and fall back to the bare browser-only install.
|
||||
local installed=false
|
||||
if [ "$OS" = "linux" ]; then
|
||||
case "$DISTRO" in
|
||||
ubuntu|debian|raspbian|pop|linuxmint|elementary|zorin|kali|parrot)
|
||||
if [ "$(id -u)" -eq 0 ] || (command -v sudo >/dev/null 2>&1 && sudo -n true 2>/dev/null); then
|
||||
log_info "Installing system deps with --with-deps (sudo available)"
|
||||
if npx --yes playwright install --with-deps chromium; then
|
||||
installed=true
|
||||
fi
|
||||
else
|
||||
log_warn "sudo not available non-interactively — installing Chromium without system deps."
|
||||
log_info "If browser tools fail to launch, an administrator should run:"
|
||||
log_info " sudo npx playwright install-deps chromium"
|
||||
fi
|
||||
;;
|
||||
arch|manjaro|cachyos|endeavouros|garuda)
|
||||
log_info "Arch-family system dependencies are not auto-installed."
|
||||
log_info "If launch fails, run: sudo pacman -S nss atk at-spi2-core cups libdrm libxkbcommon mesa pango cairo alsa-lib"
|
||||
;;
|
||||
fedora|rhel|centos|rocky|alma)
|
||||
log_info "Fedora/RHEL system dependencies are not auto-installed."
|
||||
log_info "If launch fails, run: sudo dnf install nss atk at-spi2-core cups-libs libdrm libxkbcommon mesa-libgbm pango cairo alsa-lib"
|
||||
;;
|
||||
opensuse*|sles)
|
||||
log_info "openSUSE system dependencies are not auto-installed."
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
if [ "$installed" = false ]; then
|
||||
if npx --yes playwright install chromium; then
|
||||
installed=true
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$installed" = true ]; then
|
||||
log_success "Playwright Chromium installed"
|
||||
else
|
||||
log_error "Playwright Chromium install failed"
|
||||
log_info "Try again later: npx --yes playwright install chromium"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# Main
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
main() {
|
||||
log_info "Hermes Agent: bootstrapping browser tools"
|
||||
log_info " HERMES_HOME = $HERMES_HOME"
|
||||
log_info " OS / arch = $NODE_OS-$NODE_ARCH ${DISTRO:+($DISTRO)}"
|
||||
|
||||
ensure_node
|
||||
ensure_agent_browser
|
||||
ensure_chromium
|
||||
|
||||
log_success "Browser tools setup complete."
|
||||
log_info "Hermes Agent will pick up agent-browser from $NODE_PREFIX/bin/ on next launch."
|
||||
}
|
||||
|
||||
main
|
||||
|
|
@ -182,56 +182,31 @@ def _run_setup() -> None:
|
|||
|
||||
|
||||
def _run_setup_browser(assume_yes: bool = False) -> int:
|
||||
"""Bootstrap agent-browser + Playwright Chromium for the registry-install path.
|
||||
"""Bootstrap agent-browser + Chromium.
|
||||
|
||||
Shells out to the bundled platform-specific bootstrap script
|
||||
(acp_adapter/bootstrap/bootstrap_browser_tools.{sh,ps1}) so the install
|
||||
logic lives in one place — readable, debuggable, and shareable with
|
||||
install.sh / install.ps1 if we ever want to call it from there too.
|
||||
Routes through dep_ensure -> install.{sh,ps1} --ensure, sharing code
|
||||
with ``hermes postinstall`` and the runtime lazy installer.
|
||||
|
||||
Returns the script's exit code (0 on success).
|
||||
Returns 0 on success, 1 on failure.
|
||||
"""
|
||||
import platform
|
||||
import subprocess
|
||||
from hermes_cli.dep_ensure import ensure_dependency
|
||||
|
||||
bootstrap_dir = Path(__file__).resolve().parent / "bootstrap"
|
||||
|
||||
if platform.system() == "Windows":
|
||||
script = bootstrap_dir / "bootstrap_browser_tools.ps1"
|
||||
if not script.is_file():
|
||||
print(
|
||||
f"Bootstrap script not found at {script} — wheel may be incomplete.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
cmd = [
|
||||
"powershell.exe",
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy", "Bypass",
|
||||
"-File", str(script),
|
||||
]
|
||||
if assume_yes:
|
||||
cmd.append("-Yes")
|
||||
else:
|
||||
script = bootstrap_dir / "bootstrap_browser_tools.sh"
|
||||
if not script.is_file():
|
||||
print(
|
||||
f"Bootstrap script not found at {script} — wheel may be incomplete.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
cmd = ["bash", str(script)]
|
||||
if assume_yes:
|
||||
cmd.append("--yes")
|
||||
|
||||
# stdio is inherited so the user sees the bootstrap's progress live.
|
||||
try:
|
||||
result = subprocess.run(cmd, check=False)
|
||||
except FileNotFoundError as exc:
|
||||
# bash / powershell.exe not on PATH
|
||||
print(f"Could not launch browser bootstrap: {exc}", file=sys.stderr)
|
||||
node_ok = ensure_dependency("node", interactive=not assume_yes)
|
||||
if not node_ok:
|
||||
print("Node.js installation failed — cannot proceed with browser tools.",
|
||||
file=sys.stderr)
|
||||
return 1
|
||||
|
||||
browser_ok = ensure_dependency("browser", interactive=not assume_yes)
|
||||
if not browser_ok:
|
||||
print("Browser tools installation failed.", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
return 0
|
||||
except OSError as exc:
|
||||
print(f"Browser bootstrap failed: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
return result.returncode
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> None:
|
||||
|
|
|
|||
|
|
@ -1512,6 +1512,17 @@ find_system_browser() {
|
|||
fi
|
||||
done
|
||||
|
||||
if [ "$(uname)" = "Darwin" ]; then
|
||||
for app in \
|
||||
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
|
||||
"/Applications/Chromium.app/Contents/MacOS/Chromium"; do
|
||||
if [ -x "$app" ]; then
|
||||
echo "$app"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
|
|
@ -1534,10 +1545,15 @@ configure_browser_env_from_system_browser() {
|
|||
browser_path="$(find_system_browser 2>/dev/null || true)"
|
||||
fi
|
||||
|
||||
if [ -z "$browser_path" ] || [ ! -f "$env_file" ]; then
|
||||
if [ -z "$browser_path" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
mkdir -p "$HERMES_HOME"
|
||||
if [ ! -f "$env_file" ]; then
|
||||
touch "$env_file"
|
||||
fi
|
||||
|
||||
if grep -q '^AGENT_BROWSER_EXECUTABLE_PATH=' "$env_file" 2>/dev/null; then
|
||||
log_info "AGENT_BROWSER_EXECUTABLE_PATH already configured"
|
||||
return 0
|
||||
|
|
@ -1888,6 +1904,73 @@ print_success() {
|
|||
fi
|
||||
}
|
||||
|
||||
ensure_browser() {
|
||||
if ! command -v node >/dev/null 2>&1; then
|
||||
local node_bin="$HERMES_HOME/node/bin/node"
|
||||
if [ -x "$node_bin" ]; then
|
||||
export PATH="$HERMES_HOME/node/bin:$PATH"
|
||||
else
|
||||
log_error "Node.js not found. Run with --ensure node first."
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
local npm_bin
|
||||
npm_bin="$(command -v npm 2>/dev/null || echo "$HERMES_HOME/node/bin/npm")"
|
||||
if [ ! -x "$npm_bin" ]; then
|
||||
log_error "npm not found"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_info "Installing agent-browser..."
|
||||
local log_file
|
||||
log_file="$(mktemp)"
|
||||
if ! "$npm_bin" install -g --prefix "$HERMES_HOME/node" --silent --ignore-scripts \
|
||||
"agent-browser@^0.26.0" \
|
||||
"@askjo/camofox-browser@^1.5.2" \
|
||||
>"$log_file" 2>&1; then
|
||||
log_error "npm install failed:"
|
||||
cat "$log_file" >&2
|
||||
rm -f "$log_file"
|
||||
return 1
|
||||
fi
|
||||
rm -f "$log_file"
|
||||
export PATH="$HERMES_HOME/node/bin:$PATH"
|
||||
|
||||
local sys_browser
|
||||
sys_browser="$(find_system_browser 2>/dev/null || true)"
|
||||
if [ -n "$sys_browser" ]; then
|
||||
configure_browser_env_from_system_browser "$sys_browser"
|
||||
log_info "System browser detected -- skipping Chromium download"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_info "Installing Chromium via agent-browser install..."
|
||||
local ab_bin="$HERMES_HOME/node/bin/agent-browser"
|
||||
if [ -x "$ab_bin" ]; then
|
||||
"$ab_bin" install 2>/dev/null || {
|
||||
log_warn "Chromium install failed. Browser tools may not work without a system browser."
|
||||
|
||||
# OS-specific hints (detect_os sets $DISTRO)
|
||||
case "${DISTRO:-unknown}" in
|
||||
ubuntu|debian)
|
||||
log_info "Try: sudo apt-get install -y chromium-browser"
|
||||
;;
|
||||
arch)
|
||||
log_info "Try: sudo pacman -S chromium"
|
||||
;;
|
||||
fedora|rhel|centos)
|
||||
log_info "Try: sudo dnf install -y chromium"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
else
|
||||
log_warn "agent-browser not found at $ab_bin"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
ensure_mode() {
|
||||
detect_os
|
||||
|
||||
|
|
@ -1901,19 +1984,7 @@ ensure_mode() {
|
|||
browser)
|
||||
check_node
|
||||
if [ "$HAS_NODE" = true ]; then
|
||||
DETECTED_BROWSER_EXECUTABLE="$(find_system_browser 2>/dev/null || true)"
|
||||
if [ -z "$DETECTED_BROWSER_EXECUTABLE" ]; then
|
||||
log_info "Installing agent-browser + Chromium..."
|
||||
npm_bin="$(command -v npm 2>/dev/null || echo "")"
|
||||
if [ -n "$npm_bin" ]; then
|
||||
local agent_browser_dir="$HERMES_HOME/node_modules"
|
||||
mkdir -p "$agent_browser_dir"
|
||||
"$npm_bin" install --prefix "$HERMES_HOME" agent-browser 2>/dev/null || true
|
||||
npx playwright install chromium 2>/dev/null || true
|
||||
fi
|
||||
else
|
||||
log_success "System browser found: $DETECTED_BROWSER_EXECUTABLE"
|
||||
fi
|
||||
ensure_browser
|
||||
fi
|
||||
;;
|
||||
ripgrep)
|
||||
|
|
@ -1948,16 +2019,7 @@ postinstall_mode() {
|
|||
install_system_packages
|
||||
|
||||
if [ "$HAS_NODE" = true ] && [ "$SKIP_BROWSER" = false ]; then
|
||||
DETECTED_BROWSER_EXECUTABLE="$(find_system_browser 2>/dev/null || true)"
|
||||
if [ -z "$DETECTED_BROWSER_EXECUTABLE" ]; then
|
||||
log_info "Installing browser engine..."
|
||||
npm_bin="$(command -v npm 2>/dev/null || echo "")"
|
||||
if [ -n "$npm_bin" ]; then
|
||||
npx playwright install chromium 2>/dev/null || true
|
||||
fi
|
||||
else
|
||||
log_success "System browser found: $DETECTED_BROWSER_EXECUTABLE"
|
||||
fi
|
||||
ensure_browser
|
||||
fi
|
||||
|
||||
HERMES_CMD="$(command -v hermes 2>/dev/null || echo "")"
|
||||
|
|
|
|||
|
|
@ -94,103 +94,62 @@ def test_main_setup_skips_browser_prompt_on_no(monkeypatch):
|
|||
assert called == []
|
||||
|
||||
|
||||
def test_main_setup_browser_invokes_bundled_script(monkeypatch):
|
||||
"""`hermes-acp --setup-browser` must shell out to the bundled bootstrap
|
||||
script — never reimplement the install logic inline."""
|
||||
monkeypatch.setattr("platform.system", lambda: "Linux")
|
||||
def test_main_setup_browser_calls_ensure_dependency(monkeypatch):
|
||||
"""`hermes-acp --setup-browser` routes through dep_ensure.ensure_dependency."""
|
||||
calls = []
|
||||
|
||||
captured = {}
|
||||
def fake_ensure(dep, interactive=True):
|
||||
calls.append((dep, interactive))
|
||||
return True
|
||||
|
||||
def fake_run(cmd, check=False):
|
||||
captured["cmd"] = cmd
|
||||
|
||||
class _R:
|
||||
returncode = 0
|
||||
|
||||
return _R()
|
||||
|
||||
monkeypatch.setattr("subprocess.run", fake_run)
|
||||
monkeypatch.setattr("hermes_cli.dep_ensure.ensure_dependency", fake_ensure)
|
||||
|
||||
entry.main(["--setup-browser"])
|
||||
|
||||
assert captured["cmd"][0] == "bash"
|
||||
assert captured["cmd"][1].endswith("bootstrap_browser_tools.sh")
|
||||
# --yes is NOT passed when the flag is absent.
|
||||
assert "--yes" not in captured["cmd"]
|
||||
assert ("node", True) in calls
|
||||
assert ("browser", True) in calls
|
||||
|
||||
|
||||
def test_main_setup_browser_forwards_yes_flag(monkeypatch):
|
||||
monkeypatch.setattr("platform.system", lambda: "Linux")
|
||||
"""--yes suppresses interactive prompts in ensure_dependency."""
|
||||
calls = []
|
||||
|
||||
captured = {}
|
||||
def fake_ensure(dep, interactive=True):
|
||||
calls.append((dep, interactive))
|
||||
return True
|
||||
|
||||
def fake_run(cmd, check=False):
|
||||
captured["cmd"] = cmd
|
||||
|
||||
class _R:
|
||||
returncode = 0
|
||||
|
||||
return _R()
|
||||
|
||||
monkeypatch.setattr("subprocess.run", fake_run)
|
||||
monkeypatch.setattr("hermes_cli.dep_ensure.ensure_dependency", fake_ensure)
|
||||
|
||||
entry.main(["--setup-browser", "--yes"])
|
||||
|
||||
assert "--yes" in captured["cmd"]
|
||||
assert ("node", False) in calls
|
||||
assert ("browser", False) in calls
|
||||
|
||||
|
||||
def test_main_setup_browser_uses_powershell_on_windows(monkeypatch):
|
||||
monkeypatch.setattr("platform.system", lambda: "Windows")
|
||||
def test_main_setup_browser_stops_on_node_failure(monkeypatch):
|
||||
"""If node install fails, browser install is not attempted."""
|
||||
calls = []
|
||||
|
||||
captured = {}
|
||||
def fake_ensure(dep, interactive=True):
|
||||
calls.append(dep)
|
||||
return dep != "node" # node fails
|
||||
|
||||
def fake_run(cmd, check=False):
|
||||
captured["cmd"] = cmd
|
||||
|
||||
class _R:
|
||||
returncode = 0
|
||||
|
||||
return _R()
|
||||
|
||||
monkeypatch.setattr("subprocess.run", fake_run)
|
||||
|
||||
entry.main(["--setup-browser", "--yes"])
|
||||
|
||||
assert captured["cmd"][0] == "powershell.exe"
|
||||
assert any(part.endswith("bootstrap_browser_tools.ps1") for part in captured["cmd"])
|
||||
assert "-Yes" in captured["cmd"]
|
||||
|
||||
|
||||
def test_main_setup_browser_propagates_failure(monkeypatch):
|
||||
monkeypatch.setattr("platform.system", lambda: "Linux")
|
||||
|
||||
class _R:
|
||||
returncode = 7
|
||||
|
||||
monkeypatch.setattr("subprocess.run", lambda cmd, check=False: _R())
|
||||
monkeypatch.setattr("hermes_cli.dep_ensure.ensure_dependency", fake_ensure)
|
||||
|
||||
with pytest.raises(SystemExit) as excinfo:
|
||||
entry.main(["--setup-browser"])
|
||||
assert excinfo.value.code == 7
|
||||
assert excinfo.value.code == 1
|
||||
assert "node" in calls
|
||||
assert "browser" not in calls
|
||||
|
||||
|
||||
def test_bootstrap_scripts_ship_with_package():
|
||||
"""The package-data wiring (pyproject.toml) must include the bootstrap
|
||||
scripts — otherwise `--setup-browser` 404s at runtime."""
|
||||
from pathlib import Path
|
||||
def test_main_setup_browser_propagates_browser_failure(monkeypatch):
|
||||
"""If browser install fails, exit code is 1."""
|
||||
def fake_ensure(dep, interactive=True):
|
||||
return dep != "browser" # browser fails
|
||||
|
||||
bootstrap_dir = Path(entry.__file__).resolve().parent / "bootstrap"
|
||||
sh = bootstrap_dir / "bootstrap_browser_tools.sh"
|
||||
ps1 = bootstrap_dir / "bootstrap_browser_tools.ps1"
|
||||
monkeypatch.setattr("hermes_cli.dep_ensure.ensure_dependency", fake_ensure)
|
||||
|
||||
assert sh.is_file(), f"missing bundled script: {sh}"
|
||||
assert ps1.is_file(), f"missing bundled script: {ps1}"
|
||||
|
||||
sh_text = sh.read_text(encoding="utf-8")
|
||||
ps1_text = ps1.read_text(encoding="utf-8")
|
||||
|
||||
# Sanity: scripts know how to find the Hermes-managed Node prefix.
|
||||
assert "HERMES_HOME" in sh_text
|
||||
assert "agent-browser" in sh_text
|
||||
assert "HermesHome" in ps1_text
|
||||
assert "agent-browser" in ps1_text
|
||||
with pytest.raises(SystemExit) as excinfo:
|
||||
entry.main(["--setup-browser"])
|
||||
assert excinfo.value.code == 1
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue