https://github.com/connorads/remobi
📱 Your terminal. Everywhere.
https://github.com/connorads/remobi
mobile node pwa terminal tmux touch ttyd typescript
Last synced: 3 months ago
JSON representation
📱 Your terminal. Everywhere.
- Host: GitHub
- URL: https://github.com/connorads/remobi
- Owner: connorads
- License: mit
- Created: 2026-02-18T16:28:33.000Z (4 months ago)
- Default Branch: main
- Last Pushed: 2026-04-01T20:41:06.000Z (3 months ago)
- Last Synced: 2026-04-02T08:06:10.763Z (3 months ago)
- Topics: mobile, node, pwa, terminal, tmux, touch, ttyd, typescript
- Language: TypeScript
- Homepage: https://remobi.app
- Size: 943 KB
- Stars: 15
- Watchers: 1
- Forks: 1
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
- Security: SECURITY.md
- Agents: AGENTS.md
Awesome Lists containing this project
README
# remobi
[](https://github.com/connorads/remobi/actions/workflows/ci.yml)
[](https://www.npmjs.com/package/remobi)
[](LICENSE)
**Your terminal. Everywhere.**
Your tmux session, on your phone. Same panes, same windows, same bindings — nothing changes on your computer. Swipe between windows, pinch to zoom, tap to send commands. You just get a remote control.
It's a terminal on a 6-inch screen. It won't win design awards. But you can do *everything* — monitor coding agents, intervene when they're stuck, scroll through output, switch contexts. Full power.
```bash
/bin/bash -c "$(curl -fsSL http://remobi.app/install.sh)"
```
To upgrade stable: `npm install -g remobi@latest`
To try the experimental channel: `npm install -g remobi@dev`
Your coding agent handles the rest. It installs remobi, inspects your tmux config, generates a config, and suggests tweaks to make your tmux more mobile-friendly — one conversation. Works with Claude Code and Codex.
## Why remobi
- **Zero workflow changes** — your existing tmux setup, untouched
- **Swipe between windows** — gesture navigation, no prefix key fumbling on a phone screen
- **Pinch to zoom** — resize text like every other app on your phone
- **Install to your home screen** — standalone PWA, looks and feels native
- **Config-driven** — your buttons, your gestures, your layout
- **Self-hosted** — local-first by default. Bring your own access layer (Tailscale, Cloudflare, ngrok)
## Requirements
- [Node.js](https://nodejs.org/) ≥ 22
- [tmux](https://github.com/tmux/tmux) (the target multiplexer)
## Manual setup
```bash
# 1. Install
npm install -g remobi
# 2. Enable mouse mode in tmux (required for touch scroll and tap-to-focus)
# Add to ~/.config/tmux/tmux.conf or ~/.tmux.conf:
# set -g mouse on
# 3. Start (spawns your command, serves remobi on 127.0.0.1:7681)
remobi serve
```
**Essential tmux settings for mobile** — add to your `tmux.conf` if not already present:
| Setting | Command | Why |
|---------|---------|-----|
| Mouse mode | `set -g mouse on` | Enables touch scroll, tap-to-focus, drag resize. **The single highest-value setting for mobile use.** |
| Status position | `set -g status-position top` | Keeps status bar away from remobi's touch toolbar at the bottom |
| Renumber windows | `set -g renumber-windows on` | Keeps window list tidy after closing windows |
See [Mobile-friendly tmux config](.agents/skills/remobi-setup/references/mobile-tmux.md) for responsive status bars, popup sizing, and more.
For local development, see the [Development](#development) section below.
Open `http://localhost:7681` on the same machine to verify it works. For phone access, put a trusted proxy/tunnel in front of it, for example [Tailscale Serve](.agents/skills/remobi-setup/references/tailscale-serve.md). If your proxy mounts remobi under a URL prefix, start remobi with `--base-path /that-prefix` so the HTML, PWA links, and WebSocket all use the same external path.
## Release channels
- `main` publishes stable releases to npm `latest`
- `dev` publishes prereleases to npm `dev`
- merge `dev` into `main` to promote an experimental line to stable
If an experimental change is breaking for consumers, include a `BREAKING CHANGE:` footer so semantic-release computes the right next version on both channels. `!` in the header is optional shorthand only; on its own it does not trigger a major release in this repo.
## Set up with AI
The setup skill checks your environment, inspects your tmux config, generates a `remobi.config.ts`, and suggests tmux mobile optimisations — one conversation. Three ways to use it, from simplest to most manual:
**Option 1: One-liner** — installs the skill and launches an interactive session with your coding agent:
```bash
/bin/bash -c "$(curl -fsSL http://remobi.app/install.sh)"
```
**Option 2: Install the skill** — if you prefer to start the conversation yourself:
```bash
npx skills add connorads/remobi
```
Then tell your coding agent: "Use the remobi-setup skill to onboard me."
**Option 3: Already in a session?** — if an agent is already looking at this repo (or you don't want to install a skill), tell it:
> Read `.agents/skills/remobi-setup/SKILL.md` in this repo and follow it to onboard me.
The skill file contains the full setup workflow. The agent can read it directly — no installation needed.
## Security model
`remobi` is a remote-control surface for your terminal. Anyone who can reach it can drive the tmux session with your user privileges.
- `remobi serve` binds to `127.0.0.1` by default.
- The inner PTY-backed terminal session stays local to the remobi process.
- There is no built-in login, password, or ACL in remobi itself.
- Safe default: keep it on localhost and publish it through a trusted layer like Tailscale Serve.
- If you use `remobi serve --host 0.0.0.0`, you are exposing terminal control to your LAN/whatever can route to that port. Do that only if you intentionally want direct network exposure and have separate network controls in place.
To report a vulnerability, see [SECURITY.md](SECURITY.md).
## CLI reference
```text
remobi serve [--config ] [--port ] [--host ] [--base-path ] [-- ]
Start remobi with its built-in web terminal and PWA support.
Default host: 127.0.0.1. Default port: 7681. Default command: tmux new-session -A -s main
Example: remobi serve --host 0.0.0.0 --port 8080
Example: remobi serve --base-path /random-token
Example: remobi serve --port 8080 -- tmux new -As dev
remobi build [--config ] [--output ] [--dry-run]
Deprecated. remobi no longer patches ttyd HTML.
remobi inject [--config ] [--dry-run]
Deprecated. remobi no longer patches ttyd HTML.
remobi init
Scaffold a remobi.config.ts with commented defaults.
remobi --version
remobi --help
```
Short flags: `-c` (`--config`), `-p` (`--port`). Legacy deprecated flags: `-o` (`--output`), `-n` (`--dry-run`).
### Config resolution
When `--config` is not specified, remobi searches:
1. `remobi.config.ts` / `.js` in the current directory
2. `~/.config/remobi/remobi.config.ts` / `.js` (XDG fallback)
## Configuration
Create `remobi.config.ts` (or run `remobi init`):
```typescript
export default {
font: {
family: 'JetBrainsMono NFM, monospace',
mobileSizeDefault: 16,
sizeRange: [8, 32],
},
toolbar: {
row1: [
{ id: 'esc', label: 'Esc', description: 'Send Escape key', action: { type: 'send', data: '\x1b' } },
{ id: 'tmux-prefix', label: 'Prefix', description: 'Send tmux prefix key (Ctrl-B)', action: { type: 'send', data: '\x02' } },
// ...
],
row2: [
{ id: 'alt-enter', label: 'M-↵', description: 'Send Alt+Enter (ESC + Enter)', action: { type: 'send', data: '\x1b\r' } },
{ id: 'drawer-toggle', label: '☰ More', description: 'Open command drawer', action: { type: 'drawer-toggle' } },
{ id: 'paste', label: 'Paste', description: 'Paste from clipboard', action: { type: 'paste' } },
{ id: 'backspace', label: '⌫', description: 'Send Backspace key', action: { type: 'send', data: '\x7f' } },
// ...
],
},
drawer: {
buttons: [
{ id: 'tmux-new-window', label: '+ Win', description: 'Create tmux window', action: { type: 'send', data: '\x02c' } },
{ id: 'tmux-split-vertical', label: 'Split |', description: 'Split pane vertically', action: { type: 'send', data: '\x02%' } },
{ id: 'combo-picker', label: 'Combo', description: 'Open combo sender (Ctrl/Alt + key)', action: { type: 'combo-picker' } },
// ...
],
},
gestures: {
swipe: {
enabled: true,
left: '\x02n', // data sent on swipe left (default: next tmux window)
right: '\x02p', // data sent on swipe right (default: prev tmux window)
leftLabel: 'Next tmux window', // shown in help overlay
rightLabel: 'Previous tmux window',
},
scroll: {
enabled: true,
strategy: 'wheel',
sensitivity: 40,
wheelIntervalMs: 24,
},
pinch: { enabled: true },
},
mobile: {
initData: '\x02z', // send on mobile load when viewport < widthThreshold
widthThreshold: 768, // px — default matches common phone/tablet breakpoint
},
floatingButtons: [
{
position: 'top-left',
buttons: [
{ id: 'zoom', label: 'Zoom', description: 'Toggle pane zoom', action: { type: 'send', data: '\x02z' } },
],
},
],
}
```
All fields are optional — the CLI fills in defaults internally when it loads the config.
Shipped tmux drawer defaults stick to stock tmux bindings (`c`, `%`, `"`, `s`, `w`, `[`, `?`, `x`, `z`) rather than personal popup workflows.
Replace the drawer entirely with a plain array when you want a fully custom setup:
```typescript
import { defineConfig } from 'remobi/config'
export default defineConfig({
drawer: {
buttons: [
{ id: 'sessions', label: 'Sessions', description: 'Choose tmux session', action: { type: 'send', data: '\x02s' } },
{ id: 'git', label: 'Git', description: 'Open my tmux git popup', action: { type: 'send', data: '\x02g' } },
],
},
})
```
At runtime, remobi validates the config object shape and rejects unknown keys with clear path-based errors.
`gestures.scroll.strategy` controls touch scroll behaviour:
- `wheel` (default): sends SGR mouse wheel events with touch-mapped terminal coordinates.
- `keys`: sends `PageUp` / `PageDown` for app-level paging when preferred.
### Programmatic API
```typescript
import { defineConfig, serialiseThemeForTtyd } from 'remobi/config'
import type { RemobiConfig, ControlButton } from 'remobi/types'
import { init } from 'remobi'
```
Advanced consumers can use hook registry primitives to observe lifecycle and terminal-send events:
```typescript
import { createHookRegistry, init } from 'remobi'
const hooks = createHookRegistry()
hooks.on('beforeSendData', (ctx) => {
if (ctx.data.includes('rm -rf /')) return { block: true }
})
init(undefined, hooks)
```
## Guides
- [Mobile-friendly tmux config](.agents/skills/remobi-setup/references/mobile-tmux.md) — responsive status bar, popup sizing, binding ergonomics
- [Mobile pane navigation](.agents/skills/remobi-setup/references/mobile-panes.md) — zoom-aware swipe, auto-zoom on load, floating buttons
- [Tailscale Serve](.agents/skills/remobi-setup/references/tailscale-serve.md) — expose over your tailnet with HTTPS
- [Keeping your Mac awake](.agents/skills/remobi-setup/references/keep-awake.md) — prevent sleep during remote sessions
- [ttyd flags](.agents/skills/remobi-setup/references/ttyd-flags.md) — legacy notes for old ttyd-based setups
## Architecture docs
- [How remobi works](docs/architecture/how-remobi-works.md) — runtime overview, shared session model, and boot path
- [Networking and WebSocket flow](docs/architecture/networking-and-websockets.md) — request lifecycle, protocol, and network boundary
## Architecture
Pure TypeScript + DOM API — no framework. The build bundles the browser client via esbuild, serves it from Node, and bridges browser input/output to a local PTY via `node-pty`. `xterm.js` handles terminal rendering in the browser; remobi layers the mobile controls on top. The docs above walk through the current runtime in more detail, including diagrams for the server, browser, and WebSocket flow.
Key modules:
| Module | Purpose |
|--------|---------|
| `src/toolbar/` | Two-row touch toolbar |
| `src/drawer/` | Command drawer with grid layout |
| `src/gestures/` | Swipe, pinch, scroll detection |
| `src/controls/` | Font size, help overlay, scroll buttons |
| `src/theme/` | Catppuccin Mocha + theme application |
| `src/viewport/` | Height management, landscape detection |
| `src/util/` | DOM helpers, terminal, keyboard, haptics |
## Public API and semver
remobi follows semantic versioning. The public API is defined by the following import paths:
| Import path | Contents | Stability |
|---|---|---|
| `remobi` | `init`, `defineConfig`, `createHookRegistry`, `RemobiConfig`, `ControlButton`, `ButtonAction`, `ButtonArrayPatch`, `ButtonArrayInput`, `RemobiConfigOverrides`, `HookRegistry` | Public — breaking changes are semver-major |
| `remobi/config` | `defineConfig`, `mergeConfig`, `defaultConfig`, `serialiseThemeForTtyd` | Public |
| `remobi/types` | All types in `src/types.ts` | Public |
**Internal modules** (not part of the public API — may change without a major version bump):
`src/toolbar/`, `src/drawer/`, `src/gestures/`, `src/controls/`, `src/theme/`, `src/viewport/`, `src/util/`, `src/serve.ts`, `src/cli/`, `build.ts`
**Semver policy**:
- **Major**: removing or renaming a public export, changing a public function signature incompatibly, removing a config field
- **Minor**: adding new public exports, new optional config fields, new `ButtonArrayInput` operations
- **Patch**: bug fixes, internal refactors, documentation updates
## Development
```bash
git clone https://github.com/connorads/remobi.git && cd remobi
pnpm install
git config core.hooksPath .hk-hooks # enable commit hooks (conventional commits, biome)
```
### Running locally
From source (bundles the browser client on the fly via esbuild — no build step needed):
```bash
tsx cli.ts serve # localhost:7681, default tmux session
```
Or build first, then run from dist/:
```bash
pnpm run build:dist # transpile TS → JS + bundle browser client
node dist/cli.mjs serve # run locally-built version on localhost:7681
```
No watch mode — re-run the build or use `tsx` for automatic source bundling.
### Checks
```bash
pnpm test # vitest (unit + integration)
pnpm run test:pw # playwright e2e (needs: pnpm exec playwright install chromium webkit --with-deps)
pnpm run check # biome lint + format
```
## FAQ
**Is this secure?**
remobi doesn't handle auth — it's a UI overlay. Use a tunnel or VPN you trust. We recommend [Tailscale](.agents/skills/remobi-setup/references/tailscale-serve.md) (deployment guide included) — your session never leaves your tailnet. Cloudflare Tunnel and ngrok also work. Security is your responsibility.
**Why not Termux / Termius / SSH apps?**
They work. But you're managing SSH keys, losing your tmux setup, and fighting a UI that wasn't built for touch. remobi keeps your exact workflow — same panes, same windows, same bindings — and adds touch controls on top.
**Why not [Happy](https://github.com/slopus/happy) / Claude resume / chat-based mobile apps?**
Those tools change your workflow. Chat relays route through third-party servers. Claude's resume has limitations. remobi gives you the raw terminal — full power, self-hosted, works with every agent because it works with tmux.
**Why Node?**
remobi migrated from Bun to Node.js + pnpm for broader compatibility. It transpiles to JS via tsdown for npm distribution and uses esbuild for the browser client bundle.
**Is this production-ready?**
It's v0.1. The author uses it daily. It works. It's also early — feedback welcome, forks encouraged.
## Acknowledgements
remobi owns the full web terminal path now: a local PTY on the server, `xterm.js` in the browser, and the mobile touch controls on top.
Earlier versions were built on top of [ttyd](https://github.com/tsl0922/ttyd). It helped remobi launch quickly, prove the workflow, and shape the product before the runtime moved in-house.
## Licence
MIT