The dura command line

DevelopersUpdated

dura runs your household's money from a terminal. It's one Ruby file using only the standard library, so it installs anywhere Ruby 3.2 or newer runs, including the machine your AI agent works on.

Install

mkdir -p ~/.local/bin
curl -fsSL https://dura.muchiround.com/cli/dura -o ~/.local/bin/dura
chmod +x ~/.local/bin/dura
dura version

Log in

Make a token under Apps and agents on your account screen, then:

dura login --token -      # paste the token, then Ctrl-D
dura whoami

Or sign in with your email and password (it asks for the password): dura login --email you@example.com. The token and server are saved in ~/.config/dura/config.json, readable only by you. The environment variables DURA_TOKEN, DURA_URL and DURA_HOUSEHOLD override the file, which is the simplest setup for agents. In more than one household? dura use "Moyo family". Your private pocket is never the default: reach it with dura --household Private table.

Everyday commands

dura table                                    # balances, budgets, IOUs
dura spend groceries 74.99 Peanut butter      # the rest of the line is the note
dura spend airtime 12 "Data bundle" --from transport
dura spend groceries 230 --receipt slip.jpg --date 2026-10-08
dura cash-in 200 --electronic 210 --source payoneer \
  --split groceries=120 --split transport=50 --split piggy=30
dura move groceries school 10 --note "Uniform fund"
dura allowance kuda 25 October --from fun     # lands in Kuda's private pocket
dura settle airtime transport                 # the whole IOU, or add an amount
dura count gas 10 --reason lost
dura entries --envelope groceries --month 2026-10
dura edit 18 --amount 70.50
dura undo 22
dura month 2026-09

Also: envelopes, envelopes add Pharmacy --budget 40 --emoji 💊, envelopes edit pharm --budget 45, envelopes archive pharmacy, envelopes order gas groceries transport airtime (the whole list, top to bottom; everyone in the household sees the same order), groups, groups add Bills, groups order bills kids fun, groups delete bills (its envelopes become ungrouped, never archived), envelopes edit rent --group bills or --no-group, envelope transport, ious, sources, members, tokens, logout --revoke, and dura api GET /households for anything else. dura help spend shows a command's options.

Loans from the piggy

dura loans ask 2000 --months 12 New phone            # a personal loan: lands in your private pocket
dura loans ask 300 --months 3 --joint --into groceries Date night
dura loans ask 2000 --months 12 --rate 10 Phone      # flat interest, only if you both agreed it
dura loans                                           # pending, open, behind, closed (--status behind)
dura loan phone                                      # the plan and the history
dura loans approve phone --note "Go for it"          # another member; the cash moves now
dura loans decline phone --note "Not this month"
dura loans cancel phone                              # your own pending request
dura loans repay phone                               # what's due now (personal: from your pocket)
dura loans repay date 100 --from groceries           # joint: from a household envelope
dura cash-in 600 --split groceries=500 --repay date=100
dura loans skip phone
dura loans reschedule phone 6 --start 2026-12-01
dura loans forgive phone 200 --note "Birthday"

A loan is its id or a unique part of its purpose. Nothing leaves the piggy until someone else approves, and you can borrow up to what the piggy holds in cash. The total is fixed when you ask: skipping or a new plan never adds to it. dura table shows what's lent out and which repayments are due; dura undo on a repayment's entry reopens it.

Savings groups (mukando)

dura mukando                                        # your groups, the cycle, the pool
dura mukando show                                   # this month, my position, where the money is, the rules
dura mukando position
dura mukando rotation                               # every month's recipient; swaps waiting
dura mukando book --status pending
dura mukando places
dura mukando loans
dura mukando waiting                                # what's waiting for you to confirm
dura mukando confirm 17                             # people only: your own word
dura mukando dispute 17 "I paid $100, not $120"     # people only
dura mukando swap chipo                             # ask to swap payout months

# the keeper
dura mukando record contribution tsitsi --month 1 --into "chipo cash"
dura mukando record payout --month 1 --from "chipo cash"
dura mukando record loan rudo 60 --from "chipo cash" --check   # the rate lines; nothing saved
dura mukando record repayment rudo 20 --into "chipo cash"
dura mukando record handover 200 --from "chipo cash" --to ecocash
dura mukando places add "Rudo — EcoCash" --holder rudo --kind account
dura mukando reverse 41 --note "Wrong amount"
dura mukando share-out finalise

A savings group keeps its own book, apart from your household. Pick it with --group (an id or part of its name) when you're in more than one. Members, places and loans take an id or a unique part of the name. The keeper records; each member confirms what involves her, and nothing counts until she does. confirm, dispute, swap accept, keeper accept, record write-off and share-out finalise are people's own decisions, never an agent's. Starting a group, inviting, joining and starting a cycle happen in the app.

Receipts

dura scan slip.jpg --wait       # upload, then wait for the suggested envelopes
dura receipt 12                 # check the lines
dura confirm 12                 # record them as suggested
dura confirm 12 --line "Bread=24.99=groceries" --line "Bus card=5=transport" --from groceries
dura discard 12

Nothing is deducted until you confirm. A line with an empty envelope (--line "Gift wrap=2=") is left out.

Names and amounts

  • Amounts are in your currency, not cents: 74.99, R74.99 and $74.99 all work.
  • Envelopes and funding sources take an id or a unique part of the name: groc, airtime, piggy. A partial name needs at least three letters, and a name that matches two envelopes is refused rather than guessed, because money comes out of whatever it picks.

Output for agents and scripts

On a terminal you get readable lines. When the output is piped (as it is for an agent) you get the API's JSON: {"data": ...} on success and {"error": {"code", "message"}} on failure. --json and --human force either.

  • Exit code 0: done. 1: the API refused or couldn't be reached. 2: the command was wrong.
  • Every write sends an Idempotency-Key, and dropped connections or 502/503/504 answers are retried with the same key. Pass your own with --idempotency-key to make a retry from a separate run safe too.

More for developers