mull turns a bank's CSV export into ledger-cli transactions,
predicting each transaction's account from an existing journal. It runs
unattended and entirely locally, and its options follow
reckon's, so it can replace
reckon --unattended.
One of the few things that has worked very well bouncing around th world has been using ledger to track my finances from various countries and currencies and not having to use terrible local accounting packages. I use it for my personal finances, business finances (much to my acountants' dismay), and things like the investment portfolio.
This was a huge time suck (despite its utiilty) until Andrew Cantino's excellent reckon gem, which automated a lot of the manual categorization away and vastly reduced my time to handle my finances monthly. Sadly, as my life has gotten more complex and country-jumping, the simple bayesian it was using would get "stuck" misclassifying things month after month, despite the fact the prediction should be changing. So, I made some changes to the predictors the technique uses, weighting more recent changes more heavily, and also using a nearest neighbour. The system makes a set of predictions and uses the one with the highest likelihood. It also does a slightly better job on totally new merchants and transactions for which no prior history is indicated (though basically an educated guess). In tests, it performed much better (at least on my actual data), than reckon did. I initially forked the Go code from into-ledger, though its quite a bit different than that initial attempt. Oh, and note this was one of my first attempts at "vibe coding" so go gentle.
mull reads your journal through ledger, so ledger
must be on your PATH. Homebrew installs it for you; elsewhere install it with
your package manager (apt install ledger, dnf install ledger, pacman -S ledger, ...).
brew install wakatara/tap/mulliganPrebuilt binaries for macOS, Linux, Windows, FreeBSD and OpenBSD (amd64, arm64,
386, armv6) are on the releases page,
along with checksums.txt. Unpack the archive and put mull somewhere on
your PATH:
tar -xzf mulligan_Darwin_arm64.tar.gz
sudo mv mull /usr/local/bin/Linux packages are published with each release too:
- DEB:
sudo apt install ./mulligan_*.deb - RPM:
sudo dnf install ./mulligan-*.rpm - APK:
sudo apk add --allow-untrusted mulligan_*.apk - Arch:
sudo pacman -U mulligan-*.pkg.tar.zst
go install github.com/wakatara/mulligan/cmd/mull@latestRequires Go 1.27 or later.
git clone https://github.com/wakatara/mulligan.git
cd mulligan
go test ./...
go build -o mull ./cmd/mullCheck the install with mull --version.
I called the program mulligan from the old-timey term for a do-over, since it
was from having to manually fix categorizations and "do overs" that I decided
to see if I could improve on reckon's sterling pedigree. I also like that it
allowed me to make the cli command mull.
You would use it like this. It needs the file you're processing, the account you want as its "base" for the csv export you have (like your visa liabilities category or main bank account), and an output file (not output file and it will just put everything on-screen, so good for trial runs.). A fuller example is below:
mull -f rbcVisa_Oct2026.csv -a "Liabilities:CAD:RBC Visa" \
-l 2026_perso.ledger -t account_tokens.yaml -c CAD --suffixed \
-o 2026_perso_oct.ledger
The learned-from journal is only read; the tool will refuse to write into it (you can append that yourself but it is a safety measure to avoid footguns). Every transaction in the CSV is processed. Ones already in the journal (same date and amount on the account) are skipped, as reckon does.
Output matches reckon's layout:
2026-10-02 Visa; 4514011813287057; DELIVEROO DUBLIN; 31.91 EUR @ 1.652773
Expenses:Food:Delivery
Liabilities:CAD:RBC Visa -52.74 CAD
Like reckon, the date, money and description columns are detected from the
data. The description is every column that is neither the date nor money,
joined with ; . Override any of them, numbering columns from 1:
| Option | Meaning |
|---|---|
--date-column 3 |
date column |
--money-column 7 |
one signed money column |
--money-columns 3,4 |
money out, money in (needed when a running balance column fools detection) |
--description-columns 5,6 |
description columns |
--ignore-columns 1,2 |
columns to blank out first |
--date-format "%d %b %Y" |
CSV date format; detected if omitted |
--ledger-date-format "%Y-%m-%d" |
output date format (default ISO) |
--contains-header N, --contains-footer N |
rows to skip; a header is detected if omitted |
-c CAD --suffixed |
currency, after the amount |
-i |
negate every amount |
Run with -v to see which columns were chosen.
- Account tokens (
-t, reckon's YAML format):/regex/ientries are hard rules, and the longest match wins. Plain strings are added as training text. - Known merchants: descriptions are cleaned (currency codes, FX rates, amounts, card numbers and city names removed), then matched against individual past transactions on character 3–4-grams, weighted by recency (90-day half-life).
- New merchants: a wide vote over past transactions that share words ("coffee"), then a Bayes model on the cleaned words.
- Otherwise
--default-into-account/--default-outof-account(Expenses:Unknown/Income:Unknown), or fail with--fail-on-unknown-account.
Backtest the predictors on your own journal, training on each month's history and scoring the next month:
go run ./cmd/ledger-eval -j 2026_perso.ledger [-t held_out_month.ledger [-show]]
Requires ledger on the PATH to read the journal (see Installation).
I personally use this with a personal agent I've trained that handles processing the csv exports from my various financial lives and conslidating everything as much as possible. The main goal was to reduce the wrong predictions so that this could take place even more from an automated perspective (and without having to use something like Claude or Gemini to process the financial transactions.)