Negotiate against an AI that actually pushes back, then get a line-by-line breakdown of where you gave ground you didn't have to.
BusinessTypeScript · PLpgSQLGitHub @tahanaqvi14OnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
AI negotiation simulator — practise against an opponent that actually pushes back, then get a report that quotes you back to yourself. Next.js 16, Supabase RLS, Gemini.
Loading the full README.
Read the full README ↗Practice the conversation before it costs you.
A negotiation simulator that puts you across the table from an AI opponent that actually pushes back — one that anchors low, stalls, manufactures deadlines, and will walk away from you. When it's over, it hands you a report that quotes your own messages back to you and tells you exactly where you gave ground you didn't have to.
Built with Next.js 16, Supabase and Gemini, running entirely on free tiers.
Open the live demo — one click, no signup, into an account that already has negotiation history in it.
Negotiation is a skill you almost only practise live, in the situations where getting it wrong is most expensive. There is no low-stakes rehearsal for asking for a raise. Worse, the feedback loop is broken at both ends: you rarely find out what the other side would have accepted, and generic advice ("be confident", "anchor high") is impossible to act on in the moment.
Two things had to be true for this to be worth building:
Everything below follows from those two constraints.
Each turn, the model returns structured JSON: its reply, the tactic it read in your message, and the position it wants to move to. Application code then clamps that position into a range the persona's playbook allows.
model proposes → ledger clamps → user sees the clamped number
Conceding past the reservation value is arithmetically impossible rather than merely discouraged. No amount of persuasion — or instruction-shaped text pasted into a message — moves an opponent further than its budget permits.
Two things are deliberately withheld from the model:
Tone alone produces four models that cave at the same rate in different words.
The real differentiation lives in playbooks.ts
as numbers the model never sees: how far each will ever move, what makes it
move, what it does when you concede, and what makes it leave.
Measured across a 4-turn scripted replay:
| user behaviour | Lowballer | Staller | Professional | Closer |
|---|---|---|---|---|
| holds firm, no justification | 0% | 0% | 0% | walks |
| concedes unprompted each turn | 0% | 0% | 0% | 0% |
| brings verifiable evidence | 15% | 7% | 8% | 25% |
| turns hostile | walks | stalls on | walks | walks |
The teaching signal falls straight out of that table: evidence is the only thing that moves anyone. Repeating your number moves nobody. Conceding unprompted is never reciprocated.
The Friendly-but-Firm Professional is the difficulty-4 opponent, not the aggressive one — it has the smallest concession budget of the four. Losing to someone pleasant feels like winning, which is exactly what makes it hard.
The report generator is given the transcript with explicit [#n] markers, and
its output schema requires a message_seq and a verbatim quote on every
observation. Then every citation is verified against the transcript:
If too few survive, the whole report is regenerated with the failures named. A model cannot bluff a citation past this, which is what makes the report worth reading rather than merely well-written.
| Framework | Next.js 16 (App Router), React 19, TypeScript |
| Styling | Tailwind CSS v4 |
| Database | Supabase Postgres with RLS |
| Auth | Supabase Auth — email/password + Google OAuth |
| AI | Gemini (3.5-flash-lite for turns, 3.8-flash for reports) |
| Charts | Recharts |
| Motion | Motion (Framer Motion) |
| Voice | Web Speech API — no external service |
| Hosting | Vercel |
Type: Fraunces (display), Instrument Sans (body), IBM Plex Mono — every figure renders in tabular mono so offers and deltas align down the page. Colour carries meaning rather than decoration: brass is you, steel is the opponent who has not moved.
Multi-tenancy is enforced by Postgres, not by application code. Every table
has RLS enabled and every policy is scoped to auth.uid(). Server routes are
built on the publishable key plus the user's own cookie, not the secret key —
so server-side reads pass through the same policies the browser does. The secret
key never touches a user's data: its only runtime uses are the keepalive cron
and housekeeping for the shared demo account (below).
Transcripts and reports are append-only at the database level. No
UPDATE/DELETE policy exists on either. A transcript you can edit after the
fact makes the feedback report meaningless, so the database refuses rather than
trusting the app. Once a session finishes it is frozen too: its owner can no
longer update it, delete it, or append to its transcript.
This is the beginning of the README. Read the rest at the original. Read the full README ↗