The Transactions table: filters across the top, then rows showing date, account, issuer, the raw bank label, category and amount.

Personal accounting that just feels right.

A personal-finance app for one person's own accounts. Import bank statements, curate the raw rows into issuers and categories, and read back where the money went.

  • Self-hosted. The whole state is one SQLite file on a machine you control.
  • Single-user. Built for its author's own bank exports — not a product, and nothing to join.
  • Curated, not automatic. Matching rules assign issuers; categories come off the issuer unless you override the row.

Get the code on GitHubOpen the app

What is mamen

Eight surfaces, and the path a bank row takes through them: imported, given an issuer and a category, then counted.

Transactions
The curation surface: a filterable table of every imported row. Give one an issuer, a category or a note, hold it out of the spend totals, or group several into a single operation.
Transfers
Movements between your own accounts. mamen suggests the debit and credit pairings and you confirm or dismiss them; a confirmed pair is held out of spend.
Recap
Where the money went over a month, a calendar year or all time, broken down by issuer and by category. Spend only, so transfers and excluded rows are reported beside the totals rather than inside them.
Import
Upload a statement, preview every row it will create, and commit. Nothing reaches the database until you do.
Accounts
The accounts you import into, and a grid of which months have landed for each.
Issuers
Who money goes to and comes from. Each one carries a default category, an image, and matching rules that assign it to new rows at import time and to old ones retroactively.
Categories
A tree of any depth. Only leaves are assignable and folders total their children; a row takes its category from its issuer unless you override it.
Two statement formats
CSV, parsed in the browser, with one bank's parser shipping and a module per bank after it. PDF, extracted by whichever AI provider you picked in Settings, staged in a temp directory and deleted afterwards.

Run it yourself

mamen is not hosted anywhere for you. Clone it, point it at your own bank exports, and it runs on your machine against one SQLite file.

  • Bun 1.3.4 The runtime, the package manager and the test runner. The repo pins its version.
  • portless (optional) Fronts each dev server at a name instead of a port, which is what the start command expects. Without it, the same servers run directly on the ports listed below.
  • The claude CLI (optional) Needed by PDF import on its default provider, which hands it the statement. Pick a hosted vendor in Settings instead, and nothing needs it. Every other part of the app runs without it either way.
  1. Clone and install

    Bun installs every workspace in the monorepo — the API, the web app and this page.

    git clone https://github.com/lucasriondel/mamen.git
    cd mamen
    bun install
  2. Write the encryption key

    The API reads its environment from a file beside it. Everything in there has a working default except the key that encrypts the AI provider credentials you paste into Settings, which has none on purpose.

    # packages/api/.env
    TOKEN_ENCRYPTION_KEY=<64 hex characters — openssl rand -hex 32>
  3. Start it

    One command runs every dev server through Turborepo. The database is created and migrated on first boot, seeded with a base category tree. The servers come up behind portless, each fronted at a name instead of a port; without it installed they run directly, at the second address listed below.

    bun dev
  • web https://mamen.localhost/app/ http://localhost:5070/app/ The app itself, under the prefix it is served at in production too.
  • API https://api.mamen.localhost http://localhost:5500 The HTTP API, with its Scalar docs and the OpenAPI spec it emits.
  • landing page https://landing.mamen.localhost http://localhost:5080 This page, as the deployed site serves it at its root.
  • TOKEN_ENCRYPTION_KEY The one setting with no default, because a default would be a published encryption key. The app starts without it and every feature but AI credentials works.

How to contribute

The repository is public because there is no reason for it not to be, not because it is looking for users. There is no roadmap, no release cadence and no support promise, and the maintainer is one person.

Bug reports are welcome, and so is a parser for another bank's export — that is the one extension point the code is deliberately shaped for. Read the guide before spending time on anything larger.

Contributing guide