Seamless screenshots from your Mac to your dev servers — for people who run CLI agents (pi, codex, claude code, opencode…) over SSH.
Before: screenshot → save → scp → paste path into the agent. After: just paste the image, or say "look at my screen".
brew install --cask Erencv/tap/shotpipeOr grab a build from releases: Shotpipe.app (macOS, universal) · shotpipe-linux-x86_64.tar.gz (for servers).
One Rust binary, two roles:
- Mac (default /
tray) — a menu bar app that:- watches your screenshots folder and mirrors it to every enabled server,
keeping a
latest.pngsymlink pointing at the newest image, - serves your clipboard over per-server
ssh -Rtunnels, - gives you a global pause switch, per-server toggles, manual sync, start-at-login, all in the menu bar.
- watches your screenshots folder and mirrors it to every enabled server,
keeping a
- Server (
shotpipe in/shotpipe latest) — pulls the current clipboard image or prints the newest synced screenshot path.
┌────────────── macOS ──────────────┐ ┌──────────── server ────────────┐
│ ~/Screenshots ───rsync──▶──────────┼──ssh───▶ │ ~/shots/ │
│ (FSEvents watcher) │ │ └─ latest.png → newest │
│ │ │ │
│ clipboard ──▶ 127.0.0.1:8777 ─ssh -R───────▶ │ 127.0.0.1:8777 │
│ (NSPasteboard → PNG) │ │ └─ `shotpipe in` │
│ │ │ │
│ paste-image temp files │ │ `shotpipe resolve <pasted>` │
│ (/var/folders/...T/clipboard-*) ─▶│────────▶ │ → ~/shots/<same basename> │
└───────────────────────────────────┘ └─────────────────────────────────┘
Three capture flows, all hands-off:
| What you do on the Mac | What lands on the server |
|---|---|
Cmd+V an image into the agent's terminal |
the pasted path just works (shotpipe resolve) |
Cmd+Shift+5 → Cmd+C (clipboard only) |
~/shots/clip-*.png + latest.png |
Cmd+Shift+4 / any file in the watched folder |
~/shots/<name> + latest.png |
Already installed via brew or a release download? Just launch Shotpipe and do the one-time screenshot-folder setup:
mkdir -p ~/Screenshots
defaults write com.apple.screencapture location ~/Screenshots && killall SystemUIServerA menu bar icon appears on first launch; the default config is created at
~/.config/shotpipe/config.toml with a dev server entry to edit.
From source instead:
cargo build --release
./target/release/shotpipe # run the binary directly
scripts/make-app.sh # or build a Shotpipe.app bundleUse Start at login in the menu to install a LaunchAgent automatically.
Build the same crate on the server (it skips all macOS-only dependencies):
rsync -a --exclude target --exclude .git ./ server:shotpipe-src/
ssh server 'cd shotpipe-src && cargo build --release'
ssh server 'cp <target-dir>/release/shotpipe ~/.local/bin/' # CARGO_TARGET_DIR may relocate itNothing to configure on the server: the tunnel is established by the Mac.
Add to AGENTS.md / CLAUDE.md on the server (and a pi skill if you like —
skill template):
- If I paste a file path that doesn't exist on this machine (usually a Mac path like
/var/folders/.../T/clipboard-*.png), runshotpipe resolve <path>and use the path it prints.shotpipe in— saves the image currently on my Mac's clipboard to /tmp and prints the path.~/shots/latest.png— newest synced screenshot (older ones next to it,ls -t).When I mention a screenshot or say "look at this", fetch the image with the above, then read the resulting path as an image.
~/.config/shotpipe/config.toml — hot-reloaded on save; the menu bar toggles
rewrite it, so the file is always the source of truth. Override the directory
with SHOTPIPE_CONFIG_DIR (used by the tests).
[general]
screenshot_dir = "~/Screenshots" # watched folder (follows config changes live)
remote_dir = "shots" # destination, relative to $HOME on the server
clipboard_port = 8777 # local server + remote forwarded port
token = "" # optional shared secret for the clipboard server
mirror_clipboard = true # auto-save copied images into the synced folder
paste_capture = true # capture terminal paste-images (/var/folders/...)
downscale = { enabled = true, max_width = 1600 } # retina → agent-sized PNGs
[[servers]]
host = "dev" # anything ssh accepts: alias, user@host, jumps
name = "dev" # optional menu label
enabled = true # toggled by the menu bar checkbox
sync = true # mirror the screenshots folder
clipboard = true # maintain an ssh -R clipboard tunnel
remote_dir = "shots" # optional per-server override
[[servers]]
host = "user@other.example.com"
enabled = falseRequirements: key-based SSH auth to each server (the app never prompts), and
rsync on both ends. mosh does not forward ports — if you use mosh, keep a
separate ssh -N -R 8777:127.0.0.1:8777 <host> connection.
[ ] screenshot brackets; filled dot = running, hollow ring = paused
├─ ⏸ Pause all # global on/off (kills tunnels, pauses sync)
├─ ✓ dev · tunnel ✓ · synced 14:32
├─ ✗ box · off
├─ ⟳ Sync now
├─ Start at login
├─ Open config…
└─ Quit shotpipe
- The clipboard server binds
127.0.0.1on the Mac and reaches the server only through your SSH connection. - On multi-user servers the tunnel lands on
localhost:8777— other local users could read your clipboard there. Settokenin the config (andSHOTPIPE_TOKEN/--tokenon the server side) to require auth. - Images are downscaled to
max_widthbefore leaving the Mac (configurable).
cargo test # unit tests
cargo build # debug build (Mac)
SHOTPIPE_CONFIG_DIR=/tmp/x ./target/debug/shotpipe daemon # headless engineVerified end-to-end against a real server: initial sync, watcher-triggered
sync, latest.png retargeting, tunnel health, token rejection (401),
shotpipe in pulling the live Mac clipboard through the tunnel, clipboard
mirroring, and shotpipe resolve mapping a pasted /var/folders/... path
to the synced copy.
src/main.rs dispatch + usage (cross-platform)
src/config.rs TOML config, hot-reload-friendly (cross-platform)
src/client.rs `in` / `latest` / `resolve` server commands (cross-platform)
src/clipboard.rs NSPasteboard → PNG (+TIFF decode, downscale, cache) [macOS]
src/daemon.rs engine: watchers, rsync fan-out, tunnels, HTTP, reload [macOS]
src/tray.rs menu bar icon + menu, login item [macOS]
scripts/make-icons.py regenerate assets/Shotpipe.icns (pure stdlib)
scripts/make-app.sh build Shotpipe.app with icon + ad-hoc signature