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.
Related articles
The New ChatGPT Desktop App: Chat Is No Longer the Center
The redesigned ChatGPT desktop app demotes the text box and promotes Work and Codex. Here's how it differs from the web app, what that says about where AI interfaces are heading, and how to split your workflow between the two.
Claude Fable 5 in Claude Code: A Setup That Doesn't Burn Your Quota
Fable 5 is the smartest model you can run in Claude Code — and the one with the tightest weekly quota. Here's the configuration we landed on: Fable for judgment, cheaper models for execution, and hard limits on context growth.
Local AI in Your Terminal: Ollama + Gemma 4 + ShellGPT
Run Google's Gemma 4 locally with Ollama and wire it into your terminal workflow via ShellGPT — from shell commands to image OCR.