reminal

Lets agents list, search, read and type into the terminals you already have running, on every machine you own.

Documentation

reminal

Every window, desktop and terminal on your machines — live in any browser.

Close the laptop lid and walk away. reminal keeps the machine serving — and hands you its actual apps, not just a shell — in any browser, from anywhere. No open ports, no keys on disk, nothing to install on the device you're holding.

License: AGPL v3 Release Go Platform Encryption Relay

Animation: a MacBook desktop with VS Code, Keynote and Mail windows folds its lid shut — then the same windows appear live as panes in the reminal web viewer, still controllable, with nothing plugged in

The lid shuts with no monitor and no dongle attached — and the windows keep streaming, live and controllable, into a browser.


No monitor. No dummy plug. No dongle.

Close a MacBook's lid and macOS puts it to sleep — unless it's on power with a monitor and keyboard attached. The usual fix is a hardware "dummy" HDMI plug that fakes a display, or just leaving the lid propped open on your desk.

Closed-lid mode does it in software. Flip it on and leave — no monitor, no dongle, in any order. reminal disables clamshell sleep and, because GUI apps need a screen to draw on, spins up a virtual display the moment the Mac goes headless — so window mirroring keeps working with nothing plugged in. Toggle it off and everything is undone.

Closed-lid mode is macOS-only; everything else below works on macOS, Linux and Windows alike.


Set it up in one line

curl -fsSL https://raw.githubusercontent.com/harshalgajjar/Reminal/main/install.sh | sh
reminal

On Windows (PowerShell):

irm https://raw.githubusercontent.com/harshalgajjar/Reminal/main/install.ps1 | iex
reminal

That's the whole setup — one command on the machine you're leaving behind. Everything else you own is already a client, because the client is a browser.


Reach in and drive the apps

Any app window streams live into your browser — and you don't just watch it, you drive it: cursor, click and right-click, type, scroll, drag, pinch-zoom. Not open yet? Launch any installed app on the host from the Apps menu, then drive it. Or tap Host → View full desktop to run the whole machine at once. On a phone the screen becomes a trackpad — your Mac, fully hands-on, from your pocket.

Real capture: a phone opens the Mac's TextEdit window and types 'Hello from my phone!' — the words land in the real app on the Mac, live — then the phone mirrors the entire desktop

Real capture, unedited. The phone picks a window, types into it — the words appear in the real app on the Mac — then mirrors the whole desktop. Native capture, streamed peer-to-peer.


60 fps, from a machine that isn't here

And nothing about those windows says stream. Drag it, scrub a video inside it, watch a build scroll past — it stays fluid the whole way, on hotel Wi-Fi or a phone on cellular. Sixty frames a second, 17 ms from its screen to yours.

A Mac Studio named mac-studio, with one of its windows streaming live into a browser below it at 60 fps — the motion inside sweeping a smooth trail across the canvas — beside a large '60 fps' and the line 'of a live window, from a machine that isn't here'

H.264 down the session's own DataChannel — peer-to-peer, about 2.6 Mbps. Relay-only viewers get the same video at 30 fps.


A full terminal, too — scan a QR and you're in

reminal prints a session ID, a PIN, and a QR code. Scan it, and your phone is a full terminal on your machine — real color, touch text-selection, on-screen modifier keys. No port forwarding. No keys to manage. Nothing to install on the phone; the browser is the client.

A terminal runs reminal and prints a QR code; a phone scans it and joins the same session — end-to-end encrypted

The fastest SSH you'll ever configure — because there's nothing to configure.


Interact with your machines like they're right here

Pop any remote window out onto your desktop and it sits there like its own app window — except it's running on another machine entirely. Line up an editor from your MacBook, a terminal on a cloud box, and a dashboard from the Mac mini at home, and work across all of them as if they were local.

Three native windows in front — an editor, a terminal, and a dashboard — each linked by a dotted line to the machine behind it: a MacBook (studio-mac), a cloud VM (aws-eu-1), and a Mac mini (mini-01)

Own your machines. Watch them all.

The moment your work spans more than one machine — a rack of servers, or agents let loose on several boxes at once — the hard part isn't starting it, it's seeing it. reminal machines is one live view of everything you own: every machine, every terminal on it, what's running right now, who's watching, how long it's been idle. An agent running a test suite on your laptop, patching a CVE on a cloud VM, and rotating backups on the Mac mini — or just your own sessions — all at a glance. It's on the CLI and in the web Machines panel, where you can jump into any session, rename it, spawn a new one, or kill it on any box.

That single pane works because you own the machines. Enroll a device once — reminal own prints its id, you paste sudo reminal add owner <id> on each machine — and from then on it reaches every session with no PIN. The trust is a per-device key: revocable one at a time, sudo-gated to grant, and the relay still only ever sees ciphertext.

A live 'reminal machines' view of a fleet you own — studio-mac, aws-eu-1 and mini-01, all online — each showing its sessions: agents running a test suite, patching a CVE and rotating backups, alongside ordinary sessions like api-prod, worker and grafana, with live indicators and viewer counts

Your agents can see each other

reminal integrate

One command registers reminal's MCP server with every agent CLI you have — Claude Code, Codex, Cursor, Gemini, OpenCode, Antigravity, Amp. From then on your agents can:

  • list_sessions — every session you own, on this machine and every enrolled box
  • search_sessions — regex across live terminal scrollback, on all of them
  • read_transcript — read another session's terminal as plain text
  • send_keys — type into another session's PTY, across machines

Which means an agent on your laptop can watch what an agent on your build box is doing, and answer it. They don't have to be the same agent, or from the same vendor — Claude Code can drive a Codex session. There's no protocol to adopt and nothing to integrate against: the bus is the terminal. If it runs in a PTY, it can be read and typed into.

Agents can also raise a hand. add_note pins a badge on the actual window it's about — the editor, the browser it's driving — and that reaches your phone, where you can answer it.

send_keys types real keystrokes into a real shell on a machine you own. It only reaches boxes you've enrolled yourself, and everything it does is visible live in the viewer — but treat it with the respect you'd give any tool that can type Ctrl+C into your terminal.

Prefer no MCP? Hand an agent a session ID and PIN and it connects like any other viewer, to every machine you've shared.

An AI agent given the session key dispatches jobs in parallel to three machines — run test suite on studio-mac, scan and patch on aws-eu-1, rotate backups on mini-01

Share a local port with the world

reminal expose 3000 turns whatever's running on localhost into a public HTTPS URL — a dev server, a webhook target, a build to show a client. PIN-gated by default (or --public to open it up), so you can share the link without deploying anything. It's a built-in ngrok, on the tool you already have running.

A terminal runs a dev server on localhost:3000, then reminal expose 3000 prints a public https URL — which loads the same app live on a phone, PIN-gated over TLS

Move a file between any two machines

reminal copy report.pdf on one machine prints a one-time code; reminal paste <code> on another pulls the file down — Mac to Linux, Windows to Mac, laptop to server, anywhere to anywhere. End-to-end encrypted, no cloud drive, no account. AirDrop, for every machine you own.

One terminal runs reminal copy report.pdf and gets a one-time code; another machine runs reminal paste with that code and the file transfers, end-to-end encrypted

The last thing you'll install standing at your computer

Set it up once, in person — then you never have to sit at that machine again. From any browser you get its terminal, any window, the whole desktop, a public link to a local port, files to and from it, even a live session shared with someone else. One tool, every remote job.

reminal at the center, wired to the five things it does: a terminal in any browser, control any window or desktop, a public URL for a local port, sending files between machines, and pairing with anyone live — no accounts, no subscriptions, nothing for the other side to install

How it works

Architecture: your machine (reminal) and any device both dial out to a Cloudflare relay over encrypted WSS — the relay only routes ciphertext it can't read — while window and desktop frames go directly peer-to-peer over WebRTC, off the relay

Your machine dials out to a relay over WSS; viewers dial out to the same relay. Nothing ever listens on your machine. Everything through the relay is encrypted end-to-end — it routes ciphertext it cannot read. Window and desktop frames don't even take that path: they ride a direct WebRTC connection between browser and host.

You trust Cloudflare to deliver packets — the same way you trust your ISP with SSH traffic. Neither can read what you send. The difference: reminal never opens your machine to the internet.


Everything you get

Join a session from anywhere — phone (scan the QR), any browser (open the URL, type the PIN), or another terminal (reminal --connect <id> --pin <pin>). Then:

Persistent, resilient shell

Close the laptop, switch to your phone, reconnect from a different city — your shell is right where you left it, and the current screen paints instantly (a snapshot, no slow fast-forward). Wi-Fi drop, tunnel, elevator? Auto-reconnect with backoff, 2 MiB of scrollback intact.

Pair with anyone

Send a session ID and PIN to a teammate over any channel and they join the same live shell — or a window mirror — from a browser. No account, no install, multiple viewers at once. Ctrl+C ends it; there's nothing to revoke.

Sessions that outlive your terminal

Kick off a long job and close the lid — it keeps running. reminal new deploy spawns a named session; list · attach · rename · kill · prune manage the whole fleet by name, id, or fuzzy match. The Host panel shows live CPU/memory and spawns one in a tap.

Zero-install web terminal

A full xterm.js terminal is built into the relay. Any browser is the client — phone, iPad, locked-down work laptop, hotel-lobby PC. Pinch-zoom, text selection with draggable handles, on-screen modifier keys, voice dictation, find-in-scrollback.

Files, ports & pings

reminal copy / paste move a file between any two machines with a one-time code. reminal expose 3000 puts a local port on a public, PIN-gated URL. reminal send pushes a file to every viewer at once, and reminal notify fires a browser notification on all of them.

Secure by construction

No open ports, ephemeral session ID + PIN, AES-256-GCM end-to-end with a PIN-authenticated X25519 handshake the relay can't crack offline. Ctrl+C and the credentials are gone. Details below.

Own a machine, skip the PIN

Enroll a device as an ownerreminal own, then sudo reminal add owner <id> once — and it connects to any of that machine's sessions with no PIN. Per-device trust, revocable one at a time (reminal owners revoke, or self-revoke from the browser). The relay stays blind either way.

Every machine, one list

reminal machines shows every box you own and each live session on it — what's running, viewers, idle time. Same view in the web Machines panel: attach, rename, spawn, or kill a session on any machine from the browser.


Security

Built to be as secure as a properly configured SSH — and safer by default.

SSH leaves port 22 open, stores long-lived keys on disk, and trusts you to configure everything correctly. reminal takes the opposite approach: nothing to expose, nothing permanent to steal, encryption end-to-end.

LayerWhat it does
No open portsYour machine only initiates outbound connections. There is nothing on the network to scan, brute-force, or zero-day.
Ephemeral credentialsSession ID and PIN exist only while reminal is running. Ctrl+C and they are gone forever.
Owner devices, revocableA device you enroll as an owner connects without the PIN using its own key — a separate trust path from the ephemeral PIN, gated behind sudo to enroll and revocable per-device (or self-revoked from any browser). The relay still only routes ciphertext.
Dual-factor by designAn attacker needs both the session ID (~1 trillion combinations) and the 6-digit PIN. Knowing one is useless.
Rate-limited by the agentEvery PIN guess costs a full online handshake with your machine, and the agent answers at most ~6 per minute (burst of 8, one token per 10s). Exhausting a 6-digit PIN at that rate takes months — far longer than a session lives.
End-to-end encryptionAES-256-GCM with a fresh random 256-bit session key per agent run. Distributed to each viewer via a PIN-authenticated X25519 handshake (EKE-style) — the relay never sees the key or anything offline-brute-forceable from it.
Forward-secret handshakeEach WebSocket connection runs its own ephemeral X25519 exchange. Even if a future attacker recovers the PIN, recorded ciphertext stays unreadable.
Relay-blindCloudflare Workers route ciphertext. A relay that records traffic cannot recover the session key offline — wrong PIN guesses are detectable only by attempting a full handshake online (one shot each, bounded by the agent's kex throttle).
P2P you can trustWebRTC signaling (SDP, ICE) rides inside the already-encrypted session channel, so the relay can't tamper with DTLS fingerprints — no man-in-the-middle window. Frames on the DataChannel are DTLS-protected end-to-end.
TLS in transitWSS / TLS on every hop in production.

One deliberate exception: reminal expose port-forwards are not end-to-end encrypted — the visitor is an ordinary browser with no reminal key, so that traffic passes through the relay in plaintext (PIN-gated, but readable by the relay). Everything else above is E2E. Self-host the relay if that matters to you.

Best practices: share the session ID and PIN over different channels (email the ID, text the PIN) · Ctrl+C when done — credentials die instantly · keep the client current with reminal upgrade.

Digging deeper: Security architecture · Threat model · Subprocessors & data handling · Self-assessment · Report a vulnerability


reminal vs SSH, at a glance

SSH was designed in 1995 — it assumes a static IP, a router you can configure, and keys you keep rotated. reminal assumes none of that, so the trade-offs line up differently:

reminalSSH
Setup timeOne commandKeys, configs, port-forwarding, firewalls
Listening portNoneTCP 22 exposed to the internet
CredentialsEphemeral session ID + PINPermanent keys on disk
Behind NAT / hotel Wi-FiJust worksVPN or jump host required
Client required on viewerNone — a browser is the clientssh + a configured key per device
Phone friendlyScan QR → inNo native client
Session survives disconnectShell keeps running, hop between devicesDrop the connection, lose your work (unless you wrapped it in tmux)
Network blipsAuto-reconnect, scrollback replayWrite failed: Broken pipe
GUI appsMirror & control any window — or the whole desktopX11 forwarding, if you dare
Laptop lid shut, no monitorClosed-lid mode keeps serving on a virtual displayTerminal only
If laptop is stolenSessions already deadOld keys still grant access
EncryptionEnd-to-end through relayEnd-to-end direct (if configured right)

Run your own relay (free, one time)

The relay runs on Cloudflare Workers + Durable Objects. The free tier handles thousands of sessions a month — and window frames go peer-to-peer, so the heavy traffic never touches it.

cd cloudflare
npm install
npx wrangler login
npm run deploy

Then copy reminal.build.env.example to the gitignored reminal.build.env, put your workers.dev URL there, and run ./scripts/build.sh. No source edit is needed. Full guide in cloudflare/README.md.


Local development

# Build once; source builds retain the upstream public relay by default
./scripts/build.sh

# Terminal 1 — your own relay on localhost:8080
./dist/reminal relay

# Terminal 2 — share a session via the local relay
REMINAL_LOCAL=1 ./dist/reminal

# Terminal 3 — connect from another shell or the browser
REMINAL_LOCAL=1 ./dist/reminal connect <session_id> <pin>
# or http://localhost:8080/?s=<session_id>

To test against a remote relay without rebuilding, set either runtime URL; reminal derives its counterpart automatically:

REMINAL_RELAY=wss://your-relay.example/ws ./dist/reminal
# or: REMINAL_WEB=https://your-relay.example ./dist/reminal

Reference

Platform support

The mirroring you see above isn't macOS-only — window capture and full control (click, type, scroll, drag) work on Linux/X11 and Windows as well.

CapabilitymacOSLinuxWindows
Terminal sharing · sessions · files · port forwarding✅ ConPTY
Owner connect (PIN-free) · reminal machines
Window & desktop mirroring + control✅ ScreenCaptureKit — H.264 up to 60 fps✅ X11 — wmctrl · xdotool · ImageMagick✅ Win32 — PrintWindow · SendInput
Closed-lid mode (auto virtual display)
Hot restart (reminal restart)✅ (foreground sessions convert to background + attached viewer)

Linux capture needs an X11 session (or Xwayland) — native Wayland blocks synthetic input, so it isn't supported yet. Apple Silicon, x86_64, and Windows ARM64 all supported.

Windows notes

  • Shell: sessions open PowerShell 7 (pwsh) when installed, else Windows PowerShell, else cmd — set $env:SHELL to override. Terminals run through ConPTY, the same API Windows Terminal uses, so colors, TUIs, and resizing behave like a native console.
  • No permission prompts: unlike macOS's Screen Recording grant, window mirroring and input injection need nothing enabled — it works out of the box.
  • Firewall prompt on first mirror: when a viewer first attaches to a window/desktop pane, Windows Firewall asks about reminal — that's the direct peer-to-peer (WebRTC) stream binding a UDP port, the same prompt any video-call app gets. Allow enables P2P; Cancel is also fine — streaming falls back to the encrypted relay path.
  • Streaming: Windows uses the JPEG capture path (~5–15 fps). The 60 fps H.264 pipeline is currently macOS-only.
  • Mirroring needs a logged-in desktop — a machine sitting at the login screen (or a service session) has no windows to capture; terminal sharing works regardless.
  • Upgrades & hot restart: reminal upgrade swaps the exe in place (the running one is renamed aside), and reminal restart hot-swaps a session's agent onto the new binary without touching the shell inside — each session's shell lives in a tiny ConPTY-holder process, so the agent can be replaced under it (the session's PID changes, unlike Unix). A foreground session restarts by converting: it moves to the background and your terminal becomes an attached viewer of it — same shell, same keystrokes, Ctrl-] detaches. The background host also restarts itself automatically after an upgrade.

Commands

CommandWhat it does
reminal [--name <name>]Share this terminal session
reminal new [name]Spawn a fresh background session (detached — survives this terminal closing)
reminal list [filter] [-v]List sessions, recent-first; filter by id/name/cwd/title (--idle, --viewers, --headless)
reminal attach [id|name]Re-connect to a local session as a viewer (no arg → interactive picker)
reminal connect <id-or-url> [pin]Connect to a remote session from your terminal (PIN prompted if omitted)
reminal rename [id|name] <new-name>Rename a running session (inside a session: reminal rename <new-name>)
reminal stop [id|name|port]Stop the reminal layer — kicks viewers, keeps your shell/server running
reminal kill [id|name]Fully terminate a session (kills the shell — irreversible)
reminal prune [dur] [-y]Kill idle, unwatched sessions in one go (default idle ≥ 30m)
reminal restart [--all]Hot-swap the running agent(s) onto the latest binary — the shell stays alive
reminal integrate [--remove]Register reminal's MCP server with your agent CLIs (Claude Code, Codex, Cursor, Gemini, OpenCode, Antigravity, Amp)
reminal mcpRun the MCP server on stdio — list, search, read and type into sessions across your machines
reminal expose <port> [--public]Forward a local HTTP port to a public URL (PIN-protected by default)
reminal send <file>Push a file to every connected viewer (web client auto-downloads)
reminal copy [--ttl <dur>] <file>Offer a file for pickup anywhere; prints a one-time code
reminal paste <code> [dest]Fetch a file offered by reminal copy on another machine
reminal notify <message>Push a notification to viewers (browser notification on web)
reminal connectionsList currently attached viewers with connect time
reminal ownPrint this device's owner id + the add owner line to paste on machines you want to own
reminal add owner <id> [--label <name>]Enroll an owner device on this machine (needs sudo / an Administrator terminal on Windows) — lets it connect PIN-free
reminal owners [rename|revoke|restore <id|label> …]List / relabel / revoke / restore this machine's owner devices
reminal machines [rename <id|name> <new-name>]List every machine you own and its live sessions (web Machines panel manages them)
reminal info [id|name] [--all] [--qr] [--json]Show connect details — ID / PIN / URL / QR
reminal qr [id|name]Print just the join QR (for a second screen)
reminal settingsSettings page: keep the Mac unlocked for remote control; closed-lid mode (serve with the lid shut and nothing plugged in — disables clamshell sleep, auto-creates a virtual display while headless)
reminal doctorSelf-diagnostic: version, relay reachability, terminal, shell
reminal permissionsmacOS: grant Screen Recording to reminal once, so background (+) sessions can mirror windows
reminal completion <bash|zsh|fish|powershell>Print a shell completion script
reminal upgradeUpgrade to the latest release
reminal relay [port]Start a local relay (development only)
reminal version [--verbose]Print version

Sessions resolve by exact id, exact name, unique id prefix, or unique substring of name / cwd / title — reminal attach deploy just works.

Environment variables

VariableDefaultWhat it does
REMINAL_RELAYUpstream public relayRelay WebSocket base URL; also derives REMINAL_WEB when that is unset
REMINAL_WEBUpstream public web UIWeb UI URL; also derives REMINAL_RELAY when that is unset
REMINAL_LOCALSet to 1 to point everything at localhost
REMINAL_OWNERS_DIR/etc/reminal (%ProgramData%\reminal on Windows)Where the machine's owner list lives (the admin-gated trust store) — override for tests or unusual layouts
REMINAL_NO_KEEP_AWAKESet to 1 to let the host sleep while reminal runs (defaults to keeping it awake via caffeinate / systemd-inhibit / SetThreadExecutionState)
REMINAL_TURN / REMINAL_TURN_USER / REMINAL_TURN_PASSOptional TURN server for P2P window mirroring behind hostile NATs (or REMINAL_TURN_CF_KEY + REMINAL_TURN_CF_TOKEN for Cloudflare TURN). Without one, un-punchable viewers stay on the relay fallback
REMINAL_NO_CAPTURE_HELPERSet to 1 to force the screenshot capture path (skip the native ScreenCaptureKit helper)
REMINAL_DEBUGSet to 1 to append the raw error string to status lines, for diagnosing connection problems
SHELL$SHELL, then probes /bin/zsh, /bin/bash, /bin/sh (Windows: pwshpowershellcmd)Which shell to spawn inside the session

Installs to ~/.local/bin/reminal (macOS/Linux) or %LOCALAPPDATA%\Programs\reminal (Windows) — no sudo/admin needed. Apple Silicon, x86_64, and Windows ARM64. Build from source with ./scripts/build.sh (Go 1.25+, Swift toolchain on macOS for the native capture helper); on Windows it's a plain go build ./cmd/reminal.

For a persistent custom default in local builds, copy reminal.build.env.example to reminal.build.env and set REMINAL_DEFAULT_RELAY and/or REMINAL_DEFAULT_WEB. The local file is ignored by git and is parsed as inert KEY=VALUE data (not executed as shell code). Release workflows use repository variables with the same names, so forks can publish their own defaults; when those variables are absent, the upstream defaults remain intact so ordinary contributor and upstream builds continue to work.


Ready to try it?

curl -fsSL https://raw.githubusercontent.com/harshalgajjar/Reminal/main/install.sh | sh
reminal

On Windows (PowerShell):

irm https://raw.githubusercontent.com/harshalgajjar/Reminal/main/install.ps1 | iex
reminal

Scan the QR — you're in. No signup, no port-forwarding, no keys on disk. About 30 seconds from this page to your own machine, live in a browser.


License

reminal is dual-licensed under AGPL-3.0. Using it — personally or inside a company, unmodified — needs nothing from us. A commercial license covers embedding reminal in a product you distribute, or running a modified copy as a service. See LICENSING.md for where that line sits, and CLA.md if you'd like to contribute.

Built by @harshalgajjar. Stars are appreciated. Issues even more so.