Repository navigation
Library Usage
Embed TUIOS in your own Bubble Tea program with the pkg/tuios Go package, or serve it over SSH or in the browser.
The pkg/tuios package exposes the window manager as a Bubble Tea model. You can run it on its own, wrap it in your own model, or serve it over SSH or the browser.
go get github.com/Gaurav-Gosain/tuios/pkg/tuiosWarning
The API is pre-1.0 and can change between releases. Model is a type alias for the internal app.OS type, so its exported fields and methods change with it. Pin a version in your go.mod.
Parts of TUIOS are also Go libraries of their own. (Added in v0.10.0)
| Module | What it does |
|---|---|
tuios.dev/vt |
The terminal emulator of TUIOS. Needs Go 1.26 |
tuios.dev/swatch |
Reads the colour theme of a desktop, such as Omarchy's, and follows it. Reads kitty, ghostty, alacritty and wezterm colour files |
tuios.dev/progstatus |
The parser and encoder of the Program Status Protocol |
The pages are at tuios.dev/docs/libraries.
tuios.New returns a *tuios.Model, which implements tea.Model. You build the program yourself, so you keep control of program options and error handling. There is no tuios.Run.
package main
import (
"log"
tea "charm.land/bubbletea/v2"
"github.com/Gaurav-Gosain/tuios/pkg/tuios"
)
func main() {
model := tuios.New()
p := tea.NewProgram(model, tuios.ProgramOptions()...)
if _, err := p.Run(); err != nil {
log.Fatal(err)
}
}New takes functional options:
model := tuios.New(
tuios.WithTheme("dracula"),
tuios.WithShowKeys(true),
tuios.WithAnimations(false),
tuios.WithWorkspaces(4),
tuios.WithBorderStyle("thick"),
tuios.WithScrollbackLines(50000),
)TUIOS reads the user's config file first and then applies your options on top, the same way the tuios binary applies its command-line flags. An option left at its zero value keeps whatever the config file says.
| Option | Type | If not set | Notes |
|---|---|---|---|
WithTheme |
string |
config file | A theme name, such as dracula or nord. |
WithShowKeys |
bool |
config file | Shows the showkeys overlay. |
WithAnimations |
bool |
config file |
false turns animations off. true cannot turn them on if the config turned them off. |
WithASCIIOnly |
bool |
config file | ASCII instead of Nerd Font glyphs. |
WithWorkspaces |
int |
9 |
Clamped to 1 to 9. |
WithBorderStyle |
string |
config file | See border_style. |
WithDockbarPosition |
string |
config file |
bottom, top or hidden. |
WithHideWindowButtons |
bool |
config file | Hides minimize, maximize and close. |
WithWindowButtonStyle |
string |
config file |
pill or dots (macOS traffic lights). The built-in default is dots. |
WithWindowButtonPosition |
string |
config file |
left or right. The built-in default is left. |
WithScrollbackLines |
int |
10000 |
Clamped to 100 to 1,000,000. Always applied, so it replaces the config's scrollback_lines. |
WithSize |
int, int |
detected | Initial width and height in cells. |
WithSSHMode |
bool |
false |
Marks the model as an SSH client. See Serve over SSH. |
WithUserConfig |
*config.UserConfig |
loaded from disk | Use this config instead of reading the user's file. |
Out-of-range numbers are clamped, not rejected. WithWorkspaces(50) gives 9 and WithScrollbackLines(10) gives 100. DefaultOptions() returns the starting values as an Options struct.
tuios.Config exposes three config functions, so you do not need to import an internal package:
cfg, err := tuios.Config.LoadUserConfig()
if err != nil {
cfg = tuios.Config.DefaultConfig()
}
cfg.Keybindings.LeaderKey = "ctrl+a"
model := tuios.New(tuios.WithUserConfig(cfg))tuios.Config.GetConfigPath() returns the path of the config file and an error.
Delegate to the TUIOS model and handle your own messages first. In Bubble Tea v2, View returns a tea.View.
type app struct {
wm *tuios.Model
}
func (a *app) Init() tea.Cmd {
return a.wm.Init()
}
func (a *app) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if _, ok := msg.(pingMsg); ok {
// Handle your own message and stop here.
return a, nil
}
updated, cmd := a.wm.Update(msg)
a.wm = updated.(*tuios.Model)
return a, cmd
}
func (a *app) View() tea.View {
return a.wm.View()
}To check the mode, read the Mode field. The package exports WindowManagementMode, which TUIOS starts in, and TerminalMode:
if model.Mode == tuios.TerminalMode {
// keys go to the focused pane
}ProgramOptions() returns the Bubble Tea options every TUIOS client runs with:
- the frame rate cap from the config (
max_fps) - no signal handler, so your program owns the process signals
tea.WithFilter(tuios.FilterMouseMotion)
Pass them through instead of writing your own, so you get later additions.
FilterMouseMotion drops mouse motion that nothing on screen reacts to. Motion still passes when something needs it, for example during a drag or resize, over a link, the dock or the sidebar, and when the focused pane runs an application that tracks the mouse. It is exported for callers that build their own option list.
Warning
The last filter wins
Bubble Tea keeps only one tea.WithFilter. SSH and web serving libraries add their own filter, so put their options first and tuios.ProgramOptions() last. Otherwise the TUIOS filter is replaced and every mouse motion event reaches the model.
NewForPTY takes the initial size from anything with Width() and Height() methods:
type PTY interface {
Width() int
Height() int
}
func NewForPTY(pty PTY, opts ...Option) *ModelThe interface needs methods. A struct with Width and Height fields, such as sip.Pty, does not satisfy it. For those, use New with WithSize, as the recipes below do.
sip serves a Bubble Tea program to the browser. It is the library behind tuios-web. Use ServeWithProgram and build the program yourself:
package main
import (
"context"
"log"
tea "charm.land/bubbletea/v2"
"github.com/Gaurav-Gosain/sip"
"github.com/Gaurav-Gosain/tuios/pkg/tuios"
)
func main() {
server := sip.NewServer(sip.DefaultConfig())
err := server.ServeWithProgram(context.Background(), func(sess sip.Session) *tea.Program {
pty := sess.Pty()
model := tuios.New(
tuios.WithSize(pty.Width, pty.Height),
tuios.WithTheme("dracula"),
)
// sip's options first, then tuios's, so the tuios filter wins.
opts := append(sip.MakeOptions(sess), tuios.ProgramOptions()...)
return tea.NewProgram(model, opts...)
})
if err != nil {
log.Fatal(err)
}
}Do not use sip's Serve here. It appends its own options after the ones you return, so its filter replaces the TUIOS one. A model served this way counts as a local client: there is no option that marks it as a browser.
With Wish, use MiddlewareWithProgramHandler for the same reason: bubbletea.Middleware appends its options after yours.
package main
import (
"log"
tea "charm.land/bubbletea/v2"
"charm.land/ssh"
"charm.land/wish/v2"
"charm.land/wish/v2/bubbletea"
"github.com/Gaurav-Gosain/tuios/pkg/tuios"
)
func main() {
s, err := wish.NewServer(
wish.WithAddress(":2222"),
wish.WithHostKeyPath(".ssh/tuios_ed25519"),
wish.WithMiddleware(
bubbletea.MiddlewareWithProgramHandler(func(sess ssh.Session) *tea.Program {
pty, _, _ := sess.Pty()
model := tuios.New(
tuios.WithSize(pty.Window.Width, pty.Window.Height),
tuios.WithSSHMode(true),
)
// wish's options first, then tuios's, so the tuios filter wins.
opts := append(bubbletea.MakeOptions(sess), tuios.ProgramOptions()...)
return tea.NewProgram(model, opts...)
}),
),
)
if err != nil {
log.Fatal(err)
}
log.Fatal(s.ListenAndServe())
}WithSSHMode(true) makes the model an SSH client. The settings page does not write the server's config file, and desktop actions do not run on the server. It does not give the model the SSH session, a graphics channel or the client terminal's capabilities, so images are not forwarded to the client.
-
No daemon. Sessions, detach and reattach, and the control protocol are internal and only reachable through the
tuiosbinary.pkg/tuiosgives you an in-process window manager. -
No SSH or web helper.
tuios sshandtuios-webare built from internal packages. Use the recipes above to serve the model yourself. - Some settings are process-wide. Theme, border style, dockbar position, window buttons, ASCII mode, scrollback and animations are written to package-level state in the config package. Two models in one process share them, and the last one built wins. This matters when you serve several sessions from one process.
-
No Go examples in the repository. The
examples/directory holds.tapescripts and a dock config, not Go programs. The recipes on this page all compile against currentmain.
- Architecture: How TUIOS is put together
- Configuration: The config file these options sit on top of
- Web Terminal: The tuios-web binary built on sip
- Introduction
- Getting Started
- Learn tuios, hands on
- Keybindings
- Hints
- Finding a Pane
- Links
- TUIOS vs Others
Windows and Layout
Sessions and Machines
Agents
- Agents
- Inbox and Review
- Agent Messaging
- Program Status
- Worktrees
- Checkpoints
- Ship
- Push Notifications
- Turn Off Agent Features
Scripting
Recording
Reference