Skip to content

Repository files navigation

Magia

Magia

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".

CI License: MIT


Trying it

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.

The problem

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.

What it does

  • 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.

Design notes

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.

Stack

Next.js 16 (App Router) · TypeScript · Prisma + PostgreSQL · Clerk · Tailwind v4 · Vitest

Status

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.

Running it locally

Requires Node 22+ and PostgreSQL 14+.

git clone https://github.com/YOUR_USERNAME/magia.git
cd magia
npm install
cp .env.example .env.local

Fill 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:3000
npm test             # commission engine + CSV importer
npm run typecheck
npm run lint

Deploying

Full 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.

Your data

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_DAY per user (default 5), so one account can't drain your Anthropic budget. Leave ANTHROPIC_API_KEY unset and the feature is off entirely — everything else still works.

Security

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.

Feedback wanted

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.

Licence

MIT.

About

Commission tracking for salespeople on variable pay. A deal pays out in separate events — a booking fee, a close bonus, a tier that unlocks later — and each one can quietly go missing.

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages