Files
hermes-skills/skills/devops/hermes-browser-setup/SKILL.md
T

16 KiB

name, description, version, author, license, platforms, metadata
name description version author license platforms metadata
hermes-browser-setup Install, configure, and troubleshoot Hermes browser automation tools — local Chromium via Playwright, cloud providers (browser-use, Browserbase, Camofox), and CDP-based headless mode. Covers the full lifecycle from zero to working browser_* tools. 1.2.0 Hermes Agent MIT
linux
hermes
tags related_skills
hermes
browser
automation
playwright
chromium
cdp
browser-use
hermes-agent
hermes-migration
computer-use

Hermes Browser Setup

Hermes ships a browser toolset with ~10 tools (browser_navigate, browser_snapshot, browser_click, browser_type, browser_scroll, etc.) that can run via a local Chromium CDP or cloud providers (browser-use.com, Browserbase, Camofox).

This skill covers getting from zero to working browser tools, including the common failure mode where the toolset is enabled but no tools appear because no credential or CDP URL is configured.

Quick Success Check

# Are browser tools enabled?
hermes tools list | grep browser

# Is a CDP URL or cloud key set?
grep -i 'browser\|cdp\|base\|fox' ~/.hermes/.env
grep -A5 'browser:' ~/.hermes/config.yaml

Tools gated out with no error? Check .env for BROWSER_CDP_URL, BROWSER_USE_API_KEY, BROWSERBASE_API_KEY, or CAMOFOX_URL. If none are set, the browser tools will be silently disabled regardless of hermes tools list showing them enabled.

1. Install Playwright + Chromium

pip3 install playwright
python3 -m playwright install chromium
python3 -m playwright install-deps chromium

Chromium binary lands at ~/.cache/ms-playwright/chromium-<version>/chrome-linux64/chrome.

2. Start headless CDP server

Quick test:

~/.cache/ms-playwright/chromium-*/chrome-linux64/chrome \
    --headless=new --no-sandbox --disable-gpu --disable-dev-shm-usage \
    --remote-debugging-port=9222 --user-data-dir=/tmp/chromium-data &

# Verify
curl -s http://127.0.0.1:9222/json/version
# Expected: Chrome/<version> + webSocketDebuggerUrl

Persistent systemd service:

cat > /etc/systemd/system/hermes-browser.service << 'EOF'
[Unit]
Description=Hermes Headless Chromium (CDP Browser)
After=network.target

[Service]
Type=simple
User=root
ExecStart=/root/.cache/ms-playwright/chromium-<version>/chrome-linux64/chrome \
    --headless=new \
    --no-sandbox \
    --disable-gpu \
    --disable-dev-shm-usage \
    --remote-debugging-port=9222 \
    --user-data-dir=/tmp/chromium-data
Restart=always
RestartSec=5
Environment=DBUS_SYSTEM_BUS_ADDRESS=unix:path=/var/run/dbus/system_bus_socket

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable hermes-browser.service
systemctl start hermes-browser.service

3. Wire up Hermes config

# Set CDP URL in .env
echo 'BROWSER_CDP_URL=ws://127.0.0.1:9222' >> ~/.hermes/.env

# Set local mode in config
hermes config set browser.cloud_provider local

# Restart gateway (or /reset in chat)
hermes gateway restart

4. Verify

hermes chat -q "Use browser_navigate to visit http://whatsmyuseragent.com and report the browser string" --toolsets browser

Expected: successful navigation, browser reports HeadlessChrome/<version>.

Mode B: browser-use.com Cloud (when local Chromium isn't enough)

Use cloud mode when sites detect headless Chromium, require real mobile/desktop fingerprints, or you need browser-use's anti-detection features.

# Get API key from https://browser-use.com/
# Then:
echo 'BROWSER_USE_API_KEY=<your-key>' >> ~/.hermes/.env
hermes config set browser.cloud_provider browser-use
hermes gateway restart

Cost: ~$0.02/hr per browser instance. No minimums.

Mode C: Browserbase (alternative cloud)

echo 'BROWSERBASE_API_KEY=<key>' >> ~/.hermes/.env
echo 'BROWSERBASE_PROJECT_ID=<project>' >> ~/.hermes/.env
hermes config set browser.cloud_provider browserbase
hermes gateway restart

Has a free tier suitable for light usage.

Mode D: Camofox (local anti-detection)

echo 'CAMOFOX_URL=http://localhost:<port>' >> ~/.hermes/.env

Configuration Reference

Config key / env var Default Mode Description
browser.cloud_provider browser-use All local, browser-use, browserbase, or camofox
BROWSER_CDP_URL Local WebSocket URL of local Chromium (e.g. ws://127.0.0.1:9222)
BROWSER_USE_API_KEY Cloud Key from browser-use.com
BROWSERBASE_API_KEY Cloud Key from browserbase.com
BROWSERBASE_PROJECT_ID Cloud Project ID from Browserbase
CAMOFOX_URL Local Camofox anti-detection URL
browser.inactivity_timeout 120 All Seconds before idle browser session is closed

SPA Loading Behavior

JS-rendered API doc sites (Redoc, Swagger UI, Stoplight) often show "Loading..." in the page title and have an empty accessibility tree on first browser_snapshot. This is normal — the SPA bootstraps, then renders the sidebar tree, then lazy-loads the content pane. The browser_snapshot tool captures the current accessibility tree state and will grow as the page renders.

Proven resolution sequence (netcup KVM, Chromium 149):

  1. browser_navigate(url) — returns {title: "Loading...", element_count: 0}. This is fine.
  2. browser_snapshot(full=true) — after ~2-3s, the sidebar appears with 7,000+ elements. The content pane may still be sparse.
  3. If the content pane is empty, use browser_click(ref) on a sidebar link to navigate to a specific endpoint, then browser_snapshot again. Redoc renders endpoint details on click.
  4. For bulk extraction, use browser_cdp with Runtime.evaluate on the correct target (identified via Target.getTargets) targeting the page's title/URL, not the chrome://newtab page. Execute document.body.innerText.substring(N, M) to page through the full rendered text.

See references/js-rendered-api-docs.md for the full probe pattern.

Auto-Provisioning Flow (any phone, any source)

Phones purchased from any retail source (Amazon, Best Buy, eBay, second-hand from a previous provider) can auto-provision on RingLogix/NetSapiens. The critical insight: MAC registration is the only per-phone step. Everything else (DHCP, provisioning server URL) is network-level config.

The Flow

Step What happens Who does it
1. Factory reset (used phones only) Hold reset pin or use phone menu to wipe old config Customer or you, one-time
2. DHCP discovery Phone boots, requests IP. Router serves DHCP options pointing to provisioning server Router/DHCP server (configured once)
3. MAC registration Register the phone's MAC address in RingLogix with model info API or portal — ?object=mac&action=create
4. Device linking Link MAC to a subscriber extension API or portal — ?object=device&action=create
5. Provisioning request Phone contacts the NDP (NetSapiens Device Provisioning) server, sends its MAC Phone does this automatically
6. Config push Server matches MAC, pushes SIP credentials, extension, caller ID, features RingLogix NDP server
7. Auto-reboot Phone reboots with new config, registers to VoIPSimplicity Automatic
8. Verify Check subscriber status, registration, and call capability API — ?object=subscriber&action=read

DHCP Options Required (configure once on your router/MikroTik)

DHCP Option Provider/Standard What it sets
Option 156 Yealink / Poly Provisioning server URL
Option 66 (TFTP server) Generic SIP phones TFTP server address for config files
Option 160 Grandstream HTTP provisioning URL
Option 43 (Vendor-specific) Cisco Cisco-specific provisioning

The actual provisioning server URL (e.g., prov.ringlogix.com) is configured in the RingLogix portal UI — it is not exposed through the REST API. The API provides the building blocks (MAC registration, device model queries) but not the provisioning server config itself.

MAC Endpoints (from RingLogix API docs)

All POST to https://api.ringlogix.com/pbx/v1/?object=mac&action=<action>.

Create: ?object=mac&action=create Permission: OMP (Office Manager / Reseller / Super User)

Param Required Description
mac Yes MAC address, hex only, 12 chars, no - : .
domain Yes Domain of new device
model Yes Phone model (query via Device_Model endpoint)
transport No NDP transport type
server, last_pull, date_created No Metadata
device1..device8 No Device field references
territory, notes No Organization
line1_ext..line2_ext No Extension lines
line1_enable..line8_enable No Per-line enable flags
line1_share..line8_share No Line sharing
overrides, dir_inc, presence No NDP overrides

Read: ?object=mac&action=read — Permission: Reseller

Param Required Description
mac Yes MAC to look up (hex, colons removed)
extension Yes Numerical string, searches lines with this extension associated with the MAC
checkExistance Yes Boolean — check if MAC exists

Response includes: mac, server, territory, dir_inc, presence, domain, model, device1-8, notes, line1-8_share, overrides, phone_ext, transport, fxs, sla, sidecar, resync, directory_support, user_agent, contact, registration information.

Update: ?object=mac&action=update — Permission: Reseller Requires mac; all other fields optional (model, line, device, transport, server, notes, overrides, etc.).

Delete: ?object=mac&action=delete — Permission: Reseller Requires mac (hex, colons removed) and domain.

Count: ?object=mac&action=count — Permission: Reseller Requires domain. Optional: territory, mac, mac_LIKE (partial match), model, notes.

Device Model Query

POST https://api.ringlogix.com/pbx/v1/?object=devicemodel&action=read

Permission: Reseller Optional: brand, model, portal_view, include_ndp_defs, ndp_syntax

Important API Notes

  • All endpoints use POST — parameters in form body
  • Routing via ?object= and ?action= query params (not RESTful paths)
  • Reseller permission required for provisioning ops; subscriber&action=list needs only Basic User
  • RingLogix provides a Postman test collection from the docs site
  • The docs site uses Redoc JS viewer — see references/js-rendered-api-docs.md for SPA navigation tips
  • Client ID/Secret from RingLogix support (separate from PBX user credentials) — this is required before any API call works

Reference Files

File Contents
references/js-rendered-api-docs.md Navigating JS-heavy API doc sites (Redoc, Swagger) — sidebar vs main pane behavior, probe sequence
references/ringlogix-api.md RingLogix (NetSapiens/CPaaS) API reference — OAuth2 auth, subscriber management, device provisioning, MAC endpoints, CDRs, and full endpoint catalog

Available Browser Tools

Tool Purpose Requires
browser_navigate Load a URL First call that initializes session
browser_snapshot Get accessibility tree with ref IDs browser_navigate first
browser_click Click element by ref ID browser_snapshot first
browser_type Type into a field by ref ID browser_snapshot first
browser_scroll Scroll up/down/left/right browser_navigate first
browser_back Navigate backward in history browser_navigate first
browser_press Press a keyboard key browser_navigate first
browser_console Get JS console output browser_navigate first
browser_get_images List images on current page browser_navigate first
browser_vision Screenshot + vision analysis browser_navigate first

Troubleshooting

Tools enabled but not appearing in function list

# Check if this machine
hermes tools list | grep browser    # should show "enabled"
grep BROWSER ~/.hermes/.env         # must show CDP URL or API key

Root cause: The browser toolset checks for a configured backend at startup. If no key/CDP URL is found, all browser tools silently gate out. Setting cloud_provider alone is NOT sufficient — the corresponding env var must exist.

Chromium exits with code 127 (binary not found) or path mismatch

$ ~/.cache/ms-playwright/chromium-1228/chrome-linux/chrome
bash: .../chrome: No such file or directory

The Playwright Chromium binary is NOT at chrome-linux/chrome. Since Playwright ~1.50, the path is chrome-linux64/chrome. This was confirmed on Playwright 1.61.0 / Chromium 1228 installed Jul 7, 2026 on Debian 13. Always use:

find ~/.cache/ms-playwright/chromium-*/ -name chrome -type f

Then use the full discovered path in the systemd unit's ExecStart. The incorrect chrome-linux/chrome path causes exit code 127 immediately with no CDP port responding.

Chromium fails to start with exit code 133

Error: dbus/bus.cc failed to connect to the bus

Fix 1: Start the D-Bus daemon:

dbus-daemon --system --fork

Fix 2: Set the env var in the systemd unit:

Environment=DBUS_SYSTEM_BUS_ADDRESS=unix:path=/var/run/dbus/system_bus_socket

Fix 3: Install dbus-x11 if missing:

apt-get install -y dbus dbus-x11

Headless Chrome crashes on KVM VPS (Debian 13)

Symptoms: Process exits with 133 or 255 immediately, no CDP port responding.

Known issue on netcup KVM (nested virtualization): The system CPU scaling files (/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq) are missing. These errors are non-fatal warnings — the real cause is usually:

  1. Missing --no-sandbox flag
  2. D-Bus socket unavailable
  3. Wrong path to the chrome binary (Playwright installs to chrome-linux64/chrome, not chrome-linux/chrome)

Always check the binary path with find:

find ~/.cache/ms-playwright/chromium-*/ -name chrome -type f

Test: verify browser is working

From any Hermes session:

hermes chat -q "Open browser_navigate to http://example.com and report the page title" --toolsets browser

Expect: Example Domain title returned.

SPA site returns "Loading..." and empty snapshot — that's normal

When browsing JS-rendered API docs (Redoc, Swagger), the first browser_navigate call returns title: "Loading..." with element_count: 0. This is expected behavior — the SPA has not finished rendering. Resolution sequence proven on netcup KVM with Chromium 149:

  1. Call browser_navigate(url) — accept the "Loading..." title
  2. Call browser_snapshot(full=true) — after ~2-3s, sidebar appears with 7,000+ elements
  3. If content pane is empty, use browser_click(ref) on a sidebar link, then browser_snapshot again
  4. For bulk extraction, use browser_cdp with Runtime.evaluate on the correct page target (identified via Target.getTargets) targeting the page's URL/title, not the chrome://newtab page. Execute document.body.innerText.substring(N, M) to page through rendered content

See references/js-rendered-api-docs.md for the full probe pattern.

Multiple page targets exist — use Target.getTargets to find the right one

When browser_cdp calls with Runtime.evaluate fail with method not found, the issue is often that the browser has multiple open tabs (new tab page + your page). Use Target.getTargets to find the page target by URL, then pass target_id in subsequent CDP calls. The browser_navigate tool interacts with the most recent target, but raw CDP calls need the explicit id.

  • computer-use — desktop GUI driving (separate from browser tools; use browser_* for web automation)
  • server-architecture-plan — service placement on this server (browser runs well on 8C/15G boxes)
  • reboot-with-health-check — verify hermes-browser.service comes back after server reboot