iPhone & iPad · iOS 17 · coming to the App Store
A single-purpose SSH terminal for iPhone and iPad. It dials your machine directly — no relay, no account, no telemetry — and renders vim, tmux and journalctl the way your desk does.
· One up-front price. No subscription, no in-app purchases.
1 use anyhow::Result; 2 3 pub fn reconcile(s: &State) -> Result<Plan> { 4 let mut plan = Plan::new(); 5 for svc in s.drifted() { 6 plan.push(Step::Restart(svc.id)); 7 } 8 // drift is settled; nothing else to do 9 Ok(plan) 10 } ~ ~ ~ reconcile.rs rust utf-8 9:18 -- INSERT -- [ops] 0:vim* 1:logs 2:psql edge-01 09:42
Not a screenshot. slassh has no analytics SDK, no crash reporter and no screenshot service — so we have never seen a session, and we could not show you a real one if we wanted to.
What follows is the interface redrawn to the pixel in the app's own palette, running invented hosts and invented output. That is the whole product argument in one page: your terminal is not ours to display.
What it is for
A real VT emulator underneath — alternate screen, scrollback, SGR mouse reporting, bold, italic and underline, 24-bit colour. Programs do not find out they are talking to a phone.
Full-screen programs get the alternate screen they expect, and the
gesture you make on it goes to them — as SGR wheel reports when the program
asked for the mouse, as arrow keys when it did not, which is what makes
less and man scroll.
⌃ and ⌥-as-Meta included, so emacs chords survive the trip.⌘↑ / ⌘↓.dev@builder:~/src/reconcile$ cargo test --workspace Compiling reconcile v0.9.2 (/home/dev/src/reconcile) Finished `test` profile [unoptimized] in 12.44s Running unittests src/lib.rs running 41 tests test plan::drift_is_idempotent .............. ok test plan::restart_orders_by_dependency ..... ok test store::rejects_unknown_schema .......... ok test store::round_trips_utf8_hostnames ...... ok … 36 more test result: ok. 41 passed; 0 failed; 0 ignored dev@builder:~/src/reconcile$ [dev] 0:vim 1:cargo* 2:ssh builder 14:08
A long run you started at your desk is a tail -f away from
anywhere. The hard part on iOS is not the terminal — it is staying connected while
you look at something else, and that is the part we spent the time on.
dev@agent-box:~$ tail -f .agent/run-4471.log 14:02:11 run start task=migrate-auth model=opus 14:02:12 tool read src/auth/session.rs 14:02:19 tool edit src/auth/session.rs +34 -12 14:03:04 tool bash cargo check 14:03:22 warn 2 warnings, 0 errors 14:04:10 tool edit tests/session.rs +58 -0 14:06:41 tool bash cargo test --workspace 14:07:55 check ok 41 passed, 0 failed 14:07:56 run done 18 files · 6m 45s · $0.71 dev@agent-box:~$
No laptop, no tethering, no VPN client to wake up first. Face ID on the key, the host key checked against the one you pinned, and you are at a prompt on the box that is unhappy.
^, ⌥ and ⌘ under your thumb; ⌘ then 1–9 switches hosts with no hardware keyboard anywhere.ops@edge-03:~$ systemctl status caddy × caddy.service — Caddy web server Active: failed (Result: exit-code) since 02:14:06 Error: listen tcp :443: bind: address in use ops@edge-03:~$ ss -lptn 'sport = :443' State Local Address:Port Process LISTEN 0.0.0.0:443 caddy,pid=811,fd=9 ops@edge-03:~$ sudo kill 811 && systemctl start caddy ops@edge-03:~$ systemctl is-active caddy active ops@edge-03:~$
Keyboard first
Every screen — the host list, the settings, the import review, the key generator — is fully operable from a hardware keyboard. On an iPad in a case that means you never leave the home row. Without one, the on-screen modifier row reaches the same commands.
~/.sshThe row is one app-wide switch, and it survives a relaunch. Text fields keep their keyboard either way, so a password prompt still works with the row turned off.
Hands elsewhere
iOS dictation cannot reach a terminal. It revises what it heard by replacing ranges of text, and there are no ranges here — only bytes already on the wire. So slassh brought its own recogniser, and the difference is the point: it buffers, so nothing is on the wire until you have read it.
The MIC key, the button in the tab bar and ⌘D
are one switch, and all three light amber while the microphone is open. A banner across
the top shows the transcript being revised as it settles.
dev@builder:~/deploy$ git pull --rebase Successfully rebased and updated refs/heads/main. dev@builder:~/deploy$
For when reading a line before it runs is not an option — walking,
driving, mid-set at the gym. Press the mic once and it keeps listening until you say
hi slash mic off.
One of them would have to be “slash”, and a slash is what you dictate at a
terminal all day — every path, and Claude Code's own /clear and
/compact. A single-word wake would swallow them.
Matching is on sounds, not spellings, because sounds are what comes back. A device transcript of “hi slash mic off, hi slash clear” arrived as:
Hi Celeste, Michael hi Celeste clear high/clear hello/
So the tables list what the recogniser actually returns — “celeste”,
“michael”, “hello” — rather than what a dictionary would suggest. A path still cannot
fire a command: the phrase needs a greeting in front of the slash, and
cd /usr/local/bin has none.
Speech
recognition makes mistakes. It mishears words, names and punctuation, and the shorter
the phrase the likelier it is wrong — “mic off” has come back as “Michael”, “Mac off” and
“my golf” on real devices. That is the argument for buffering, and against a Return you did
not ask for. Dictation needs iOS 26; where the model cannot run, every microphone in the app
is absent rather than disabled — no button, no MIC key, no
⌘D, nothing in Settings. A control that cannot work is worse than
no control.
Setting up
Most SSH apps make you paste keys in one at a time. slassh reads the config off the machine that already has it — over SSH, the same way you would yourself.
A laptop running sshd, a bastion, anything. slassh looks in ~/.ssh,
then ~/.config/ssh, and tells you on the review screen which
one it actually read.
Hosts, the keys they reference, Include fragments expanded
in place. Anything that cannot come across is listed with the file and line it came
from — nothing is dropped quietly.
Private keys land in the Keychain, never in the Files app. Import the same config again later and it updates in place — hosts match on alias, keys on fingerprint.
The config parser is checked against
ssh -G itself, so what slassh resolves is what ssh resolves —
Match blocks, negated patterns, first-value-wins precedence and
%h/%p expansion included.
Privacy, itemised
Privacy-first is easy to put on a page. Here is the specific version, phrased so that you could go and verify it. The formal version is DopeCode's privacy policy.
.biometryCurrentSet, so enrolling a new face or finger locks that key
until you deliberately unlock it again. Generate ed25519 on the device and the private
half never exists anywhere else.ssh-rsa (SHA-1) is off the host-key algorithm list
deliberately, spelled out rather than filtered, so a library update cannot quietly add
one back.NSProcessInfo at all,
so there is no code path in it that could accept a host key on its own, take a password
out of the environment, or read a key off a filesystem path.russh) and the terminal emulator
(alacritty) are memory-safe Rust. The Swift side draws the cell grid
and encodes your keystrokes — that is the whole of its job. No web view, no JavaScript,
no bundled runtime.The honest part
Better you read this here than find it out after paying. slassh is one thing done properly, and the list of things it is not is short and deliberate.
iOS has no background mode that means “I am holding a TCP connection”, and no app can invent one. Leaving slassh buys about half a minute of running time, then the system freezes the process and a frozen process answers no keepalives.
So: coming back, every session that was live is re-checked against the server and redialled if it has gone. There is no switch that holds the app awake indefinitely — the only way to build one is to play silence in the background, and that is not what the audio mode is for. Anyone promising otherwise is describing a relay, not a terminal.
The durable answer is not on the phone at all: run tmux on the far end and the shell lives on the machine iOS is not freezing. A dropped link costs an attach, and the layout, the scrollback and the running job are where you left them.
Help
Tap Add a Host and type the address the way you would at a terminal — root@server.example.com or pi@192.168.1.20:2222 fills in the host, user and port. Choose Password or SSH Key, save, and tap the host. The first time, slassh shows the server's key fingerprint and asks whether to trust it; from then on it stops you if that key ever changes.
In the host, choose SSH Key and tap Generate a New Key. The key is made on the device and never leaves it. Then open it under SSH Keys — the key button on the Hosts screen — and tap Install on a Host: slassh logs in once with your password and adds the key to that server's ~/.ssh/authorized_keys.
A key you already have imports the same way: Import a Key, then paste it or pick the file. OpenSSH, PKCS#8 and PEM keys all work, with or without a passphrase.
Yes — + ▸ Import from ~/.ssh. The easy route reads it over SSH from a machine you can already reach: add your laptop as a host first (on a Mac, switch on Remote Login), then point the importer at it. You see every host and key before anything is saved.
“Refused the connection” means something answered, but not SSH on that port: check that SSH is running (on a Mac: System Settings ▸ General ▸ Sharing ▸ Remote Login) and that the port is right. “Didn't answer in time” means the device cannot reach the machine at all — usually a different network, or a VPN that is not on.
Check the user name first — it is the usual cause. slassh answers both ways servers ask for a password, including the keyboard-interactive prompt of FreeBSD and many PAM setups. It cannot answer a second factor such as a one-time code; use an SSH key there.
iOS gives every app about half a minute in the background, then pauses it, and a paused app cannot answer the server. Coming back, slassh checks each session and reconnects the ones that dropped. Run tmux on the server and a drop costs one tmux attach: the layout, the scrollback and the running job are all still there.
On the old one, deliberately: keys are kept out of backups and cannot be restored onto another device. Generate a new key on the new phone and install it on your servers. One key per device means a lost phone costs one line in authorized_keys, not a new key everywhere.
Dictation runs entirely on the device, so it needs iOS 26 and hardware that can run Apple's on-device speech model. Where that is missing, slassh leaves the microphone out rather than showing a button that cannot work.
Still stuck? Write to hello@dopecode.com with your slassh version (Settings ▸ About) and what the error said. Never send keys or passwords — we will never ask for them.
iPhone & iPad · App Store
slassh is a one-time purchase. No subscription, no in-app purchases, no tier that unlocks the feature you actually needed. It has nothing to sell you afterwards, which is the point.
· Questions first? Help answers the common ones.