Skip to content

goude/cop

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

33 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

cop

cop is one command for the clipboard, wherever you happen to be. It picks the right backend for the machine you're on (pbcopy, wl-copy, xclip, or OSC 52 over SSH) so you don't have to remember which one lives where.

echo "hello" | cop      # copy stdin
cop file.txt            # copy a file's contents
cop -p                  # paste to stdout
cop -p out.txt          # paste into a file

Pipe something in to copy it, run cop -p to get it back. Same two commands on macOS, on Linux under Wayland or X11, and inside an SSH session. When you want more, it can also sync between machines through a small cloud relay (encrypted by default), append to the clipboard, copy a whole directory at once, and drop starter files like .gitignore into a project.

Install

homeshick clone gh:goude/cop
homeshick link cop

That symlinks ~/.homesick/repos/cop/home/bin/cop to ~/bin/cop. There is nothing else to configure. pas is a symlink to cop for pasting, so pas and cop -p do the same thing.

Optional fish completions:

cop --completions fish > ~/.config/fish/completions/cop.fish

Everyday use

echo "hello" | cop      # copy text
cop file.txt            # copy file contents
cop < file.txt          # same, via redirect
cop dir/                # copy every file in dir/, each labelled by path

cop -p                  # paste to stdout
cop -p out.txt          # paste into a file
pas                     # paste (pas is a symlink to cop)

echo "more" | cop -a    # append to what's already on the clipboard

-t (--tee) passes input straight through to stdout while it copies, so you can watch a long command run and still keep its output:

make 2>&1 | cop -t      # the build scrolls past live; the full log lands on the clipboard

cop --notes opens (or creates) a NOTES.md in the current directory with your $EDITOR.

Cloud sync

cop -n moves text between machines through a small Cloudflare Worker. Two things to know before you use it:

It encrypts by default. You set COP_SECRET, and the text is encrypted (AES-256-CBC with an HMAC for integrity) before it leaves your machine. A fetch with cop -pn detects the format and decrypts it for you. If COP_SECRET isn't set, cop -n refuses rather than send anything in the clear.

It always asks first. Every upload shows a short preview and waits for y/N. Nothing is sent silently.

export COP_SECRET=your-passphrase

echo "hi" | cop -n      # asks, then encrypts and uploads
cop -pn                 # fetches and decrypts to stdout
cop -pnc                # also copies the result locally

The read endpoint is public, so if you genuinely want to send plaintext you have to say so:

echo "public note" | cop -n --plain   # warns, asks, then uploads as-is
cop -pn --plain                        # fetch the raw stored value

Reading never needs a secret. Pushing needs COP_WRITE_SECRET (see below).

A note on the crypto: the MAC is checked before anything is decrypted, so a tampered or wrong-key value fails instead of returning garbage. The check uses a plain string compare rather than a constant-time one, which is fine for a personal clipboard with a high-entropy secret over HTTPS but worth knowing.

Templates

Files cop can copy into the current directory:

Template What it is
.gitignore A broad gitignore for most projects
.editorconfig EditorConfig with common defaults
NOTES.md A NOTES.md stub
cop --templates              # list them
cop --template .gitignore    # copy one into ./

Names prefix-match, so cop --template git resolves to .gitignore.

Running your own worker

The default endpoint is the author's personal worker, so for real use you'll want your own. Point cop at it with environment variables:

export COP_SERVICE_URL=https://your-worker.workers.dev/cop
export COP_WRITE_SECRET=...   # the push token; reads don't need it

The worker is a single file, cloudflare/worker.js. GET is public, POST requires a Bearer token. To deploy it:

  1. Create a Worker (Cloudflare dashboard, Workers & Pages, Create) and paste in cloudflare/worker.js.
  2. Create a KV namespace and bind it to the worker under the name COP_STORE.
  3. Add a secret named COP_WRITE_SECRET, for example openssl rand -hex 32.
  4. Deploy, and note the *.workers.dev URL.

Keep COP_WRITE_SECRET out of any repo. It belongs in Cloudflare and in the shell environment of the machines that push. Put both variables in your shell profile to keep them across sessions. Since encryption is on by default, what sits in the KV store is ciphertext; only --plain sends would be readable by anyone who knows the URL.

Development

Layout:

home/bin/cop          bootstrap: resolves its real path and sources the modules
lib/cop/
  ui.sh               colours, help text, content_stats, completions
  clipboard.sh        backend detection, OSC 52
  crypto.sh           base64 and authenticated encryption (cop_encrypt/cop_decrypt)
  network.sh          worker calls and the upload confirmation
  templates.sh        template copy and list
  tests.sh            self-test suite
  core.sh             do_copy, do_paste, main
  templates/          the template files themselves

The bootstrap sources modules in dependency order (ui, clipboard, crypto, network, templates, tests, core) and then calls main.

Checks before committing:

cop --test                                  # self-tests; network round-trips run only if COP_WRITE_SECRET is set
shfmt -i 2 -d home/bin/cop lib/cop/*.sh     # formatting (shfmt is the formatter)
docker run --rm -v "$PWD:/mnt" -w /mnt koalaman/shellcheck \
  -x -P SCRIPTDIR home/bin/cop lib/cop/*.sh # lint

To add a template, drop a file into lib/cop/templates/. It shows up in cop --templates immediately.

To add a module, create lib/cop/<name>.sh, add a source line for it in home/bin/cop before anything that depends on it, and run cop --test.

About

shell helper for copying and other shenanigans

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors