{"id":"e25421a1-155a-405a-b3a5-922c714da420","slug":"limoni","kind":"mcp","name":"limoni","summary":"Terminal UI engine for Go that tests can click and AI agents can drive (MCP). Zero-allocation rendering, immediate mode + Elm architecture, 3D, images, charts, accessibility, WebAssembly.","owner":"thebanri","repoUrl":"https://github.com/thebanri/limoni","license":"Apache-2.0","licenseTier":"permissive","trust":"community","stars":154,"match":null,"fewStars":false,"format":"mcp","createdAt":"2026-08-06T20:25:13.000Z","addedAt":"2026-10-14T03:44:05.435Z","logoUrl":"https://avatars.githubusercontent.com/u/41839715?u=45a7d81d9dc3a1cc7e7ef44b758c2697952d4f54&v=4","linkOnly":false,"collection":null,"skillPath":null,"categories":["design-media","cloud-infra","developer-tools"],"tags":["cli","go","golang","terminal","terminal-ui","tui","tui-framework","accessibility"],"runtime":"go","local":true,"cloudCapable":false,"cloudCapability":"unknown","cloudReadyOnPlatform":false,"needsApiKey":false,"reviewed":false,"syncedAt":"2026-10-15T02:44:28.607Z","sample":false,"url":"https://conduits.dev/tools/limoni","language":"Go","transports":["stdio"],"install":{},"requirements":{"env":[],"upstreamMayCharge":false},"tools":null,"readme":"<p align=\"center\">\n  <img src=\"assets/logo.png\" alt=\"Limoni Logo\" width=\"160\" />\n</p>\n\n<h1 align=\"center\">🍋 Limoni</h1>\n\n<p align=\"center\">\n  <strong>A terminal UI engine for Go that tests can click, AI agents can drive,<br>and the garbage collector never sees.</strong>\n</p>\n\n<p align=\"center\">\n  <a href=\"https://github.com/thebanri/limoni/actions\"><img src=\"https://img.shields.io/github/actions/workflow/status/thebanri/limoni/ci.yml?branch=main&style=flat-square&logo=github\" alt=\"Build Status\"></a>\n  <a href=\"https://pkg.go.dev/github.com/thebanri/limoni\"><img src=\"https://img.shields.io/badge/go.dev-reference-007d9c?style=flat-square&logo=go&logoColor=white\" alt=\"Go Reference\"></a>\n  <a href=\"https://golang.org\"><img src=\"https://img.shields.io/badge/go-%3E%3D%201.25-blue?style=flat-square&logo=go\" alt=\"Go Version\"></a>\n  <a href=\"LICENSE\"><img src=\"https://img.shields.io/badge/license-Apache_2.0-blue?style=flat-square\" alt=\"License\"></a>\n  <a href=\"docs/benchmarks.md\"><img src=\"https://img.shields.io/badge/draw%20path-0_B%2Fop-brightgreen?style=flat-square\" alt=\"Zero Allocations\"></a>\n</p>\n\n<p align=\"center\">\n  <a href=\"README.md\">English</a> • <a href=\"README_TR.md\">Türkçe</a>\n</p>\n\n<p align=\"center\">\n  <strong><a href=\"https://thebanri.github.io/limoni/\">▶ Try it in your browser</a></strong> — the same engine compiled to WebAssembly, no install.\n</p>\n\n<p align=\"center\">\n  <img src=\"assets/3d.gif\" alt=\"Limoni rendering a shaded 3D model in the terminal\" width=\"100%\" />\n</p>\n\n---\n\n## Quick start\n\n```bash\ngo get github.com/thebanri/limoni\n```\n\n```go\npackage main\n\nimport \"github.com/thebanri/limoni\"\n\nfunc main() {\n\tlimoni.Run(func(f *limoni.Frame, ev *limoni.Event) bool {\n\t\tif ev != nil && ev.Type == limoni.EventKey && ev.Key.Type == limoni.KeyEsc {\n\t\t\treturn false // quit\n\t\t}\n\t\tf.RenderComponent(limoni.Border(\n\t\t\tlimoni.Center(limoni.Label(\"Hello from Limoni 🍋  (Esc quits)\", limoni.Bold())),\n\t\t\tlimoni.SymbolsRounded,\n\t\t\tlimoni.Fg(limoni.Hex(\"#FFCC00\")),\n\t\t), f.Area())\n\t\treturn true\n\t})\n}\n```\n\nOr generate a project that runs straight away, with a test already written:\n\n```bash\ngo run github.com/thebanri/limoni/cmd/limoni@latest new myapp    # -template counter|dashboard|form|chat|ssh\ncd myapp && go mod tidy && go run .\ngo test ./...                                                     # a uitest test comes with every template\n```\n\n**Next:** [Getting started](docs/getting-started.md) · [Widget gallery](docs/widget-gallery.md) · [Examples](#examples)\n\n---\n\n## Why Limoni\n\n### 1. Your TUI has a semantic tree, so tests and agents address widgets by name\n\nMost terminal automation, such as [termwright](https://github.com/fcoury/termwright) or\n[mcp-tui-test](https://github.com/GeorgePearse/mcp-tui-test), parses the rendered character\ngrid, so a test breaks when the layout moves by one column. A Limoni app builds a\nsemantic tree every frame (the same tree a screen reader uses), and the tools below\nwork on that tree instead.\n\n**`uitest`: Playwright-style tests.** Checks wait instead of sleeping, and a failure\nprints the whole tree of the last frame.\n\n```go\npage := uitest.Run(t, 80, 24, app.draw)            // in process: no terminal needed\n\npage.GetByRole(\"input\", \"New task\").Type(\"Tag v1.0\")\npage.GetByRole(\"button\", \"Add task\").Click()\npage.Expect(page.GetByRole(\"list-item\", \"\").Within(page.GetByRole(\"list\", \"Tasks\"))).ToHaveCount(3)\npage.Expect(page.GetByID(\"status\")).ToContainLabel(\"Added\")\n```\n\n**`limoni-mcp`: let an AI agent drive the app.** It gives Claude Code, Cursor or any\nMCP client eight tools (`tree`, `click`, `type_text`, `wait_for`…) that work on the\nsame tree. In one recorded run, Claude Code completed a release checklist in 17 tool\ncalls. That run included typing a deploy token the agent could not read back.\n\n```bash\ngo run -tags limoni_debug ./examples/agent_checklist\nclaude mcp add limoni -- limoni-mcp -socket \"$XDG_RUNTIME_DIR/limoni-checklist.sock\"\n```\n\nThe automation socket only exists in `-tags limoni_debug` builds. It is closed by\ndefault, only the same user can connect, and secret fields are never exposed.\n→ [Semantic automation, MCP and uitest](docs/automation.md)\n\n### 2. Fast where you can feel it\n\n- **Zero heap allocations per frame** for the renderer, every widget's drawing, and the click handling of the common input widgets, enforced in CI, so animations don't stutter from GC pauses. That includes widgets that drag or call your code — Table, Slider, Dialog, TreeView, CommandPalette — once their state is attached. [The details](docs/architecture.md#5-allocation-free-interactive-frames).\n- **Few bytes per frame.** Blank runs become `ECH`/`EL` and repeats become `REP`: a full-screen redraw is **377 bytes**, and an idle app sends **nothing**. Bytes, not CPU, are what you feel over SSH.\n- **Virtual tables and lists.** One million rows scroll at ~2.7 ms a frame, because only visible rows are touched.\n\n| Measured on a Ryzen 5 5600, Go 1.27.1 | Latency | Allocations |\n| :--- | ---: | ---: |\n| Full-screen diff, 120×40, every cell changed | ~50 µs | 0 |\n| 10% of the screen changed | ~11 µs | 0 |\n| Nothing changed | ~2 ns | 0 |\n| 100 layered blocks, drawn and diffed | ~68 µs | 0 |\n\nAbsolute numbers depend on the machine. The comparisons against Ratatui 0.30.2,\nUltraviolet and Bubble Tea v1.3.10, including which ratios are *not* meaningful, are in\n[docs/benchmarks.md](docs/benchmarks.md) and the [methodology](docs/benchmark-methodology.md).\nOne earlier run showed a 4,700× lead; it came from a bug in the benchmark harness,\nand the [methodology](docs/benchmark-methodology.md#the-4700-that-was-a-harness-bug) explains how it was caught.\n\n### 3. Batteries that other TUI libraries leave to you\n\n| | |\n| :--- | :--- |\n| 🕶️ **3D** | A software rasteriser for OBJ/STL/PLY/GLB with Lambert and Gouraud shading, drawn in terminal cells |\n| 🖼️ **Images** | Kitty, Sixel, iTerm2 and half-block fallback |\n| 📊 **Charts** | Braille line charts, bar charts, pie charts, sparklines |\n| 📝 **Markdown** | Headings, lists (nested, numbered, tasks), quotes, tables, links, and fenced code highlighted by language — including a block still streaming in |\n| ♿ **Accessibility** | A semantic tree, a screen-reader line mode, `NO_COLOR`, high contrast, reduced motion |\n| 🧬 **Unicode** | UAX #29 grapheme clusters (Unicode 17.0, all 766 conformance tests), so flags and emoji families take one cell |\n| 🔗 **Hyperlinks** | OSC 8 links, in markdown or any style — and where the terminal cannot show them, the address is printed instead |\n| 📃 **Inline mode** | Render in a band of the normal screen, like `gum`, with scrollback intact |\n| ⏺️ **Session replay** | Record a session, replay it as a regression test ([docs](docs/session-recording.md)) |\n| 🌐 **Everywhere** | Linux, macOS, BSD, Windows, WebAssembly in the browser, SSH sessions |\n\nThe only dependencies are `golang.org/x/sys` and `golang.org/x/crypto`.\n\n<table>\n  <tr>\n    <td><img src=\"assets/treeview.gif\" alt=\"TreeView with image preview\" /></td>\n    <td><img src=\"assets/chart.gif\" alt=\"Charts\" /></td>\n  </tr>\n  <tr>\n    <td align=\"center\"><code>go run ./examples/treeview</code></td>\n    <td align=\"center\"><code>go run ./examples/charts</code></td>\n  </tr>\n</table>\n\n### Is Limoni the right choice?\n\n**Pick Limoni if** you are writing a terminal app in Go and any of these matter:\n\n- **You want to test the UI, or let an AI agent use it**, by widget role and label\n  rather than by screen coordinates (`uitest`, `limoni-mcp`).\n- **It redraws a lot**: dashboards, log viewers, monitoring, games, animation,\n  anything over SSH. The draw path makes no garbage and sends few bytes.\n- **It needs things other libraries leave to you**: 3D models, images, charts,\n  markdown, a million-row table, screen-reader support, WebAssembly in the browser.\n- **You want both styles in one library**: immediate mode (`limoni.Run`) for\n  dashboards, the Elm architecture (`limoni.RunProgram`) for forms and wizards.\n- **You want a small dependency tree**: `golang.org/x/sys` and `golang.org/x/crypto`.\n\n**Pick something else if:**\n\n- You need a **stable 1.0 API** today. Limoni is pre-1.0 (see [Status](#status)).\n- You rely on the **Charm ecosystem** (Bubbles, Huh, Glamour, Wish) and its\n  community. Bubble Tea is the larger and older project. If you already have a\n  Bubble Tea app, `compat/bubbletea` runs your models on Limoni\n  ([migration guide](docs/bubbletea-migration.md)), so you can try it without a rewrite.\n- You are writing **Rust**: use Ratatui.\n- The app is a **one-shot prompt** (a single question, a spinner): a small prompt\n  library is less to learn.\n\nA feature-by-feature table against Bubble Tea v1/v2 and Ratatui 0.30 is in\n[docs/comparison.md](docs/comparison.md).\n\n---\n\n## Built with Limoni: zest\n\n<p align=\"center\"><img src=\"assets/zest.gif\" alt=\"zest filtering a million-line log to billing errors, opening a line's details, then clearing the filter to show it in context\" width=\"100%\" /></p>\n\n[**zest**](cmd/zest) is a log viewer and Limoni's flagship app. It follows files and pipes, colours by\nlevel, and filters a million lines without stalling. A 67 MiB, 1,000,000-line log is on screen in about half\na second.\n\n```bash\ngo install github.com/thebanri/limoni/cmd/zest@latest\nzest -demo 1000000        # or: zest app.log, kubectl logs -f pod | zest\n```\n\nOr [try it in the browser](https://thebanri.github.io/limoni/): the \"Logs · zest\" scene.\n\n### And: globe\n\n[**globe**](apps/globe) is a world you can turn, search, zoom into and pin — and what a Limoni\napplication looks like from the outside, since it is a module of its own that depends on a\npublished Limoni and uses nothing but its public API.\n\n```bash\ngo install github.com/thebanri/limoni/apps/globe@latest\nglobe -at Türkiye\n```\n\n`go install` puts the binary in `$(go env GOPATH)/bin` — `~/go/bin` unless you changed it —\nwhich a fresh Go install does not add to your `PATH`. If `globe` is \"command not found\", add\nthat directory to `PATH` or run `~/go/bin/globe`.\n\nEverything on it is in the semantic tree, so \"find Turkey on the world map\" is something an agent\ncan do over MCP: type into `search`, click the row, read `image#globe` back as\n`value=\"39.3°N 34.5°E · zoom 2.6×\"`. Nothing of it reaches you when you import Limoni — a\ndirectory with its own `go.mod` is not part of the module around it.\n\n### And your shell: backdrop-shell\n\n<p align=\"center\"><img src=\"assets/backdrop-shell.gif\" alt=\"backdrop-shell: a fish prompt over an animated aurora; ls lists files in front of it, backdrop-shell opacity 0.9 brightens it at once, and backdrop-shell enable -scene synthwave switches the running terminal to a striped sunset over a neon grid\" width=\"100%\" /></p>\n\n[**backdrop-shell**](apps/backdrop-shell) puts a moving scene behind your shell, in any terminal —\nan aurora, a night city, a starfield, a synthwave sunset, a picture, or ASCII art of your own that\nblinks or drifts. The text in front stays sharp, selecting copies only what the shell wrote, a\nnarrower window keeps every line, and the opacity or the scene changes at once in every open\nterminal. It costs about half a percent of one core, and nothing while the window is in the\nbackground.\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/thebanri/limoni/main/apps/backdrop-shell/install.sh | sh\nbackdrop-shell opacity 0.3                # quieter, in every open terminal\nbackdrop-shell enable -scene synthwave    # or -image wall.jpg, or -art my-art.txt\n```\n\nDrawing and animating your own: [docs/backdrop-art.md](docs/backdrop-art.md). The same scenes go\nbehind any Limoni application with `limoni.WithBackdrop(backdrop.Aurora())`.\n\n### And a game: Castle Lemonstein\n\n<p align=\"center\"><img src=\"assets/castle-lemonstein.png\" alt=\"Castle Lemonstein: the moonlit castle title screen with gold lettering and burgundy banners\" width=\"100%\" /></p>\n\n[**Castle Lemonstein**](apps/castle-lemonstein) is a short first-person game. Squirt the rats and find ten lemons inside a banner-lined stone castle,\nand the gate to the lair lifts on Ratatui, king of the rats, who has a crown, a health bar and cheese to throw.\n\n```bash\ngo install github.com/thebanri/limoni/apps/castle-lemonstein@latest\ncastle-lemonstein\n```\n\nOr [play it in the browser](https://thebanri.github.io/limoni/?app=castle-lemonstein), sound included.\n\nIt is a raycaster drawn two pixels to a cell in half blocks, with lit textures, pixel-art rats, 3D half lemons\nray-traced per pixel, and sound synthesised at start-up. A frame allocates nothing, through the diff too. It\nholds keys down with `WithKeyReleases`, so walking is smooth in kitty, Ghostty and WezTerm. Runs are scored\non aim and time, onto a leaderboard kept in a file, or in the browser's storage.\n\n[**Lemon Drop**](apps/lemondrop) is falling blocks where a piece that lands crumbles into sand of its colour.\nA run of one colour that reaches from wall to wall is the line: it flashes and goes, and what rested on it\nfalls, sometimes into the next one.\n\n```bash\ngo install github.com/thebanri/limoni/apps/lemondrop@latest\nlemondrop\n```\n\nOr [play it in the browser](https://thebanri.github.io/limoni/?app=lemondrop), with sound and a\nworld leaderboard.\n\n[**Lemon Snake**](apps/lemonsnake) is a snake that eats lemons. Every five, a monster climbs out of the far\ncorner and hunts the head; spit the seeds your lemons bring at it, and where it steps on\nthe body it bites the rest off. Every level is faster, and so is its monster. `t` switches between five\ncolour themes at any time. It glides in half-block pixels, plays soft synthesised sounds, and\nsends finished runs to the same shared leaderboard as Lemon Drop.\n\n```bash\ngo install github.com/thebanri/limoni/apps/lemonsnake@latest\nlemonsnake\n```\n\nOr [play it in the browser](https://thebanri.github.io/limoni/?app=lemonsnake), with its sound and the\nworld leaderboard.\n\n<p align=\"center\"><img src=\"assets/pikap.gif\" alt=\"pikap playing its demo record: the needle is put down half-way in, the record is dragged back and forth to scratch, then it is drawn in ASCII\" /></p>\n\n[**pikap**](apps/pikap) is a record player drawn in half blocks (or ASCII). It follows whatever the computer is playing\n(Spotify, a browser tab, mpv, anything on MPRIS): the cover turns on the record and the tonearm moves\nacross it. Dragging the record or the arm moves the song back and forth. Given files, it plays them\nitself, and then pausing slows the music down with the platter and dragging the record scratches.\n\n```bash\ngo install github.com/thebanri/limoni/apps/pikap@latest\npikap            # follow what is playing\npikap ~/Music    # or play files\n```\n\n---\n\n## Two ways to write an app\n\n| | Immediate mode | Declarative (Elm architecture) |\n| :--- | :--- | :--- |\n| **Entry point** | `limoni.Run(func(f, ev) bool)` | `limoni.RunProgram(ctx, model)` |\n| **State lives in** | your closure | a `limoni.Model` with `Init` / `Update` / `View` |\n| **Best for** | dashboards, 3D, games, animation | forms, wizards, CRUD tools, async work |\n| **Runtime gives you** | a redraw on every event | commands, cancellation, panic recovery, session recording |\n\nBoth use the same renderer and widgets and are available from the root package.\n[`examples/counter`](examples/counter) is a complete declarative app in under 80 lines.\nComing from Bubble Tea? See the [migration guide](docs/bubbletea-migration.md).\n\n---\n\n## Widgets\n\n| Category | Widgets |\n| :--- | :--- |\n| **Layout** | `VStack` / `HStack` / `ZStack`, `Flex`, `Border`, grid layout (`layout.GridLayout`), `Block` (with border merging), `SplitPane` (draggable), `Viewport`, `Dialog`, `Popup`, `StatusBar` |\n| **Data** | `Table` (virtual), `List` (virtual), `TreeView`, `FilePicker`, `Calendar`, `Sparkline`, `ProgressBar`, `Gauge`, `LineGauge`, `RichText` |\n| **Charts** | `LineChart`, `BarChart`, `PieChart` — Braille, sextant or quadrant markers |\n| **Input** | `TextInput`, `Autocomplete`, `TextArea`, `Checkbox`, `RadioGroup`, `Select`, `Slider`, `ColorPicker` |\n| **Navigation** | `Tabs`, `Scrollbar`, `CommandPalette`, fuzzy search, keybinding manager |\n| **Feedback** | `Spinner`, `Toast`, desktop notifications (OSC 9 / OSC 99) |\n| **Graphics** | `Canvas` (Braille, sextant, quadrant, block), 3D meshes as dots or as a picture over kitty/iTerm2/Sixel, `Image` |\n| **Text** | `Markdown`, `CodeView` (syntax highlighting), `BigText`, `Label`, `Paragraph` |\n| **Tooling** | `DevTools` HUD (`F12`), themes, validation |\n\n→ [Widget gallery](docs/widget-gallery.md) · [Widget reference](docs/widgets-reference.md)\n\n---\n\n## Examples\n\n| Example | What it shows |\n| :--- | :--- |\n| [`demo`](examples/demo) | The feature trailer: a 3D lemon in ASCII, Braille and half-blocks |\n| [`xray`](examples/xray) | A night city under an X-ray: every cell the diff sends lights up, with measured bytes against a full repaint (`-film` for recording) |\n| [`showcase`](examples/showcase) | Tabs, forms, matrix rain, 3D, command palette, DevTools (`F12`) |\n| [`3d_viewer`](examples/3d_viewer) | OBJ/STL/PLY viewer with shading and orbit controls (`-fps 240`) |\n| [`dashboard`](examples/dashboard) | Live CPU and memory sparklines, a process table, streaming logs |\n| [`table_virtual`](examples/table_virtual) | A one-million-row table |\n| [`agent_checklist`](examples/agent_checklist) | An app built to be driven by an AI agent, and tested with `uitest` |\n| [`todo`](examples/todo) | A declarative todo app with tags, filters and fuzzy search |\n| [`counter`](examples/counter) | The smallest declarative app |\n| [`composable`](examples/composable) | Layout with `VStack`, `HStack`, `Border`, `Flex` |\n| [`forms`](examples/forms) · [`layer_demo`](examples/layer_demo) · [`treeview`](examples/treeview) · [`charts`](examples/charts) | Inputs, modals, file tree, charts |\n| [`ssh_server`](examples/ssh_server) · [`wasm`](examples/wasm) | Serving over SSH, running in the browser |\n\nRun any of them with `go run ./examples/<name>`, or without cloning:\n`go run github.com/thebanri/limoni/examples/3d_viewer@latest`.\nAll examples: [docs/examples.md](docs/examples.md).\n\n---\n\n## Documentation\n\n| | |\n| :--- | :--- |\n| [Getting started](docs/getting-started.md) | Install, first app, both application models |\n| [Architecture](docs/architecture.md) | The flat cell grid, the diff, why the draw path doesn't allocate |\n| [Layout](docs/layout-guide.md) · [Widgets](docs/widgets-reference.md) · [Core API](docs/core-api.md) | Reference |\n| [Semantic automation](docs/automation.md) | The automation socket, `limoni-mcp`, `uitest` and the security model |\n| [Session recording](docs/session-recording.md) | Record and replay sessions as regression tests |\n| [Graphics](docs/graphics-and-canvas.md) · [Animation](docs/animation-and-physics.md) · [Accessibility](docs/accessibility-and-theming.md) | Feature guides |\n| [Drivers and platforms](docs/drivers-and-platforms.md) | Unix, Windows, WebAssembly, SSH |\n| [How it compares](docs/comparison.md) | Against Bubble Tea v1/v2, Lip Gloss and Ratatui, with the caveats |\n| [Benchmarks](docs/benchmarks.md) | Every measured number and how to reproduce it |\n| [Rendering FAQ](docs/faq.md) | Hairline gaps, recommended terminals, emoji |\n| [Stability](docs/stability.md) · [Changelog](CHANGELOG.md) | What may change before 1.0 |\n\nTurkish documentation: [docs/tr](docs/tr/README.md).\n\n---\n\n## Status\n\nLimoni is **pre-1.0**. Patch releases don't break the API; minor releases may, and\nevery break is listed in the [changelog](CHANGELOG.md). The core renderer, layout and\nwidgets are settling; the automation, `uitest` and session packages are new and\nexperimental. See [docs/stability.md](docs/stability.md) for what has to happen\nbefore v1.0.\n\n## Community and contributing\n\n- **Questions and ideas:** [GitHub Discussions](https://github.com/thebanri/limoni/discussions)\n- **Bugs:** [open an issue](https://github.com/thebanri/limoni/issues/new/choose). The template asks for your terminal emulator, since most rendering bugs depend on it.\n- **First contribution:** issues labelled [`good first issue`](https://github.com/thebanri/limoni/labels/good%20first%20issue) are scoped to one file and say how to verify the change. Start with [CONTRIBUTING.md](CONTRIBUTING.md).\n- **Built something?** Add it to [AWESOME.md](AWESOME.md).\n\nAI assistants (Claude, Gemini) were used during development for scaffolding, tests\nand documentation drafts. The architecture was designed, profiled and benchmarked\nby the author, and the benchmark harness exists to check claims, from people or tools.\n\n[Security policy](SECURITY.md) · [Code of Conduct](CODE_OF_CONDUCT.md) · Apache License 2.0\n"}