go-booba - Web-based BubbleTea TUIs using libghostty

April 29, 2026 · View on GitHub

Command Reference Latest Release GoDoc Code Of Conduct

go-booba is a Golang module that facilitates embedding BubbleTea Terminal User Interfaces (TUIs) into a Web Browser, served over HTTP with a Ghostty-powered terminal frontend.

There are three ways to use this package:

  • Compile a BubbleTea program to WebAssembly and run it entirely in the browser.
  • Run a BubbleTea backend server and connect to it from the browser over WebSocket or WebTransport.
  • Wrap any local CLI program in a browser terminal with the `booba` command.
booba mascot

Installation

Go (server-side library and CLI tools):

go get github.com/NimbleMarkets/go-booba

npm (TypeScript/JavaScript frontend):

npm install @nimblemarkets/booba

How and What?

The primary enabling technologies of this are:

  • libghostty - Terminal emulation engine
  • ghostty-web - Web-based terminal using Ghostty's VT100 parser via WebAssembly
  • BubbleTea - Terminal UI framework for Go
  • WebAssembly - For running Go code in browsers
  • WebTransport - HTTP/3-based low-latency browser transport (with WebSocket fallback)

The name booba is a portmanteau of the words Boba and Boo!: the key ingredient of Bubble Tea evoking a Ghost's exclamation of joy.

TypeScript API

The BoobaTerminal class wraps ghostty-web's Terminal and provides a high-level API for embedding BubbleTea programs:

import { BoobaTerminal } from './booba/booba.js';

const booba = new BoobaTerminal('terminal-container', {
    cols: 80, rows: 24, fontSize: 14,
    theme: { background: '#1e1e1e', foreground: '#d4d4d4' },
});

await booba.init();
booba.connectWebSocket('ws://localhost:8080/ws');
booba.focus();

BoobaTerminal exposes methods for selection, scrollback, terminal control, mode queries, events, link detection, and custom key/wheel handlers. See docs/TYPESCRIPT_API.md for the full reference.

For adapter usage (WebSocket, WASM, custom), see ADAPTER_USAGE.md.

Embedding a BubbleTea Application in a Web Browser

We can take entire BubbleTea applications and embed them into a Web Browser. The primary limitation is that all of its dependencies can also be compiled to WebAssembly.

Quickstart

The top-level booba.Run picks the right runtime for the build target, so a single main.go works for both the native terminal and the browser:

package main

import (
    "log"

    booba "github.com/NimbleMarkets/go-booba"
)

func main() {
    if err := booba.Run(initialModel()); err != nil {
        log.Fatal(err)
    }
}

For easier porting from Bubble Tea — or when you need the program handle for Send, Quit, etc. — use booba.NewProgram:

func main() {
    bp := booba.NewProgram(initialModel())
    if _, err := bp.Run(); err != nil {
        log.Fatal(err)
    }
}

Build and run natively with go run ./cmd/myapp. Build for the browser with go run github.com/NimbleMarkets/go-booba/cmd/booba-wasm-build -o web/app.wasm ./cmd/myapp/.

For finer control, the wasm subpackage exposes the browser bridge directly, and native code can construct a tea.Program the usual way.

Web Frontend for BubbleTea-based service

Otherwise, one might have a BubbleTea program running on a remote machine. While one might use ssh to access it, booba enables an HTTP-based interface to it. The top-level serve package is the single server implementation for that path, serving the embedded Ghostty frontend and bridging browser clients over WebSocket or WebTransport.

Middleware

The serve package exposes three composable middleware layers that mirror and extend the Wish/sip shape:

LayerTypeWrapsInstall
1. HandshakeConnectMiddleware*http.Request for both WS upgrade and WT CONNECTWithConnectMiddleware(...)
2. Session I/OSessionMiddlewareSession (transport byte streams)WithSessionMiddleware(...)
3. HandlerMiddlewareHandler (per-session tea.Model construction)WithMiddleware(...)

serve.LiftHTTPMiddleware(mw) adapts any func(http.Handler) http.Handler into a ConnectMiddleware that runs on both the WebSocket and WebTransport handshake paths — so the full chi/gorilla/tollbooth/otelhttp ecosystem is reusable at the handshake.

Built-in middleware subpackages:

  • serve/middleware/osc52gate — allow/deny/audit OSC 52 clipboard-write escapes in the outbound stream.
  • serve/middleware/recover — catch panics during handler construction.
  • serve/middleware/logging — slog-based session start/end logging.
  • serve/sipmetrics — Prometheus counters/gauges/histogram for session lifecycle and byte throughput (isolated behind a subpackage so the main module avoids a prometheus/client_golang dep).
srv := serve.NewServer(cfg,
    serve.WithConnectMiddleware(serve.LiftHTTPMiddleware(myHTTPMiddleware)),
    serve.WithSessionMiddleware(osc52gate.New(osc52gate.ModeDeny)),
    serve.WithMiddleware(recover.New(), logging.New()),
)

Basic Auth, connection limits, and cfg.IdleTimeout are auto-installed by NewServer when the corresponding Config fields are set.

Config knobs

Beyond the listener/TLS/auth fields, serve.Config exposes protocol-safety knobs with sensible defaults:

  • MaxPasteBytes (default 1 MiB) — cap bracketed-paste payloads from clients.
  • ResizeThrottle (default 16ms) — debounce inbound resize messages.
  • MaxWindowDims$ (\text{default} 4096 \times 4096) — \text{reject} \text{adversarial} \text{resize} \text{values} \text{before} \text{the} \text{PTY} $ioctl.
  • InitialResizeTimeout (default 10s) — deadline on the initial Resize message after the handshake.
  • IdleTimeout — close sessions with no inbound bytes for the given duration (0 = disabled).

See docs/DESIGN_MIDDLEWARE.md for the design rationale.

booba CLI Command Wrapper

The booba command wraps any local CLI program and serves it in the browser through the same embedded terminal stack.

Build and run it from the repository root:

task build-cmd-booba
./bin/booba --listen 127.0.0.1:8080 -- htop

Everything after -- is treated as the wrapped command and its arguments:

./bin/booba --listen 127.0.0.1:8080 -- bash
./bin/booba --listen 127.0.0.1:8080 -- python3 -q
./bin/booba --listen 127.0.0.1:8080 -- vim README.md

Build and run the example server from the repository root:

task build-cmd-booba-view-example-native
./bin/booba-view-example --listen 127.0.0.1:8080

The browser page served from http://127.0.0.1:8080/ will use WebTransport automatically when available and fall back to WebSocket otherwise. When you provide --cert-file and --key-file, the same public port is used for HTTPS/WSS over TCP and HTTP/3 WebTransport over UDP.

Useful flags

./bin/booba-view-example --listen 127.0.0.1:8080 --http3-port=-1
./bin/booba-view-example --listen 127.0.0.1:8080 --origin=https://app.example.com,https://*.example.net
./bin/booba-view-example --listen 127.0.0.1:8080 --cert-file=server.crt --key-file=server.key
./bin/booba-view-example --listen 127.0.0.1:8080 --username=admin --password=secret
./bin/booba-view-example --listen 127.0.0.1:8080 --username=admin --password-file=/run/secrets/booba
BOOBA_PASSWORD=secret ./bin/booba-view-example --listen 127.0.0.1:8080 --username=admin

Notes:

  • --http3-port=-1 disables WebTransport and uses WebSocket only.
  • the default bind address is loopback (127.0.0.1); non-loopback --listen addresses require --cert-file and --key-file.
  • browser origins are same-host by default; use --origin to allow additional cross-origin browser clients. Patterns are Go path.Match shell globs, not regex — so *.example.com matches one subdomain level, and [abc] is a character class. Patterns are tested against both scheme://host and the bare host.
  • Basic Auth requires --cert-file and --key-file; the server refuses to start otherwise.
  • prefer --password-file or $BOOBA_PASSWORD over --password: the flag form leaks the secret into argv, shell history, and ps listings. Precedence is flag > file > env.
  • static frontend files are embedded with go:embed, so after frontend asset changes you must rebuild the Go binary you run.
  • reverse-proxy deployment: booba's index.html resolves every endpoint against document.baseURI, so hosting at a non-root path (e.g. nginx location /terminal/) works as long as the proxy strips the prefix before forwarding. For custom frontends, use the exported resolveBoobaURLs(document.baseURI) helper from @nimblemarkets/booba.

booba-sip-client

The booba-sip-client command connects to a running booba server and provides an interactive terminal session or dump-frames mode for diagnostics. The name comes from sip, a tool by @Gaurav-Gosain that pioneered a similar wire protocol; go-booba adopted and extended that protocol.

Build and run it:

task build-cmd-booba-sip-client
./bin/booba-sip-client ws://localhost:8080/ws

WebTransport

booba-sip-client can dial servers over WebTransport by using an https:// URL:

booba-sip-client https://host:8443/wt

WebTransport uses HTTP/3 over QUIC and offers lower head-of-line-blocking latency than WebSocket. Requires the server to have HTTP/3 enabled (serve.DefaultConfig() enables it automatically; set HTTP3Port: -1 to disable). For self-signed dev certs, use --insecure-skip-verify.

Open Collaboration

We welcome contributions and feedback. Please adhere to our Code of Conduct when engaging our community.

Acknowledgements

Thanks to the Ghostty developers, the ghostty-web developers, and to Charm.sh for making the command line glamorous with Bubble Tea.

Thanks to @BigJK for the initial inspiration when I was exploring this before libghostty.

Thanks to @Gaurav-Gosain, who cotemporaneously invented sip. That sip tool is similar to this library, but works with xterm.js. We adopted and extended its protocol and it also inspired our CLI tool.

License

Released under the MIT License, see LICENSE.txt, except for the following files:

  • The Booba Ghost image, ./etc/booba.png remains All Rights Reserved by Neomantra Corp. You may use it only in unmodified form and only as part of this project (e.g., in forks or distributions of the project). You may not extract it for unrelated use, modify it, or redistribute it separately without explicit permission.

Copyright (c) 2026 Neomantra Corp.


Made with :heart: and :fire: by the team behind Nimble.Markets.