Commission tracking for salespeople on variable pay.
A deal doesn't pay out once. It pays out in separate events — a booking fee now, a close bonus in six weeks, a tier that unlocks when the account grows — and each one can quietly go missing. Magia's job is that none of them do.
מגיע — Hebrew for "it's owed to you".
There is no hosted version yet. This repo is the source code — running it takes a database and a Clerk key, which is fine for developers and a non-starter for everyone else.
- Developer? Run it locally — about five minutes.
- Not a developer? You need someone to deploy it for you, or a link to a running instance. If you got here from a post, ask for one.
- Want to host it? docs/DEPLOYING.md — Vercel + Neon, free tier, about fifteen minutes.
Ask a salesperson on commission whether they were paid correctly last quarter and most can't tell you. Not because they don't care — because the answer lives across a CRM, a spreadsheet, and twelve months of payslips, and reconciling it by hand takes an afternoon nobody has.
The money that goes missing isn't stolen. It's forgotten: a tier that unlocked when nobody was looking, a close bonus that never made it onto a payslip, a deal marked won in the CRM six weeks after it closed.
- Answers one question on the home screen — how much is currently sitting with your employer. Not what you've earned. What you're owed.
- Reconciles payslips. Enter what the payslip actually said; Magia checks it against what it expected and tells you when it came up short.
- Models real comp plans — booking fees, close bonuses, percentage rates, tiered thresholds, source-specific rules, and tiers that either stack on each other or replace everything below them. A rate and a flat fee can sit in the same plan, so "₪100 a meeting plus 8% of what closes" is one model.
- Asks instead of assuming. It can't know when a deal closes or an account grows. It can notice its information has gone stale, and ask.
- Imports from your CRM via CSV, auto-detecting columns. Re-running the same file is a no-op, and a row that looks like a deal you already typed in is offered as a link rather than added a second time.
- Reads your contract (optional) — Claude extracts the commission model and shows the exact clause behind every field before anything is saved.
- Shows what a different plan would have paid on your real deals, so you walk into a comp negotiation with a number instead of a feeling.
- English and Hebrew, with full RTL.
A few decisions that aren't obvious from the screenshots:
Status is derived, never stored. How much a deal owes you is computed from the model and the payment records every time it's read. There is no status column to drift out of sync.
One tint, six statuses, and never the two mixed. Blue means exactly one thing: you can touch this. Every other colour says something about money. Money that's owed renders in plain white — it's the status of most rows, so colouring it would tint the entire list.
Partially paid ≠ unpaid. A line item paid short is a payroll error and
surfaces as UNDERPAID. A line item wholly unpaid is money that hasn't arrived
yet — normal when a tier newly unlocks. Collapsing those two into one status is
the bug that would make the app useless, so tiers are never merged into a single
"bonus" line.
Imports never silently overwrite a hand-corrected figure. If you fixed a deal value by hand and the CRM later disagrees, that's a conflict you resolve — not something the importer decides for you.
Next.js 16 (App Router) · TypeScript · Prisma + PostgreSQL · Clerk · Tailwind v4 · Vitest
Working software, used by its author, not yet battle-tested by anyone else. Expect rough edges — that's exactly why it's public.
The commission engine and CSV importer have tests. Nothing else does.
Requires Node 22+ and PostgreSQL 14+.
git clone https://github.com/YOUR_USERNAME/magia.git
cd magia
npm install
cp .env.example .env.localFill in .env.local:
| Variable | Required | What it's for |
|---|---|---|
DATABASE_URL |
yes | PostgreSQL connection string |
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY |
yes | Auth — free key from clerk.com |
CLERK_SECRET_KEY |
yes | Auth |
ANTHROPIC_API_KEY |
no | Contract scanning, plain-language model setup |
ANTHROPIC_MODEL |
no | Which Claude model extracts — defaults to claude-opus-5 |
EXTRACTIONS_PER_DAY |
no | Per-user daily cap on contract scans — defaults to 5 |
DIGEST_CRON_SECRET |
no | Guards the weekly-digest endpoint |
RESEND_API_KEY |
no | Sends the weekly digest |
DIGEST_FROM_EMAIL |
no | Sender address for the digest |
BLOB_READ_WRITE_TOKEN |
no | Object storage for uploads — required on serverless hosts |
DATABASE_URL must also be in .env — the Prisma CLI reads that file, not
.env.local.
npm run db:migrate # create the schema
npm run db:seed # optional: a populated demo account
npm run dev # http://localhost:3000npm test # commission engine + CSV importer
npm run typecheck
npm run lintFull guide: docs/DEPLOYING.md. The short version is Vercel + Neon + Clerk, all free tier, about fifteen minutes. Migrations run automatically on deploy.
Uploaded contracts need blob storage on any serverless host, since those
filesystems are read-only. src/lib/storage.ts uses Vercel Blob when
BLOB_READ_WRITE_TOKEN is present and local disk otherwise — no code change
either way.
If you deploy an instance other people sign up to, read the section on holding someone else's salary data first.
Magia stores commission amounts, client names, deal values and payslip totals — your salary information. Before you self-host:
- Everything lives in your database. There is no hosted service, no telemetry, and nothing is sent anywhere you didn't configure.
- Uploaded contracts are stored unencrypted in
var/uploads/(gitignored). See the deploy note above. - If you set
ANTHROPIC_API_KEY, contract text you upload is sent to Anthropic's API for extraction. Leave it unset and the feature is simply off. - Contract scans are capped at
EXTRACTIONS_PER_DAYper user (default 5), so one account can't drain your Anthropic budget. LeaveANTHROPIC_API_KEYunset and the feature is off entirely — everything else still works.
Every server action resolves the signed-in user and scopes its query to them.
Routes are protected by Clerk middleware except /sign-in, /sign-up, and
/api/digest — which uses a shared-secret header and refuses to run if that
secret is unset.
Found something? Please don't open a public issue — see SECURITY.md.
Especially from people who get paid commission. The most valuable thing you can tell me is a comp plan Magia can't express — there's an issue template for it.
See CONTRIBUTING.md.
MIT.