Skip to content
Back to blog
7 min read

I Built My Own Consulting ERP

I Built My Own Consulting ERP

Every solo consultant is running a company. Most of us are steering it with duct tape.

Here’s a Tuesday I’ve lived more than once: a retainer client’s invoice is due, and the truth about that client is scattered across four tabs. The proposal is a Google Doc. The signed SOW is a PDF buried in Drive. The invoice itself lives in Wave, which has never heard of the proposal or the SOW and has no idea this month is a retainer, not a one-off. The hours are in a spreadsheet I update when I remember to. The 25% I’m supposed to be setting aside for taxes is a number I do in my head, badly.

None of those tools did anything wrong. Wave is a fine invoicing tool. The problem is that I don’t have an invoicing problem. I have a back office problem, and nobody sells a back office sized for one person.

So I built one. It’s called Helm, it’s a local-first PWA, and as of this week it’s tagged v1.0.0 and running my actual practice: helm.brett-buskirk.dev.

The pipeline nobody sells you a tool for

Generic invoicing software is built around a single moment: you did the work, now bill for it. That’s not my pipeline. Mine is proposal → SOW → deposit → milestones → retainer → tax set-aside, and every stage needs the stages before it. An invoice without the SOW it came from is just a number I have to trust myself to remember correctly. A retainer without a history of what was actually delivered is a fight waiting to happen. Wave, FreshBooks, QuickBooks: they’re all excellent at the last step of a pipeline that, for a consultant, has five steps before it.

The value I needed wasn’t more invoicing features. It was niche fit: a tool shaped around the actual shape of the work, not a generic shape I have to bend the work into.

One hub, not a filing cabinet

The design decision everything else in Helm hangs off is small to say and large to build: everything links to a client. Open a client and you see their entire history in one place: every proposal, every SOW, every invoice, every hour logged, every document generated, in one connected graph instead of four disconnected apps that have never met each other.

An accepted proposal turns into an invoice in one click, because the proposal already is the invoice’s ancestor; there’s no re-typing the line items and hoping they still match. A retainer client’s monthly invoice generates itself from the dashboard, because the system already knows who’s on a retainer and what it’s worth. Unbilled hours roll straight into an invoice, and if I cancel that invoice, the hours come back, because the graph, not a spreadsheet, is the source of truth. None of this is clever. It’s just what happens when the data model matches the actual business instead of a generic idea of “a business.”

The parts that aren’t the fun parts

The features I’ve described so far are the ones a demo shows off. The ones that took the most care are the ones a demo never shows.

Helm is local-first: no server, no accounts, no cloud database. Everything lives in the browser’s IndexedDB, via Dexie, on the machine I’m sitting at. For a tool holding client contracts and financial history, that’s not a technical preference, it’s the actual pitch: there’s no server to breach, because there’s no server. But local-first has a cost the marketing copy always leaves out, which is that “your data lives only on your machine” is also a threat, not just a feature. Clear your browser storage and it’s gone. So backup and restore isn’t a nice-to-have I got to eventually; it was a Phase 0 requirement, same priority as the invoices themselves. One-click JSON export, one-click import, and an encrypted version of both (AES-256-GCM) for anyone who wants their backup file to be as safe as the database it came from. Opt-in at-rest encryption locks the live app behind a passphrase, too, with no recovery path if you lose it, because a recoverable passphrase isn’t really encryption. And the PDFs (invoices, proposals, branded documents) are generated entirely client-side, because sending contract language to a third-party rendering service is exactly the kind of thing a local-first tool isn’t supposed to do.

None of that is glamorous. It’s the seatbelt, not the engine. But it’s the part that decides whether I’d actually trust the tool with my own business, which is the only bar that matters for something I built to dogfood.

The decision I almost shipped wrong

For most of the build, I assumed “ship it” meant native installers. Helm has a Tauri v2 desktop wrapper sitting in the repo, and the plan was straightforward: package it, sign it, ship a .dmg and an .exe like real desktop software does.

Then I actually priced out what “sign it” costs. macOS notarization needs an Apple Developer Program membership, paid annually. Windows code-signing, since mid-2023, has to come off a hardware token or a cloud HSM, not just a certificate file, because Microsoft (correctly) got tired of stolen signing keys. Add per-OS packaging, notarization, and an auto-update story for three platforms, and I was looking at recurring cost and real ongoing maintenance, all in service of shipping a tool with exactly one intended user: me.

That’s the moment the whole premise cracked open. Local-first is supposed to be the low-friction option. I was about to bolt a subscription’s worth of platform overhead onto a tool whose entire pitch is “no server, no accounts, no ongoing anything.” The installer wasn’t serving the user. It was serving an assumption about what “real software” looks like that I’d never actually questioned.

So I reversed it. The PWA is the shipped product. It installs straight from the browser, updates itself silently through the service worker, costs nothing to host, and runs on anything with a modern browser: no signing, no notarization, no per-OS builds. The Tauri wrapper is still in the repo, isolated and free to keep, in case a genuine desktop-only reason ever shows up. But v1.0.0 shipped as a PWA on purpose, not as a fallback.

It’s the same shape as the lockfile that broke a deploy: the expensive path looked like the correct one right up until I checked what it actually cost, and the boring path turned out to be the one that matched the goal.

The dogfooding part

Helm isn’t a demo app with sample data I generated once for a screenshot. It’s the tool I invoice through right now, this month, for real clients. When something’s clunky, I feel it the next time I bill someone, and it goes on the list. That’s a different discipline than building something to show it works: the software has an angry user if it’s wrong, and that user is me.

It also pairs with something I’ve written about before. AgentGate is the guardrail that keeps an eager AI agent’s pull requests honest; Helm is the proof that the same working style (agents doing the labor, a human owning the judgment, real review before anything merges) ships a full product, not just infrastructure tooling and CLIs. Range isn’t a slide in a pitch deck. It’s a live app I use every day and a CI pipeline that gates every change to it, same as everything else I ship.

Where it landed

A helm, on an actual boat, isn’t complicated. It’s a wheel connected to everything that matters: the rudder, the course, the whole vessel responding to one set of hands. That’s the entire idea behind the name. A solo consulting practice is a very small boat, and most of us have been steering it with four unconnected instruments and a lot of guessing.

I didn’t need more invoicing features. I needed one wheel that turned the whole ship.