macOS menu-bar app that receives shared screenshots via a native Share Extension and uploads them to an SSH server, copying the remote path to the clipboard.
  • Rust 85.3%
  • Swift 9.1%
  • Shell 5.6%
Find a file
Till Wegmueller 0d05c13c58 Add 'Reload from ~/.ssh/config' to the Server submenu
The host list was built once at startup; newly added ssh-config hosts
didn't appear without restarting. The Server submenu now has a reload
action that re-reads ~/.ssh/config and rebuilds the host list live
(removing old items, re-checking the configured target).

(Uploads already re-read the config per send; only the picker list was stale.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 15:15:00 +02:00
app Initial commit: share-receiver macOS tray app + Share Extension 2026-06-02 13:47:41 +02:00
examples Rework: pure-Rust SSH, pasteboard/Darwin IPC, tray host picker 2026-06-02 15:04:22 +02:00
extension Rework: pure-Rust SSH, pasteboard/Darwin IPC, tray host picker 2026-06-02 15:04:22 +02:00
scripts Initial commit: share-receiver macOS tray app + Share Extension 2026-06-02 13:47:41 +02:00
src Add 'Reload from ~/.ssh/config' to the Server submenu 2026-06-02 15:15:00 +02:00
.gitignore Initial commit: share-receiver macOS tray app + Share Extension 2026-06-02 13:47:41 +02:00
Cargo.lock Add launch-at-login toggle via SMAppService 2026-06-02 15:08:47 +02:00
Cargo.toml Add launch-at-login toggle via SMAppService 2026-06-02 15:08:47 +02:00
README.md Add launch-at-login toggle via SMAppService 2026-06-02 15:08:47 +02:00

share-receiver

A tiny macOS menu-bar app that receives shared screenshots and uploads them to an SSH server of your choice. After upload it copies the remote path to your clipboard, so you can paste it straight into a remote Claude Code session.

[ Screenshot ] --Share menu--> [ Share Extension ] --pasteboard + Darwin notif--> [ tray app ] --ssh--> [ server ]
                                                                                        |
                                                                                        +--> remote path → clipboard
  • Lives in the menu bar (no Dock icon, no window).
  • Share menu integration via a native macOS Share Extension.
  • Pick the server from the tray — the "Server" submenu lists the host aliases in your ~/.ssh/config.
  • Pure-Rust SSH (via russh) — no ssh subprocess. Resolves HostName/User/Port/IdentityAgent/IdentityFile from ~/.ssh/config (including Includes) and authenticates through your SSH agent — including a non-default IdentityAgent socket like 1Password.
  • Uploads to the remote ~/Downloads, falling back to ${XDG_DATA_HOME:-~/.local/share}/share-receiver (created if needed).

How the pieces talk

A macOS Share Extension always runs as its own sandboxed process, so it needs some IPC to hand the image to the tray app. We use a dedicated named NSPasteboard + a Darwin notification:

  1. The extension writes the PNG (and original filename) onto a private named pasteboard — never the general clipboard — and posts a Darwin notification.
  2. The tray app observes that notification (via notify(3), no run loop) and reads the pasteboard on the main thread.
  3. It uploads over SSH on a background thread, copies the remote path, and shows a notification.

No localhost server, no open port, and the extension needs only the app-sandbox entitlement (no network). Both processes reach the same named pasteboard through the system pasteboard server, which works with a free signing cert (no App Group required).

Build

# Rust core only (for development / running tests):
cargo build
cargo test -- --test-threads=1

# Smoke-test the SSH upload against a real host (uses your ~/.ssh/config + agent):
cargo run --example ssh_smoke -- <ssh-config-alias>

# Full signed app bundle:
./scripts/build_app.sh "Apple Development: you@example.com (TEAMID)"

The bundle is written to dist/ShareReceiver.app.

Signing (required for the Share menu)

macOS will run the menu-bar app when ad-hoc signed, but it generally refuses to load a sandboxed Share Extension unless it's signed with a real identity. A free Apple ID is enough:

  1. Open Xcode → Settings → Accounts, add your Apple ID (installs an "Apple Development" certificate).
  2. If security find-identity -v -p codesigning shows 0 valid identities even though the cert exists, you're likely missing the Apple WWDR G3 intermediate. Install it once:
    curl -O https://www.apple.com/certificateauthority/AppleWWDRCAG3.cer
    security import AppleWWDRCAG3.cer -k ~/Library/Keychains/login.keychain-db
    
  3. Build with that identity:
    ./scripts/build_app.sh "Apple Development: you@example.com (TEAMID)"
    

Install & enable

mv dist/ShareReceiver.app /Applications/
open /Applications/ShareReceiver.app
  1. Enable the extension: System Settings → General → Login Items & ExtensionsSharing → turn on “Share to Receiver”.
  2. Pick your server: menu-bar icon → Server ▸ → choose a ~/.ssh/config host. (Or use Edit Settings… to set ssh_host directly.)
  3. Test it: menu-bar icon → Test Connection.

Use

Take a screenshot, click its thumbnail (or use the Share button anywhere an image can be shared), and pick Share to Receiver. You'll get a notification and the remote path will be on your clipboard, e.g.:

/home/you/Downloads/1733140800-Screenshot_2026-06-02_at_11.45.00.png

Paste that into your remote Claude Code session.

Configuration

~/.config/share-receiver/config.toml:

# A host alias from ~/.ssh/config. We resolve HostName/User/Port/IdentityAgent
# /IdentityFile from it and connect with a built-in SSH client.
ssh_host = "myserver"

Picking a host from the tray's Server submenu writes this for you. Config is re-read on every upload, so edits take effect without a restart.

Layout

Path What
src/main.rs Tray UI, host picker, wiring
src/ipc.rs Named-pasteboard read/write + Darwin-notification observer
src/upload.rs Pure-Rust SSH upload (russh) + clipboard + notify
src/sshconfig.rs ~/.ssh/config reader (Include, Host *, IdentityAgent)
src/config.rs TOML config
src/shared.rs Constants shared with the extension
extension/ Swift Share Extension + Info.plist + entitlements
app/Info.plist Containing-app metadata (LSUIElement)
scripts/build_app.sh Assemble + sign the .app
examples/ssh_smoke.rs Manual SSH upload smoke test

Limitations / notes

  • The tray app must be running when you share — Darwin notifications aren't queued for a process that isn't observing.
  • Same machine only for the share step (the pasteboard is local).
  • Host-key verification is trust-on-first-use (records unknown hosts to ~/.ssh/known_hosts, but refuses a changed key).
  • SSH-agent auth only (keys in your agent, incl. 1Password). No password auth.
  • The remote filename is sanitized to [A-Za-z0-9._-] and timestamp-prefixed.
  • Launch at login: toggle Open at Login in the tray menu (uses SMAppService; also visible under System Settings → Login Items). This only works from the installed, signed .app — not via cargo run.