Skip to main content
WritingSpeakingCodeAboutNow

My clients live in a folder now

Freelance tools are good at the books and bad at everything around them. mavis keeps clients, calls, time and invoices as Markdown files, run from one binary.

The part of freelancing I am worst at is not the work. It is everything wrapped around it: what we agreed on that call, what I said I would send by Friday, which client I have not spoken to since the summer, and whether last month’s days are on an invoice yet.

The tools for this are built around the books. FreeAgent is very good at invoices and bank reconciliation, and that part of my business is handled by someone who is better at it than I am. What none of it holds is the stuff I touch every day, so it ends up scattered across a notes app, my calendar, and my memory, which is the least reliable of the three.

So I built mavis. It is one Go binary that keeps a freelance business as Markdown files in a folder: clients, the work you do for them, every call and note, what you promised to do next, your time, and the quotes and invoices that come out of it. You run it from the terminal, and the command I built it around is this one:

Terminal window
$ mavis today
Overdue invoices
INV-2026-001 acme · 1,857.00 GBP owed · due 2026-03-03 (213 days ago) · not chased yet; reminder 1 due: mavis invoice remind INV-2026-001
Retainers to bill
globex Care plan · August 2026 · 800.00
globex Care plan · September 2026 · 800.00
Draft them with: mavis invoice retainers --draft
Quotes waiting
Q-2026-001 initech · Reporting rebuild · 6,500.00 GBP · sent 2026-10-02 (today) · valid until 2026-11-01
Due this week
Send estimate acme · due 2026-10-05
Active engagements
acme Bug fixes hourly · since 2026-01-05
acme Reporting module day · since 2026-01-05
globex Care plan retainer · since 2026-08-01

That is sample data, but it is the real output. Everything that needs me, in order of how much it costs to ignore: money owed, then money I have not asked for yet, then decisions someone is waiting on, then things I promised.

Why files

The decision everything else follows from is that the records are plain files, not rows in someone’s database.

A client is clients/acme.md. A call is log/2026-10-02-acme-call.md. They are Markdown with a block of frontmatter at the top, which means I can cat a client, grep a year of calls, and git diff the day a rate changed. Nothing needs exporting, because nothing was ever locked in.

---
type: log
kind: call
date: 2026-10-02T11:08
client: '[[acme]]'
engagement: '[[acme-reporting]]'
with: [Jo Bloggs]
---
Scoped the reporting module; exports by month end.
## Follow-ups
- [ ] Send estimate (due 2026-10-05)
- [ ] Share staging access (due 2026-10-09)

Those [[acme]] links are Obsidian links on purpose. Point mavis at a vault and every record becomes a note: a client’s backlinks are its engagements and its whole history, and a follow-up is an ordinary checkbox you can tick in Obsidian just as well as from the terminal. Both see the same file.

That only works if mavis never fights you over the files. When it rewrites one, it keeps whatever you added by hand: extra properties, their order, comments, and the body exactly as you left it. That guarantee is the one I would refuse to break, because a tool that tidies away your notes is a tool you stop trusting with them.

The day to day

Most of it is three commands. After a call:

Terminal window
$ mavis log call acme "Agreed the export format; they want CSV first." -f "Send the revised estimate" --due +2d
Logged call with acme: log/2026-10-02-acme-call-2.md
1 follow-up

Before you stop for the day, time against the piece of work it was for, in days or hours, whichever suits the work:

Terminal window
mavis time acme-reporting 1d "Export endpoint"

And mavis today the next morning, which now knows about the estimate.

Clients have a temperature: prospect, active, warm or cold. today will tell you when an active client has gone quiet for a fortnight with nothing on, or a warm one has not heard from you in a month. What it will never do is move them. Whether a relationship has cooled is a judgement, and a tool that quietly reclassified people would be wrong in exactly the cases that matter.

If you would rather not live in the command line, mavis tui puts the same thing on one screen, with forms for logging calls, time, new clients and engagements. It reloads every couple of seconds, so a note you add in Obsidian shows up on its own.

Getting paid

This is where most of the rules live, because this is where mistakes cost money.

At the end of a month, mavis invoice new acme --month 2026-09 drafts one line per piece of work: day and hourly time at its rate, and a retainer’s fee if it was running. Retainers, which bill in arrears, have their own shortcut:

Terminal window
$ mavis invoice retainers --draft
Drafted draft-globex-care-2026-08: 960.00 GBP
Drafted draft-globex-care-2026-09: 960.00 GBP
Check them, then: mavis invoice issue <draft>

A month is never billed twice. Ask for a month that is already on an invoice and mavis declines, and log time into a month you have already billed and it warns you that the time is not on it.

A draft is a table you can edit. mavis invoice issue then numbers it, freezes it and writes the PDF. Issued means frozen: edit an issued invoice by hand and mavis notices that the lines no longer add up to what was issued, and refuses to read it. The correction for a wrong invoice is a credit note, and mavis drafts those too.

When someone is late, mavis invoice remind INV-2026-001 writes the email for you, worded for how far the chase has got: a friendly nudge, then a follow-up that mentions it, then a final request with a deadline. It never sends anything. You send it, and tell mavis you did.

And because the UK is mandating e-invoicing from 2029, mavis can write each invoice as a Peppol e-invoice alongside the PDF, in the same format einvoicing.dev works with. It is off until you give it your Peppol ID, because almost nobody needs it yet.

What it will not do

mavis is not accounting software, and I do not want it to be. There is no bank feed, no reconciliation and no tax return. FreeAgent, or whatever your accountant uses, stays the books. mavis is the part before the books: the clients, the conversations, the time, and the invoice that comes out at the end.

Agents draft, you commit

The other reason it is files is that an agent can keep them too. mavis runs as an MCP server, and the repository is a Claude Code plugin, so you can say “I just got off a call with Acme, they want CSV exports first and I need to send a revised estimate by Friday” and have it logged properly, follow-up and all.

The line is drawn in the tools themselves. An agent can read everything, keep the records and draft invoices, quotes and reminders. It cannot issue an invoice, send a quote or mark anything paid, because those tools do not exist on the server. When a draft is ready, it hands you the command. Everything it creates is stamped by: agent, so you can always tell its notes from yours.

Where it went wrong

The first time I set it up for real, I ran mavis init from inside the mavis repository, which made the source code my records folder. Every client I added next would have been committed alongside the Go. Now init notices when it is inside a git repository and asks first, more loudly if that repository holds code. In a new folder it starts a repository for you instead, because your business records deserve version control as much as your code does.

Trying it

mavis runs on Linux and macOS:

Terminal window
curl -fsSL https://raw.githubusercontent.com/JustSteveKing/mavis/main/install.sh | sh
mavis init ~/business
mavis client add acme --name "Acme Ltd" --contact "Jo Bloggs"
mavis today

The project page has the rest, and mavis --help covers every command. If you want to try it without pointing it at anything real, make sandbox in a checkout builds a folder of sample clients to poke at.

If your freelance admin currently lives in your head, give it a folder instead.

Share

XLinkedIn

Related

Keep Reading

All posts →