Build Hermes Desktop from source instead of packaging the CLI

Install > AI is for GUI apps, so the package becomes the desktop shell and the
terminal agent moves to a lazy mise stub that omarchy installs itself.

Nous builds Hermes Desktop for macOS and Windows only -- their download page
offers a .dmg and an .exe and points Linux users at a terminal install -- so
there is no vendor binary to repackage the way grok-bot repackages xAI's deb.
Their electron-builder config does carry a Linux target though, untested by
upstream but working: npm ci at the workspace root, npm run pack in
apps/desktop, and electron-builder stages node-pty and get-windows for
linux-x64 on its own. 337 MB unpacked, 129 MB packaged.

The app is only a shell -- it runs `hermes serve` against a CLI it does not
ship, and clones its own unpinned copy into ~/.hermes when it finds none. So
/usr/bin/hermes-desktop makes sure the CLI is there before handing over, and
says so plainly rather than bootstrapping a second Hermes where Omarchy's
installer isn't around to do it properly.
This commit is contained in:
Omabot
2026-08-19 02:42:15 -07:00
parent ee051b4f8e
commit a57f63914f
7 changed files with 135 additions and 122 deletions
-73
View File
@@ -1,73 +0,0 @@
# Maintainer: David Heinemeier Hansson <david@hey.com>
# Hermes pins every one of its ~120 dependencies with == and declares
# Requires-Python >=3.11,<3.14, so it can neither be built against Arch's
# Python nor share the python-* packages, and Arch's next Python bump would
# break any venv this package built. So the package ships a wrapper rather than
# the application: mise gives Hermes a private, supported environment on first
# run and keeps it current, the arrangement omarchy-mise-install already makes
# for the other coding agents. pkgver therefore tracks the wrapper, not Hermes.
pkgname=hermes-agent
pkgver=1.0.0
pkgrel=1
pkgdesc='The self-improving AI agent that grows with you'
arch=('any')
url='https://github.com/NousResearch/hermes-agent'
license=('MIT')
depends=(
'hicolor-icon-theme'
'mise'
'uv' # mise's pipx backend refuses to run without uv or pipx
'xdg-terminal-exec'
)
optdepends=(
'chromium: browser automation tools'
'ffmpeg: text-to-speech voice messages'
'git: version control integration'
'nodejs: browser-use CLI and MCP servers'
'ripgrep: faster file search'
)
makedepends=('imagemagick')
options=('!debug')
source=('hermes.sh'
'hermes.desktop'
'hermes.png')
sha256sums=('39a13f25b2a1616b55da19400045630e6b0900140444a4457d7e0ff58208badb'
'0fee8e42dce35e7eee0e6b0ee296cc90ddf192f0f9b8297b7e5ea66800d94159'
'd60d164e24fdcf6532133b8ea43c77a201e4b9e9dbc396187b58d51d8590ef52')
build() {
# Upstream ships one 1024x1024 master. Menus and the dock ask for the small
# sizes, and letting them downscale a 1024px portrait themselves is what
# makes it a smudge at 48px.
local size
for size in 512 256 128 64 48; do
magick "$srcdir/hermes.png" -resize "${size}x${size}" "$srcdir/hermes-${size}.png"
done
}
package() {
install -Dm755 "$srcdir/hermes.sh" "$pkgdir/usr/bin/hermes"
# Hermes' other two entry points. The wrapper dispatches on the name it was
# invoked under, so these are symlinks rather than copies.
ln -s hermes "$pkgdir/usr/bin/hermes-agent"
ln -s hermes "$pkgdir/usr/bin/hermes-acp"
install -Dm644 "$srcdir/hermes.desktop" \
"$pkgdir/usr/share/applications/hermes.desktop"
install -Dm644 "$srcdir/hermes.png" \
"$pkgdir/usr/share/icons/hicolor/1024x1024/apps/hermes.png"
local size
for size in 512 256 128 64 48; do
install -Dm644 "$srcdir/hermes-${size}.png" \
"$pkgdir/usr/share/icons/hicolor/${size}x${size}/apps/hermes.png"
done
}
-46
View File
@@ -1,46 +0,0 @@
#!/bin/bash
# Hermes is a Python application that pins every one of its dependencies
# exactly and declares Requires-Python >=3.11,<3.14, so it cannot be built
# against Arch's Python or share the python-* packages. mise builds it a
# private environment instead, on first run and on demand.
#
# The interpreter pin below is not belt-and-braces. Given no compatible
# interpreter to hand, uv builds the venv against the system Python in
# violation of Hermes' own bound, reports success, and leaves the failure to
# surface later inside a dependency.
set -euo pipefail
readonly tool='pipx:hermes-agent[extras=all]'
readonly python="${HERMES_PYTHON:-3.13}"
export UV_PYTHON="$python"
# `mise up` -- which omarchy update runs -- reinstalls without this wrapper's
# UV_PYTHON, so an upgrade can quietly move Hermes onto the system Python.
# Check the interpreter that is actually there, not merely that something is.
hermes_installed_on_pinned_python() {
local prefix
prefix=$(mise where "$tool" 2>/dev/null) || return 1
[[ -d "$prefix/hermes-agent/lib/python$python" ]]
}
if ! hermes_installed_on_pinned_python; then
echo "Installing Hermes on Python $python (this takes a minute)..." >&2
# Hermes ships several times a week, so mise's release cooldown would hold a
# new version back for days. Same call omarchy-mise-install makes.
MISE_MINIMUM_RELEASE_AGE=0 mise use -g --quiet --force "$tool" || exit 1
fi
# Installed as /usr/bin/hermes, with hermes-agent and hermes-acp symlinked to
# it, so the name we were invoked under picks the entry point. Anything else --
# someone running this file straight out of a checkout -- means hermes.
command=$(basename "$0")
case "$command" in
hermes | hermes-agent | hermes-acp) ;;
*) command='hermes' ;;
esac
exec mise x "$tool" -- "$command" "$@"
+112
View File
@@ -0,0 +1,112 @@
# Maintainer: David Heinemeier Hansson <david@hey.com>
# Nous builds Hermes Desktop for macOS and Windows only -- their download page
# offers a .dmg and an .exe and tells Linux users to install from a terminal --
# so there is no vendor binary to repackage. Their electron-builder config does
# carry a Linux target, though, and it works; this builds it.
#
# The app is only a shell: it runs `hermes serve` against a Hermes CLI it does
# not ship, and clones its own copy with the upstream install script when it
# finds none. /usr/bin/hermes-desktop heads that off. See hermes-desktop.sh.
pkgname=hermes-desktop
pkgver=2026.8.18
pkgrel=1
pkgdesc='Native desktop shell for Hermes Agent'
arch=('x86_64')
url='https://github.com/NousResearch/hermes-agent'
license=('MIT')
depends=(
'alsa-lib'
'at-spi2-core'
'cairo'
'dbus'
'expat'
'gcc-libs'
'gdk-pixbuf2'
'glib2'
'glibc'
'gtk3'
'hicolor-icon-theme'
'libcups'
'libdrm'
'libglvnd'
'libnotify'
'libx11'
'libxcb'
'libxcomposite'
'libxdamage'
'libxext'
'libxfixes'
'libxkbcommon'
'libxrandr'
'mesa'
'nspr'
'nss'
'pango'
'systemd-libs'
'xdg-utils'
)
optdepends=('omarchy: installs the Hermes CLI the app needs on first launch')
# The build runs the repo's own npm workspace install, which fetches Electron
# and rebuilds node-pty against it.
makedepends=('git' 'imagemagick' 'nodejs' 'npm' 'python')
# Electron bundles prebuilt binaries that stripping corrupts.
options=('!strip' '!debug')
_srcdir="hermes-agent-${pkgver}"
source=("${pkgname}-${pkgver}.tar.gz::${url}/archive/refs/tags/v${pkgver}.tar.gz"
'hermes-desktop.sh'
'hermes-desktop.desktop'
'hermes-desktop.png')
sha256sums=('1e3d39d3638ec15fa9d31af262568a953e9272090deb1c50c44cd401175f5b80'
'716978c836ad46aa4ad173366d3a5d322c174fadf2f8095e6ce81301e1cad048'
'a71e5b3599515b97ea694d51771edede69107a221f301f91c088a5d81de7a8c0'
'd60d164e24fdcf6532133b8ea43c77a201e4b9e9dbc396187b58d51d8590ef52')
build() {
cd "${srcdir}/${_srcdir}"
# The desktop workspace resolves against the repo root, so the install has to
# happen there rather than in apps/desktop.
npm ci
cd apps/desktop
npm run pack
}
package() {
cd "${srcdir}/${_srcdir}/apps/desktop/release/linux-unpacked"
install -dm755 "${pkgdir}/opt/${pkgname}"
cp -a . "${pkgdir}/opt/${pkgname}/"
install -Dm755 "${srcdir}/hermes-desktop.sh" "${pkgdir}/usr/bin/${pkgname}"
install -Dm644 "${srcdir}/hermes-desktop.desktop" \
"${pkgdir}/usr/share/applications/${pkgname}.desktop"
install -Dm644 "${srcdir}/hermes-desktop.png" \
"${pkgdir}/usr/share/icons/hicolor/1024x1024/apps/${pkgname}.png"
local size
for size in 512 256 128 64 48; do
magick "${srcdir}/hermes-desktop.png" -resize "${size}x${size}" "${srcdir}/icon-${size}.png"
install -Dm644 "${srcdir}/icon-${size}.png" \
"${pkgdir}/usr/share/icons/hicolor/${size}x${size}/apps/${pkgname}.png"
done
install -Dm644 "${pkgdir}/opt/${pkgname}/LICENSE.electron.txt" \
"${pkgdir}/usr/share/licenses/${pkgname}/LICENSE.electron.txt"
# Chromium's setuid sandbox is only needed where unprivileged user namespaces
# are unavailable; where they work, setuid root is the worse of the two.
if ! { [[ -L /proc/self/ns/user ]] && unshare --user true; }; then
chmod 4755 "${pkgdir}/opt/${pkgname}/chrome-sandbox"
else
chmod 0755 "${pkgdir}/opt/${pkgname}/chrome-sandbox"
fi
}
@@ -4,9 +4,10 @@ Type=Application
Name=Hermes Name=Hermes
GenericName=AI Agent GenericName=AI Agent
Comment=The self-improving AI agent that grows with you Comment=The self-improving AI agent that grows with you
Exec=xdg-terminal-exec --app-id=TUI.tile -e hermes Exec=hermes-desktop %U
Icon=hermes Icon=hermes-desktop
Terminal=false Terminal=false
Categories=Utility; Categories=Development;
Keywords=ai;agent;assistant;hermes;nous; Keywords=ai;agent;assistant;hermes;nous;
StartupNotify=true StartupNotify=true
StartupWMClass=Hermes

Before

Width:  |  Height:  |  Size: 561 KiB

After

Width:  |  Height:  |  Size: 561 KiB

@@ -0,0 +1,19 @@
#!/bin/bash
# The desktop app is only a shell: it runs `hermes serve` against a Hermes CLI
# it does not ship. Finding none, it clones its own copy with the upstream
# install script -- an unpinned checkout in ~/.hermes that no package manages.
#
# So make sure the CLI is there first. Omarchy installs it with its own script;
# elsewhere, say so rather than letting the app quietly bootstrap a second
# Hermes behind the user's back.
if ! command -v hermes >/dev/null 2>&1; then
if command -v omarchy-install-hermes-cli >/dev/null 2>&1; then
omarchy-install-hermes-cli --now
else
echo "The Hermes CLI is not installed, so Hermes Desktop will install its" >&2
echo "own copy under ~/.hermes. Install the CLI first to avoid that." >&2
fi
fi
exec /opt/hermes-desktop/Hermes "$@"