feat(desktop): add ripgrep to NSIS prereq page + polish layout

Add ripgrep as a third (recommended) prereq alongside Python and Git in
the NSIS prereq detection page, and clean up the page layout based on
on-VM testing.

Why ripgrep
- Hermes' search_files tool calls `rg` directly for content + filename
  search (tools/file_operations.py:1382). Falls back to grep/find from
  Git Bash when missing — works but slower and noisier (no .gitignore
  awareness).
- ~5MB winget install via `BurntSushi.ripgrep.MSVC --scope user` — no
  UAC prompt, parallel to how Python installs.
- scripts/install.ps1 already installs ripgrep as part of
  Install-SystemPackages; this brings the desktop installer to parity.

Why "recommended" not "required"
- Python and Git are hard requirements: without them the agent runtime
  or terminal tool refuses to start. The bootstrapper preflight throws.
- ripgrep is a performance enhancement: missing it just means slower
  searches. Page wording reflects this; failure to install is logged
  but doesn't show a MessageBox or block.

Layout polish (response to on-VM screenshot review)
- Wizard header now correctly reads "System Requirements" instead of
  the leftover "Choose Install Location" from the previous page. Set
  via `GetDlgItem $HWNDPARENT 1037/1038` + WM_SETTEXT — the standard
  NSIS pattern for overriding the page header on a custom Page.
- Removed redundant in-body title + verbose intro paragraph; the
  wizard header IS the title now. Body has one short intro line.
- Group boxes tightened to 26u with content positioned just below the
  groupbox title (not top-anchored status + bottom-anchored checkbox
  with empty space in the middle). All three panels + footer fit
  comfortably in 126u, well under the 140u page limit.
- Checkbox labels simplified: dropped "(per-user, no admin prompt)"
  and "(administrator approval required)" suffixes. The footer note
  still calls out UAC for Git when relevant.
- Footer text trimmed to fit cleanly without clipping.

Install order (in customInstall macro)
- Python → ripgrep → Git
- Python and ripgrep are silent and run first; Git's UAC prompt comes
  last so the user's approval interaction isn't interrupted by silent
  activity afterwards.

Skip behavior unchanged
- All three detected → page auto-skips via Abort
- Silent install (/S) → customInstall winget block skips
- User unchecks all → page advances without running winget

Files
- apps/desktop/installer/prereq-check.nsh: ripgrep detection block,
  ripgrep page panel + checkbox, ripgrep customInstall block,
  GetDlgItem header override, layout reflow
- apps/desktop/README.md: Runtime prerequisites section updated to
  list ripgrep as recommended, with manual winget command
This commit is contained in:
emozilla 2026-05-11 16:40:47 -04:00
parent 1270f50e8b
commit 32f0fde35c
2 changed files with 112 additions and 48 deletions

View file

@ -26,16 +26,18 @@ HERMES_DESKTOP_HERMES_ROOT=/path/to/your/clone npm run dev
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.
- **Python 3.11+** — for the agent runtime, dashboard backend, and tool execution. (required)
- **Git for Windows** (Windows only) — provides Git Bash, which Hermes' terminal tool calls directly. Linux and macOS already ship a system bash. (required)
- **ripgrep** — used by Hermes' `search_files` tool for fast `.gitignore`-aware file/content search. Recommended on all platforms; Hermes falls back to `grep`/`find` if missing (works but slower and noisier).
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.
The packaged Windows installer (`Hermes-*.exe`) detects all three at install time. Required items missing are auto-installed via `winget install -e --id Python.Python.3.11 --scope user` and `winget install -e --id Git.Git`. The recommended ripgrep is offered as `winget install -e --id BurntSushi.ripgrep.MSVC --scope user`. 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:
For dev (`npm run dev`) the Python and Git Bash 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 Python.Python.3.11 --scope user
winget install -e --id Git.Git
winget install -e --id BurntSushi.ripgrep.MSVC --scope user
```
## Development

View file

@ -3,8 +3,8 @@
; ============================================================================
;
; 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.
; selection page and the install-files page. Detects Python 3.11+, Git for
; Windows, and ripgrep; offers to install missing items via winget.
;
; Page sequence:
; Welcome Directory [PrereqPage] InstFiles Finish
@ -17,31 +17,38 @@
;
; 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.
; the Page directive so the optimizer doesn't strip them.
;
; 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.
; Python: --scope user, no UAC.
; ripgrep: --scope user, no UAC.
; Git for Windows: always per-machine, triggers UAC prompt.
; Footer warns the user about Git's UAC; ExecShellWait preserves the
; foreground focus chain so the prompt comes to front.
;
; 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.
;
; Python: try `py -3.11`/`-3.12`/`-3.13`/`-3.14`. 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.
;
; ripgrep: `where rg` returns exit 0 if rg is on PATH.
; winget: `where winget` returns exit 0 on Win11 / Win10 1809+ with App
; Installer. If unavailable, the page shows manual download URLs.
; Installer. If unavailable, the page shows manual download URLs.
;
; Required vs. recommended:
; Python and Git are REQUIRED without them the agent's runtime + terminal
; tool fail. The page emphasizes "required" wording and the bootstrapper
; throws if either is missing at first launch.
; ripgrep is RECOMMENDED Hermes' search_files tool uses it for fast
; .gitignore-aware search, and falls back to grep/find from Git Bash when
; missing (works but slower, less filtering). Page wording is softer for
; ripgrep so users understand they CAN skip it.
;
; Skip behaviors:
; - Both prereqs already installed page is auto-skipped via Abort
; - All three already detected page is auto-skipped via Abort
; - Silent install (/S) customInstall winget block skips
; - User unchecks both checkboxes page advances without running winget
; - User unchecks all checkboxes page advances without running winget
; ============================================================================
!include "LogicLib.nsh"
@ -53,16 +60,21 @@ Var HermesPyStatusLabel
Var HermesPyCheckbox
Var HermesGitStatusLabel
Var HermesGitCheckbox
Var HermesRgStatusLabel
Var HermesRgCheckbox
Var HermesFooterLabel
Var HermesHasWinget
Var HermesHasPython
Var HermesHasGit
Var HermesHasRipgrep
Var HermesInstallPython
Var HermesInstallGit
Var HermesInstallRipgrep
; ----------------------------------------------------------------------------
; HermesDetectPrereqs populates $HermesHasWinget / $HermesHasPython /
; $HermesHasGit with "0" or "1". Called from the page-create function.
; $HermesHasGit / $HermesHasRipgrep with "0" or "1". Called from the
; page-create function.
; ----------------------------------------------------------------------------
Function HermesDetectPrereqs
; --- winget ---
@ -110,10 +122,19 @@ Function HermesDetectPrereqs
${Else}
StrCpy $HermesHasGit "0"
${EndIf}
; --- ripgrep ---
nsExec::Exec 'cmd.exe /c where rg >nul 2>&1'
Pop $0
${If} $0 == 0
StrCpy $HermesHasRipgrep "1"
${Else}
StrCpy $HermesHasRipgrep "0"
${EndIf}
FunctionEnd
; ----------------------------------------------------------------------------
; HermesPrereqPageCreate builds the prereq page UI. If both prereqs are
; HermesPrereqPageCreate builds the prereq page UI. If all three items are
; already installed we Abort, which causes NSIS to skip directly to the next
; page in the sequence (InstFiles).
; ----------------------------------------------------------------------------
@ -122,9 +143,18 @@ Function HermesPrereqPageCreate
${If} $HermesHasPython == "1"
${AndIf} $HermesHasGit == "1"
${AndIf} $HermesHasRipgrep == "1"
Abort
${EndIf}
; Set the wizard's standard header (top blue/gradient bar). 1037 is the
; title control, 1038 is the subtitle. Without this, the header still
; reads "Choose Install Location" left over from the Directory page.
GetDlgItem $0 $HWNDPARENT 1037
SendMessage $0 ${WM_SETTEXT} 0 "STR:System Requirements"
GetDlgItem $0 $HWNDPARENT 1038
SendMessage $0 ${WM_SETTEXT} 0 "STR:Hermes needs Python 3.11+ and Git for Windows. ripgrep is recommended."
nsDialogs::Create 1018
Pop $HermesDialog
${If} $HermesDialog == error
@ -133,61 +163,75 @@ Function HermesPrereqPageCreate
StrCpy $HermesInstallPython "0"
StrCpy $HermesInstallGit "0"
StrCpy $HermesInstallRipgrep "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."
; Page body intro. The wizard's header (set above) shows the title
; "System Requirements" and subtitle, so we don't repeat them here
; just one short explanatory line.
${NSD_CreateLabel} 0u 0u 100% 16u "Items already installed are listed as detected. Missing items can be installed automatically via winget."
Pop $0
; --- Python panel ---
${NSD_CreateGroupBox} 0u 34u 100% 32u "Python 3.11+"
; --- Python panel (REQUIRED) ---
${NSD_CreateGroupBox} 0u 18u 100% 26u "Python 3.11+ (required)"
Pop $0
${If} $HermesHasPython == "1"
${NSD_CreateLabel} 8u 46u 95% 12u "Detected on your system."
${NSD_CreateLabel} 8u 28u 95% 10u "Detected on your system."
Pop $HermesPyStatusLabel
${Else}
${If} $HermesHasWinget == "1"
${NSD_CreateLabel} 8u 44u 95% 9u "Not detected."
${NSD_CreateLabel} 8u 27u 95% 9u "Not detected."
Pop $HermesPyStatusLabel
${NSD_CreateCheckbox} 8u 54u 95% 10u "Install Python 3.11 (per-user install, no admin prompt)"
${NSD_CreateCheckbox} 8u 36u 95% 9u "Install Python 3.11"
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."
${NSD_CreateLabel} 8u 27u 95% 14u "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)"
; --- Git panel (REQUIRED) ---
${NSD_CreateGroupBox} 0u 48u 100% 26u "Git for Windows (required, provides Git Bash)"
Pop $0
${If} $HermesHasGit == "1"
${NSD_CreateLabel} 8u 82u 95% 12u "Detected on your system."
${NSD_CreateLabel} 8u 58u 95% 10u "Detected on your system."
Pop $HermesGitStatusLabel
${Else}
${If} $HermesHasWinget == "1"
${NSD_CreateLabel} 8u 80u 95% 9u "Not detected. Required by Hermes' terminal tool."
${NSD_CreateLabel} 8u 57u 95% 9u "Not detected. Required by Hermes' terminal tool."
Pop $HermesGitStatusLabel
${NSD_CreateCheckbox} 8u 90u 95% 10u "Install Git for Windows (administrator approval required)"
${NSD_CreateCheckbox} 8u 66u 95% 9u "Install Git for Windows"
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."
${NSD_CreateLabel} 8u 57u 95% 14u "Not detected. Install manually from https://git-scm.com/download/win and re-run this installer."
Pop $HermesGitStatusLabel
${EndIf}
${EndIf}
; --- ripgrep panel (RECOMMENDED) ---
${NSD_CreateGroupBox} 0u 78u 100% 26u "ripgrep (recommended for fast file search)"
Pop $0
${If} $HermesHasRipgrep == "1"
${NSD_CreateLabel} 8u 88u 95% 10u "Detected on your system."
Pop $HermesRgStatusLabel
${Else}
${If} $HermesHasWinget == "1"
${NSD_CreateLabel} 8u 87u 95% 9u "Not detected. Hermes will fall back to slower grep/find."
Pop $HermesRgStatusLabel
${NSD_CreateCheckbox} 8u 96u 95% 9u "Install ripgrep"
Pop $HermesRgCheckbox
${NSD_Check} $HermesRgCheckbox
${Else}
${NSD_CreateLabel} 8u 87u 95% 14u "Not detected. Install manually from https://github.com/BurntSushi/ripgrep#installation if you want fast .gitignore-aware search."
Pop $HermesRgStatusLabel
${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."
${NSD_CreateLabel} 0u 108u 100% 18u "Note: Git for Windows requires administrator approval. The UAC prompt may appear behind this window — check your taskbar."
Pop $HermesFooterLabel
${EndIf}
@ -208,6 +252,10 @@ Function HermesPrereqPageLeave
${AndIf} $HermesHasWinget == "1"
${NSD_GetState} $HermesGitCheckbox $HermesInstallGit
${EndIf}
${If} $HermesHasRipgrep == "0"
${AndIf} $HermesHasWinget == "1"
${NSD_GetState} $HermesRgCheckbox $HermesInstallRipgrep
${EndIf}
FunctionEnd
; ----------------------------------------------------------------------------
@ -250,6 +298,20 @@ FunctionEnd
${EndIf}
${EndIf}
${If} $HermesInstallRipgrep == "1"
; ripgrep with --scope user ~5MB, no UAC needed. Failure is non-fatal:
; Hermes' search_files tool falls back to grep/find from Git Bash.
; nsExec::ExecToLog streams output to the install log.
DetailPrint "Installing ripgrep via winget (silent per-user install, no admin prompt)..."
nsExec::ExecToLog 'winget install -e --id BurntSushi.ripgrep.MSVC --scope user --silent --disable-interactivity --accept-package-agreements --accept-source-agreements'
Pop $0
${If} $0 != 0
DetailPrint "ripgrep install via winget exited with code $0 (non-fatal — Hermes will fall back to grep/find)."
${Else}
DetailPrint "ripgrep 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