--- name: remobi-setup description: > 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, use remobi with zellij or herdr, or deploy remobi with Tailscale. Also use when the user says "onboard me" or "set up my phone terminal". --- # remobi-setup Interactive onboarding skill for [remobi](https://github.com/connorads/remobi) — monitor and control tmux (or zellij, herdr) from your phone. This skill walks the user through setup in one conversation. The guiding principle: **detect everything possible, default everything sensible, ask only what requires human intent.** Most users answer 1-3 questions total. ## Workflow ### Phase 1: Welcome and understand (1 question) Open with a one-liner confirming what they're getting, then ask what brings them here: > "remobi puts your tmux session on your phone — same panes, same windows, touch controls on top. Everything we set up here you can change later." > > "What brings you to remobi? For example: monitoring coding agents from your phone, getting phone access to your dev sessions, or just curious to try it out." Map the answer to a persona internally (don't tell the user their "persona"): | Persona | Signals | Downstream effect | |---------|---------|-------------------| | **Agent Watcher** | Mentions coding agents, Claude Code, Codex, AI, monitoring | Auto-zoom on, floating zoom button, double-tap zoom enabled, lean config, minimal questions | | **Remote Dev** | Mentions tmux, SSH, dev workflow, existing setup | Inspect config thoroughly, offer popup drawer buttons, ask about auto-zoom | | **Newcomer** | Says curious, trying it out, heard about it, no specific use case | Offer tmux setup, explain concepts, auto-zoom on, sensible defaults | If the answer is ambiguous, lean towards Agent Watcher — it's the most common path and the defaults work well for everyone. ### Phase 2: Environment and tmux setup #### Check prerequisites Run silently, then report what's present vs missing: ```bash node --version # need >= 22 tmux -V # default target multiplexer which zellij # alternative multiplexer (see zellij path below) which herdr # alternative multiplexer (see herdr path below) which remobi # npm install -g remobi ``` If anything is missing, help install it: - **Node**: suggest mise, nvm, or direct install - **tmux**: `brew install tmux` or distro package - **remobi**: `npm install -g remobi` #### zellij instead of tmux [zellij](https://github.com/zellij-org/zellij) is a batteries-included tmux alternative with a discoverable, modal UI. If zellij is installed and the user prefers it (or asks for it), take the zellij path: ```bash remobi serve -- zellij attach --create main ``` `zellij attach --create ` attaches or creates, like `tmux new-session -A`. On the zellij path: - **Skip the tmux inspection and mouse-mode steps entirely** — zellij enables mouse mode by default, so touch scroll and tap-to-focus work with no multiplexer config - Stock zellij ships a tmux-compat mode on Ctrl-B (`\x02`): the Prefix button, swipe gestures (`\x02n`/`\x02p` — next/previous tab), and the `+ Win` (`\x02c`), Split (`\x02%`/`\x02"`), Zoom (`\x02z`), Copy (`\x02[` — scroll mode), and Kill (`\x02x`) buttons work unchanged - Replace the three drawer buttons zellij doesn't bind (`tmux-sessions`, `tmux-windows`, `tmux-help`) — see the [zellij example config](#zellij--modal-tmux-alternative) and [Composing zellij key sequences](#composing-zellij-key-sequences) - Custom keybindings live in `~/.config/zellij/config.kdl` under `keybinds` — inspect it if present; if it uses `clear-defaults=true` without a `tmux` mode block, the Ctrl-B compat bindings are gone and the drawer needs zellij-native sequences throughout - For small screens, suggest `default_layout "compact"` and `pane_frames false` in `config.kdl` to reclaim rows from zellij's UI chrome - If the user also stays attached from their desktop, suggest `mirror_session true` in `config.kdl` so both clients see the same view Then continue at Phase 3. #### herdr instead of tmux [herdr](https://github.com/ogulcancelik/herdr) is an agent multiplexer — a tmux alternative with built-in agent status detection, common among Agent Watchers. If herdr is installed and the user prefers it (or asks for it), take the herdr path: ```bash remobi serve -- herdr --session main ``` `herdr --session ` attaches or creates, like `tmux new-session -A`. On the herdr path: - **Skip the tmux inspection and mouse-mode steps entirely** — herdr captures mouse input by default, so touch scroll and tap-to-focus work with no multiplexer config - herdr's default prefix is Ctrl-B (`\x02`), the same as tmux: the Prefix button, swipe gestures (`\x02n`/`\x02p` — next/previous tab), and the `+ Win` (`\x02c`), Zoom (`\x02z`), Kill (`\x02x`), and Help (`\x02?`) buttons work unchanged - Replace the drawer buttons herdr doesn't bind — see the [herdr example config](#herdr--agent-multiplexer) and [Composing herdr key sequences](#composing-herdr-key-sequences) - herdr has a built-in single-column layout for narrow terminals (`ui.mobile_width_threshold` in herdr's `config.toml`); no status-bar or popup tuning needed - Custom keybindings live in `~/.config/herdr/config.toml` under `[keys]` — inspect it if present, and translate any remapped prefix or bindings the same way as a custom tmux prefix Then continue at Phase 3. #### Inspect tmux 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 show-options -g status-position # top or bottom tmux list-keys | grep display-popup # popup bindings ``` If 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/null ``` Auto-detect and note: - Prefix key and byte (Ctrl-B = `\x02`, Ctrl-A = `\x01`, etc.) - Custom popup bindings (lazygit, yazi, scratch shell, system monitor, etc.) - Whether mouse mode is on - Split bindings (stock `%`/`"` or remapped `|`/`-`) - Status bar complexity and position - Plugin manager (tpm, etc.) - Double-tap zoom gesture (see `references/mobile-panes.md` for pane workflows) **Detect installed tools** — check for popular tools that work well as tmux popup bindings: ```bash which lazygit # Git TUI which yazi # File manager which btm || which htop # System monitor which nvim || which vim # Editor ``` #### Offer tmux setup (Agent Watcher and Newcomer only) If no tmux config exists, read `references/tmux-basics.md` and offer to create one. Frame it as a proposal, not a gap: **Agent Watcher framing:** > "I'll create a tmux config tuned for monitoring agents — mouse support, status bar at top, and double-tap zoom so you can zoom into any agent pane on your phone. Go ahead?" **Newcomer framing:** > "tmux is the terminal multiplexer that remobi sits on top of — it keeps your sessions running even when you disconnect. I'll set up a config with mouse support, sensible defaults, and a help popup to learn the keybindings. Want me to explain what each setting does as I go?" **Remote Dev:** Skip — they already have a config. The starter config comes from `references/tmux-basics.md`. For Agent Watchers, include the "Agent watcher starter config" section (zoom indicator, auto-rename, double-tap zoom via remobi config). For Newcomers with detected tools, also offer popup bindings: > "I found lazygit and yazi on your system. These work great as tmux popups — one keypress to open a floating window. Want me to add popup bindings for them?" Only proceed to Phase 3 once the user has a working tmux session. ### Phase 3: Confirm detections and ask what's needed (0-3 questions) Present a summary of what you found and what you plan to configure. The style is "here's what I'll do" with checkpoints, not an interview. **Summary format:** > "Based on your setup, here's what I'll configure: > - Prefix: Ctrl-B (detected from your tmux config) > - Auto-zoom on mobile load (pane fills the phone screen) > - Floating zoom button (one-tap zoom toggle) > - Default toolbar and drawer buttons > - [If applicable:] Drawer buttons for lazygit and yazi (matching your popup bindings)" Then ask **only** questions that can't be detected or defaulted: #### Questions by persona **Agent Watcher (0-1 questions):** If popup bindings or tools were detected: > "I found [lazygit/yazi/btm] on your system and matching popup bindings. Want drawer buttons for these in remobi so you can trigger them from your phone?" If nothing special detected: **zero questions** — proceed straight to config generation. **Remote Dev (1-3 questions):** Question 1 (if popup bindings or tools detected): > "I found popup bindings for [list]. Want matching drawer buttons in remobi?" Question 2 (if multi-pane layout likely): > "Do you want auto-zoom when you open remobi on your phone? This zooms the current pane to full screen — works well with multi-pane layouts on a small screen." Question 3 (catch-all): > "Anything else you want accessible from your phone? Custom tmux bindings, specific tools, anything I missed?" **Newcomer (0-1 questions):** If tools were detected and popup bindings were set up in Phase 2: > "I set up popup bindings for [lazygit/yazi]. Want matching buttons in remobi's command drawer?" Otherwise: **zero questions** — defaults are great to start with. Summarise what you've gathered before moving to config generation. ### Phase 4: Generate config and suggest tmux tweaks #### Generate `remobi.config.ts` Export a plain config object — only include keys that differ from defaults, omit everything else. **Do not** `import { defineConfig } from 'remobi'` — the CLI calls `defineConfig()` internally so the config just needs a plain object export. ```typescript export default { // Only non-default overrides here } ``` Place at `~/.config/remobi/remobi.config.ts` (XDG location) unless the user prefers elsewhere. After writing, validate by starting remobi. remobi auto-discovers config from the current directory first, then `~/.config/remobi/`, so `--config` is only needed when you want to force a specific file: ```bash remobi serve --port 18765 -- /bin/true ``` A zero exit means the config loaded and the command started cleanly. If the user stored config somewhere custom, validate that path explicitly instead: ```bash remobi serve --config /path/to/remobi.config.ts --port 18765 -- /bin/true ``` Fix any errors and re-validate until clean. See [Config reference](#config-reference) below for the full schema, allowed keys, action types, and escape codes. #### Suggest tmux mobile optimisations (Remote Dev only) For Remote Dev users who already had a tmux config, offer mobile tweaks as a single confirmation. Read `references/mobile-tmux.md` and `references/mobile-panes.md` for full context. > "I have a few suggestions to make your tmux more mobile-friendly: [list 2-3 most impactful items]. Want me to add these to your tmux.conf?" Prioritise by impact, suggest maximum 3: 1. **Double-tap zoom** (if multi-pane user — enable via remobi `gestures.doubleTap`) 2. **Responsive status bar** (if status bar would overflow on phone — see `references/mobile-tmux.md`) 3. **Zoom indicator** (if `#{window_zoomed_flag}` missing from status) Also check and mention (but don't push): | Check | Command | Good sign | Suggestion if missing | |-------|---------|-----------|----------------------| | Mouse mode | `tmux show -g mouse` | `on` | `set -g mouse on` | | Status position | `tmux show -g status-position` | `top` | `set -g status-position top` (keeps status away from remobi toolbar) | | Popup sizing | `tmux list-keys \| grep display-popup` | Uses `%` dimensions | Replace fixed char sizes with `95%`/`100%` | | Window renumbering | `tmux show -g renumber-windows` | `on` | `set -g renumber-windows on` | Suggest snippets only — never modify `tmux.conf` without explicit permission. **Skip this for Newcomers** — their starter config from Phase 2 already includes the essentials. ### Phase 5: Deploy and wrap up #### Deployment Detect what's available and recommend accordingly: ```bash which tailscale # check for Tailscale ``` **If Tailscale installed:** recommend Tailscale Serve directly: > "I see Tailscale on your system. Tailscale Serve is the simplest way to access remobi from your phone — HTTPS over your private network, no extra setup." Read `references/tailscale-serve.md` for the full guide. **If no Tailscale:** offer options: > "To access remobi from your phone, you need to put it behind a trusted network layer. Options: > - **Tailscale Serve** (recommended) — private VPN, HTTPS, easiest setup > - **Cloudflare Tunnel + Access** — private tunnel with access policies > - **Local network** — if your phone is on the same WiFi/VPN" remobi is a remote-control surface for your terminal — never expose it to the public internet. All deployment options keep access private. #### Security hardening remobi hardens the connection even on private networks. Mention these if the user has security concerns: - **Binds `127.0.0.1` only** — never exposed to network without explicit `--host` flag - **Content-Security-Policy** — strict default-src, script-src, connect-src scoped to same host - **WebSocket origin validation** — rejects cross-origin upgrade requests - **Relay buffer limit** — 1 MB per connection; drops oversized payloads - **Local-only default** — remobi binds to `127.0.0.1` unless the user explicitly changes `--host` - **X-Frame-Options DENY** — prevents clickjacking via iframes - **Referrer-Policy: no-referrer** — no URL leaking to external sites For macOS users, mention `--no-sleep` and point to `references/keep-awake.md` for persistent options. For users migrating from old ttyd-based setups, point to `references/ttyd-flags.md` as legacy guidance only. #### Summary Tell the user: 1. What was configured and why (prefix byte, custom bindings, gestures, auto-zoom) 2. How to start: `remobi serve` 3. How to access from their phone (URL from deployment choice) 4. PWA install: on mobile, tap "Add to Home Screen" for a standalone app experience 5. Built-in mobile controls (these work out of the box, no config needed): - **Font size**: `+`/`-` buttons in top-right. Config: `font.mobileSizeDefault` (default 16px), `font.sizeRange` (default [8, 32]), steps by 2 - **Scroll buttons**: Floating arrow buttons on the sides. Long-press for rapid repeat (300ms delay, 100ms interval). Auto-fade after 2s. Strategy follows `gestures.scroll.strategy` (`wheel` sends mouse events, `keys` sends PageUp/PageDown) - **Combo picker**: Modal for arbitrary key combos — type `C-s`, `M-Enter`, `Alt-x`, `C-[`. Supports Ctrl, Alt, Shift modifiers + named keys (PageUp, Escape, etc.). Opened via drawer "Combo" button - **Help overlay**: `?` button in top-right. Shows all configured buttons, gestures, and floating buttons in tables. Config-driven, updates when you change buttons - **Landscape + keyboard**: When on-screen keyboard opens in landscape, row 2 auto-hides and buttons shrink. No config needed 6. PWA: enabled by default. On mobile Safari/Chrome, tap Share then "Add to Home Screen" for standalone app experience. Config options: - `pwa.enabled` (default `true`) — set `false` to disable manifest + icons - `pwa.themeColor` (default `'#1e1e2e'`) — status bar colour on mobile - `pwa.shortName` (optional) — short name for home screen icon (falls back to `name`) 7. This is a starting point — not a locked-in config. Run this skill again any time to tweak buttons, add drawer commands, or change gestures. --- ## Config reference ### Allowed root keys Exactly these — validation rejects anything else: ``` name theme font toolbar drawer gestures mobile floatingButtons pwa reconnect ``` ### ButtonAction union | `type` | Required fields | Notes | |------------------|---------------------|-------| | `send` | `data: string` | Optional `keyLabel?: string` for help overlay | | `prefix` | `data: string` | Sends prefix byte then opens combo picker for follow-up key. Use `{ type: 'send', data: '\x02' }` for raw prefix-only behaviour | | `ctrl-modifier` | (none) | Opens Ctrl+key combo UI | | `paste` | (none) | Paste from clipboard | | `combo-picker` | (none) | Opens Ctrl/Alt + key modal | | `drawer-toggle` | (none) | Opens/closes command drawer | Non-`send`/`prefix` actions must NOT have `data` or `keyLabel` — the validator rejects them. ### ControlButton 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`) Two 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 `ControlButton[]` is rejected: ```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-right` ### Default button IDs **Toolbar row 1** (10 buttons): | `id` | `label` | `action` | |------|---------|----------| | `esc` | Esc | `send` `\x1b` | | `tmux-prefix` | Prefix | `prefix` `\x02` (sends prefix then opens combo picker for follow-up key) | | `tab` | Tab | `send` `\t` | | `shift-tab` | S-Tab | `send` `\x1b[Z` | | `left` | <- | `send` `\x1b[D` | | `up` | up arrow | `send` `\x1b[A` | | `down` | down arrow | `send` `\x1b[B` | | `right` | -> | `send` `\x1b[C` | | `ctrl-c` | C-c | `send` `\x03` | | `enter` | enter | `send` `\r` | **Toolbar row 2** (7 buttons): | `id` | `label` | `action` | |------|---------|----------| | `q` | q | `send` `q` | | `alt-enter` | M-enter | `send` `\x1b\r` | | `ctrl-d` | C-d | `send` `\x04` | | `drawer-toggle` | hamburger More | `drawer-toggle` | | `paste` | Paste | `paste` | | `backspace` | backspace | `send` `\x7f` | | `space` | Space | `send` `' '` | **Drawer** (12 buttons): | `id` | `label` | `action` | |------|---------|----------| | `tmux-new-window` | + Win | `send` `\x02c` | | `tmux-split-vertical` | Split \| | `send` `\x02%` | | `tmux-split-horizontal` | Split -- | `send` `\x02"` | | `tmux-zoom` | Zoom | `send` `\x02z` | | `tmux-sessions` | Sessions | `send` `\x02s` | | `tmux-windows` | Windows | `send` `\x02w` | | `page-up` | PgUp | `send` `\x1b[5~` | | `page-down` | PgDn | `send` `\x1b[6~` | | `tmux-copy` | Copy | `send` `\x02[` | | `tmux-help` | Help | `send` `\x02?` | | `tmux-kill-pane` | Kill | `send` `\x02x` | | `combo-picker` | Combo | `combo-picker` | ### Gestures | Field | Default | Notes | |-------|---------|-------| | `gestures.swipe.enabled` | `true` | | | `gestures.swipe.left` | `'\x02n'` | Next tmux window | | `gestures.swipe.right` | `'\x02p'` | Previous tmux window | | `gestures.swipe.threshold` | `80` | Pixels | | `gestures.swipe.maxDuration` | `400` | Milliseconds | | `gestures.pinch.enabled` | `false` | | | `gestures.scroll.enabled` | `true` | | | `gestures.scroll.strategy` | `'wheel'` | `'wheel'` (recommended) sends SGR mouse wheel sequences — works in vim, less, htop. `'keys'` sends PageUp/PageDown — simpler, works everywhere | | `gestures.scroll.sensitivity` | `40` | | | `gestures.scroll.wheelIntervalMs` | `24` | | | `gestures.doubleTap.enabled` | `false` | Opt-in double-tap gesture on terminal screen | | `gestures.doubleTap.data` | `'\x02z'` | Data to send on double-tap (default: tmux zoom toggle) | | `gestures.doubleTap.maxInterval` | `300` | Max milliseconds between taps | ### Font | Field | Default | Notes | |-------|---------|-------| | `font.family` | `'JetBrainsMono NFM, monospace'` | CSS font-family | | `font.cdnUrl` | jsdelivr nerdfont URL | CSS file for web font | | `font.mobileSizeDefault` | `16` | px, applied on mobile | | `font.sizeRange` | `[8, 32]` | Min/max for +/- buttons | ### PWA | Field | Default | Notes | |-------|---------|-------| | `pwa.enabled` | `true` | Set `false` to disable manifest + icons | | `pwa.themeColor` | `'#1e1e2e'` | Status bar colour on mobile | | `pwa.shortName` | (none) | Short name for home screen icon, falls back to `name` | ### Hooks (advanced) Hooks are programmatic, not via `defineConfig()`. See `references/hooks.md` if the user asks about analytics, action filtering, or custom DOM. Do not proactively suggest hooks during setup. ### Escape-code cheat sheet Use these in `action.data` and gesture `left`/`right` fields: | Key | Escape sequence | Notes | |----------------|-----------------|-------| | Ctrl-B (prefix)| `\x02` | Default tmux prefix | | Ctrl-A (prefix)| `\x01` | screen/byobu/custom prefix | | Ctrl-C | `\x03` | Interrupt | | Ctrl-D | `\x04` | EOF / exit shell | | Escape | `\x1b` | | | Tab | `\t` | | | Shift+Tab | `\x1b[Z` | | | Enter | `\r` | | | Alt+Enter | `\x1b\r` | | | Backspace | `\x7f` | DEL character | | Up arrow | `\x1b[A` | | | Down arrow | `\x1b[B` | | | Right arrow | `\x1b[C` | | | Left arrow | `\x1b[D` | | | Page Up | `\x1b[5~` | | | Page Down | `\x1b[6~` | | | Space | `' '` | literal space | ### Composing tmux key sequences tmux bindings are `prefix` + `key`. Concatenate the bytes: ``` Ctrl-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 `\x02` with `\x01`. ### Composing zellij key sequences Stock zellij's tmux-compat mode means `\x02c` / `\x02n` / `\x02p` / `\x02%` / `\x02"` / `\x02z` / `\x02[` / `\x02x` work as in tmux (tabs instead of windows). zellij-native sequences use its modal shortcuts: ``` Ctrl-O + w -> '\x0fw' (session manager) Ctrl-O + d -> '\x0fd' (detach) Ctrl-T + n -> '\x14n' (new tab, native tab mode) Ctrl-P + n -> '\x10n' (new pane, native pane mode) Ctrl-G -> '\x07' (toggle locked mode — passes Ctrl keys through) ``` Locked mode deserves a button when the user runs TUIs that want Ctrl shortcuts zellij captures. ### Composing herdr key sequences herdr shares tmux's Ctrl-B prefix, and `\x02c` / `\x02n` / `\x02p` / `\x02z` / `\x02x` / `\x02?` mean the same thing (tabs instead of windows). Bindings that differ from tmux: ``` Ctrl-B + v -> '\x02v' (split side-by-side) Ctrl-B + - -> '\x02-' (split stacked) Ctrl-B + w -> '\x02w' (workspace picker) Ctrl-B + b -> '\x02b' (toggle agent sidebar) Ctrl-B + e -> '\x02e' (edit scrollback) Ctrl-B + g -> '\x02g' (goto picker) Ctrl-B + q -> '\x02q' (detach) ``` ## Example configs ### Minimal — default Ctrl-B prefix, custom name only ```typescript export default { name: 'dev', } ``` ### Custom prefix — Ctrl-A (screen/byobu style) Replace the default `tmux-prefix` button and update swipe gestures: ```typescript export default { name: 'dev', toolbar: { row1: (defaults) => defaults.map(b => b.id === 'tmux-prefix' ? { ...b, description: 'Send tmux prefix key (Ctrl-A)', action: { type: 'prefix', 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 }), }, } ``` ### Agent watcher — auto-zoom + floating button ```typescript export default { name: 'agents', mobile: { initData: '\x02z', // zoom focused pane on mobile load }, floatingButtons: [ { position: 'top-left', buttons: [ { id: 'zoom', label: 'Zoom', description: 'Toggle pane zoom', action: { type: 'send', data: '\x02z' }, }, ], }, ], } ``` ### zellij — modal tmux alternative Keeps the tmux-compat defaults, swaps the three unbound buttons for zellij equivalents: ```typescript export default { name: 'zellij', drawer: { buttons: (defaults) => [ ...defaults.filter((b) => !['tmux-sessions', 'tmux-windows', 'tmux-help'].includes(b.id)), { id: 'zellij-sessions', label: 'Sessions', description: 'Open session manager (Ctrl-O + w)', action: { type: 'send', data: '\x0fw' } }, { id: 'zellij-lock', label: 'Lock', description: 'Toggle locked mode (Ctrl-G)', action: { type: 'send', data: '\x07' } }, ], }, } ``` Start with `remobi serve -- zellij attach --create main`. ### herdr — agent multiplexer Keeps the shared-binding defaults, swaps the tmux-only buttons for herdr equivalents: ```typescript export default { name: 'herdr', drawer: { buttons: (defaults) => [ ...defaults.filter( (b) => !['tmux-split-vertical', 'tmux-split-horizontal', 'tmux-sessions', 'tmux-windows', 'tmux-copy'].includes(b.id), ), { id: 'herdr-split-v', label: 'Split |', description: 'Split pane side-by-side (prefix + v)', action: { type: 'send', data: '\x02v' } }, { id: 'herdr-split-h', label: 'Split —', description: 'Split pane stacked (prefix + -)', action: { type: 'send', data: '\x02-' } }, { id: 'herdr-workspaces', label: 'Spaces', description: 'Open workspace picker (prefix + w)', action: { type: 'send', data: '\x02w' } }, { id: 'herdr-sidebar', label: 'Sidebar', description: 'Toggle agent sidebar (prefix + b)', action: { type: 'send', data: '\x02b' } }, { id: 'herdr-scrollback', label: 'Scroll', description: 'Edit scrollback (prefix + e)', action: { type: 'send', data: '\x02e' } }, ], }, } ``` Start with `remobi serve -- herdr --session main`. `tmux-windows` is filtered out because in herdr `prefix+w` opens the workspace picker — `herdr-workspaces` re-adds the same sequence with an accurate label. ### Scroll strategy — keys instead of wheel ```typescript export default { gestures: { scroll: { strategy: 'keys' }, }, } ``` ### Popup-heavy workflow — lazygit, yazi, scratch shell Uses function form to keep default drawer buttons and append popup triggers: ```typescript export default { name: 'dev', drawer: { buttons: (defaults) => [ ...defaults, { id: 'lazygit', label: 'Git', description: 'Open lazygit popup (prefix + g)', action: { type: 'send', data: '\x02g' }, }, { id: 'yazi', label: 'Files', description: 'Open yazi file manager popup (prefix + y)', action: { type: 'send', data: '\x02y' }, }, { id: 'scratch', label: 'Scratch', description: 'Open scratch shell popup (prefix + `)', action: { type: 'send', data: '\x02`' }, }, ], }, } ``` Requires matching tmux bindings (see `references/tmux-basics.md` popup section). ## Guardrails - **Do not `import` from `'remobi'`** — the CLI calls `defineConfig()` internally, so configs just export a plain object. Using `import { defineConfig } from 'remobi'` fails when the config lives outside a project with remobi installed. - **Never invent root keys.** The validator rejects unknown keys with a path-based error. - **Use `drawer.buttons`, never `drawer.commands`** — the latter was renamed and no longer works. - **`send` actions require `data`** — omitting it fails validation. - **Non-`send` actions must not have `data` or `keyLabel`** — validator rejects them. - **`floatingButtons` is an array of groups** — wrap buttons in `{ position, buttons }`. - **`toolbar` has `row1` and `row2`** — there is no `row3` or flat `buttons` key on toolbar. - **`mobile.initData`** is `string | null` — set to `null` to disable, not `false` or `''`. - **`reconnect`** has only `enabled: boolean` — defaults to `true`. Set `{ enabled: false }` to disable. - **`gestures.scroll` is an object, not a string** — use `{ strategy: 'wheel' }` or `{ strategy: 'keys' }`, never a bare `'wheel'` / `'keys'` string. ## Validation ```bash remobi serve --port 18765 -- /bin/true ``` A zero exit means the config is valid when the file is in the normal search path (current directory or `~/.config/remobi/`). For a custom location, validate explicitly: ```bash remobi serve --config /path/to/remobi.config.ts --port 18765 -- /bin/true ``` Any error output means fix the reported paths before proceeding. ### Common validation errors | Error | Cause | Fix | |-------|-------|-----| | `config.` | Invented or legacy root key | Remove it; only allowed root keys are valid | | `config.drawer.commands` | Old key name | Rename to `drawer.buttons` | | `config.toolbar.buttons` | Wrong toolbar shape | Use `toolbar.row1` and/or `toolbar.row2` | | `action.type: expected 'send' \| ...` | Wrong type string | Use exact literal from ButtonAction union | | `action.data: expected string, received undefined` | `send` action missing `data` | Add `data: '\x...'` | | `action.data: expected undefined` | `data` on non-`send` action | Remove `data` from non-`send` actions | | `floatingButtons[0]: expected object` | Flat `ControlButton[]` | Wrap in group: `{ position: 'top-left', buttons: [...] }` | | `mobile.initData: expected string or null` | `false` or `0` passed | Use `null` to disable, or a string to send | | `Cannot find package 'remobi'` | Config uses `import ... from 'remobi'` | Remove the import — export a plain object instead. The CLI calls `defineConfig()` internally | | `gestures.scroll: expected Object, received string` | Bare `'wheel'` / `'keys'` string | Use `{ strategy: 'wheel' }` or `{ strategy: 'keys' }` |