Bug description
The Theme Switcher UI is visible in Hermes Desktop, but its first ctx.rest('/list') call can fail with a 404 when Desktop is connected to a Linux Hermes backend over SSH:
Error invoking remote method 'hermes:api': Error: 404: {"error":"Headless backend (hermes serve): web UI disabled — use `hermes dashboard` for the browser UI."} — the theme-switcher backend mounts when the app starts. Restart the app if this persists.
The same pack also does not provide a working web dashboard Themes tab. That appears to be a separate delivery mismatch: this repo ships a native Desktop ESM plugin, but no web-dashboard UI bundle.
Environment
- Theme pack: current
main, commit fd718829bd9b8c7bf0846bf44bdd59bfdf3d0068
- Hermes Agent:
v0.20.0 (2026.8.3)
- Backend host: Linux
- Client: Hermes Desktop on Windows, connected to the Linux backend over SSH
- Backend plugin is installed under
~/.hermes/plugins/theme-switcher/ and listed as enabled
- Desktop plugin is installed in the Windows-local
desktop-plugins/theme-switcher/plugin.js directory
Reproduction
- Start/connect Hermes Desktop to a remote Linux machine over SSH. This launches
hermes serve --isolated --port 0 ... on the remote machine.
- Install and enable the
theme-switcher backend after that headless process is already running.
- Install/reload the Desktop
plugin.js locally on Windows.
- Open Themes.
- The
/api/plugins/theme-switcher/list request gets the 404 above.
For the browser dashboard case:
- Run
hermes dashboard.
- Install this repo as documented.
- There is no web-dashboard Theme Switcher implementation to load.
Root-cause evidence
This is not a malformed route in plugin.js: ctx.rest('/list') correctly resolves to /api/plugins/theme-switcher/list.
Hermes mounts Python plugin routers only once, during web_server.py module/app startup (_mount_plugin_api_routes()), before the SPA/headless catch-all. Enabling the plugin later does not add its router to already-running hermes serve processes.
On the affected machine I found three SSH-owned headless processes that all predated the plugin enable/install step. Their OpenAPI documents had no theme-switcher routes:
/api/plugins/theme-switcher/* -> absent
A fresh temporary hermes serve launched after the plugin was enabled exposed:
/api/plugins/theme-switcher/apply
/api/plugins/theme-switcher/install
/api/plugins/theme-switcher/list
/api/plugins/theme-switcher/raw
/api/plugins/theme-switcher/settings
and an authenticated request to /api/plugins/theme-switcher/list returned HTTP 200.
The standalone hermes dashboard process also mounted these routes successfully after a fresh start. So the SSH failure is specifically a stale backend process, not the Python router implementation.
The current UI error is misleading because it says the backend “mounts when the app starts.” More precisely, it mounts when the backend server process starts. Reloading the Desktop plugin, reloading the window, or retrying cannot repair an already-running SSH backend whose FastAPI app was created before enablement.
Web dashboard is a different plugin system
Hermes documents the native Desktop SDK and web Dashboard SDK as unrelated:
- Desktop:
@hermes/plugin-sdk, local desktop-plugins/<id>/plugin.js
- Web dashboard:
window.__HERMES_PLUGIN_SDK__, dashboard/manifest.json, and a prebuilt IIFE entry
This repo's plugins/theme-switcher/dashboard/manifest.json contains only:
{ "name": "theme-switcher", "api": "plugin_api.py" }
There is no entry, tab, or dashboard/dist/index.js, so it installs only the shared backend API—not a web-dashboard tab. The README phrase “Theme Switcher (desktop browser)” is easy to read as browser-dashboard support even though the implementation is Desktop-only.
Suggested fixes
- Make installation atomic and correctly ordered. Have
install.sh install the skins, backend plugin, enable it, and install/copy the Desktop plugin (or clearly split local-client vs remote-backend steps). Right now ./install.sh installs only skins, while the backend/UI steps are separate manual commands.
- Detect stale backend processes. After enabling the backend, print an explicit instruction to restart/reconnect the remote
hermes serve backend—not merely reload/restart the renderer. In SSH mode, closing/reopening the UI may reuse or leave old remote serve processes, so the instruction should name the backend lifecycle.
- Improve the error message. On a 404 for
/list, say that the backend route is absent and the connected Hermes backend must be restarted after enabling the plugin. Do not imply Retry or Desktop hot reload can mount it.
- Clarify web support. Either:
- document prominently that the Themes page is native-Hermes-Desktop-only and that the web dashboard only receives the backend API; or
- ship a separate web-dashboard UI bundle using
window.__HERMES_PLUGIN_SDK__, with tab + entry in dashboard/manifest.json.
- Add an integration test. In a temporary
HERMES_HOME, install + enable the plugin, start a fresh headless server, and assert /api/plugins/theme-switcher/list returns 200. If web support is intended, also validate that the dashboard manifest has a real entry bundle.
Expected behavior
- Following the documented install path results in a working Themes page without needing to infer that a remote backend process—not just the app window—must restart.
- The README clearly distinguishes native Desktop support from browser-dashboard support.
Bug description
The Theme Switcher UI is visible in Hermes Desktop, but its first
ctx.rest('/list')call can fail with a 404 when Desktop is connected to a Linux Hermes backend over SSH:The same pack also does not provide a working web dashboard Themes tab. That appears to be a separate delivery mismatch: this repo ships a native Desktop ESM plugin, but no web-dashboard UI bundle.
Environment
main, commitfd718829bd9b8c7bf0846bf44bdd59bfdf3d0068v0.20.0 (2026.8.3)~/.hermes/plugins/theme-switcher/and listed as enableddesktop-plugins/theme-switcher/plugin.jsdirectoryReproduction
hermes serve --isolated --port 0 ...on the remote machine.theme-switcherbackend after that headless process is already running.plugin.jslocally on Windows./api/plugins/theme-switcher/listrequest gets the 404 above.For the browser dashboard case:
hermes dashboard.Root-cause evidence
This is not a malformed route in
plugin.js:ctx.rest('/list')correctly resolves to/api/plugins/theme-switcher/list.Hermes mounts Python plugin routers only once, during
web_server.pymodule/app startup (_mount_plugin_api_routes()), before the SPA/headless catch-all. Enabling the plugin later does not add its router to already-runninghermes serveprocesses.On the affected machine I found three SSH-owned headless processes that all predated the plugin enable/install step. Their OpenAPI documents had no
theme-switcherroutes:A fresh temporary
hermes servelaunched after the plugin was enabled exposed:and an authenticated request to
/api/plugins/theme-switcher/listreturnedHTTP 200.The standalone
hermes dashboardprocess also mounted these routes successfully after a fresh start. So the SSH failure is specifically a stale backend process, not the Python router implementation.The current UI error is misleading because it says the backend “mounts when the app starts.” More precisely, it mounts when the backend server process starts. Reloading the Desktop plugin, reloading the window, or retrying cannot repair an already-running SSH backend whose FastAPI app was created before enablement.
Web dashboard is a different plugin system
Hermes documents the native Desktop SDK and web Dashboard SDK as unrelated:
@hermes/plugin-sdk, localdesktop-plugins/<id>/plugin.jswindow.__HERMES_PLUGIN_SDK__,dashboard/manifest.json, and a prebuilt IIFEentryThis repo's
plugins/theme-switcher/dashboard/manifest.jsoncontains only:{ "name": "theme-switcher", "api": "plugin_api.py" }There is no
entry,tab, ordashboard/dist/index.js, so it installs only the shared backend API—not a web-dashboard tab. The README phrase “Theme Switcher (desktop browser)” is easy to read as browser-dashboard support even though the implementation is Desktop-only.Suggested fixes
install.shinstall the skins, backend plugin, enable it, and install/copy the Desktop plugin (or clearly split local-client vs remote-backend steps). Right now./install.shinstalls only skins, while the backend/UI steps are separate manual commands.hermes servebackend—not merely reload/restart the renderer. In SSH mode, closing/reopening the UI may reuse or leave old remote serve processes, so the instruction should name the backend lifecycle./list, say that the backend route is absent and the connected Hermes backend must be restarted after enabling the plugin. Do not imply Retry or Desktop hot reload can mount it.window.__HERMES_PLUGIN_SDK__, withtab+entryindashboard/manifest.json.HERMES_HOME, install + enable the plugin, start a fresh headless server, and assert/api/plugins/theme-switcher/listreturns 200. If web support is intended, also validate that the dashboard manifest has a real entry bundle.Expected behavior