clipardo: what we did before the assistant could open a file
August 1, 20265 min read
For a long stretch, working with an AI on code meant two windows and a lot of clicking.
The chat lived in a browser. The code lived in an editor. Between them was me. The model could reason about anything I put in front of it and could see absolutely nothing I didn't. It had no hands. I was the hands. (The same era is why I was building chatbots as hand-wired graphs of n8n nodes — nothing came for free.)
The paper cut
The loop went like this. Open the file. Ctrl+A, Ctrl+C. Switch windows. Paste. Type the question. Send.
Then: "I'd need to see the component that imports this."
Switch back. Find that one. Ctrl+A, Ctrl+C. Switch. Paste. Send.
Then: "and your Tailwind config?"
None of those steps is hard. That's exactly the point. Each one costs three seconds and zero thought, which is the kind of cost that never gets counted and never stops being paid.
The worse problem, which wasn't the clicking
Paste six files into a chat box back to back and you get six blobs of code with nothing to tell them apart. The model has no way to know the third one is lib/db.js and the fifth is a route handler two directories away. So it guesses. It guesses reasonably, and then it answers with an import path that doesn't exist, or edits the wrong one of your two files named index.js.
The answers weren't wrong about the code. They were wrong about where the code lived, which in practice is the same thing.
So I started typing the filename above each paste by hand. That fixed it. It also meant I was now doing more clicking than before.
The observation
The unit of the conversation was never "the code." It was the code, plus where it lives, plus what language it's in. Three things, always together, always in that order.
Markdown already had a container for exactly that, and the chat box already rendered it:
lib/db.js
``` javascript
export const db = new Database("app.sqlite")
```
That is the entire idea. The script just stopped me from typing it.
The script
clipardo lib/db.js app/api/orders/route.js
Both files are in the clipboard, labeled, fenced, ready. One paste.
clipardo -r components/
Every text file under components/, recursively, each one with its path on top.
Everything else in those 200 lines is about what not to copy.
The parts that mattered more than the copying
It reads your .gitignore. Inside a git work tree it asks git itself, file by file, and skips whatever git would skip. This started as a convenience — nobody wants to paste node_modules — and became the most important thing in the script. The clipboard is the most casual way in the world to leak a secret. A recursive copy of a project root without that filter is .env going into a chat window at one in the morning, and you would never notice.
It drops binaries. An extension whitelist first, then file --mime-type for everything else. Point a recursive copy at a folder with a PNG in it and the PNG simply isn't in the paste. That's also its one real gotcha: it's silent. You can get back fewer files than you asked for and nothing will tell you.
It asks before doing something enormous. Past a hundred files it stops and waits: You are about to copy 187 files, continue? [y/N]. Copying an entire project in one command was genuinely possible and usually a genuinely bad idea. Context windows were smaller then, and the failure mode wasn't an error message — it was a model that had read everything and understood nothing.
The path line above each fence. The cheapest thing in the payload and by far the most valuable. It costs one printf, and it's what turns a pile of code into a map.
The language tag on the fence. Syntax highlighting for me, a hint for the model. The mapping uses editor language IDs, so .jsx comes out as javascriptreact — a fossil of wherever I copied that list from. It never mattered enough to fix.
Its entire maintenance history
Three commits.
The first is the day my dotfiles repo was born: 5 May 2025, 203 lines, already sitting inside the initial commit. Which means the script is older than its own history and I genuinely can't tell you when I wrote it.
The second, more than a year later, adds one flag: -c, content only, no path lines — for when you're pasting into a document instead of a conversation and the paths are noise.
The third changed my name in a comment.
It's 207 lines today. It carries an MIT header, which is what you put at the top of a personal bash script when some part of you believes you'll publish it one day.
And then the era ended
I'm writing this in a terminal where the assistant opens the files itself. It greps. It follows imports. It reads the three files I didn't think to mention. The problem clipardo solves is a problem I mostly don't have anymore, and I'd rather say that plainly than pretend the script is still load-bearing.
It's still installed.
Partly because not every conversation is with an agent. A web chat, a model with no tools, a colleague on WhatsApp, a bug report, a forum post — every one of those still wants code with its path on top and a fence around it.
Partly because curation didn't disappear, it moved. "Here are exactly these six files and nothing else" is still a decision you make by hand, and sometimes it's the whole trick.
But mostly because of what it taught, which the agents later formalized: context is something you choose, and the location is part of the payload. A file without its path is a fact with nowhere to stand.
The good tools of that era were not clever. They took a small, stupid indignity you performed over and over in an afternoon and turned it into one word. That's the whole design.
clipardo lib/, and paste.