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:
@ -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:
|
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
|
Routes through dep_ensure -> install.{sh,ps1} --ensure, sharing code
|
||||||
(acp_adapter/bootstrap/bootstrap_browser_tools.{sh,ps1}) so the install
|
with ``hermes postinstall`` and the runtime lazy installer.
|
||||||
logic lives in one place — readable, debuggable, and shareable with
|
|
||||||
install.sh / install.ps1 if we ever want to call it from there too.
|
|
||||||
|
|
||||||
Returns the script's exit code (0 on success).
|
Returns 0 on success, 1 on failure.
|
||||||
"""
|
"""
|
||||||
import platform
|
from hermes_cli.dep_ensure import ensure_dependency
|
||||||
import subprocess
|
|
||||||
|
|
||||||
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:
|
try:
|
||||||
result = subprocess.run(cmd, check=False)
|
node_ok = ensure_dependency("node", interactive=not assume_yes)
|
||||||
except FileNotFoundError as exc:
|
if not node_ok:
|
||||||
# bash / powershell.exe not on PATH
|
print("Node.js installation failed — cannot proceed with browser tools.",
|
||||||
print(f"Could not launch browser bootstrap: {exc}", file=sys.stderr)
|
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 1
|
||||||
return result.returncode
|
|
||||||
|
|
||||||
|
|
||||||
def main(argv: list[str] | None = None) -> None:
|
def main(argv: list[str] | None = None) -> None:
|
||||||
|
|||||||
@ -1512,6 +1512,17 @@ find_system_browser() {
|
|||||||
fi
|
fi
|
||||||
done
|
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
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1534,10 +1545,15 @@ configure_browser_env_from_system_browser() {
|
|||||||
browser_path="$(find_system_browser 2>/dev/null || true)"
|
browser_path="$(find_system_browser 2>/dev/null || true)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "$browser_path" ] || [ ! -f "$env_file" ]; then
|
if [ -z "$browser_path" ]; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
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
|
if grep -q '^AGENT_BROWSER_EXECUTABLE_PATH=' "$env_file" 2>/dev/null; then
|
||||||
log_info "AGENT_BROWSER_EXECUTABLE_PATH already configured"
|
log_info "AGENT_BROWSER_EXECUTABLE_PATH already configured"
|
||||||
return 0
|
return 0
|
||||||
@ -1888,6 +1904,73 @@ print_success() {
|
|||||||
fi
|
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() {
|
ensure_mode() {
|
||||||
detect_os
|
detect_os
|
||||||
|
|
||||||
@ -1901,19 +1984,7 @@ ensure_mode() {
|
|||||||
browser)
|
browser)
|
||||||
check_node
|
check_node
|
||||||
if [ "$HAS_NODE" = true ]; then
|
if [ "$HAS_NODE" = true ]; then
|
||||||
DETECTED_BROWSER_EXECUTABLE="$(find_system_browser 2>/dev/null || true)"
|
ensure_browser
|
||||||
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
|
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
ripgrep)
|
ripgrep)
|
||||||
@ -1948,16 +2019,7 @@ postinstall_mode() {
|
|||||||
install_system_packages
|
install_system_packages
|
||||||
|
|
||||||
if [ "$HAS_NODE" = true ] && [ "$SKIP_BROWSER" = false ]; then
|
if [ "$HAS_NODE" = true ] && [ "$SKIP_BROWSER" = false ]; then
|
||||||
DETECTED_BROWSER_EXECUTABLE="$(find_system_browser 2>/dev/null || true)"
|
ensure_browser
|
||||||
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
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
HERMES_CMD="$(command -v hermes 2>/dev/null || echo "")"
|
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 == []
|
assert called == []
|
||||||
|
|
||||||
|
|
||||||
def test_main_setup_browser_invokes_bundled_script(monkeypatch):
|
def test_main_setup_browser_calls_ensure_dependency(monkeypatch):
|
||||||
"""`hermes-acp --setup-browser` must shell out to the bundled bootstrap
|
"""`hermes-acp --setup-browser` routes through dep_ensure.ensure_dependency."""
|
||||||
script — never reimplement the install logic inline."""
|
calls = []
|
||||||
monkeypatch.setattr("platform.system", lambda: "Linux")
|
|
||||||
|
|
||||||
captured = {}
|
def fake_ensure(dep, interactive=True):
|
||||||
|
calls.append((dep, interactive))
|
||||||
|
return True
|
||||||
|
|
||||||
def fake_run(cmd, check=False):
|
monkeypatch.setattr("hermes_cli.dep_ensure.ensure_dependency", fake_ensure)
|
||||||
captured["cmd"] = cmd
|
|
||||||
|
|
||||||
class _R:
|
|
||||||
returncode = 0
|
|
||||||
|
|
||||||
return _R()
|
|
||||||
|
|
||||||
monkeypatch.setattr("subprocess.run", fake_run)
|
|
||||||
|
|
||||||
entry.main(["--setup-browser"])
|
entry.main(["--setup-browser"])
|
||||||
|
|
||||||
assert captured["cmd"][0] == "bash"
|
assert ("node", True) in calls
|
||||||
assert captured["cmd"][1].endswith("bootstrap_browser_tools.sh")
|
assert ("browser", True) in calls
|
||||||
# --yes is NOT passed when the flag is absent.
|
|
||||||
assert "--yes" not in captured["cmd"]
|
|
||||||
|
|
||||||
|
|
||||||
def test_main_setup_browser_forwards_yes_flag(monkeypatch):
|
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):
|
monkeypatch.setattr("hermes_cli.dep_ensure.ensure_dependency", fake_ensure)
|
||||||
captured["cmd"] = cmd
|
|
||||||
|
|
||||||
class _R:
|
|
||||||
returncode = 0
|
|
||||||
|
|
||||||
return _R()
|
|
||||||
|
|
||||||
monkeypatch.setattr("subprocess.run", fake_run)
|
|
||||||
|
|
||||||
entry.main(["--setup-browser", "--yes"])
|
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):
|
def test_main_setup_browser_stops_on_node_failure(monkeypatch):
|
||||||
monkeypatch.setattr("platform.system", lambda: "Windows")
|
"""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):
|
monkeypatch.setattr("hermes_cli.dep_ensure.ensure_dependency", fake_ensure)
|
||||||
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())
|
|
||||||
|
|
||||||
with pytest.raises(SystemExit) as excinfo:
|
with pytest.raises(SystemExit) as excinfo:
|
||||||
entry.main(["--setup-browser"])
|
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():
|
def test_main_setup_browser_propagates_browser_failure(monkeypatch):
|
||||||
"""The package-data wiring (pyproject.toml) must include the bootstrap
|
"""If browser install fails, exit code is 1."""
|
||||||
scripts — otherwise `--setup-browser` 404s at runtime."""
|
def fake_ensure(dep, interactive=True):
|
||||||
from pathlib import Path
|
return dep != "browser" # browser fails
|
||||||
|
|
||||||
bootstrap_dir = Path(entry.__file__).resolve().parent / "bootstrap"
|
monkeypatch.setattr("hermes_cli.dep_ensure.ensure_dependency", fake_ensure)
|
||||||
sh = bootstrap_dir / "bootstrap_browser_tools.sh"
|
|
||||||
ps1 = bootstrap_dir / "bootstrap_browser_tools.ps1"
|
|
||||||
|
|
||||||
assert sh.is_file(), f"missing bundled script: {sh}"
|
with pytest.raises(SystemExit) as excinfo:
|
||||||
assert ps1.is_file(), f"missing bundled script: {ps1}"
|
entry.main(["--setup-browser"])
|
||||||
|
assert excinfo.value.code == 1
|
||||||
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
|
|
||||||
|
|||||||
Reference in New Issue
Block a user