Merge branch 'bb/gui' of github.com:NousResearch/hermes-agent into bb/gui

This commit is contained in:
Brooklyn Nicholson 2026-05-11 15:28:51 -04:00
commit 50a9d6333f
4 changed files with 368 additions and 1 deletions

View file

@ -22,6 +22,22 @@ If you're hacking on Hermes from a clone outside `HERMES_HOME/hermes-agent`, poi
HERMES_DESKTOP_HERMES_ROOT=/path/to/your/clone npm run dev
```
### Runtime prerequisites
Hermes Desktop needs:
- **Python 3.11+** — for the agent runtime, dashboard backend, and tool execution.
- **Git for Windows** (Windows only) — provides Git Bash, which Hermes' terminal tool calls directly. Linux and macOS already ship a system bash.
The packaged Windows installer (`Hermes-*.exe`) detects both at install time. If either is missing it offers to install them via `winget install -e --id Python.Python.3.11` and `winget install -e --id Git.Git`. If `winget` isn't available the installer shows manual download URLs and lets you continue. The MSI installer (`Hermes-*.msi`) doesn't run the prereq page — enterprise deploys are expected to handle prereqs out-of-band.
For dev (`npm run dev`) the same checks happen at first launch via the Electron bootstrapper, which throws a clear error if either prereq is missing. Manual install commands you can run yourself:
```powershell
winget install -e --id Python.Python.3.11
winget install -e --id Git.Git
```
## Development
```bash

View file

@ -440,6 +440,46 @@ function findSystemPython() {
return null
}
// findGitBash — locate bash.exe on Windows. Hermes' terminal tool requires
// bash (POSIX shell), and on Windows that's almost always Git for Windows'
// bundled Git Bash. We check the same set of locations tools/environments/
// local.py:_find_bash() checks at runtime, so a positive result here means
// the agent will be able to start a terminal too.
//
// On non-Windows hosts bash is part of the OS and this just returns the
// first bash on PATH.
function findGitBash() {
if (!IS_WINDOWS) {
return findOnPath('bash')
}
// install.ps1 drops PortableGit at %LOCALAPPDATA%\hermes\git\... — checked
// first so users who installed via install.ps1 are detected before we
// start probing system-wide locations.
const localAppData = process.env.LOCALAPPDATA || ''
const candidates = []
if (localAppData) {
candidates.push(path.join(localAppData, 'hermes', 'git', 'bin', 'bash.exe'))
candidates.push(path.join(localAppData, 'hermes', 'git', 'usr', 'bin', 'bash.exe'))
}
// Standard Git for Windows install locations.
candidates.push(path.join(process.env['ProgramFiles'] || 'C:\\Program Files', 'Git', 'bin', 'bash.exe'))
candidates.push(path.join(process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)', 'Git', 'bin', 'bash.exe'))
if (localAppData) {
candidates.push(path.join(localAppData, 'Programs', 'Git', 'bin', 'bash.exe'))
}
for (const candidate of candidates) {
if (fileExists(candidate)) return candidate
}
// Last resort — bash on PATH (covers WSL bash, MSYS2, custom installs).
// On WSL hosts findOnPath itself filters out Windows-binary paths via
// isWindowsBinaryPathInWsl, so we won't hand back a wsl.exe shim either.
return findOnPath('bash')
}
function getVenvPython(venvRoot) {
return path.join(venvRoot, IS_WINDOWS ? path.join('Scripts', 'python.exe') : path.join('bin', 'python'))
}
@ -953,6 +993,22 @@ async function ensureRuntime(backend) {
await runProcess(systemPython, ['-m', 'venv', VENV_ROOT])
}
// Step 2b: On Windows, preflight Git Bash. Hermes' terminal tool calls
// bash.exe directly (tools/environments/local.py); without it the agent
// can't run a terminal command. We surface this here as a clear, actionable
// error rather than letting the user discover it on their first chat
// ("hey, run `ls`" → opaque tool failure). The NSIS prereq page handles
// this for installer users; this check catches everyone else (.msi users,
// npm run dev with a fresh checkout, manual installs, etc.).
if (IS_WINDOWS && !findGitBash()) {
throw new Error(
'Git for Windows is required for Hermes on Windows (provides Git Bash, ' +
"which the agent's terminal tool uses). Install it from " +
'https://git-scm.com/download/win or run `winget install -e --id Git.Git`, ' +
'then relaunch Hermes.'
)
}
// Step 3: Ensure deps are installed. We compare a marker against the
// active pyproject.toml's hash and only run pip when something changed —
// keeps `npm run dev` boots fast on a stable repo.

View file

@ -0,0 +1,293 @@
; ============================================================================
; Hermes Desktop installer prerequisite detection page
; ============================================================================
;
; A native NSIS Wizard page (using nsDialogs) inserted between the directory
; selection page and the install-files page. Detects Python 3.11+ and Git
; for Windows; offers to install missing prereqs via winget.
;
; Page sequence:
; Welcome Directory [PrereqPage] InstFiles Finish
;
; Hooks used:
; customPageAfterChangeDir page declaration (electron-builder's hook for
; inserting a page between Directory and InstFiles)
; customInstall execute winget for any prereqs the user
; checked on the page
;
; The Function declarations live at top-level in this file so they're parsed
; at include time; the customPageAfterChangeDir macro references them via
; the Page directive so the optimizer doesn't strip them. customInstall has
; a defensive runtime reference too, in case the customPageAfterChangeDir
; hook isn't expanded by some future electron-builder version.
;
; UAC behavior:
; Python is installed with --scope user (no UAC prompt).
; Git for Windows always installs per-machine and triggers a UAC prompt.
; We pre-warn the user via the page footer; the UAC dialog may appear
; behind the installer, so BringToFront is called after each winget run.
;
; Detection:
; Python: try `py -3.11`, `py -3.12`, `py -3.13`, `py -3.14` in order.
; The Python launcher returns exit 0 only when that specific version is
; installed. The Microsoft Store "Python stub" doesn't install py.exe,
; so users with only the stub get correctly classified as not-installed.
;
; Git: `where git` returns exit 0 if git is on PATH.
;
; winget: `where winget` returns exit 0 on Win11 / Win10 1809+ with App
; Installer. If unavailable, the page shows manual download URLs.
;
; Skip behaviors:
; - Both prereqs already installed page is auto-skipped via Abort
; - Silent install (/S) customInstall winget block skips
; - User unchecks both checkboxes page advances without running winget
; ============================================================================
!include "LogicLib.nsh"
!include "nsDialogs.nsh"
!include "WinMessages.nsh"
Var HermesDialog
Var HermesPyStatusLabel
Var HermesPyCheckbox
Var HermesGitStatusLabel
Var HermesGitCheckbox
Var HermesFooterLabel
Var HermesHasWinget
Var HermesHasPython
Var HermesHasGit
Var HermesInstallPython
Var HermesInstallGit
; ----------------------------------------------------------------------------
; HermesDetectPrereqs populates $HermesHasWinget / $HermesHasPython /
; $HermesHasGit with "0" or "1". Called from the page-create function.
; ----------------------------------------------------------------------------
Function HermesDetectPrereqs
; --- winget ---
nsExec::Exec 'cmd.exe /c where winget >nul 2>&1'
Pop $0
${If} $0 == 0
StrCpy $HermesHasWinget "1"
${Else}
StrCpy $HermesHasWinget "0"
${EndIf}
; --- Python 3.11+ ---
; The py launcher returns exit 0 only when that specific version is
; installed. We probe each version Hermes' pyproject.toml accepts.
StrCpy $HermesHasPython "0"
nsExec::Exec 'cmd.exe /c py -3.11 --version >nul 2>&1'
Pop $0
${If} $0 == 0
StrCpy $HermesHasPython "1"
${Else}
nsExec::Exec 'cmd.exe /c py -3.12 --version >nul 2>&1'
Pop $0
${If} $0 == 0
StrCpy $HermesHasPython "1"
${Else}
nsExec::Exec 'cmd.exe /c py -3.13 --version >nul 2>&1'
Pop $0
${If} $0 == 0
StrCpy $HermesHasPython "1"
${Else}
nsExec::Exec 'cmd.exe /c py -3.14 --version >nul 2>&1'
Pop $0
${If} $0 == 0
StrCpy $HermesHasPython "1"
${EndIf}
${EndIf}
${EndIf}
${EndIf}
; --- Git ---
nsExec::Exec 'cmd.exe /c where git >nul 2>&1'
Pop $0
${If} $0 == 0
StrCpy $HermesHasGit "1"
${Else}
StrCpy $HermesHasGit "0"
${EndIf}
FunctionEnd
; ----------------------------------------------------------------------------
; HermesPrereqPageCreate builds the prereq page UI. If both prereqs are
; already installed we Abort, which causes NSIS to skip directly to the next
; page in the sequence (InstFiles).
; ----------------------------------------------------------------------------
Function HermesPrereqPageCreate
Call HermesDetectPrereqs
${If} $HermesHasPython == "1"
${AndIf} $HermesHasGit == "1"
Abort
${EndIf}
nsDialogs::Create 1018
Pop $HermesDialog
${If} $HermesDialog == error
Abort
${EndIf}
StrCpy $HermesInstallPython "0"
StrCpy $HermesInstallGit "0"
; Page title (bold) and subtitle. We can't use MUI_HEADER_TEXT here
; electron-builder's NSIS template configures MUI internally but doesn't
; expose the header-text macros to user includes. So we render our own
; title in the page body using a label with bold font.
${NSD_CreateLabel} 0u 0u 100% 10u "System Requirements"
Pop $0
CreateFont $1 "$(^Font)" "10" "700"
SendMessage $0 ${WM_SETFONT} $1 0
${NSD_CreateLabel} 0u 12u 100% 18u "Hermes Agent needs Python 3.11+ and Git for Windows to run. Items already installed are listed as detected; missing items can be installed automatically."
Pop $0
; --- Python panel ---
${NSD_CreateGroupBox} 0u 34u 100% 32u "Python 3.11+"
Pop $0
${If} $HermesHasPython == "1"
${NSD_CreateLabel} 8u 46u 95% 12u "Detected on your system."
Pop $HermesPyStatusLabel
${Else}
${If} $HermesHasWinget == "1"
${NSD_CreateLabel} 8u 44u 95% 9u "Not detected."
Pop $HermesPyStatusLabel
${NSD_CreateCheckbox} 8u 54u 95% 10u "Install Python 3.11 (per-user install, no admin prompt)"
Pop $HermesPyCheckbox
${NSD_Check} $HermesPyCheckbox
${Else}
${NSD_CreateLabel} 8u 44u 95% 20u "Not detected. Install manually from https://www.python.org/downloads/ and re-run this installer."
Pop $HermesPyStatusLabel
${EndIf}
${EndIf}
; --- Git panel ---
${NSD_CreateGroupBox} 0u 70u 100% 32u "Git for Windows (provides Git Bash)"
Pop $0
${If} $HermesHasGit == "1"
${NSD_CreateLabel} 8u 82u 95% 12u "Detected on your system."
Pop $HermesGitStatusLabel
${Else}
${If} $HermesHasWinget == "1"
${NSD_CreateLabel} 8u 80u 95% 9u "Not detected. Required by Hermes' terminal tool."
Pop $HermesGitStatusLabel
${NSD_CreateCheckbox} 8u 90u 95% 10u "Install Git for Windows (administrator approval required)"
Pop $HermesGitCheckbox
${NSD_Check} $HermesGitCheckbox
${Else}
${NSD_CreateLabel} 8u 80u 95% 20u "Not detected. Install manually from https://git-scm.com/download/win and re-run this installer."
Pop $HermesGitStatusLabel
${EndIf}
${EndIf}
; --- Footer (UAC notice when Git install will run) ---
${If} $HermesHasGit == "0"
${AndIf} $HermesHasWinget == "1"
${NSD_CreateLabel} 0u 108u 100% 30u "Note: installing Git for Windows requires administrator approval. The User Account Control prompt may appear behind this window — use the taskbar to find it if needed."
Pop $HermesFooterLabel
${EndIf}
nsDialogs::Show
FunctionEnd
; ----------------------------------------------------------------------------
; HermesPrereqPageLeave read checkbox states when the user clicks Next.
; Variables stay at "0" if a checkbox doesn't exist (because the
; corresponding prereq is already installed or winget isn't available).
; ----------------------------------------------------------------------------
Function HermesPrereqPageLeave
${If} $HermesHasPython == "0"
${AndIf} $HermesHasWinget == "1"
${NSD_GetState} $HermesPyCheckbox $HermesInstallPython
${EndIf}
${If} $HermesHasGit == "0"
${AndIf} $HermesHasWinget == "1"
${NSD_GetState} $HermesGitCheckbox $HermesInstallGit
${EndIf}
FunctionEnd
; ----------------------------------------------------------------------------
; Page declaration inserted between the Directory page and InstFiles via
; the customPageAfterChangeDir hook (defined in
; node_modules/app-builder-lib/templates/nsis/assistedInstaller.nsh, included
; whenever build.nsis.oneClick=false).
;
; Note: NSIS's optimizer emits "warning 6010: install function ... not
; referenced" for these functions because Page custom directives don't count
; as references in the optimizer's reference-tracking pass. We set
; build.nsis.warningsAsErrors=false in package.json so this warning doesn't
; fail the build. The functions ARE actually called by NSIS at page-display
; time the optimizer just can't see it statically.
; ----------------------------------------------------------------------------
!macro customPageAfterChangeDir
Page custom HermesPrereqPageCreate HermesPrereqPageLeave
!macroend
; ----------------------------------------------------------------------------
; customInstall runs the actual winget commands for whatever prereqs the
; user checked on the page. Output streams to the install progress log.
; ----------------------------------------------------------------------------
!macro customInstall
; Skip on silent installs (managed deploys handle prereqs out-of-band).
IfSilent hermes_prereq_install_done
${If} $HermesInstallPython == "1"
; Python with --scope user installs to %LOCALAPPDATA%\Programs\Python\
; no UAC, no foreground chain to preserve. nsExec::ExecToLog gives
; us live output streaming to the install log.
DetailPrint "Installing Python 3.11+ via winget (silent per-user install, no admin prompt)..."
nsExec::ExecToLog 'winget install -e --id Python.Python.3.11 --scope user --silent --disable-interactivity --accept-package-agreements --accept-source-agreements'
Pop $0
${If} $0 != 0
DetailPrint "Python install via winget exited with code $0."
MessageBox MB_OK|MB_ICONEXCLAMATION|MB_TOPMOST "Python install via winget did not complete successfully (exit code $0).$\r$\n$\r$\nYou can install Python 3.11+ manually from https://www.python.org/downloads/ after Hermes setup finishes. Hermes will not run until Python is installed."
${Else}
DetailPrint "Python 3.11+ installed successfully."
${EndIf}
${EndIf}
${If} $HermesInstallGit == "1"
; Git for Windows always installs per-machine and triggers UAC. We use
; ExecShellWait (NSIS's wrapper around Windows ShellExecute) instead of
; nsExec::ExecToLog because ShellExecute preserves the foreground focus
; chain across non-elevated elevated process spawns. With nsExec the
; intermediate hidden winget.exe breaks that chain and UAC ends up
; behind the installer window.
;
; Trade-off: ExecShellWait doesn't capture output, so winget runs in
; its own console window. The console flashes briefly while winget
; downloads, then UAC fires for the elevated Git installer with
; correct foreground promotion.
DetailPrint "Installing Git for Windows via winget (UAC prompt will appear)..."
ExecShellWait "open" "winget" "install -e --id Git.Git --silent --disable-interactivity --accept-package-agreements --accept-source-agreements" SW_SHOWNORMAL
; ExecShellWait returns no exit code, so verify by checking the file
; system directly. Don't use `where git` — that reads OUR process's
; PATH, which was captured at NSIS startup before Git's installer ran
; and modified the system PATH. Until we restart, the new PATH isn't
; visible to us. Probe Git's standard install locations instead.
StrCpy $0 "0" ; "git found" flag
${If} ${FileExists} "$PROGRAMFILES64\Git\bin\bash.exe"
StrCpy $0 "1"
${ElseIf} ${FileExists} "$PROGRAMFILES\Git\bin\bash.exe"
StrCpy $0 "1"
${ElseIf} ${FileExists} "$PROGRAMFILES32\Git\bin\bash.exe"
StrCpy $0 "1"
${ElseIf} ${FileExists} "$LOCALAPPDATA\Programs\Git\bin\bash.exe"
StrCpy $0 "1"
${EndIf}
${If} $0 == "1"
DetailPrint "Git for Windows installed successfully."
${Else}
DetailPrint "Git for Windows install did not complete (bash.exe not found at standard install locations)."
MessageBox MB_OK|MB_ICONEXCLAMATION|MB_TOPMOST "Git for Windows install via winget did not complete successfully.$\r$\n$\r$\nYou can install Git for Windows manually from https://git-scm.com/download/win after Hermes setup finishes. Hermes' terminal tool will not work until Git Bash is available."
${EndIf}
${EndIf}
hermes_prereq_install_done:
!macroend

View file

@ -186,7 +186,9 @@
"allowToChangeInstallationDirectory": true,
"perMachine": false,
"shortcutName": "Hermes",
"uninstallDisplayName": "Hermes"
"uninstallDisplayName": "Hermes",
"include": "installer/prereq-check.nsh",
"warningsAsErrors": false
}
}
}