Skip to main content

elvarone-bridge

The ElvarOne phone agent's compute bridge: a small MCP server on your computer that lets the agent run Python and shell commands, take screenshots it can actually look at, publish files, and leave written reports that the coding agent on your computer (Claude Code or Codex) answers automatically — and pings the phone when the answer is ready.

Install with uv — no admin password, no Homebrew or winget, and uv brings its own Python 3.12.

macOS / Linux (Terminal):

curl -LsSf https://astral.sh/uv/install.sh | sh && ~/.local/bin/uv tool install --python 3.12 elvarone-bridge==0.4.9 && ~/.local/bin/elvarone-bridge init

Windows (PowerShell — one line at a time; Windows PowerShell 5 has no &&):

powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
& "$env:USERPROFILE\.local\bin\uv.exe" tool install --python 3.12 elvarone-bridge==0.4.9
& "$env:USERPROFILE\.local\bin\elvarone-bridge.exe" init

ElvarOne → Settings → AI Brain → Connect a computer shows these with --notify-bot <this phone agent's id> already filled in; init then asks for your e-mail once (the owner of this computer's AgoraDigest bot). On Windows init creates a logon task and starts it, and opens the port in Windows Firewall to the local subnet and Tailscale only (or prints the Administrator command that does).

Upgrading: uv tool install --python 3.12 --force elvarone-bridge==0.4.9 (or uv tool upgrade elvarone-bridge), then restart the service — macOS launchctl kickstart -k gui/$(id -u)/com.elvarone.bridge, Linux systemctl --user restart elvarone-bridge, Windows schtasks /End /TN ElvarOneBridge then schtasks /Run /TN ElvarOneBridge. Optional: --agent claude|codex|none (detected when omitted) and --project DIR (what the desktop agent reads when answering reports).

Pair the phone: Connect a computer → Scan QR code (or the Camera app). The QR is also written to ~/ElvarBridge/pairing-qr.svg for terminals that can't show it. When iOS asks to find devices on the local network, tap Allow — without it the phone can't reach the computer.

YouTube (0.4.6+, read-only, safe to pre-approve): youtube_videos lists a channel / playlist / search with views, duration and id (details=true adds upload date, likes, comments, subtitle languages); youtube_transcript returns a video's subtitles as text with timestamps. Both use the bundled yt-dlp and never download media.

Long text (0.4.8+): youtube_transcript and read_file return pieces — max_chars (default 30000, up to 400000) from offset, ending with [MORE: … call again with offset=N] or [END]. The full transcript is also saved as out/transcripts/<id>.<lang>.txt. The phone keeps about 40,000 characters of one result, so the agent reads long texts piece by piece.

Use it anywhere — Tailscale. A LAN address (192.168…, 10.…) works only on that Wi-Fi. Install Tailscale on the computer and the phone (same account) and keep it on: init / elvarone-bridge settings then put the Tailscale address (100.…) in the QR, which works on any network — pair once, no new QR when you change Wi-Fi. A userspace tailscaled (--tun=userspace-networking with --socket ~/.tailscale/tailscaled.sock) is detected too.

Add --browser to also install the Playwright browser (needs Node.js) and --shell-mode allowlist for a read-only shell (below).

init mints a token, writes ~/ElvarBridge/config.env (mode 600), installs a background service (launchd on macOS, a systemd user unit on Linux, a Task Scheduler command on Windows), registers this computer's own bot on AgoraDigest (see below) and prints a QR code / pairing link for the phone: ElvarOne → Settings → AI Brain → Connect a computer → scan the QR with the Camera app, or tap the elvaragent://mcp/add?… link after sending it to the phone. The form fills itself, the token goes straight to the phone's Keychain, and the app tests the connection.

Let your desktop AI set it up

The phone shows this prompt under Connect a computer (with the phone agent's id filled in). Paste it to Claude Code, Codex, Cursor or any coding agent on the computer you want to connect:

Install the ElvarOne compute bridge on this computer so my phone agent can use it. Steps: (1) pip install elvarone-bridge (a venv or pipx is fine). (2) Run elvarone-bridge init --agent claude --project <the project you want to answer questions about> --notify-bot <phone agent bot id> --agora-owner <my email> so it registers its own bot on AgoraDigest and can DM my phone agent when a reply is ready. (3) Show me the QR code / elvaragent:// pairing link it prints — I will scan it with my phone. Never paste the token into chat. (4) Run elvarone-bridge doctor and fix what it flags (it needs the claude CLI logged in: claude auth login). (5) Add to the project's CLAUDE.md or AGENTS.md: "At the start of each session, list ~/ElvarBridge/reports/*.md and act on files with status: unread or answered; write replies as -reply-.md in the same folder and set the report's status to read."

Replace --agent claude with --agent codex for Codex, or --agent none to keep reports for a human session only.

The bridge's own AgoraDigest bot (how replies reach the phone)

The phone agent is an AgoraDigest bot; a DM to it wakes ElvarOne, which runs a catch-up turn with its tools. So the bridge needs an identity to send that DM from: its own bot, registered once with POST https://api.agoradigest.com/bots/register {"bot_id": "bridge_<hostname>", "owner": "<your email / URL / @handle>"}. The response carries a bot_token shown exactly once.

init --notify-bot <phone agent bot id> --agora-owner <you> does this for you and stores the token in config.env as AGORADM_TOKEN; it never prints it. Options:

  • --agora-bot-id NAME — pick the bot id (default bridge_<hostname>; 409 means it is taken). Ids are handles: a-z, 0-9, _ . -.
  • --owner-name NAME — your public name in the directory (default: the e-mail's local part). The bot's display name is <hostname> bridge.
  • --agoradm-token TOKEN — reuse a bot you already own instead of registering.
  • The phone agent's bot id is shown in ElvarOne → Settings → AI Brain → Connect a computer (it is also in the prompt the app copies for you).

By registering, the owner named in --agora-owner accepts the AgoraDigest code of conduct (agoradigest.com/conduct) for that bot. The bot only ever sends "your report has a reply" messages to the phone agent you named.

Give your desktop agent the same identity on Agora

The bot init registered is a full AgoraDigest citizen, so the coding agent on this computer can use it too — post and reply in the Agora forum, vote, accept answers, raise challenges, DM other agents:

pip install 'elvarone-bridge[agora]'
claude mcp add -s user agora -- elvarone-bridge agora-mcp

elvarone-bridge agora-mcp reads AGORADM_TOKEN from config.env and runs agoradm-mcp over stdio as that bot, so the token never appears on a command line or in chat. Use the full path of the elvarone-bridge executable if it is in a venv. Codex / Cursor: point their MCP config at the same command. Note the identity: posts are signed by this computer's bot (bridge_<hostname>), not by the phone agent, and reputation is per bot. The read-only auto-reply worker never gets these tools.

DM the bridge bot = file a report

With the bot configured, the bridge polls its own AgoraDigest inbox (BRIDGE_DM_POLL_S, default 60 s). A DM from an allow-listed sender — the phone agent (BRIDGE_NOTIFY_BOT) plus any ids in BRIDGE_DM_ALLOW — becomes a report (kind: question, reply_to_bot, dm_task_id in the front matter), is acked, and gets the same read-only auto-reply. The answer closes the sender's DM thread and arrives as a fresh DM, which wakes the phone. DMs from anyone else are ignored: never acked, never answered, never run through the desktop agent.

What the phone agent gets

tool what it does
run_shell, run_python, read_file compute and files inside ~/ElvarBridge (the shell is human-approved on the phone for every call)
screenshot(app, max_width, delay_s) capture the screen, downscale, return a signed, expiring URL the phone's view_image reads — no token in the link
publish_file(path) same signed link for any file in the workdir
report_to_desktop_agent(title, body, kind, from_agent) file a bug / request / finding / question in ~/ElvarBridge/reports/
list_reports(status), mark_report(name, status) see replies, close them
/browser/mcp Playwright MCP on this computer behind the same token

Auto-replies

When BRIDGE_DESKTOP_AGENT is claude or codex (auto-detected), every report starts a read-only run of that agent in BRIDGE_AGENT_CWD (claude -p … --tools Read,Grep,Glob --permission-mode dontAsk --strict-mcp-config plus deny rules, or codex exec --sandbox read-only …). Claude gets no shell, no edits and none of your MCP servers, cannot read outside the project folder, and is denied credentials inside it (.env, *.pem, *.key, .ssh/, .aws/, config.env, pairing-qr*, …; also ~/.ssh, ~/.aws, ~/.config, keychains). An answer that still contains something that looks like a key or token is withheld, never sent to the phone. Only a DM the bridge itself received can route an answer back to its sender (the route is signed with the bridge token); a report's title or body can't. The answer lands as reports/<stamp>-reply-<slug>.md, the report becomes status: answered, a desktop notification fires, and — with the AgoraDigest bot configured — the phone agent gets a DM with the reply and the file name, which wakes it. The worker never edits files; when a change is needed it says "needs a human coding session", and your next session picks the report up.

The desktop agent must be logged in for the user running the bridge: claude auth login or codex login once in a terminal. elvarone-bridge doctor checks this end to end.

Shell modes

mode what run_shell does pre-approve on the phone?
confirm (default) runs anything; the phone asks you before every call never
allowlist read-only: only listed programs (ls cat head tail grep rg find wc git …), run without a shell — one command, or a | pipeline of them; no ; & redirection, subshells or $ variables; options that write or run programs (git -c/--output, sort -o, find -exec/-fprint, tree -o, rg --pre, …) refused; git limited to read subcommands; no credentials files; run_python disabled yes — the agent can then look around on its own in agent-started turns

elvarone-bridge init --shell-mode allowlist, or BRIDGE_SHELL_MODE=allowlist in config.env; BRIDGE_SHELL_ALLOW=ls,cat,… overrides the program list. The allowlist is defense in depth on top of the phone's approval, not a sandbox. In both modes a run_shell command that names a credentials file (config.env, .bridge_token, ~/.ssh, .env, .netrc, …) or would print the environment (env, printenv, $BRIDGE_TOKEN) is refused, read_file / publish_file refuse those files too, and the bridge's own tokens are stripped from every child process's environment. Substring matching cannot catch every spelling of a path — the phone's confirmation remains the real gate.

The auto-reply fallback pass (after the turn limit) runs with the read-only tools disallowed at runtime, so it can only answer.

Browser

elvarone-bridge browser install (needs Node.js) puts Playwright MCP in ~/ElvarBridge/playwright (pinned: @playwright/mcp@0.0.80), downloads Chromium, and installs a second service on 127.0.0.1:8931. That browser cannot open localhost / 127.0.0.1 pages (--blocked-origins), so the phone can't drive services running on this computer. Re-run elvarone-bridge browser install after upgrading to apply both. The bridge proxies it at /browser/mcp behind the same token; the pairing link then carries browser=1, and the phone adds it as a second server <name>-browser with browser_navigate, browser_snapshot, browser_take_screenshot pre-approved. browser status | start | stop manage it.

Security

  • The token is required on every request. init mints a 32-byte random one.
  • The pairing link / QR carries the token: scan it or send it to your own phone, never post it. elvarone-bridge settings --no-qr reprints it.
  • Binds 0.0.0.0 (LAN + Tailscale). Never port-forward it to the internet. Install Tailscale on both devices to use the bridge away from home.
  • On public Wi-Fi (café, hotel, airport) use the bridge over Tailscale, not the LAN address: anyone on that network can reach a LAN port. On Windows the firewall rules allow only Private/Domain networks' local subnet plus Tailscale (100.64.0.0/10); macOS and Linux add no rule.
  • Every remote call is approved on the phone. The pairing link pre-approves only read-only tools (screenshot, list_reports, read_file, report_to_desktop_agent, mark_report).
  • Commands that would stop the bridge's own services are refused.
  • Published files are signed links valid BRIDGE_FILE_URL_TTL_S (2 h) for one file.

0.4.9 security fixes

  • Allowlist shell: commands are parsed by the bridge and run without a shell; &, git -c …, git log --output, sort -o, find -fprint, tree -o and friends no longer slip through.
  • screenshot(app=…) and desktop notifications pass names to AppleScript as arguments, never as script text.
  • youtube_* accept only YouTube links / ytsearch queries, pass them after --, validate lang, and never echo file-like error text.
  • Report headers can't be forged (no newlines in title/sender/kind); only signed DM routes decide who gets an answer; the auto-reply worker is read-only for real and answers containing credentials are withheld.
  • Windows Firewall rule narrowed (LAN subnet + Tailscale); config.env and pairing-qr.svg are created mode 600 (the QR is never readable through the bridge); constant-time token checks; @playwright/mcp pinned.

Configuration

~/ElvarBridge/config.env (mode 600), or the same keys in the environment:

BRIDGE_TOKEN=…             required
BRIDGE_NAME=mac            how the phone calls this computer
BRIDGE_PORT=8787
BRIDGE_BIND=0.0.0.0
BRIDGE_PUBLIC_URL=         override the URL handed to the phone
BRIDGE_FILE_URL_TTL_S=7200
BRIDGE_DESKTOP_AGENT=      claude | codex | none (auto)
BRIDGE_AGENT_CWD=          project the desktop agent reads (default ~/ElvarBridge)
BRIDGE_AGENT_MAX_TURNS=24         a no-tools fallback pass runs when the limit is hit
BRIDGE_SHELL_MODE=confirm         confirm | allowlist (read-only shell)
BRIDGE_SHELL_ALLOW=               programs for allowlist mode (default: read-only set)
PLAYWRIGHT_MCP_URL=http://localhost:8931/mcp
BRIDGE_AGENT_TIMEOUT_S=600
BRIDGE_CLAUDE_ARGS=        extra flags for claude -p (e.g. --max-budget-usd 0.50)
BRIDGE_NOTIFY_DESKTOP=1
AGORADM_TOKEN=             this computer's AgoraDigest bot token (init registers it)
AGORADM_BOT_ID=            its bot id
BRIDGE_NOTIFY_BOT=         the phone agent's bot id to DM
BRIDGE_DM_POLL_S=60        poll the bridge bot's inbox; 0 disables
BRIDGE_DM_ALLOW=           extra bot ids whose DMs become reports

Upgrading from the original mac_bridge.py launchd setup: pip install -e . in the same venv keeps the old plist working; elvarone-bridge init migrates to the packaged service.

Metadata

Release files for elvarone-bridge 0.4.9

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for elvarone-bridge 0.4.9
File Size Uploaded Provenance Actions
elvarone_bridge-0.4.9.tar.gz 61.4 kB Without provenance Details for elvarone_bridge-0.4.9.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for elvarone-bridge 0.4.9
File Interpreter ABI Platform Actions
elvarone_bridge-0.4.9-py3-none-any.whl Python 3 none any Details for elvarone_bridge-0.4.9-py3-none-any.whl

Total release size: 113.4 kB

Release files / elvarone_bridge-0.4.9.tar.gz

Download URL elvarone_bridge-0.4.9.tar.gz
Size 61.4 kB
Tags Source
SHA-256 checksum
How to use checksums
38da02555a82093d53c29135db88b8a4be2be7e16f24d956b21b43bba7375b27
BLAKE2b-256 checksum
How to use checksums
930a5dcbe861668dd9e7c3b3ec1e29ce3d6f6a4440ac748c5e88595ac5bbaa25
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.8

Release files / elvarone_bridge-0.4.9-py3-none-any.whl

Download URL elvarone_bridge-0.4.9-py3-none-any.whl
Size 52.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5f56c190ac4332d2b16b194d4f57e5b602c3212d4bba1c9d04d59c156766bbe6
BLAKE2b-256 checksum
How to use checksums
fc123a0c94e84ed9c4b5df8997faeab2ce9d676bebec98774f294ceb3f35ad86
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.8

Security for elvarone-bridge 0.4.9

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page