Skip to content

schenkei-code/nexa-term

NexaTerm

A native SSH + Mosh client for iOS, iPadOS and Android.
First-class rendering for claude --output-format stream-json, tmux-aware, Tailscale-friendly.

Stars Forks License iOS build Android build


Table of Contents


Why NexaTerm?

Note

Blink Shell is excellent. Termius has cloud sync. Prompt is polished. But none of them are AI-CLI-first.

NexaTerm is built for the era where your real dev environment is a VPS running tmux + claude + gemini + aider — and your phone is the pane of glass. It is:

  • Native SwiftUI + Jetpack Compose — not a WebView wrapper, not Electron, not a React-Native shim. Liquid Glass on iOS 26, Material 3 Expressive on Android 16.
  • Stream-JSON aware — it parses the structured output from modern agent CLIs and renders tool calls, todos, diffs, thinking traces and usage as native cards over the terminal surface.
  • Mosh-resilient — your session survives subway tunnels, airplane mode and IP changes. tmux on the VPS, Mosh on the wire, smooth on the screen.
  • Tailscale-friendly — point it at a tailnet host, authenticate with your tailnet identity, never expose SSH to the public internet.
  • Self-hostable backend — an optional FastAPI bridge gives you per-user isolated Docker containers with tmux, claude, gemini, codex pre-installed. BYO API keys, zero vendor lock-in.

Features

Terminal core

  • SwiftTerm (iOS) / Termux-view fork (Android)
  • 24-bit color, UTF-8, wide chars, ligatures
  • Hardware keyboard first — Caps Lock as Esc, tmux chord handling, arrow-key-in-Vim sanity
  • Per-host profile: font, theme, scroll-back, paste-bracket
  • Split panes, multi-tab, tmux passthrough

Agent rendering

  • Line-delimited JSON parser for claude --output-format stream-json
  • Native cards for tool_use, tool_result, thinking, assistant, system, result
  • Auto-detects when an agent CLI is running — no flag to remember
  • Slash-command autocomplete for known CLIs
  • Usage/cost ticker, model badge, session id

Connectivity

  • SSH via libssh2 / Citadel (pure-Swift) / Android JSch fallback
  • Mosh (iOS: vendored fork, Android: termux-mosh)
  • ED25519 keys stored in Secure Enclave / Android Keystore
  • Known-hosts pinning, agent forwarding, jump-host chains
  • Tailnet-first: browse your tailnet, not the public DNS

Self-hosting

  • FastAPI backend spins a Docker container per user
  • tmux, claude, gemini, codex, aider pre-baked
  • WebSocket stream (xterm.js compatible) — works with any client
  • Fly.io / Hostinger deploy manifests included
  • BYO API keys, keys never touch the backend

Demo

Tip

Short looping demos (GIF / MP4) live in docs/assets/. Swap these placeholders once you record the first sessions.

iOS demo iPad demo Android demo

What you are looking at
  1. SSH into a tailnet VPS, attach to a persistent tmux session.
  2. Kick off claude --output-format stream-json inside tmux.
  3. Watch the terminal pane scroll raw text while an overlay of native cards renders every tool call, todo update and diff.
  4. Detach, lock the phone, come back 20 minutes later — Mosh reconnects, tmux state is right where you left it.

Architecture

 ┌──────────────────────────────┐          ┌──────────────────────────────┐
 │  iOS / iPadOS / Android app  │  SSH /   │  Your VPS (or your Mac)      │
 │                              │  Mosh    │                              │
 │  SwiftUI / Compose shell     │◄────────►│  sshd                        │
 │  SwiftTerm / termux-view     │          │   └─ tmux                    │
 │  StreamJSON overlay          │          │        └─ claude, gemini,    │
 │  Tailscale, Secure Enclave   │          │           codex, aider, zsh  │
 └───────────────┬──────────────┘          └──────────────────────────────┘
                 │
                 │  (optional) WebSocket
                 ▼
 ┌──────────────────────────────┐
 │  Self-hosted bridge          │
 │  FastAPI + Docker per user   │
 │  xterm.js-compatible stream  │
 └──────────────────────────────┘

See docs/concept-2026-04-18.md for the full design doc, docs/backend-architecture.md for the bridge, and docs/decisions.md for ADRs.


Quick Start

git clone https://github.com/schenkei-code/nexa-term.git
cd nexa-term

# Shared TypeScript types (parser schemas, host-config shape)
npm install
npm run shared:build

Pick the target you care about and jump to its setup section below.


iOS Setup

Requirements:

  • macOS 15+ with Xcode 16.3+
  • Tuist 4.x (the project uses Tuist manifests)
  • An Apple Developer account if you want to run on-device
cd apps/ios
tuist install          # resolve SwiftTerm + Citadel via SwiftPM
tuist generate         # writes MobileTerminal.xcodeproj
open MobileTerminal.xcworkspace

Hit ⌘R. Simulator defaults to iPhone 17 Pro on iOS 26.

Manual Xcode path (no Tuist)

Create a fresh "App" project in Xcode, drop the contents of apps/ios/MobileTerminal/ in, and add these SwiftPM dependencies:

  • https://github.com/migueldeicaza/SwiftTerm
  • https://github.com/orlandos-nl/Citadel

Run tests: tuist test or ⌘U in Xcode.


Android Setup

Requirements:

  • JDK 21
  • Android Studio Narwhal+ with Android SDK 36 (Android 16)
  • An Android device or emulator on API 34+
cd apps/android
./gradlew :app:assembleDebug
./gradlew :app:installDebug   # deploys to the first connected device

See apps/android/ARCHITECTURE.md for module boundaries and apps/android/KEYBINDINGS.md for the keymap.


Backend / Self-Hosting

The backend is optional. You only need it if you want the per-user Docker container model (for example, to hand accounts out to teammates or run a hosted service).

cd backend
cp .env.example .env
docker compose up --build

Default port: 8080. WebSocket endpoint is xterm.js-compatible so any browser terminal can also attach.

Deploy recipes:

API keys (Anthropic, OpenAI, etc.) are never stored by the backend. Users paste them into their own container volume after first login.


Comparison

Feature NexaTerm Blink Shell Termius Prompt 3 Shell Fish
Native SwiftUI / Compose (no WebView) Yes Partial No Yes Yes
Open source MIT GPL-3 No No No
Mosh Yes Yes No No No
stream-json agent rendering Yes No No No No
Agent-CLI switcher (claude/gemini/...) Yes No No No No
Tailnet-native host picker Yes Manual Manual Manual Manual
Self-hosted backend Yes No Cloud No No
Liquid Glass (iOS 26) Yes No No Partial No
Material 3 Expressive (Android 16) Yes Yes

Tech Stack

  • iOS / iPadOS — SwiftUI, SwiftTerm, Citadel (pure-Swift SSH), Secure Enclave
  • Android — Jetpack Compose, Material 3, termux-view fork, Android Keystore
  • Backend — FastAPI + Docker-in-Docker, per-user volumes, WebSocket bridge
  • Shared — TypeScript types for the stream-json schema, host-config shape, SSH-config shape

Roadmap

  • v0.1 — MVP (Weeks 1–8) · iOS SwiftUI + SwiftTerm + SSH + slash-command autocomplete
  • v1.0 — Multi-platform (Months 3–4) · Mosh, multi-tab, Android GA, stream-json overlay on both platforms
  • v2.0 — Swarm (Month 6+) · Agent dashboard across all your hosts, one-tap CLI switcher, Web PWA, watchOS companion

Track progress in CHANGELOG.md and the GitHub Projects board.


FAQ

Why not just use an existing SSH client?

Existing mobile SSH clients treat agent CLI output as a wall of text. NexaTerm parses the structured stream and renders it as UI — so a 400-line diff from an agent becomes a collapsible diff card, a long tool-use trace becomes a tappable card stack, and token/cost data shows up in the status bar. It is still a full SSH client underneath.

Do I have to be on iOS 26 / Android 16?

Older versions are on the roadmap. The MVP targets the newest OS because Liquid Glass (iOS 26) and Material 3 Expressive (Android 16) are the design systems this project ships with. A compat layer for iOS 17+ is planned for v1.1.

Is my private key safe?

Keys never leave the device. iOS stores them in Secure Enclave, Android in the hardware-backed Keystore. The optional self-hosted backend never sees your keys or your API tokens.

Do you phone home?

No analytics, no telemetry, no crash reporting to a third party. Period. If you run the self-hosted bridge, you control the logs.

Can I use it without any AI CLI?

Yes. It is a great SSH + Mosh client even if you never install claude or gemini. The stream-json overlay only activates when one of those CLIs is detected.

Commercial use?

MIT licensed. Fork it, rebrand it, ship it. If you use it commercially a credit in your about screen is appreciated but not required.


Contributing

PRs welcome. Please read CONTRIBUTING.md and CODE_OF_CONDUCT.md first.


License

MIT © Dominik Schenkel.


Built for the dev who lives in tmux and commutes on the U-Bahn.

About

Native SSH + Mosh client for iOS, iPadOS and Android with first-class AI-CLI stream-json rendering

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors