@hydra-acp/browser
v0.1.48
Published
Browser-based UI for hydra-acp sessions.
Maintainers
Readme
hydra-acp-browser
A browser-based UI for hydra-acp sessions. Runs as a hydra extension (or standalone) and serves a small single-page app on localhost that lists live sessions, mirrors them in real time, and lets you prompt, approve permission requests, switch modes/models, create fresh sessions, kill old ones, and browse the project files of any session — all from a phone or laptop browser.
The hydra master token never leaves the machine; the browser authenticates
you with a password (set via hydra-acp auth password) and issues its own
session cookie.
Install
npm install -g @hydra-acp/cli @hydra-acp/browserThis drops the hydra-acp (and hydra) CLI plus an hydra-acp-browser
binary on your PATH. The CLI dispatches hydra-acp <name> to any
hydra-acp-<name> binary on PATH, so the browser is also reachable as
hydra-acp browser.
Register it as a hydra extension:
hydra-acp extensions add hydra-acp-browserextensions add is config-only — it doesn't spawn anything yet. Either
bounce the daemon, or, if the daemon is already running, kick the
extension into life:
hydra-acp extensions start hydra-acp-browserSet the sign-in password once, on the machine running the daemon:
hydra-acp auth passwordOpen the printed URL and log in with the password you just set; a
successful login sets an hb_session cookie and subsequent requests are
authenticated by that cookie alone.
That's the whole setup for local (loopback) use. If you land on an empty
session list, that's likely because no agent is configured yet — the
browser is a client of hydra-acp, not a coding agent itself. Pick one
with hydra-acp agent list / hydra-acp agent set <id>; see
hydra-acp's README
for the full picture (auth, per-session/per-editor overrides, switching
mid-conversation).
Building from source instead, or want it reachable from your phone? Keep reading.
Access from your phone or over your LAN
Loopback-only access needs nothing extra. Reaching it from another device means binding to a real interface, which requires HTTPS (the server refuses a non-loopback bind otherwise — same rule as the hydra daemon).
If you're on Tailscale, this is one command:
hydra-acp-browser tailscale setupIt mints a real Let's Encrypt cert via tailscale cert, binds to your
tailnet IP, and restarts the extension for you. It also sets
BROWSER_PREFERRED_HOST to your MagicDNS name, so the server shows and
QR-encodes the friendly mybox.tailxxxx.ts.net hostname instead of the
raw tailnet IP. See HTTPS below for the self-signed alternative
if you're not on Tailscale, plus cert-trust steps for iOS/macOS/Linux.
Grab the URL (and a scannable QR code for your phone) any time, without starting anything, with:
hydra-acp-browser urlBuilding from source
git clone https://github.com/smagnuso/hydra-acp-browser.git ~/dev/hydra-acp-browser
cd ~/dev/hydra-acp-browser
npm install
npm run buildPoint the extension at the build instead of the npm binary:
hydra-acp extensions add hydra-acp-browser \
--command node \
--args ~/dev/hydra-acp-browser/dist/index.jsThat writes the equivalent entry into ~/.hydra-acp/config.json:
{
"extensions": {
"hydra-acp-browser": {
"command": ["node"],
"args": ["/home/you/dev/hydra-acp-browser/dist/index.js"],
"enabled": true
}
}
}After a rebuild, restart (not start) is the right call:
hydra-acp extensions restart hydra-acp-browserOn startup, hydra spawns hydra-acp-browser with these env vars set:
HYDRA_ACP_DAEMON_URL, HYDRA_ACP_TOKEN, HYDRA_ACP_WS_URL.
Stdout/stderr land in ~/.hydra-acp/extensions/hydra-acp-browser.log.
Running standalone, without the hydra extension wrapper: set
HYDRA_TOKEN in ~/.hydra-acp/browser.conf (or export
HYDRA_ACP_TOKEN), then:
npm startHow it works
hydra REST +--------------------+ browser
/v1/sessions <---------- | | ----> GET /
| hydra-acp-browser | <----> /ws?session=<id>
hydra WSS <----------> | |
/acp +--------------------+
|
~/.hydra-acp/browser/
linkThe extension exposes:
- HTTP routes at
/api/sessions(GET list, POST create),/api/agents,/api/kill,/api/files/list,/api/files/read,/api/sessions/:id/export(GET — download a*.hydrabundle),/api/sessions/import(POST — accept a bundle),/api/health. - A WebSocket bridge at
/ws?session=<id>. Each browser tab gets its own attach to hydra's/acp; ACP frames flow through unchanged in the upstream→browser direction. Browser→upstream traffic is method-whitelisted (session/prompt,session/cancel,session/set_mode,session/set_model, plus permission responses) so a tab can't issue arbitrary admin calls.
HTTPS
Optional on 127.0.0.1, required for any non-loopback bind (the server
refuses otherwise — same rule as the hydra daemon).
On a tailnet (recommended)
hydra-acp-browser tailscale setupMints a real Let's Encrypt cert via tailscale cert, points
BROWSER_TLS_CERT/BROWSER_TLS_KEY at it, binds BROWSER_HOST to your
tailnet IP specifically (not 0.0.0.0 — your LAN never sees it), sets
BROWSER_PREFERRED_HOST to your MagicDNS name (implicitly allowed for the
Host-header check, and used for display — see hydra-acp-browser url),
and offers to restart the extension. No trust prompts, no manual SAN
wrangling. Certs expire after ~90 days; re-run the same command to renew.
If tailscale cert needs root (no operator set — see tailscale set
--operator=$(whoami) to fix this permanently), the wizard offers to retry
with sudo and fixes up file ownership afterward so the server can still
read the key.
Without Tailscale: self-signed
The simplest setup is a self-signed cert in ~/.hydra-acp/browser/tls/.
Generate cert + key. ECDSA P-256, 5-year validity, with a SAN covering loopback. Add any extra hostnames you'll hit it from (LAN IP, etc.) to the SAN inline:
mkdir -p ~/.hydra-acp/browser/tls && chmod 700 ~/.hydra-acp/browser/tls cd ~/.hydra-acp/browser/tls SAN='subjectAltName=DNS:localhost,DNS:'"$(hostname)"',IP:127.0.0.1,IP:::1' openssl req -x509 \ -newkey ec -pkeyopt ec_paramgen_curve:P-256 \ -sha256 -days 1825 -nodes \ -keyout key.pem -out cert.pem \ -subj "/CN=hydra-acp-browser" \ -addext "$SAN" \ -addext "extendedKeyUsage=serverAuth" chmod 600 key.pem cert.pemVerify the SAN landed:
openssl x509 -in cert.pem -noout -text | grep -A1 'Subject Alternative Name'The cert's CN doesn't matter to modern browsers — only the SAN does. Skipping
-addext "subjectAltName=…"will make every browser reject the cert withNET::ERR_CERT_COMMON_NAME_INVALID.Wire into config. Append to
~/.hydra-acp/browser.conf:BROWSER_TLS_CERT=~/.hydra-acp/browser/tls/cert.pem BROWSER_TLS_KEY=~/.hydra-acp/browser/tls/key.pemTo expose beyond loopback, also set:
BROWSER_HOST=0.0.0.0 BROWSER_ALLOWED_HOSTS=mybox,100.64.1.5Every entry in
BROWSER_ALLOWED_HOSTSmust also be in the cert's SAN.Apply with
hydra-acp extensions restart hydra-acp-browser. The log line should now readlistening on https://…and theOpen: https://…/URL is what you load. The auth cookie carriesSecureautomatically when serving HTTPS.Trust the cert. Self-signed certs trip browser warnings.
- Click-through: open the URL, accept the warning. Per-site only.
- Linux Chrome/Chromium:
certutil -d sql:$HOME/.pki/nssdb -A -t "P,," -n hydra-acp browser -i ~/.hydra-acp/browser/tls/cert.pem - macOS: double-click
cert.pem, add to System keychain, set "Always Trust" in Get Info. - iOS: AirDrop/email
cert.pemto the device, install profile (Settings → General → VPN & Device Management), then enable under Settings → General → About → Certificate Trust Settings.
If you flip-flop between HTTP and HTTPS, the Secure cookie set under
HTTPS won't be sent over plain HTTP — clear cookies for the site (or hit
/logout) and sign in again.
Configuration keys
~/.hydra-acp/browser.conf (KEY=VALUE). All keys are optional unless noted.
| Key | Default | Notes |
|------------------------------|----------------------------------------|-------|
| BROWSER_HOST | 127.0.0.1 | Bind host. Non-loopback requires TLS. |
| BROWSER_PORT | 5514 | Listen port. |
| BROWSER_TLS_CERT | (none) | If set with BROWSER_TLS_KEY, listen on HTTPS. |
| BROWSER_TLS_KEY | (none) | Path to TLS key. |
| BROWSER_LINK_FILE | ~/.hydra-acp/browser/link | URL written for convenience. |
| BROWSER_PREFERRED_HOST | (none) | Hostname shown in the link file/logs and hydra-acp-browser url instead of BROWSER_HOST (set by tailscale setup to your MagicDNS name). Always implicitly allowed for the Host-header check — no need to also list it in BROWSER_ALLOWED_HOSTS. |
| BROWSER_ALLOWED_HOSTS | empty | Comma-sep extra Host values for DNS-rebind allowlist (e.g. Tailscale name). |
| BROWSER_FILE_MAX_BYTES | 262144 | Upper bound for /api/files/read. |
| HYDRA_DAEMON_URL | from env / http://127.0.0.1:55514 | HYDRA_ACP_DAEMON_URL env wins. |
| HYDRA_WS_URL | derived | HYDRA_ACP_WS_URL env wins. |
| HYDRA_TOKEN | (required) | Same precedence as the slack ext. |
| DEBUG | false | Verbose logging. |
Security
- Password vs. hydra token. The browser only ever sees the session token the daemon issues after a password login. The hydra master token stays on the server.
- Loopback or TLS. The server refuses to bind a non-loopback host
unless
BROWSER_TLS_CERTandBROWSER_TLS_KEYare configured — mirrors hydra's daemon. - DNS-rebind protection. The
Hostheader must match127.0.0.1[:port],localhost[:port],BROWSER_PREFERRED_HOST, or an entry inBROWSER_ALLOWED_HOSTS. - CSRF. State-changing requests check
Origin(against<scheme>://<allowed-host>:<port>) andSec-Fetch-Site(same-origin/noneonly). - CSP. The HTML response carries a per-request nonce; only
'self'and the matching nonce are allowed for scripts/styles. - Rate limit. 10 failed auth attempts in 15 min from a single
remote IP triggers a
429until the window rolls. - WS method whitelist. A compromised tab can only send
session/prompt,session/cancel,session/set_mode,session/set_model, plus responses to permission requests it has actually been forwarded. fs/*reverse calls from agents are rejected at the bridge so a tab can't accidentally expose the user's filesystem to the agent via this surface.
Tests
npm testRuns the auth, CSRF, file-traversal, and bridge-whitelist tests under the built-in Node test runner.
Status
Experimental. v1 covers list / chat / tool calls / permissions /
session create / kill / file browse / mode + model picker /
session export + import (download a *.hydra bundle from any session,
re-import a bundle from disk). Out of scope: multi-user UI, image
upload from the browser into the agent, transcript search.
License
MIT.
