cli tools
tmux
go

tmx: Taking My Tmux Session Manager to the Next Level

7 min read

In a previous article, I wrote about tmux-sessionizer — a small bash script that combined find, fzf, and tmux to let me jump between projects with a single keypress. It was under 30 lines, dead simple, and it genuinely changed how I work.


But as with most tools you use every day, you start noticing rough edges. The script was fast to write but slow to evolve. Adding features meant wrestling with bash, and I wanted something I could actually maintain and extend over time. So I did what any reasonable developer would do: I rewrote it in Go.


The result is tmx — a proper CLI tool that does everything the original script did, and quite a bit more.


What Was Missing in the Bash Script

The original tmux-sessionizer had one job: find a directory, create a tmux session, switch to it. It did that job well. But I kept running into the same limitations:

  • No way to configure different window layouts per project
  • Hard-coded search depth — always one level deep
  • No awareness of which projects I actually use frequently
  • Adding any new behavior meant hacking the script in ways that made it harder to read


I wanted a tool that knew about my projects, not just my filesystem.


Why Go?

I've been using Go for CLI tools for a while now, and it fits the use case really well. Single binary, fast startup, easy to distribute, and the standard library covers most of what you need for filesystem operations and running external commands. Compared to bash, the code is dramatically easier to read after a few months away from it. And compared to something like Rust, the learning curve is much gentler while still giving you solid performance and type safety.


For a tool that runs dozens of times a day, startup time matters. A Go binary starts in milliseconds — no interpreter, no VM, no warm-up.


What tmx Does

At its core, tmx still does what the bash script did: present a fuzzy-searchable list of directories, create a tmux session for the one you pick, and attach to it. But there are a few additions that made a real difference in daily use.


Zoxide integration

Zoxide is a smarter cd replacement that tracks which directories you visit and how often. tmx queries the zoxide cache and puts those frequently-used directories at the top of the fzf list, marked with a ★.


This sounds like a small thing, but in practice it means I almost never have to type anything in fzf. My most-used projects bubble to the top automatically. If zoxide isn't installed, tmx silently falls back to regular directory search — no errors, no configuration needed.


fd support with configurable depth

The original script used find with a fixed depth of 1. tmx detects whether fd is available and uses it instead (it's significantly faster on large directory trees). It falls back to find if fd isn't installed.


More importantly, the search depth is now configurable. The default is still 1 (direct subdirectories, fastest), but you can go deeper:

Search 3 levels deep tmx --depth 3 # Unlimited depth in a specific directory tmx --depth 0

~/Projects


Workspace configurations

This is the feature I wanted most. tmx reads a TOML config file from ~/.config/tmx/tmx.toml where you can define workspace layouts per project:

[[workspace]] directory = "/git/my-app" name = "my app" windows = ["editor", "server", "lazygit"]


[[workspace]] directory = "/git/another-project" name = "another project" windows = ["code", "build", "logs"]


When you select a directory that matches a workspace config, tmx creates the session with exactly those windows. For everything else, it falls back to a single default window — same behavior as the old script.


The matching uses base path comparison, so /git/my-app matches any path ending in my-app. This means the config stays valid even if you move your projects folder around.


How It's Structured

The codebase is split into focused packages, each responsible for one thing:

  • pkg/discovery — orchestrates directory listing by combining zoxide and find/fd results, deduplicates paths, and feeds the list to fzf

  • pkg/search — the low-level layer that actually runs zoxide and find/fd

  • pkg/session — manages the full tmux session lifecycle: create, attach, list, kill

  • pkg/ui — thin wrapper around fzf

  • pkg/config — parses and validates the TOML configuration


Extra Commands

Beyond the main workflow, tmx ships with a few subcommands I use regularly:

  • tmx list (or tmx ls) — list all active tmux sessions

  • tmx connect (or tmx c) — connect to an existing session by name, with fzf for selection

  • tmx kill — kill a session


Shell completions are also included for bash, zsh, and fish. Running eval "$(tmx completion zsh)" in your .zshrc gives you tab completion for subcommands and flags.


Installation

If you're on macOS, the easiest way is Homebrew:

brew install vbrdnk/tap/tmx

Or if you have Go installed:

go install github.com/vbrdnk/tmx@latest

What I Learned From This Rewrite

Rewriting a working tool is usually a mistake. The bash script worked fine. But this rewrite taught me a few things worth carrying into future projects.


Start with the user-facing behavior, not the implementation. Before writing a line of Go, I listed exactly what the tool should do and where the bash script fell short. This kept the scope tight — I didn't end up adding features I didn't need.


Graceful degradation matters more than you think. Both fd and zoxide are optional in tmx. If you don't have them, the tool still works — it just uses find and skips the frecency sorting. This means I can install tmx on any machine and it works immediately, without worrying about which optional tools are available.


Configuration should be additive. Not every project needs a custom workspace. The tool works without any config file at all, and you only add workspace definitions for the projects where you actually want custom layouts. Complexity is opt-in.


What's Next

There are a couple of things I still want to add. The most useful would be a way to run a setup script when a new session is created — similar to the ready-tmux approach I mentioned in the previous article. For now, the window layout gets you most of the way there for most projects.


If you're already using tmux-sessionizer or a similar bash script and find yourself wanting more control over your workspace setup, give tmx a try. The source is on GitHub.


Keep in touch and happy hacking!