# tmuxrdr herdr without the disrespect for tmux users. [herdr](https://herdr.dev) is a terminal workspace manager for AI coding agents: tmux's model -- a server owning panes, clients attaching -- plus an agent-aware sidebar. Its keys are its own. tmuxrdr is herdr with tmux's keys and tmux's behaviour wherever herdr can be made to have them: tmux 3.7c's default prefix table, with this machine's `dot/config/tmux.conf` and `~/.tmux.conf.local` on top. It is a separate binary, `tmuxrdr`, with its own config, sessions and plugins, so it runs beside stock herdr without touching it. Everything tmuxrdr adds is in this directory, plus one small patch to herdr's code. The rest of the checkout is herdr, on a herdr release tag. | Path | What | |---|---| | `config/config.toml` | tmuxrdr's config: tmux's bindings on herdr's actions, each commented with the tmux line it mirrors | | `plugin/` | the tmux-mode plugin: the bindings herdr has no action for -- break-pane, swap/rotate by pane order, marks, last session, select/move-window, paste-buffer, display-message, select-layout -E, kill with y/n, the `:` prompt, clock, messages, automatic-rename | | `scripts/build` | builds the checkout and installs it as `~/.local/bin/tmuxrdr` | | `scripts/install` | points tmuxrdr at the config and plugin | | `scripts/test-e2e` | presses every binding in a real tmuxrdr client and checks the result | | `scripts/sync-upstream` | rebases tmuxrdr onto a newer herdr release | | `ROADMAP.md` | what only further patches to herdr's code can do, with the patch for each | ## Status - Based on herdr **v0.9.3**, the release Homebrew ships. - Code patches: tmuxrdr's own directories (`src/config/io.rs`, below), and tmux's status line as the tab row -- `ui.tab_style = "tmux"`, drawn by `src/client/shell/tabs_tmux.rs`; client-side only, so it works against a stock herdr server too. - Of tmux's 88 default prefix bindings, **67 work** (some approximately -- ROADMAP.md, "Coverage"), **5 are moot** in herdr, **16 need code**. `scripts/test-e2e`: 76 checks, all passing. ## tmuxrdr and herdr side by side | | `herdr` (Homebrew) | `tmuxrdr` (this checkout) | |---|---|---| | Binary | `/opt/homebrew/bin/herdr` | `~/.local/bin/tmuxrdr` | | Config | `~/.config/herdr/config.toml`, herdr's own | `~/.config/tmuxrdr/config.toml` -> `config/config.toml` | | Plugins | its own registry (none here) | its own registry: `tmuxrdr.tmux-mode` | | Sessions, sockets, logs | `~/.config/herdr/` | `~/.config/tmuxrdr/` | | State | `~/.local/state/herdr/` | `~/.local/state/tmuxrdr/` | The patch: herdr derives every one of those directories from `app_dir_name()` in `src/config/io.rs`, and tmuxrdr's returns `tmuxrdr` instead of `herdr`. Debug builds keep `herdr-dev`, which herdr's tests expect. The binary is cargo's `herdr`, installed under the name `tmuxrdr`; it starts its server from its own path, so nothing else needs renaming. Run either, in any terminal: `herdr`, `tmuxrdr`, `tmuxrdr --session work`. What they still share: the environment variable names (`HERDR_ENV`, `HERDR_SOCKET_PATH`, ...). Inside a tmuxrdr pane, `herdr` CLI commands -- and the agent integrations, which use them -- reach that tmuxrdr server, which is what they should do. Starting one's interface inside the other's pane is refused as a nested launch; `env -u HERDR_ENV -u HERDR_SOCKET_PATH herdr` gets past that when wanted. ## Install ```sh tmuxrdr/scripts/build # Building, below, for the toolchains tmuxrdr/scripts/install # dry run: what it would do tmuxrdr/scripts/install yes ``` `install` links `~/.config/tmuxrdr/config.toml` to `config/config.toml`, keeping a real file already there as `config.toml.pre-tmuxrdr`, and `~/.local/share/tmuxrdr/plugin` to `plugin/`, which it registers with `tmuxrdr plugin link`. It refuses any binary that does not read `~/.config/tmuxrdr/config.toml`, so stock herdr cannot pick tmuxrdr's setup up by mistake. herdr saves its settings in place, so changes made from tmuxrdr's settings screen land in `config/config.toml` -- `git diff` shows them. In a running tmuxrdr: `prefix+r`. Needs `jq`. Run it again after moving the checkout. To leave: `tmuxrdr plugin unlink tmuxrdr.tmux-mode`, then remove `~/.local/bin/tmuxrdr`, `~/.config/tmuxrdr`, `~/.local/state/tmuxrdr` and `~/.local/share/tmuxrdr`. Stock herdr is unaffected either way. ## Test ```sh tmuxrdr/scripts/test-e2e ``` About a minute. It checks the config, runs a tmuxrdr client inside a throwaway tmux server (its own socket, its prefix off) on a throwaway session and a copy of the config, presses each binding, checks the effect through the CLI, and removes everything after. Your sessions and config are not touched. `HERDR=target/release/herdr` tests a build before installing it. ## Maintaining The fork is meant to follow herdr release by release with as little work as possible. What keeps it that way: - **One branch, `tmuxrdr`:** a herdr release tag with tmuxrdr's commits on top. Remote `upstream` is herdr, fetch-only -- its push URL is disabled. - **Fork content stays in `tmuxrdr/`.** Upstream never touches this directory, so those commits never conflict. Do not edit upstream files for anything that can live here -- not even the top-level README. - **Code patches are one commit each, and small.** The directory patch is a single line. Put larger logic in new files and keep the lines changed in upstream's to the hooks that call it; ROADMAP.md says where each touches. - **rerere is on** in this checkout (`git config rerere.enabled true`), so a conflict resolved once is resolved by itself next time. To move to a new herdr release: ```sh tmuxrdr/scripts/sync-upstream # or: sync-upstream v0.9.4 tmuxrdr/scripts/build tmuxrdr/scripts/test-e2e ``` `sync-upstream` keeps the branch as it was under `tmuxrdr-on-`; if something is wrong, `git switch -C tmuxrdr tmuxrdr-on-` puts it back. `brew upgrade herdr` moves stock herdr to the same release. ## Building `scripts/build` finds its toolchains without PATH or shell-profile changes -- dot's shell profiles are links into a repo: - **Rust**, the version `rust-toolchain.toml` pins (1.96.1), through rustup: Homebrew's keg-only `rustup` (`brew install rustup`), used from `/opt/homebrew/opt/rustup/bin` when no cargo is on PATH. - **Zig, exactly 0.16.0**, which herdr's vendored libghostty-vt requires; Homebrew's is newer. `$ZIG`, else `~/.local/opt/zig-0.16.0/zig`: the ziglang.org tarball, checked against its minisign signature (key `RWSGOq2NVecA2UPNdBUZykf1CCb147pkmdtYxgb3Ti+JO/wCYvhbAb/U`, as published on ziglang.org/download). - **sfw** (Socket Firewall): crates are fetched through it. Zig's packages are fetched first, by Zig itself, each checked against the hash in `build.zig.zon` -- sfw's proxy breaks Zig's downloads. It installs `target/release/herdr` as `~/.local/bin/tmuxrdr` by a rename, so a running tmuxrdr server keeps the old binary until it restarts (`tmuxrdr server stop` ends it and its panes). `[update] version_check` is off in the config: herdr's update would download stock herdr over tmuxrdr. Never run `tmuxrdr update`; new releases come through `sync-upstream`. The build prints herdr's notice for external contributors; tmuxrdr does not contribute upstream, so it does not apply. License: Apache-2.0, like herdr (`LICENSE` at the top of the checkout).