Skip to content

Instantly share code, notes, and snippets.

@froody
Last active June 13, 2026 10:50
Show Gist options
  • Select an option

  • Save froody/87d20463ccd1a3eae552997ae16ced45 to your computer and use it in GitHub Desktop.

Select an option

Save froody/87d20463ccd1a3eae552997ae16ced45 to your computer and use it in GitHub Desktop.

Rewriting the goose terminal user interface (TUI) in Rust using the Ratatui framework is highly feasible and would be a fantastic architectural alignment for the project.

Because goose itself is written in Rust, replacing the Node.js/TypeScript-based TUI (ui/text) with a native Rust implementation would eliminate the Node/pnpm dependency, reduce binary package sizes, and provide a single, ultra-fast, statically compiled binary.

Below is an in-depth analysis of the effort, technical challenges, and step-by-step path for me (as an AI agent) to rewrite the TUI in Rust with Ratatui.


Executive Summary

  • Estimated Effort for Me (goose): 40–50 hours of highly structured, iterative focus (roughly 120–150 agent turns across several task delegations).
  • Overall Difficulty: Medium-High. The core protocol and state management are very straightforward in Rust, but custom rich content rendering (beautifully rendering markdown, nested lists, status spinners, folded tool logs, and multiline text inputs with paste detection) in Ratatui's immediate-mode framework requires high precision.
  • Feasibility: Excellent. All foundational blocks (the Agent Client Protocol client library in Rust, async tokio runtimes, crossterm integration) already exist inside the workspace.

1. Architectural Comparison: React-Ink vs. Ratatui

Feature Current Stack (ui/text with React + Ink) Proposed Stack (Rust + Ratatui) Impact of Change
Rendering Model Retained/Declarative: React re-renders virtual DOM; Ink handles diffing and outputs ANSI escape codes. Immediate Mode: The entire screen is drawn from scratch on every tick/event (using drawing commands onto a buffer). Requires shifting from a reactive "component tree" mindset to state-machine drawing loops.
Async & I/O Node Streams + child_process.spawn. tokio::process::Command + tokio async streams. Rust's async system is much more performant and type-safe for handling fast streaming stdout/stdin.
Distribution Bundled Node, pnpm workspace, @aaif/goose-sdk wrapper, and platform-specific prebuilts. Zero-dependency, single static binary (built directly into goose-cli). Massive improvement in distribution, developer setup, and launch speed.

2. What is Already Available in Rust (The "Easy" Parts)

A significant portion of the back-end plumbing is already complete in Rust:

  1. The ACP Client Crate: The workspace already contains the agent-client-protocol crate. The example client (crates/goose-sdk/examples/acp_client.rs) already demonstrates spawning the goose server in a background process, communicating over stdio, and handling structured session notifications (like AgentMessageChunk and ToolCall).
  2. Configuration Access: The crates/goose crate has native structures for configuration, permissions, and extensions. The Rust TUI could interface with these directly rather than querying them strictly through ACP API calls (though talking over ACP is cleaner for architectural separation).

3. The Core Technical Challenges in Ratatui (The "Hard" Parts)

To match the existing TUI's user experience line-for-line, we would need to solve several nontrivial terminal-drawing challenges:

A. Rich Markdown Rendering (High Effort)

The React TUI uses marked and marked-terminal to parse and render styled markdown (headers, tables, lists, inline formatting).

  • Ratatui equivalent: Ratatui does not have a comprehensive built-in Markdown widget.
  • The Solution: We would use pulldown-cmark to parse Markdown into events, and write a custom renderer to transform those events into styled Ratatui Text<'a> or Paragraph<'a> widgets, preserving block quotes, lists, bold/italic, code blocks, and code syntax highlighting.

B. Folded Tool Calls & Multi-line Buffers (Medium Effort)

The chat screen isn't just static text; it's a list of turns containing text, collapsible tool calls (showing input arguments, output results, durations, and statuses), and live indicators.

  • The Solution: We must model the chat log as a list of distinct structured items (e.g., UserPrompt, TextChunk, ToolCall(ToolCallState)). We'll render them linearly inside a scrollable vertical view layout, explicitly tracking the visible range and pixel heights.

C. Multiline Input Box & Paste Handling (Medium Effort)

React Ink handles multi-character pastes and wrapping inside a standard console layout. Ratatui's standard input libraries are often single-line.

  • The Solution: Use the excellent tui-textarea crate, which provides multiline text area support, navigation, and editing. We would wrap it with a paste-detection layer (detecting high-velocity characters to avoid triggering keybind behaviors per-character).

D. Multiple Screens (Onboarding, Configuration, Extensions)

The existing TUI has interactive step-by-step onboarding forms, a searchable provider model picker, and an active extension management layout.

  • The Solution: A clean State pattern (enum Screen { Onboarding(OnboardingState), ActiveChat(ChatState), Configure(ConfigState), Extensions(ExtensionState) }) that changes drawing logic, input handlers, and keybindings based on active state.

4. Step-by-Step Implementation Plan (How I would execute this)

If you task me with this rewrite, I would execute it in 4 distinct, incremental stages to ensure safety, compile-time validity, and high test coverage:

┌────────────────────────────────────────────────────────┐
│ STAGE 1: Foundation & Event Loop                       │
│ ─ Set up crates/goose-cli/src/commands/tui.rs          │
│ ─ Setup Crossterm & Ratatui alternate screen loop      │
│ ─ Integrate tokio async thread to handle ACP Client    │
└───────────────────────────┬────────────────────────────┘
                            ▼
┌────────────────────────────────────────────────────────┐
│ STAGE 2: Core Chat UI & Text Editor                    │
│ ─ Implement custom scrollable Turn log                 │
│ ─ Use tui-textarea for the Multiline Input box         │
│ ─ Build slash command parser (/help, /clear)           │
└───────────────────────────┬────────────────────────────┘
                            ▼
┌────────────────────────────────────────────────────────┐
│ STAGE 3: Advanced Visuals                              │
│ ─ Write pulldown-cmark parser -> Ratatui Span mapper   │
│ ─ Render interactive folded/unfolded Tool Execution    │
│ ─ Port DiffViewer.tsx line highlighter to Rust         │
└───────────────────────────┬────────────────────────────┘
                            ▼
┌────────────────────────────────────────────────────────┐
│ STAGE 4: Configuration Screens & Forms                 │
│ ─ Implement interactive multi-step Onboarding wizard   │
│ ─ Render Extension and Provider management interfaces  │
└────────────────────────────────────────────────────────┘

Summary of Benefits vs. Effort

  • Performance: Incredible. Startup time would drop from ~500ms (Node.js bootstrap) to <5ms. Memory usage would drop from ~100MB to ~15MB.
  • Flicker-Free Resizing: Ratatui uses double-buffering, solving layout overflow bugs that frequently plague React Ink when resizing terminals.
  • Self-Contained: No pnpm, Node, or node-gyp native compilation steps on the target machine.

If you would like to proceed, we can start by drafting the basic layout, event loop, and async ACP thread under crates/goose-cli/src/commands/tui.rs. Let me know if you would like me to begin Stage 1!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment