The story behind Jeansh, an Android SSH client for coding and supervising AI agents from a phone or tablet.
A few months ago, I spent weeks shuttling between home and the hospital. Through all of it, I kept trying to keep an eye on my work, because that's my responsibility as a WFA (work from anywhere) engineer. Lately, most of that work is done by AI coding agents like Claude Code: I start the task, the agent does it, and then I check the result.
So I made a simple decision: I left my work laptop at home. The internet at home is stable, so an agent in the middle of a task wouldn't drop off while I was on the road. All I needed to carry was one device to keep an eye on my work from afar.
At first, the device I carried was a MacBook Air. It turned out that, for days like those, a laptop is too big and impractical. Every time a nurse called me, I had to close it, then open it again when I got back to my seat. That's if I got a seat at all: sometimes I did, sometimes I didn't, and without a seat, opening a laptop properly is really hard. A phone or an 8-inch tablet is far more practical: it fits in a smaller bag and works right away without a table or a chair, so while I waited I could still read what the AI had done.
In the end, all I carried were my phone and my tablet, and from either one I got into the laptop at home over SSH.
On paper, the plan was perfect. In practice, it wasn't.
What made me give up
I used Termius. For most people, Termius is a good SSH client, and I still think so. But my days back then weren't the days its makers had in mind.
The connection had to stay alive. To know how the agent was doing, the SSH session had to stay connected in the background, and it drained my phone's battery. Yet that phone couldn't die: I used it to pay for things and to book rides home from the hospital.
I kept checking. There was no way for the server to say "the work is done." So in the waiting room, I opened the app again and again just to see whether the agent had stopped.
Reading the agent's output was torture. Agents leave plans, READMEs and notes in Markdown, often complete with Mermaid diagrams. In a terminal, all of that is just raw text.
The small things piled up. Finding and checking the files the agent had worked on was hard: there was no file explorer with a tree view, and to see what was in them I had to open them one by one in the terminal, with vim, which is a mess on a touch screen. On top of that, tmux wasn't integrated natively, and I couldn't see what git changes the agent had made. Now and then I also needed to do some ops work, like running a query or a command against PostgreSQL, MongoDB or Redis.
What I needed was actually simple: an app I could close completely, that picked up exactly where I left off when I opened it again, and that told me when the agent was done.
Has anyone already solved this?
Before writing a single line of code, I checked. Building your own tool is expensive, and if a good one already exists, I'd rather use it.
SSH clients for the phone such as Termius, JuiceSSH and ConnectBot can open a terminal to a server. Termux is something else: it's a Linux terminal that runs on the phone itself. They all start from the same idea, the phone as a small terminal that you open and use. Their SFTP features are built for moving files, not for reading them, and as far as I know, none of them renders Markdown, let alone Mermaid.
Companion apps for AI coding agents are starting to appear too. But the ones I came across usually run the agent in a cloud sandbox, or ask you to install extra software next to the agent on your machine. I wanted the agent to stay on my own laptop, with my code, my environment and my credentials, and to reach it over the SSH connection I already trust.
What I couldn't find was the combination: an app you can close without worrying, an agent that keeps running in tmux, a server that tells you when it's done, and a comfortable way to read what the agent produced, all in one app built for a touch screen.
So I built it myself. It's called Jeansh, terminal buddy in your pocket.
Jeansh: the phone as a remote control and a pager
The core idea is simple: on a phone, the work doesn't happen on the phone. It happens on a machine that stays on, and the phone is just its remote control and its pager.
- Close it any time. The agent keeps running in tmux on your machine. When you open the app again, your tabs come back and the tmux panes are still alive.
- The server tells you. One curl at the end of a job, and the notification lands on your phone. Tap it, and you're back on that host.
- Read what the agent wrote. A VS Code-style file tree, an editor with syntax highlighting, and Markdown rendered complete with images and Mermaid diagrams, in a tab beside the shell.
- Claude Code as a chat. Chat mode reads the Claude Code sessions running on the host and follows them live, in sync with the terminal. Want to show it a bug? Paste a screenshot into the terminal, and Jeansh uploads it and types its path for Claude.
- Databases over SSH. Run queries or commands against PostgreSQL, MongoDB and Redis over the same connection, edit rows right in the grid, and save them all at once.
How I built it
The foundation
Jeansh is built with Flutter. Its SSH connection uses dartssh2, the terminal is xterm2, and the editor is re_editor. Passwords and private keys are stored encrypted with the Android Keystore, and every new host key is shown to you before it's trusted.
For tmux, Jeansh doesn't just show tmux inside a terminal. Every tab is a tmux session driven through control mode, so split panes become real panes you can touch, and a dropped connection comes back to the same pane.
Notifications with two paths
The most fun part to build was notifications.
The direct path. When it connects, Jeansh opens a port on the server's loopback that is forwarded back through the SSH connection itself, like ssh -R. On the phone's end sits a small HTTP handler. The server just runs curl against that port, and the notification arrives over the connection that's already there, without going through any other server.
The relay path, for when the app is closed. The server sends to a relay on Cloudflare Workers, which passes it on through Firebase Cloud Messaging. So that a notification can't be sent to the wrong phone, every host gets its own ECDSA P-256 key pair, and every request is signed, much like SNAP, the signing standard that payment APIs in Indonesia use. The relay checks the signature, the time window and replays before it sends anything.
Chat mode for Claude Code
Chat mode doesn't scrape text off the terminal screen. Jeansh runs claude -p with stream-json input and output on one exec channel over the same SSH connection, the same entry point the VS Code extension uses. The list of running sessions comes from claude agents --json.
Built with several AI agents
I built Jeansh the same way Jeansh helps you work: by running several Claude Code sessions at once. One session is the coordinator that holds my tablet, while other sessions build the features, each in its own git worktree.
There's one rule: a feature isn't done when it's merged, but when I've tried it myself on the tablet and said OK. So far there have been 638 commits and 132 features and fixes tracked. Every push to main that changes the app is released automatically to closed testing on Google Play.
The two problems that stuck with me most
Not everything went smoothly. Two problems stuck with me the most during development.
The big download that froze the app. One day I downloaded an Android build file of about 70 MB from my laptop to my tablet, and the app froze until Android offered to close it for not responding.
The cause turned out to be in how SSH works. Every byte that goes over SSH is encrypted, so the app has to decrypt it before it can save it. Initially, that work ran on the UI thread. While the app was busy decrypting that much data, the screen never got a chance to re-render, and the app hung.
So why was the decryption so heavy? The default encryption method was AES-GCM. On a laptop, AES-GCM is very fast because the processor has dedicated instructions for AES. But in Jeansh, AES-GCM ran as plain Dart code without the help of those instructions, and that was the root cause.
SSH actually supports several encryption methods, so I measured the others. It turned out chacha20-poly1305 was about 30 times faster. It was designed to run fast as plain code: it's made of simple operations like addition, bit rotation and XOR, so it doesn't need any special instructions from the processor.
The fix was simple. When connecting, an SSH client lists the encryption methods in the order it prefers, and the server picks the first one it also supports. Jeansh now puts chacha20-poly1305 first and AES-GCM last. After that, I moved the SSH work, decryption included, onto a separate thread, so however heavy the transfer, the UI thread stays free to render the screen. As a result, downloading large files no longer freezes the app. The lesson: in a mobile app, anything heavy on the UI thread feels like a broken app.
The notification that suddenly didn't arrive. The second problem came from the feature that mattered most to me. One day, the curl that usually sent a notification to my phone suddenly failed, and all it said was "Could not connect to server".
The problem lay where two things met. First, the direct notification uses a port opened just for one SSH connection, and its address is stored in a shell environment variable. Second, every tmux pane keeps a copy of the environment from when it was created. So when the connection dropped and I reconnected, the new connection got a new port, but the old pane still held the address of a port that had died with the old connection. As a result, curl from that pane tried to connect to a port that no longer existed, and failed.
The fix came in two layers. Every time it reconnects, Jeansh updates the tmux environment, so every new pane gets the right address straight away. For panes that were already running, notifications don't rely on the direct path alone: they can also go through the relay, signed with the host's own key. That key doesn't change when you reconnect, so the relay path keeps working. That's why the way of sending notifications I recommend on the website always tries the direct path first, then falls back to the relay if it fails.
The lesson: anything tied to a connection dies with it, environment variables included. So keep a fallback path that isn't tied to that connection.
Want to try it?
Jeansh is now in closed beta on Google Play, free and with no ads. You can find the full feature list, including a guide to sending notifications from your server with curl, at jeansh.brata.cloud.
If you often remote into servers from your phone or tablet, or run AI agents you have to keep an eye on from afar, I'd love to hear your story: what still gets in the way, and what you'd need to be able to leave your laptop at home.
Publishing notes (not part of the article)
- Title (chosen): "Why I Left Termius and Built My Own Android SSH Client". "Android SSH client" and "Termius" are both searched for often. The backup title is "This Is Why I Finally Built My Own Coding Tool and Left Termius".
- Medium subtitle: "The story behind Jeansh, an Android SSH client for coding and supervising AI agents from a phone or tablet."
- Tags (5 max): Android, SSH, Productivity, Artificial Intelligence, Software Development.
-
Images are in the article as
markers. When copying it into Medium, replace each marker by uploading its image. They are all in~/jeansh-promo/share/medium/images/, numbered in the order they appear:-
01-cover.png: the article's cover image. -
02-jeansh-features-en.gif: under the heading "Jeansh: the phone as a remote control and a pager", before the feature list. This is the English story, unlike the Indonesian article's02-fitur-jeansh.gif. -
03a-markdown-mermaid.png: under "Read what the agent wrote". -
03b-database-grid.png: under "Databases over SSH". -
04-notifikasi-tests-passed.png: in "Notifications with two paths", right after the "The direct path" paragraph. It is a frame from the demo video showing the curl command, the{"ok":true}reply and the "Tests passed" notification banner on one screen. Suggested caption: "One curl from the server, and the notification shows up on the tablet right away."
-
- Canonical: this post also goes up on the Jeansh blog (jeansh.brata.cloud/blog), so the Medium copy is a republish: in Medium's story settings, set the canonical link to the blog post's URL. The Indonesian version is a translation, not a duplicate, and needs no canonical of its own.
- Links: only one call to action, to the website, at the end, so it doesn't read like a sales pitch.





Top comments (0)