Contacts management in a TUI

· 8 min read · 1642 words

Contacts TUI screenshot

I haven’t really liked social networking as a development. Sometimes I’d even say I mourn its existence. I used to have people with whom I shared a great email correspondence who abandoned that in favor of the Eternal Holiday Newsletter mode of Facebook, or the “oh, I tweeted about it last week if you want to look it up” of the assorted microblogging services.

The kind of conversation we can have at a BBQ you invited your high school friends to is different from the one we can have over coffee. The kinds of things I’d say to you on my front porch might even be different from what I’d say at the cafe.

So the net effect has been to have a higher volume of very lossy connections at the cost of some very meaningful and higher-fidelity connections.

As I get older, that has gone from something I don’t like to something I feel very concerned about. Alison is constantly on me to stretch before and after our morning runs because she’s right: I do not have the body that once came off a six-week layoff from a parachuting injury to run in the Ft. Bragg 10-Miler the first day I was allowed to run again. Likewise, as much as I have a few habits of thought I try to cultivate to keep from turning completely inward sometimes, there are some parts of the world that don’t become more comfortable to deal with as we age. Some days, my reaction to whatever is going on out there is to wish people would just shut the fuck up about it. I think that’s a fair, reasonable, understandable reaction, but I also think that’s not a great place to go live.

As much as I’m introverted, politely contrarian, and completely over some of the fucking takes I’m reading, I have to stretch. As much as there are some people I’d love to hear more and different from, at some point you just have to acknowledge that for whatever reason, those vibrant email threads petered out and are not coming back, so you have to replenish the supply of human connection.

During my unplanned sabbatical a couple of years ago, I realized I was going to need to settle in for a few months of being at loose ends while I figured out what was next and started to drum up opportunities. I knew I needed to network, and I also knew I needed to just interact with people. Left to my own devices, and without a plan, I knew I’d just go full introvert and I didn’t want to go full introvert.

So I set about leveraging org-contacts to build what I called the plaintext CRM: A bunch of functions that made it easier to keep in touch with people. The core ideas were:

I used it for plain old social stuff during my layoff, and also to track recruiter pings. It helped me feel (and behave) a lot less transactionally about reaching out to people, because while I did have a small pitchfork at my back to get out and network, it reminded me to follow up on social stuff as well.

It was a good system for where I was at, because I was living in Emacs, it was sort of a fun and stunty layoff project to implement something like that in Emacs, and I was able to stick to it because it was right there in my todo system.

Usage fell off once I was back at work and got busy again, which has bothered me on and off for a while now.

Several weeks ago, I fed the elisp to Claude as a proof of concept for an MCP. The repo for that is private because I did put it into production for a period and the security studies on MCP are a little dire. They’re just modulations on common security issues other protocols can present, but I’m not gonna lay bare my vibe-coded contact management infra.

The MCP itself worked as well as a chat-based interface can, and did what a lot of AI stuff does these days: Points to something that is both pretty cool and simply not there yet.

The pretty cool part was making Claude aware of my contacts database and the state of contact with everyone in it. During day-planning, I had it set up to check in on the state of contacts and drop a few pings, followups, or calls into my calendar for the day. That was a small proof of concept for MCPs as a kind of executive assistant.

The parts that were not there yet:

On that last point:

You see people advertising their MCPs as “running locally and completely in your control.” Well, okay, but what the LLM is doing with that data is not completely in your control. What the LLM’s owner is doing with that data is not completely in your control. And for a homebrew sort of hobby implementation of a new protocol in a fast-moving technology, you’re assuming extra risk.

Second, I’m just curious, not completely obtuse. I want to learn about these things – work actually requires me to learn about these things – but there are people with heartfelt, sincere, thought-through objections to the AI industry whom I know would not like that even innocuous data about them is being fed into an LLM maw. I’m sorry I picked a project that involved that. I stopped at “actual need and well-understood problem” and didn’t think past that.

So I took the MCP down, which left me a little frustrated: I really did not want to go back to managing contacts in Emacs, and I really do not like any of the alternatives I’ve found for contact management: Finding the sweet spot of actually managing contact information and also managing the workflow of interacting with contacts has been a little hard.

So I teed up one last session with Claude to use the existing MCP workflows, data structures, and actions and turned them into a Go TUI that just repurposed my existing sqlite-hosted data into a database on my local disk. It does pretty much what my original elisp implementation does, but it’s written in Go so it’s a compact binary (and Claude is sort of a Go prodigy compared to experiments in Typsecript, Ruby, and bash).

I’m pretty happy with what I got:

This version is publicly available and pretty easy to build and install on a Mac, at least:

pdxmph/contacts-tui

There are a few features mentioned in the docs for creating a fixtures db you can kick the tires with, or just onboard with a blank db and start using it. There is no contact import tooling. Given the simplicity of the sqlite backend, it’d be pretty easy to lash up a CSV importer, and iirc it is not terribly hard to write some AppleScript to do that from the Contacts app or from a format like VCF.

I’m not sure it solves anything for anyone else the way they’d like, but it hits an intersection that’s useful to me by creating structure to be better at something I often neglect and wish I did better.

#Lmno