Files
hermes-agent/setup-hermes.ps1
ethernet fffccbb2ae fix(setup): prepare native Windows ARM64 build dependencies
Source activation could not build cryptography because the setup shell
could not discover the installed OpenSSL development libraries.

Configure Visual Studio ARM64, Clang, Rust, and static OpenSSL before PM
runs. Reuse installed tools and install missing prerequisites. Select a
classic vcpkg with a ports tree and use an explicit installation root.
Report damaged shared libraries without deleting the shared installation.

Verified native PowerShell activation and deactivation on Promise, then
warm activation with no new dependency generation. Cryptography imported
with static OpenSSL. Real vcpkg checks covered installation into a path
with spaces, warm reuse, manifest mode, and damaged-package rejection.
The canonical Windows runner passed the helper and output-encoding tests.

Fresh Visual Studio and Rust installation were not exercised because
Promise already had those toolchains installed.
2026-09-11 13:29:45 -04:00

148 lines
6.8 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
}
# ---------------------------------------------------------------------------
# ARM64 source wheels need the native compiler and OpenSSL development libraries.
# Activation runs setup in a child, so these build variables do not leak into its caller.
if ($arch -eq 'arm64') {
. (Join-Path $repo 'scripts\windows-build-deps.ps1')
Initialize-HermesArm64BuildTools -StateRoot (Split-Path $store -Parent)
}
# Delegate to pm: python + venv + tool store + hash-verified venv sync
# ---------------------------------------------------------------------------
Write-Host 'Installing python + tools + 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 {
# 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' }
& $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 ''