Cairn: An Idea, an Agent, and a Working App in Days
My 2015 MacBook Finally Gave Up
I got a lot of mileage out of my 2015 MacBook Pro. For years it just worked — until it didn’t. Apple stopped shipping updates for it. The hardware, which was already at its ceiling running a heavy JetBrains IDE alongside a handful of Docker containers, started to buckle. It wasn’t a sudden death; it was a slow decline that I kept patching around until I couldn’t anymore.
Out of curiosity more than necessity, I decided to see if I could run Linux on it. I pulled out a thumb drive, tried a few distros — Fedora, Ubuntu, Kubuntu — and eventually settled on Fedora for the laptop because it had the fewest hardware compatibility headaches. Later I picked up a desktop running Kubuntu. It’s more powerful than anything I could afford in the Apple ecosystem and costs significantly less.
So now I have three machines: two Linux boxes and a MacBook I still use at work. That’s when the problem I’d been quietly ignoring got impossible to avoid.
Three Devices, Three Browsers, One Mess
With three machines in regular rotation, I was also using slightly different browsers on each. I could have synced everything through a single browser vendor, but that would mean handing over my data to yet another account and being locked into someone else’s system — which I’d just spent months escaping. My bookmark situation was a disaster. Years of “I’ll sort this later” had accumulated into folders I never opened and links that were almost certainly dead.
What I really wanted was something that:
- lived locally, in a database I owned
- worked from the terminal, where I spend most of my time anyway
- synced across machines without requiring me to run a server
- let bookmarks expire — because the internet moves on and so should I Nothing I found did all of that. So I built it. I called it Cairn.
What Cairn Is
Cairn is a CLI bookmark manager with a terminal UI and a Vicinae browser extension. It stores everything in a local SQLite database. You can add bookmarks from the command line, browse and search them in a keyboard-driven TUI, tag and pin entries, archive old ones, and sync the whole thing to Dropbox. The name comes from the cairn — a human-made pile of stones used for centuries as trail markers, summit indicators, and wayfinding guides. A cairn marks a place worth returning to. That felt right for a bookmark manager.
Features at a glance:
- Interactive TUI — browse, search, manage with the keyboard
- Full-text search via SQLite FTS5
- Tags, pinning, archiving
- Dropbox sync — no server required
- JSON output for piping into other tools
- Vicinae browser extension for saving and searching from the browser launcher
- A single static binary — no runtime dependencies,
CGO_ENABLED=0You can install it with:
curl -sSL https://raw.githubusercontent.com/ndy40/cairn/main/install.sh | sh
But the point of this post isn’t really what Cairn does. It’s how it got built.
This Isn’t My First Agentic Rodeo
I’ve written before about my journey from AI-assisted coding to agentic development. Cairn isn’t the first project I’ve tackled with an agent workflow. But it’s the one where I went deepest.
For Cairn I used:
- GitHub Speckit for feature specification and planning — writing specs before touching code forces clarity about what you’re actually building
- Backlog.md for task management, wired into the development loop so the agent and I shared a single source of truth on what was done and what wasn’t
- Claude Code as the primary agent What made this different from previous experiments was the end-to-end commitment. I didn’t dip in for a feature here or a refactor there. I ran the full product development lifecycle through this workflow — from spec to implementation to CI/CD to install script to documentation.
I Didn’t Need to Become a Charm Expert
Go’s Charm libraries — Bubbletea, Bubbles, Lipgloss — are excellent. They’re also a non-trivial surface to learn if you want to do anything beyond the basic tutorial. Building a TUI that feels good takes time to figure out. I made a deliberate choice: I didn’t want to spend two weeks becoming a Charm expert. I wanted a working TUI. Those are different goals.
So I focused on what the TUI needed to do — the keyboard bindings, the state transitions, the list behaviour, the modal overlays — and let the agent handle the Charm specifics. I stayed involved enough to review what it produced, correct it when it was off, and redirect it when it was going somewhere I didn’t want. But I wasn’t writing Lipgloss layout code from scratch.
This is the thing that’s genuinely shifted for me: I don’t need to be fluent in every library to capitalise on it. I still dig into documentation — you have to, or you can’t guide the agent meaningfully — but the bar has moved from mastery before building to enough understanding to direct well.
The Moment That Crystallised the Whole Thing
The sync feature is where this clicked most clearly for me. I needed a way to keep bookmarks in sync across three machines. The agent’s first instinct was to propose a backend server — a hosted API endpoint, database replication, the works. Which is a perfectly valid technical solution and also exactly the kind of infrastructure I didn’t want to run.
I pushed back. What if we just used Dropbox? I have an account I barely use. The database is a single SQLite file. Sync it to Dropbox, pull it on any machine. Done. The agent ran with it. No server. No new infrastructure. No ongoing cost. Just a small local binary that reads and writes to a Dropbox-connected folder.
That exchange stuck with me because it’s a good illustration of where the human still matters: not in knowing how to implement every option, but in knowing which problem is actually worth solving. The agent brought implementation breadth. I brought solution judgement.
What I Still Do Myself
I want to be honest here, because the “agents do everything” story is seductive and also not quite true.
I still review every piece of code that goes in. Not line by line with a magnifying glass, but I read it and I catch things. I’ve asked for refactoring when the agent made choices I didn’t like — moving inline help text out of functions and into files that get read at display time, for example. Small things that matter for maintainability over time.
I also set direction. The agent doesn’t know that I’d rather have no backend than a complicated one. It doesn’t know that I want Cairn to feel like a tool I’d actually reach for, not just a working prototype. That taste, that sense of what fits — that’s still mine to provide.
Architecture is still a human job. Specification is still a human job. So is code review. The agent is fast, capable, and tireless. But it needs someone at the wheel who knows where they’re going.
The Limit Has Moved
Before this workflow, my constraint was time and breadth. There were things I wanted to build but didn’t, because getting up to speed on an unfamiliar library took too long, or because the scaffolding cost was too high, or because I’d run out of energy before I got to the interesting part.
That constraint is largely gone.
The limit now is clarity — can I articulate what I actually want well enough to specify it? And taste — do I know a good solution when I see one? Cairn is a tool I use every day. I built it in a fraction of the time it would have taken me working alone, and it’s more complete than anything I’d have shipped on my own schedule — TUI, full-text search, sync, browser extension, install script, CI/CD, documentation, the lot.
That’s what agentic development, done properly, looks like to me. Not magic. Not a replacement for knowing your craft. A genuine multiplier for people who already know what they want to build.
If that sounds interesting, Cairn is open source.