Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Install tma

Four ways to get the binary: the install script, cargo install from a clone, the Nix flake, or the Home Manager module. All four land the same single tma executable, so pick the one that matches how you manage the rest of the machine.

You need tmux 3.6 or newer. That is the release tma is developed and tested against; older servers load configs in a different order and expand display-popup differently, so the keybindings and the picker can misbehave. tma doctor warns when the server it is talking to is older, and keeps working.

Installing the binary is the whole install. Hooks (tma install-hooks <agent>) and keybindings (tma install-keys) are separate commands you run afterward, because both edit files you own and both show you the diff first. tma init runs that whole sequence for the agents it finds on your PATH, if you would rather do it in one command.

With the install script

The shortest path, and the one that needs no toolchain. It works from the first public release onward, because it downloads a release artifact:

$ curl -fsSL https://raw.githubusercontent.com/pperanich/tmux-agents/main/scripts/install.sh | sh

The script picks the build for your platform, verifies the tarball against the release’s SHA256SUMS, installs the binary to ~/.local/bin, and prints a PATH hint if that directory is not on yours. It never uses sudo. To upgrade, run it again: the new binary replaces the old one in place.

Prebuilt binaries cover macOS on Apple Silicon and Intel, and Linux on x86_64 and aarch64. The Linux builds are static musl binaries, so your distribution’s glibc does not come into it. On any other platform the script stops and points you at the cargo path below.

It also installs shell completions, for whichever of bash, zsh, and fish it finds on your machine, into that shell’s per-user directory. zsh needs one more line in your ~/.zshrc for its directory to be searched at all, which the script prints when it applies.

Three environment variables change what it does:

variableeffect
TMA_VERSIONInstall this tag instead of the latest release.
TMA_INSTALL_DIRInstall here instead of ~/.local/bin. Created if it does not exist.
TMA_NO_COMPLETIONSSet to anything to install only the binary.

They belong on the sh, not on the curl, or the pipeline hands them to the wrong process:

$ curl -fsSL https://raw.githubusercontent.com/pperanich/tmux-agents/main/scripts/install.sh \
    | TMA_VERSION=v0.5.15 TMA_INSTALL_DIR=~/bin sh

If piping a script into a shell is not your habit, read it first and run it from disk.

From a clone, with cargo

You need git and a Rust toolchain (1.88 or newer):

$ git clone https://github.com/pperanich/tmux-agents
$ cd tmux-agents
$ cargo install --path crates/tma

The workspace builds one binary. Check it landed on your PATH:

$ tma --version
tma 0.5.15

To upgrade, git pull and run the same cargo install again; it replaces the binary in place.

cargo install places a binary and nothing else, so wire the completions yourself — tma completions <shell> writes the script for one shell to stdout, and tma completions says where each shell wants it. The install script and the Nix package both do this for you.

From the Nix flake

The flake builds for x86_64-linux, aarch64-linux, x86_64-darwin, and aarch64-darwin, and exposes:

outputwhat it is
packages.<system>.tma, packages.<system>.defaultthe tma package, versioned from the workspace Cargo.toml
overlays.defaultadds tma to a nixpkgs instance
homeModules.default, homeModules.tmathe Home Manager module below (homeManagerModules is an alias for older setups)
devShells.defaultthe contributor shell: the package’s build inputs plus clippy, rustfmt, rust-analyzer, tmux, mise, and mdbook
checks.<system>.tmathe package build, whose test suite spawns its own scratch tmux server
formatter.<system>nixfmt-rfc-style, so nix fmt formats the Nix files

Try it without installing anything, or install it for real:

$ nix run github:pperanich/tmux-agents -- --version
$ nix profile install github:pperanich/tmux-agents

There is no apps output; nix run resolves packages.default, whose meta.mainProgram is tma.

To pull the package into your own flake, use the overlay:

{
  inputs.tma.url = "github:pperanich/tmux-agents";

  # wherever you build your nixpkgs instance:
  nixpkgs.overlays = [ inputs.tma.overlays.default ];  # then: pkgs.tma
}

The hook wrapper

tma install-hooks writes a small tma-hook wrapper next to the tma binary. That directory is the read-only store here, so the Nix package installs the wrapper itself: install-hooks finds its own script already in place and writes nothing. Nothing to set, and tma-hook comes onto your PATH with the binary, which is all the default [install] wrapper_ref = "bare" needs: agent configs get the name tma-hook and resolve it off $PATH at hook time. tma doctor shows the reference and the file behind it:

wrapper: tma-hook ✓ on $PATH (/nix/store/<hash>-tma-<version>/bin/tma-hook)

wrapper_ref = "absolute" is where the store needs care, and tma takes it. What lands in the agent config is then a path outside the store: your profile’s ~/.nix-profile/bin/tma-hook, or /etc/profiles/per-user/<user>/bin/tma-hook under Home Manager. tma will not write the store path itself, because that path names one build and is deleted when the build is collected, which would break your hooks at the next nix flake update rather than at anything you did. It finds the profile entry by walking $PATH for a tma-hook outside any store that resolves to the same file, so the substitution only happens when the two are provably the same install:

wrapper: /etc/profiles/per-user/you/bin/tma-hook ✓ (/nix/store/<hash>-tma-<version>/bin/tma-hook)

Running straight from the store with no profile install (nix run, a nix build result) has no such stable path to find, so tma wires the store path and warns that it will not survive collection.

--wrapper-path <PATH> (env TMA_WRAPPER_PATH) keeps the wrapper out of the store entirely by naming where it is written; tma install-hooks --check and tma doctor resolve it the same way install did, so export the variable rather than passing the flag once.

With the Home Manager module

{
  imports = [ inputs.tma.homeModules.default ];

  programs.tma = {
    enable = true;
    settings.notify.on = [ "blocked" "done" ];
    keybindings.enable = true;
  };
}

The module’s options:

optiontypedefaulteffect
programs.tma.enableboolfalseInstalls the package and writes the files the options below ask for.
programs.tma.packagepackagepkgs.tma when the overlay is applied, otherwise built from the module’s own source treeThe package to install.
programs.tma.settingsTOML attrset{}Written to $XDG_CONFIG_HOME/tma/config.toml. An empty attrset writes no file at all, leaving tma on its built-in defaults. Keys are the ones in configuration, and an unknown one is a parse error at runtime, not at build time.
programs.tma.agentsattrset of TOML attrsets{}Each entry becomes $XDG_CONFIG_HOME/tma/agents/<name>.toml. A new stem adds an agent; a stem matching a bundled manifest (claude, codex, cursor, gemini, opencode, pi) replaces it wholesale, so that entry has to be a complete manifest.
programs.tma.keybindings.enableboolfalseAppends tma’s tmux bindings to programs.tmux.extraConfig. An assertion fails the build unless programs.tmux.enable is also on.

keybindings.enable is the declarative alternative to running tma install-keys. Use one or the other: both write the same keys, and running both defines each binding twice. The module writes the whole prefix set and nothing else; the opt-in mouse bindings still need tma install-keys --mouse. A test in the binary reads the module and fails if its block drifts from the bindings install-keys writes, so the two cannot silently disagree. See Keybindings for what each key does.

The module installs the binary and writes config. It does not touch your agents’ own config files, so wiring an agent is still tma install-hooks, which needs nothing set.

Next