Mobile companion for using the local Codex session layer from a phone.
This project intentionally uses local-session mode only:
npm startopens a desktop pairing page and creates a temporary HTTPS tunnel when possible.- The QR code pairs a phone with an in-memory session token. The token is valid for 7 days while the desktop companion process stays alive and is refreshed as the phone uses the app.
- If the desktop companion/tunnel restarts, the user scans a fresh QR.
- No hosted relay, paid domain, ngrok account, or long-lived remote service is required.
- Notifications are session notifications: they work while the mobile browser remains connected to the desktop bridge.
The bridge starts codex app-server over stdio and exposes a small HTTP/WebSocket API for the mobile UI. It does not read Codex SQLite/JSONL state directly. The mobile client is built with Vite + React + TypeScript/TSX and served by the bridge from dist/.
First, install dependencies and check your desktop setup:
npm run setupThen start the local companion:
npm startnpm start builds the mobile client, starts the bridge in the background, creates a temporary HTTPS remote tunnel when possible, opens the desktop pairing window, checks Codex CLI/login status there, warms Codex App Server, and shows a QR that can be opened from mobile data. After the pairing window opens, the terminal that launched npm start can be closed.
For foreground logs while developing:
npm run start:foregroundLater, when installed globally, the same launcher is available as:
npm link
codex-mobileDevelopment URL:
http://127.0.0.1:8787
LAN phone pairing:
npm start -- --localIf the advertised LAN URL is not what the phone can reach, set it explicitly:
HOST=0.0.0.0 PUBLIC_URL=http://192.168.0.10:8787 npm startnpm run probeExpected result: recent Codex threads are printed, including threads visible in Codex Desktop.
GET /api/healthGET /api/desktop/statusPOST /api/desktop/login/startGET /api/desktop/login/statusPOST /api/desktop/login/cancelPOST /api/pair/startPOST /api/pair/completePOST /api/uploadsGET /api/projectsPOST /api/threadsGET /api/modelsGET /api/skillsGET /api/settingsGET /api/threads?cwd=<path>GET /api/threads/:threadIdGET /api/threads/:threadId/contextGET /api/threads/:threadId/changesGET /api/threads/:threadId/token-usageGET /api/threads/:threadId/branchesPOST /api/threads/:threadId/git/checkoutPOST /api/threads/:threadId/git/commitPOST /api/threads/:threadId/actionsPOST /api/threads/:threadId/messagesPOST /api/threads/:threadId/interruptPOST /api/approvals/:requestIdWS /api/events?token=<accessToken>
Implemented:
- Dependency-free Node bridge.
- Codex JSON-RPC stdio client.
- Desktop readiness check for Codex CLI installation, Codex login status, and lazy App Server startup.
- Desktop OpenAI login flow launcher backed by
codex login --device-auth. - Project grouping by
cwd. - Thread list and thread detail reads.
- Short-lived pairing code and bearer token.
- Local QR SVG generation for pairing URLs.
- Vite + React + TypeScript/TSX mobile web shell with project/thread/detail views.
- Desktop-like composer controls for branch switching/creation, combined model/reasoning selection, permission menu, chat, and changes.
- Compact composer token usage dial backed by App Server
thread/tokenUsage/updatedevents. - Working-tree changes endpoint backed by Git status/diff data.
- Commit-all action for current working-tree changes from the mobile changes panel.
- New thread creation and project/thread search UI.
- Settings surface for account, rate limits, runtime config, plugins, skills, apps, MCP servers, and automations.
- Desktop-style
$skill,@context, and/commandcomposer pickers backed by App Server skills, fuzzy file search, apps, plugins, and thread actions. - Mobile image/file attachments in the chat composer. Images are sent as Codex
localImageinputs; files are staged on the desktop and referenced by local path. - Installable PWA (manifest, service worker, iOS-compatible meta tags). The in-app install prompt is suppressed on temporary tunnel origins (
*.trycloudflare.com,*.ngrok.app,*.loca.lt); it activates on stable origins (Tailscale Funnel*.ts.net, LAN IP, custom domains). - Message send with model/effort overrides, interrupt, approval response, and WebSocket event handling hooks.
- Session notifications for approval requests and completed Codex turns while the mobile browser is connected.
- QR-time Codex App Server and project-cache prewarm so mobile opens quickly after scanning.
Known next work:
- Test message sending and streaming against a disposable Codex thread.
- Harden approval response shapes for
item/permissions/requestApproval. - Add paired-device revocation, desktop-side pairing confirmation, and plugin/automation mutation UI.
By default, npm start creates a temporary *.trycloudflare.com tunnel that
rotates per session — fine for QR pairing, not for installing the app to your
phone home screen.
If you want a permanent URL so the PWA installs once and survives desktop
restarts, expose the bridge through a stable origin and pass it via
PUBLIC_URL. Recommended free setup:
One-time setup on the desktop:
-
Run
npm run setup. On macOS, if Tailscale is missing and Homebrew is available, setup offers to install it withbrew install --cask tailscale. Otherwise it prints the manual download link. -
Sign in to Tailscale with any account (Google/GitHub/etc). Setup opens the Tailscale app when it can, then asks you to re-run
npm run setupafter login. -
The doctor checks for the Tailscale CLI and login, detects whether Funnel is already enabled for the bridge port, and — if not — offers to enable it for you (sudo password required, one prompt):
Tailscale Funnel (optional, for stable PWA URL): Funnel for port 8787 is not configured (device: my-mac.tail-xxxx.ts.net). Enable now (sudo password required)? [y/N] y OK Funnel enabled at https://my-mac.tail-xxxx.ts.net -
Done. From now on,
npm startautomatically detects the active Funnel and uses its URL for the mobile QR — noPUBLIC_URLenv var needed. If Tailscale is already installed and logged in but Funnel is not configured,npm startalso attempts to enable Funnel automatically before falling back to a temporary Cloudflare URL.
To enable manually instead, run sudo tailscale funnel --bg 8787 once.
Open the QR'd URL on your phone, complete pairing, then Add to Home Screen (iOS Safari) or tap Install when the in-app prompt appears (Android Chrome). The icon survives desktop restarts; only the per-session pairing token is renewed on next launch.
PUBLIC_URL env var > detected Tailscale Funnel > Cloudflare quick tunnel.
Set PUBLIC_URL only if you want to pin the QR to a different stable
origin (e.g. Cloudflare named tunnel on your own domain).
- Tailscale Funnel is free on the Personal plan (April 2026 update: 6 users, unlimited devices, Funnel included).
- The pairing token is still stored in the running desktop bridge process, so restarting the companion requires pairing again. While the process stays alive, active mobile sessions are refreshed for up to 7 days.
- Tailscale Funnel exposes the URL publicly. The pairing-token check still gates every API call, identical to the temporary tunnel mode.
Any stable HTTPS origin works — Cloudflare named tunnels with your own
domain, ngrok paid subdomains, self-hosted reverse proxies, or even a fixed
LAN IP for same-network use. Set PUBLIC_URL accordingly.
Closed-app push notifications remain out of scope: this project only delivers notifications while the paired mobile browser/PWA is connected during the current desktop session.
Developer preview:
git clone <repo-url>
cd codex-mobile
npm run setup
npm startThe setup command installs the TypeScript/Vite client dependencies and checks the local Codex setup. A packaged desktop wrapper can later run the same checks and bridge without exposing Terminal.
In the packaged desktop app, users should not run codex login --device-auth themselves. The app should start that flow internally when needed, wait for the browser-based OpenAI login to complete, then enable the QR button.
MIT