A Herdr plugin that transfers files in both directions between a
herdr --remote host and the connected Mac.
Select visible file paths with hint characters, or transfer files from
file:// links displayed by Codex, Claude, and other tools. It supports regular
files of any extension, including PDFs, PPTX files, images, and archives;
directories are archived and extracted automatically. In
the other direction, choose one or more Mac files and save them to the focused
remote pane's current directory.
Herdr plugins run on the remote host, so a small transfer service runs on the
connected Mac. Transfers use only an SSH RemoteForward; no public port is
opened. The Mac service and remote-to-Mac download path are implemented in
Rust. The Mac-to-remote receiver uses only the Python standard library.
remote Herdr plugin
-> /tmp/herdr-remote-download-<remote-user>.sock
-> SSH RemoteForward
-> 127.0.0.1:18340 on the connected Mac
<-> ~/Downloads or macOS file picker
<-> focused remote pane cwd
Transfers are authenticated with a random 64-character token and verified with
SHA-256 after receipt. The default size limit is 512 MiB. Existing files are
never overwritten; a duplicate is saved as name (1).ext, for example.
- Herdr 0.7.0 or later
- Connected machine: macOS, Git, and Rust/Cargo 1.88 or later
- Remote host: Linux or macOS, Git, Rust/Cargo 1.88 or later, and Python 3.9 or later
- A remote host defined in your SSH config
Make sure the existing SSH target works with ssh your-server, then run on
the Mac:
git clone https://github.com/kosuketut/herdr-remotedownloder.git
cd herdr-remotedownloder
./setup.sh your-serverThis one command builds and starts the Mac service, installs the remote plugin,
copies the authentication token, configures both key bindings, creates the
forwarding-only SSH host, and installs the hr function. Reload the shell and
connect:
source ~/.zshrc
hr your-serverRun setup.sh again with another SSH host to add it. Repeating it for the same
host does not duplicate the managed settings. Before the first change, the
script saves ~/.ssh/config and ~/.zshrc with a
.before-herdr-remote-download.bak suffix.
./setup.sh mercury installs a LaunchAgent for the transfer tunnel. It starts
at Mac login and reconnects after SSH disconnects. Only stale, non-listening
sockets are removed. SSH authentication must work non-interactively (load your
key into the SSH agent if needed).
herdr machine add mercury --label "mercury"Skip registration if already registered. Keep the machine target as mercury.
The updated hr mercury can coexist with saved-machine connections. To add only
automatic recovery to an existing setup, stop your old manual tunnel and run:
python3 herdr_transfer_tunnel.py install mercuryOld hr functions delete the socket: rerun setup.sh and source ~/.zshrc
before using them alongside the LaunchAgent.
Logs: ~/Library/Logs/com.kosukeyano.herdr-transfer-tunnel.mercury.log.
Check connectivity with:
ssh mercury 'curl -fsS --max-time 5 --unix-socket /tmp/herdr-remote-download-$(id -un).sock http://localhost/health'Dead SSH connections are normally detected in about 45 seconds; launchd retries at intervals of at least 30 seconds. If the socket on the remote host is removed while the tunnel is running, the tunnel reconnects within about 10 seconds.
Use the following steps when the automatic setup is not available.
The examples below use your-server as the existing SSH host name.
Clone the repository on the Mac and install the launchd service.
git clone https://github.com/kosuketut/herdr-remotedownloder.git
cd herdr-remotedownloder
cargo build --release --locked
./target/release/herdr-remote-download install-service
./target/release/herdr-remote-download service-status
curl -fsS http://127.0.0.1:18340/healthThe service binds only to 127.0.0.1 and writes logs to
~/Library/Logs/herdr-remote-download.log. Its authentication token is created
at ~/.config/herdr-remote-download/token.
Add a dedicated Herdr host to ~/.ssh/config on the Mac. Use a different name
from the normal SSH host so the forwarding configuration remains isolated.
Place this block before Host *.
Host your-server-herdr
HostName server.example.com
User your-name
IdentityFile ~/.ssh/id_ed25519
IdentitiesOnly yes
RemoteForward /tmp/herdr-remote-download-your-name.sock 127.0.0.1:18340
StreamLocalBindUnlink yes
ExitOnForwardFailure yes
ServerAliveInterval 15
ServerAliveCountMax 3
ControlMaster no
ControlPath noneReplace your-name in the socket path with the user name returned by id -un
on the remote host. The Unix socket prevents old and new SSH sessions from
sharing the same TCP forwarding port. ExitOnForwardFailure yes stops Herdr at
connection time if the socket cannot be created.
Run:
herdr plugin install kosuketut/herdr-remotedownloderAfter you confirm the installation, the installer builds both Rust binaries according to the plugin manifest. The upload client needs no additional build.
Run this on the Mac:
ssh your-server \
'config_dir=$(herdr plugin config-dir kosukeyano.remote-download) &&
mkdir -p "$config_dir" &&
umask 077 &&
cat > "$config_dir/token"' \
< ~/.config/herdr-remote-download/tokenDo not add the token to a repository or share it with anyone else.
Add the following block to ~/.config/herdr/config.toml on the remote host:
[[keys.command]]
key = "prefix+d"
type = "plugin_action"
command = "kosukeyano.remote-download.pick"
description = "pick a remote file to download"
[[keys.command]]
key = "prefix+u"
type = "plugin_action"
command = "kosukeyano.remote-download.upload"
description = "upload files from the connected Mac"Reload the configuration:
herdr server reload-configTo use server-side key bindings in a remote session, remove any stale socket before connecting. Even if an old SSH process remains, its unlinked socket is unreachable and the new connection creates the path again.
ssh your-server 'rm -f /tmp/herdr-remote-download-your-name.sock'
herdr --remote your-server-herdr --remote-keybindings serverTo keep using hr your-server for these two commands, add the following
function to ~/.zshrc on the Mac. Replace your-server and your-name with the
same values used above.
unalias hr 2>/dev/null
hr() {
if [[ "$1" == "your-server" ]]; then
shift
command ssh your-server \
'rm -f /tmp/herdr-remote-download-your-name.sock' || return
command herdr --remote-keybindings server --remote your-server-herdr "$@"
else
command herdr --remote-keybindings server --remote "$@"
fi
}
compdef _herdr hrunalias ensures that an old hr alias in the current shell is replaced by
the function. Reload the configuration once and verify the result:
source ~/.zshrc
whence -w hr
# hr: functionPress prefix+d to add hint characters to existing file paths on the visible
screen. Type a hint to transfer that file to the Mac. Press Esc to close the
picker.
To transfer several files at once, press Tab, type the hints of each file (typing a hint again deselects it), then press Tab or Enter. Files are sent one after another. During each transfer the picker shows the file number, percentage, and sent bytes.
The picker recognizes absolute paths, paths relative to the pane's current
working directory, and paths ending in :line or :line:column. It works with
any existing file shown on screen, not only files printed by Codex or Claude.
You can also invoke the action directly:
herdr plugin action invoke kosukeyano.remote-download.pickBy default the Mac selects each downloaded file in Finder. To open files with
their default app instead, or to do nothing, reinstall the transfer service
with --after-save:
./target/release/herdr-remote-download install-service --after-save open # or reveal, nonesetup.sh also reads HERDR_DOWNLOAD_AFTER_SAVE. Folders and files that can
run code (such as .command, .sh, .app, or .pkg) are only revealed, even
with open.
Control-click a file:// link displayed by Codex, Claude, or another tool in
Herdr to transfer its target. On macOS, Control is the modifier Herdr captures
for link actions.
Select a file path and invoke the
kosukeyano.remote-download.download action directly.
Focus the destination pane and press prefix+u. Choose one or more regular
files in the Mac dialog using Command or Shift. The plugin saves them in that
pane's current directory. The overlay shows the current file number, filename,
percentage, and received bytes during transfer, then the saved paths. Press
Enter to close it.
herdr plugin action invoke kosukeyano.remote-download.upload- Directories are archived as
.tar.gzautomatically and extracted into a new folder in the download directory. Symlinks and special files inside the archive are skipped. - Files larger than 512 MiB each are rejected by default. Set
HERDR_DOWNLOAD_MAX_MBon both sides (for example2048, or0for unlimited), or pass--max-mb. - Automatic transfer-service startup uses launchd on macOS and a systemd user service on Linux.
- The SSH
RemoteForwardand authentication token must be configured for each remote host.
If the connection reports remote port forwarding failed for listen path, run
the rm -f command shown above and reconnect.
If the SSH tunnel is lost during a transfer, the picker displays the failure after you select the file: within about a second when the tunnel is down, or within about 20 seconds when it does not answer. On a lossy network the first answer through an idle tunnel can be slow, so the check is retried once automatically. Press Esc or Enter to close it. With the LaunchAgent tunnel, retry after about a minute; each connection attempt is timestamped in its log. With the manual setup, reconnect Herdr.
cargo test --locked
cargo clippy --locked --all-targets -- -D warnings
cargo build --release --locked
python3 -m unittest discover -s tests -v
python3 -m py_compile herdr_remote_upload.py
tests/test_setup.shThe picker UI and hint assignment use
herdr-tiny-fingers.
See LICENSES for third-party license information.
This project is released under the MIT License.