Think ngrok, but for keystrokes.
Let your colleague type into your terminal over a screenshare.
Install • Quick start • Commands • Security • Website
Client (stdin) ──WS──▶ Relay (broker) ──WS──▶ Host (PTY)
◀────────────────────── (output back to client)
- The host starts a session and gets a human-readable code (e.g.
keen-fox-42) - The client joins using that code
- Keystrokes flow through the relay to the host's terminal, output flows back
- All data is encrypted end-to-end using X25519 key exchange + AES-256-GCM
curl -fsSL https://keytun.com/install.sh | shbrew install gboston/tap/keytungo install github.com/gboston/keytun@latest# Host a session (uses relay.keytun.com by default)
keytun host
# Join the session (use the code from the host output)
keytun join keen-fox-42To use a local relay instead:
keytun relay --port 8080
keytun host --relay ws://localhost:8080/ws
keytun join keen-fox-42 --relay ws://localhost:8080/wsStarts the WebSocket relay broker.
--port, -p Port to listen on (default: 8080)
Hosts a session and shares a session code with your colleague.
--relay Relay server URL (default: wss://relay.keytun.com/ws)
--mode Injection mode: "terminal" or "system" (default: terminal)
--target Target app name for system mode, e.g. "TextEdit" (macOS only)
Terminal mode spawns a PTY with your shell — the remote user sees and types into a full terminal session.
System mode injects keystrokes at the OS level into the focused application (macOS only).
Joins an existing session. Press Escape twice to disconnect.
--relay Relay server URL (default: wss://relay.keytun.com/ws)
All data between host and client is end-to-end encrypted. The relay only sees opaque ciphertext.
| Layer | Algorithm |
|---|---|
| Key exchange | X25519 ECDH |
| Encryption | AES-256-GCM |
| Key derivation | HKDF-SHA256 |
The relay is a dumb pipe — it cannot read keystrokes or terminal output.
Requires Go 1.25+ and just (install via mise install).
just build # Compile binary to ./keytun
just test # Run all tests
just clean # Remove compiled binaryReleases are automated via GoReleaser and GitHub Actions.
- Add an entry in
CHANGELOG.md - Commit and tag:
git add -A && git commit -m "release: v0.X.Y" git tag v0.X.Y git push origin main --tags
- The
release.ymlworkflow triggers on thev*tag push and:- Builds macOS binaries (arm64 + amd64) with CGO disabled
- Creates a GitHub Release with the binaries and checksums
- Updates the Homebrew cask in
gboston/homebrew-tap
| Secret | Purpose |
|---|---|
GITHUB_TOKEN |
Auto-provided by Actions, used for the GitHub Release |
HOMEBREW_TAP_GITHUB_TOKEN |
PAT with write access to gboston/homebrew-tap |
AGPL-3.0