@akshitcodes/remote-agents
v0.4.11
Published
Self-hosted mobile web UI for your Codex, Claude Code, and Grok CLI sessions.
Downloads
845
Maintainers
Readme
remote-agents
remote-agents is a self-hosted phone UI for the Codex, Claude Code, and Grok sessions already on your computer. It reads each CLI's session files, lets you continue a thread, streams changes over Server-Sent Events, and can notify an installed PWA when a turn driven through the bridge finishes.
The bridge runs on your computer and uses your already logged-in agent CLIs. It does not provide a hosted relay or a shared account.
macOS setup
You need Node.js 22.18 or newer (the bridge uses Node's built-in SQLite module) and at least one supported CLI installed and logged in. You do not need to prepare Tailscale first: setup can download its current official signed macOS installer, open it, and wait while you approve installation and sign in. The recommended Funnel connection works from anywhere without installing Tailscale—or anything else—on the phone.
Install the package, then run one setup command:
npm install -g @akshitcodes/remote-agents
remote-agents setupsetup asks one plain question: who should be able to reach this Mac? Press
Enter for the recommended “phone anywhere with the private pairing link” option;
the choice is remembered, so later runs do not ask again. It
generates a private per-install token, chooses and remembers a free port,
checks all three provider CLIs, installs a per-user macOS LaunchAgent, configures
Tailscale Funnel, and verifies that the authenticated app answers through the
stable HTTPS name. On a tailnet that has never used Funnel, Tailscale opens a
browser approval page; approve enabling Funnel in that tab while setup keeps
waiting. Funnel is available on every Tailscale plan, including free. Setup
also verifies that MagicDNS is enabled and names that setting specifically if
it is not. After approval, the first HTTPS request can take about 28 seconds
while Tailscale issues the certificate, so the verifier waits up to 75 seconds.
Only then does it print the phone pairing QR.
Running setup again while the supervised bridge is already healthy does not
restart it. Active agent turns keep running, and setup safely updates the saved
service definition to the current package path. When the turns are idle, run
remote-agents stop and then remote-agents start to load updated package code
or newly installed providers; setup prints this reminder rather than claiming
that the live process changed.
If Tailscale is missing, setup offers to download and signature-check the official installer (or open the official download page), then stays active while you install and sign in. If Tailscale or its HTTPS URL still cannot be verified, setup prints the exact recovery steps and no phone QR. It never substitutes a LAN HTTP link that looks successful but cannot install the PWA or receive push.
- iPhone/iPad: open in Safari, then Share -> Add to Home Screen.
- Android Chrome: tap Install app to use Chrome's native install prompt.
- Open the installed app and tap Enable notifications when offered.
iPhone and iPad Web Push requires an installed Home Screen web app and iOS or iPadOS 16.4 or newer. A normal Safari tab cannot opt in.
Try it without installing a service
This foreground command uses the same Tailscale-first flow and persistent config, but the local bridge stops when the terminal closes:
npx @akshitcodes/remote-agentsWho can reach the Mac?
- A phone anywhere with the private pairing link — recommended. Tailscale Funnel provides stable public HTTPS. Only the Mac needs Tailscale; the bridge's long random token is still required for every app/API response.
- Only devices in your Tailscale account. Tailscale Serve keeps the HTTPS address private, but every phone must install Tailscale and join the same account.
- People allowed by your Cloudflare policy — advanced. A stable hostname on your own domain with an identity sign-in policy; the cost is Cloudflare account, domain/DNS, tunnel, and Access setup.
There is intentionally no LAN HTTP mode. Plain LAN HTTP cannot provide the secure context needed for PWA installation or Web Push. Cloudflare Quick Tunnels are also excluded: their hostname changes on restart and Cloudflare documents that Quick Tunnels do not support Server-Sent Events, which is this app's live transport.
For the advanced option, first create a named tunnel, route its public hostname to the bridge, and create a Cloudflare Access self-hosted application with an Allow policy for that hostname. Then run:
remote-agents setup --transport cloudflare
remote-agents tunnel --name my-agents --hostname agents.example.com --access-protectedThe Cloudflare command refuses anonymous Quick Tunnels and withholds its QR until the stable hostname returns the authenticated app. A Cloudflare Access login page alone is deliberately not enough: it proves the policy exists, but not that the app and token work behind it. The URL still contains the bridge pairing token; treat it like a password.
Commands
remote-agents foreground server + QR
remote-agents setup install/start service; Funnel by default
remote-agents setup --transport serve private Tailscale-only access
remote-agents setup --transport cloudflare
remote-agents tailscale configure/retry saved Tailscale mode
remote-agents tailscale --transport funnel|serve
remote-agents tunnel --name NAME --hostname HOST --access-protected
remote-agents url print pairing QR again
remote-agents status show service status and pairing URLs
remote-agents terminal enable create a 5-minute device enrollment QR/code
remote-agents terminal devices list trusted terminal devices
remote-agents terminal revoke ID revoke one device and stop its shell sessions
remote-agents terminal disable revoke every device and disable shell access
remote-agents start|stop control the installed service
remote-agents uninstall remove only the remote-agents serviceUseful foreground overrides:
remote-agents serve --host 127.0.0.1 --port 9317
remote-agents serve --token 'at-least-32-random-characters'Values supplied with --host, --port, or --token are persisted. A new
public origin is persisted only after it returns the authenticated app, so a
typo cannot strand an existing installed PWA. Later setup, start, status, and
url commands reuse the saved transport, origin, port, and token. If Tailscale
re-registers the Mac under a different public name, the CLI refuses the change
and explains why installed phones would look broken. --replace-origin is the
explicit recovery path; after using it, reinstall the phone PWA and enable
notifications again.
status and url report the service manager, saved port and transport,
Tailscale connection state, saved/verified public address, and readiness of
Codex, Claude, and Grok. A missing provider does not block setup when another is
ready. If none is installed and signed in, setup stops before installing a
background service. Missing or signed-out providers are never installed or
authenticated automatically; the CLI prints both commands for each one:
Codex install: curl -fsSL https://chatgpt.com/codex/install.sh | sh
sign in: codex login
Claude install: curl -fsSL https://claude.ai/install.sh | bash
sign in: claude auth login
Grok install: curl -fsSL https://x.ai/cli/install.sh | bash
sign in: grok loginThe web app exposes only providers that passed both checks when the bridge started. After installing or signing in to another CLI, restart Remote Agents to add it to the provider picker.
Configuration and files
The existing configuration home is reused:
~/.codex-phone/config.json token, port, public URL, VAPID keypair, options
~/.codex-phone/push.json browser Web Push subscriptions
~/.codex-phone/usage-retry-policy.json global/per-task auto-resume choices
~/.codex-phone/usage-retries.json durable pending usage-limit resumes
~/.codex-phone/terminal-security.json public passkey credentials and trusted devices
~/.codex-phone/logs/ background-service logsThe directory is written with mode 0700 and secret-bearing JSON with 0600.
Set REMOTE_AGENTS_HOME to relocate it (also useful for isolated testing).
Supported optional config fields include:
{
"codexBinary": "/path/to/codex",
"claudeBinary": "/path/to/claude",
"grokBinary": "/path/to/grok",
"fileAccess": "anywhere",
"pushSubject": "mailto:[email protected]"
}fileAccess defaults to anywhere: paired clients can preview and download
files anywhere on the bridge computer, subject to the bridge process's OS
permissions. Absolute paths may point outside the project; relative paths still
resolve against the thread's working directory. Set fileAccess to project
to retain project/worktree-only access and individual outside-file approvals.
Existing explicit settings are preserved. Authentication, image validation and
isolated HTML previews remain required in either mode.
Continue after a usage limit
Auto-resume is off by default. Open a task, tap the settings gear, and enable
Auto-continue after usage resets for that task, or enable it for all tasks.
After a provider reports a terminal usage-limit failure, the bridge durably
queues one Continue. message. It refreshes the currently signed-in account's
usage every minute, waits for the task to be idle, and revalidates the exact
model, effort, and permission settings before sending. Switching provider
accounts is picked up by the next check.
The queued status, provider reset hint, next check, Check now, and Cancel remain available after closing the PWA or restarting the bridge. A bridge restart during the actual send is marked uncertain and is never retried automatically, because sending twice is worse than requiring a manual check.
Project terminal security
Shell access is disabled by default and is deliberately stronger than ordinary
app pairing. On the bridge computer, run remote-agents terminal enable, scan
the five-minute QR (or enter its one-time code), and create a passkey using Face
ID, Touch ID, Windows Hello, or the device PIN. Each browser is enrolled
separately. Opening a terminal later requires a fresh passkey assertion; the
short unlock expires after inactivity.
The terminal starts in the selected task's provider-reported project folder, which the server resolves again before every new session. That folder is a starting directory, not a filesystem sandbox: the shell has the same OS access as the bridge user. Interactive PTYs run in a separate worker so a native terminal-addon failure cannot stop the bridge or provider agents. If the native PTY is unavailable, the page offers passkey-protected one-shot command mode and never automatically repeats a command whose result is uncertain.
Revoking a device or disabling terminal access invalidates its unlocks and tickets and stops both its interactive and one-shot shell processes. Terminal administration is accepted only from the bridge machine through an authenticated loopback control endpoint; it is not exposed in the web app.
When a provider is not detected
Setup lists each provider CLI as one of four states, and only two of them stop it:
| State | Meaning | Does it block setup? |
| --- | --- | --- |
| installed and signed in | a signed-in account was confirmed | no |
| sign-in unverified | the CLI is there, but nothing proved either way | no — it stays enabled |
| installed, but not signed in | the CLI itself said nobody is signed in | only if every provider says this |
| not installed | no binary found | only if no provider is installed |
sign-in unverified is normal on a fresh machine: the first run of a provider
CLI can take a while, and its answer is not always machine-readable. The
provider stays selectable, because trying to send is a better test than a guess
made during setup — and the failure it reports is far more specific.
Binaries are looked for on PATH and in the places the official installers use
(~/.local/bin, Homebrew, npm/bun/volta/asdf prefixes, and the codex bundled
inside ChatGPT.app). Credentials are looked for in ~/.codex/auth.json,
~/.grok/auth.json, ~/.claude/.credentials.json, the macOS Keychain item
Claude Code-credentials, and the usual API-key environment variables.
If your setup is unusual, these override the defaults:
| Variable | Purpose |
| --- | --- |
| REMOTE_AGENTS_BIN_DIRS | colon-separated directories to search instead of the built-in list (PATH is still used) |
| REMOTE_AGENTS_CODEX_APP | path to the codex binary inside a ChatGPT desktop app that is not in /Applications |
| REMOTE_AGENTS_AUTH_TIMEOUT_MS | how long to wait for a provider's auth check (default 20000) |
You can also pin a binary permanently in ~/.codex-phone/config.json via
codexBinary, claudeBinary or grokBinary; setup records the absolute paths
it found there, so the background service resolves the same binaries your shell
does even though launchd gives services a minimal PATH.
Provider CLI versions
Provider CLIs add, rename and retire capabilities between releases. The bridge
therefore uses the strongest provider-owned surface available: Codex app-server
model/list; Grok ACP initialization metadata; and, where no structured API
exists, the installed CLI's own --help. Claude model aliases come from help,
while full model IDs are learned from successful provider transcripts. Every
provider also offers an explicit Provider default choice rather than making
the bridge guess a value.
Known equivalent names such as Claude's default/manual are mapped to the same
Manual contract. If a build cannot prove it can apply the model, effort, or
permission setting shown in the UI, the send is refused with an upgrade/reload
instruction instead of silently running with different settings.
For example, Claude Code 2.1.0 has no --effort and does not accept
--permission-mode auto, while 2.1.238 has both and calls manual approval
manual instead of default. On 2.1.0 a Manual send uses the equivalent
--permission-mode default; a send asking for Auto or explicit reasoning effort
returns a clear instruction to update Claude Code.
This keeps the status bar truthful: choosing High can never secretly run the provider's default effort, and a restrictive permission mode can never be widened. If a CLI's help cannot be read, explicit help-derived settings fail closed and the probe is retried later. Provider-default model/effort remains discoverable, but a send waits until its permission contract can also be proven. This avoids both freezing the bridge during discovery and guessing from the version installed on the package author's machine.
Notifications
On first server start, a VAPID keypair is generated into the same config. After
the installed PWA opts in, its Push subscription is stored in push.json.
Completed turns are encrypted and signed with web-push; expired subscriptions
are pruned when a push service returns 404 or 410. The service worker displays
the notification and opens the matching provider/thread when tapped.
Notifications currently cover turns driven through this bridge. The filesystem watcher follows an external terminal/IDE thread only while a client is viewing it; remote-agents does not globally monitor every external session while the PWA is closed.
Connected agents: CLI, MCP and UI
The Linked sessions panel shows bridge-connected agents separately from native provider subagents. You can create a connected agent, connect an existing session, and navigate between creators, children and collaborators. A connection does not copy a conversation or change who owns a turn.
The CLI and MCP tools use the same bridge operations and durable request receipts.
Sessions this bridge starts or resumes (Codex, Claude and Grok) are given their
own Remote Agents MCP server, bound to that one session. Their calls are
identified by that connection: an agent can spawn, send, queue, steer, reply or
wait directly, without whoami, attach or as. A different as (or a
different spawn parent) on such a connection is refused, never honoured. The
binding passes only the path of a private credential file to the provider; the
credential ends when the session's process ends, and a resumed session gets a
new one.
From the CLI, or from an MCP server configured globally in a session this bridge did not start (an IDE or terminal session), name the caller explicitly:
remote-agents agent providers
remote-agents agent attach --provider codex --thread YOUR_THREAD_ID
remote-agents agent whoami --as YOUR_AGENT_HANDLE
remote-agents agent spawn --as YOUR_AGENT_HANDLE --provider claude \
--model MODEL_FROM_CATALOG --effort low --mode auto --cwd /absolute/project/path \
--role reviewer --request-id review-001 --prompt 'Review the proposed changes.'
remote-agents agent status CHILD_AGENT_HANDLE
remote-agents agent messages CHILD_AGENT_HANDLEModel, effort and mode may be given directly when you know valid values; they
are used exactly as given and an invalid one is refused with the catalog's own
error. providers lists the catalog when you need it.
Every mutation requires a stable request ID. After a lost response, inspect its
receipt with the same ID; do not create another session or resend with a new ID.
Send to an idle agent, queue behind a busy turn, or steer where supported. Grok
does not support steering. Stopping one agent never stops its children.
remote-agents agent mcp-config
remote-agents mcpmcp-config prints the literal stdio server configuration for the installed
package. Register it in each provider's MCP settings, then reconnect that provider
process to discover the tools. mcp runs the stdio server; it is not an interactive
shell command. A global MCP server serves many sessions, so its calls explicitly
name the caller's handle rather than guessing from a working directory; sessions
already open when you first register it may need to be reopened. npm installation
does not silently alter provider configuration or authentication. The saved bridge
pairing grants full owner access, not restricted agent permissions.
For the complete operation and identity contract, see the Connected agents guide.
Task progress above the composer
Connected agents can record task milestones using task_update and read them
with task_list. The coordinator creates a stable task ID for each requested
outcome, gives it a title and a plain-English progress summary, and links a
worker through assignee plus the exact workRequestId it sent. Assigned
workers pass the coordinator's handle as owner and report active or
blocked; only the coordinator marks tasks done or cancelled after checking
the outcome. Use a new update request ID for each milestone, and replay the
same ID/payload after a lost response.
After confirming a fix is resolved and deployed, the coordinator can set
archived: true (CLI: --archived true) on its done task to remove it from
the current board. Cancelled tasks can also be archived when no longer needed.
Done-but-not-deployed tasks stay visible. task_list retains archived history;
archived: false or reopening a task restores it to the board.
remote-agents agent task_update --as COORDINATOR --task-id composer-dictation \
--request-id dictation-plan-1 --title 'Composer dictation' --status planned \
--summary 'Not started; implementation is next.'These are reported milestones, not automatic test verification or provider liveness. A task does not become done merely because a worker's turn ends. The panel updates live across devices and recovers after restart, with clickable reporter names and the age of each report. Collapse leaves a small task-count header; that local preference survives reload and updates never force it open. Saved drafts/scheduled messages open from the composer toolbar; queued messages remain above the composer. Completion Snooze/Done controls live in the sidebar.
Status metadata does not start a paid turn, send notifications or replace result delivery and async waits. Agents must use the reporting tools; older conversations are not parsed or backfilled into tasks. Existing MCP processes need reconnecting to discover newly added tools. This is an additive progress surface, not an autonomous scheduler.
Service behavior
On macOS, setup creates
~/Library/LaunchAgents/com.remoteagents.bridge.plist with RunAtLoad and
KeepAlive. It records absolute Node/package paths and writes logs below the
config directory. Nothing is installed from npm's postinstall; machine changes
happen only after the explicit setup command. The supervised process starts only
the local bridge; setup remains the single owner of Funnel/Serve changes,
public verification, and QR output.
Linux retains a best-effort user systemd service. It has not received the same platform verification as macOS.
On native Windows, setup registers a task for the current user's interactive login, without a
password or administrator privilege. The task starts after login, runs on battery,
and its launcher retries a failed bridge at most three times, one minute apart. A second
start request does not create a parallel task instance. Its name includes a hash
of the configuration directory, so relocated scratch installs control their own
task. uninstall removes autostart and retains configuration, pairing, and logs.
Run setup from an ordinary terminal while logged in; unattended SSH cannot prove
interactive login behavior. Node/package paths are recorded, so run setup again
after moving or upgrading the global installation, then restart when turns are idle.
Windows provider .cmd shims use the existing explicit quoting boundary.
Arguments containing %NAME% remain refused because cmd would expand them.
Line breaks, NUL and unpassable variable references are refused before launch.
Native discovery includes npm and user-installed executables, and Tailscale's
Program Files location. Provider authentication remains with each user's CLI;
setup never copies credentials. Native package, provider, terminal and task checks
are distinct from the coordinated reboot/login and sleep/resume checks; a manual
start does not certify automatic login. WSL is Linux and does not establish native
Windows support. Consult the release handoff for the exact artifact's verification
and any remaining limitations.
For private access use Tailscale Serve; Funnel provides a public HTTPS address
protected by the app's pairing/authentication. Keep the local bridge on loopback
(--host 127.0.0.1) when only a local Tailscale or Access-protected Cloudflare
proxy needs it. Do not open a public router port or blanket firewall exception.
If direct LAN access is deliberate, limit the inbound rule to the chosen port,
private profile, and intended clients. The bridge does not create firewall
rules or change transport on supervised restart.
Security notes
- Pairing through
?t=...immediately redirects to a clean URL and stores an HttpOnly, SameSite cookie. HTTPS pairings also receive the Secure flag. - Fresh installs generate a 256-bit random pairing token. Repeated bad token attempts receive per-client exponential backoff and rate limiting.
- App, API, manifest, service-worker, icon, and vendor responses all require the pairing cookie or Bearer token. Unauthenticated responses contain only a generic error and no bridge marker, thread data, local paths, or version.
- Responses set clickjacking, MIME-sniffing, referrer, permissions, CSP, and same-origin isolation headers; HTTPS responses also set HSTS.
- Anyone with the pairing URL can drive agents with the permissions selected in the UI. Rotate the token in config if it leaks, then restart the service.
- Funnel is internet-reachable and the pairing URL is a password. Choose private Serve or Cloudflare Access if the token boundary does not fit your threat model.
- Agent Markdown is sanitized before rendering. The file viewer is a convenience boundary, not a substitute for authentication.
How it works
A high-level walkthrough of the pieces, provider connections, and multi-device sync is in docs/architecture.html.
- Codex sessions:
~/.codex/sessions, withcodex app-serverfor active RPC. - Claude Code sessions:
~/.claude/projects, using Claude's streaming JSON mode. - Grok sessions:
~/.grok/sessions, using its streaming messages protocol. - Browser transport: authenticated HTTP APIs plus SSE from
/api/events. - PWA: one hand-written
public/index.html, a manifest, andpublic/sw.js; no framework or build step.
Development checks
Agent-created visualizations
Codex, Claude and Grok can call the Remote Agents preview_check tool with an
existing HTML path, or html for new self-contained content. It checks the
actual sandbox in a temporary headless Chrome and returns an image plus blocked
resources and script errors. Chrome must be installed on the bridge host
(CHROME_PATH may select another Chromium). Missing Chrome or failed checks are
reported as not verified; nothing is installed automatically.
The session determines the project folder; agents cannot substitute another chat or cwd. Existing file approvals still apply. CDNs remain blocked even when their origin is on the allowed list. Prefer inline CSS/JS or pre-rendered SVG. A screenshot is bounded visual evidence, not proof of every interaction or later callback.
After checking, reuse the returned reference in the final reply:
{"path":"/absolute/project/chart.html"}Keep the fence; bare visualize{...} is plain text. For inline source use a
fenced html-preview block. These display formats do not require a tool call;
the tool is for verification. Previews remain inside the app. Agents using the
CLI can run remote-agents agent preview_check --path chart.html --as <own-id>;
JSON includes a private temporary screenshot path to inspect and remove when done.
Focused tests
For a small change, run the affected end-to-end journey and the critical boundary checks, not every unrelated provider, retry and recovery scenario:
npm run test:focus -- test/visualization-reference-browser.test.mjs test/html-preview.test.mjsThe selection is explicit and runs with isolated state, one file at a time. An empty or invalid selection fails instead of accidentally running everything. Prefer tests of real user outcomes; retain smaller checks for brittle algorithms, security, ownership, cancellation and no-duplicate-send boundaries. Avoid tests that only pin incidental source spelling or duplicate an already-covered outcome.
npm run test:full (also npm test) retains the complete four-way suite for CI,
whole-app releases and changes with a demonstrated broad dependency. Do not run
it automatically for a narrow UI change or repeat it because an unrelated test
failed. Choose checks before editing, preserve failures and investigate the
smallest affected path. Packaging changes still need an installed-artifact check.
Package verification
Version 0.4.11 is intended for the normal latest install under the owner's explicit small self/friend rollout approval. It includes agent preview checks, in-app HTML expansion, compact queued controls, device-local Queue/Steer defaults, wait recovery, folder browsing and the Node 24 compressed-history fix. Scoped Mac and exact installed-package evidence accompany the release; this is not a new whole-app/full/serial or exact Windows certification. Broader historical CI limitations remain open. Native Windows 0.4.9 reboot/sign-in evidence confirmed bridge startup after login; external connectivity required Tailscale activation. Pre-login operation, sleep/wake and very-large-thread latency remain uncertified. Use Node 22.18 or newer. Installation does not restart an existing bridge; restart when idle and reconnect agent tool sessions for newly available tools.
Before publishing a release:
npm pack --dry-run
node --check server.mjs
node --check bin/codex-phone.mjsThe package allowlist explicitly includes every top-level runtime module,
including config.mjs and onboarding.mjs, plus providers, the PWA, and public
docs. The internal portability audit is intentionally not published because it
records details of the owner's previous installation. Do not add a
service-installing postinstall.
License
MIT
