DEV Community

Cover image for [iTerm2] Send Clipboard Images and Files to and from SSH Hosts with a Single Keystroke
upa_rupa
upa_rupa

Posted on

[iTerm2] Send Clipboard Images and Files to and from SSH Hosts with a Single Keystroke

The Problem

I run Claude Code and Codex on remote servers over SSH, and I constantly want to say "look at this screenshot" or "check this local file."

Locally, that's just ⌘V or Ctrl+V. Over SSH, it turns into a chore:

  1. Take a screenshot and save it to a local file first (or dig up the file in Finder)
  2. Send it to the server with scp
  3. Type the remote path by hand (and inevitably make typos)

The reverse is just as bad. When an agent replies "I created the report: /home/dev/tmp/report.pdf", I have to retype that path into scp to bring the file back.

I'm usually connected to several remote machines at once, giving instructions to a different agent on each, so I did this all day long and it was driving me up the wall.

Tools Exist for Images, but Not Quite What I Wanted

If all you need is to paste images to an SSH host, there are plenty of good tools: imgssh, clipssh, cc-clip (Japanese), clipaste, claude-ssh-image-skill, and more.

But every one of them only sends clipboard images. I also wanted to send PDFs and folders, and to download logs. Finding a tool that roughly fits, installing it, configuring it, and living with whatever it lacks takes effort. Since I knew exactly what I wanted, I had AI build it instead.

What I Ended Up With

⌘⇧V: From the Mac to the SSH Host

  1. Take a screenshot to the clipboard (⌘⇧⌃4), or select files or folders in Finder and press ⌘C (multiple items are fine)
  2. Press ⌘⇧V in a tab connected over SSH

The images, files, or folders land in ~/tmp/ on the remote host, and their full paths are typed into the terminal. They go wherever the cursor is, whether that's a shell or Claude Code's input box, so you can drop them into a command or add a sentence around them and send.

The path of the sent image typed into Claude Code's input box

# Transfer and cd in one go
$ cd "/home/dev/tmp/spec_docs"

# Move it wherever you like
$ mv "/home/dev/tmp/spec_docs" spec_docs
Enter fullscreen mode Exit fullscreen mode

⌘⇧D: From the SSH Host to the Mac

Select a path shown on screen with the mouse and press ⌘⇧D, and the file lands in ~/Downloads on the Mac. Folders work too. No need to copy anything.

Selecting a path on screen and pressing ⌘⇧D shows a dialog saying it was saved to ~/Downloads

  • Even if the path wraps onto two lines on screen, just select it as is
  • If the selection is a URL, it opens in the Mac's browser

Folders and Large Files

  • The destination or source is the SSH host the current tab is connected to. Switch tabs to work with a different machine
  • Transferring folders or files larger than 10 MB triggers a confirmation dialog and displays a progress window

The confirmation dialog before sending a folder, and the progress window while sending

Making It My Own as I Go

With an off-the-shelf tool, you either adapt to the author's way of thinking or go hunting through settings. When the tool was built just for you, you simply tell the AI on the spot whenever something bugs you. Here are a couple of things I actually said:

  • "Don't we need to handle macOS dakuten and handakuten?": File names created in Finder can store a character like "パ" decomposed into "ハ" + "゜" (NFD). On Linux, that becomes a different name from the visually identical composed "パ", so I had it convert names to the composed form (NFC) when sending.

  • "Make it work even when a path wraps and a space sneaks in": When you select a path that the screen has wrapped, it now strips the inserted space and treats it as the correct path.

A general-purpose tool would probably let you choose the download folder in a config file or settings screen, for convenience. But when an AI agent is doing the building, even if it's hardcoded, saying "save it here" is the configuration. There's no need to build a settings feature at all.

How to Use It

The scripts are on GitHub:

archelon-inc/iterm2-ssh-transfer

git clone https://github.com/archelon-inc/iterm2-ssh-transfer.git ~/iterm2-ssh-transfer
Enter fullscreen mode Exit fullscreen mode

1. Assign the Keys

In iTerm2, go to Settings → Keys → Key Bindings, press +, and set up each script like this:

  • Keyboard Shortcut: ⌘⇧V and ⌘⇧D (or whatever you prefer)
  • Action: Run Coprocess
  • The field below it (Enter command to run): /Users/<your user name>/iterm2-ssh-transfer/upload.sh and {same folder}/download.sh

Don't add any arguments. Set the dropdown at the bottom of the editor to apply to the current session only. With "Apply to all sessions", a single key press runs the script in every open tab at once.

2. Let SSH Connect Without Asking for a Password

The scripts open a separate ssh connection in the background, so they can't answer a password prompt. You need ssh to connect without asking for a password or passphrase. A key without a passphrase would work, but that's a bit much, so here's what I do instead.

The first ssh connection you open to a host becomes the "master", and the scripts' ssh connections piggyback on it. Just add this to ~/.ssh/config:

Host *
  ControlMaster auto
  ControlPath ~/.ssh/cm-%C
Enter fullscreen mode Exit fullscreen mode
  • When you run ssh <host> in a tab, that connection becomes the master. That's the only time you're asked for the passphrase
  • When the scripts connect in the background, they ride on the master connection, so no further authentication is needed
  • Since there's no ControlPersist, closing the master connection ends the piggybacking. Connecting without authentication only works while the tab is connected
  • As a bonus, the scripts reuse the existing connection instead of opening a new one, so there's no connection wait, even through a jump host

One quirk: if you connect to the same host from a second tab, it also piggybacks on the first tab. If you then exit in the first tab, it won't finish and keeps waiting until you close the second tab.

3. Raise the Selection Size Limit

Under Settings → Advanced, increase "Maximum number of bytes of selection to provide to Python API". Even if you leave it at the default of 100, ⌘⇧D will tell you with a dialog when you select a long path and open this setting for you.

Caveat: How Far It Reaches

The destination or source is the host you connected to directly from the Mac. It also works through a jump host (ProxyJump) and in tabs using iTerm2's tmux integration (tmux -CC). However, it can't reach a host you ssh into again after logging in to the remote. That ssh runs on the remote host, and the Mac can't see it.

Tested on macOS 27.0.1.

Wrapping Up

Just copy and press ⌘⇧V, or select and press ⌘⇧D. It's surprisingly convenient. Getting a screenshot in front of Claude Code takes a few seconds, and opening files the agent created on my Mac is a breeze.

While writing this, I realized that articles like this used to be mostly about how the thing is implemented. Now that AI does the implementing, that's not very meaningful. So this time I wrote it from the angle of "here's something handy to have AI build for you."

If it looks useful, fork the repository and have AI adapt it to your own environment and taste.


This article was originally published in Japanese at archelon-inc.hatenablog.com.

Top comments (0)