Moneymoor

NAME

App::Moneymoor - YNAB-style envelope budgeting: a derivation engine over an encrypted SQLite file, and a terminal UI that drives it.

SYNOPSIS

Run the app:


moneymoor                      # or: raku -I lib bin/moneymoor
moneymoor --version
MONEYMOOR_HOME=/tmp/demo moneymoor    # a throwaway data home

Or use the engine directly:


use MacOS::NativeLib <sqlcipher>;   # macOS only — see PORTABILITY
use App::Moneymoor;
use App::Moneymoor::DB;
use App::Moneymoor::Service::Workspace;
use App::Moneymoor::Model::Account;
use App::Moneymoor::Model::Category;
use App::Moneymoor::Model::Transaction;
use App::Moneymoor::Model::Split;

say App::Moneymoor.version;

my $db = App::Moneymoor::DB.new(:db-path("$*HOME/.moneymoor/budget.db"));
$db.connect('correct horse battery staple');

my $ws = App::Moneymoor::Service::Workspace.new(:$db);

my $current = $ws.accounts.create(App::Moneymoor::Model::Account.new(
    name => 'Current Account', type => 'cash'));
my $food = $ws.categories.create(App::Moneymoor::Model::Category.new(
    name => 'Groceries'));
my $rta = $ws.categories.rta-category;

# £1,800 in:
$ws.transactions.create(
    App::Moneymoor::Model::Transaction.new(
        account-id => $current.id, date => '2026-03-01', amount => 180000),
    splits => [App::Moneymoor::Model::Split.new(
        category-id => $rta.id, amount => 180000)],
);

# Give it a job. Budgets are keyed by period start; under the default
# calendar-month scheme that is the first of the month.
$ws.set-assigned('2026-03-01', $food.id, 40000);

my $view = $ws.budget(through-period => '2026-03-01');
say $view.rta('2026-03-01');                            # 140000
say $view.category('2026-03-01', $food.id).available;   # 40000

$db.disconnect;

DESCRIPTION

Moneymoor is an envelope budget: every pound you have gets a job before you spend it. It is the "give every pound a job, budget only money you actually hold, roll the rest forward" school of budgeting — the same model YNAB popularised — implemented as a Raku library over an encrypted SQLite (SQLCipher) file.

The engine came first and is still usable on its own — everything under Model, Gateway, Service and DB is a library with no opinion about how you look at it. On top of that sits a Selkie terminal UI, bin/moneymoor, which is what most people will actually use — see #THE TERMINAL UI below.

WHAT IT IS BUILT ON

  • Facts in, everything else derived. The database stores only what you authored: accounts, categories, transactions with their splits, and per-period assignments. Balances, activity, available, Ready to Assign and credit-card payment moves are recomputed on demand by a pure function. Nothing derived is stored, so nothing derived can drift.

  • The master invariant. Envelopes partition cash: Ī£ available + RTA == Ī£ cash-account balances. Its general form (with future assignments and uncovered card spending) is checked for every period by BudgetView.invariant-errors and asserted after every operation by the property test suite.

  • Integer pence. No floats, no Rats, anywhere. A budget that has to prove an equality cannot afford lossy addition.

  • Encrypted at rest. One SQLCipher file, keyed on connect.

THE PIECES

  • App::Moneymoor::DB — SQLCipher connection, migrations (additive ones replayed on every connect, transforming ones gated on a schema revision), transactions.

  • App::Moneymoor::Model::* — attribute-only row classes.

  • App::Moneymoor::Gateway::* — the SQL, and the invariants that have to hold before a row is written (splits sum to their transaction, transfers come in pairs, every credit account owns a payment envelope).

  • App::Moneymoor::Service::Budget — the pure derivation. Its Pod is the specification of the budgeting semantics, with worked numbers for each of the four rules.

  • App::Moneymoor::Service::Workspace — gateways in, derived budget out. Owns the budget's period scheme and is the only layer that reads the clock.

  • App::Moneymoor::Service::Target — what an envelope's target asks for in the period you are looking at. Pure, and the only module that knows what the three target kinds mean.

  • App::Moneymoor::Util::Period — what a budget period is: a calendar month, a month anchored on payday, or an every-N-weeks pay window. Pure, and the only module that knows.

  • App::Moneymoor::Util::Money — pence ↔ "Ā£12.34".

WHAT IT DOES NOT DO

Scheduled transactions, CSV or bank imports, multi-currency, incremental rollup caching, and net worth over time (which needs a per-month, per-account balance derivation the engine does not have yet).

Targets do exist — see the Budget tab below — but note where they live: on the category row, read by the view layer, and invisible to Service::Budget. There are three kinds of them — refill to a level, set aside an amount each period, and reach an amount by a period, the last with a milestone schedule and optional repeat — and all three are derived by Service::Target from figures the engine produced for its own reasons. The engine still derives money from facts alone, and a target is not a fact about money that has moved. The Budget tab's envelope editor sets all three kinds.

THE TERMINAL UI

bin/moneymoor launches it; --version and --help are the only things it will do without a terminal. Everything else — creating a budget, assigning money, entering transactions, reconciling a statement — happens inside, from the keyboard.

Logging in

Budgets live in the data home, ~/.moneymoor/, one encrypted *.db file each. Set MONEYMOOR_HOME to put them somewhere else; the config file, the budget list and the error log all move together, which is what makes a throwaway run safe.

The first screen lists the budgets it found and asks for a passphrase, or — with none to list, or on Ctrl+N — offers to create one. That passphrase is the encryption key: there is no reset, no recovery question and no copy of it anywhere. A wrong one is reported differently from a wrong file, because they are different mistakes.

Inside

Three tabs, 1 / 2 / 3 (also Ctrl+1 / Ctrl+2 / Ctrl+3 on terminals that can send it):

  • Budget — the envelope grid for one period, grouped, with Ready-to-Assign above it and a detail rail beside it that shows exactly how an envelope's available figure was arrived at. a assigns, m moves money between envelopes, x explains a derived number, [ and ] change period. Give an envelope a target in its editor — refill, set-aside, or a goal to reach by a date — and the grid gains a Target column, the rail says how much is still to fund, the assign field takes =450 ("make it Ā£450") and a bare = ("fund this envelope's plan for this period"), and f funds every underfunded envelope at once in a single write.

  • Accounts — the ledgers on the left, the register on the right. n adds a transaction (with splits), t a transfer, c cycles uncleared → cleared → reconciled, Ctrl+R starts a reconciliation against a statement balance.

  • Reports — the period's cash flow, and where the money went.

Ctrl+H lists the keys for whatever has focus; the bottom line always shows the important ones. Ctrl+G opens diagnostics — the derivation's warnings, invariant errors and a digest fingerprint safe to paste into a bug report. Ctrl+O opens settings: eleven palettes and a glyph tier (plain Unicode or Nerd Font), applied live and remembered in ~/.moneymoor/config.json.

The UI's pieces

  • App::Moneymoor::UI — the entry point: app construction, the login handshake, the hand-off to the main screen.

  • App::Moneymoor::Screen::* — Login, the Main shell, and one controller per tab.

  • App::Moneymoor::StoreHandlers — the state shape, and the single effect every mutation in the app funnels through.

  • App::Moneymoor::View::* — the row and span builders, pure and unit-tested; no widget code decides what a number says.

  • App::Moneymoor::Theme / Themes / Service::Icons — palette and glyph tiers.

PORTABILITY

The sqlcipher shared library has to be loadable by NativeCall. On macOS, <use MacOS::NativeLib <sqlcipher>;> before the first use App::Moneymoor::DB points NativeCall at the Homebrew install. On Linux the system loader finds a distro sqlcipher by itself only when its soname is libsqlcipher.so.0; modern Debian and Ubuntu (24.04+) package soname 1, which DBIish's built-in lookup refuses — point DBIISH_SQLCIPHER_LIB at the library (/usr/lib/x86_64-linux-gnu/libsqlcipher.so.1) and it is loaded verbatim. On Windows the DLL just has to be on PATH. Nothing else in the distribution is platform-specific, and the pure engine (Service::Budget, Util::Money, every Model) needs no native library at all.

AUTHOR

Matt Doughty <[email protected]>

COPYRIGHT AND LICENSE

Copyright 2026 Matt Doughty

This library is free software; you can redistribute it and/or modify it under the Artistic License 2.0.

App::Moneymoor v0.4.2

YNAB-style envelope budgeting: a derivation engine

Authors

  • Matt Doughty

License

Artistic-2.0

Dependencies

DBIish:ver<0.6.7>:auth<zef:raku-community-modules>Notcurses::Native:ver<0.6.5+>:auth<zef:apogee>Selkie:ver<0.16.0+>:auth<zef:apogee>JSON::Fast:ver<0.19>:auth<cpan:TIMOTIMO>MacOS::NativeLib:ver<0.0.6>:auth<zef:lizmat>

Test Dependencies

Provides

  • App::Moneymoor
  • App::Moneymoor::Config
  • App::Moneymoor::DB
  • App::Moneymoor::Gateway::Account
  • App::Moneymoor::Gateway::Assignment
  • App::Moneymoor::Gateway::Category
  • App::Moneymoor::Gateway::Payee
  • App::Moneymoor::Gateway::Transaction
  • App::Moneymoor::Handlers::Boot
  • App::Moneymoor::Model::Account
  • App::Moneymoor::Model::Assignment
  • App::Moneymoor::Model::Category
  • App::Moneymoor::Model::CategoryGroup
  • App::Moneymoor::Model::Payee
  • App::Moneymoor::Model::Split
  • App::Moneymoor::Model::Transaction
  • App::Moneymoor::Screen::Accounts
  • App::Moneymoor::Screen::Budget
  • App::Moneymoor::Screen::Login
  • App::Moneymoor::Screen::Main
  • App::Moneymoor::Screen::Main::Keybinds
  • App::Moneymoor::Screen::Main::Modals
  • App::Moneymoor::Screen::Main::Subscriptions
  • App::Moneymoor::Screen::Reports
  • App::Moneymoor::Service::Budget
  • App::Moneymoor::Service::Icons
  • App::Moneymoor::Service::Target
  • App::Moneymoor::Service::Workspace
  • App::Moneymoor::StoreHandlers
  • App::Moneymoor::Theme
  • App::Moneymoor::Theme::Catppuccin
  • App::Moneymoor::Theme::Dracula
  • App::Moneymoor::Theme::Everforest
  • App::Moneymoor::Theme::Gruvbox
  • App::Moneymoor::Theme::Kanagawa
  • App::Moneymoor::Theme::Monokai
  • App::Moneymoor::Theme::Nord
  • App::Moneymoor::Theme::OneDark
  • App::Moneymoor::Theme::RosePine
  • App::Moneymoor::Theme::Solarized
  • App::Moneymoor::Theme::TokyoNight
  • App::Moneymoor::Themes
  • App::Moneymoor::UI
  • App::Moneymoor::Util::Money
  • App::Moneymoor::Util::Period
  • App::Moneymoor::View::BudgetRow
  • App::Moneymoor::View::EmptyState
  • App::Moneymoor::View::HintBar
  • App::Moneymoor::View::InspectorPane
  • App::Moneymoor::View::ModalChrome
  • App::Moneymoor::View::RegisterRow
  • App::Moneymoor::View::ReportRow
  • App::Moneymoor::Widget::BannerBar
  • App::Moneymoor::Widget::BootProgressModal

The Camelia image is copyright 2009 by Larry Wall. "Raku" is a trademark of the Yet Another Society. All rights reserved.

Built with Podlite — the markup and publishing tools behind this site.