libtmux-go

module
v0.1.3 Latest Latest
Warning

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

Go to latest
Published: Nov 7, 2026 License: MIT

README

libtmux for Go

libtmux for Go

Drive tmux from Go: sessions, windows, and panes as typed values, every tmux option and hook as a typed accessor, and errors classified by what tmux actually refused.

tests Go Reference libtmux.org workspace reference mcp reference

Run a command in a real terminal and read what it printed, find and filter what a tmux server holds, type into a pane and wait for its answer, and hear about changes as tmux makes them.

It suits tools that drive a terminal program, test suites that need a real tmux, and agents that share a terminal.

Add it to your module:

$ go get github.com/libtmux/libtmux-go/tmux@latest

Releases are numbered 0.1.N, and each release increments N. The API is not settled: any release may change or remove exported identifiers, and the changelog marks each break. Pin an exact version to avoid one.

It needs Go 1.26 or newer and tmux 3.3a or newer, on a Unix host. Against an older daemon, an operation returns tmux.ErrVersionTooLow and withholds the requested operation. A read-only version probe may run first.

tmux release candidates are not supported: a feature can require one only until its release ships, and a candidate such as X.Y-rcN ranks below X.Y. See Release candidates.

The detail is in the library contracts.

A tmux server for AI agents and a tmuxp-style workspace builder ship beside the library, under Packages.

Contents — Quick start · Find things · Type and wait · Watch tmux · Choosing a path · Testing · Packages · Leaves out · Support · Documentation

Quick start

Run a command in tmux and print what its terminal showed. This is the whole program:

// Command quickstart runs a command in tmux and prints what it showed.
package main

import (
	"context"
	"errors"
	"fmt"
	"log"
	"os"
	"path/filepath"

	"github.com/libtmux/libtmux-go/tmux"
)

func main() {
	if err := run(context.Background()); err != nil {
		log.Fatal(err)
	}
}

func run(ctx context.Context) (err error) {
	directory, err := os.MkdirTemp("", "libtmux-quickstart-")
	if err != nil {
		return fmt.Errorf("create socket directory: %w", err)
	}
	defer func() { err = errors.Join(err, os.RemoveAll(directory)) }()

	server, err := tmux.NewServer(tmux.ServerOptions{
		SocketPath: filepath.Join(directory, "tmux.sock"),
		ConfigFile: os.DevNull,
	})
	if err != nil {
		return fmt.Errorf("configure tmux: %w", err)
	}
	// Cleanup runs even when ctx is the reason the program is leaving.
	defer func() {
		if cleanup := server.Kill(context.WithoutCancel(ctx)); !errors.Is(cleanup, tmux.ErrNoServer) {
			err = errors.Join(err, cleanup)
		}
	}()

	session, err := server.NewSession(ctx, tmux.NewSessionRequest{Name: "demo"})
	if err != nil {
		return fmt.Errorf("create session: %w", err)
	}

	// The command runs in a window of its own, with a terminal.
	result, err := session.Run(ctx, "echo hello from tmux", tmux.RunOptions{})
	if err != nil {
		return fmt.Errorf("run command: %w", err)
	}
	for _, line := range result.Lines {
		fmt.Println(line)
	}
	fmt.Println("exit status:", result.Status)
	return nil
}

It prints:

hello from tmux
exit status: 0

To run it, save it as main.go in an empty directory, with tmux 3.3a or newer on PATH:

$ go mod init demo && \
    go get github.com/libtmux/libtmux-go/tmux@latest && \
    go run .

The program creates a socket path in a new temporary directory, then kills its server and removes that directory on the way out. With no socket in ServerOptions, it uses the socket in LIBTMUX_SOCKET_PATH, else the name in LIBTMUX_SOCKET; with neither set it reaches the server your own tmux command does.

Session.Run is os/exec for a program that needs a terminal. The command runs in a window of its own with a tty, and you get back its exit status and the screen tmux rendered.

A nonzero exit status arrives in the result. examples/run-to-completion shows one, on a tmux server of its own.

Every Go block on this page comes from a program or test that runs on the releases its features support, against a real tmux wherever it uses one. Each output shown is what that run printed.

Find sessions, windows, and panes

Server.Sessions: what tmux has now

One call asks tmux for every session. Server.Windows and Server.Panes are its siblings:

// Given: ctx context.Context; server tmux.Server
sessions, err := server.Sessions(ctx)
if err != nil {
	return fmt.Errorf("list sessions: %w", err)
}
for _, session := range sessions {
	name, _ := session.Name()
	fmt.Println("session:", name)
}

Each block on this page is the body of a function that returns an error, with the values its first line names already in hand.

examples/filter-query runs this and the two blocks below on a tmux server of its own. Like every example, it runs from a clone of the repository, with go -C examples run ./filter-query; examples/README.md has the commands to clone it.

This one prints session: libtmux-filter.

What comes back is a record: the Session you hold is what tmux said when you asked, and it stays as it was when tmux changes.

With no tmux server running, a listing returns an error matching ErrNoServer instead of an empty list, so "nothing there" and "nobody to ask" stay different answers.

tmuxq.Matching: several answers from one read

Collect a snapshot with Server.Snapshot, then filter the captured records in Go. Collection uses sequential listings and identity probes; it is not an atomic view of one instant. Matching takes any filter the tmux package declares:

// Given: ctx context.Context; server tmux.Server
snapshot, err := server.Snapshot(ctx)
if err != nil {
	return err
}
active, err := tmuxq.Matching(snapshot.Panes(), tmux.PaneActiveIs(true))
if err != nil {
	return err
}
fmt.Println("active panes:", len(active))

That session has one window with one pane in it, which is the active one, so the example goes on to print active panes: 1.

Server.SearchSessions: let tmux filter

tmux filters the listing, so only the matches come back; the call also adds the identity probes that detect a replaced daemon:

// Given: ctx context.Context; server tmux.Server
live := tmux.TmuxFilter("#{==:#{session_name},libtmux-filter}")
sessions, err := server.SearchSessions(ctx, &live)
if err != nil {
	return err
}
fmt.Println("live matches:", len(sessions))

One session has that name, so this prints live matches: 1.

The filter is a tmux format, written the way the FORMATS section of the tmux manual describes, and tmux evaluates it while it lists. tmuxq/ has the filters that run in Go and the helpers that apply them.

Session.Refresh: read a held record again

A Session, Window or Pane you hold is what tmux said when it was read, and a change to tmux does not reach it. Python libtmux's .refresh() updates the object in place; here Refresh asks tmux again and returns a replacement, and the record you called it on stays as it was. Window.Refresh and Pane.Refresh work the same way:

// Given: ctx context.Context; session tmux.Session
// session was renamed above, so it still says "old". Refresh asks tmux
// again and returns a replacement; session itself does not change.
fresh, err := session.Refresh(ctx)
if err != nil {
	return fmt.Errorf("refresh session: %w", err)
}
name, _ := fresh.Name()
fmt.Println("refreshed record:", name)

examples/object-model renames a session first, so the held record still has the old name when Refresh returns the new one:

It prints held record: old, then returned record: new, then refreshed record: new.

Type into a pane and wait

Pane.WaitForText: wait for text on every release

Pane.WaitForText polls the pane's visible screen until it holds the text, on every tmux release from 3.3a, and never opens a control client. It returns the screen it last read with every error. A pane or server that ends first returns a PaneEndedError, which matches ErrPaneEnded and, through the failed read, ErrNotFound or ErrNoServer. A deadline or cancel returns ctx.Err().

A pane is an io.Writer through Pane.Writer, and a newline is the Enter key. Type, then wait for text the command prints:

// Given: ctx context.Context; pane tmux.Pane
// A pane is an io.Writer, and a newline is the Enter key.
command := `echo "6 x 7 = $((6 * 7))"`
if _, err := fmt.Fprintln(pane.Writer(ctx), command); err != nil {
	return fmt.Errorf("type command: %w", err)
}
// The screen already holds the typed line, which says "$((6 * 7))"
// and never "42", so the answer is what the wait names.
if _, err := pane.WaitForText(ctx, tmux.WaitForTextRequest{
	Text: "6 x 7 = 42",
}); err != nil {
	return fmt.Errorf("wait for answer: %w", err)
}
fmt.Println("the pane answered 6 x 7 = 42")

The wait is level-triggered: text already on screen matches at once, with MatchedAtEntry set, and the shell's echo of a typed line matches too, so name the answer and not the command. It reads the visible screen only, so text that scrolled off is not seen, and a poll sees the final state of text a program overwrote. Each read runs the owner's after-capture-pane hook. See docs/contracts.md.

PaneObservation.WaitFor: wait for what a command prints

Needs tmux 3.7. It reports only output written after its baseline, where Pane.WaitForText matches what is already on screen. tmux.Poll is for a state tmux does not announce, not for pane text.

A pane is an io.Writer through Pane.Writer, and a newline is the Enter key. Open an observation with Pane.OpenObservation before typing, then wait for text the command prints:

// Given: ctx context.Context; pane tmux.Pane
// Open the observation before typing, so no output is missed.
output, err := pane.OpenObservation(ctx)
if err != nil {
	return fmt.Errorf("observe pane: %w", err)
}
defer func() { err = errors.Join(err, output.Close()) }()

// A pane is an io.Writer, and a newline is the Enter key.
command := `echo "6 x 7 = $((6 * 7))"`
if _, err := fmt.Fprintln(pane.Writer(ctx), command); err != nil {
	return fmt.Errorf("type command: %w", err)
}
// The terminal echoes what is typed, so wait for the answer: the typed
// line says "$((6 * 7))" and never "42".
if _, err := output.WaitFor(ctx, func(text string) bool {
	return strings.Contains(text, "6 x 7 = 42")
}); err != nil {
	return fmt.Errorf("wait for answer: %w", err)
}
fmt.Println("the pane answered 6 x 7 = 42")

tmux pushes each write as it happens, so nothing polls. The terminal echoes what is typed, and that echo is output too, which is why the wait names the answer the command prints.

Runnable: examples/pane-io, which goes on to read the pane as an io.Reader.

To move a payload, skip the typing: the shell in the pane reads quotes, $, and backslashes in it as syntax.

Server.LoadBufferFrom fills a tmux buffer from an io.Reader, and Pane.CaptureTo writes a scrollback to an io.Writer. examples/byte-streams does both.

Watch tmux change

Session.OpenNotifications: hear changes as they happen

It returns a stream of what tmux does, such as a window opening or a session being renamed. tmux pushes each change when it happens, so nothing has to guess how often to ask:

// Given: ctx context.Context; session tmux.Session
stream, err := session.OpenNotifications(ctx, tmux.NotificationOptions{})
if err != nil {
	return fmt.Errorf("open notification stream: %w", err)
}
defer func() { err = errors.Join(err, stream.Close()) }()

// Rename after subscribing; notifications do not include earlier changes.
if _, err := session.Rename(ctx, "control-example"); err != nil {
	return fmt.Errorf("rename session: %w", err)
}

for {
	notification, err := stream.Next(ctx)
	if err != nil {
		return fmt.Errorf("read notification: %w", err)
	}
	fmt.Printf("notification: %s\n", notification.Kind())
	if notification.Kind() == tmux.ControlNotificationSessionRenamed {
		fmt.Println("heard the rename")
		break
	}
}

The first notification a new stream reads is %session-changed, which is tmux telling the client which session it joined. The rename follows it.

This prints notification: %session-changed, then notification: %session-renamed, then heard the rename.

NotificationStream.Subscribe: have tmux watch a value

A subscription goes further: tmux evaluates a format for you and reports it each time its value changes, so a program hears about a window count or a pane's current command without asking again:

// Given: ctx context.Context; session tmux.Session;
// stream *tmux.NotificationStream
// A subscription is a format tmux evaluates for you: it reports the value
// when it first looks, about a second later, and then each time it changes.
if err := stream.Subscribe(ctx, tmux.SubscriptionRequest{
	Name: "windows", Format: "#{session_windows}",
}); err != nil {
	return fmt.Errorf("subscribe: %w", err)
}
if _, err := session.NewWindow(ctx, tmux.NewWindowRequest{}); err != nil {
	return fmt.Errorf("open window: %w", err)
}
for {
	notification, err := stream.Next(ctx)
	if err != nil {
		return fmt.Errorf("read notification: %w", err)
	}
	if change, ok := notification.Subscription(); ok && change.Value == "2" {
		fmt.Println("session has", change.Value, "windows")
		return nil
	}
}

The session started with one window, so this prints session has 2 windows.

Runnable: examples/control-mode-subscribe is the program both blocks come from.

Choosing how to reach tmux

Start with a plain Server: one tmux process per command. The other paths keep a tmux client attached, or send several commands at once, to start fewer:

Path Construct it with Cost Reach for it
a process per command tmux.NewServer a tmux process per command sent one-shot commands
a connection Session.OpenControl one tmux client, kept attached repeated commands
several connections ConnectionOptions{Lanes: N} N tmux clients, kept attached readers running in parallel
a plan tmux.NewPlan then Plan.Run fewer process starts builds and layouts
a stream Session.OpenNotifications one tmux client, kept attached watching what tmux does
tmux.NewPlan: write a layout in one pass

A plan records commands instead of running them, sends the ones needing no answer together, and hands back a reference to what a step will create.

new("echo built") is how Go 1.26 writes a pointer to a value, for a field that may be left unset:

// Given: window tmux.Window
plan := tmux.NewPlan()
plan.SelectLayout(window.Ref(), tmux.SelectLayoutRequest{Layout: "tiled"})
editor := plan.SplitPane(window.Ref(), tmux.SplitPaneRequest{Attach: true})
plan.SetPaneTitle(editor, "editor")
plan.SendKeys(editor, tmux.SendKeysRequest{Command: new("echo built")})
plan.DisplayMessage(editor, "#{pane_title}")

Plan.Run sends it, and reports on each step by itself even where several shared one tmux process:

// Given: ctx context.Context; server tmux.Server; plan *tmux.Plan
result, err := plan.Run(ctx, server)
if err != nil {
	return fmt.Errorf("run plan: %w", err)
}
if !result.OK() {
	return fmt.Errorf("plan did not complete: %w", result.Err())
}

// Results remain per operation even when operations share an invocation.
for index, op := range result.Ops {
	fmt.Printf("step %d %-16s %-8s created %q stdout %q\n",
		index, op.Command, op.Status, op.Created, op.Stdout)
}

examples/planned-build is the program both blocks come from.

It prints operations: 6, tmux invocations: 4 before it runs the plan: six tmux commands, in four process starts rather than six. The last step reads back the title the plan set, so its line ends stdout ["editor"].

A connection appears in list-clients and counts toward session_attached, which is why opening one is explicit.

"Execution modes" in the package documentation has the rules each path follows. BENCHMARKS.md is what each costs, measured on every supported tmux.

examples/fast-path runs the same reads down each path.

Testing your own code

tmuxtest.RunInPane: run your program in a real pane

tmux/tmuxtest runs your program inside a real tmux and lets a test assert on what it drew, without a sleep in it. This is a whole test file:

package greeter_test

import (
	"context"
	"os"
	"testing"
	"time"

	"github.com/libtmux/libtmux-go/tmux/tmuxtest"
)

func TestMain(m *testing.M) {
	os.Exit(tmuxtest.Main(m))
}

func TestGreeterAnswersALine(t *testing.T) {
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	// A stand-in for your program: it greets each name it reads.
	greeter := `while read name; do echo "hello, $name"; done`
	pane := tmuxtest.RunInPane(ctx, t, greeter)

	tmuxtest.Type(ctx, t, pane, "world")
	tmuxtest.WaitForLine(ctx, t, pane, "hello, world")
}
tmuxtest.WaitForText: a failed wait shows the screen

A wait that runs out fails with the screen the pane last held, which is what a print statement would have shown. This one was waiting for ready:

tmuxtest: pane %1 never showed a line containing "ready"
the pane showed 3 line(s):
    | tmuxtest$ printf 'loading widgets\nconnecting\n'; cat
    | loading widgets
    | connecting
More in tmuxtest

tmuxtest.NewServer gives a test whose subject is tmux itself a server on a socket of its own, killed when the test ends.

tmuxtest.ScriptedTmux answers the tmux commands you name instead, so a test of your own code needs no tmux installed. tmux/tmuxtest shows both.

Packages

Package Source Reference What it is
tmux tmux/ pkg.go.dev The library. Sessions, windows, panes, options, hooks, formats, filters, snapshots, plans.
tmuxtest tmux/tmuxtest/ pkg.go.dev Run your program in a real tmux and assert on what it drew.
tmuxq tmuxq/ pkg.go.dev Model-free generic helpers for slices and iter.Seq.

Others ship as separate modules, so go get on the library pulls in none of them:

Module Source Reference What it is
mcp mcp/ pkg.go.dev A tmux server for AI agents, over the Model Context Protocol
workspace workspace/ pkg.go.dev Loads tmuxp-style YAML workspaces and builds them, with a CLI
benchmarks benchmarks/ — Prints what each way of reaching tmux costs

The MCP server:

$ go install github.com/libtmux/libtmux-go/mcp/cmd/libtmux-mcp@latest

The workspace CLI:

$ go install github.com/libtmux/libtmux-go/workspace/cmd/tmux-workspace@latest
tmux: the library

Sessions, windows, panes, options, hooks, formats, filters, snapshots, and plans, with no dependency outside the standard library. Reference: pkg.go.dev and libtmux.org.

Session.Run runs a command in a window of its own, with a terminal. A nonzero exit status arrives in the result:

// Given: ctx context.Context; session tmux.Session
// The command runs with a terminal, so test -t holds where a pipe fails.
command := "test -t 1 && echo stdout is a terminal; exit 3"
result, err := session.Run(ctx, command, tmux.RunOptions{})
if err != nil {
	return fmt.Errorf("run command: %w", err)
}
for _, line := range result.Lines {
	fmt.Println("screen:", line)
}
fmt.Println("exited", result.Status)

This prints screen: stdout is a terminal, then exited 3.

tmuxq: filters for any slice

Helpers that find records in a slice or an iter.Seq. The package imports no tmux type, so they work on a slice of your own as well as on the records tmux returns. Reference: pkg.go.dev and libtmux.org.

tmuxq.Where keeps what a function accepts, and tmuxq.First returns the first match. This needs no tmux:

type task struct {
	name string
	done bool
}
tasks := []task{{"build", true}, {"test", false}, {"deploy", false}}
open := func(t *task) bool { return !t.done }

for _, t := range tmuxq.Where(tasks, open) {
	fmt.Println("open:", t.name)
}
first, _ := tmuxq.First(tasks, open)
fmt.Println("first open:", first.name)

This prints open: test and open: deploy, then first open: test.

tmuxtest: a real tmux for tests

Runs your program in a real pane and lets a test assert on what it drew. The server, its sessions, and its panes end with the test. Reference: pkg.go.dev and libtmux.org.

tmuxtest.NewServer gives a test whose subject is tmux itself a server on a socket of its own. This is the body of a test, in a package whose TestMain calls tmuxtest.Main, as in the test above:

// Given: ctx context.Context; t *testing.T
server := tmuxtest.NewServer(ctx, t)

session, err := server.NewSession(ctx, tmux.NewSessionRequest{Name: "under-test"})
if err != nil {
	t.Fatal(err)
}
if name, _ := session.Name(); name != "under-test" {
	t.Errorf("session name = %q, want under-test", name)
}
workspace: tmuxp-style workspaces

Loads tmuxp-style YAML workspace files and builds them in tmux, from Go or with the tmux-workspace command. Reference: pkg.go.dev, libtmux.org, and the command reference. workspace/CLI.md covers every command and its output formats.

With the command installed as above and a workspace file saved as project.yaml, tmux-workspace load builds it on a tmux socket named project without attaching to it:

$ tmux-workspace load \
    -L project \
    -d \
    ./project.yaml
mcp: a tmux server for agents

Serves one tmux server to Model Context Protocol clients, as the libtmux-mcp command or from a program of your own. Reference: pkg.go.dev, libtmux.org, and the tool pages.

mcp/README.md covers client configuration, and mcp/TOOLS.md lists each tool's arguments and results.

An MCP client starts the server installed above itself, and speaks to it over stdin and stdout. Register it with Claude Code:

$ claude mcp add tmux -- libtmux-mcp

Check which socket it reaches, and what that holds, without a client, with libtmux-mcp -doctor:

$ libtmux-mcp -doctor

The benchmarks module is a command that prints what each way of reaching tmux costs.

What it leaves out

  • Live objects. A Session or a Pane is a record of what tmux reported when it was read, and does not change when tmux does. Read again, or subscribe, to see a change.
  • A terminal emulator. A capture and Session.Run return the screen tmux drew. PaneObservation returns the bytes a program wrote.
  • Your tmux configuration file. Options and hooks are read and set on a running server, and nothing here edits ~/.tmux.conf. tmux reads it when it starts a server, unless ServerOptions.ConfigFile names another.

Support and status

Any release may change or remove exported identifiers without a deprecation period, so pin an exact version. Not recommended for production.

  • No third-party Go dependencies. The core module imports only the standard library; tmux is required at runtime.
  • Go 1.26+, rising with Go's own support window, about twice a year. tmux 3.3a through 3.8 across the core, workspace, and MCP modules, tested on releases spanning that range.
  • Linux, including WSL, is the primary platform. CI runs every module's tests on Linux against tmux 3.3a and 3.8 on each push, and across tmux 3.3a through 3.8 for each release. macOS is supported on a best-effort basis, and CI coverage for it is highly limited.
  • Other hosts. The BSDs, illumos, and Plan 9 build. A Windows build compiles, and CI tests executable lookup there; no tmux runs there.
  • Tagged per directory. Each module carries its own version: the tags are mcp/vN and workspace/vN beside the core's plain vN. Pin the exact ones you want in your own go.mod; the commands here fetch the newest. Each module keeps a changelog of its own beside its go.mod.

Documentation

The package documentation is the reference, written to be read start to finish:

$ go doc github.com/libtmux/libtmux-go/tmux

It opens with a task index, and carries the rule mapping a tmux command to its Go method: kill-pane is Pane.Kill, and rename-session is Session.Rename. A command usually leads to its method without a lookup.

Reference Every exported identifier, method, and field, one page each
tmux/ How the library is shaped: records, naming, options, errors
tmux/docs/contracts.md What the library promises: the tmux version check
examples/ Runnable programs, one per topic, each with a page of notes
DESIGN.md The conventions this package holds itself to, and the bakeoffs behind them
PARITY.md How the surface is checked against the Python libtmux
BENCHMARKS.md What each way of reaching tmux costs
CHANGELOG.md What each release of the tmux module changed, and the changes that span modules
workspace/CHANGELOG.md What each release of workspace changed
mcp/CHANGELOG.md What each release of mcp changed
SECURITY.md What this software executes, and how to report a hole in it
CONTRIBUTING.md The gates a change has to pass, and where the writing rules live
WRITING.md How this repository writes: docs, the changelog, commits
AGENTS.md Which of the above applies to what you are changing

Attribution

Please use the following BibTeX template to cite libtmux-go in scientific discourse:

@misc{libtmux-go,
   author = {Tony Narlock},
   year = {2026},
   note = {https://libtmux.org/en/go/},
   title = {libtmux-go: Go wrapper for tmux}
}

License

MIT. See LICENSE.

Directories

Path Synopsis
mcp module
Package tmux provides a typed, context-aware API for tmux 3.3a through tmux 3.8.
Package tmux provides a typed, context-aware API for tmux 3.3a through tmux 3.8.
internal/generate/docs command
Command docs replaces marked Markdown blocks with matching Go source.
Command docs replaces marked Markdown blocks with matching Go source.
internal/generate/filters command
Command filters generates the typed tmux snapshot filters.
Command filters generates the typed tmux snapshot filters.
internal/generate/formats command
Command formats generates tmux format metadata and typed accessors.
Command formats generates tmux format metadata and typed accessors.
internal/generate/options command
Command options generates tmux option and hook metadata and typed value surfaces.
Command options generates tmux option and hook metadata and typed value surfaces.
internal/goname
Package goname defines the exported Go spelling shared by generators and parity checks for tmux and Python lower_snake_case names.
Package goname defines the exported Go spelling shared by generators and parity checks for tmux and Python lower_snake_case names.
internal/layout
Package layout checks tmux's serialized layout grammar without applying geometry.
Package layout checks tmux's serialized layout grammar without applying geometry.
internal/termtext
Package termtext turns the bytes a terminal program writes into text a match can read.
Package termtext turns the bytes a terminal program writes into text a match can read.
internal/tmuxcmd
Package tmuxcmd contains the private tmux process boundary.
Package tmuxcmd contains the private tmux process boundary.
tmuxtest
Package tmuxtest runs your program inside a real tmux and lets a test assert on what it drew.
Package tmuxtest runs your program inside a real tmux and lets a test assert on what it drew.
Package tmuxq queries materialized values without representing or invoking tmux.
Package tmuxq queries materialized values without representing or invoking tmux.
workspace module

Jump to

Keyboard shortcuts

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