Kindling

Self-hosted personal finance. Your money, your hardware, your model.

License: MIT Next.js 15 SQLite via node:sqlite Bank data via Plaid Bring your own LLM Private by default

---

Kindling overview (Plaid Sandbox data): ember-lit net worth, insights, 90-day cash-flow forecast

The night-hearth theme on Plaid Sandbox data. Every screenshot in this repo is fake money; real financial data never reaches git.

Real bank data via [Plaid](https://plaid.com), everything else on hardware you own, **~$0/year**. A Copilot Money / Monarch replacement built around one idea: **your financial data, and the AI that reads it, never have to leave your network.** Kindling is the daily-money companion in a small family of fire-themed finance apps (its sibling **EmberPlan** handles FIRE planning). Kindling is what you tend every day so the ember catches. ๐Ÿ”ฅ ## Why it exists Hosted finance apps cost ~$100/yr, ship your transactions to their cloud, and increasingly pipe them through third-party LLMs. Kindling flips all three: Plaid's free tier covers a typical household (10 institutions), the app runs on any always-on box you already have, and the AI layer points at whatever OpenAI-compatible endpoint you choose. Run a local model and nothing leaves your network. ## What you get - ๐Ÿฆ **Plaid sync:** banks + brokerages, cursor-based transaction sync, balances, investment holdings - ๐Ÿท๏ธ **Categorization that's yours:** Plaid's taxonomy โ†’ *your editable rules* โ†’ an LLM tail for the ambiguous remainder, with write-back so good calls become rules. To-Review inbox, one-tap AI suggestions, propagation with undo - ๐Ÿ” **Subscriptions:** Plaid Recurring + an in-house detector; price-hike / unused / trial flags; what-if-cancel (tick rows, watch the $/mo drop); an AI cut-plan; cancel guides; a charge calendar - ๐Ÿ“ˆ **Net worth that thinks:** knows linking an account is new *visibility*, not new money. Manual assets/debts with AI valuation, amortizing loans, equity-grant vesting with live quotes - ๐Ÿ”ฎ **Forward-looking:** cash-flow forecast, budgets, goals, tax planning - ๐Ÿ’ฌ **Chat with your money:** text-to-SQL over a read-only connection, every query gated by a validator - โœ‰๏ธ **Weekly digest:** optional plain-English email (Resend) - ๐ŸŒ— **Ember themes:** navy night-hearth dark and warm-paper light, one tap

Subscriptions (Plaid Sandbox data): $/mo hero, AI cut-plan actions, category donut

The subscription leak-ledger, again on Sandbox data: live $/mo hero, what-if-cancel, AI cut-plan.

## The privacy posture | Layer | Stance | |---|---| | Dashboard | Private network only (Tailscale or similar); never public | | Webhooks | Optional; exactly one public path, Plaid-JWT-verified, signal-only | | Plaid tokens | AES-256-GCM encrypted at rest | | LLM | Any OpenAI-compatible endpoint; local = data stays home; UI flags local vs remote per model | | Chat SQL | Read-only connection, single-SELECT + table allowlist, injected LIMIT | | Repo | `finance.db`, screenshots, and deploy overrides are gitignored; real data never reaches git | ## Quickstart (Plaid Sandbox) ```bash git clone https://github.com/BioInfo/kindling && cd kindling npm install # secrets: export PLAID_CLIENT_ID / PLAID_SECRET / APP_ENC_KEY, # or store them in `pass` (api-keys/plaid-client-id, api-keys/plaid-secret-sandbox) ./run.sh dev # http://localhost:3408 ``` Open the app โ†’ **Connect a bank**. In Sandbox use Plaid's test credentials (`user_good` / `pass_good`, any OTP), then **Sync**. Per-deployment config (gateway URLs, model names) goes in `local.env`: see [`local.env.example`](local.env.example). Every AI feature is best-effort and degrades cleanly if no gateway is configured. ## Or hand it to your agent The 2026 install path: paste this into Claude Code (or Cowork, or any capable coding agent) and let it drive. It will ask you for the two human steps (creating the free Plaid account, approving anything irreversible) and handle the rest. ```text Install Kindling, the self-hosted personal finance app, from https://github.com/BioInfo/kindling. Clone it, check my Node version (node:sqlite needs Node 22.5+), install dependencies, and read README.md and PLAN.md before changing anything. Then walk me through Plaid credentials: help me create a free account at dashboard.plaid.com, find my client_id and Sandbox secret, and store them the way I prefer (pass entries api-keys/plaid-client-id and api-keys/plaid-secret-sandbox, or exported env vars). Generate an APP_ENC_KEY for me with openssl rand -hex 32. Start the app with ./run.sh dev, confirm http://localhost:3408 responds, and walk me through connecting Plaid's sandbox bank (user_good / pass_good, any OTP). LLM features are optional: if I have an OpenAI-compatible endpoint (Ollama counts), wire it up via local.env using local.env.example; otherwise skip, everything degrades cleanly. When I'm ready for real accounts, walk me through Plaid Production access and PLAID_ENV=production, and remind me to keep the app private-network-only. Ask before anything irreversible. ``` ## Going to production 1. Get Plaid Production keys (free tier: 10 Items), store the secret in `pass` as `api-keys/plaid-secret-production`. 2. `PLAID_ENV=production ./run.sh dev` and connect real accounts. 3. For always-on serving: `start-prod.sh` builds once (and rebuilds when any `.ts`/`.tsx` under `app/` or `lib/` is newer than `.next/BUILD_ID`) and serves `next start` on `127.0.0.1:3408`. [`deploy/`](deploy/) has LaunchAgent templates (`SET_ME` placeholders; installed copies live in `~/Library/LaunchAgents`, outside git). Put it behind `tailscale serve` or your reverse proxy of choice. Never expose it publicly. ## Operating it - **Source edits do not show in prod until rebuilt.** Stop the service, `rm -rf .next`, start it again; `start-prod.sh` rebuilds with secrets from `pass`. Do not run `next dev` or `npm run build` alongside a live prod server (shared `.next/`). - **Commit gate:** `npm run typecheck` exits 0. - **Weekly digest:** `scripts/weekly-summary.py` (dry run by default, `--send` mails via Resend; key from `pass api-keys/resend`) driven by a LaunchAgent template in `deploy/`. Resend sits behind Cloudflare, so the script sends a named User-Agent. - **Quotes:** manual holdings are priced from Yahoo Finance (no key) on Sync and Refresh; `scripts/refresh-quotes.mjs` plus the `deploy/` template adds a nightly refresh. - **Backup:** `finance.db` is WAL-mode SQLite; back it up with `sqlite3 data/finance.db ".backup "`, never a plain file copy. - **LLM and search features** need the configured gateway (`LITELLM_BASE_URL`) and `LITESEARCH_URL` reachable; a cold local model can take 30-60s on first call. - Numeric money inputs strip `$` and `,` (`parseMoney`) on client and server; do the same for new fields. Architecture, security model, and design principles: [`PLAN.md`](PLAN.md). ## Stack [Next.js](https://nextjs.org) (App Router, TypeScript) ยท [`node:sqlite`](https://nodejs.org/api/sqlite.html) (zero native build steps) ยท [Plaid Node SDK](https://github.com/plaid/plaid-node) ยท [Recharts](https://recharts.org) ยท any OpenAI-compatible LLM gateway ## License [MIT](LICENSE)