Oracle3
Oracle3: 예측시장 거래 엔진·MCP 서버
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
A self-hosted personal-finance, budgeting & net-worth tracker.
Log income and expenses on a monthly calendar with a running projected cash balance, link your banks with Plaid for automatic transaction sync, budget by
category, set savings goals, plan your debt payoff, and watch your net worth and
trends evolve over time. Your data lives in your own database, on your own machine.
Built with Next.js 16 (App Router) · TypeScript · Prisma 7 · PostgreSQL · Auth.js v5 · Plaid · Tailwind v4 · Recharts.README 전문 보기 ↗A self-hosted personal-finance, budgeting & net-worth tracker. Log income and expenses on a monthly calendar with a running projected cash balance, link your banks with Plaid for automatic transaction sync, budget by category, set savings goals, plan your debt payoff, and watch your net worth and trends evolve over time. Your data lives in your own database, on your own machine.
Built with Next.js 16 (App Router) · TypeScript · Prisma 7 · PostgreSQL · Auth.js v5 · Plaid · Tailwind v4 · Recharts.
Try the live demo → - no sign-up; it serves a seeded sample dataset, and anything you change stays in your browser only.
AI disclaimer: Moolah was built with help from AI (Anthropic's Claude). Treat it accordingly - review the code yourself before trusting it with sensitive financial data, and note that it comes with no warranty (see Disclaimer).
Planned improvements, roughly in priority order:
SYNC_UPDATES_AVAILABLE to a verified /api/plaid/webhook
endpoint so new transactions land the moment the bank reports them, instead of waiting for the
next scheduled sweep. Needs a public HTTPS entry point (Cloudflare Tunnel, Tailscale Funnel, or a
reverse proxy), so it stays opt-in behind a PLAID_WEBHOOK_URL env var - the background sync
scheduler remains the default and the fallback when a webhook is missed.Have a request? Open an issue on GitHub.
{{variables}}. Every fired rule lands in the
in-app inbox; the sidebar bell shows an unread badge. Time-based triggers run on an in-process
sweep every 15 minutes, so they need an always-on / self-hosted deployment (like scheduled
backups). Transaction triggers fire immediately after a Plaid sync or CSV import.⌘K search, n new transaction, c open
chat, i import CSV, / focus the page's search box, and ? for a cheat sheet - are ignored
while typing in a field. Recurrence / projection / debt-payoff / matching math is unit-tested,
with a Playwright e2e suite and CI on every push and PR.You need Node 22+. No Docker or system Postgres required - a real
Postgres is downloaded and run for you by
embedded-postgres.
npm install
cp .env.example .env # the defaults already work for local dev
npm run setup # download the bundled DB (first run) & create the schema
npm run start:all # run the database and web app together
npm run start:all runs the bundled Postgres and the Next.js dev server side by side (via
concurrently), so you only need one terminal. Open http://localhost:3000.
With the shipped defaults (AUTH_BYPASS="true") you're signed in automatically as a local
user - no password or sign-in screen - so you can start adding transactions right away. Set
AUTH_BYPASS="false" when you want a real password-protected login.
Heads up: the web app needs the database running. Use
npm run start:all(DB + web) rather thannpm run devalone, or the app will fail to reach Postgres.
Load an isolated demo dataset full of example accounts, transactions, budgets and goals:
npm run setup -- --seed # create the schema *and* the demo data
Then, to browse that demo data, set DEMO_MODE="true" (with AUTH_BYPASS="true") in .env and
relaunch - no sign-in needed. The demo seed is fully isolated: it only ever touches the throwaway
[email protected] account and never modifies your own data.
Useful scripts:
| Script | What it does |
|---|---|
npm run setup | First-run: create the schema (add -- --seed for demo data) |
npm run start:all | Run the bundled Postgres and the app together |
npm run dev | Start just the app (assumes the DB is already running) |
npm run db:local | Run the bundled local Postgres on port 5433 |
npm run db:push | Sync the Prisma schema to the database |
npm run db:seed | Load/refresh the isolated demo data |
npm run db:studio | Browse the database in Prisma Studio |
npm run test | Run the unit tests (recurrence, projection, debt-payoff, matching) |
npm run test:e2e | Run the Playwright end-to-end suite against a production build |
npm run build | Production build |
For a deploy that runs like any other self-hosted service (Unraid, a NAS, a VPS), use the bundled image and compose file - the app and its own Postgres, no Node toolchain on the host.
cp .env.docker.example .env # then edit .env
npx auth secret # generate a value for AUTH_SECRET in .env
docker compose up -d --build
Open http://localhost:3000 (or whatever APP_PORT you set). On start the container waits for
Postgres, applies any pending database migrations automatically, then serves the app. Data lives in
the moolah-db Docker volume, so it survives restarts and image rebuilds.
The image is a multi-stage build producing Next.js standalone
output and runs as a non-root user. To update: git pull && docker compose up -d --build.
A prebuilt image is also published to GHCR on every push to master, so you don't
have to build locally: ghcr.io/vinnymicale/moolah:latest.
Use the bundled template so every field — image, WebUI link, port, and all variables — is pre-filled. The Add Container page's "Template" dropdown only lists templates Unraid already has on disk, so first drop the template file where it looks for them. On the Unraid box (Tools → Web Terminal):
wget -O /boot/config/plugins/dockerMan/templates-user/my-Moolah.xml \
https://raw.githubusercontent.com/vinnymicale/moolah/master/unraid-template.xml
Then Docker → Add Container, open the Template dropdown, and pick my-Moolah (under
"User templates"). Every field populates. Set the three values that have no usable default
(DATABASE_URL, AUTH_SECRET, AUTH_URL — see the table below), leave the rest, and click
Apply. The app applies migrations on start, so an empty database is initialized
automatically. Open the container's WebUI link, or http://<unraid-ip>:<port>.
If you plan to use scheduled backups to a local
folder, point the Backups path at a real share (it defaults to
/mnt/user/appdata/moolah/backups). It maps to /backups inside the container, where
BACKUP_LOCAL_DIR already points, so backups land on the host and survive container rebuilds. All of
this is editable later from the container's Edit screen.
Before first launch, create the database in your Postgres container:
CREATE USER moolah WITH PASSWORD 'moolah';
CREATE DATABASE moolah OWNER moolah;
| Variable | Required | Default | Notes |
|---|---|---|---|
DATABASE_URL | yes | — | Connection string to your Postgres. On the default bridge network use the Unraid host IP + published Postgres port (container-name DNS only works on a shared custom network), e.g. postgresql://moolah:[email protected]:5432/moolah. |
AUTH_SECRET | yes | — | Signs session tokens. Generate with openssl rand -base64 33. Keep it stable across updates, or existing logins are invalidated. |
ENCRYPTION_KEY | no | (uses AUTH_SECRET) | Key for encrypting stored Plaid/AI secrets. Optional, but when restoring a backup from another instance it must match the source's key (see Backing up your data), or Plaid pages error. Setting a dedicated value lets AUTH_SECRET rotate independently. |
AUTH_URL | yes | — | The URL you reach the app at, e.g. http://192.168.1.10:5555. Pins post-login redirects so they don't bounce to the container's internal hostname. |
AUTH_BYPASS | no | false | Leave false. The no-password auto sign-in only works over localhost, so a LAN-reachable container must use a password (you set one on first load). |
AUTH_TRUST_HOST | no | true | Lets Auth.js trust the proxy/host header. Leave as-is unless you front the app with something that rewrites it. |
DEMO_MODE | no | false | true serves a seeded read-only sample dataset. Leave false for a real instance. |
PLAID_CLIENT_ID | no | — | Only if you want automatic bank sync. From your Plaid dashboard. |
PLAID_SECRET | no | — |
Moolah exposes versioned, read-only /api/v1 endpoints so external tools - e.g. a Home
Assistant dashboard on your network - can pull your numbers. Generate a personal token under
Settings → Read-only API access (shown once; only its hash is stored), then send it as a bearer
token:
curl -H "Authorization: Bearer moolah_…" http://localhost:3000/api/v1/summary
| Endpoint | Returns |
|---|---|
GET /api/v1/summary | Net worth, current-month budget status, upcoming bills |
GET /api/v1/net-worth | Assets, liabilities, net, and per-account balances. Add ?range=3m|1y|all for a daily history series and ?forecast=12 (1-24 months) for a net-worth projection |
GET /api/v1/accounts | All non-archived accounts with balances |
GET /api/v1/budget | Budget vs. actual per category (?month=YYYY-MM optional) |
GET /api/v1/upcoming | Bills/income in the next N days (?days=14, 1-90) |
Pass ?tz=America/New_York to anchor "today"/"this month" to your timezone (defaults to UTC).
Requests are rate-limited per token. Regenerating or revoking the token in Settings immediately
invalidates the old one.
Two ways to surface these numbers in Home Assistant:
hass-moolah) - a
companion custom component (HACS-style) that you point at your Moolah URL and paste the read-only
token into during setup. It polls /api/v1 and exposes sensors for net worth (current and
12-month projection), assets, liabilities, budget limit/spent/remaining, and
upcoming bills - ready to drop onto a dashboard. Install it via HACS as a custom repository, or
copy custom_components/moolah into your Home Assistant config./api/v1/summary with
the bearer token works without installing anything.Create a free account at the Plaid Dashboard, grab your keys from
Developers → Keys, and paste them into Settings → Plaid bank sync - they're stored
per-user (secret encrypted) so each account can use its own Plaid credentials. Alternatively, set
them in .env as an instance-wide fallback for users without their own keys:
PLAID_CLIENT_ID="..."
PLAID_SECRET="..." # use the secret that matches PLAID_ENV
PLAID_ENV="sandbox" # "sandbox" = fake test data; "production" = your real banks
sandbox lets you link Plaid's test institutions (use credentials like user_good /
pass_good) with fake data - perfect for trying everything out at no cost.production connects your real banks. It requires requesting production access in the
Plaid Dashboard, and Plaid bills you per linked item (bank connection) - so avoid
re-linking the same bank (see Backing up your data).development environment has been retired and is no longer an option.Once set, use Connect a bank on the Accounts page; balances and posted transactions sync automatically and are auto-categorised from the bank's category data. Linking is optional - manual and CSV entry work without Plaid.
When you run locally, everything lives in the Postgres data directory .pgdata/ in the project
root: accounts, transactions, budgets, goals - and your Plaid access tokens (the
PlaidItem.accessToken column). Lose .pgdata/ and you have to re-link every bank, which on the
production Plaid environment is a fresh, billed connection each time. So if you connect real
banks, back this up.
A Plaid access token is tied to your PLAID_CLIENT_ID + PLAID_SECRET + PLAID_ENV, not to this
computer or database, so you can move it to another machine and keep the same connections.
Restoring a saved token never costs a new Plaid item - only clicking "Connect a bank" does.
Backup and restore are admin-only. The file holds every member's login and the household's Plaid access tokens, so only an owner or admin can download or restore one - members and viewers get a 403. What comes out is the whole database, not just the caller's rows: a partial dump would restore into a household missing its members.
Preserving your Plaid connections across a move takes both pieces below. Move the backup but not the key and the connections are technically there, yet every Plaid page errors because the app can't decrypt the credentials it needs to actually call Plaid.
.pgdata/). It carries each bank's accessToken in plain
text, so the tokens themselves restore fine on any machine, under any key.PLAID_CLIENT_ID /
PLAID_SECRET) and AI keys are stored encrypted, keyed off ENCRYPTION_KEY (or
AUTH_SECRET if you never set one). The app decrypts the Plaid secret on every sync, so if the
new host can't decrypt it, the access tokens are useless and Plaid pages fail. The key must
match the source instance.So the rule is: copy the backup and set the same ENCRYPTION_KEY / AUTH_SECRET on the new
host before you restore. (If your Plaid keys live only in .env as an instance-wide fallback,
rather than in the household's Settings, they aren't encrypted in the database - just copy that
.env and the key question is moot.) The full step-by-step is in the ⚠️ callout under Option A.
Export everything to a single JSON file (all tables, including the Plaid tokens):
npm run db:backup # writes backups/moolah-backup-<timestamp>.json
Or, in the running app, go to Settings → Back up everything → Download backup to get the same file in your browser. Copy it somewhere safe (external drive / cloud folder).
To restore in the app: go to Settings → Restore from a backup, pick a backup file, and confirm. This overwrites the current database with the backup's contents, so you're signed out afterward and log back in with a restored account - every member's login comes back with the role and permissions they had. Ideal for the export → spin up Docker → import-without-reconfiguring workflow.
To restore from the CLI (e.g. into a fresh clone):
npm install
npm run db:local # start the bundled Postgres
npm run db:push # create the (empty) schema
npm run db:restore -- ./path/to/moolah-backup-<timestamp>.json
Your banks reconnect with no new Plaid items and no re-linking. (db:restore only writes into an
empty database; pass --force to overwrite an existing one - the in-app restore always overwrites.)
The backups/ folder is gitignored.
⚠️ Moving to a new machine? Set the encryption key before you restore. Restore the backup under a different key and decryption of your Plaid/AI secrets fails, surfacing as a "Something went wrong" error on the Accounts page (and anywhere Plaid is touched). The correct order on the new host is:
- On the old instance, read its key:
grep -E 'ENCRYPTION_KEY|AUTH_SECRET' .env.- On the new instance, set both
AUTH_SECRETandENCRYPTION_KEYto that value in.env(Docker/Unraid: the same two environment variables), and restart so the new values take effect.- Then restore the backup (in-app or
npm run db:restore).Setting a dedicated
ENCRYPTION_KEYon both ends letsAUTH_SECRETrotate independently later. If the original key is truly lost, those encrypted secrets are unrecoverable - restore anyway (your data and access tokens come back), then clear and re-enter your Plaid/AI keys from Settings; because the access tokens are intact, re-entering the API keys reconnects the same Plaid items with no new billed links.
For a full cold copy you can also just back up the database folder itself:
.pgdata/ directory and your .env file somewhere safe..pgdata/ comes back world-readable and Postgres
refuses to start with a "data directory has invalid permissions" error. Fix it once:
chmod 700 .pgdata # Postgres requires the data dir to be 0700 (or 0750)
rm -f .pgdata/postmaster.pid # clear any stale lockfile copied from a running server
Then start the app. (Option A avoids this entirely, since it restores into a fresh .pgdata.)⚠️ Treat any backup as a secret - the Plaid access tokens are stored unencrypted and grant access to your bank data. Keep them somewhere private.
Never run
npm run db:resetor delete.pgdata/without a current backup - both wipe your tokens.
If your instance stays running (self-hosted Docker, Unraid, or npm start), it can take the Option A
backup for you on a schedule. Go to Settings → Scheduled backups and set:
A Run backup now button takes one on demand, and the section shows the last run's time and
result. The backups are the exact same JSON snapshot as Option A, so any of them can be restored via
Settings → Restore from a backup or npm run db:restore.
This runs inside the app process, so it only fires while the server is up - it's meant for an always-on host, not serverless. It's disabled in demo mode.
Local destination: backups are written to BACKUP_LOCAL_DIR (default ./backups). In Docker,
set it to a mounted volume like /backups so copies survive container rebuilds.
Google Drive destination: uses an OAuth refresh token rather than a browser sign-in at backup time, so it works headless. One-time setup:
https://developers.google.com/oauthplayground as an
authorized redirect URI. Note its client ID and client secret. Also enable the
Google Drive API for the project, and add your own Google account under Test users on the
OAuth consent screen (otherwise the authorization is rejected).https://www.googleapis.com/auth/drive.file scope and Authorize APIs; in step 2,
Exchange authorization code for tokens. Copy the refresh token from the response (it
starts with 1//). A Web-application client is needed here rather than a Desktop one because the
Playground signs in through that redirect URI.ENCRYPTION_KEY and never sent back to
the browser.Backups are uploaded into that folder, and retention only prunes Moolah's own
moolah-backup-*.json files there - anything else in the folder is left alone.
Each cash account (checking/savings/cash flagged "include in cash flow") has a currentBalance
that's treated as the truth as of today. For any calendar day the projected end-of-day balance
is todayBalance + (cumulative signed transactions up to that day - cumulative up to today), where
income is + and expense is -. This single formula reconstructs past days and projects future
ones - including not-yet-cleared and recurring items. The logic lives in
src/lib/projection.ts and src/lib/recurrence.ts
and is covered by unit tests.
Note: recording transactions does not auto-mutate an account's
currentBalance. Update real balances via Update balance on the Accounts page (which also builds net-worth history), or let Plaid sync keep linked balances current. This keeps reconciled balances and the projected ledger cleanly separated.
prisma/ schema.prisma, migrations, seed.ts
scripts/ setup.ts (first-run schema), local-db.ts (embedded Postgres runner)
src/
app/(auth)/ sign-in
app/(app)/ dashboard, calendar, notifications, transactions, accounts, networth,
recurring, budgets, goals, debt, categories, trends, settings
app/api/ plaid (link/exchange/sync/recategorize), export (CSV),
backup (download/import), v1 (read-only API), chat (AI assistant), health
actions/ server actions (mutations)
lib/ prisma, auth/session, money, dates, recurrence, projection,
calendar, reports, queries/, plaid-sync, debt-payoff, milestones,
budget-suggestions, notifications/ (triggers, engine, Discord delivery),
snapshots (net-worth history),
networth-forecast, backup/ (full export/restore, Google Drive),
crypto (secret encryption), api-auth (token auth)
components/ AppChrome, Sidebar, CommandPalette, MultiSelect, TransactionModal,
Modal, Toast, AnimatedNumber, Sparkline, charts, icons
Moolah is free and self-hosted. If you find it useful and want to support its development, you can buy me a coffee ☕ - entirely optional, and always appreciated.
Moolah was developed with assistance from AI (Anthropic's Claude). It is a personal project provided as-is, without warranty of any kind, express or implied. You run it at your own risk: review the code before trusting it with real financial data, keep your own backups, and remember that anything it shows you (projections, debt payoff, net worth) is for informational purposes only and is not financial advice. You are responsible for the security of your own deployment, credentials, and Plaid access tokens.
Moolah is released under the MIT License - you're free to use, modify, and self-host it.
Plaid API secret (matches PLAID_ENV). |
PLAID_ENV | no | sandbox | sandbox for fake test data, production for real banks. |
PLAID_SYNC_CRON | no | */30 * * * * | How often the background sync checks for new bank data, so transactions and notifications land without a browser open. Standard cron syntax. An invalid value disables background sync (the app still syncs when you open it). |
PLAID_STALE_MS | no | 3600000 | How old an item's last sync must be before it's re-synced, in milliseconds (default 1 hour). Guards against hammering Plaid; lower it only if you really need fresher data. |
BACKUP_LOCAL_DIR | no | /backups | Where scheduled backups land for the local destination. The template pre-sets this to /backups and mounts it from the host via the Backups path, so leave it as-is unless you also change that mapping. Ignored for the Google Drive destination. |