1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
|
# 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-<old tag>`; if
something is wrong, `git switch -C tmuxrdr tmuxrdr-on-<old tag>` 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).
|