🚀 agy-remote
Self-Hosted, Encrypted Mobile Web Remote & PWA for Google Antigravity CLI (
agy)
Access, monitor, and direct your locally runningagysessions from your phone over Tailscale or Local Wi-Fi — with zero cloud lock-in, AES-256-GCM encrypted payloads, a live mirrored terminal view, self-hosted Web Push alerts, one-tap tool approvals, and voice dictation.
📑 Table of Contents
- Overview
- Architecture & Backends
- Key Features
- Installation
- Quickstart: Antigravity (
agy) - Using opencode instead
- Mobile PWA Setup
- Remote Tool Approvals
- Terminal Key Controls
- Web Push Notifications
- Security & Cryptography
- CLI Reference
- Testing & Quality Assurance
- Troubleshooting & FAQ
- License
🌟 Overview
When running an autonomous coding agent like Google Antigravity CLI (agy), tasks frequently involve multi-step file edits, automated test runs, and tool permission gates that take minutes to complete. Staying tethered to your desk or using awkward mobile SSH clients (where monospace terminals break text wrapping on narrow phone screens and soft keyboards make tool approvals painful) is suboptimal.
The workflow is inspired by Claude Code and its remote-control and mobile-approval experience — agy-remote brings that same "step away from the desk while the agent works" loop to Google Antigravity CLI, self-hosted and without a cloud relay. If you're coming from Claude Code, Gemini CLI, or another terminal coding agent, the concepts (transcript streaming, tool-permission gates, execution modes) map directly.
agy-remote bridges your local desktop agent session to a rich, responsive Progressive Web App (PWA) on your phone:
- Built for a terminal agent:
agyhas no server and no web UI of its own; this is what puts it on a phone. - Zero Cloud Dependence: 100% self-hosted, and the PWA loads no third-party scripts — it works on an air-gapped tailnet.
- Encrypted Payloads: AES-256-GCM on every WebSocket frame with replay protection, keyed by a secret shared via the URL hash (
#key=...). See Security for what this does and does not protect. - First-Class Mobile UX: Native mobile text wrapping, collapsible thinking accordions, colored diffs, voice dictation, and one-tap tool approval banners.
🏗️ Architecture & Backends
agy-remote decouples the mobile interface from the underlying agent via a unified backend adapter interface:
flowchart TD
subgraph Host [Local Computer / Host]
subgraph Sessions [agy, driven two ways]
OWNED["agy started by <b>run</b><br/>PTY supervisor owns it<br/>dies with the server"]
ADOPTED["agy in a tmux pane<br/><b>attach</b> adopts it<br/>outlives the server"]
end
BRAIN[("brain/<id>/transcript.jsonl<br/>the conversation, on disk")]
HOOK["PreToolUse hook<br/>one process per tool call,<br/>blocked until answered"]
REG[("agy-remote-servers/<port>.json<br/>which server drives which tmux session")]
SERVER[agy-remote<br/>FastAPI + WebSocket]
VAPID[Self-hosted VAPID push]
OWNED -->|writes| BRAIN
ADOPTED -->|writes| BRAIN
BRAIN -->|tailed| SERVER
OWNED -->|"keystrokes (pty)"| SERVER
SERVER -->|"send-keys / capture-pane<br/>addressed to the pane"| ADOPTED
OWNED -.->|spawns| HOOK
ADOPTED -.->|spawns| HOOK
HOOK -->|"AGY_REMOTE_URL, else $TMUX session id"| REG
REG -.->|"names the server"| HOOK
HOOK <-->|"asks; waits 270s"| SERVER
SERVER <--> VAPID
end
subgraph Transport [Private Transport Layer]
TS[Tailscale WireGuard Mesh / Local Wi-Fi]
end
subgraph Mobile [Mobile Phone / Tablet]
PWA[Mobile PWA Client]
CRYPTO[Client-Side Web Crypto AES-GCM]
SW[ServiceWorker & Web Push Receiver]
PWA <--> CRYPTO
PWA <--> SW
end
SERVER <===>|AES-256-GCM Encrypted WebSocket| TS <===> CRYPTO
VAPID --->|W3C Push Notification| SW
The two paths matter. An agy that run started is a child: it dies with the
server, and its hook is told where to post through AGY_REMOTE_URL. An agy
adopted from tmux is nobody's child — it outlives the server, is typed into by
pane target rather than by session (a session name aims at whichever pane is
active, which may be your own shell), and its hook finds the right server by
the tmux session id that $TMUX carries into every process in the pane.
Backend Mechanics
| Capability | Google Antigravity (agy) |
|---|---|
| Transcript Source | Local disk log tailing (transcript.jsonl) |
| Tool Approvals | Lifecycle hook gateway (PreToolUse via hooks.json) |
| Hook Setup Needed | Yes (agy-remote setup-hooks) |
| Prompt Injection | PTY / tmux keystroke injection |
| Supervisor Launch | agy-remote run |
The backend seam (backends.py) stays in place with one implementation: a manager
that knows about transcript files is the alternative, and adopting a session the
supervisor did not start plugs in here rather than threading conditionals through
the manager.
✨ Key Features
- 🔐 Payload Encryption: Every WebSocket frame in both directions is sealed with 256-bit AES-GCM, with replay protection. The key travels in the
#key=...URL hash, which browsers never send to the server, and is scrubbed from the address bar on load. See Security for the precise threat model. - 🔔 Self-Hosted Web Push Notifications: Native iOS & Android lock-screen push alerts via local VAPID keys whenever
agyneeds tool approval or completes a task. - 🔄 tmux Session Persistence: Keep sessions running in the background across laptop sleep, screen locks, or closed terminals (
agy-remote run --tmux). - 🪝 Adopt a Running Session:
agy-remote attachtakes over anagyalready running in tmux — your terminal keeps it, the phone gets full control, and killing the server leavesagyuntouched. Keys go to the agent's pane, never to whichever window you happen to be reading. - 🛎️ Always Ready:
agy-remote attach --waitserves from login with nothing running, and starts a session when your phone connects. Tapping the home-screen icon is the entire interaction. - 🗂️ Approvals Per Session: Every session's permission requests reach the phone, but each banner stays in its own transcript — the header says how many are waiting elsewhere, and the drawer says which sessions.
- 📱 Responsive PWA: Installable directly to your iOS or Android Home Screen with safe-area padding and a sleek dark theme.
- 🛡️ One-Tap Tool Permissions: Forwards tool permission prompts (shell commands, file modifications, git pushes) to your phone with haptic feedback to
[Allow]or[Deny]. - 📎 Photo & Screenshot Upload: Capture screenshots or camera photos directly from mobile into your workspace.
- 📝 Visual Diff Viewer: Interactive colored diffs for file edits, rendered locally.
- 🎙️ Voice Dictation: Dictate instructions into active prompts using mobile Web Speech recognition.
- 🖥️ Live Terminal Mirror: The supervised terminal is emulated server-side and streamed as plain text — see pickers, confirmations, and status bar from the phone, with no third-party scripts shipped to the browser.
- ⌨️ Remote Key Control: Cycle execution modes (
Shift+Tab), close panels (Esc), and drive selection lists — an allowlisted named-key channel, never raw bytes. - 🧹 Readable Transcripts: Tool calls collapse to one line (
run_command(git status)), bulk output to a line count, and agent internal scaffolding is labelled instead of masquerading as conversation. - ⏳ Expiring Pairings: The pairing URL is a durable secret, so it expires after 30 days by default;
--rotate-tokenrevokes every paired device immediately. - 🔗 Tailscale & LAN Auto-Discovery: Auto-detects Tailscale IPv4, obtains a real HTTPS certificate from Tailscale, and renders an interactive ASCII QR Code in your terminal on launch.
📦 Installation
agy-remote is published on PyPI and requires Python 3.13+.
Option 1: Run on-demand with uvx (Fastest, zero install)
Run directly from any project folder without a permanent installation:
uvx agy-remote run
Option 2: Install globally with uv tool (Recommended)
Install agy-remote globally so you can use the CLI anywhere on your system:
uv tool install agy-remote
Once installed, run it directly:
agy-remote run
To update to the latest release in the future:
uv tool upgrade agy-remote
Option 3: From Source (Development)
# Clone the repository
git clone https://github.com/ollisulopuisto/agy-remote.git
cd agy-remote
# Install dependencies and sync virtual environment
uv sync
(Optional) Install tmux for background session persistence:
- macOS:
brew install tmux - Ubuntu/Debian:
sudo apt install tmux
🚀 Quickstart: Antigravity (agy)
1. PTY Supervisor Mode (Recommended)
Runs agy under a pseudoterminal: you get the normal agy TUI in your terminal, while the phone can read the transcript, send prompts and keystrokes, approve tools, and view the mirrored terminal screen:
uvx agy-remote run
# or if installed globally:
agy-remote run
Scan the QR code with your phone camera to connect. The pairing survives restarts — the token and encryption key are minted once and reused.
2. tmux Persistence Mode
Keeps the session alive across laptop sleep, closed terminals, and SSH drops by running agy inside a tmux session named agy-remote. Prompt injection and key control work; the live terminal mirror is PTY-mode only:
agy-remote run --tmux
The QR pauses on screen until you press a key — attaching to tmux replaces the
whole terminal, so the code would otherwise vanish behind agy before you can
scan it. agy-remote qr re-displays it at any time.
3. Adopt an agy that is already running (attach)
run owns the agy it starts and dies with it. attach takes over one that is
already there — your terminal keeps the session, and the phone gets the same
transcript, screen, keys and approvals:
# In your own terminal, however you like to work:
tmux new-session -s agy-work agy
# From anywhere else — another terminal, an ssh session, a cron job:
agy-remote attach
With no --session it finds the tmux session running agy and adopts it. If
there is more than one it lists them and asks, rather than guessing and driving
the wrong agent:
agy-remote attach --session agy-work
agy-remote attach --session agy-work -p 8766 # alongside another instance
Why tmux is required. A process's controlling terminal cannot be taken over
after the fact — an agy in a plain shell owns a pty nothing else may write to.
Inside tmux it is addressable by name from any process: send-keys types into
the pane and capture-pane reads the screen back, which is exactly what the
phone needs. Nothing is attached to, nothing is restarted, and killing
agy-remote leaves agy untouched.
If you would rather not use tmux, agy-remote run -- --resume <session-id>
restarts the conversation under a supervisor that owns it.
4. Always on, waiting for your phone (attach --wait)
The Mac listens from login with no agy running at all. When your phone
connects, a session appears — an existing tmux agy is adopted, and if there
isn't one, agy is started in a detached tmux session and adopted:
agy-remote attach --wait
That is the whole interaction from the phone's side: tap the home-screen
icon. The QR code is a one-time pairing, not a login — the token and E2EE key
live in the PWA's localStorage and the address bar is scrubbed on first load,
so there is nothing to scan or type again.
To get that icon, open the pairing URL in Safari (not Chrome — iOS only
installs web apps and delivers Web Push from Safari), then Share → Add to
Home Screen. The installed app has its own storage, separate from the Safari
tab, so it is paired through the manifest's start_url rather than by
inheriting anything: launch it once from the home screen and tap the bell to
enable approval notifications.
For a login service, contrib/net.agy-remote.plist
is a ready launchd agent:
cp contrib/net.agy-remote.plist ~/Library/LaunchAgents/
# edit ProgramArguments to your path (`which agy-remote`)
launchctl load ~/Library/LaunchAgents/net.agy-remote.plist
Two things worth knowing for an always-on setup:
- Pairing expires after 30 days by default and the phone is then silently
unpaired, needing the QR again. Raise it with
AGY_REMOTE_CREDENTIAL_TTL_DAYS(the plist sets 365). - Every connected device has full control. There is one host-wide token and
one key: no per-device identity, no revoking a single phone. The header shows
a device count whenever more than one is connected — with no identity to
audit, that count is the only sign your pairing URL has escaped. To revoke,
restart with
--rotate-tokenand re-pair everything.
The tmux session outlives the server: stop agy-remote and agy keeps working;
start it again and it re-adopts the same session.
5. Standalone Watcher Server Mode
If agy is running outside tmux. Read-only plus approvals: the phone sees
the transcript and can approve tools, because the transcript is on disk and the
PreToolUse hook finds the server through the state file — but there is no pane
to type into:
agy-remote serve
6. Two agy sessions at once
Run a second, fully independent instance on another port. Each server supervises
its own agy, gets its own tmux session name, and hands its phone its own URL:
agy-remote run # session A -> :8765, tmux "agy-remote"
agy-remote run -p 8766 # session B -> :8766, tmux "agy-remote-8766"
agy-remote qr --port 8766 # pairing QR for session B
Each session's tool approvals go to its own phone: the server exports
AGY_REMOTE_URL into the agy it launches, and that agy's PreToolUse hook
posts there. Pair the phone with both URLs and switch between them like any two
web apps — both share the host's token and encryption key, so there is no second
pairing secret to manage.
🔀 Using opencode instead
agy-remote used to front opencode as a second
backend. It no longer does, because opencode does not need it: opencode serve
already hosts a mobile-tuned web app at /, and opencode web is the same thing
with a browser opened for you.
What that leaves you to solve is transport, and Tailscale does it in one command:
opencode serve --port 4096 # stays on 127.0.0.1, its default
tailscale serve --bg --https 8443 4096 # tailnet HTTPS, real certificate
Then open https://<your-machine>.<tailnet>.ts.net:8443/ on your phone. There is a
ready-made shell helper in contrib/opencode-tailscale.zsh
that starts both, waits for the server, and prints the URL with a scannable QR code.
[!WARNING] Do not give opencode
--hostname 0.0.0.0. It prints "OPENCODE_SERVER_PASSWORD is not set; server is unsecured" for a reason — anything that reaches the port can drive an agent with shell access. Keep it on loopback, lettailscale servebe the only door, and setOPENCODE_SERVER_PASSWORDas well. Nevertailscale funnelit.
What you give up versus this PWA: lock-screen push when a tool needs approval, and a single paired app for both agents.
📱 Mobile PWA Setup
- Connect via Tailscale: Ensure both your Mac and phone are on your private Tailscale network.
- Scan QR Code: Point your phone camera at the QR code printed by
agy-remote. - Install as PWA:
- iOS (Safari): Tap the Share button (
⎋) ➔ Tap Add to Home Screen (⊞). - Android (Chrome): Tap the Menu (
⋮) ➔ Tap Install App / Add to Home screen.
- iOS (Safari): Tap the Share button (
- Enable Push Alerts: Tap the bell icon (
🔔) in the top navigation bar to grant lock-screen notification permissions.
🛡️ Remote Tool Approvals
When an agent needs permission to execute bash commands, edit files, or invoke
external APIs, agy-remote pauses execution, sends a Web Push notification to
your phone lock screen, and presents interactive [Allow] / [Deny] buttons.
It only pauses if someone is there. With no phone connected — the normal
state of a server that runs all day — the hook is answered immediately with
ask, and agy prompts in its own terminal exactly as it would with no hook
installed. Anything else would stall every tool call on the machine for as long
as agy waits before killing the hook.
Approvals belong to the session that raised them. Every session's requests reach the phone, but a banner is drawn only in its own transcript: the header carries one badge saying how many are waiting elsewhere and where, and each row of the session drawer shows its own count. Open that session to answer.
Three timeouts, nested inward, so the layer that gives up first is the one
that can explain itself: the server decides at 240s, the hook gives up on the
socket at 270s, and agy kills the hook at 300s. Reversed, a slow answer
surfaces as signal: killed rather than "approval timed out on mobile remote".
For Google Antigravity (agy):
Install the Antigravity PreToolUse lifecycle hooks:
# Configure global Antigravity hooks (~/.gemini/config/hooks.json)
agy-remote setup-hooks
# Or configure hooks specifically for the current project (.agents/hooks.json)
agy-remote setup-hooks --project
⌨️ Terminal Key Controls
A prompt from the phone is text plus Enter, which cannot express the keys agy's
TUI actually needs: Shift+Tab cycles the execution mode (default →
accept-edits → plan), Esc closes a panel or halts a stream, and the arrow
keys drive the selection lists behind /model, /permissions and /resume.
The PWA sends those as named keys, over the same sealed WebSocket as prompts, or
via POST /api/key with {"key": "shift_tab"}. Only names from the allowlist in
keys.py are accepted — never raw bytes, since the pty is wired to a live agent
session. Both supervisors implement it: the PTY path writes the escape sequence,
the tmux path calls tmux send-keys.
| Key | Name | Use |
|---|---|---|
Shift+Tab |
shift_tab |
Cycle execution mode |
Esc |
escape |
Close panel / halt stream |
↑ ↓ ← → |
up down left right |
Move through a selection list |
Enter |
enter |
Confirm the highlighted choice |
Tab |
tab |
Confirm slash-command autocomplete |
y / n |
yes / no |
Answer a tool confirmation |
Ctrl+C |
interrupt |
Interrupt |
PgUp PgDn |
page_up page_down |
Scroll a panel |
Backspace |
backspace |
Delete a character |
Seeing the screen. The PWA renders transcript.jsonl, which holds the
conversation and nothing else — everything agy draws transiently (the /model
picker, /permissions, autocomplete, the execution mode in the status bar)
exists only on the terminal. The supervisor now feeds its pty output through a
terminal emulator on the server (screen.py, pyte) and ships the resulting
grid of plain text: tap Screen to see it, and the execution mode appears as
a badge beside the title. Running the emulator server-side keeps the PWA free of
third-party scripts, so it still works on an air-gapped tailnet.
The mirror matches the pty, which inherits the size of the desktop terminal that launched it. One pty has one size, so the phone sees the desktop's geometry — scroll horizontally rather than expecting a reflow. In watcher mode there is no supervised session and no screen.
What the phone still cannot see. Anything agy draws as a transient panel — the /model picker,
the mode indicator in the status bar, autocomplete — never reaches the
transcript, so keys aimed at those panels are sent blind. Slash commands that
produce conversation output work normally; ones that open a panel need the
desktop terminal in view.
🔔 Web Push Notifications
agy-remote features a fully self-contained VAPID Web Push server:
- VAPID keypairs are automatically generated and stored locally in
~/.gemini/antigravity-cli/vapid.json. - Zero third-party push notification SaaS accounts required.
- Test push notifications anytime from the command line:
uv run agy-remote push-test "Test alert from agy-remote"
🔒 Security & Cryptography
Threat model in one line
The token and encryption key are minted once and kept in
~/.gemini/antigravity-cli/agy-remote-credentials.json (owner-only), so the URL
saved on your phone keeps working across restarts and agy-remote run needs no
arguments. --rotate-token issues new ones and revokes every paired device. Pairings also
expire on their own after 30 days (AGY_REMOTE_CREDENTIAL_TTL_DAYS; 0
disables expiry): a pairing URL is a durable secret, and a leaked QR screenshot
or a lost phone should not stay a way in forever. After expiry the next launch
prints a fresh QR to re-scan.
Anyone who can reach this server and holds the token can run arbitrary code on your machine — prompts are injected straight into your live agy session. Treat the token like an SSH key.
What protects what
| Layer | Implementation | Covers |
|---|---|---|
| Transport | Tailscale WireGuard mesh (recommended), or plain HTTP on LAN | Phone ↔ tailnet peer. Not the last hop behind a subnet router. |
| Payload encryption | AES-256-GCM over every WebSocket frame, both directions | Transcripts, tool args, diffs, prompts, approvals — even on a cleartext hop |
| Replay defence | Timestamp bound as GCM AAD + nonce cache, ±300 s window | Captured send_prompt / approve_tool frames cannot be re-injected |
| Downgrade defence | Unsealed frames are rejected outright when E2EE is on | Token holder without the key cannot drive the agent |
| Authentication | High-entropy token, constant-time secrets.compare_digest on every entry point (REST, WebSocket, hook) |
Unauthorized access |
| Bind safety | --no-auth is refused on any non-loopback bind |
Unauthenticated RCE |
| Browser | Strict CSP, zero third-party origins, escape-first Markdown renderer | Prompt-injected XSS stealing the key from localStorage |
| Path traversal | Strict conversation_id charset + containment check |
Reading files outside the brain dir |
| Uploads | Extension allowlist and magic-byte sniffing, 25 MB cap, 0600 |
Active-content and disguised-payload drops |
| Secrets at rest | vapid.json, the runtime state file and each server registration are 0600 |
Local key theft |
| Access model | One host-wide token and key: every paired device is a full operator, and --rotate-token revokes all of them together |
Not per-device access. There is nothing to audit afterwards, so the header's device count is the only sign a pairing URL has escaped |
| Approval routing | The hook posts to the server naming its own tmux session, and is answered immediately when no phone is watching | One server cannot answer for another's session, and a background server cannot stall an agent nobody is watching |
Honest naming
The key is generated by the server and shared with the browser through the URL hash, so this is pre-shared-key payload encryption between your phone and your Mac — not zero-knowledge end-to-end encryption. The server is one of the two endpoints and necessarily sees plaintext. What it buys you is real: the payload stays sealed across any hop the transport does not protect.
Where the encryption actually earns its keep
With a Tailscale subnet router (e.g. OpenWrt) rather than Tailscale on the Mac itself:
Phone ──WireGuard──▶ OpenWrt (subnet router) ──plain LAN──▶ Mac
encrypted cleartext
The tunnel terminates at the router. That last LAN hop is unencrypted HTTP, so the AES-GCM payload layer is the only thing protecting your transcripts there. Keep E2EE enabled in this topology. Running tailscaled on the Mac itself removes the gap entirely and is the stronger setup.
Operational guidance
- ✅ Tailscale (on the Mac, ideally) with auth enabled — the intended configuration.
- ⚠️ LAN-only: works, but anyone on the Wi-Fi can reach the port. Keep auth and E2EE on.
- ❌ Never
AGY_REMOTE_NO_AUTH=1on a routable bind — the server refuses to start. - ❌ Never expose the port to the public internet or via port-forwarding.
📖 CLI Reference
Commands
| Command | Description |
|---|---|
agy-remote run [args...] |
Launch agy under a PTY with dual desktop & mobile control (recommended). |
agy-remote run --tmux |
Launch agy inside a persistent tmux session (agy-remote). |
agy-remote attach |
Adopt an agy already running in tmux: full control, nothing restarted. |
agy-remote attach --wait |
Listen with nothing running; start and adopt a session when a phone connects. |
agy-remote serve |
Start standalone log watcher server. |
agy-remote qr |
Re-display pairing QR code and active network URLs. |
agy-remote qr --port N |
Pairing QR for the instance on port N (a second instance does not own the shared runtime state). |
agy-remote run --rotate-token |
Issue a new token and encryption key, revoking every paired phone. |
agy-remote setup-hooks |
Install Antigravity lifecycle hooks for remote tool approvals (agy only). |
agy-remote push-test [msg] |
Send a test Web Push notification to registered mobile devices. |
CLI Options
| Flag | Commands | Description |
|---|---|---|
--qr-timeout <seconds> |
run |
Time in seconds to display QR code countdown before attaching TUI (default: 30s; 0 to attach immediately). |
--tmux |
run |
Run agent inside a persistent tmux session. |
--port, -p <port> |
All server commands | Web server port (default: 8765). |
--host, -h <host> |
All server commands | Web server host bind address (default: 0.0.0.0). |
--token, -t <token> |
All server commands | Custom authentication token. |
--wait |
attach |
Serve with no agy running, and start one when a phone connects (for a boot service). |
--session <name> |
attach |
tmux session to adopt (default: the one running agy, if there is exactly one). |
--rotate-token |
run, serve, attach |
Mint fresh auth token and encryption key, invalidating existing mobile pairings. |
--no-auth |
All server commands | Disable token authentication (refused on non-loopback binds). |
--no-e2ee |
All server commands | Disable payload encryption. |
--tls / --no-tls |
All server commands | Force enable or disable Tailscale HTTPS certificate. |
Environment Variables
| Variable | Default | Description |
|---|---|---|
AGY_REMOTE_AGENT |
agy |
Name of the agent this server fronts, as shown in the PWA header. |
AGY_REMOTE_PORT |
8765 |
Server port. |
AGY_REMOTE_URL |
Set by run |
Which server a supervised agy's PreToolUse hook posts to. Exported into the child; set it by hand only to point a hand-started agy at a specific instance. |
AGY_REMOTE_HOST |
0.0.0.0 |
Server bind host. |
AGY_REMOTE_TOKEN |
Stored | Override the stored authentication token. |
AGY_REMOTE_NO_AUTH |
0 |
Set 1 to disable token authentication. Refused unless bound to loopback. |
AGY_REMOTE_NO_E2EE |
0 |
Set 1 to disable payload encryption. Leave enabled unless debugging. |
AGY_BRAIN_DIR |
~/.gemini/antigravity-cli/brain |
Custom path to Antigravity brain data. |
AGY_REMOTE_E2EE_KEY |
Stored | Override the stored base64 256-bit payload key. |
AGY_REMOTE_CREDENTIAL_TTL_DAYS |
30 |
Pairing credential lifespan in days (0 to disable expiry). |
🧪 Testing & Quality Assurance
Run the automated test suite and code quality checks:
# Run pytest test suite
uv run pytest
# Check code formatting and linting
uv run ruff format .
uv run ruff check .
# Run the PWA's formatting tests (node's built-in runner, no dependencies)
node --test "tests/js/*.test.mjs"
❓ Troubleshooting & FAQ
Q: What happened to opencode support?
A: It was removed — opencode ships its own mobile-tuned web UI (opencode serve hosts it at /), so fronting it here duplicated work someone else maintains. See Using opencode instead for the two-command Tailscale setup and the helper script.
Q: Can I pass arguments to agy when launching agy-remote run?
A: Yes. Anything after -- is forwarded directly to agy:
agy-remote run -- --resume <session-id>
Q: Why does the QR code show a countdown before attaching?
A: A fullscreen TUI like agy immediately redraws the terminal when attaching, which would clear the pairing QR code before you could scan it. The countdown (--qr-timeout, default 30s) gives you time to scan or press any key to attach instantly.
Q: Why does the QR code show my LAN IP instead of Tailscale?
A: The Tailscale daemon must be running on this machine, not just installed — check tailscale status. agy-remote prioritizes a Tailscale IPv4 when one exists, and falls back to the LAN address otherwise.
Q: My router runs Tailscale. Does the Mac need it too?
A: Tailscale is per-device, not a router feature. A router acting as a subnet router can advertise the Mac's subnet, and your phone (which must itself be a tailnet node) will then reach the Mac — but the tunnel ends at the router, leaving the final LAN hop in cleartext. Keep payload encryption enabled in that setup, or install Tailscale on the Mac for an end-to-end tunnel.
Q: Do I need Tailscale at all?
A: No. If the phone and Mac share a Wi-Fi network, use the LAN URL. Keep auth and E2EE on, since anyone on that network can reach the port.
Q: Why are push notifications not arriving on iOS?
A: On iOS, Web Push requires saving the page as a PWA via Share ➔ Add to Home Screen in Safari (iOS 16.4+). Launch the app from your home screen and tap the bell icon to grant permissions.
Q: Can I use agy-remote without tmux?
A: Yes. Plain agy-remote run uses the built-in PTY supervisor; --tmux is opt-in.
📄 License
MIT. Built for seamless agentic pair-programming workflows — use it, fork it, ship it.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file agy_remote-26.8.22.37.tar.gz.
File metadata
- Download URL: agy_remote-26.8.22.37.tar.gz
- Upload date:
- Size: 107.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a0ca36db49f087a8d3171bb6d02271622269e80acdd4221a6f304d61194c9e5e
|
|
| MD5 |
8c40b73b02bd51313995cd936e9be240
|
|
| BLAKE2b-256 |
6542f9f318be885efb582edc9a71b69b605ac07065922f0a4f825323b1c60d52
|
Provenance
The following attestation bundles were made for agy_remote-26.8.22.37.tar.gz:
Publisher:
release.yml on ollisulopuisto/agy-remote
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
agy_remote-26.8.22.37.tar.gz -
Subject digest:
a0ca36db49f087a8d3171bb6d02271622269e80acdd4221a6f304d61194c9e5e - Sigstore transparency entry: 2566697607
- Sigstore integration time:
-
Permalink:
ollisulopuisto/agy-remote@679f7d2f9fcfed34544e78953bee8969dfbe8a9e -
Branch / Tag:
refs/tags/v26.08.22.37 - Owner: https://github.com/ollisulopuisto
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@679f7d2f9fcfed34544e78953bee8969dfbe8a9e -
Trigger Event:
push
-
Statement type:
File details
Details for the file agy_remote-26.8.22.37-py3-none-any.whl.
File metadata
- Download URL: agy_remote-26.8.22.37-py3-none-any.whl
- Upload date:
- Size: 106.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3b8c129ca026432ed788b316489ad2e9f4558375698fff543897a2aa036b1b14
|
|
| MD5 |
7d97b2889c957ea3f2786934aa807d29
|
|
| BLAKE2b-256 |
62e13255dcd54812fe44c32ed2c4b135716ccacc28f3697b12a88eeb8b5e6e73
|
Provenance
The following attestation bundles were made for agy_remote-26.8.22.37-py3-none-any.whl:
Publisher:
release.yml on ollisulopuisto/agy-remote
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
agy_remote-26.8.22.37-py3-none-any.whl -
Subject digest:
3b8c129ca026432ed788b316489ad2e9f4558375698fff543897a2aa036b1b14 - Sigstore transparency entry: 2566697631
- Sigstore integration time:
-
Permalink:
ollisulopuisto/agy-remote@679f7d2f9fcfed34544e78953bee8969dfbe8a9e -
Branch / Tag:
refs/tags/v26.08.22.37 - Owner: https://github.com/ollisulopuisto
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@679f7d2f9fcfed34544e78953bee8969dfbe8a9e -
Trigger Event:
push
-
Statement type: