Skip to content

matyaslustig/spendee-cli

Repository files navigation

spendee-cli

A headless, deterministic CLI that learns categorization rules from your Spendee history, fixes a selected period, and links transfers between your own wallets.

No LLM, local database, or browser is required. Data is fetched into memory for each run and discarded when the command exits.

Warning

The Spendee adapter uses the web application's Firebase/Firestore backend. It is unofficial, may break without notice, and is not endorsed by Spendee. Review Spendee's terms and use it at your own risk.

What it does

  • Learns only from transactions before the requested period.
  • Requires at least 3 observations and 80% category agreement by default.
  • Lets the latest 12 matching transactions override stale older categorization when they meet the same threshold.
  • Shows unanimous 2/2 matches as review-only suggestions; they are never auto-written.
  • Checks every transaction in the period, including already-categorized ones.
  • Leaves an already-correct category untouched.
  • Keeps Spendee's category when no rule is strong enough by default.
  • Can instead assign unknown expenses and incomes to configured fallback categories.
  • Detects same- and cross-currency transfers between selected wallets.
  • Compares cross-currency transfers through Spendee's normalized USD value.
  • Never matches transfers more than one day apart by default.
  • Previews everything unless --commit is explicitly supplied.

Install

Python 3.11 or newer is required.

python3 -m venv .venv
source .venv/bin/activate
python -m pip install -e ".[dev]"

The installed command is spendee.

Authenticate

Only email/password Spendee accounts are currently supported. Authentication is fully headless.

export SPENDEE_EMAIL="you@example.com"
export SPENDEE_PASSWORD="your-password"
spendee auth check

For VPS secret mounts, point to a file containing only the password:

export SPENDEE_PASSWORD_FILE=/run/secrets/spendee-password

Firebase ID tokens are refreshed automatically during long runs. Credentials and tokens are never written by the CLI.

Configure

Create a config and edit the wallet list. Wallet IDs are preferable because names can change.

spendee config init
spendee wallets list
spendee config validate

Configuration is resolved from --config, then SPENDEE_CONFIG, then config/config.yaml, then $XDG_CONFIG_HOME/spendee/config.yaml.

wallets:
  include: [] # Empty selects all visible wallets; IDs or exact names are accepted.
  exclude: []

categorization:
  min_observations: 3
  min_agreement: 0.80
  recent_observations: 12
  suggestion_min_observations: 2
  suggestion_min_agreement: 1.00
  fallback: spendee # spendee | other
  other_expense_category: others
  other_income_category: income
  prefer_wallet_rules: true

transfers:
  max_days: 1
  fx_tolerance_pct: 3.0
  auto_score: 80
  review_score: 60

CLI options override the file where an override is available.

Set recent_observations or suggestion_min_observations to 0 to disable that layer.

Categorize a month

Preview:

spendee categorize --from 2026-06-01 --to 2026-06-30

Write the previewed category changes and automatic transfer matches:

spendee categorize --from 2026-06-01 --to 2026-06-30 --commit

Limit a run to selected wallets:

spendee categorize \
  --from 2026-06-01 \
  --to 2026-06-30 \
  --wallet 00000000-0000-4000-8000-000000000000

Use --json for automation. A non-zero exit means at least one read or write failed.

Suggestions are included in the preview as SUGGESTION, but --commit writes only confident UPDATE decisions.

Record a reviewed correction

Preview an explicit correction by transaction ID and exact category name or ID:

spendee transactions correct TRANSACTION_ID --category Transport

Write it after review:

spendee transactions correct TRANSACTION_ID --category Transport --commit

The correction is written directly to Spendee. It becomes training history for later periods without a local database.

Other commands

spendee doctor
spendee wallets list [--json]
spendee categories list [--json]
spendee transactions list [--from DATE] [--to DATE] [--wallet ID]
spendee transactions correct TRANSACTION_ID --category NAME_OR_ID [--commit]
spendee transfers detect --from DATE --to DATE [--commit]
spendee rules inspect --before DATE
spendee config validate

Categorization algorithm

For a target period [from, to]:

  1. Transactions dated before from are trusted history. Pending items, transfers, and uncategorized items are excluded from training.
  2. History is grouped by direction (expense/income) and normalized merchant. Wallet-specific rules are preferred, with a global rule as fallback.
  3. The most frequent category wins only when the group meets both thresholds. Ties never produce a rule.
  4. When there is more history than the recent window, a qualifying rule from the latest matching transactions overrides the all-time rule. This adapts to category changes without forgetting older evidence.
  5. A unanimous group meeting only the suggestion threshold is shown for review and is never auto-written.
  6. Every non-transfer target transaction is evaluated. Matching existing categories are kept; differing categories are proposed for update.
  7. Unknowns retain their current Spendee category, or use direction-specific other categories when configured.

Transfer candidates are evaluated first so they cannot be recategorized as ordinary income or spending. Pairing requires different selected wallets, opposite signs, a maximum one-day date difference, and either an exact same-currency amount or normalized USD values within the configured FX tolerance. Equally strong pairings are marked for review and never auto-linked.

Architecture

The package has deliberately small boundaries:

  • spendee_client.py: authentication and remote Firestore adapter
  • models.py: provider-neutral wallet, category, and transaction models
  • categorization.py: pure in-memory rule learning and decisions
  • transfers.py: pure transfer candidate matching
  • service.py: fetch/evaluate/commit orchestration
  • cli.py: presentation and exit codes

The pure modules do not know about HTTP, Firestore, Typer, or persistence. This makes them straightforward to test and replace with another data provider.

Development

python -m ruff check .
python -m pytest -q
python -m build

Never use real financial exports in tests or issues. See SECURITY.md and CONTRIBUTING.md.

License

MIT. See LICENSE.

About

Headless, deterministic Spendee CLI for transaction categorization and own-wallet transfer matching.

Topics

Resources

Contributing

Security policy

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages