Mirror a terminal coding-agent session (Claude Code or Codex) onto Even
Realities G2 glasses. even-better speaks the same HTTP/SSE protocol as
@evenrealities/even-terminal, so the stock Even App connects by scanning a QR
code — but instead of spawning a new agent, it mirrors an agent you're already
running inside a terminal multiplexer (herdr or
cmux). The terminal session and the
glasses are the same process: what you see in the pane streams to the glasses,
and prompts from the glasses are typed into the pane.
┌── herdr / cmux ───────┐ ┌── even-better ─────────┐ ┌── Even App ──┐
│ claude / codex panes │◄────►│ HTTP + SSE │◄────►│ → G2 glasses │
│ (your live sessions) │socket│ even-terminal protocol │ WiFi │ │
└───────────────────────┘ └────────────────────────┘ └──────────────┘
Unofficial project — not affiliated with or endorsed by Even Realities, Anthropic, OpenAI, herdr, or cmux. The wire protocol is an independent, interoperable implementation of what
@evenrealities/even-terminalspeaks; agent output is read from the local session files Claude Code and Codex write on your own machine. No code from those projects is included, and their formats are used as observed and may change.
- macOS (primary target), with one terminal multiplexer running and at least
one
claudeorcodexagent live in a pane — even-better mirrors those; it never spawns an agent itself. Either: - Node.js ≥ 18 and pnpm.
- Even Realities G2 glasses paired with the Even App on your phone.
- For remote access: the matching CLI (
tailscale,cloudflared,ngrok,bore, or the built-insshfor pinggy) — see Remote access.
pnpm install
pnpm start # prints a QR code — scan it with the Even AppThat's it — the glasses now show your live agent. Prompts you send from the glasses are typed into the pane; the agent's replies stream back.
If both herdr and cmux are running, pick one with MUX=herdr or MUX=cmux.
- Mirrors, never spawns. No extra agent process and no extra token spend beyond what your terminal session already uses. Model, permission mode, and everything else follow whatever the pane's agent is configured with.
- Lossless output. It reads the agent's structured session transcript (Claude/Codex jsonl) as the source of truth, so what reaches the glasses matches the pane without screen-scraping guesswork. (Until that transcript is available — a fresh agent before its session exists — it streams no content yet, rather than scraping the screen.)
- Interactive. Permission prompts and questions become menus on the glasses you can answer; the answer is sent back into the pane. Prompts and interrupts from the glasses drive the same pane.
For how this is built, see docs/ARCHITECTURE.md.
Everything is optional — pnpm start works with no flags.
| Var | Default | Meaning |
|---|---|---|
MUX |
auto | Multiplexer backend: herdr or cmux. Auto-detects; if both are present, prompts on a TTY (set this to choose) |
PORT |
auto |
HTTP port. Unset/auto/0 asks the OS for a free port; set a number only when you need a fixed one |
BIND_HOST |
auto |
Local bind/QR host: auto, lan, local, tailscale, or a literal IP. PUBLIC_ACCESS requires auto or loopback |
PUBLIC_ACCESS |
none |
Public access provider: none, tailscale-funnel, pinggy, bore, ngrok, cloudflared |
PUBLIC_BASE_URL |
– | Existing external URL to put in the QR instead of starting PUBLIC_ACCESS (named tunnels, reverse proxies). Requires a fixed PORT |
BRIDGE_TOKEN |
ephemeral | Bearer token encoded into the QR. Unset means a fresh per-process token every launch |
LOG |
normal |
Logging mode: off, normal, debug, or trace |
LOG_FILE |
/tmp/even-better-<id>.events.log |
JSONL event log path |
QR |
1 |
Print a QR code. Set 0 to print only the URL |
STREAM_TICK_MS |
140 |
Milliseconds between text-reveal frames on the glasses. Larger = text types out slower (easier to read before it scrolls); smaller = faster |
The phone must be able to reach your machine over the network (same LAN, Tailscale, etc.).
Security. The endpoint can drive a coding agent — i.e. run code on your
machine — guarded only by the bearer token. On a trusted home LAN the default QR
is fine; on an untrusted network use BIND_HOST=tailscale. See
SECURITY.md.
BIND_HOST=tailscale(recommended) — private, WireGuard-encrypted, stable IP, no time limit, nothing exposed publicly. Best for regular use.PUBLIC_ACCESS=tailscale-funnel— public HTTPS at your stable*.ts.netname; the phone needs no client and SSE works. Requires Funnel enabled in the Tailscale admin console once.PUBLIC_ACCESS=pinggy— quick public access over the built-inssh, zero install, supports SSE. Free tunnels rotate every 60 min.bore/ngrokalso work (ngrokneeds an authtoken).- Cloudflare — the quick provider (
cloudflared, trycloudflare.com) does not support SSE. Use a named tunnel pointed at the local URL instead (fixedPORT+PUBLIC_BASE_URL=https://<your-hostname>).
even-terminal-compatible endpoints under /api: GET /events (SSE) ·
GET /sessions · GET /info · POST /prompt · POST /permission-response ·
POST /question-response · POST /interrupt · GET /status · GET /messages ·
GET /sessions/:id/history · GET /update-check. Field-level reference in
docs/PROTOCOL.md.
- Cost isn't computed (token counts are reported;
costUsdis always 0). - A claude/codex pane shows content only from its structured transcript — a fresh agent shows nothing until its session's jsonl exists (no lossy screen scraping).
- Permission menus are read from the screen; exotic prompts fall back to a "check your terminal" notification.
- even-better only mirrors — it won't start a brand-new session from the glasses; it picks the focused pane.
See CONTRIBUTING.md and docs/ARCHITECTURE.md.