Introduction
A folder of receipt photos tells you nothing until you add the numbers up. This post turns those images into a queryable ledger: OCR each receipt, store the vendor, date, and total in a local document store, then report total spend by category — all from the shell.
Install the packages
aux4 aux4 pkger install aux4/image-text aux4/repository aux4/2table
Step 1: read a receipt
aux4 image text runs OCR and prints the text:
aux4 image text whole-foods.png
WHOLE FOODS MARKET
Date: 2026-06-12
TOTAL 54.20
The vendor is the first line, the date and total are labeled — everything you need for a ledger entry.
Step 2: wrap extraction in a command
Rather than repeat that shell for every receipt, put the extraction in an .aux4 file as a receipt add command. Each set: line with a ! prefix runs a command and captures its output into a variable; the last line stores the record. Create .aux4:
{
"profiles": [
{
"name": "main",
"commands": [
{ "name": "receipt", "execute": ["profile:receipt"], "help": { "text": "Receipt commands" } }
]
},
{
"name": "receipt",
"commands": [
{
"name": "add",
"execute": [
"set:vendor=!aux4 image text ${file} | awk 'NR==1 {printf \"%s\", $0}'",
"set:date=!aux4 image text ${file} | awk '/Date/ {printf \"%s\", $2}'",
"set:amount=!aux4 image text ${file} | awk '/TOTAL/ {printf \"%s\", $NF}'",
"aux4 repository write receipts --data '{\"vendor\": \"${vendor}\", \"date\": \"${date}\", \"amount\": ${amount}, \"category\": \"${category}\"}'"
],
"help": {
"text": "OCR a receipt and store it in the receipts repository",
"variables": [
{ "name": "file", "text": "Receipt image path", "arg": true },
{ "name": "category", "text": "Spending category", "default": "Uncategorized" }
]
}
}
]
}
]
}
set:vendor=!<command> captures the command's output — so vendor, date, and amount come straight from the OCR text, then repository write stores them as one record. aux4 picks up the .aux4 in your working directory, so the command is ready:
aux4 receipt add whole-foods.png --category Groceries
724AB72A-DF21-4F8C-9BF4-6733D705D277
It returns the stored record's id. Run it for the rest of the pile, tagging each with a category:
aux4 receipt add trader-joes.png --category Groceries
aux4 receipt add shell.png --category Fuel
aux4 receipt add starbucks.png --category Coffee
Step 3: build the report
Now the receipts are data. List the whole ledger with aux4 2table:
aux4 repository read receipts | aux4 2table vendor,date,amount,category
vendor date amount category
WHOLE FOODS MARKET 2026-06-12 54.2 Groceries
TRADER JOES 2026-06-20 32.1 Groceries
SHELL STATION 2026-06-15 41 Fuel
STARBUCKS 2026-06-18 8.75 Coffee
Filter to one category with a SQL-like expression:
aux4 repository find receipts --expr "category = 'Groceries'" | aux4 2table vendor,amount
vendor amount
WHOLE FOODS MARKET 54.2
TRADER JOES 32.1
And roll it up — grand total and per-category totals — with a touch of jq for the arithmetic:
aux4 repository read receipts | jq '[.[].amount] | add'
136.05
aux4 repository read receipts \
| jq -c 'group_by(.category)[] | {category: .[0].category, total: (([.[].amount]|add)*100|round/100), count: length}'
{"category":"Coffee","total":8.75,"count":1}
{"category":"Fuel","total":41,"count":1}
{"category":"Groceries","total":86.3,"count":2}
Conclusion
A stack of images becomes a spending report: your own aux4 receipt add command OCRs and stores each receipt with aux4 image text and aux4 repository, and aux4 2table prints the ledger. Wrapping the extraction in an .aux4 command turns a fiddly shell snippet into one reusable verb — and because every receipt is now a queryable record, changing the report is just a different query, by month, by vendor, or by category.