Skip to content

Go SDK

wmux provides a Go client library for managing persistent terminal sessions backed by a PTY daemon. Import the pkg/client package to create, attach, and control sessions from any Go application.

Install

go get github.com/wblech/wmux

Quick start

Zero-configuration connects to (or spawns) a daemon in the default namespace:

package main

import (
    "log"

    "github.com/wblech/wmux/pkg/client"
)

func main() {
    c, err := client.New()
    if err != nil {
        log.Fatal(err)
    }
    defer c.Close()

    info, err := c.Create("hello", client.CreateParams{
        Cols: 80,
        Rows: 24,
    })
    if err != nil {
        log.Fatal(err)
    }
    log.Printf("session %s running pid %d", info.ID, info.Pid)
}

Architecture

+----------------------------------------------+
|            Your App (Go + Frontend)          |
|                                              |
|  +---------------+     +------------------+  |
|  |  Frontend     |     |   Go backend     |  |
|  |  (xterm.js)   |<--->|  (pkg/client)    |  |
|  +---------------+     +--------+---------+  |
+-----------------------------+----------------+
                              | Unix socket
          +-------------------v----------------+
          |          wmux daemon               |
          |                                    |
          |  +---------------+  +-----------+  |
          |  |   Sessions    |  | Emulator  |  |
          |  |  (PTY/shell)  |<>| (charmvt) |  |
          |  +---------------+  +-----------+  |
          +------------------------------------+

The client communicates with the daemon over a Unix socket using a length-prefixed binary protocol with two channels: control for RPCs (create, resize, list) and stream for PTY output. Both channels are authenticated automatically via a shared token file.

Two deployment modes are available:

  • Embedded daemon -- the daemon runs in-process as a goroutine. Best for applications where sessions do not need to survive restarts. See Embedded Daemon.

  • Persistent daemon -- the daemon runs as a separate background process. Sessions survive application restarts. See Persistent Daemon.

Configuration options

All options work with both client.New() and client.NewDaemon().

Option Default Description
WithNamespace(name string) "default" Logical isolation group
WithBaseDir(path string) ~/.wmux Root directory for all state
WithSocket(path string) <baseDir>/<ns>/daemon.sock Explicit Unix socket path
WithTokenPath(path string) <baseDir>/<ns>/daemon.token Auth token file path
WithDataDir(path string) <baseDir>/<ns>/sessions Session history directory
WithAutoStart(enabled bool) true Auto-start daemon if not running
WithColdRestore(enabled bool) false Restore scrollback from disk on startup
WithMaxScrollbackSize(n int64) 0 (unlimited) Max scrollback bytes per session
WithEmulatorFactory(f EmulatorFactory) nil Emulator backend via addon module (e.g., charmvt.Backend())

Addons

Emulator addons provide server-side terminal state tracking (scrollback and viewport snapshots). Without an addon, Attach() returns empty snapshots.

Addons are optional, separate Go modules — your binary only includes an addon if you import it.

Pure Go terminal emulator. No external dependencies (no Node.js). Uses charmbracelet/x/vt under the hood.

Install:

go get github.com/wblech/wmux/addons/charmvt

Use:

import (
    "github.com/wblech/wmux/pkg/client"
    "github.com/wblech/wmux/addons/charmvt"
)

// Embedded daemon with emulator addon.
d, err := client.NewDaemon(
    client.WithNamespace("myapp"),
    charmvt.Backend(),
)

// Or with options.
d, err := client.NewDaemon(
    charmvt.Backend(
        charmvt.WithScrollbackSize(50000),
        charmvt.WithCallbacks(charmvt.Callbacks{
            Title: func(sid, title string) {
                log.Printf("session %s: %s", sid, title)
            },
        }),
    ),
)

Runtime scrollback configuration

The scrollback buffer size can be changed on a live session without restarting the daemon:

err := c.UpdateEmulatorScrollback("sess-1", 50000)

This works with addons that support it (charmvt does). If the addon does not support runtime configuration, the call returns an error. Increasing the size preserves existing data. Decreasing discards the oldest lines.

Writing your own addon

Implement the client.EmulatorFactory interface:

type EmulatorFactory interface {
    Create(sessionID string, cols, rows int) ScreenEmulator
    Close()
}

type ScreenEmulator interface {
    Process(data []byte)
    Snapshot() Snapshot
    Resize(cols, rows int)
}

// Snapshot is the warm-attach payload. Replay is a self-contained VT byte
// stream: when written to an xterm.js-compatible terminal it reproduces the
// source screen and cursor exactly, regardless of prior destination state.
//
// Implementations should build Replay as:
//   1. \e[2J\e[H\e[3J  — clear viewport + scrollback, home the cursor.
//   2. The serialized scrollback history.
//   3. The serialized current viewport.
//   4. A final CUP (\e[row;colH) restoring the source cursor position.
//
// See ADR 0026 for the full contract.
type Snapshot struct {
    Replay []byte
}

Pass it via client.WithEmulatorFactory(yourFactory).

Further reading

  • Session Operations -- create, attach, write, resize, list, kill
  • Events -- subscribe to PTY output and lifecycle events