MCPcopy Index your code
hub / github.com/connorads/remobi

github.com/connorads/remobi @v1.0.4

Chat with this repo
repository ↗ · DeepWiki ↗ · release v1.0.4 ↗ · + Follow
410 symbols 1,039 edges 93 files 26 documented · 6%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

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)

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

```bash pnpm test

Extension points exported contracts — how you extend this code

CtrlState (Interface)
Ctrl sticky modifier state
src/toolbar/toolbar.ts
ScrollState (Interface)
Mutable scroll gesture state
src/gestures/scroll.ts
LoadedConfig (Interface)
(no doc)
cli.ts
StartupResizeSchedulerOptions (Interface)
(no doc)
src/startup-resize.ts
InputMessage (Interface)
(no doc)
src/session-protocol.ts
ValidationIssue (Interface)
(no doc)
src/config-validate.ts
ReconnectOverlay (Interface)
(no doc)
src/reconnect.ts
SessionStatusOverlay (Interface)
(no doc)
src/client-entry.ts

Core symbols most depended-on inside this repo

mockTerminal
called by 72
tests/fixtures.ts
el
called by 49
src/util/dom.ts
parseCliArgs
called by 46
src/cli/args.ts
close
called by 29
src/session.ts
defineConfig
called by 25
src/config.ts
onTap
called by 23
src/util/tap.ts
createDefaultActionRegistry
called by 22
src/actions/registry.ts
assertValidConfigOverrides
called by 20
src/config-validate.ts

Shape

Function 326
Interface 61
Method 19
Class 4

Languages

TypeScript100%

Modules by API surface

src/serve.ts27 symbols
src/client-entry.ts21 symbols
src/types.ts18 symbols
src/session.ts18 symbols
src/hooks/registry.ts18 symbols
src/session-protocol.ts15 symbols
cli.ts15 symbols
src/gestures/scroll.ts14 symbols
src/controls/combo-picker.ts14 symbols
src/cli/args.ts14 symbols
tests/serve-abuse.test.ts12 symbols
src/startup-resize.ts12 symbols

For agents

$ claude mcp add remobi \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact