Copy any file from yazi to any app (c, s)
A 4-line config that puts the hovered file on the macOS system clipboard, so you can paste it into Slack, ChatGPT, Mail, Finder — anywhere that accepts file uploads.
yazi is a fast terminal file manager. Its default c menu copies the file path, directory, or filename as text — useful for shell work, but none of those let you drop the file into Slack or attach it to a ChatGPT message.
This adds one chord — c then s — that puts the actual file on the macOS system clipboard as a file reference. Cmd-V into Slack uploads it. Into Mail, it attaches. Into ChatGPT web, it becomes an image/PDF upload. Into Finder, it pastes as a file.
Why pbcopy isn't enough
pbcopy writes text to the clipboard. Pipe a file into it and the clipboard holds the file's contents, not a reference to the file. Slack's paste would dump your binary into a message as gibberish.
macOS apps that accept file uploads look for public.file-url on the pasteboard. The only reliable way to write that from a shell is AppleScript:
set the clipboard to POSIX file "/absolute/path/to/file"
That single line is the whole trick. Everything else is plumbing to hand yazi the hovered file path and call osascript.
The config
Two files. First, ~/.config/yazi/copy-file.sh:
#!/bin/bash
# Copy a file to the macOS system clipboard as a file reference.
# Paste into any app that accepts file uploads.
osascript -e "set the clipboard to POSIX file \"$1\""
chmod +x ~/.config/yazi/copy-file.sh
Then ~/.config/yazi/keymap.toml:
[[mgr.prepend_keymap]]
on = ["c", "s"]
run = 'shell -- ~/.config/yazi/copy-file.sh "%h"'
desc = "Copy file to system clipboard"
Restart yazi, hover any file, press c (yazi's copy menu appears, s → Copy file to system clipboard now sits with the built-in options), then s. File is on the clipboard. Switch to any app, Cmd-V.
What each piece does
| Piece | Role |
|---|---|
mgr.prepend_keymap |
Adds a binding alongside yazi's defaults — c's path/dir/filename options still work |
["c", "s"] |
A chord: press c, then s |
shell -- |
Runs a command via sh -c; -- tells yazi "everything after this is raw, don't parse flags" |
%h |
Yazi placeholder for the hovered file's absolute path |
osascript -e |
Runs AppleScript; writes a public.file-url entry to the pasteboard |
Gotchas I hit
Escaping the AppleScript string inside a TOML-single-quoted run value inside a yazi command template that dispatches to sh -c — four nested contexts — was ugly enough that I gave up on inlining the AppleScript. The wrapper script is the clean version.
A few dead ends:
| Attempt | Result |
|---|---|
run = 'shell "pbcopy < $0"' |
Copies file content as text. Slack pastes gibberish. |
run = 'shell -- osascript -e "... \"$0\""' |
$0 is deprecated in yazi 26; use %h. |
run = 'shell -- osascript -e "... \"%h\""' |
Nested quote escaping breaks somewhere in the TOML → yazi → shell → osascript chain. |
Wrapper script + shell -- script.sh "%h" |
Works. |
Same, plus --block |
Works and errors become visible. Silent failures were the real killer while debugging. |
Key takeaway: when a yazi binding silently does nothing, add --block to the shell call. It drops the terminal to a secondary screen and shows you stderr.
Verify it works
echo "hi" > /tmp/test.txt
# In yazi: hover /tmp/test.txt, press c, then s.
# In Slack (or ChatGPT, or Mail): Cmd-V.
# Your file uploads as an attachment.
If it doesn't work, temporarily add --block:
run = 'shell --block -- ~/.config/yazi/copy-file.sh "%h"'
Press the chord again — you'll see any error from osascript.
What this pattern unlocks
Once you have shell -- script.sh "%h" working, yazi becomes the sender for anything macOS can do from AppleScript: add to Reminders, open in a specific app, trigger a Shortcut, AirDrop, send to Messages. The file manager becomes a launchpad, not just a navigator.
TL;DR
- Slack/ChatGPT/Mail pastes need a file reference, not text — only AppleScript writes those from a shell.
- Put the AppleScript in a wrapper script so you don't fight quote escaping.
- Use
%h(not the deprecated$0) andshell --in yazi 26+. - Add
--blocktemporarily when a binding silently fails.