https://github.com/nikok6/herdr-mirror
Herdr plugin that unifies local and remote sessions in one window: mirror remote herdr servers into your local sidebar and watch or drive them over ssh.
https://github.com/nikok6/herdr-mirror
herdr-plugin
Last synced: 21 days ago
JSON representation
Herdr plugin that unifies local and remote sessions in one window: mirror remote herdr servers into your local sidebar and watch or drive them over ssh.
- Host: GitHub
- URL: https://github.com/nikok6/herdr-mirror
- Owner: nikok6
- License: mit
- Created: 2026-07-04T03:18:49.000Z (22 days ago)
- Default Branch: main
- Last Pushed: 2026-07-04T03:31:30.000Z (22 days ago)
- Last Synced: 2026-07-04T05:19:55.486Z (22 days ago)
- Topics: herdr-plugin
- Language: Rust
- Size: 43.9 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# herdr-mirror
A [herdr](https://herdr.dev) plugin that mirrors a remote herdr server's
workspaces and agents into your local sidebar. One window shows the agents on
every machine — blocked, working, done — with live pane content you can watch
and drive over ssh.
Each remote workspace becomes a real local workspace named `: `.
Its panes stream the remote terminal live; its agents report their real state.
Mirroring is one-way (the remote needs no plugin — just herdr), but you can
type into any mirror pane to drive the remote session, and create remote
workspaces/tabs/panes from your side.
> **How it works.** One Rust binary (`herdr-mirror`) with subcommand modes: a
> `daemon` (control plane — reconciles remote workspaces into local mirrors
> and pushes agent status) and one `pane` process per mirror pane (data plane
> — streams the remote terminal over ssh).
## Requirements
- **Both machines**: herdr with the `terminal session` streams — preview build
`2026-06-30` or newer (`herdr channel set preview`), until the next stable.
- **Local machine**: macOS or Linux on x86_64/aarch64 — install fetches the
prebuilt binary from Releases (dev installs via `herdr plugin link` build
from source with `cargo build --release`).
- **ssh**: non-interactive key auth to each remote
(`ssh -o BatchMode=yes true` must succeed without a prompt).
## Installation
```bash
herdr plugin install nikok6/herdr-mirror # or: herdr plugin link
```
Then create the config at `herdr plugin config-dir mirror`/`hosts.toml`
(usually `~/.config/herdr/plugins/config/mirror/hosts.toml`):
```toml
[hosts.work]
target = "work" # anything ssh accepts: alias, user@host, ssh://host:2222
```
That's it — the daemon autostarts when you focus a workspace, so within a few
seconds `work: *` workspaces appear in your sidebar. Check state any time with
`herdr-mirror status`.
If you've disabled autostart, start the daemon yourself by keybinding the
"Mirror: start" action or running:
```bash
./target/release/herdr-mirror start
```
## Usage
**Watch** — mirror panes are read-only by default: a live view of the remote
pane with zero effect on the remote.
**Drive** — type into a mirror pane to take control (your keystroke is
delivered). Both sides now share the session, tmux-style. Control auto-releases
after 60s idle; `ctrl+\` releases immediately. The mouse wheel scrolls remote
scrollback while driving.
**Close / restore** — by default, closing a mirror (`prefix+x`) also closes the
pane/workspace on the remote (`close_remote_on_local_close`; set it false to
only stop mirroring and leave the remote — and its agent — running). When the
remote is left running, the **restore** action (`herdr-mirror restore`) brings
back mirrors you closed.
**Pause** — the **pause** action halts syncing; mirrors stay frozen in place
and resume with **start**. `teardown` closes all mirrors and clears state.
**Create on the remote** — four actions create objects on the remote host,
inheriting the target host and cwd from the mirror you invoke them from (the
same rule as native `prefix+shift+n`, across ssh): `remote-new-workspace`,
`remote-new-tab`, `remote-split-right`, `remote-split-down`. The new object
mirrors back within seconds.
**Continuous streaming** — every mirror pane streams its remote pane live for
its whole lifetime, each over its own direct ssh connection, so panes are never
blank and a busy pane can't contend with or drop another's stream. Sidebar
agent status is daemon-driven, not stream-derived, so every agent's state stays
live regardless of what any stream is doing.
### Keybinds
Actions have no default keys; bind them in `~/.config/herdr/config.toml`, then
`herdr server reload-config`:
```toml
[[keys.command]]
key = "prefix+shift+m"
type = "plugin_action"
command = "mirror.start" # start / resume the daemon
[[keys.command]]
key = "prefix+shift+s"
type = "plugin_action"
command = "mirror.pause" # freeze syncing; start again to resume
[[keys.command]]
key = "prefix+shift+b" # "bring back" (shift+r is native reload_config)
type = "plugin_action"
command = "mirror.restore" # un-close mirrors you closed locally
# Create objects on the REMOTE host — run these from inside a mirror pane, which
# supplies the target host and cwd. Each is herdr's native local key + alt
# (Option): same muscle memory, but it acts on the remote over ssh.
[[keys.command]]
key = "prefix+alt+n" # native new_workspace = prefix+shift+n
type = "plugin_action"
command = "mirror.remote-new-workspace"
[[keys.command]]
key = "prefix+alt+c" # native new_tab = prefix+c
type = "plugin_action"
command = "mirror.remote-new-tab"
[[keys.command]]
key = "prefix+alt+v" # native split_vertical = prefix+v
type = "plugin_action"
command = "mirror.remote-split-right"
[[keys.command]]
key = "prefix+alt+minus" # native split_horizontal = prefix+minus
type = "plugin_action"
command = "mirror.remote-split-down"
```
The `remote-*` actions need a focused mirror pane (except `remote-new-workspace`,
which falls back to `default_host` when run outside one). The remaining
actions — `mirror.status`, `mirror.once`, `mirror.teardown`, `mirror.ensure` —
are lifecycle/diagnostic and are usually run from the CLI rather than bound.
## Configuration
`hosts.toml`:
```toml
# autostart = true # focusing a workspace starts the daemon (default).
# A manual pause is sticky until you start again;
# a crash still auto-recovers on next focus.
# poll_seconds = 60 # reconcile poll interval (events drive most syncs)
# default_host = "work" # host that "new remote workspace" targets when
# invoked outside any mirror (default: first host)
# close_remote_on_local_close = true
# default. Closing a mirror pane/workspace locally
# (e.g. prefix+x) also closes it on the remote. Set
# false to only stop mirroring on a local close,
# leaving the remote pane and its agent running.
[hosts.work]
target = "work"
# prefix = "work" # sidebar prefix (default: the host key)
# remote_bin = "~/.local/bin/herdr" # remote path if it's not on ssh's PATH
# enabled = true # false stops syncing this host without
# deleting its config; mirrors stay put
[hosts.vps] # add more hosts freely; each is independent
target = "ssh://niko@203.0.113.7:2222"
```
## Limitations
- **Version-locked to preview** until the `terminal session` streams reach
stable; keep both machines on the same build.
- **Latency** above raw ssh: keystroke echo is a rendered frame round-trip, so
there's a small constant delay. For latency-critical work, plain `ssh `
is always one command away.
- **Layout geometry is copied, not linked**: pane content and typing are
always live — only sizes are a snapshot. Remote pane adds/removes reconcile,
but a split-ratio change at the remote doesn't resize an existing mirror.
- **No git status on mirror rows** — herdr derives the sidebar git branch and
ahead/behind from the local workspace cwd, and there's no API to feed it a
remote repo's state, so mirror workspaces show no git chip. The remote's real
branch and status stay visible in the streamed pane's prompt.
- **No custom sidebar UI** (plugin API limitation): mirrors are grouped only by
the `: ` naming convention.
- **Remote must be reachable and running herdr**; the daemon surfaces a
readable status if a host is down or on too old a version.
## License
MIT — see [LICENSE](./LICENSE).