remobi-setup
Original:🇺🇸 English
Translated
Full interactive onboarding for remobi — the mobile terminal overlay for tmux. Checks prerequisites, inspects tmux config, interviews the user about their workflow, generates a validated remobi.config.ts, suggests tmux mobile optimisations, and walks through deployment. Use this skill whenever someone asks to set up remobi, configure remobi, onboard with remobi, generate a remobi config, make tmux mobile-friendly, or deploy remobi with Tailscale. Also use when the user says "onboard me" or "set up my phone terminal".
11installs
Sourceconnorads/remobi
Added on
NPX Install
npx skill4agent add connorads/remobi remobi-setupTags
Translated version includes tags in frontmatterSKILL.md Content
View Translation Comparison →remobi-setup
Interactive onboarding skill for remobi — monitor and control tmux from your phone.
This skill walks the user through the full setup journey in one conversation. Each phase builds on the last; skip phases the user doesn't need.
Workflow
Phase 1: Assess environment
Check what's installed and help fill gaps.
bash
node --version # need >= 22
which ttyd # must be on PATH
tmux -V # target multiplexer
which remobi # npm install -g remobiIf anything is missing, help install it:
- Node: suggest mise, nvm, or direct install
- ttyd: (macOS), distro package or source build (Linux) — see ttyd installation
brew install ttyd - tmux: or distro package
brew install tmux - remobi:
npm install -g remobi
Move on once all four are present.
Phase 2: Inspect tmux setup
Gather the user's tmux configuration to inform config generation.
bash
tmux show-options -g prefix # prefix key
tmux list-keys # all bindings
tmux show-options -g mouse # mouse mode
tmux show-options -g status-left # status bar
tmux list-keys | grep display-popup # popup bindingsIf tmux isn't running, fall back to reading the config file directly:
bash
cat ~/.config/tmux/tmux.conf 2>/dev/null || cat ~/.tmux.conf 2>/dev/nullNote down:
- Prefix key and byte (Ctrl-B = , Ctrl-A =
\x02, etc.)\x01 - Custom bindings worth surfacing as buttons (especially popup bindings for lazygit, yazi, neovim, fzf pickers, gh-dash, scratch shells)
- Whether mouse mode is on
- Status bar complexity (affects mobile width recommendations)
- Plugin manager (tpm, etc.)
If the user has no tmux config at all, offer to help set up a basic one before continuing.
Phase 3: Interview the user
Ask questions one at a time — don't dump a list. Adapt based on what you learned in phase 2.
- What do you primarily use tmux for? (coding agents, dev workflow, server monitoring, all of the above)
- Do you use popup bindings for tools? Which ones? (lazygit, yazi, neovim, scratch shell, gh-dash, session picker)
- Do you want touch scrolling? What strategy? (for mouse-event scrolling,
wheelfor PageUp/PageDown paging)keys - Auto-zoom on mobile? When you open remobi on your phone, should the current pane zoom to full screen automatically?
- Floating zoom button? A persistent button overlaid on the terminal for one-tap zoom toggle
- Custom theme or Catppuccin Mocha? (Catppuccin Mocha is the default and looks great — only ask if the user's tmux theme is clearly different)
- Font preference? (default: JetBrainsMono NFM)
- Any other tmux bindings you want on your phone? (This catches anything the inspection missed)
Skip questions where you already know the answer from phase 2. Summarise what you've gathered before moving to config generation.
Phase 4: Generate remobi.config.ts
remobi.config.tsWrite the config using . Only include keys that differ from defaults — omit everything else.
defineConfig()typescript
import { defineConfig } from 'remobi'
export default defineConfig({
// Only non-default overrides here
})After writing, validate:
bash
remobi build --dry-runA zero exit with "Dry run: build" output means valid. Fix any errors and re-validate until clean.
See Config reference below for the full schema, allowed keys, action types, and escape codes.
Phase 5: Suggest tmux mobile optimisations
Ask: "Would you like suggestions for making your tmux config more mobile-friendly?"
If yes, run the checks below. If tmux isn't running, read the config file directly. For full context and examples, read and .
references/mobile-tmux.mdreferences/mobile-panes.md| Check | Command | Good sign | Suggestion if missing |
|---|---|---|---|
| Responsive status-left | | Contains | Add width breakpoints to strip content on narrow terminals |
| Responsive status-right | | Contains | Progressive content stripping |
| Popup sizing | | Uses | Replace fixed char sizes with |
| Zoom indicator | | Contains | Add |
| Mouse mode | | | |
| Window renumbering | | | |
| Zoom-aware navigation | | Present | Add zoom-aware |
For each missing item, offer a concrete snippet the user can paste into . Suggest snippets only — never modify without explicit permission.
tmux.conftmux.confPhase 6: Deployment guidance
Ask: "How do you want to access remobi from your phone?"
remobi is a remote-control surface for your terminal — never expose it to the public internet. All deployment options below keep access private.
Common options:
- Tailscale Serve (recommended) — HTTPS over your private tailnet. Read for the full guide.
references/tailscale-serve.md - Cloudflare Tunnel + Access — private tunnel with Cloudflare Access policies controlling who can connect (e.g. restrict by email, IdP group, device posture). Do not use unauthenticated quick tunnels.
- Local network only — on localhost behind your own VPN or private network.
remobi serve
For macOS users, mention and point to for persistent options.
--no-sleepreferences/keep-awake.mdFor users who want manual ttyd control, point to .
references/ttyd-flags.mdPhase 7: Summarise
Tell the user:
- What was configured and why (prefix byte, custom bindings, gestures, theme)
- How to start:
remobi serve - How to access from their phone (URL from deployment choice)
- PWA install: on mobile, tap "Add to Home Screen" for a standalone app experience
Config reference
Allowed root keys
Exactly these — validation rejects anything else:
name theme font toolbar drawer gestures mobile floatingButtons pwa reconnectButtonAction union
| Required fields | Notes |
|---|---|---|
| | Optional |
| (none) | Opens Ctrl+key combo UI |
| (none) | Paste from clipboard |
| (none) | Opens Ctrl/Alt + key modal |
| (none) | Opens/closes command drawer |
Non- actions must NOT have or — the validator rejects them.
senddatakeyLabelControlButton shape
Every button in toolbar rows, drawer, and floatingButtons uses this schema:
typescript
{
id: string // unique within its array
label: string // text shown on the button
description: string // shown in help overlay — keep user-facing and clear
action: ButtonAction
}Button array forms (toolbar.row1
, toolbar.row2
, drawer.buttons
)
toolbar.row1toolbar.row2drawer.buttonsTwo forms — pick the least invasive:
typescript
// 1. Replace entirely (plain array)
toolbar: { row1: [{ id, label, description, action }, ...] }
// 2. Transform (function receives defaults, returns new array)
toolbar: { row2: (defaults) => defaults.filter(b => b.id !== 'q') }
// Function form covers all operations via standard JS:
// - Append: (d) => [...d, newBtn]
// - Prepend: (d) => [newBtn, ...d]
// - Remove: (d) => d.filter(b => b.id !== 'q')
// - Replace: (d) => d.map(b => b.id === 'tmux-prefix' ? newBtn : b)
// - Insert: (d) => { const i = d.findIndex(b => b.id === 'tab'); return [...d.slice(0,i), newBtn, ...d.slice(i)] }Floating buttons
Must use the grouped shape — a flat is rejected:
ControlButton[]typescript
floatingButtons: [
{
position: 'top-left', // required
direction: 'row', // optional: 'row' | 'column' (default 'row')
buttons: [{ id, label, description, action }],
},
]Valid positions:
top-left | top-right | top-centre | bottom-left | bottom-right | bottom-centre | centre-left | centre-rightEscape-code cheat sheet
Use these in and gesture / fields:
action.dataleftright| Key | Escape sequence | Notes |
|---|---|---|
| Ctrl-B (prefix) | | Default tmux prefix |
| Ctrl-A (prefix) | | screen/byobu/custom prefix |
| Ctrl-C | | Interrupt |
| Ctrl-D | | EOF / exit shell |
| Escape | | |
| Tab | | |
| Shift+Tab | | |
| Enter | | |
| Alt+Enter | | |
| Backspace | | DEL character |
| Up arrow | | |
| Down arrow | | |
| Right arrow | | |
| Left arrow | | |
| Page Up | | |
| Page Down | | |
| Space | | literal space |
Composing tmux key sequences
tmux bindings are + . Concatenate the bytes:
prefixkeyCtrl-B + c → '\x02c' (new window)
Ctrl-B + n → '\x02n' (next window)
Ctrl-B + p → '\x02p' (previous window)
Ctrl-B + z → '\x02z' (zoom pane)
Ctrl-B + % → '\x02%' (split vertical — stock tmux)
Ctrl-B + " → '\x02"' (split horizontal — stock tmux)
Ctrl-B + [ → '\x02[' (copy mode)
Ctrl-B + d → '\x02d' (detach)For a custom prefix (e.g. Ctrl-A): replace with .
\x02\x01Example configs
Minimal — default Ctrl-B prefix, custom name only
typescript
import { defineConfig } from 'remobi'
export default defineConfig({
name: 'dev',
})Custom prefix — Ctrl-A (screen/byobu style)
Replace the default button and update swipe gestures:
tmux-prefixtypescript
import { defineConfig } from 'remobi'
export default defineConfig({
name: 'dev',
toolbar: {
row1: (defaults) => defaults.map(b =>
b.id === 'tmux-prefix'
? { ...b, description: 'Send tmux prefix key (Ctrl-A)', action: { type: 'send', data: '\x01' } }
: b
),
},
gestures: {
swipe: {
left: '\x01n',
right: '\x01p',
leftLabel: 'Next tmux window',
rightLabel: 'Previous tmux window',
},
},
drawer: {
buttons: (defaults) => defaults.map(b => {
// Remap tmux-prefixed buttons from Ctrl-B (\x02) to Ctrl-A (\x01)
if (b.action.type === 'send' && b.action.data.startsWith('\x02')) {
return { ...b, action: { ...b.action, data: '\x01' + b.action.data.slice(1) } }
}
return b
}),
},
})Floating buttons + mobile auto-zoom
typescript
import { defineConfig } from 'remobi'
export default defineConfig({
mobile: {
initData: '\x02z', // zoom focused pane on mobile load
widthThreshold: 768,
},
floatingButtons: [
{
position: 'top-left',
buttons: [
{
id: 'zoom',
label: 'Zoom',
description: 'Toggle pane zoom',
action: { type: 'send', data: '\x02z' },
},
],
},
],
})Guardrails
- Never invent root keys. The validator rejects unknown keys with a path-based error.
- Use , never
drawer.buttons— the latter was renamed and no longer works.drawer.commands - actions require
send— omitting it fails validation.data - Non-actions must not have
sendordata— validator rejects them.keyLabel - is an array of groups — wrap buttons in
floatingButtons.{ position, buttons } - has
toolbarandrow1— there is norow2or flatrow3key on toolbar.buttons - is
mobile.initData— set tostring | nullto disable, notnullorfalse.'' - has only
reconnect— defaults toenabled: boolean. Settrueto disable.{ enabled: false }
Validation
bash
remobi build --dry-run # validates config, prints plan, exits without building
remobi build --dry-run -c ./remobi.config.ts # explicit pathA zero exit with "Dry run: build" output means the config is valid. Any error output means fix the reported paths before proceeding.
Common validation errors
| Error | Cause | Fix |
|---|---|---|
| Invented or legacy root key | Remove it; only allowed root keys are valid |
| Old key name | Rename to |
| Wrong toolbar shape | Use |
| Wrong type string | Use exact literal from ButtonAction union |
| | Add |
| | Remove |
| Flat | Wrap in group: |
| | Use |