sshai

sshai docs

Everything from first install to daily use. If you'd rather follow along visually, the walkthrough covers setup in five short steps.

Getting started

The shape of the thing: a small server (sshai) runs on your machine and exposes your tmux sessions — where your coding agent lives — to your phone, only over your private Tailscale network. Setup is four steps and about five minutes.

What you need

1 · Phone: install Tailscale

Install Tailscale from the App Store and sign in with Google, GitHub, or Apple. That creates your tailnet — a private network between your devices. Leave its VPN toggle on; it costs effectively no battery.

2 · Server: run your install command

SSH into the server and paste the one-liner from your account. It looks like this, with your own key:

your machine → the VPS
ssh you@your-vps
curl -fsSL https://sshai.co/install | SSHAI_LICENSE=SSHAI-XXXX-XXXX-XXXX-XXXX bash

Here's the whole run, so you know what to expect:

what the installer prints
==> Checking license
    license ok · saved to ~/.config/sshai/license
==> Installing system packages (tmux, build tools)
==> Installing Tailscale
==> Connecting this machine to your tailnet — approve the login link below

    To authenticate, visit:
        https://login.tailscale.com/a/8f3c21ab94e7   ← open this on your phone

==> Installing Node 22 (NodeSource)

Which coding agent will this server run?

  1) Claude Code    (claude)
  2) OpenAI Codex   (codex)
  3) OpenCode       (opencode)
  4) Gemini CLI     (gemini)
  5) Kimi Code      (kimi)
  6) other — I'll type the command myself

Choice [1-6]: 1
==> Installing claude (@anthropic-ai/claude-code)
==> Downloading sshai
==> Installing the sshai service
==> One more step: log claude in (opens now — finish its login, then quit it)

sshai is running.
  2. On your iPhone: install Tailscale + the sshai app, then set the
     server address to:   100.84.12.7

Three things worth knowing about what just happened:

3 · Agent: log in once

If the installer just installed your agent, it opened its login for you. If you skipped it, run the agent's command once and finish its sign-in:

on the server (or later, from your phone)
claude        # or codex / opencode / gemini — whatever you chose
# … complete the login it shows, then quit (ctrl-c) …

Every sshai session on this server shares that credential from now on.

4 · Connect the app

app · settings → add server
Name      vps
Address   100.84.12.7        ← http:// and port 7423 are filled in for you
Token     (leave empty unless you set one — see Token lock)

Using sshai

Sessions

A session is a tmux session on your server. It survives disconnects, phone reboots, flights, and weekends — whatever is running inside keeps running. The rhythm that works: one session per project.

app · new session
Server     vps
Name       veca-api
Directory  ~/code/veca-api
Run        claude            ← the server's agent is the default;
                               "claude --continue" resumes its last conversation

Windows

Inside a session, tmux windows are tabs. The classic setup for a project: agent in window 1, tests in 2, dev server in 3.

Key bar

The row above the keyboard. Defaults: esc · tab · ⇧tab · ctrl · shift · arrows · ^C · ^R · paste · A- · A+, with ⧉ and hide-keyboard pinned at the right.

Scrolling

Drag anywhere in the terminal. sshai drives the scroll on the server through tmux, so it always does the right thing: your agent's conversation scrolls the transcript, a shell scrolls its history, vim scrolls the buffer. Scroll back down to the bottom and live output resumes.

Multiple servers

Add every machine you own — Settings → Add server. Each becomes its own named section in the list with its sessions under it, fetched independently, so one machine being offline never blocks the rest. The + sheet gets a server picker, and each server's configured agent is its default.

a well-lived session list
office mac   100.94.190.55
  ● sshai        claude · ~/sshai · 1h
    scratch      zsh · ~ · 2d

vps · hetzner   100.111.166.103
  ● veca-api     claude · ~/code/veca-api · 4h
    deploy       zsh · /var/www · 6d

homelab pi   100.87.4.21
  ● sensors      python · ~/iot · 12d

Web terminal

Every server also serves a browser terminal — http://<tailnet-ip>:7423from any device on your tailnet, Android included. Same sessions, same scrolling. On iPhone, "Add to Home Screen" gives it an app icon, but the native app is the better experience.

Maintenance

Updating

Re-run your install command on the server:

curl -fsSL https://sshai.co/install | bash
# ==> Using saved license from ~/.config/sshai/license
# ==> Using saved coding agent: claude
# … downloads the new version, restarts the service …

Sessions survive the restart — your agent keeps working; the app reconnects within a second.

Token lock (optional)

Tailscale already restricts access to devices you've signed in. If you share your tailnet with other people, add a second lock:

on the server
sudo systemctl edit sshai

# in the editor that opens, add:
[Service]
Environment=SSHAI_TOKEN=pick-something-long-and-random

sudo systemctl restart sshai

Then put the same token in the app: Settings → your server → Token. Requests without it get 401.

Uninstall

sudo systemctl disable --now sshai
sudo rm /etc/systemd/system/sshai.service && sudo systemctl daemon-reload
rm -rf ~/sshai ~/.config/sshai
# tmux/node/tailscale stay — remove them only if nothing else uses them

Security model

Your license

Troubleshooting

App can't connect. In order: is Tailscale toggled on, on the phone? Then on the server:

tailscale status        # both devices listed and not "offline"?
systemctl status sshai  # active (running)?
journalctl -u sshai -n 20

"No tmux session named …" — it was killed. The service recreates main on restart:

sudo systemctl restart sshai

License rejected at install. Copy the key exactly from your account — the format is SSHAI- plus four groups of four. Refunded licenses are revoked by design.

Agent misbehaves inside a session.Run the agent's command directly over SSH first. If it fails there too, it's an agent/account issue; if it works over SSH, it works in sshai — try killing and recreating the session.

Anything else — reply to your Stripe receipt with the output of journalctl -u sshai -n 50and we'll dig in.