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 |
|
|
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.
Mode A: Local Chromium via Playwright (free, recommended default)
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):
browser_navigate(url)— returns{title: "Loading...", element_count: 0}. This is fine.browser_snapshot(full=true)— after ~2-3s, the sidebar appears with 7,000+ elements. The content pane may still be sparse.- If the content pane is empty, use
browser_click(ref)on a sidebar link to navigate to a specific endpoint, thenbrowser_snapshotagain. Redoc renders endpoint details on click. - For bulk extraction, use
browser_cdpwithRuntime.evaluateon the correct target (identified viaTarget.getTargets) targeting the page's title/URL, not the chrome://newtab page. Executedocument.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=listneeds 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.mdfor 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:
- Missing
--no-sandboxflag - D-Bus socket unavailable
- Wrong path to the
chromebinary (Playwright installs tochrome-linux64/chrome, notchrome-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:
- Call
browser_navigate(url)— accept the "Loading..." title - Call
browser_snapshot(full=true)— after ~2-3s, sidebar appears with 7,000+ elements - If content pane is empty, use
browser_click(ref)on a sidebar link, thenbrowser_snapshotagain - For bulk extraction, use
browser_cdpwithRuntime.evaluateon the correct page target (identified viaTarget.getTargets) targeting the page's URL/title, not thechrome://newtabpage. Executedocument.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.
Related Skills
computer-use— desktop GUI driving (separate from browser tools; usebrowser_*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