clawdesk-tui
Terminal user interface built with ratatui for interactive CLI usage. Provides a full-featured chat interface in the terminal with session management, model switching, and streaming display.
Dependencies
Internal: clawdesk-types, clawdesk-domain, clawdesk-agents, clawdesk-providers
External: ratatui, crossterm, tokio, serde, tracing
Modules
| Module | Description |
|---|---|
app | Application state and event loop |
ui | UI layout and rendering |
input | Keyboard input handling and keybindings |
widgets | Custom widgets (chat history, input box, status bar) |
Key Types
/// TUI application state
pub struct App {
pub sessions: Vec<Session>,
pub active_session: Option<usize>,
pub input_buffer: String,
pub messages: Vec<DisplayMessage>,
pub status: StatusBar,
pub mode: AppMode,
}
#[derive(Debug, Clone, PartialEq)]
pub enum AppMode {
Normal,
Insert,
SessionList,
ModelSelect,
Help,
}
/// Display-formatted message for the UI
pub struct DisplayMessage {
pub role: Role,
pub content: String,
pub model: Option<String>,
pub timestamp: String,
pub streaming: bool,
}
/// Run the TUI application
pub async fn run(config: AppConfig) -> anyhow::Result<()> {
let terminal = ratatui::init();
let app = App::new(config).await?;
loop {
terminal.draw(|frame| ui::render(frame, &app))?;
if let Some(event) = crossterm::event::read()? {
match input::handle(event, &mut app) {
Action::Quit => break,
Action::SendMessage => app.send_message().await?,
Action::SwitchSession(idx) => app.switch_session(idx),
_ => {}
}
}
}
ratatui::restore();
Ok(())
}
Keybindings
| Key | Mode | Action |
|---|---|---|
i | Normal | Enter insert mode |
Esc | Insert | Return to normal mode |
Enter | Insert | Send message |
Ctrl+N | Any | New session |
Ctrl+S | Any | Session list |
Ctrl+M | Any | Model selector |
Ctrl+C | Any | Cancel stream / Quit |
? | Normal | Help screen |
↑/↓ | Normal | Scroll history |
Example Usage
# Launch the TUI
clawdesk tui
# Launch with a specific model
clawdesk tui --model claude-sonnet-4-20250514
use clawdesk_tui::run;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let config = load_config()?;
run(config).await
}
info
The TUI uses the same agent pipeline as the gateway, providing a full-featured chat experience without running an HTTP server.