Commit Graph
7 Commits
Author SHA1 Message Date
nikolaandClaude Opus 5 83d842d92e Let rules carry a note
A glob like *4412* says nothing about why it exists or who it catches, and
six months later neither does memory. Rules get an optional note: free text
that never takes part in matching, written as a TOML key rather than a
comment so it survives a round trip and can be shown back.

The rule builder grows a fourth field for it and the rules screen a last
column. Four fields spaced out are taller than a short window has room for,
so the form now drops its blank lines and then the hints on unfocused fields
before anything would run off the bottom.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 23:23:29 +02:00
nikolaandClaude Opus 5 73fefea96d Complete account and tag names in the rule builder
Both fields are free text, and a slug or tag that is slightly wrong produces
a rule that silently catches nothing — or a second, near-identical tag. They
now complete against what already exists: accounts from the folders on disk
and the index, tags from every tag in use plus any named in rules.toml, so a
tag is completable from the moment a rule mentions it.

The completion is ghosted after the cursor and never committed until it is
accepted, so inventing a new tag still works. tab takes it and moves focus
only when there is nothing left to complete; ctrl+n/ctrl+p cycle an ambiguous
prefix, and the hint under the box says how many candidates are left.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 23:06:31 +02:00
nikolaandClaude Opus 5 c2eba53fd4 Add a rules screen that finds and removes dead rules
Nothing showed whether a rule was still earning its place. The new screen, on
5, lists every rule in file order with the number of transactions it claims,
marking those that claim none.

The count comes from Engine.Usage, which counts by first match, so a rule
shadowed by an earlier one reports zero even though its glob matches. That is
the case worth catching: such a rule looks correct in isolation and can never
fire.

d removes the selected rule and p removes every unused one, each behind a y/n
confirmation since this rewrites a hand-maintained file. config.DeleteRules
edits rules.toml textually rather than re-serialising the parsed rules, so
comments and layout survive; a comment directly above a rule goes with it,
while one separated by a blank line is left as a heading. The result is
re-parsed before it replaces the file.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 02:02:01 +02:00
nikolaandClaude Opus 5 aeca203855 Add a rule builder screen with a live glob preview
Writing a glob by hand meant guessing what it would catch, then running retag
to find out. The new screen, on 4, puts the glob, account and tag fields on
the left and every still-untagged description on the right, sorted
alphabetically and grouped by description with an occurrence count.

Matches are marked as the glob is typed, along with a count, so the effect of
a rule is visible before it is written. Enter appends it to rules.toml via
config.AppendRule, reloads the engine from disk and retags, so the rows it
caught leave the list immediately.

Rules are appended rather than prepended, keeping the precedence of anything
already in the file. Since the preview only lists transactions no existing
rule has tagged, it reflects that precedence for free.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 01:41:24 +02:00
nikolaandClaude Opus 5 9ba3fe8fbe Explain empty TUI views instead of showing a bare table
An account folder is only registered in the index by an import, so a freshly
configured account.toml produced an empty accounts table with nothing to say
why or what to do about it.

Each view now explains its own emptiness and names the key that resolves it,
distinguishing configured-but-not-imported from no-folders-at-all, and
telling apart a filter that matched nothing from having no data.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 01:04:14 +02:00
nikolaandClaude Opus 5 cf1753fb0a Find the data root via the XDG config directory
Running the tool from anywhere required either a --root flag or an exported
MONEY_ROOT. Read the data root from $XDG_CONFIG_HOME/money/config.toml
(~/.config/money/config.toml) instead, so it can be configured once.

Resolution order is --root, then $MONEY_ROOT, then the config file, then
~/money. A leading tilde in the config file is expanded, since no shell has
done it for us, and relative paths are made absolute.

The new "money config" subcommand prints the resolved root and which rule
chose it, which is the first thing to check when the tool reads the wrong
directory.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 00:40:59 +02:00
nikolaandClaude Opus 5 b0026c5a79 Add money: statement-driven personal finance tracker
A data directory holds one folder per account. Statements dropped into
those folders are parsed into a rebuildable SQLite index, categorised by
ordered glob rules in rules.toml, and browsed or hand-tagged in a Bubble
Tea TUI. Movements between the user's own accounts are marked as
transfers by the same rules and excluded from spending totals.

Manual tags and transfer marks are stored separately from the rule-derived
ones and always win, so editing rules.toml and re-running retag never
destroys hand edits.

Parsers are pluggable. Three are ported from the Python extractors they
replace -- nlb and traderepublic read PDFs via pdftotext -layout, revolut
reads the CSV export -- alongside a configurable-column CSV parser and a
cmd parser that shells out to an external script.

Both ports fix two latent bugs in the originals: the sign character class
rejected the typographic minus U+2212 that some PDF fonts emit, and NLB's
hardcoded continuation indent broke when pdftotext compressed runs of
spaces, so the threshold is now measured from the description column.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 00:37:20 +02:00