fix(install.ps1): pin uv sync to venv\, verify baseline imports on Windows (#25755)

* fix(cli): allow rotating broken OpenRouter / AI Gateway key in `hermes model` flow

Before: when `OPENROUTER_API_KEY` (or `AI_GATEWAY_API_KEY`) was already
set in ~/.hermes/.env, `hermes model openrouter` / `hermes model
ai-gateway` skipped the API-key prompt entirely and jumped straight to
the model picker. Users with a broken / expired / wrong key had no way
to replace it without editing ~/.hermes/.env by hand or re-running
`hermes setup` from scratch.

Both flows now route through the existing `_prompt_api_key()` helper,
which surfaces [K]eep / [R]eplace / [C]lear when a key is already
configured — the same UX the generic API-key providers (z.ai, MiniMax,
Gemini, etc.) and the Daytona setup already use.

* fix(install.ps1): pin uv sync target to venv\, verify baseline imports

Two related Windows-installer bugs that produce a broken venv with
`ModuleNotFoundError: No module named 'dotenv'` on first `hermes` run.

## Bug 1: uv sync ignores VIRTUAL_ENV, syncs into .venv\ instead of venv\

`Install-Dependencies` creates the venv at `venv\` via `uv venv venv`,
sets `$env:VIRTUAL_ENV = "$InstallDir\venv"`, then runs
`uv sync --extra all --locked`. Modern uv (>=0.5) ignores `VIRTUAL_ENV`
for the `sync` subcommand and uses the project default `.venv\`
instead. Result: deps land in `$InstallDir\.venv\`, `venv\` stays
empty except for the python.exe stub from the earlier `uv venv` call,
`hermes.exe` ends up wired to the wrong site-packages.

The bash installer (`scripts/install.sh`) already worked around this in
`install_deps()` line 1127 by passing `UV_PROJECT_ENVIRONMENT` — that
flag tells uv exactly where to put the project env regardless of
`VIRTUAL_ENV`. Port the same fix to PowerShell.

## Bug 2: no post-install verification

If the sync still misdirects for any other reason (uv version drift,
filesystem quirk, user re-run scenarios), the installer reports success
and the user only finds out by running `hermes` and getting an
unhelpful traceback. Add a baseline-import probe that runs the venv's
own python against the four packages every `hermes` invocation needs
(`dotenv`, `openai`, `rich`, `prompt_toolkit`). On failure, throw
with a recovery command tailored to whether a sibling `.venv\` exists.

User report (Windows 11, Python 3.13.5, Hermes v0.13.0): manual repro
steps were exactly this — `uv sync` landed in `.venv\`, recovered by
junctioning `venv\` → `.venv\` to bridge the path mismatch.
This commit is contained in:
Teknium 2026-05-14 07:39:13 -07:00 committed by GitHub
parent 17e0e9d174
commit 524490a409
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -813,6 +813,14 @@ function Install-Dependencies {
# needs `make` to build from sdist) and the
# install fails.
# --extra all = just the [all] extra's contents (curated).
#
# UV_PROJECT_ENVIRONMENT pins the sync target to our venv\.
# Without it, modern uv (>=0.5) ignores VIRTUAL_ENV for `sync`
# and creates a sibling .venv\ inside the repo — leaving venv\
# empty and producing the broken state where `hermes.exe` exists
# in the wrong directory and imports fail with ModuleNotFoundError.
# (Mirrors the same flag in scripts/install.sh::install_deps.)
$env:UV_PROJECT_ENVIRONMENT = "$InstallDir\venv"
& $UvCmd sync --extra all --locked
if ($LASTEXITCODE -eq 0) {
Write-Success "Main package installed (hash-verified via uv.lock)"
@ -902,6 +910,31 @@ except Exception:
throw "Failed to install hermes-agent package even with no extras. Inspect the uv pip install output above."
}
# Baseline-import gate. Even if a tier reported success above, the
# actual deps may have landed somewhere other than $InstallDir\venv\
# (e.g. uv 0.5+ syncing into a sibling .venv\ when UV_PROJECT_ENVIRONMENT
# isn't set, leaving venv\ empty and hermes.exe broken with
# `ModuleNotFoundError: No module named 'dotenv'` on first run).
# We probe via the venv's own python so a misdirected sync is caught
# here, not 30 seconds later when the user runs `hermes`.
if (-not $NoVenv) {
$venvPython = "$InstallDir\venv\Scripts\python.exe"
if (-not (Test-Path $venvPython)) {
throw "Install reported success but $venvPython does not exist. The dependency sync likely landed in a sibling .venv\ directory. Re-run the installer; if it persists, manually: cd '$InstallDir'; Remove-Item -Recurse -Force venv,.venv; uv venv venv --python $PythonVersion; `$env:UV_PROJECT_ENVIRONMENT='$InstallDir\venv'; uv sync --extra all --locked"
}
& $venvPython -c "import dotenv, openai, rich, prompt_toolkit" 2>&1 | Out-Null
if ($LASTEXITCODE -ne 0) {
$sibling = "$InstallDir\.venv"
$hint = if (Test-Path $sibling) {
"Detected sibling .venv\ at $sibling — uv synced there instead of venv\. Recover with: cd '$InstallDir'; Remove-Item -Recurse -Force venv; Move-Item .venv venv"
} else {
"Recover with: cd '$InstallDir'; `$env:UV_PROJECT_ENVIRONMENT='$InstallDir\venv'; uv sync --extra all --locked"
}
throw "Baseline imports failed in $InstallDir\venv (dotenv/openai/rich/prompt_toolkit). The install completed but dependencies are not in the venv. $hint"
}
Write-Success "Baseline imports verified in venv"
}
# Verify the dashboard deps specifically — they're the most common thing
# users hit and lazy-import errors from `hermes dashboard` are confusing.
# If tier 1 failed (the common case), [web] was still picked up by tiers