What it is
Nine small libraries, one shape. Each one wraps a service I actually use: training logs, a watch, scales, a food diary, a wiki, a photo library, a social scheduler, an asset library, an image generator.
Inside each is the same arrangement. One module of pure functions that do the work. One declarative spec that describes every operation once. Then a command line and an agent tool, both generated from that spec rather than written twice.
Most of them are between 2,500 and 5,200 lines. They were built between mid-July and 29 August 2026.
Why I built it
I kept writing the same integration twice. Once as a command line so I could use it, and once as an agent tool so a model could. Then the two would drift, because a fix went into whichever one I happened to be holding, and a month later they disagreed about what an operation was even called.
The honest trigger was finding out how bad it had got. One of these replaced a hand-written command line of 878 lines with no agent tool at all, while my own service inventory claimed the agent tool was running. There was no container behind it. Another replaced a fork that turned out to be a byte-for-byte copy of logic that already existed somewhere else. A third converged an abandoned fork and an 851-line script that did the same job.
That is three different ways of ending up with the same code in two places, all of them mine.
The problem
Generating two surfaces from one description sounds obvious and is fiddly in the details. A command line wants short flags, positional arguments and output a person can read. An agent tool wants a typed schema, a docstring that explains when to reach for it, and output a model can parse. The temptation is to let each surface add “just one” special case, and the moment you do, you are maintaining two things again with extra machinery in between.
So the rule is that the spec is the only place an operation is described, and if a surface needs something the spec cannot express, the spec is what changes.
What I learnt
Duplication is not usually a decision. Nobody sat down and chose to write the Garmin integration twice. It happened because the second one was needed on a different day, in a different context, and copying was faster than remembering.
What stopped it was removing the place a second copy could go. There is nowhere to put one now, so nobody has to remember not to.
I also learnt to distrust my own inventory. A document that said a service was running, that nobody had checked against a container list, was worse than having no document.
What’s next
The pattern is settled and I would rather add services than change it. The next one goes in when something I use daily still needs a browser tab.