Finish bootstrap uv before PM replaces its store entry. Keep failure receipts stdlib-only and align the cryptography requirement and override with the locked version. Let bundle builders declare launch paths and update ownership. Remove payload discovery, Store probing, and the unused develop command. Derive Nix Python from the PM lock and share its provenance stamp. Document setup, activation, optional dependencies, and distribution ownership. Targeted Windows tests, relocated runtime launches, Electron bundling, and bilingual docs builds pass. Native Nix and signed-package acceptance remain CI gates.
138 lines
6.5 KiB
PowerShell
138 lines
6.5 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.
|
|
# ============================================================================
|
|
$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
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 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
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 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 ''
|