sip

package module
v0.1.11 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 23, 2025 License: MIT Imports: 31 Imported by: 0

README

Sip - Serve Bubble Tea Apps Through the Browser

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.

Demonstration

Take a sip!

demonstration-tuios-sip.webm

Features

  • 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

Installation

go get github.com/Gaurav-Gosain/sip

CLI Usage

Sip 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 -- bash

Then open http://localhost:7681 in your browser.

Library Usage (Quick Start)

package main

import (
    "context"
    "os"
    "os/signal"

    tea "github.com/charmbracelet/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())
    
    server.Serve(ctx, func(sess sip.Session) (tea.Model, []tea.ProgramOption) {
        return model{}, nil
    })
}

Then open http://localhost:7681 in your browser.

API

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
})
ProgramHandler Pattern (Advanced)

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)...)
})
Session Interface
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
}
Configuration
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
}

How It Works

  1. Browser connects via WebSocket (or WebTransport if available)
  2. Sip creates a PTY (pseudo-terminal) for proper terminal semantics
  3. Your Bubble Tea model is created via the handler
  4. Terminal I/O is bridged between the PTY and browser via xterm.js
  5. 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)

License

MIT License - see LICENSE for details

Acknowledgments

Sip is developed as part of the TUIOS project and builds on the excellent work of the Charm team and the xterm.js community.

Documentation

Overview

Package sip serves Bubble Tea applications through a web browser.

Sip provides a simple way to make any Bubble Tea TUI application accessible through a web browser with full terminal emulation, mouse support, and hardware-accelerated rendering via xterm.js.

Basic usage:

server := sip.NewServer(sip.DefaultConfig())
server.Serve(context.Background(), func(sess sip.Session) (tea.Model, []tea.ProgramOption) {
    pty := sess.Pty()
    return myModel{width: pty.Width, height: pty.Height}, nil
})

Or with a ProgramHandler for more control:

server.ServeWithProgram(ctx, func(sess sip.Session) *tea.Program {
    return tea.NewProgram(myModel{}, sip.MakeOptions(sess)...)
})

Index

Constants

View Source
const (
	MsgInput   = '0' // Terminal input (client -> server)
	MsgOutput  = '1' // Terminal output (server -> client)
	MsgResize  = '2' // Resize terminal
	MsgPing    = '3' // Ping
	MsgPong    = '4' // Pong
	MsgTitle   = '5' // Set window title
	MsgOptions = '6' // Configuration options
	MsgClose   = '7' // Session closed (server -> client)
)

Message types for WebSocket/WebTransport communication.

Variables

This section is empty.

Functions

func MakeOptions

func MakeOptions(sess Session) []tea.ProgramOption

MakeOptions returns tea.ProgramOptions configured for the web session. On Unix, this uses the PTY slave file for proper raw mode support. On Windows, this uses the Session's Reader/Writer interface with pipes.

func SetLogLevel

func SetLogLevel(level log.Level)

SetLogLevel sets the logging verbosity for the sip package.

Types

type CertInfo

type CertInfo struct {
	TLSConfig *tls.Config
	DER       []byte
	Hash      [32]byte
}

CertInfo holds generated certificate information.

func GenerateSelfSignedCert

func GenerateSelfSignedCert(_ string) (*CertInfo, error)

GenerateSelfSignedCert generates a self-signed TLS certificate for WebTransport. The certificate is valid for 10 days (Chrome requires < 14 days for serverCertificateHashes).

type CommandHandler added in v0.1.8

type CommandHandler struct {
	// contains filtered or unexported fields
}

CommandHandler creates command sessions for each browser connection.

type Config

type Config struct {
	// Host to bind to (default: "localhost")
	Host string

	// Port to listen on (default: "7681")
	Port string

	// ReadOnly disables input from clients when true
	ReadOnly bool

	// MaxConnections limits concurrent connections (0 = unlimited)
	MaxConnections int

	// IdleTimeout for connections (0 = no timeout)
	IdleTimeout time.Duration

	// AllowOrigins for CORS (empty = all origins allowed)
	AllowOrigins []string

	// TLSCert path to TLS certificate (enables HTTPS)
	TLSCert string

	// TLSKey path to TLS private key
	TLSKey string

	// Debug enables verbose logging
	Debug bool
}

Config holds the web server configuration.

func DefaultConfig

func DefaultConfig() Config

DefaultConfig returns sensible default configuration.

type Handler

type Handler func(sess Session) (tea.Model, []tea.ProgramOption)

Handler is the function Bubble Tea apps implement to hook into sip. This will create a new tea.Program for every browser connection and start it with the tea.ProgramOptions returned.

type OptionsMessage

type OptionsMessage struct {
	ReadOnly bool `json:"readOnly"`
}

OptionsMessage is sent to configure the terminal.

type ProgramHandler

type ProgramHandler func(sess Session) *tea.Program

ProgramHandler allows creating custom tea.Program instances. Use this for more control over program initialization. Make sure to use MakeOptions to properly configure I/O.

type Pty

type Pty struct {
	Width  int
	Height int
}

Pty represents pseudo-terminal information.

type ResizeMessage

type ResizeMessage struct {
	Cols int `json:"cols"`
	Rows int `json:"rows"`
}

ResizeMessage is sent when the terminal should be resized.

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server represents the web terminal server.

func NewServer

func NewServer(config Config) *Server

NewServer creates a new web terminal server with the given configuration.

func (*Server) Serve

func (s *Server) Serve(ctx context.Context, handler Handler) error

Serve starts the server and serves the Bubble Tea application. The handler is called for each new browser session to create a model. This method blocks until the context is cancelled.

func (*Server) ServeCommand added in v0.1.8

func (s *Server) ServeCommand(ctx context.Context, name string, args []string, dir string) error

ServeCommand starts the server and runs the specified command for each connection. This is used by the CLI to wrap arbitrary commands and expose them via the browser.

func (*Server) ServeWithProgram

func (s *Server) ServeWithProgram(ctx context.Context, handler ProgramHandler) error

ServeWithProgram starts the server with a custom ProgramHandler. Use this for more control over tea.Program creation.

type Session

type Session interface {
	// Pty returns the pseudo-terminal information for this session.
	Pty() Pty

	// Context returns the session's context, which is cancelled when the
	// session ends.
	Context() context.Context

	// Read reads input from the web terminal.
	Read(p []byte) (n int, err error)

	// Write writes output to the web terminal.
	Write(p []byte) (n int, err error)

	// Fd returns the file descriptor for TTY detection.
	// This is required for Bubble Tea to properly detect terminal mode.
	Fd() uintptr

	// PtySlave returns the underlying PTY slave file for direct I/O.
	// Bubble Tea requires the actual *os.File to set raw mode properly.
	PtySlave() *os.File

	// WindowChanges returns a channel that receives window size changes.
	WindowChanges() <-chan WindowSize
}

Session represents a web terminal session, similar to ssh.Session in Wish. It provides access to terminal dimensions and other session metadata.

type WindowSize

type WindowSize struct {
	Width  int
	Height int
}

WindowSize represents a terminal window size change.

Directories

Path Synopsis
cmd
sip command
Command sip wraps any CLI command and exposes it through a web browser.
Command sip wraps any CLI command and exposes it through a web browser.
examples
simple command
Simple example showing how to serve a Bubble Tea app through the browser.
Simple example showing how to serve a Bubble Tea app through the browser.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL