Drinking tea through the browser π΅
Status: v0.1.0 - Initial Release
Sip is a Go library that allows you to serve any Bubble Tea application through a web browser with full terminal emulation, mouse support, and hardware-accelerated rendering.
Take a sip!
demonstration-tuios-sip.webm
- WebGL Rendering - GPU-accelerated terminal rendering via xterm.js for smooth 60fps
- Dual Protocol Support - WebTransport (HTTP/3 over QUIC) with automatic WebSocket fallback
- Embedded Assets - All static files (HTML, CSS, JS, fonts) bundled in the binary via go:embed
- Bundled Nerd Fonts - JetBrains Mono Nerd Font included, no client-side installation needed
- Session Management - Handle multiple concurrent users with isolated sessions
- Mouse Support - Full mouse interaction support
- Auto-Reconnect - Automatic reconnection with exponential backoff
- Pure Go - No CGO dependencies, statically compiled binaries
- Zero Configuration - Works out of the box with sensible defaults
- Wish-like API - Familiar handler pattern for Charm ecosystem users
go get github.com/Gaurav-Gosain/sipSip also provides a CLI to wrap any command and expose it through the browser:
# Install the CLI
go install github.com/Gaurav-Gosain/sip/cmd/sip@latest
# Run htop in browser
sip -- htop
# Run on a specific port
sip -p 8080 -- claude -c
# Expose on all interfaces
sip --host 0.0.0.0 -- bashThen open http://localhost:7681 in your browser.
package main
import (
"context"
"fmt"
"os"
"os/signal"
tea "charm.land/bubbletea/v2"
"github.com/Gaurav-Gosain/sip"
)
type model struct {
count int
}
func (m model) Init() tea.Cmd { return nil }
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyPressMsg:
switch msg.String() {
case "q":
return m, tea.Quit
case "up":
m.count++
case "down":
m.count--
}
}
return m, nil
}
func (m model) View() tea.View {
return tea.NewView(fmt.Sprintf("Count: %d\n\nPress up/down to change, q to quit", m.count))
}
func main() {
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()
server := sip.NewServer(sip.DefaultConfig())
err := server.Serve(ctx, func(sess sip.Session) (tea.Model, []tea.ProgramOption) {
return model{}, nil
})
if err != nil {
fmt.Println(err)
}
}Then open http://localhost:7681 in your browser.
Similar to Wish's Bubble Tea middleware:
// Handler creates a model and options for each session
type Handler func(sess Session) (tea.Model, []tea.ProgramOption)
// Use with Serve()
server.Serve(ctx, func(sess sip.Session) (tea.Model, []tea.ProgramOption) {
pty := sess.Pty()
return myModel{width: pty.Width, height: pty.Height}, nil
})For more control over tea.Program creation:
// ProgramHandler creates a tea.Program directly
type ProgramHandler func(sess Session) *tea.Program
// Use with ServeWithProgram()
server.ServeWithProgram(ctx, func(sess sip.Session) *tea.Program {
return tea.NewProgram(myModel{}, sip.MakeOptions(sess)...)
})type Session interface {
Pty() Pty // Get terminal dimensions
Context() context.Context // Session context (cancelled on disconnect)
Read(p []byte) (n int, err error) // Read from terminal
Write(p []byte) (n int, err error) // Write to terminal
WindowChanges() <-chan WindowSize // Receive window resize events
}
type Pty struct {
Width int
Height int
}config := sip.Config{
Host: "localhost", // Bind address
Port: "7681", // HTTP port (WebTransport uses Port+1)
ReadOnly: false, // Disable input
MaxConnections: 0, // Connection limit (0 = unlimited)
IdleTimeout: 0, // Idle timeout (0 = no timeout)
AllowOrigins: nil, // CORS origins (nil = all)
Debug: false, // Enable debug logging
}- Browser connects via WebSocket (or WebTransport if available)
- Sip creates a PTY (pseudo-terminal) for proper terminal semantics
- Your Bubble Tea model is created via the handler
- Terminal I/O is bridged between the PTY and browser via xterm.js
- Mouse events, keyboard input, and window resizes are forwarded to your model
- Bubble Tea - The TUI framework Sip is built for
- Wish - SSH server for Bubble Tea apps (Sip's API is inspired by Wish)
- TUIOS - Terminal window manager where Sip originated
- xterm.js - Terminal emulator used in the browser
- ttyd - Share terminal over the web (C implementation)
MIT License - see LICENSE for details
Sip is developed as part of the TUIOS project and builds on the excellent work of the Charm team and the xterm.js community.