Oracle3
Oracle3: 예측시장 거래 엔진·MCP 서버
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
Personal finance that answers the question people actually ask: Cât pot cheltui astăzi? — How much can I spend today? Most money apps are very good at telling you where your money went. MONEYFLOW is built around telling you what you can do with the money you still have. The daily allowance on the dashboard is the product; transactions, budgets and goals are the inputs that make it true. [README 설치·사용 부분 발췌] Setup supabase.com/dashboard → New project. Note the project URL and the anon/publishable key from Project Settings → API.README 전문 보기 ↗
Personal finance that answers the question people actually ask:
Cât pot cheltui astăzi? — How much can I spend today?
Most money apps are very good at telling you where your money went. MONEYFLOW is built around telling you what you can do with the money you still have. The daily allowance on the dashboard is the product; transactions, budgets and goals are the inputs that make it true.
| Poți cheltui azi | One number, recomputed from the live balance: what today's spending can be without breaking the month or the savings plan. Shows its working. |
| Îmi permit? | Price in, verdict out — yes / careful / not recommended — with the reason, the daily budget that survives the purchase, and which goal it delays. |
| Money Score | 0–100 from savings rate, budget adherence, emergency fund, debt, spending consistency, goal progress and recurring load, plus the one change that would raise it most. |
| Asistent AI | Ask about your own money. Answers come from your real transactions, budgets and goals — never invented. Works with or without an API key. |
| Transactions | Income, expense, transfer. Filters and pagination in the URL. |
| Accounts | Cash, bank, card, savings. Balances maintained by database triggers. |
| Budgets | Monthly, per category, with safe / near-limit / over status. |
| Goals | Targets, deadlines, contributions, and an honest forecast. |
| Recurring | Rent, subscriptions, bills — posted automatically when they fall due, and subtracted from today's allowance before they do. |
| Debts | Both directions, with due dates and a net position. |
| Analytics | Income vs expenses, category breakdown, balance trend, savings, budget vs actual, over 7d → 1y. |
| Monthly reports | Income, expenses, savings rate, top category, and how the month compared to the last one. |
| Notifications | Bills due, budgets breached, savings hit, payday approaching — derived from the data, deduplicated. |
| PWA | Installable, offline fallback, home-screen shortcuts. |
| i18n | Română (default), Русский, English. |
Next.js 16 (App Router, Server Components, Server Actions) · React 19 · TypeScript · Tailwind CSS v4 · shadcn/ui-style components on Radix · Supabase (Auth + PostgreSQL + RLS) · Recharts · Lucide · deployed on Vercel.
supabase.com/dashboard → New project. Note the project URL and the anon/publishable key from Project Settings → API.
Either paste each file from supabase/migrations/ into the Supabase SQL
Editor in filename order, or use the CLI:
supabase link --project-ref <your-project-ref>
supabase db push
The migrations are re-runnable, so applying them twice is harmless.
cp .env.example .env.local
NEXT_PUBLIC_SUPABASE_URL=https://<project-ref>.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=<anon key>
NEXT_PUBLIC_SITE_URL=http://localhost:3000
npm install
npm run dev
Register an account at /register, answer the four onboarding questions, and
the dashboard is live. Settings → Date demo → Încarcă date demo fills the
account with three months of realistic history if you want to see it full.
http://localhost:3000/auth/callback to
Authentication → URL Configuration → Redirect URLs either way.ANTHROPIC_API_KEY to have Claude write the answers.
Without it the assistant still answers from your real data using the built-in
deterministic engine.EXCHANGE_RATES_PROVIDER=frankfurter (default, free, no
key) or static. New providers implement one interface in
src/lib/currency/index.ts.Import the repository, add the three NEXT_PUBLIC_* variables (and
ANTHROPIC_API_KEY if you want it), deploy. Set NEXT_PUBLIC_SITE_URL to the
production URL so password-reset links point at the right place — that is what
password-reset and email-confirmation links are built from.
Phone notifications (optional). Generate a VAPID pair once with
npx web-push generate-vapid-keys, then set NEXT_PUBLIC_VAPID_PUBLIC_KEY,
VAPID_PRIVATE_KEY and VAPID_SUBJECT (a mailto: for you). Without them the
settings screen says notifications are not configured rather than offering a
switch that does nothing. On iPhone they only work once the app is on the Home
Screen — Safari in a tab cannot subscribe at all.
Recurring charges on a clock. vercel.json schedules /api/cron/recurring
once a day. Set CRON_SECRET to anything long and random — Vercel presents it
as a bearer token and the route refuses everything else — and
SUPABASE_SERVICE_ROLE_KEY to the project's service role key, which is used
there and nowhere else in the app.
Without those two the job answers 503 and nothing else changes: the dashboard
still posts the current user's due charges when it opens, the way it always
did. With them, a charge posts on its due date whether or not anyone opens the
app, and the phone is told — which is the only way a notification about a bill
can ever reach you, since nothing runs while the app is closed.
The service role key bypasses Row Level Security entirely. It belongs in a
server-side variable and must never be given a NEXT_PUBLIC_ name, which would
ship it to every browser.
Put the functions next to the database. vercel.json pins them to dub1
(Dublin) because the Supabase project is in eu-west-1. Rendering a page takes
several round trips to the database, one after another — the session, the
profile, then the queries that depend on it — so every millisecond between the
two is paid several times per page. Left in the default US region against an
Irish database, that is most of the time it takes a page to open, and none of it
shows up in a profiler pointed at the code. If you move the Supabase project,
move this too: the two belong in the same place.
availableMoney = currentBalance
− reservedMoney (debts due this month)
− remainingFixedExpenses (recurring charges still to come)
− savingsTargetRemaining (what is still owed to savings)
+ upcomingIncome (recurring income still to arrive)
dailyBudget = availableMoney / remainingDaysInMonth
Two deliberate choices, both visible in the breakdown on the card:
A negative result is never shown as a negative allowance: the allowance floors at zero and the card switches to a warning that says by how much planned spending exceeds available money.
"Today" is the user's today. The server renders in UTC; the person lives somewhere else. Between their local midnight and the UTC rollover a naive server clock answers for yesterday — and an expense the browser dated today would not count toward today's spending. So the profile carries an IANA timezone, captured from the browser at onboarding and editable in Settings, and every day and month boundary is derived from it.
Nothing reaches production that no migration describes. verify-schema.sh
proves the migrations build a database that behaves; it cannot prove the live
database is that database. compare-live-schema.sh does that, listing every
function, event trigger, table with its RLS state, and policy on both sides and
diffing them. It exists because an event trigger — the one that switches Row
Level Security on for any new table in public — ran in production for weeks
while no file here created it, so a deployment rebuilt from this repository
would have come up without the safety net and nothing would have said so.
Every table has Row Level Security enabled and forced, with policies keyed on
user_id = auth.uid() for select, insert, update and delete. There is no
service-role key in the application and no admin path: the browser and the
server both talk to Supabase as the signed-in user, so the database — not
application code — is what keeps one account out of another.
RLS answers exactly one question, though: is this row mine? It says nothing
about the ids the row carries, and that gap was real — a transaction that
passed RLS on its own user_id could still name a stranger's account_id, and
the balance trigger would move a stranger's money. So every reference is a
composite foreign key on (id, user_id): the row and the thing it points at
have to belong to the same person, checked by the database on every write, in
the same place it already checks the reference exists.
The internal helper that moves balances lives in the private schema, which
PostgREST does not publish, and runs as the invoker rather than the definer —
so it has no URL, and a caller who reached it anyway would still be standing
inside their own RLS. The only security definer function signed-in users can
call is mf_delete_account, which deletes nothing but the caller's own id.
supabase/tests/01_schema_checks.sql asserts all of this against a real
PostgreSQL: isolation in both directions, the cross-owner references being
refused, and the API surface staying closed.
Passwords are handled entirely by Supabase Auth and never touch these tables. Every mutation is validated twice: in the browser for feedback, and again with Zod inside the Server Action, which is the boundary that actually counts.
npm run dev # dev server
npm run build # production build
npm run check # typecheck + lint + unit tests
npm test # unit tests only
npm run icons # regenerate the app icons
scripts/verify-schema.sh # apply migrations to a throwaway PostgreSQL and assert behaviour
# Does the live database match what those migrations describe?
LIVE_DATABASE_URL=postgres://... DATABASE_URL=postgres://...scratch... \
scripts/compare-live-schema.sh
scripts/verify-schema.sh needs a PostgreSQL 15+ (DATABASE_URL, or initdb
on PATH and it starts its own). It applies every migration twice to prove they
are re-runnable, then checks the bootstrap trigger, account-balance bookkeeping,
the transfer guard, goal progress, RLS in both directions, recurring
materialisation, the demo seed and account deletion.
src/
app/
(auth)/ login · register · forgot-password · reset-password
(app)/ everything behind the session: dashboard, transactions, …
api/ assistant · export · notifications
onboarding/ the four-step first run
components/ ui/ (primitives) · layout/ · feature folders
lib/
finance/ daily-budget · money-score · afford · period
data/ snapshot (one query pass for every intelligent surface)
assistant/ grounded context + the deterministic answer engine
supabase/ browser · server · session clients
i18n/ ro (reference) · ru · en
supabase/
migrations/ schema · RLS + triggers · RPCs · demo data · ownership keys
tests/ what the schema is supposed to do
src/lib/data/snapshot.ts assembles the whole financial picture in one parallel
query pass. The dashboard, Money Score, "can I afford it" and the AI assistant
all read from it, which is what keeps them consistent with each other — two
screens disagreeing about how much money you have is worse than either screen
being slow.
src/lib/finance/ holds the arithmetic as pure functions with no database and
no React, and tests/finance.test.ts pins it down. A wrong number in there is
wrong on every screen at once.