A CRM you never type into.
Paste the mess — an email thread, a call transcript, three lines you scribbled after a meeting — and sift reads it, works out who and what and when, shows you exactly what it wants to change, and writes it when you say so.
git clone https://github.com/openwarehq/sift
cd sift
cp .env.example .env # add a model — free key, no card, about a minute
docker compose up
# http://localhost:4336There is already an excellent open-source CRM: Twenty, with fifty thousand stars and a real team. If you want a beautiful, flexible database of customers, use it — this is not trying to beat it at that.
sift is a different bet. Every CRM rots for the same reason: keeping it current is data entry, and data entry loses to everything else on your desk. A prettier table does not fix that. Removing the form does.
So there is no "new contact" screen here. There is a box you paste into.
Read this part first.
- It is only as good as your model at reading text. On the repo's default free model it reliably pulls names, emails, phone numbers, amounts and next steps out of ordinary business prose. It is worse at long threads with many people, and it will occasionally miss the second person in a message. Every extraction is shown as a diff before it is written, which is the mitigation — but it is a mitigation, not a fix.
- It does not read your inbox. There is no email integration, no OAuth, no sync. You paste. That is a real limitation and also the reason it works with WhatsApp exports, phone notes and transcripts without anyone building a connector for each.
- It does not convert currencies, and it will not pretend to. A pipeline holding
pounds and dollars reports
£18.4k · $88.2k, not one number. Summing across currencies is wrong in a way nobody notices, because the result looks exactly like a number that is right. - The model never writes a date. It reports the words it saw — "Thursday", "end of the month" — and the arithmetic happens in TypeScript. This is not fussiness: asked to resolve "Thursday" against a stated Tuesday, the default model answered Friday three times out of three. A due date one day out never looks wrong later, you just miss it.
- No multi-user, no permissions, no audit trail beyond the activity log. It binds to localhost and assumes one person. Do not put it on the open internet.
- No email sending, no sequences, no dialer, no forecasting. It is a place to keep what you know and what you owe people.
- Duplicate detection is exact-match, on email for people and domain for companies, falling back to case-insensitive names. "Acme" and "Acme Inc" with no shared domain will be two rows. Fuzzy matching that merges the wrong two customers is worse than a duplicate you can see.
Paste anything. It is built for the shape real information arrives in: a forwarded thread with three signatures in it, a transcript with crosstalk, a note that says "priya, 6 chairs, dentrix, ~18k, quote thurs".
Nothing is written until you have seen it. Every intake produces a diff —
NEW company Meridian Dental, UPDATE deal · stage: proposal → won — field by
field, old value struck through. Click any row to skip it. This is the difference
between an extractor you trust and one you turn off the first time it invents a
phone number.
It updates instead of duplicating. Paste a follow-up about the same deal and you get updates, not a second copy: the person gains a title and a phone number, the deal moves to won. Matching happens on the server against real rows — the model is never asked whether a record already exists, because it cannot know and would guess.
Three tables and a bag. Companies, people, deals. Anything a particular business
needs that the schema does not have lands in a free-form fields bag the extractor
fills — {"chairs": 6, "software": "Dentrix"} for a dental practice,
{"fleet_size": 42} for a haulier. That is how one CRM fits every business
without a settings screen, and without a hundred columns that are null for
everyone else.
A pipeline you can see all of. Five stages, drag to move, every move logged. No swimlanes and no eleven-stage funnel — a board that needs scrolling to comprehend becomes a report nobody updates.
Your model, your machine, your file. One SQLite file in data/. CSV export for
everything. No account, no seat pricing, no per-contact billing.
| Attio | $29–69 / seat / month |
| Pipedrive | $14–165 / seat / month |
| HubSpot | free, then steeply not |
Per seat, per month, forever, for a database of people you already know.
The same three variables every Openware drop reads:
LLM_BASE_URL=https://openrouter.ai/api/v1
LLM_MODEL=google/gemma-4-26b-a4b-it:free
LLM_API_KEY=Anything that speaks the OpenAI chat-completions shape works — Ollama, LM Studio,
llama.cpp, vLLM, OpenRouter, Groq, Cerebras, Together. Point LLM_BASE_URL at
https://api.anthropic.com and sift switches wire format itself. All of it is also
settable from the Settings panel, so you can hand this to someone who has never
opened a .env.
Demo data — five companies, six people, six deals — loads and wipes from Settings. An empty board tells you nothing about whether the board is any good.
sift/
├── src/lib/
│ ├── db.ts three tables, the fields bag, and the matching that avoids duplicates
│ ├── intake.ts prompt → extraction → diff → apply
│ ├── dates.ts "Thursday" → a date, in code, because the model gets it wrong
│ └── llm.ts OpenAI + Anthropic, one call, defensive JSON parsing
└── src/components/
├── Intake.tsx the paste box and the diff
└── Pipeline.tsx the board
npm run dev # http://localhost:4336
npm test # 20 tests
npm run typecheckMIT. Not affiliated with Attio, Pipedrive, HubSpot or Twenty. Names appear only to say what this replaces.
