inspecting-hermes-desktop-dom skill
Read the live Hermes desktop DOM/CSS over CDP.
Is the inspecting-hermes-desktop-dom skill safe?
Clean: nothing in its files matched our rules. We read 1 file in the folder on 2026-11-06.
No findings.
Install the inspecting-hermes-desktop-dom skill
A skill is a folder. Copy it into your agent's skills folder and the agent loads it when the task matches its description.
git clone --depth 1 https://github.com/NousResearch/hermes-agent.git /tmp/hermes-agent mkdir -p ~/.claude/skills cp -r /tmp/hermes-agent/optional-skills/software-development/inspecting-hermes-desktop-dom ~/.claude/skills/inspecting-hermes-desktop-dom
In the Claude apps, zip the folder and upload it from the Skills settings. The folder on GitHub
The instructions your agent would load
SKILL.md as published, without the frontmatter. Read it on GitHub
Inspecting the live Hermes desktop DOM
Overview
When you are developing apps/desktop and the user is running that same app (hgui / pnpm run dev), you can read the live rendered DOM of the window they are looking at — computed styles, geometry, which CSS rule actually won, console output — instead of inferring it from .tsx and being wrong.
Dev-server runs open a Chrome DevTools Protocol port on 127.0.0.1:9222 automatically. The renderer is a Chromium page, so everything DevTools can read, a script can read.
This does not replace looking at it. CDP answers factual questions ("what is the computed padding", "did this element render", "which selector matches"). It cannot tell you whether the result looks good. Colour balance, spacing feel, and "is this ugly" still need the user's eyes or a screenshot. Answer facts with CDP; hand aesthetics to the user.
When to Use
- Verifying a UI change actually took effect in the running app
- "Why is this element still X?" — find the winning rule before editing anything
- Locating a stable selector for a component you're about to change
- Checking a design token's computed value on a real node
- Reading renderer console errors the user mentions but can't copy out
Don't use for: perf profiling or heap work (node-inspect-debugger, debugging-hermes-desktop), or anything where the real question is "does this look right".
The port
Open on 127.0.0.1:9222 for any dev-server run. Closed in exactly two cases (apps/desktop/electron/dev-cdp.ts):
dist/ is how the packaged app gets smoke tested, so it behaves like one.
- packaged builds — always, and no environment value overrides it;
- no HERMESDESKTOPDEVSERVER** — an unpackaged electron . against
HERMESDESKTOPCDP_PORT moves the port (=9333) or disables it (=off).
Check before doing anything else:
curl -s --max-time 3 http://127.0.0.1:${HERMES_DESKTOP_CDP_PORT:-9222}/json/versionEmpty → no port. Do not guess another port silently.
Never relaunch the user's app to get a port. That destroys their session and their state. Launch your own isolated instance instead (below).
Reading the DOM
apps/desktop/scripts/eval.mjs is the one-liner:
cd apps/desktop
node scripts/eval.mjs "document.querySelectorAll('[data-slot]').length"For multi-step work use the shared client — it has target discovery and promise-aware eval:
import { CDP, SELECTORS } from './scripts/perf/lib/cdp.mjs'
const cdp = await CDP.connect({ port: 9222, match: '5174' })
const out = await cdp.eval(`JSON.stringify({
radius: getComputedStyle(document.documentElement).getPropertyValue('--radius-scalar').trim(),
composer: !!document.querySelector('[data-slot="composer-rich-input"]')
})`)
cdp.close()SELECTORS in scripts/perf/lib/cdp.mjs holds the stable data-slot hooks (composer, thread viewport, assistant message, turn pair, profile rail). Prefer them over inventing a querySelector — they are updated as a unit when components move.
The question this is best at: which rule won?
Editing every call site because a style "isn't applying" is the classic waste. Read the real node first:
const el = document.querySelector('[data-slot="aui_assistant-message-root"] a')
JSON.stringify({
ownClasses: el.className,
weight: getComputedStyle(el).fontWeight,
parents: (() => {
const out = []
let n = el
while ((n = n.parentElement) && out.length < 6) out.push(n.className)
return out
})()
})If the node carries no class of its own, the value is inherited — sweeping call sites will not fix it, and you need the ancestor rule. A plugin stylesheet (e.g. @tailwindcss/typography's prose a { font-weight: 500 }) routinely beats a utility class; override on the shared class, not at each usage.
Your own isolated instance
When there is no port, or you must not disturb the user's window:
cd apps/desktop
HERMES_HOME=$HOME/.hermes/cache/scratch/cdp-probe-home \
HERMES_DESKTOP_DEV_SERVER=http://127.0.0.1:5174 \
HERMES_DESKTOP_CDP_PORT=9333 \
pnpm exec electron . --user-data-dir=$HOME/.hermes/cache/scratch/cdp-probe-userdataThe separate --user-data-dir dodges Electron's single-instance lock, so it cannot collide with a running hgui; the separate HERMES_HOME keeps it away from real sessions. Pick a port other than 9222 for the same reason. Run it in the background and kill it when done.
pnpm run perf:serve does the same with a temp HERMES_HOME baked in, if you also want the perf harness.
Pitfalls
kill nukes Chromium's socket pool, and the resulting ERRNETWORKCHANGED gets blamed on whatever you just changed.
- Never kill the user's dev server or app to "free" anything. A mid-serve
hermes:api and may exit on its own. The renderer still mounts and the DOM is readable — read promptly, and don't mistake a self-exited probe for a broken port. Chromium logs DevTools listening on ws://127.0.0.1:/… when it binds; that line is the proof the port opened.
- A throwaway HERMESHOME has no backend.** The app logs ECONNREFUSED for
the port answers.
- Poll, don't probe once. A just-launched app needs a second or two before
outerHTML will bury your context. Project down to a small JSON object inside the evaluated expression.
- Never dump the whole DOM. The desktop renders hundreds of nodes and
overlay, quick-entry window, or a devtools target instead of the main window.
- Pass match to CDP.connect. Without it you may attach to the pet
(.result.result.value). Use the wrapper.
- cdp.eval returns the value; raw Runtime.evaluate double-nests it
apps/desktop/scripts/profile-typing-lag.md claiming otherwise is stale.
- import.meta.env.DEV is true under vite dev in this repo. The note in
More skills from NousResearch/hermes-agent
- A1passwordSet up op CLI, sign in, and read or inject secrets.
- A3-statement-modelBuild integrated IS/BS/CF financial workbooks in Excel.
- AaccelerateRun PyTorch training across GPUs with minimal changes.
- Cactual-setupSet up Actual Computer (actual.inc) inference in Hermes.
- Aadversarial-ux-testRoleplay a hostile user to find and triage UX pain points.
- Aagent-merge-conflict-arbiterNeutral arbiter for merge conflicts between two agents.
- AagentmailUse when an agent needs AgentMail CLI email inboxes.
- Aai-presenter-videoMake a verified AI presenter video from script + image.
- AairtableAirtable REST API via curl. Records CRUD, filters, upserts.
- AanatomyExplain an idea as an interactive isometric machine figure.
- Cantigravity-cliOperate the Antigravity CLI (agy): plugins, auth, sandbox.
- Aapple-notesManage Apple Notes via memo CLI: create, search, edit.