▄▄▄▄▄▄ ┃ █▀██▀▀▀█▄ █▄ ┃ Rho ██▄▄▄█▀ ██ ┃ Terminal UI for OpenHands ██▀▀█▄ ████▄ ▄███▄ ┃ Built with Rust + Ratatui ▄ ██ ██ ██ ██ ██ ██ ┃ License: MIT ▀██▀ ▀██▀▄██ ██▄▀███▀ ┃
WARNING: This project is highly experimental and under active development. APIs, commands, and behavior may change without notice. Use at your own risk. Bug reports and feedback are welcome on the issue tracker.
A terminal UI for OpenHands, built with Ratatui, that connects to the OpenHands Agent Server.
- Rust toolchain (edition 2021) — install from rustup.rs
- Python 3.12+ — required to build the Agent Server
- An LLM API key (configured after install via
/settingsor--override-with-envs)
git clone https://github.com/VascoSch92/rho.git
cd rho
make installThis will:
- Build the OpenHands Agent Server from source (pinned version from
config.toml) - Compile Rho in release mode
- Install both to
~/.local/bin/
To install to a different location:
make install PREFIX=/usr/localMake sure the install directory is in your PATH:
export PATH="$HOME/.local/bin:$PATH"make uninstallOn first run, use /settings inside the TUI to set your API key, model, and provider. Settings are persisted to ~/.rho/config.toml.
Alternatively, use environment variables with the --override-with-envs flag:
export LLM_API_KEY="..."
export LLM_MODEL="openai/gpt-4o" # optional (default: anthropic/claude-sonnet-4-5-20250929)
rho --override-with-envsEnvironment variables are persisted to ~/.rho/config.toml when --override-with-envs is used, so you only need the flag once.
rhoRho automatically launches the bundled Agent Server. If you pass --debug, logs are written to ~/.rho/rho.log.
- Real-time event streaming via WebSocket
- Action confirmation policies: always / only-risky / never
- Message queue — send multiple messages while the agent is busy; they execute in order
- Slash commands (
/help,/new,/settings,/theme,/resume,/rename,/btw, ...) - Local shell shortcuts — run a command by typing
!<cmd>(e.g.!ls) - One-shot questions — ask the agent without affecting the conversation via
/btw <question> - Collapsible actions +
Ctrl+Eexpand/collapse all - Status bar with timer, model, context usage, cost, and token counts
- Themes — 8 built-in themes (rho, dracula, catppuccin, tokyonight, solarized, gruvbox, github, custom), persisted across sessions
- Markdown rendering with headings, lists, code blocks, tables, and inline code
- Web mode — access the TUI from a browser via
rho web - Headless mode — run tasks without the TUI via
rho headless, with JSON output for scripting
rho --server http://192.168.1.100:8000If an Agent Server is already running on the default port, Rho will detect and reuse it automatically (version is verified against the pinned version in config.toml).
rho web # default: http://127.0.0.1:12000
rho web --host 0.0.0.0 --port 8080 # custom host/portOpen the printed URL in your browser. Each tab gets its own independent session.
# Inline task
rho headless --task "Fix the bug in main.py"
# Task from file
rho headless --file task.txt
# JSON Lines output for machine consumption
rho headless --json --task "Write tests" | jq '.type'
# With timeout and auto-approve (for CI/CD)
rho headless --task "Run linting" --timeout 300 --auto-approveExit codes: 0 success, 1 task error, 2 timeout, 3 connection error.
# Override LLM settings from environment variables (persisted to ~/.rho/config.toml)
rho --override-with-envs
# Server URL
rho --server http://192.168.1.100:8000
# Agent Server session auth (header: X-Session-API-Key)
rho --session-api-key your-session-key
# Workspace directory sent to the agent as the working directory
rho --workspace /path/to/repo
# Theme (also settable via /theme or ~/.rho/config.toml)
rho --theme dracula
# Skip exit confirmation
rho --exit-without-confirmation
# Enable debug logging (writes ~/.rho/rho.log)
rho --debugRho stores user configuration in ~/.rho/config.toml. This file is created automatically when you change settings.
Priority order (highest to lowest):
- CLI flags (
--theme,--override-with-envs) ~/.rho/config.toml(persisted settings)- Embedded defaults (
config.tomlat build time)
Settings that can be configured:
- LLM — provider, model, API key, base URL (via
/settingsor--override-with-envs) - Theme — active theme name (via
/themeor--theme)
The full set of customizations (themes, spinners, keybindings, scroll speed, selector indicator) can be edited in the embedded config.toml or overridden in ~/.rho/config.toml.
8 built-in themes: rho, dracula, catppuccin, tokyonight, solarized, gruvbox, github.
Change theme with /theme (opens picker) or /theme <name>. The selection is persisted.
To add a custom theme, add a section to ~/.rho/config.toml:
[theme.themes.my_theme]
primary = "#e06c75"
accent = "#61afef"
foreground = "#abb2bf"
background = "reset"
muted = "#5c6370"
border = "#3e4452"
error = "#be5046"
success = "#98c379"| Key | Action |
|---|---|
Enter |
Send message |
Alt+Enter / Shift+Enter |
Insert newline in input |
Esc |
Pause agent (when running) / close modals |
Ctrl+Q / Ctrl+C |
Quit (with confirmation unless --exit-without-confirmation) |
Ctrl+E |
Expand/collapse all actions |
Ctrl+T |
Toggle task list panel |
Up/Down |
Scroll messages |
PgUp/PgDn |
Scroll faster |
When actions require confirmation:
| Key | Action |
|---|---|
Left/Right |
Select confirm option |
Enter |
Apply selected option |
Y |
Accept |
N |
Reject |
A |
Always accept (auto-approve future actions) |
Esc |
Defer (pause) |
| Command | Description |
|---|---|
/help |
Show help modal |
/new |
Start a new conversation (clears UI state) |
/resume |
Resume a previous conversation |
/rename <name> |
Rename the current conversation |
/btw <question> |
Ask the agent a one-shot question (not part of the conversation) |
/pause |
Pause the agent |
/usage |
Show token usage details |
/settings |
Show/edit current settings |
/theme [name] |
Pick or set theme |
/confirm [always|risky|never] |
Show/change confirmation policy |
/exit / /quit |
Exit the application |
Prefix any input with ! to run it locally and show output in the message log:
!pwd!ls -la
┌──────────────────────────────────────────────────────────────┐
│ Ratatui TUI (Rust) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ Input Field │ │ Message Log │ │ Confirmation UI │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
│ │ │
│ ┌───────────────┴───────────────┐ │
│ │ Async Event Handler │ │
│ │ (tokio + WebSocket client) │ │
│ └───────────────┬───────────────┘ │
└───────────────────────────┼──────────────────────────────────┘
│ HTTP/WebSocket
▼
┌──────────────────────────────────────────────────────────────┐
│ Agent Server (OpenHands SDK) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Conversation, Tools, Events │ │
│ └─────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
Web mode (rho web):
┌──────────────────────────────────────────────────────────────┐
│ Browser (xterm.js) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ WebSocket client │ │
│ └──────────────────────┬──────────────────────────────┘ │
└─────────────────────────┼────────────────────────────────────┘
│ WebSocket
┌─────────────────────────┼────────────────────────────────────┐
│ ▼ │
│ Rho Web Server (axum + portable-pty) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ PTY <-> spawns rho TUI as subprocess │ │
│ └─────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
Headless mode (rho headless):
┌──────────────────────────────────────────────────────────────┐
│ rho headless │
│ ┌─────────────┐ ┌──────────────────────────────────┐ │
│ │ CLI Args │--->│ HeadlessRunner │ │
│ │ (cli.rs) │ │ - Start conversation │ │
│ └─────────────┘ │ - Stream events via WebSocket │ │
│ │ - Print to stdout (text / JSON) │ │
│ └──────────────┬───────────────────┘ │
│ │ HTTP/WebSocket │
└────────────────────────────────────┼─────────────────────────┘
▼
┌─────────────────────────┐
│ Agent Server │
│ (existing, unchanged) │
└─────────────────────────┘
Rho uses ~/.rho/ for all persistent data:
| Path | Description |
|---|---|
~/.rho/config.toml |
User settings (LLM, theme) |
~/.rho/conversations/ |
Agent Server conversation storage |
~/.rho/agent_settings.json |
LLM settings (shared with openhands-cli) |
~/.rho/rho.log |
Debug log (when --debug is enabled) |
dist/
└── openhands-agent-server/ # Agent Server binary (onedir PyInstaller)
scripts/
└── build-agent-server.sh # Builds the Agent Server from source
src/
├── main.rs # Entry point, terminal setup, embedded server launcher
├── cli.rs # CLI args and subcommands (clap)
├── client/ # HTTP + WebSocket client for Agent Server
├── handlers/ # Key handling, slash commands, settings edits, command execution
├── state/ # App state + message models
├── ui/ # Ratatui UI (widgets, modals, markdown rendering)
├── config/ # Configuration loading, themes, keybindings
├── events/ # Event types (mirroring OpenHands SDK events)
├── headless/ # Headless runner (no TUI, stdout output)
└── web/ # Web server (axum + PTY + xterm.js frontend)
web/
└── index.html # xterm.js frontend (embedded at compile time)
config.toml # Default configuration (embedded in binary)
cargo test
cargo clippy
cargo fmt
# Hot reloading (requires cargo-watch)
cargo watch -x runMIT (see Cargo.toml).