Remote control for your local Codex on your Mac from your iPhone.
This project started as a local-only fork of Zane (credit: https://github.com/z-siddiqi/zane):
curl -fsSL https://raw.githubusercontent.com/ddevalco/coderelay/main/scripts/install-local.sh | bash
tailscale up
tailscale serve --bg http://127.0.0.1:8790
http://127.0.0.1:8790/admin and sign in with your Access Token.If anything doesn’t work, run:
~/.coderelay/bin/coderelay ensure
docs/INSTALL.mddocs/CLI.mddocs/ADMIN.mddocs/TROUBLESHOOTING.mddocs/TROUBLESHOOTING_PROVIDERS.mddocs/ARCHITECTURE.mddocs/CI.mddocs/NATIVE_IOS_ROADMAP.mddocs/DIFFERENCES_FROM_ZANE.mddocs/DEVELOPMENT.mddocs/TESTING.mdCONTRIBUTING.mdWe track the canonical backlog in GitHub Projects:
All bugs, features, and reliability work are tracked as GitHub Issues:
Workflow rules:
Fixes #... or Refs #...).Prioritized engineering recommendations are maintained in:
coderelay: command not found
~/.coderelay/bin/coderelay.~/.coderelay/bin/coderelay summaryecho 'export PATH="$HOME/.coderelay/bin:$PATH"' >> ~/.zshrcexec zsh./bin/coderelay ... (if you cd bin, use ./coderelay, not coderelay)."Serve is not enabled on your tailnet"
tailscale serve --bg http://127.0.0.1:8790 and follow the link Tailscale prints to enable Serve.Admin shows "Failed to fetch" or stays on "Loading..."
~/.coderelay/bin/coderelay ensure.Admin asks for the Access Token again
~/.coderelay/bin/coderelay token to print it.iPhone opens the tailnet URL but shows disconnected / no device
/admin on the Mac and click Validate then Repair.Port conflict / service won’t start
~/.coderelay/bin/coderelay diagnose to see what’s listening and the latest logs.🔌 Dynamic Capability Matrix — Each provider declares four capability flags:
CAN_ATTACH_FILES: File/image attachment supportCAN_FILTER_HISTORY: Thread history filteringSUPPORTS_APPROVALS: Interactive tool permission promptsSUPPORTS_STREAMING: Real-time response streaming🎛️ Graceful Degradation UX — UI elements automatically disable when capabilities are unavailable. Disabled buttons show tooltips explaining why (e.g., "This provider does not support attachments"). No broken interactions.
📎 Full Attachment Support — Upload images and files to Codex and Copilot ACP. Files are stored locally (~/.coderelay/uploads), base64-encoded for ACP protocol, with automatic text-only fallback if attachments are rejected.
🔒 ACP Approval System — Interactive prompts for tool permissions (shell commands, file operations, etc.):
allow_once, allow_always, reject_once, reject_alwayscoderelay_acp_approval_policies)--allow-all-tools is enabled)🔍 Advanced Filtering — Dual-axis thread filtering:
coderelay_thread_filters)CodeRelay supports multiple AI providers through a unified adapter interface:
Codex (via Anchor)
GitHub Copilot ACP
gh copilot --acp or copilot --acp child processAcpClientconfig.json (providers["copilot-acp"].enabled)| Capability | Codex | Copilot ACP |
|---|---|---|
CAN_ATTACH_FILES |
✅ | ✅ |
CAN_FILTER_HISTORY |
✅ | ❌ |
SUPPORTS_APPROVALS |
✅ | ✅ (dynamic) |
SUPPORTS_STREAMING |
✅ | ✅ |
Note: Copilot SUPPORTS_APPROVALS is false when started with --allow-all-tools flag (tools are auto-approved at provider level).
See docs/PROVIDERS.md for the adapter development guide. Key steps:
ProviderAdapter interfaceservices/local-orbit/src/index.tsWhen Copilot ACP requests tool permissions (e.g., running shell commands, reading files), you receive interactive approval prompts with four decision options:
allow_once): Approve this specific tool call onlyallow_always): Auto-approve this tool for all future calls (saved as policy)reject_once): Deny this specific tool call onlyreject_always): Auto-deny this tool for all future calls (saved as policy)Policy Management:
coderelay_acp_approval_policies)--allow-all-toolsApproval Flow:
session/request_permission via JSON-RPCapproval_request event (60s timeout)approval_decisionFilter your thread list with dual-axis filtering:
Provider Filter:
all (default): Show all threadscodex: Show Codex threads onlycopilot-acp: Show Copilot ACP sessions onlyStatus Filter:
all (default): Show all threadsactive: Show non-archived threadsarchived: Show archived threads onlyFeatures:
coderelay_thread_filters)CodeRelay is a local-first, multi-provider AI interface with secure Tailscale-based remote access.
local-orbit: Single local Node.js server (port 8790 default)
/ws for UI clients, /ws/anchor for Codex bridge)Anchor: Codex Desktop bridge process
codex app-server and relays JSON-RPC over stdioProvider Adapters: Pluggable AI provider backends
CodexAdapter: Codex Desktop integration (via Anchor)CopilotAcpAdapter: GitHub Copilot ACP process spawningWeb UI: Svelte 5 SPA
iPhone/Mac Browser
|
| wss:// (via Tailscale)
v
local-orbit (WebSocket relay)
|
+---> Anchor ---> codex app-server (stdio JSON-RPC)
|
+---> CopilotAcpAdapter ---> gh copilot --acp (stdio JSON-RPC)
|
+---> SQLite (events, uploads, sessions)
coderelay_thread_filters: Provider and status filter statecoderelay_acp_approval_policies: ACP tool permission policiescoderelay_enter_behavior: Composer Enter key behavior (per-device)events table:
thread_id and created_at for fast replayupload_tokens table:
expires_at for automated cleanuptoken_sessions table:
revoked_at for active session queriesSee docs/DIFFERENCES_FROM_ZANE.md for a complete architectural comparison.
Key differences:
launchd integration, full lifecycle management/admin): status, logs, start/stop Anchor, one-time pairing QR for your iPhonelocal-orbit) that serves:/ws) for realtime control/threads/:id/events)~/.codex/.codex-global-state.json)This repo includes a GitHub Actions workflow (.github/workflows/ci.yml) that builds the UI and runs smoke tests (including a WebSocket relay test) to catch regressions like “blank threads”.
/admin)./admin mints a short-lived one-time pairing code (shown as QR)./admin.CodeRelay is built around a simple security goal: don’t expose your local Codex to the public internet. Tailscale is what makes that practical.
What it provides in this setup:
my-mac.tailXXXX.ts.net).How it fits CodeRelay:
127.0.0.1.tailscale serve publishes that local-only service to your tailnet over HTTPS/WSS.~/.coderelay/uploads).0 days). You can set retention (days) in /admin./u/<token>). This avoids putting your Access Token in image URLs and allows <img> tags to load on iPhone.curl -fsSL https://raw.githubusercontent.com/ddevalco/coderelay/main/scripts/install-local.sh | bash
After install:
http://127.0.0.1:8790.pbcopy, best-effort).What the installer does:
~/.coderelay/.launchd agent. If your system blocks launchctl (common on managed Macs), it will fall back to running in the background and prints Service started via: ....tailscale serve so your iPhone can reach the service via MagicDNS.If you want a clean slate (stop service, disable tailscale serve, remove launchd agent, delete ~/.coderelay):
curl -fsSL https://raw.githubusercontent.com/ddevalco/coderelay/main/scripts/reset-and-install.sh | bash
If you only want to wipe without reinstalling, run the local script after install:
~/.coderelay/app/scripts/wipe-local.sh
If you do not have Tailscale yet:
tailscale up on the MacTerminology:
my-mac.tailXXXX.ts.net).Expose the service on your tailnet (run on Mac):
tailscale serve --bg http://127.0.0.1:8790
Note: Some tailnets require you to enable Tailscale Serve in the admin console the first time. If you see an error like "Serve is not enabled on your tailnet", follow the link it prints and enable it.
Then open on your Mac (to pair your iPhone):
http://127.0.0.1:8790/adminWhat to expect after pairing:
https://<your-mac-magicdns-host>/ and connect automatically (no manual “server URL” setup)./admin and ~/.coderelay/anchor.log.Note about the Codex desktop app:
services/local-orbit/src/index.tsservices/anchor/src/index.tsdocs/INSTALL.mddocs/ADMIN.mddocs/CLI.mddocs/ARCHITECTURE.mddocs/SECURITY.mddocs/CONFIG.mddocs/PROTOCOL.mddocs/HARDENING.mddocs/TROUBLESHOOTING.mddocs/NATIVE_IOS_ROADMAP.mdCHANGELOG.mdSee docs/ATTRIBUTION.md.