MCPcopy Index your code
hub / github.com/adibhanna/tsm

github.com/adibhanna/tsm @v0.7.1

Chat with this repo
repository ↗ · DeepWiki ↗ · release v0.7.1 ↗ · + Follow
956 symbols 3,104 edges 70 files 240 documented · 25%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

tsm

tsm logo

tsm is a terminal session manager. It keeps shell sessions alive as background daemons, lets you detach and reattach later, and restores terminal state without turning the project into a tmux clone.

It is built around a simple model:

  • one daemon per session
  • one PTY per session
  • native splits and workspaces via tsm mux (cmux, kitty, Ghostty, and WezTerm backends)
  • git worktree support — auto-naming, grouping, and full lifecycle management via tsm wt
  • fast switching between named sessions from the CLI or TUI

For full-screen terminal apps like Neovim, the preferred build uses Ghostty's VT engine so reattach can restore the visible screen instead of only restoring terminal modes.

Screenshots

Full TUI:

Full TUI

Simplified session palette:

Simplified Palette

Compact help layout:

Shortcut Help

Why TSM

tsm is for people who want persistent terminal sessions without adopting tmux's window and pane model.

Use it when you want:

  • a long-lived shell or editor per project
  • fast switching between sessions
  • detach / reattach from anywhere
  • a command-palette-style session picker
  • git worktree workflows with auto-named sessions and workspace layouts
  • quick Codex / Claude activity checks before switching
  • full-screen restore for Neovim and similar apps on the Ghostty-backed build

For splits and workspaces, use tsm mux to orchestrate your terminal's native split system:

  • tsm mux open dev opens a workspace with splits and sessions from a TOML manifest
  • supports cmux, kitty, Ghostty, and WezTerm as backends (auto-detected)
  • each pane is a real native terminal surface with full GPU rendering, ligatures, scrollback
  • no VT re-emulation like tmux/zellij

Native Multiplexer (tsm mux)

tsm mux lets you define workspaces as TOML manifests and open them with native terminal splits. Unlike tmux, it delegates layout to your terminal emulator's own split system — no server wrapping the terminal.

Workspace manifest

Create ~/.config/tsm/workspaces/dev.toml:

name = "dev"
version = 1

[[surface]]
name = "editor"
session = "editor"
cwd = "~/Developer/myproject"
command = "nvim ."

  [[surface.split]]
  name = "shell"
  session = "shell"
  direction = "right"
  cwd = "~/Developer/myproject"

Open a workspace

tsm mux open dev

This creates the sessions, opens native splits in your terminal, attaches each session, and runs the startup commands.

Mux commands

tsm mux open <workspace>                Open workspace from manifest
tsm mux split <left|right|up|down> <s>  Split focused pane with session
tsm mux tab new <session>               New tab with session
tsm mux save <workspace>                Save workspace manifest
tsm mux restore <workspace>             Restore workspace from manifest
tsm mux doctor <workspace>              Diagnose workspace health
tsm mux sidebar sync <workspace>        Sync agent state to cmux sidebar
tsm mux last                            Focus previous pane
tsm mux next                            Focus next pane
tsm mux workspace [name]                List or switch workspaces
tsm mux setup kitty                     Configure kitty for remote control
tsm mux status                          Show terminal, backend, workspace info

Supported backends

Terminal Backend Detection Splits Tabs Sidebar
cmux cmux CMUX_SOCKET_PATH yes yes yes
kitty kitty KITTY_PID yes yes no
Ghostty ghostty GHOSTTY_RESOURCES_DIR yes yes no
WezTerm wezterm WEZTERM_UNIX_SOCKET yes yes no

Override with TSM_MUX_BACKEND=cmux (or kitty, ghostty, wezterm).

TUI workspace picker

Press w in the TUI to see available workspaces and open one with Enter.

Git Worktree Support (tsm wt)

tsm wt manages git worktrees and their sessions together. It auto-detects worktrees, creates sessions with repo@branch naming, and lets you open full workspace layouts with splits.

Auto-naming

When you create a session inside a linked git worktree, tsm automatically names it repo@branch:

cd ~/Developer/myapp-feature-login
tsm attach
# → creates session "myapp@feature-login"

Regular (non-worktree) repos keep using the directory name. When a session is created in a repo with worktrees, tsm auto-creates sessions for all sibling worktrees.

TUI grouping

The TUI groups sessions by git repository with collapsible headers:

  ▾ myapp (3)
  ▸ myapp@main            ●1  2h
    myapp@feature-login   ○0  30m
    myapp@bugfix-42       ○0  5m
  nvim                    ○0  10m
  • Left/right arrows or Enter collapse/expand groups
  • Filter (/) matches branch and repo names
  • Ctrl+] opens the session picker from inside any app (neovim, claude, etc.)

Worktree commands

tsm wt                           List worktrees and session status
tsm wt <branch>                  Attach to (or create) session for a branch
tsm wt open [--split <cmd>...]   Open worktrees as workspace with splits
tsm wt tui                       Open TUI filtered to this repo's sessions
tsm wt add <branch...>           Create new git worktrees + sessions
tsm wt rm <branch...> [-f]       Remove worktrees and kill sessions
tsm wt move <branch> <path>      Move a worktree to a new path
tsm wt prune                     Prune stale worktrees and orphaned sessions
tsm wt --create                  Create sessions for all existing worktrees

Worktree workspaces with splits

Open all worktrees as tabs with splits for your tools:

# Each worktree gets a tab with claude + nvim
tsm wt open --split "claude" --split "nvim"

# Only specific branches
tsm wt open main feature-login --split "claude" --split "nvim"

The first --split command runs in the main pane, each additional one creates a right-split. Requires a mux backend (cmux, kitty, ghostty, or wezterm).

Worktree lifecycle

# Create worktrees and sessions in one step
tsm wt add feature-login bugfix-42 hotfix-99

# Remove worktrees and kill their sessions
tsm wt rm feature-login bugfix-42

# Force remove (uncommitted changes)
tsm wt rm hotfix-99 -f

# Clean up stale worktrees and orphaned sessions
tsm wt prune

Install

There are two supported install tracks.

Install path Backend Best for
Release archive libghostty-vt Best restore quality, recommended for most users
Homebrew libghostty-vt Managed installs and upgrades

Release archive

This is the recommended install path.

Release archives are self-contained. They bundle:

  • tsm
  • libghostty-vt

Users do not need to install Ghostty separately.

Supported bundled release targets:

  • macOS amd64
  • macOS arm64
  • Linux amd64
  • Linux arm64

See docs/COMPATIBILITY.md for the current OS, shell, terminal, and agent-support contract. See docs/KNOWN_LIMITATIONS.md for the current product boundaries and caveats.

Download the matching archive from GitHub Releases, extract it, and place tsm on your PATH.

Homebrew

tsm is distributed through a custom tap, not homebrew/core, so it will not show up in a plain brew search tsm.

Install it with the tap-qualified formula name:

brew tap adibhanna/tsm
brew install adibhanna/tsm/tsm

Remove it with:

brew uninstall adibhanna/tsm/tsm

The supported Homebrew path is the self-contained release archive formula published by the release workflow. A source-backed formula is brittle because Ghostty's Zig build fetches external dependencies during the build, which Homebrew may block in its sandbox. Until tagged release archives are published, prefer the source install.

Build from source

Prerequisites:

  • Go 1.25+
  • Zig 0.15.2
  • pkg-config (brew install pkg-config on macOS, apt install pkg-config on Linux)
git clone https://github.com/adibhanna/tsm.git
cd tsm
make setup
make build

make setup verifies prerequisites, clones Ghostty into ./ghostty, and builds libghostty-vt into ./.ghostty-prefix. After that, make build, make test, and make lint all work.

Install under a user prefix:

make install

That installs:

  • tsm into ~/.local/bin
  • libghostty-vt into ~/.local/lib/tsm

Remove it cleanly:

make uninstall

Override the install root if needed:

make install PREFIX=/opt/homebrew
make uninstall PREFIX=/opt/homebrew

System-wide install:

sudo make install PREFIX=/usr/local
sudo make uninstall PREFIX=/usr/local

Quick Start

Create or attach a session:

tsm attach

That command is intentionally smart:

  • no sessions: create one named after the current directory and attach
  • one session: attach directly
  • multiple sessions: open the picker

Create a specific session and start a command inside it:

tsm new api bash -lc 'npm run dev'

Open Neovim in a session:

tsm attach editor
nvim

Detach interactively:

Ctrl+\

Reattach later:

tsm attach editor

List sessions:

tsm ls

Run diagnostics:

tsm doctor

Session Model

Each session is a long-lived daemon with:

  • a Unix socket
  • a PTY
  • a foreground process like zsh, bash, fish, nvim, or a custom command

The session keeps running after you detach. You only lose it if you explicitly kill it or the process exits on its own.

CLI Workflow

Use this if you prefer to stay in the shell.

Core commands

tsm
tsm tui [--simplified] [--keymap default|palette]
tsm palette
tsm attach [name]
tsm detach [name]
tsm new <name> [cmd...]
tsm ls
tsm wt
tsm wt <branch>
tsm wt open [--split <cmd>...]
tsm wt add <branch...>
tsm wt rm <branch...> [-f]
tsm wt move <branch> <path>
tsm wt prune
tsm wt tui
tsm mux open <workspace>
tsm mux split <dir> <session>
tsm mux tab new <session>
tsm mux save <workspace>
tsm mux restore <workspace>
tsm mux doctor <workspace>
tsm mux status
tsm doctor
tsm doctor clean-stale
tsm debug session <name>
tsm rename <old> <new>
tsm kill [name...]
tsm claude-statusline
tsm config install [--force]
tsm version

Attach behavior

tsm attach with no name:

  • no sessions: create one named after the current directory and attach
  • one session: attach directly
  • multiple sessions: open the chooser

tsm attach <name>:

  • attach to the named session
  • create it if it does not exist
  • if run from inside another attached session, switch locally instead of nesting one attach inside another PTY
  • local switches avoid the full terminal clear path, so switching is less visually disruptive
  • if the session daemon was started by an older tsm build, warn so you know to recreate the session if behavior looks stale

New session with command

Anything after the session name becomes the command started inside the session instead of your default login shell.

Examples:

tsm new work
tsm new logs tail -f /var/log/system.log
tsm new editor nvim
tsm new api bash -lc 'npm run dev'

Detach behavior

tsm detach with no name uses $TSM_SESSION, so it detaches the current session when run inside an attached shell.

tsm detach <name> detaches all attached clients from that named session without killing the daemon.

Kill behavior

tsm kill with no name uses $TSM_SESSION, so it kills the current session when run inside an attached shell.

tsm kill <name>... kills one or more named sessions.

Examples:

tsm detach
tsm detach work
tsm kill
tsm kill api worker repl

Diagnostics

Use tsm doctor when install, runtime-linking, or socket issues are unclear.

It reports:

  • current binary/version/backend
  • config path state
  • socket directory
  • pkg-config and libghostty-vt availability
  • live versus stale session sockets
  • live sessions still running an older daemon build
  • orphaned per-session sidecars with no matching socket

If tsm doctor reports stale sockets or orphaned sidecars, clean them up with:

tsm doctor clean-stale

Use tsm debug session <name> when one specific session is acting strangely.

It reports:

  • socket path
  • live / stale / missing state
  • daemon PID and client count
  • command and cwd
  • task end status when available
  • a short current preview snapshot

TUI Workflow

tsm and tsm tui open the full TUI.

The full TUI is the best workflow when you want:

  • a session list
  • a live preview
  • session metadata
  • activity log feedback
  • a selected-session Codex / Claude activity line
  • create / rename / detach / kill flows without typing long commands

Full TUI default keys

Key Action
Navigate sessions (including group headers)
Collapse repo group / scroll preview
Expand repo group / scroll preview
enter Attach (or toggle collapse on group header)
space Toggle selection
ctrl+a Select or deselect all
d Detach selected session(s)
n New session
k Kill selected session(s)
r Rename session
c Copy attach command

Extension points exported contracts — how you extend this code

Backend (Interface)
Backend is the interface that terminal emulator mux backends must implement. Each backend wraps a specific terminal emul [5 …
internal/mux/backend.go
TerminalBackend (Interface)
TerminalBackend tracks PTY output and can serialize enough terminal state to bring a newly attached client back into syn [3 …
internal/session/termstate.go

Core symbols most depended-on inside this repo

Close
called by 56
internal/session/termstate.go
DefaultConfig
called by 50
internal/session/config.go
SocketPath
called by 43
internal/session/config.go
Write
called by 29
internal/session/scrollback.go
matchesAction
called by 23
internal/tui/keys.go
NewModel
called by 22
internal/tui/model_core.go
IsSocket
called by 22
internal/session/socket.go
actionLabel
called by 21
internal/tui/keys.go

Shape

Function 559
Method 305
Struct 79
TypeAlias 8
Interface 4
Class 1

Languages

Go100%
Ruby1%

Modules by API surface

main.go98 symbols
internal/tui/model_core.go64 symbols
internal/session/daemon.go53 symbols
internal/tui/model_view.go50 symbols
main_test.go45 symbols
internal/tui/keys.go42 symbols
internal/tui/model_test.go34 symbols
internal/engine/agent_status.go32 symbols
internal/mux/backend/kitty/kitty.go29 symbols
internal/mux/backend/wezterm/wezterm.go28 symbols
internal/mux/backend/ghostty/ghostty.go28 symbols
internal/mux/backend/cmux/cmux.go28 symbols

For agents

$ claude mcp add tsm \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact