{"id":49182252,"url":"https://github.com/nimblemarkets/go-booba","last_synced_at":"2026-04-23T02:01:07.774Z","repository":{"id":351633691,"uuid":"1211582176","full_name":"NimbleMarkets/go-booba","owner":"NimbleMarkets","description":"TUIs in the browser via BubbleTea, Ghostty, WASM, WebSockets, and WebTransport.","archived":false,"fork":false,"pushed_at":"2026-04-22T20:34:02.000Z","size":966,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-22T22:26:22.885Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/NimbleMarkets.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-04-15T14:33:54.000Z","updated_at":"2026-04-22T20:34:01.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/NimbleMarkets/go-booba","commit_stats":null,"previous_names":["nimblemarkets/go-booba"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/NimbleMarkets/go-booba","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NimbleMarkets%2Fgo-booba","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NimbleMarkets%2Fgo-booba/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NimbleMarkets%2Fgo-booba/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NimbleMarkets%2Fgo-booba/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/NimbleMarkets","download_url":"https://codeload.github.com/NimbleMarkets/go-booba/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/NimbleMarkets%2Fgo-booba/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32162611,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-22T17:06:48.269Z","status":"online","status_checked_at":"2026-04-23T02:00:06.710Z","response_time":53,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-04-23T02:01:06.288Z","updated_at":"2026-04-23T02:01:07.764Z","avatar_url":"https://github.com/NimbleMarkets.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# go-booba - Web-based BubbleTea TUIs using libghostty\n\n\u003cp\u003e\n    \u003ca href=\"https://nimblemarkets.github.io/go-booba/\"\u003e\u003cimg src=\"https://img.shields.io/badge/Command%20Ref-6B2DAD\" alt=\"Command Reference\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://github.com/NimbleMarkets/go-booba/tags\"\u003e\u003cimg src=\"https://img.shields.io/github/tag/NimbleMarkets/go-booba.svg\" alt=\"Latest Release\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://pkg.go.dev/github.com/NimbleMarkets/go-booba?tab=doc\"\u003e\u003cimg src=\"https://pkg.go.dev/badge/github.com/NimbleMarkets/go-booba?utm_source=godoc\" alt=\"GoDoc\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://github.com/NimbleMarkets/go-booba/blob/main/CODE_OF_CONDUCT.md\"\u003e\u003cimg src=\"https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg\"  alt=\"Code Of Conduct\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n`go-booba` is a Golang module that facilitates embedding [BubbleTea](https://github.com/charmbracelet/bubbletea) Terminal User Interfaces (TUIs) into a Web Browser. Generally, these are accessed via a local terminal or via SSH. This module exposes an HTTP-based terminal connection to a BubbleTea program.\n\nThere are two facets we address with this package:\n\n * Running a full BubbleTea program in a Web Browser (via WebAssembly)\n\n * Running a Terminal in a browser that connects over WebSockets to a BubbleTea backend\n\n## How and What?\n\nThe primary enabling technologies of this are:\n\n * [`libghostty`](https://github.com/ghostty-org/ghostty) - Terminal emulation engine\n * [`ghostty-web`](https://github.com/coder/ghostty-web) - Web-based terminal using Ghostty's VT100 parser via WebAssembly\n * [`BubbleTea`](https://github.com/charmbracelet/bubbletea) - Terminal UI framework for Go\n * [`WebAssembly`](https://webassembly.org) - For running Go code in browsers\n\nThe name `booba` is a portmanteau of the words *Boba* and *Boo!*: the [key ingredient of Bubble Tea](https://github.com/charmbracelet/bubbletea#bubble-tea) evoking a [Ghost's exclamation of joy](https://ghostty.org).\n\n## TypeScript API\n\nThe `BoobaTerminal` class wraps ghostty-web's Terminal and provides a high-level API for embedding BubbleTea programs:\n\n```javascript\nimport { BoobaTerminal } from './booba/booba.js';\n\nconst booba = new BoobaTerminal('terminal-container', {\n    cols: 80,\n    rows: 24,\n    fontSize: 14,\n    scrollback: 1000,\n    allowOSC52: true, // Enable OSC 52 clipboard access\n    cursorBlink: true,\n    cursorStyle: 'block',\n    theme: {\n        background: '#1e1e1e',\n        foreground: '#d4d4d4',\n    },\n});\n\nawait booba.init();\nbooba.connectWebSocket('ws://localhost:8080/ws');\nbooba.focus();\n```\n\n### Terminal Options\n\nAll [ghostty-web ITerminalOptions](https://github.com/coder/ghostty-web) are supported: `fontSize`, `fontFamily`, `cols`, `rows`, `cursorBlink`, `cursorStyle`, `scrollback`, `allowOSC52`, `allowTransparency`, `convertEol`, `disableStdin`, `smoothScrollDuration`, and a full `theme` with 16-color palette and cursor/selection colors.\n\n### Selection \u0026 Clipboard\n\n```javascript\nbooba.getSelection()       // Get selected text\nbooba.hasSelection()       // Check if text is selected\nbooba.copySelection()      // Copy to clipboard\nbooba.selectAll()          // Select all text\nbooba.clearSelection()     // Clear selection\nbooba.select(col, row, len) // Select at position\nbooba.selectLines(start, end)\nbooba.getSelectionPosition() // Get selection range\n```\n\n### Scrollback \u0026 Viewport\n\n```javascript\nbooba.scrollLines(amount)  // Scroll by lines\nbooba.scrollPages(amount)  // Scroll by pages\nbooba.scrollToTop()        // Scroll to top of history\nbooba.scrollToBottom()     // Scroll to current output\nbooba.scrollToLine(line)   // Scroll to specific line\nbooba.getViewportY()       // Get current scroll position\n```\n\n### Terminal Control\n\n```javascript\nbooba.paste(data)          // Paste with bracketed paste support\nbooba.input(data)          // Input as if typed\nbooba.focus()              // Focus terminal\nbooba.blur()               // Remove focus\nbooba.clear()              // Clear screen\nbooba.reset()              // Reset terminal state\nbooba.write(data)          // Write to display\nbooba.writeln(data)        // Write with newline\n```\n\n### Terminal Mode Queries\n\n```javascript\nbooba.hasMouseTracking()   // Is mouse tracking enabled?\nbooba.hasBracketedPaste()  // Is bracketed paste enabled?\nbooba.hasFocusEvents()     // Are focus events enabled?\nbooba.getMode(mode, isAnsi) // Query arbitrary terminal mode\n```\n\n### Events\n\n```javascript\nbooba.onStatusChange = (state, message) =\u003e { /* connection state */ };\nbooba.onTitleChange = (title) =\u003e { /* program set window title */ };\nbooba.onBell = () =\u003e { /* bell/beep fired */ };\nbooba.onSelectionChange = () =\u003e { /* selection changed */ };\nbooba.onKey = (event) =\u003e { /* key pressed */ };\nbooba.onScroll = (viewportY) =\u003e { /* viewport scrolled */ };\nbooba.onRender = ({ start, end }) =\u003e { /* rows rendered */ };\nbooba.onCursorMove = () =\u003e { /* cursor moved */ };\n```\n\n### Link Detection\n\n```javascript\nconst disposable = booba.registerLinkProvider({\n    provideLinks(y, callback) {\n        // Detect links on row y, call callback with results\n        callback(links);\n    }\n});\ndisposable.dispose(); // Unregister when done\n```\n\n### Custom Event Handlers\n\n```javascript\nbooba.attachCustomKeyEventHandler((event) =\u003e {\n    // Return true to prevent default handling\n    return false;\n});\n\nbooba.attachCustomWheelEventHandler((event) =\u003e {\n    // Return true to prevent default scroll handling\n    return false;\n});\n```\n\n### Lifecycle\n\n```javascript\nbooba.dispose()            // Clean up all resources\nbooba.terminal             // Access underlying ghostty-web Terminal\nbooba.cols                 // Current column count\nbooba.rows                 // Current row count\n```\n\n### Types\n\nAll types are exported for TypeScript consumers:\n\n```typescript\nimport type {\n    BoobaTerminalOptions,\n    BoobaTheme,\n    BoobaBufferRange,\n    BoobaKeyEvent,\n    BoobaRenderEvent,\n    BoobaLinkProvider,\n    BoobaLink,\n    BoobaAdapter,\n    BoobaConnectionState,\n} from './booba/booba.js';\n```\n\nFor adapter usage (WebSocket, WASM, custom), see [ADAPTER_USAGE.md](./ADAPTER_USAGE.md).\n\n## Embedding a BubbleTea Application in a Web Browser\n\nWe 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.\n\n### Quickstart\n\nThe 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:\n\n```go\npackage main\n\nimport (\n    \"log\"\n\n    booba \"github.com/NimbleMarkets/go-booba\"\n)\n\nfunc main() {\n    if err := booba.Run(initialModel()); err != nil {\n        log.Fatal(err)\n    }\n}\n```\n\nFor easier porting from Bubble Tea — or when you need the program handle for `Send`, `Quit`, etc. — use `booba.NewProgram`:\n\n```go\nfunc main() {\n    bp := booba.NewProgram(initialModel())\n    if _, err := bp.Run(); err != nil {\n        log.Fatal(err)\n    }\n}\n```\n\nBuild 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/`.\n\nFor finer control, the [`wasm`](./wasm) subpackage exposes the browser bridge directly, and native code can construct a `tea.Program` the usual way.\n\nTODO: link to live example\n\n## Web Frontend for BubbleTea-based service\n\nOtherwise, 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.\n\n### Middleware\n\nThe `serve` package exposes three composable middleware layers that mirror and extend the Wish/sip shape:\n\n| Layer | Type | Wraps | Install |\n|---|---|---|---|\n| 1. Handshake | `ConnectMiddleware` | `*http.Request` for both WS upgrade and WT CONNECT | `WithConnectMiddleware(...)` |\n| 2. Session I/O | `SessionMiddleware` | `Session` (transport byte streams) | `WithSessionMiddleware(...)` |\n| 3. Handler | `Middleware` | `Handler` (per-session `tea.Model` construction) | `WithMiddleware(...)` |\n\n`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.\n\nBuilt-in middleware subpackages:\n\n- `serve/middleware/osc52gate` — allow/deny/audit OSC 52 clipboard-write escapes in the outbound stream.\n- `serve/middleware/recover` — catch panics during handler construction.\n- `serve/middleware/logging` — slog-based session start/end logging.\n- `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).\n\n```go\nsrv := serve.NewServer(cfg,\n    serve.WithConnectMiddleware(serve.LiftHTTPMiddleware(myHTTPMiddleware)),\n    serve.WithSessionMiddleware(osc52gate.New(osc52gate.ModeDeny)),\n    serve.WithMiddleware(recover.New(), logging.New()),\n)\n```\n\nBasic Auth, connection limits, and `cfg.IdleTimeout` are auto-installed by `NewServer` when the corresponding `Config` fields are set.\n\n### Config knobs\n\nBeyond the listener/TLS/auth fields, `serve.Config` exposes protocol-safety knobs with sensible defaults:\n\n- `MaxPasteBytes` (default 1 MiB) — cap bracketed-paste payloads from clients.\n- `ResizeThrottle` (default 16ms) — debounce inbound resize messages.\n- `MaxWindowDims` (default 4096×4096) — reject adversarial resize values before the PTY `ioctl`.\n- `InitialResizeTimeout` (default 10s) — deadline on the initial Resize message after the handshake.\n- `IdleTimeout` — close sessions with no inbound bytes for the given duration (0 = disabled).\n\nSee `docs/DESIGN_MIDDLEWARE.md` for the design rationale.\n\n## `booba` CLI Command Wrapper\n\nThe `booba` command wraps any local CLI program and serves it in the browser through the same embedded terminal stack.\n\nBuild and run it from the repository root:\n\n```sh\ntask build-cmd-booba\n./bin/booba --listen 127.0.0.1:8080 -- htop\n```\n\nEverything after `--` is treated as the wrapped command and its arguments:\n\n```sh\n./bin/booba --listen 127.0.0.1:8080 -- bash\n./bin/booba --listen 127.0.0.1:8080 -- python3 -q\n./bin/booba --listen 127.0.0.1:8080 -- vim README.md\n```\n\nBuild and run the example server from the repository root:\n\n```sh\ntask build-cmd-booba-view-example-native\n./bin/booba-view-example --listen 127.0.0.1:8080\n```\n\nThe 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.\n\nUseful flags:\n\n```sh\n./bin/booba-view-example --listen 127.0.0.1:8080 --http3-port=-1\n./bin/booba-view-example --listen 127.0.0.1:8080 --origin=https://app.example.com,https://*.example.net\n./bin/booba-view-example --listen 127.0.0.1:8080 --cert-file=server.crt --key-file=server.key\n./bin/booba-view-example --listen 127.0.0.1:8080 --username=admin --password=secret\n./bin/booba-view-example --listen 127.0.0.1:8080 --username=admin --password-file=/run/secrets/booba\nBOOBA_PASSWORD=secret ./bin/booba-view-example --listen 127.0.0.1:8080 --username=admin\n```\n\nNotes:\n\n * `--http3-port=-1` disables WebTransport and uses WebSocket only.\n * the default bind address is loopback (`127.0.0.1`); non-loopback `--listen` addresses require `--cert-file` and `--key-file`.\n * browser origins are same-host by default; use `--origin` to allow additional cross-origin browser clients. Patterns are Go [`path.Match`](https://pkg.go.dev/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.\n * Basic Auth requires `--cert-file` and `--key-file`; the server refuses to start otherwise.\n * prefer `--password-file` or `$BOOBA_PASSWORD` over `--password`: the flag form leaks the secret into argv, shell history, and `ps` listings. Precedence is flag \u003e file \u003e env.\n * static frontend files are embedded with `go:embed`, so after frontend asset changes you must rebuild the Go binary you run.\n * 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`.\n\n## Open Collaboration\n\nWe welcome contributions and feedback.  Please adhere to our [Code of Conduct](./CODE_OF_CONDUCT.md) when engaging our community.\n\n * [GitHub Issues](https://github.com/NimbleMarkets/go-booba/issues)\n * [GitHub Pull Requests](https://github.com/NimbleMarkets/go-booba/pulls)\n\n## Acknowledgements\n\nThanks to the [Ghostty developers](https://github.com/ghostty-org/ghostty), the [ghostty-web](https://github.com/coder/ghostty-web) developers, and to [Charm.sh](https://charm.sh) for making the command line glamorous with [Bubble Tea](https://github.com/charmbracelet/bubbletea).\n\nThanks to [@BigJK](https://github.com/BigJk/bubbletea-in-wasm) for the initial inspiration when I was exploring this before `libghostty`.\n\nThanks to [@Gaurav-Gosain](https://github.com/Gaurav-Gosain), who cotemporaneously invented `sip`.  [That `sip` tool](https://github.com/Gaurav-Gosain/sip) is similar to this library, but works with `xterm.js`.   We adopted and extended its protocol and it also inspired our CLI tool.\n\n## License\n\nReleased under the [MIT License](https://en.wikipedia.org/wiki/MIT_License), see [LICENSE.txt](./LICENSE.txt).\n\nCopyright (c) 2026 [Neomantra Corp](https://www.neomantra.com).   \n\n----\nMade with :heart: and :fire: by the team behind [Nimble.Markets](https://nimble.markets).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnimblemarkets%2Fgo-booba","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnimblemarkets%2Fgo-booba","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnimblemarkets%2Fgo-booba/lists"}