{"id":51442829,"url":"https://github.com/nikok6/herdr-mirror","last_synced_at":"2026-07-05T13:01:45.240Z","repository":{"id":369200390,"uuid":"1288844316","full_name":"nikok6/herdr-mirror","owner":"nikok6","description":"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.","archived":false,"fork":false,"pushed_at":"2026-07-04T03:31:30.000Z","size":45,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-04T05:19:55.486Z","etag":null,"topics":["herdr-plugin"],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nikok6.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-07-04T03:18:49.000Z","updated_at":"2026-07-04T03:34:28.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/nikok6/herdr-mirror","commit_stats":null,"previous_names":["nikok6/herdr-mirror"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/nikok6/herdr-mirror","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nikok6%2Fherdr-mirror","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nikok6%2Fherdr-mirror/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nikok6%2Fherdr-mirror/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nikok6%2Fherdr-mirror/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nikok6","download_url":"https://codeload.github.com/nikok6/herdr-mirror/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nikok6%2Fherdr-mirror/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35154748,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-05T02:00:06.290Z","response_time":100,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["herdr-plugin"],"created_at":"2026-07-05T13:01:42.857Z","updated_at":"2026-07-05T13:01:45.205Z","avatar_url":"https://github.com/nikok6.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# herdr-mirror\n\nA [herdr](https://herdr.dev) plugin that mirrors a remote herdr server's\nworkspaces and agents into your local sidebar. One window shows the agents on\nevery machine — blocked, working, done — with live pane content you can watch\nand drive over ssh.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"assets/hero.png\" width=\"720\" alt=\"herdr-mirror: local and remote herdr sessions unified in one window\"\u003e\n\u003c/p\u003e\n\nEach remote workspace becomes a real local workspace named `\u003chost\u003e: \u003cname\u003e`.\nIts panes stream the remote terminal live; its agents report their real state.\nMirroring is one-way (the remote needs no plugin — just herdr), but you can\ntype into any mirror pane to drive the remote session, and create remote\nworkspaces/tabs/panes from your side.\n\n\u003e **How it works.** One Rust binary (`herdr-mirror`) with subcommand modes: a\n\u003e `daemon` (control plane — reconciles remote workspaces into local mirrors\n\u003e and pushes agent status) and one `pane` process per mirror pane (data plane\n\u003e — streams the remote terminal over ssh).\n\n## Requirements\n\n- **Both machines**: herdr with the `terminal session` streams — preview build\n  `2026-06-30` or newer (`herdr channel set preview`), until the next stable.\n- **Local machine**: macOS or Linux on x86_64/aarch64 — install fetches the\n  prebuilt binary from Releases (dev installs via `herdr plugin link` build\n  from source with `cargo build --release`).\n- **ssh**: non-interactive key auth to each remote\n  (`ssh -o BatchMode=yes \u003chost\u003e true` must succeed without a prompt).\n\n## Installation\n\n```bash\nherdr plugin install nikok6/herdr-mirror     # or: herdr plugin link \u003cpath\u003e\n```\n\nThen create the config at `herdr plugin config-dir mirror`/`hosts.toml`\n(usually `~/.config/herdr/plugins/config/mirror/hosts.toml`):\n\n```toml\n[hosts.work]\ntarget = \"work\"        # anything ssh accepts: alias, user@host, ssh://host:2222\n```\n\nThat's it — the daemon autostarts when you focus a workspace, so within a few\nseconds `work: *` workspaces appear in your sidebar. Check state any time with\n`herdr-mirror status`.\n\nIf you've disabled autostart, start the daemon yourself by keybinding the\n\"Mirror: start\" action or running:\n\n```bash\n./target/release/herdr-mirror start\n```\n\n## Usage\n\n**Watch** — mirror panes are read-only by default: a live view of the remote\npane with zero effect on the remote.\n\n**Drive** — type into a mirror pane to take control (your keystroke is\ndelivered). Both sides now share the session, tmux-style. Control auto-releases\nafter 60s idle; `ctrl+\\` releases immediately. The mouse wheel scrolls remote\nscrollback while driving.\n\n**Close / restore** — by default, closing a mirror (`prefix+x`) also closes the\npane/workspace on the remote (`close_remote_on_local_close`; set it false to\nonly stop mirroring and leave the remote — and its agent — running). When the\nremote is left running, the **restore** action (`herdr-mirror restore`) brings\nback mirrors you closed.\n\n**Pause** — the **pause** action halts syncing; mirrors stay frozen in place\nand resume with **start**. `teardown` closes all mirrors and clears state.\n\n**Create on the remote** — four actions create objects on the remote host,\ninheriting the target host and cwd from the mirror you invoke them from (the\nsame rule as native `prefix+shift+n`, across ssh): `remote-new-workspace`,\n`remote-new-tab`, `remote-split-right`, `remote-split-down`. The new object\nmirrors back within seconds.\n\n**Continuous streaming** — every mirror pane streams its remote pane live for\nits whole lifetime, each over its own direct ssh connection, so panes are never\nblank and a busy pane can't contend with or drop another's stream. Sidebar\nagent status is daemon-driven, not stream-derived, so every agent's state stays\nlive regardless of what any stream is doing.\n\n### Keybinds\n\nActions have no default keys; bind them in `~/.config/herdr/config.toml`, then\n`herdr server reload-config`:\n\n```toml\n[[keys.command]]\nkey = \"prefix+shift+m\"\ntype = \"plugin_action\"\ncommand = \"mirror.start\"       # start / resume the daemon\n\n[[keys.command]]\nkey = \"prefix+shift+s\"\ntype = \"plugin_action\"\ncommand = \"mirror.pause\"       # freeze syncing; start again to resume\n\n[[keys.command]]\nkey = \"prefix+shift+b\"         # \"bring back\" (shift+r is native reload_config)\ntype = \"plugin_action\"\ncommand = \"mirror.restore\"     # un-close mirrors you closed locally\n\n# Create objects on the REMOTE host — run these from inside a mirror pane, which\n# supplies the target host and cwd. Each is herdr's native local key + alt\n# (Option): same muscle memory, but it acts on the remote over ssh.\n[[keys.command]]\nkey = \"prefix+alt+n\"           # native new_workspace = prefix+shift+n\ntype = \"plugin_action\"\ncommand = \"mirror.remote-new-workspace\"\n\n[[keys.command]]\nkey = \"prefix+alt+c\"           # native new_tab = prefix+c\ntype = \"plugin_action\"\ncommand = \"mirror.remote-new-tab\"\n\n[[keys.command]]\nkey = \"prefix+alt+v\"           # native split_vertical = prefix+v\ntype = \"plugin_action\"\ncommand = \"mirror.remote-split-right\"\n\n[[keys.command]]\nkey = \"prefix+alt+minus\"       # native split_horizontal = prefix+minus\ntype = \"plugin_action\"\ncommand = \"mirror.remote-split-down\"\n```\n\nThe `remote-*` actions need a focused mirror pane (except `remote-new-workspace`,\nwhich falls back to `default_host` when run outside one). The remaining\nactions — `mirror.status`, `mirror.once`, `mirror.teardown`, `mirror.ensure` —\nare lifecycle/diagnostic and are usually run from the CLI rather than bound.\n\n## Configuration\n\n`hosts.toml`:\n\n```toml\n# autostart = true       # focusing a workspace starts the daemon (default).\n                         # A manual pause is sticky until you start again;\n                         # a crash still auto-recovers on next focus.\n# poll_seconds = 60      # reconcile poll interval (events drive most syncs)\n# default_host = \"work\"  # host that \"new remote workspace\" targets when\n                         # invoked outside any mirror (default: first host)\n# close_remote_on_local_close = true\n                         # default. Closing a mirror pane/workspace locally\n                         # (e.g. prefix+x) also closes it on the remote. Set\n                         # false to only stop mirroring on a local close,\n                         # leaving the remote pane and its agent running.\n\n[hosts.work]\ntarget = \"work\"\n# prefix = \"work\"                    # sidebar prefix (default: the host key)\n# remote_bin = \"~/.local/bin/herdr\"  # remote path if it's not on ssh's PATH\n# enabled = true                     # false stops syncing this host without\n                                     # deleting its config; mirrors stay put\n\n[hosts.vps]                          # add more hosts freely; each is independent\ntarget = \"ssh://niko@203.0.113.7:2222\"\n```\n\n## Limitations\n\n- **Version-locked to preview** until the `terminal session` streams reach\n  stable; keep both machines on the same build.\n- **Latency** above raw ssh: keystroke echo is a rendered frame round-trip, so\n  there's a small constant delay. For latency-critical work, plain `ssh \u003chost\u003e`\n  is always one command away.\n- **Layout geometry is copied, not linked**: pane content and typing are\n  always live — only sizes are a snapshot. Remote pane adds/removes reconcile,\n  but a split-ratio change at the remote doesn't resize an existing mirror.\n- **No git status on mirror rows** — herdr derives the sidebar git branch and\n  ahead/behind from the local workspace cwd, and there's no API to feed it a\n  remote repo's state, so mirror workspaces show no git chip. The remote's real\n  branch and status stay visible in the streamed pane's prompt.\n- **No custom sidebar UI** (plugin API limitation): mirrors are grouped only by\n  the `\u003chost\u003e: ` naming convention.\n- **Remote must be reachable and running herdr**; the daemon surfaces a\n  readable status if a host is down or on too old a version.\n\n## License\n\nMIT — see [LICENSE](./LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnikok6%2Fherdr-mirror","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnikok6%2Fherdr-mirror","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnikok6%2Fherdr-mirror/lists"}