{"id":47282807,"url":"https://github.com/connorads/remobi","last_synced_at":"2026-04-02T11:40:56.405Z","repository":{"id":344697666,"uuid":"1161014706","full_name":"connorads/remobi","owner":"connorads","description":"📱 Your terminal. Everywhere.","archived":false,"fork":false,"pushed_at":"2026-04-01T20:41:06.000Z","size":966,"stargazers_count":15,"open_issues_count":2,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-04-02T08:06:10.763Z","etag":null,"topics":["mobile","node","pwa","terminal","tmux","touch","ttyd","typescript"],"latest_commit_sha":null,"homepage":"https://remobi.app","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/connorads.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-02-18T16:28:33.000Z","updated_at":"2026-04-01T20:41:09.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/connorads/remobi","commit_stats":null,"previous_names":["connorads/remobi"],"tags_count":20,"template":false,"template_full_name":null,"purl":"pkg:github/connorads/remobi","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/connorads%2Fremobi","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/connorads%2Fremobi/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/connorads%2Fremobi/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/connorads%2Fremobi/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/connorads","download_url":"https://codeload.github.com/connorads/remobi/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/connorads%2Fremobi/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31305721,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T09:48:21.550Z","status":"ssl_error","status_checked_at":"2026-04-02T09:48:19.196Z","response_time":89,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["mobile","node","pwa","terminal","tmux","touch","ttyd","typescript"],"created_at":"2026-03-16T02:38:17.690Z","updated_at":"2026-04-02T11:40:56.400Z","avatar_url":"https://github.com/connorads.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"logo/logo.svg\" width=\"128\" alt=\"remobi logo\"/\u003e\n\u003c/div\u003e\n\n# remobi\n\n[![CI](https://github.com/connorads/remobi/actions/workflows/ci.yml/badge.svg)](https://github.com/connorads/remobi/actions/workflows/ci.yml)\n[![npm](https://img.shields.io/npm/v/remobi)](https://www.npmjs.com/package/remobi)\n[![licence](https://img.shields.io/npm/l/remobi)](LICENSE)\n\n**Your terminal. Everywhere.**\n\nYour 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.\n\nIt'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.\n\n```bash\n/bin/bash -c \"$(curl -fsSL http://remobi.app/install.sh)\"\n```\n\nTo upgrade stable: `npm install -g remobi@latest`\n\nTo try the experimental channel: `npm install -g remobi@dev`\n\nYour 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.\n\n## Why remobi\n\n- **Zero workflow changes** — your existing tmux setup, untouched\n- **Swipe between windows** — gesture navigation, no prefix key fumbling on a phone screen\n- **Pinch to zoom** — resize text like every other app on your phone\n- **Install to your home screen** — standalone PWA, looks and feels native\n- **Config-driven** — your buttons, your gestures, your layout\n- **Self-hosted** — local-first by default. Bring your own access layer (Tailscale, Cloudflare, ngrok)\n\n\u003cdiv align=\"center\"\u003e\n  \u003cvideo src=\"https://github.com/user-attachments/assets/952bdb34-4b73-4210-815a-b2b60f99f87f\" /\u003e\n\u003c/div\u003e\n\n## Requirements\n\n- [Node.js](https://nodejs.org/) ≥ 22\n- [tmux](https://github.com/tmux/tmux) (the target multiplexer)\n\n## Manual setup\n\n```bash\n# 1. Install\nnpm install -g remobi\n\n# 2. Enable mouse mode in tmux (required for touch scroll and tap-to-focus)\n#    Add to ~/.config/tmux/tmux.conf or ~/.tmux.conf:\n#    set -g mouse on\n\n# 3. Start (spawns your command, serves remobi on 127.0.0.1:7681)\nremobi serve\n```\n\n**Essential tmux settings for mobile** — add to your `tmux.conf` if not already present:\n\n| Setting | Command | Why |\n|---------|---------|-----|\n| Mouse mode | `set -g mouse on` | Enables touch scroll, tap-to-focus, drag resize. **The single highest-value setting for mobile use.** |\n| Status position | `set -g status-position top` | Keeps status bar away from remobi's touch toolbar at the bottom |\n| Renumber windows | `set -g renumber-windows on` | Keeps window list tidy after closing windows |\n\nSee [Mobile-friendly tmux config](.agents/skills/remobi-setup/references/mobile-tmux.md) for responsive status bars, popup sizing, and more.\n\nFor local development, see the [Development](#development) section below.\n\nOpen `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.\n\n## Release channels\n\n- `main` publishes stable releases to npm `latest`\n- `dev` publishes prereleases to npm `dev`\n- merge `dev` into `main` to promote an experimental line to stable\n\nIf 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.\n\n## Set up with AI\n\nThe 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:\n\n**Option 1: One-liner** — installs the skill and launches an interactive session with your coding agent:\n\n```bash\n/bin/bash -c \"$(curl -fsSL http://remobi.app/install.sh)\"\n```\n\n**Option 2: Install the skill** — if you prefer to start the conversation yourself:\n\n```bash\nnpx skills add connorads/remobi\n```\n\nThen tell your coding agent: \"Use the remobi-setup skill to onboard me.\"\n\n**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:\n\n\u003e Read `.agents/skills/remobi-setup/SKILL.md` in this repo and follow it to onboard me.\n\nThe skill file contains the full setup workflow. The agent can read it directly — no installation needed.\n\n## Security model\n\n`remobi` is a remote-control surface for your terminal. Anyone who can reach it can drive the tmux session with your user privileges.\n\n- `remobi serve` binds to `127.0.0.1` by default.\n- The inner PTY-backed terminal session stays local to the remobi process.\n- There is no built-in login, password, or ACL in remobi itself.\n- Safe default: keep it on localhost and publish it through a trusted layer like Tailscale Serve.\n- 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.\n\nTo report a vulnerability, see [SECURITY.md](SECURITY.md).\n\n## CLI reference\n\n```text\nremobi serve [--config \u003cpath\u003e] [--port \u003cn\u003e] [--host \u003caddr\u003e] [--base-path \u003cpath\u003e] [-- \u003ccommand...\u003e]\n  Start remobi with its built-in web terminal and PWA support.\n  Default host: 127.0.0.1. Default port: 7681. Default command: tmux new-session -A -s main\n  Example: remobi serve --host 0.0.0.0 --port 8080\n  Example: remobi serve --base-path /random-token\n  Example: remobi serve --port 8080 -- tmux new -As dev\n\nremobi build [--config \u003cpath\u003e] [--output \u003cpath\u003e] [--dry-run]\n  Deprecated. remobi no longer patches ttyd HTML.\n\nremobi inject [--config \u003cpath\u003e] [--dry-run]\n  Deprecated. remobi no longer patches ttyd HTML.\n\nremobi init\n  Scaffold a remobi.config.ts with commented defaults.\n\nremobi --version\nremobi --help\n```\n\nShort flags: `-c` (`--config`), `-p` (`--port`). Legacy deprecated flags: `-o` (`--output`), `-n` (`--dry-run`).\n\n### Config resolution\n\nWhen `--config` is not specified, remobi searches:\n\n1. `remobi.config.ts` / `.js` in the current directory\n2. `~/.config/remobi/remobi.config.ts` / `.js` (XDG fallback)\n\n## Configuration\n\nCreate `remobi.config.ts` (or run `remobi init`):\n\n```typescript\nexport default {\n  font: {\n    family: 'JetBrainsMono NFM, monospace',\n    mobileSizeDefault: 16,\n    sizeRange: [8, 32],\n  },\n  toolbar: {\n    row1: [\n      { id: 'esc', label: 'Esc', description: 'Send Escape key', action: { type: 'send', data: '\\x1b' } },\n      { id: 'tmux-prefix', label: 'Prefix', description: 'Send tmux prefix key (Ctrl-B)', action: { type: 'send', data: '\\x02' } },\n      // ...\n    ],\n    row2: [\n      { id: 'alt-enter', label: 'M-↵', description: 'Send Alt+Enter (ESC + Enter)', action: { type: 'send', data: '\\x1b\\r' } },\n      { id: 'drawer-toggle', label: '☰ More', description: 'Open command drawer', action: { type: 'drawer-toggle' } },\n      { id: 'paste', label: 'Paste', description: 'Paste from clipboard', action: { type: 'paste' } },\n      { id: 'backspace', label: '⌫', description: 'Send Backspace key', action: { type: 'send', data: '\\x7f' } },\n      // ...\n    ],\n  },\n  drawer: {\n    buttons: [\n      { id: 'tmux-new-window', label: '+ Win', description: 'Create tmux window', action: { type: 'send', data: '\\x02c' } },\n      { id: 'tmux-split-vertical', label: 'Split |', description: 'Split pane vertically', action: { type: 'send', data: '\\x02%' } },\n      { id: 'combo-picker', label: 'Combo', description: 'Open combo sender (Ctrl/Alt + key)', action: { type: 'combo-picker' } },\n      // ...\n    ],\n  },\n  gestures: {\n    swipe: {\n      enabled: true,\n      left: '\\x02n',         // data sent on swipe left (default: next tmux window)\n      right: '\\x02p',        // data sent on swipe right (default: prev tmux window)\n      leftLabel: 'Next tmux window',    // shown in help overlay\n      rightLabel: 'Previous tmux window',\n    },\n    scroll: {\n      enabled: true,\n      strategy: 'wheel',\n      sensitivity: 40,\n      wheelIntervalMs: 24,\n    },\n    pinch: { enabled: true },\n  },\n  mobile: {\n    initData: '\\x02z',     // send on mobile load when viewport \u003c widthThreshold\n    widthThreshold: 768,   // px — default matches common phone/tablet breakpoint\n  },\n  floatingButtons: [\n    {\n      position: 'top-left',\n      buttons: [\n        { id: 'zoom', label: 'Zoom', description: 'Toggle pane zoom', action: { type: 'send', data: '\\x02z' } },\n      ],\n    },\n  ],\n}\n```\n\nAll fields are optional — the CLI fills in defaults internally when it loads the config.\n\nShipped tmux drawer defaults stick to stock tmux bindings (`c`, `%`, `\"`, `s`, `w`, `[`, `?`, `x`, `z`) rather than personal popup workflows.\n\nReplace the drawer entirely with a plain array when you want a fully custom setup:\n\n```typescript\nimport { defineConfig } from 'remobi/config'\n\nexport default defineConfig({\n  drawer: {\n    buttons: [\n      { id: 'sessions', label: 'Sessions', description: 'Choose tmux session', action: { type: 'send', data: '\\x02s' } },\n      { id: 'git', label: 'Git', description: 'Open my tmux git popup', action: { type: 'send', data: '\\x02g' } },\n    ],\n  },\n})\n```\n\nAt runtime, remobi validates the config object shape and rejects unknown keys with clear path-based errors.\n\n`gestures.scroll.strategy` controls touch scroll behaviour:\n\n- `wheel` (default): sends SGR mouse wheel events with touch-mapped terminal coordinates.\n- `keys`: sends `PageUp` / `PageDown` for app-level paging when preferred.\n\n### Programmatic API\n\n```typescript\nimport { defineConfig, serialiseThemeForTtyd } from 'remobi/config'\nimport type { RemobiConfig, ControlButton } from 'remobi/types'\nimport { init } from 'remobi'\n```\n\nAdvanced consumers can use hook registry primitives to observe lifecycle and terminal-send events:\n\n```typescript\nimport { createHookRegistry, init } from 'remobi'\n\nconst hooks = createHookRegistry()\nhooks.on('beforeSendData', (ctx) =\u003e {\n  if (ctx.data.includes('rm -rf /')) return { block: true }\n})\n\ninit(undefined, hooks)\n```\n\n## Guides\n\n- [Mobile-friendly tmux config](.agents/skills/remobi-setup/references/mobile-tmux.md) — responsive status bar, popup sizing, binding ergonomics\n- [Mobile pane navigation](.agents/skills/remobi-setup/references/mobile-panes.md) — zoom-aware swipe, auto-zoom on load, floating buttons\n- [Tailscale Serve](.agents/skills/remobi-setup/references/tailscale-serve.md) — expose over your tailnet with HTTPS\n- [Keeping your Mac awake](.agents/skills/remobi-setup/references/keep-awake.md) — prevent sleep during remote sessions\n- [ttyd flags](.agents/skills/remobi-setup/references/ttyd-flags.md) — legacy notes for old ttyd-based setups\n\n## Architecture docs\n\n- [How remobi works](docs/architecture/how-remobi-works.md) — runtime overview, shared session model, and boot path\n- [Networking and WebSocket flow](docs/architecture/networking-and-websockets.md) — request lifecycle, protocol, and network boundary\n\n## Architecture\n\nPure 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.\n\nKey modules:\n\n| Module | Purpose |\n|--------|---------|\n| `src/toolbar/` | Two-row touch toolbar |\n| `src/drawer/` | Command drawer with grid layout |\n| `src/gestures/` | Swipe, pinch, scroll detection |\n| `src/controls/` | Font size, help overlay, scroll buttons |\n| `src/theme/` | Catppuccin Mocha + theme application |\n| `src/viewport/` | Height management, landscape detection |\n| `src/util/` | DOM helpers, terminal, keyboard, haptics |\n\n## Public API and semver\n\nremobi follows semantic versioning. The public API is defined by the following import paths:\n\n| Import path | Contents | Stability |\n|---|---|---|\n| `remobi` | `init`, `defineConfig`, `createHookRegistry`, `RemobiConfig`, `ControlButton`, `ButtonAction`, `ButtonArrayPatch`, `ButtonArrayInput`, `RemobiConfigOverrides`, `HookRegistry` | Public — breaking changes are semver-major |\n| `remobi/config` | `defineConfig`, `mergeConfig`, `defaultConfig`, `serialiseThemeForTtyd` | Public |\n| `remobi/types` | All types in `src/types.ts` | Public |\n\n**Internal modules** (not part of the public API — may change without a major version bump):\n`src/toolbar/`, `src/drawer/`, `src/gestures/`, `src/controls/`, `src/theme/`, `src/viewport/`, `src/util/`, `src/serve.ts`, `src/cli/`, `build.ts`\n\n**Semver policy**:\n- **Major**: removing or renaming a public export, changing a public function signature incompatibly, removing a config field\n- **Minor**: adding new public exports, new optional config fields, new `ButtonArrayInput` operations\n- **Patch**: bug fixes, internal refactors, documentation updates\n\n## Development\n\n```bash\ngit clone https://github.com/connorads/remobi.git \u0026\u0026 cd remobi\npnpm install\ngit config core.hooksPath .hk-hooks   # enable commit hooks (conventional commits, biome)\n```\n\n### Running locally\n\nFrom source (bundles the browser client on the fly via esbuild — no build step needed):\n\n```bash\ntsx cli.ts serve              # localhost:7681, default tmux session\n```\n\nOr build first, then run from dist/:\n\n```bash\npnpm run build:dist          # transpile TS → JS + bundle browser client\nnode dist/cli.mjs serve      # run locally-built version on localhost:7681\n```\n\nNo watch mode — re-run the build or use `tsx` for automatic source bundling.\n\n### Checks\n\n```bash\npnpm test            # vitest (unit + integration)\npnpm run test:pw     # playwright e2e (needs: pnpm exec playwright install chromium webkit --with-deps)\npnpm run check       # biome lint + format\n```\n\n## FAQ\n\n**Is this secure?**\nremobi 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.\n\n**Why not Termux / Termius / SSH apps?**\nThey 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.\n\n**Why not [Happy](https://github.com/slopus/happy) / Claude resume / chat-based mobile apps?**\nThose 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.\n\n**Why Node?**\nremobi 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.\n\n**Is this production-ready?**\nIt's v0.1. The author uses it daily. It works. It's also early — feedback welcome, forks encouraged.\n\n## Acknowledgements\n\nremobi 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.\n\nEarlier 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.\n\n## Licence\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fconnorads%2Fremobi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fconnorads%2Fremobi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fconnorads%2Fremobi/lists"}