Files
hermes-agent/setup-hermes.ps1
ethernet 9c11bb96bd fix(pm): put tools on PATH before the venv sync
Windows PowerShell 5.1 returns every match from Get-Command. .Source on
that array joins the paths with a space, and the call operator then treats
the joined string as one program name. Git for Windows ships git.exe in
cmd\ and bin\, so setup died with CommandNotFoundException before pm
install ran.

setup-hermes.ps1 now installs the tool closure first (`pm install
--tools-only`), then prepares the ARM64 compiler environment, then syncs
the venv. The sync inherits that compiler environment. A bare `pm install`
and the update takeover path publish tools and put them on PATH before
uv sync. A missing tool stops the sync. A missing venv does not.

Verified: scripts/run_tests.sh on test_install_default_closure.py,
test_install_extra.py, and test_windows_build_deps.py — 13 passed.
2026-09-22 12:33:02 -04:00

167 lines
7.7 KiB
PowerShell

# ============================================================================
# Hermes Agent Setup Script (Windows) — THE dev-environment entry point.
# ============================================================================
# Sets up the pm-managed development environment from a fresh clone:
# 1. Stage the pinned uv from pm/lock.json (sha256-verified, into the pm
# store slot) - pm needs uv to bootstrap, so it cannot stage uv itself.
# 2. Use uv to install and locate bootstrap Python, then let uv exit.
# Run `python -m pm.cli install` directly so PM can safely replace uv.
# PM owns the final interpreter, tool store, and dependency generation.
# 3. Point you at `.\activate.ps1` - the venv-style way to put the pm env
# (PATH + tool vars) into your current session.
# ============================================================================
param([switch]$RuntimeOnly)
$ErrorActionPreference = 'Stop'
Write-Host ''
Write-Host 'Hermes Agent Setup' -ForegroundColor Cyan
Write-Host ''
$repo = $PSScriptRoot
$lockPath = Join-Path $repo 'pm/lock.json'
if (-not (Test-Path $lockPath)) { throw 'pm/lock.json not found' }
$lock = Get-Content -Raw $lockPath | ConvertFrom-Json
$machineArch = (Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment').PROCESSOR_ARCHITECTURE
$arch = if ($machineArch -eq 'ARM64') { 'arm64' } else { 'x64' }
$target = "win32-$arch"
# ---------------------------------------------------------------------------
# Stage the pinned uv from pm/lock.json into the pm store slot
# ---------------------------------------------------------------------------
$uvPin = $lock.packages.uv
if (-not $uvPin) { throw 'no uv pin in pm/lock.json' }
$artifact = $uvPin.artifacts.$target
if (-not $artifact) { $artifact = $uvPin.artifacts.any }
if (-not $artifact) { throw "no uv artifact for $target" }
$pyPin = $lock.packages.python
$pyVersion = if ($pyPin) { ($pyPin.version -split '\+')[0] -replace '^(\d+\.\d+).*', '$1' } else { '3.11' }
$store = if ($env:HERMES_RUNTIME_DIR) { $env:HERMES_RUNTIME_DIR } else { Join-Path $HOME '.hermes/tools' }
$entry = Join-Path $store "uv-$($uvPin.version)-$target"
$uv = Join-Path $entry 'uv.exe'
if (Test-Path $uv) {
Write-Host ("pinned uv found: " + (& $uv --version)) -ForegroundColor Green
} else {
Write-Host "Staging pinned uv $($uvPin.version) ($target) into the pm store..." -ForegroundColor Cyan
$tmp = Join-Path ([System.IO.Path]::GetTempPath()) ("hermes-setup-" + [guid]::NewGuid().ToString('n'))
New-Item -ItemType Directory -Path $tmp | Out-Null
try {
$archive = Join-Path $tmp ([uri]$artifact.url).Segments[-1]
Invoke-WebRequest -Uri $artifact.url -OutFile $archive
$got = (Get-FileHash -Algorithm SHA256 $archive).Hash.ToLowerInvariant()
if ($got -ne $artifact.sha256) {
throw "sha256 mismatch for uv (got $got, pinned $($artifact.sha256))"
}
$tree = Join-Path $tmp 'tree'
Expand-Archive -Path $archive -DestinationPath $tree
# flatten a single wrapping dir
$inner = @(Get-ChildItem $tree)
$src = if ($inner.Count -eq 1 -and $inner[0].PSIsContainer) { $inner[0].FullName } else { $tree }
New-Item -ItemType Directory -Force -Path $store | Out-Null
if (Test-Path $entry) { Remove-Item -Recurse -Force $entry }
Move-Item $src $entry
} finally {
Remove-Item -Recurse -Force $tmp -ErrorAction SilentlyContinue
}
Write-Host ("uv installed: " + (& $uv --version)) -ForegroundColor Green
}
# ---------------------------------------------------------------------------
# Tools first, then the compiler environment, then the venv sync.
# ARM64 source wheels need the native compiler and OpenSSL development
# libraries, and that setup shells out to git. The git it finds must be the
# one pm just installed, not a host install whose PATH has two git.exe files
# (cmd\ and bin\). Activation runs setup in a child, so these build variables
# do not leak into its caller.
# ---------------------------------------------------------------------------
Write-Host 'Installing python + tools via pm...' -ForegroundColor Cyan
Push-Location $repo
try {
# PM can replace its uv entry only after the bootstrap uv has exited.
& $uv python install --no-bin $pyVersion
if ($LASTEXITCODE -ne 0) { throw 'bootstrap Python installation failed' }
$bootPy = (& $uv python find --managed-python $pyVersion) -join "`n"
if ($LASTEXITCODE -ne 0 -or -not $bootPy) { throw 'bootstrap Python lookup failed' }
# The closure pm install would provision, minus the venv. A bare
# `pm install` also syncs the venv, and that sync must not run until the
# compiler environment below is on PATH.
& $bootPy.Trim() -m pm.cli install --tools-only
if ($LASTEXITCODE -ne 0) { throw 'pm tool install failed - see output above.' }
} finally {
Pop-Location
}
Write-Host 'Tools installed' -ForegroundColor Green
if ($arch -eq 'arm64') {
. (Join-Path $repo 'scripts\windows-build-deps.ps1')
Initialize-HermesArm64BuildTools -StateRoot (Split-Path $store -Parent)
}
# The venv sync. Tools are already on PATH inside that process (pm install
# publishes them before syncing); the compiler env set above is inherited.
# ---------------------------------------------------------------------------
Write-Host 'Installing dependencies via pm (hash-verified via uv.lock)...' -ForegroundColor Cyan
Write-Host '(first run on a fresh checkout can take 1-5 minutes)'
Push-Location $repo
try {
& $bootPy.Trim() -m pm.cli install
if ($LASTEXITCODE -ne 0) { throw 'pm install failed - see output above.' }
} finally {
Pop-Location
}
Write-Host 'Tools + dependencies installed (hash-verified via pm + uv.lock)' -ForegroundColor Green
if ($RuntimeOnly) { exit 0 }
# ---------------------------------------------------------------------------
# Environment file
# ---------------------------------------------------------------------------
$envFile = Join-Path $repo '.env'
if (-not (Test-Path $envFile)) {
if (Test-Path (Join-Path $repo '.env.example')) {
Copy-Item (Join-Path $repo '.env.example') $envFile
Write-Host 'Created .env from template' -ForegroundColor Green
}
} else {
Write-Host '.env exists' -ForegroundColor Green
}
# ---------------------------------------------------------------------------
# Seed bundled skills into ~/.hermes/skills/
# ---------------------------------------------------------------------------
$skillsDir = if ($env:HERMES_HOME) { Join-Path $env:HERMES_HOME 'skills' } else { Join-Path $HOME '.hermes/skills' }
New-Item -ItemType Directory -Force -Path $skillsDir | Out-Null
$sync = Join-Path $repo 'tools/skills_sync.py'
$venvPy = Join-Path $repo 'venv/Scripts/python.exe'
if ((Test-Path $sync) -and (Test-Path $venvPy)) {
& $venvPy $sync 2>$null
Write-Host 'Skills synced' -ForegroundColor Green
}
# ---------------------------------------------------------------------------
# Done
# ---------------------------------------------------------------------------
Write-Host ''
Write-Host 'Setup complete!' -ForegroundColor Green
Write-Host ''
Write-Host 'Next steps:'
Write-Host ''
Write-Host ' 1. Activate the dev environment (venv-style, in THIS session):'
Write-Host ' .\activate.ps1'
Write-Host ''
Write-Host ' 2. Run the setup wizard to configure API keys:'
Write-Host ' hermes setup'
Write-Host ''
Write-Host ' 3. Start chatting:'
Write-Host ' hermes'
Write-Host ''
Write-Host 'Other commands:'
Write-Host ' hermes pm install # Re-run the tool + dependency install'
Write-Host ' hermes status # Check configuration'
Write-Host ' hermes doctor # Diagnose issues'
Write-Host ' deactivate # Undo the activation (restore PATH etc.)'
Write-Host ''