Skip to content

connorads/remobi

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

228 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

remobi logo

remobi

CI npm licence

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.

/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)
remobi.mp4

Requirements

Manual setup

# 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 for responsive status bars, popup sizing, and more.

For local development, see the 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. 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:

/bin/bash -c "$(curl -fsSL http://remobi.app/install.sh)"

Option 2: Install the skill β€” if you prefer to start the conversation yourself:

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.

CLI reference

remobi serve [--config <path>] [--port <n>] [--host <addr>] [--base-path <path>] [-- <command...>]
  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 <path>] [--output <path>] [--dry-run]
  Deprecated. remobi no longer patches ttyd HTML.

remobi inject [--config <path>] [--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):

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:

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

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:

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

Architecture docs

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

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):

tsx cli.ts serve              # localhost:7681, default tmux session

Or build first, then run from dist/:

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

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 (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 / 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. It helped remobi launch quickly, prove the workflow, and shape the product before the runtime moved in-house.

Licence

MIT