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.
167 lines
7.7 KiB
PowerShell
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 ''
|