146 lines
7.0 KiB
PowerShell
146 lines
7.0 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.
|
|
# ============================================================================
|
|
# Setup and activation both prepare the isolated test interpreter; installers
|
|
# invoke pm.cli directly and do not select it. -TestExtras overrides coverage.
|
|
param([switch]$RuntimeOnly, [string]$TestExtras = '')
|
|
$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
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# PM installs the tools, then the venv. On ARM64 PM prepares the compiler and
|
|
# OpenSSL environment for that sync itself (pm/native_build.py), after its own
|
|
# git is published, so every install path builds the same way.
|
|
# Activation trusts the recorded tool digest. A direct setup re-checks it.
|
|
# ---------------------------------------------------------------------------
|
|
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 --no-registry $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 ($RuntimeOnly) { '--trust-recorded' }) "--test-environment=$TestExtras"
|
|
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 ''
|