feat(terminal): paste & drop images into terminal#90
Merged
Conversation
Drag a file or paste an image-from-Finder into the terminal and the absolute path is typed (escaped, then wrapped via term.paste so xterm emits bracketed-paste markers). CLI agents like Claude Code see the path as a paste rather than literal typing, which triggers their file-attachment recognition and turns "/Users/.../image.png" into [Image #N] instead of a "no such file" error. Implementation: - new IPC ResolveClipboardPaste — main-process picks file URL → image → text in priority order, so Finder-copied image files pass an absolute path instead of the bare basename - new IPC SaveDroppedImage — base64 round-trip (renderer's invoke() wrapper destroys typed arrays via JSON.parse(JSON.stringify())) so browser-origin <img> drops still produce a usable temp path - webUtils.getPathForFile exposed via preload as window.electron.getPathForFile (Electron 32+ replacement for File.path) - TerminalView capture-phase dragover/drop listeners so xterm's own bubble-phase handler doesn't insert the basename first - escapePath helper backslash-escapes whitespace + shell metacharacters so paths round-trip cleanly through both POSIX shells and agent prompt parsers - legacy SaveClipboardImage handler removed Linux clipboard support reads x-special/gnome-copied-files (Files, Nemo, Caja) ahead of text/uri-list (KDE, Xfce). Per-file resolution runs in its own try/catch so one unreadable item in a mixed drop doesn't cancel the resolvable siblings. Temp filenames append a 6-char random suffix so concurrent same-name drops don't collide. Spec captured under openspec capability terminal-image-paste (6 requirements, 32 scenarios) plus an archived change record at openspec/changes/archive/2026-04-28-support-paste-images/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Owner
|
Very cool! Thank you very much! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
fixes #59 (comment)
Drag a file (or paste an image-from-Finder copy) into the terminal and the absolute path is typed into the active CLI agent — escaped, then delivered through
term.paste()so xterm wraps it in bracketed-paste markers. Agents like Claude Code recognise that as a path paste and turn it into[Image #N]instead of the current "no such file" experience.Fixes the v1.2.1 report:
What changed
ResolveClipboardPaste— main-process picksfile URL → image → textin priority order. macOS readspublic.file-url; Linux readsx-special/gnome-copied-files(Files / Nemo / Caja) ahead oftext/uri-list(KDE / Xfce). Without this, copying an image file in Finder returned only the basename vianavigator.clipboard.readText().SaveDroppedImage— bytes from path-less drops (e.g.<img>dragged from a browser tab) are base64-encoded over IPC and decoded in main withBuffer.from(b64, 'base64'). The base64 detour exists because the renderer'sinvoke()helper does aJSON.parse(JSON.stringify(args))round-trip that destroys typed arrays.webUtils.getPathForFileexposed via the existing preload bridge aswindow.electron.getPathForFile— Electron 32+ replacement for the removedFile.path.dragover/droplisteners so xterm's own bubble-phase handler can't insert the basename first. Multiple files are space-joined.escapePathhelper backslash-escapes whitespace and shell metacharacters; output round-trips cleanly through both POSIX shells and agent prompt parsers, matching macOS Terminal / iTerm2 / VS Code drag-insert convention.term.paste()delivery for paste and drop payloads so xterm emits bracketed-paste markers (\x1b[200~ … \x1b[201~) when the agent has bracketed-paste mode on. Without these markers Claude Code reads the path as literal typing and skips the file-attachment step.crypto.randomBytes(3)suffix so concurrent same-name drops don't overwrite each other.SaveClipboardImageIPC + handler — fully superseded byResolveClipboardPaste.terminal-image-paste(6 requirements, 32 scenarios) atopenspec/specs/terminal-image-paste/spec.md. The change record is archived underopenspec/changes/archive/2026-04-28-support-paste-images/.Scope
macOS and Linux only, per the project's published platforms. Windows clipboard formats (
FileNameW) and quoting ("…"for cmd / PowerShell) are explicitly out of scope and called out in the design doc as a future change if Windows is ever added.Test plan
npm run typecheck(renderer + electron tsconfigs) ✅npx vitest run— 322 passed, 0 failed (9 new tests forescapePath+dataTransferToShellArgs)npm run lint(eslint, --max-warnings 0) ✅npx prettier --check✅openspec validate --all --strict— 9 / 0 ✅.pngfrom Finder into aclaudesession — expect[Image #N]placeholder<img>from a browser tab — temp file written, path inserted🤖 Generated with Claude Code