From bfe7456d4cd19b0fb1d63b74bb3b0c5a112aca81 Mon Sep 17 00:00:00 2001 From: Steve Molitor Date: Sun, 19 Apr 2026 13:51:14 -0500 Subject: [PATCH 1/4] Add image paste support via yank-media MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On Emacs 29 and later, register a `yank-media-handler' for `image/.*' in every Claude buffer. When the user pastes an image from the system clipboard the handler writes the bytes to a temp file and injects `@ ' into the terminal via `claude-code--term-send-string' — Claude's CLI reads `@path' references natively and attaches the image to the next message. Temp files are tracked per-buffer and cleaned up on kill-buffer when `claude-code-image-paste-cleanup-on-kill' is non-nil. Works transparently across all three terminal backends (eat, vterm, ghostel) because the abstraction already dispatches send-string per backend. Two new defcustoms control the feature and cleanup behavior. Documents the feature in README and adds a Features bullet. --- README.md | 7 ++++ claude-code.el | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+) diff --git a/README.md b/README.md index a511678..f2929dd 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ An Emacs interface for [Claude Code CLI](https://github.com/anthropics/claude-co - **Seamless Emacs Integration** - Start, manage, and interact with Claude without leaving Emacs - **Stay in Your Buffer** - Send code, regions, or commands to Claude while keeping your focus - **Fix Errors Instantly** - Point at a flycheck/flymake error and ask Claude to fix it +- **Paste Images** - `M-x yank-media` sends a clipboard image to Claude as an `@path` reference (Emacs 29+) - **Multiple Instances** - Run separate Claude sessions for different projects or tasks - **Quick Responses** - Answer Claude with a keystroke (//1/2/3) without switching buffers - **Smart Context** - Optionally include file paths and line numbers when sending commands to Claude @@ -172,6 +173,12 @@ If you put your cursor over a flymake or flycheck error, you can ask Claude to f To show and hide the Claude buffer use `claude-code-toggle` (`C-c c t`). To jump to the Claude buffer use `claude-code-switch-to-buffer` (`C-c c b`). This will open the buffer if hidden. +### Pasting Images + +On Emacs 29 and later, you can paste an image from the system clipboard directly into a Claude buffer with `M-x yank-media`. claude-code.el writes the image to a temp file and injects an `@/path/to/image` reference at the prompt; the Claude CLI reads `@path` references natively and attaches the image to your next message. The temp files are cleaned up when the Claude buffer is killed. + +Works with all terminal backends (eat, vterm, ghostel). Disable by setting `claude-code-enable-image-paste` to `nil`, or disable cleanup with `claude-code-image-paste-cleanup-on-kill`. + ### Managing Claude Windows The `claude-code-toggle` (`C-c c t`) will show and hide the Claude window. Use the `claude-code-switch-to-buffer` (`C-c c b`) command to switch to the Claude window even if it is hidden. diff --git a/claude-code.el b/claude-code.el index 7d0e0cf..4f74ffb 100644 --- a/claude-code.el +++ b/claude-code.el @@ -201,6 +201,30 @@ current buffer." :type 'boolean :group 'claude-code-window) +(defcustom claude-code-enable-image-paste t + "Whether to enable image paste via `yank-media' in Claude buffers. + +When non-nil, pasting an image from the system clipboard (or dragging +and dropping an image onto a Claude buffer) via `yank-media' writes the +image to a temp file and inserts an `@/path/to/image' reference at the +prompt. Claude's CLI reads `@path' references natively and will +attach the image to your next message. + +Requires Emacs 29 or later (for `yank-media-handler'). Has no effect +on older Emacs versions." + :type 'boolean + :group 'claude-code) + +(defcustom claude-code-image-paste-cleanup-on-kill t + "Whether to delete pasted image temp files when the Claude buffer is killed. + +When non-nil, any temp files created by `claude-code-enable-image-paste' +are removed when the Claude buffer is killed. When nil, the files +stay in the variable `temporary-file-directory' until the OS cleans +them up." + :type 'boolean + :group 'claude-code) + ;;;;; Eat terminal customizations ;; Eat-specific terminal faces (defface claude-code-eat-prompt-annotation-running-face @@ -1105,6 +1129,71 @@ BUFFER can be either a buffer object or a buffer name string." (buffer-name buffer)))) (and name (string-match-p "^\\*claude:" name)))) +;;;;; Image paste + +(defvar-local claude-code--pasted-image-files nil + "List of temp image files created by `yank-media' in this buffer. +Cleaned up on `kill-buffer' when `claude-code-image-paste-cleanup-on-kill' +is non-nil.") + +(defconst claude-code--image-mime-extensions + '(("image/png" . ".png") + ("image/jpeg" . ".jpg") + ("image/jpg" . ".jpg") + ("image/gif" . ".gif") + ("image/webp" . ".webp") + ("image/bmp" . ".bmp")) + "Alist mapping image MIME-type prefix to file extension.") + +(defun claude-code--image-extension-for-mimetype (mimetype) + "Return the file extension (including the dot) for MIMETYPE. +MIMETYPE may be a symbol or a string. Falls back to \".png\" if +the type is unrecognized." + (let* ((str (if (symbolp mimetype) (symbol-name mimetype) mimetype)) + (match (seq-find (lambda (pair) (string-prefix-p (car pair) str)) + claude-code--image-mime-extensions))) + (if match (cdr match) ".png"))) + +(defun claude-code--image-yank-media-handler (mimetype data) + "Handle an `image/*' paste in a Claude buffer. +MIMETYPE is the MIME type (string or symbol); DATA is the raw bytes. + +Writes DATA to a temp file named by MIMETYPE's extension, remembers it +for cleanup, then injects `@ ' into the terminal so Claude's CLI +receives it as a file reference at the prompt. + +Returns non-nil to signal the paste was handled." + (let* ((ext (claude-code--image-extension-for-mimetype mimetype)) + (path (make-temp-file "claude-image-" nil ext)) + (coding-system-for-write 'binary)) + (with-temp-file path (insert data)) + (push path claude-code--pasted-image-files) + (claude-code--term-send-string claude-code-terminal-backend + (concat "@" path " ")) + (message "Pasted image as %s" path) + t)) + +(defun claude-code--cleanup-pasted-images () + "Delete any temp image files created by `yank-media' in this buffer. +Called from `kill-buffer-hook' when +`claude-code-image-paste-cleanup-on-kill' is non-nil." + (when claude-code-image-paste-cleanup-on-kill + (dolist (file claude-code--pasted-image-files) + (when (file-exists-p file) + (ignore-errors (delete-file file)))) + (setq claude-code--pasted-image-files nil))) + +(defun claude-code--register-image-yank-media-handler () + "Register `yank-media-handler' for images in the current Claude buffer. +No-op on Emacs versions without `yank-media-handler' (pre-29)." + (when (and claude-code-enable-image-paste + (fboundp 'yank-media-handler)) + (yank-media-handler "image/.*" + #'claude-code--image-yank-media-handler) + (add-hook 'kill-buffer-hook + #'claude-code--cleanup-pasted-images + nil t))) + (defun claude-code--directory () "Get get the root Claude directory for the current buffer. @@ -1459,6 +1548,10 @@ With double prefix ARG (\\[universal-argument] \\[universal-argument]), prompt f ;; Add cleanup hook to remove directory mappings when buffer is killed (add-hook 'kill-buffer-hook #'claude-code--cleanup-directory-mapping nil t) + ;; Register yank-media handler so users can paste images from the + ;; clipboard; the handler writes to a temp file and inserts @path. + (claude-code--register-image-yank-media-handler) + ;; run start hooks (run-hooks 'claude-code-start-hook) From 3e6b0653cf8b68eb033ce34a5ae2cbecdefea640 Mon Sep 17 00:00:00 2001 From: Steve Molitor Date: Sun, 19 Apr 2026 13:55:59 -0500 Subject: [PATCH 2/4] Bind claude-code-yank-media to C-c c p and add transient entry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wrap yank-media in an interactive command that targets the Claude buffer via claude-code--with-buffer, so the user can invoke it from anywhere — command map prefix on p, or the Send group of the transient menu. README updated to mention the binding. --- README.md | 2 +- claude-code.el | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f2929dd..7f2c019 100644 --- a/README.md +++ b/README.md @@ -175,7 +175,7 @@ To show and hide the Claude buffer use `claude-code-toggle` (`C-c c t`). To jum ### Pasting Images -On Emacs 29 and later, you can paste an image from the system clipboard directly into a Claude buffer with `M-x yank-media`. claude-code.el writes the image to a temp file and injects an `@/path/to/image` reference at the prompt; the Claude CLI reads `@path` references natively and attaches the image to your next message. The temp files are cleaned up when the Claude buffer is killed. +On Emacs 29 and later, you can paste an image from the system clipboard directly into a Claude buffer with `claude-code-yank-media` (`C-c c p`), or with `M-x yank-media` from inside the Claude buffer. claude-code.el writes the image to a temp file and injects an `@/path/to/image` reference at the prompt; the Claude CLI reads `@path` references natively and attaches the image to your next message. The temp files are cleaned up when the Claude buffer is killed. Works with all terminal backends (eat, vterm, ghostel). Disable by setting `claude-code-enable-image-paste` to `nil`, or disable cleanup with `claude-code-image-paste-cleanup-on-kill`. diff --git a/claude-code.el b/claude-code.el index 4f74ffb..68ba940 100644 --- a/claude-code.el +++ b/claude-code.el @@ -427,6 +427,7 @@ this history by adding `claude-code-command-history' to (define-key map (kbd "3") 'claude-code-send-3) (define-key map (kbd "M") 'claude-code-cycle-mode) (define-key map (kbd "o") 'claude-code-send-buffer-file) + (define-key map (kbd "p") 'claude-code-yank-media) map) "Keymap for Claude commands.") @@ -457,6 +458,7 @@ this history by adding `claude-code-command-history' to ("x" "Send command with context" claude-code-send-command-with-context) ("r" "Send region or buffer" claude-code-send-region) ("o" "Send buffer file" claude-code-send-buffer-file) + ("p" "Paste image from clipboard" claude-code-yank-media) ("e" "Fix error at point" claude-code-fix-error-at-point) ("f" "Fork conversation" claude-code-fork) ("/" "Slash Commands" claude-code-slash-commands)] @@ -2066,6 +2068,22 @@ having to switch to the REPL buffer." (interactive) (claude-code--do-send-command "")) +;;;###autoload +(defun claude-code-yank-media () + "Paste an image from the clipboard into the current Claude buffer. + +Runs `yank-media' in the Claude buffer, which dispatches to the handler +installed by `claude-code--register-image-yank-media-handler': the image +is written to a temp file and an `@/path/to/image' reference is inserted +at the prompt. Claude's CLI reads `@path' references natively. + +Requires Emacs 29 or later." + (interactive) + (unless (fboundp 'yank-media) + (user-error "`yank-media' requires Emacs 29 or later")) + (claude-code--with-buffer + (call-interactively #'yank-media))) + ;;;###autoload (defun claude-code-send-1 () "Send \"1\" to the Claude Code REPL. From 4a1df2a39eac3dbc9d9455fe2aa8034160fbf58e Mon Sep 17 00:00:00 2001 From: Steve Molitor Date: Sun, 19 Apr 2026 14:07:44 -0500 Subject: [PATCH 3/4] Write pasted images to /tmp for shorter @-references The default temp dir on macOS is nested deep under /var/folders/... which makes the @ reference inserted at the prompt unreadable. New defcustom claude-code-image-paste-directory defaults to /tmp on systems that have it and falls back to temporary-file-directory on others. Users can override to e.g. ~/.claude/pastes/ for a project directory. --- claude-code.el | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/claude-code.el b/claude-code.el index 68ba940..e495f03 100644 --- a/claude-code.el +++ b/claude-code.el @@ -220,11 +220,21 @@ on older Emacs versions." When non-nil, any temp files created by `claude-code-enable-image-paste' are removed when the Claude buffer is killed. When nil, the files -stay in the variable `temporary-file-directory' until the OS cleans -them up." +stay in `claude-code-image-paste-directory' until the OS cleans them up." :type 'boolean :group 'claude-code) +(defcustom claude-code-image-paste-directory + (if (file-directory-p "/tmp") "/tmp" temporary-file-directory) + "Directory where pasted images are written. + +Defaults to \"/tmp\" on systems that have it (macOS, Linux, BSD) so +the `@/path/to/image' reference inserted at the prompt stays short and +readable. Falls back to the variable `temporary-file-directory' on +systems (e.g. Windows) without a top-level /tmp." + :type 'directory + :group 'claude-code) + ;;;;; Eat terminal customizations ;; Eat-specific terminal faces (defface claude-code-eat-prompt-annotation-running-face @@ -1166,6 +1176,7 @@ receives it as a file reference at the prompt. Returns non-nil to signal the paste was handled." (let* ((ext (claude-code--image-extension-for-mimetype mimetype)) + (temporary-file-directory claude-code-image-paste-directory) (path (make-temp-file "claude-image-" nil ext)) (coding-system-for-write 'binary)) (with-temp-file path (insert data)) From da1dc92b062afcd0b91e6f04dec3424829bba93c Mon Sep 17 00:00:00 2001 From: Steve Molitor Date: Thu, 30 Apr 2026 14:48:35 -0500 Subject: [PATCH 4/4] Note that ghostel backend supports C-v image paste natively --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 7f2c019..8f82f92 100644 --- a/README.md +++ b/README.md @@ -179,6 +179,8 @@ On Emacs 29 and later, you can paste an image from the system clipboard directly Works with all terminal backends (eat, vterm, ghostel). Disable by setting `claude-code-enable-image-paste` to `nil`, or disable cleanup with `claude-code-image-paste-cleanup-on-kill`. +If you use the [ghostel](https://github.com/dakra/ghostel) backend, regular `C-v` will also paste clipboard images directly into the Claude buffer — no claude-code.el handling required, since ghostel forwards image data through libghostty as a terminal feature ([thanks @dakra](https://github.com/stevemolitor/claude-code.el/issues/127#issuecomment-4288290963)). Drag-and-drop of image files into the buffer works the same way. + ### Managing Claude Windows The `claude-code-toggle` (`C-c c t`) will show and hide the Claude window. Use the `claude-code-switch-to-buffer` (`C-c c b`) command to switch to the Claude window even if it is hidden.