render agent UIs
in the terminal
a2tea parses the A2UI an agent emits — interleaved with prose in an LLM reply — and draws the described surfaces as Bubble Tea models. JSON in, a live TUI out.
a real renderer, not a mock
Everything a host needs to recognize A2UI in a model's reply and draw it — detection, real Lip Gloss rendering, and the first wired interaction round-trip.
Detect A2UI in any reply
Contains and Scan split an assistant message into ordered parts of prose and typed A2UI messages — no hand-rolled JSON sniffing.
Scan reference →Real Lip Gloss rendering
Text variants, rounded Cards, Column / Row / List layout, Dividers and focusable Buttons — drawn with charm.land/lipgloss/v2.
Wire format →The real A2UI protocol
Targets A2UI v0.9 via tmc/a2ui. a2tea invents no component types — it decodes and renders the actual A2UI catalog.
Read the spec notes →Wired interaction
Tab / Shift+Tab cycle focusables; Enter emits event.ButtonClicked and a protocol-native ClientMessage back to the agent.
Events →Embeddable by design
Render returns a render.Model child that never calls tea.Quit — the host owns the terminal, the layout, and the theme.
Composition →Run it standalone
Wrap any surface in a2tea.Standalone to quit on q / Esc and forward terminal size — perfect for examples and manual testing.
See examples →reply in, surface out
Agent emits A2UI
Messages arrive interleaved with prose, wrapped in <a2ui-json> tags.
Scan splits it
You get ordered []Part — each a chunk of Text plus typed Messages.
Render draws it
Messages composite into an embeddable render.Model the host draws.
The message lifecycle is applied in order — updateComponents composites by ID, updateDataModel resolves bindings, deleteSurface clears the surface.
a host is a handful of lines
Feed an assistant reply to Scan, render each part's text as prose, and hand each part's messages to Render. No quit handling — the model embeds into your program.
Full quickstart →parts, err := a2tea.Scan(reply) for _, p := range parts { if p.Text != "" { renderProse(p.Text) } model, err := a2tea.Render(p.Messages) if err != nil { continue // no renderable surface } draw(model) // an embeddable tea.Model }