<div align="center">
<picture>
<source srcset="https://raw.githubusercontent.com/libtmux/libtmux-rs/HEAD/crates/tmux-workspace/assets/logo.svg" type="image/svg+xml">
<img src="https://raw.githubusercontent.com/libtmux/libtmux-rs/HEAD/crates/tmux-workspace/assets/logo.png" width="128" height="128" alt="libtmux for Rust workspace">
</picture>
</div>
<div align="center">
# tmux-workspace
Load tmux sessions from YAML, and save running ones back to it. A native
[tmuxp](https://tmuxp.git-pull.com/): it reads tmuxp's workspace files, finds
them where tmuxp does and takes tmuxp's flags, as one binary with no Python.
</div>
> **Alpha.** Commands and flags can change between releases, and changes are
> listed in the [changelog][changelog] rather than flagged as breaking.
> `cargo install` skips prereleases unless asked, so the command below names
> the version.
## Install
```console
$ cargo install tmux-workspace \
--version 0.1.0-alpha.16 \
--locked
```
It needs tmux 3.3a or newer on `PATH`, and Rust 1.85 to build.
[`cargo binstall`](https://github.com/cargo-bins/cargo-binstall) fetches a
prebuilt binary for Linux or macOS when the release carries one, and builds
from source when it does not:
```console
$ cargo binstall tmux-workspace --version 0.1.0-alpha.16
```
## Load a session
Describe the session:
```yaml
session_name: dev
windows:
- window_name: editor
layout: main-vertical
panes:
- vim
- git status
- window_name: monitor
panes:
- top
```
Save it as `dev.yaml`, and load it:
```console
$ tmux-workspace load ./dev.yaml
```
That builds the session and attaches to it. Run inside tmux, it asks whether
to switch to the new session, keep it in the background, or add its windows
to the session you are in.
## Loading
Build the `.tmuxp.yaml` in a project directory:
```console
$ tmux-workspace load .
```
Keep workspaces in `~/.config/tmuxp/` and load them by name from anywhere:
```console
$ tmux-workspace load dev
```
See what one would build -- sessions, windows, panes, directories and
commands -- without touching tmux or running its `before_script`:
```console
$ tmux-workspace load --dry-run dev
```
Load several, attaching to the last:
```console
$ tmux-workspace load api web
```
Build without attaching:
```console
$ tmux-workspace load -d dev
```
Build under a different session name:
```console
$ tmux-workspace load -s scratch dev
```
Add the windows to the session you are in:
```console
$ tmux-workspace load -a tools
```
Build on a separate tmux server, named or by socket path:
```console
$ tmux-workspace load -L work dev
```
## Saving and converting
Save a running session as a workspace:
```console
$ tmux-workspace freeze dev -o dev.yaml
```
Freezing recovers windows, panes, layouts and working directories. It cannot
recover the command someone typed to start a pane, only what the pane is
running now, so read the file before relying on it.
Convert between YAML and JSON; it shows the destination and asks first:
```console
$ tmux-workspace convert dev.yaml
```
Import a tmuxinator project or a teamocil layout. It asks, then saves it
where `load` finds it by name -- here `~/.config/tmuxp/api.yaml`, so
`tmux-workspace load api` builds it next:
```console
$ tmux-workspace import tmuxinator api
```
```console
$ tmux-workspace import teamocil dev
```
Open a workspace in `$EDITOR`:
```console
$ tmux-workspace edit dev
```
## Finding workspaces
List every workspace it can find, grouped by directory:
```console
$ tmux-workspace ls --tree
```
Search them; `window:`, `session:`, `pane:` and `path:` limit a pattern to
one field:
```console
$ tmux-workspace search window:logs
```
## Shell setup
Completions and the manual page are generated by the binary. For zsh, with
`~/.zfunc` on `fpath`:
```console
$ tmux-workspace --generate zsh > ~/.zfunc/_tmux-workspace
```
```console
$ tmux-workspace --generate man > ~/.local/share/man/man1/tmux-workspace.1
```
bash, fish, PowerShell and elvish are in the
[command reference](docs/cli.md#generating-completions-and-the-manual). Every
command has examples under `--help`.
Attach this to a bug report:
```console
$ tmux-workspace debug-info
```
## Coming from tmuxp
Your workspace files load unchanged: the same keys, the same `.tmuxp.yaml`
project files, the same directories (`$TMUXP_CONFIGDIR`, `~/.config/tmuxp`,
`~/.tmuxp`) and the same `load` flags (`-d`, `-s`, `-a`, `-L`, `-S`, `-f`,
`-2`, `-y`). Where it differs:
- An unknown key is refused rather than ignored. Prefix a key with `x-` to
carry your own fields; `convert` preserves them.
- A window that names no `layout` is tiled rather than stacked.
- A `session_name` containing `:` or `.` is refused: tmux reads those as
window and pane separators.
- `$VAR` in a command is expanded by the pane's shell, with your value passed
to it, rather than pasted into the command text: a value holding `;` stays
a value.
- `freeze` saves only where `-o` says, or prints with `--json`.
- `plugins` and custom `workspace_builder`s run through tmuxp itself, so they
need tmuxp 1.74.x and Python libtmux declaring a hard tmux minimum of 3.3a
or newer with per-command daemon version check. Runtime discovery and the
builder child check this before importing extension hooks.
The Python bridge depends on the coordinated [Python libtmux tmux-support
change](https://github.com/tmux-python/libtmux/pull/779), which has not been
released. CI qualifies its pinned source with tmux 3.3a. Released tmuxp 1.74.0
pins libtmux below 0.62; installing that release alone does not qualify the
bridge. Use a compatible tmuxp environment carrying the updated Python library
and select it with `TMUX_WORKSPACE_PYTHON`.
The [command reference](docs/cli.md) has the complete list.
It starts in about a millisecond where tmuxp takes about 125, and a cold
two-window load takes about 150 ms against 205 (one Linux machine, tmux 3.7d,
tmuxp 1.74.0, medians of 20 starts and 5 loads). To measure your own:
```console
$ just bench-cli --binary target/release/tmux-workspace --tmuxp "$(command -v tmuxp)"
```
## Scripting
`--json` prints one JSON document, and `--ndjson` streams one event per line
as windows and panes are created. A load under either needs `-d`, because
nothing is attached:
```console
$ tmux-workspace --ndjson load -d api web
```
A failure writes `{"schema_version":1,"code":"...","message":"..."}` to
stderr and exits 1, or 2 when the command line itself is wrong. The codes and
events are in the [command reference](docs/cli.md#machine-output).
## Let an agent watch it
[`tmux-mcp`](https://crates.io/crates/tmux-mcp) gives an MCP client typed,
read-only access to the sessions a workspace builds. Load on a named server
and point the server at the same one:
```console
$ tmux-workspace load -d -L agents dev
```
```console
$ LIBTMUX_TOOLSETS=inspect tmux-mcp -L agents
```
Configure your MCP client to launch that second command. The workspace keeps
running after the client disconnects. The
[tmux-mcp README](https://github.com/libtmux/libtmux-rs/blob/master/crates/tmux-mcp/README.md)
covers installing it and wiring clients.
## Use it from Rust
The same package is a library: parse a workspace, preview the tmux commands
it would run, build it, or freeze a session, through
[libtmux](https://docs.rs/libtmux). It reads a document the way the command
does, tmuxp's way; the command reference lists
[where the two differ](docs/cli.md#where-the-command-and-the-library-differ).
```console
$ cargo add tmux-workspace@0.1.0-alpha.16 --no-default-features
```
`--no-default-features` leaves the command and its dependencies out.
Examples and the API are on [docs.rs](https://docs.rs/tmux-workspace).
## Development
The tests drive a real tmux on an isolated socket, so tmux must be on `$PATH`:
```console
$ cargo test -p tmux-workspace --all-features
```
## License
Licensed under either of [Apache License, Version 2.0](LICENSE-APACHE)
or [MIT license](LICENSE-MIT) at your option.
[changelog]: https://github.com/libtmux/libtmux-rs/blob/master/CHANGELOG.md