pennywise

module
v0.0.0-...-676fa8b Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 5, 2026 License: MIT

README

Pennywise

Local-first personal expense tracking. Your money, your machine.

Pennywise is a single-tenant, self-hosted expense tracker that runs as a single static binary on your laptop, home server, Raspberry Pi, or NAS — in the same spirit as Plex, Jellyfin, Linkwarden, or Home Assistant. One install, one user, one SQLite file you can copy. No external services. No accounts somewhere else.

┌──────────────────────────────────────────────┐
│  Today        $24.50                         │
│  May 2026     $612.00                        │
│  2026         $4,127.50                      │
├──────────────────────────────────────────────┤
│  ▁▂▁▂▃▂▃▅▆▄▃▂▁▁▂▃▄▆▇▅▃▂▁▁▂▃▄▅▆ Last 30 days │
└──────────────────────────────────────────────┘

Why Pennywise

  • Local-first. All data lives in one SQLite file you control. Back it up by copying it. Move installs by copying it. Inspect it with sqlite3 whenever.
  • Self-hosted, single-tenant. No accounts on someone else's server, no subscriptions. One owner per install.
  • One binary, no runtime. Pure-Go SQLite, embedded templates and assets. No Postgres, no Redis, no Node.js — one Go binary.
  • Optional LLM + Telegram, both BYOK. Log expenses by texting a Telegram bot — "5000 fuel", "how much this month?", or even a snapshot of a receipt. Bring your own API key for OpenAI, Anthropic, Google Gemini, or xAI; Pennywise never proxies your traffic.

Quick start

Anywhere with Go 1.25+ installed (Linux, macOS, WSL, BSD):

go install github.com/Arthurobo/pennywise/cmd/pennywise@latest
pennywise init      # interactive: email, password, currency, timezone
pennywise start     # installs as an OS service + starts it

Then sign in at http://127.0.0.1:9002/login.

pennywise start doesn't just launch the server — it installs Pennywise as a real OS service so it auto-starts at every reboot and restarts itself if it crashes. One command, no service-file editing, no platform-specific knowledge required:

  • macOS → launchd LaunchAgent at ~/Library/LaunchAgents/com.pennywise.app.plist
  • Linux → systemd --user unit at ~/.config/systemd/user/pennywise.service
  • Windows → Task Scheduler task Pennywise triggered at user logon

None of these require admin / sudo — they're all user-scoped.

go install drops the binary in $GOBIN (default ~/go/bin). Make sure that directory is on your $PATH so pennywise is callable from anywhere.

Your data lives in ~/.pennywise/pennywise.db (or %USERPROFILE%\.pennywise\pennywise.db on Windows) by default; override via PENNYWISE_DATA_DIR.

Build from source

For contributors and anyone who'd rather inspect the code first:

git clone https://github.com/Arthurobo/pennywise.git
cd pennywise
make build
./pennywise init
./pennywise start

Requires Go 1.25+ and bash (the Tailwind CLI is downloaded on first build).

Features

Core (v1)
  • Single-owner, password-protected install. Bcrypt + DB-backed sessions, CSRF on every form, HttpOnly secure cookies.
  • Dashboard: today/month/year totals, last-30-days chart, recent expenses, active-ledgers grid with budget progress bars.
  • Expenses: full CRUD, filtering by date / ledger / category / search / amount range, HTMX-powered swaps, bulk select & bulk delete.
  • Soft delete with a Recently deleted trash view and per-owner retention window (default 30 days, max 365).
  • Ledgers (project / trip / budget envelopes) with detail page, breakdown charts, archive toggle.
  • Categories with archive (not delete) so historical data stays linked.
  • Reports: by-category and by-ledger breakdowns, time series, top-10 largest expenses.
  • CSV export.
  • All-currency picker (155 ISO 4217 codes), 30 common timezones.
Optional add-ons (v2)
  • Telegram bot (BYOB — bring your own bot): create a bot with @BotFather, paste the token in Settings, pair via /start <code>, then text expenses in plain language. See docs/telegram.md.
  • LLM expense parsing (BYOK — bring your own key): OpenAI, Anthropic, Gemini, or xAI. One model per provider, all on the cheap-fast tier. See docs/llm-providers.md.
  • Receipt image uploads in both the dashboard and the bot — drop a JPG or PDF receipt and Pennywise pre-fills (or logs) the expense. See docs/receipts.md.
  • Low-confidence prompts: ambiguous parses ("paid 3000") trigger a Yes / Edit / Cancel confirmation in the bot before anything is logged.

Tech stack

Layer Choice
Language Go 1.25+
HTTP router github.com/go-chi/chi/v5
Database SQLite (one file, embedded)
SQLite driver modernc.org/sqlite — pure Go, no CGO
SQL → Go sqlc from hand-written queries
Migrations github.com/golang-migrate/migrate/v4
Templating html/template (stdlib), embedded
Frontend HTMX 2.x + a tiny app.js
Charts Chart.js (vendored, not CDN)
CSS Tailwind via standalone CLI (no Node.js)
CLI github.com/spf13/cobra
Password hashing golang.org/x/crypto/bcrypt, cost 12
Session storage One sessions table, custom manager
LLM (optional) Direct REST calls — no SDK per provider
Telegram (opt.) Direct Bot API calls — no SDK

CLI reference

pennywise                  # run the server in the foreground (default)
pennywise serve            # explicit form of the default
pennywise init             # interactive first-run setup

# OS service lifecycle (Linux / macOS / Windows):
pennywise start            # install service + start; survives reboot
pennywise stop             # stop and remove from auto-start
pennywise status           # installed? running? PID, dashboard URL, logs

pennywise update           # `go install` latest + restart service to use it
pennywise uninstall        # permanently remove Pennywise (service + data + binary)
pennywise reset-password   # reset the owner password (revokes sessions)
pennywise version          # print build info

pennywise start writes a LaunchAgent (~/Library/LaunchAgents/com.pennywise.app.plist on macOS) or systemd --user unit (~/.config/systemd/user/pennywise.service on Linux). Re-running pennywise start after a binary upgrade refreshes the service definition automatically — no manual reload step.

DB migrations run automatically every time Pennywise opens the database — on every start, serve, init, and reset-password. There's no separate migrate command; you never need to think about schema versions.

Backup & restore

Everything is in $PENNYWISE_DATA_DIR/pennywise.db. To back up:

# Stop the server first for a perfectly consistent snapshot.
cp ~/.pennywise/pennywise.db ~/backups/pennywise-$(date +%F).db

WAL mode is on, so the DB survives kill -9 mid-write. See docs/backup-and-restore.md for hot-backup.

Documentation

Contributing

See CONTRIBUTING.md. PRs welcome. Open an issue first for anything bigger than a small fix.

License

MIT. See LICENSE.

Directories

Path Synopsis
cmd
pennywise command
Pennywise — single-tenant, self-hosted personal expense tracker.
Pennywise — single-tenant, self-hosted personal expense tracker.
internal
auth
Package auth implements password hashing, sessions, CSRF, and middleware.
Package auth implements password hashing, sessions, CSRF, and middleware.
cli
Package cli implements the cobra command tree for the pennywise binary.
Package cli implements the cobra command tree for the pennywise binary.
config
Package config loads runtime configuration from environment variables.
Package config loads runtime configuration from environment variables.
db
Package db wires up the SQLite connection and runs migrations.
Package db wires up the SQLite connection and runs migrations.
fuzzy
Package fuzzy provides the small string-matching helpers Pennywise uses to reconcile LLM-suggested category and ledger names against the user's actual records.
Package fuzzy provides the small string-matching helpers Pennywise uses to reconcile LLM-suggested category and ledger names against the user's actual records.
handlers
Package handlers contains the HTTP handlers grouped by feature.
Package handlers contains the HTTP handlers grouped by feature.
llm
Package llm wraps the four supported text-completion providers behind a single Provider interface and routes every call through Engine, which timeouts, logs, and reports back to the rest of the codebase.
Package llm wraps the four supported text-completion providers behind a single Provider interface and routes every call through Engine, which timeouts, logs, and reports back to the rest of the codebase.
models
Package models holds domain types and helpers that aren't database rows.
Package models holds domain types and helpers that aren't database rows.
server
Package server wires the HTTP router, middleware, and lifecycle.
Package server wires the HTTP router, middleware, and lifecycle.
setupseed
Package setupseed contains the first-run owner+categories seed logic shared by the web /setup handler and the `pennywise init` CLI command.
Package setupseed contains the first-run owner+categories seed logic shared by the web /setup handler and the `pennywise init` CLI command.
static
Package static embeds the CSS, JS, and image assets into the binary.
Package static embeds the CSS, JS, and image assets into the binary.
telegram
Package telegram drives the Telegram bot: long polling, message dispatch, inline-keyboard callbacks, and the supervisor that starts/stops the bot based on llm_config and telegram_config.
Package telegram drives the Telegram bot: long polling, message dispatch, inline-keyboard callbacks, and the supervisor that starts/stops the bot based on llm_config and telegram_config.
templates
Package templates parses and executes the embedded HTML templates.
Package templates parses and executes the embedded HTML templates.
testutil
Package testutil provides shared test helpers: ephemeral SQLite databases with migrations applied, a MockProvider for the LLM interface, and a FakeTelegram httptest server that captures bot API calls.
Package testutil provides shared test helpers: ephemeral SQLite databases with migrations applied, a MockProvider for the LLM interface, and a FakeTelegram httptest server that captures bot API calls.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL