install
Two ways onto your Mac: a one-line install script, or the .dmg by hand. Either way you end up with Twin.app in Applications.
quick install
curl -fsSL https://raw.githubusercontent.com/woustachemax/twin/main/scripts/install.sh | bash
Downloads the latest release's .dmg, mounts it, copies Twin.app into /Applications, and clears the quarantine flag so Gatekeeper doesn't block the first launch. Safe to re-run: an existing install gets replaced, not skipped.
manual download
Not keen on piping a script into bash? Grab the .dmg from the download section on the landing page instead, and drag Twin.app into Applications yourself.
the gatekeeper warning
Twin ships ad-hoc signed, not notarized by Apple. On a Mac other than the one that built it, the first launch after a manual download gets blocked: "Twin can't be opened because Apple cannot check it for malicious software." Click Done, then:
- Open System Settings → Privacy & Security.
- Scroll to the Security section. You'll see "Twin was blocked to protect your Mac" with an Open Anyway button next to it.
- Click Open Anyway, then confirm Open (Touch ID or your password may be asked for).
Or skip all of it: xattr -cr /Applications/Twin.app in Terminal after dragging Twin in. The quick install script does this step for you automatically.
building from source
git clone https://github.com/woustachemax/twin.git cd twin python3 -m venv .venv source .venv/bin/activate pip install anthropic duckdb python-dotenv pynput pyobjc-framework-Cocoa pyobjc-framework-Quartz pyobjc-framework-Vision pyobjc-framework-EventKit pyobjc-framework-Speech pyobjc-framework-AVFoundation python3 buddy.py
That's the core install. You'll need Python 3 with Tk (the python.org installer includes it, or brew install python-tk). Add pip install pypdfium2 torch transformers sentencepiece protobuf Pillow if you also want /ingest. It pulls in torch, so it's a much bigger download, and it's why the downloadable app leaves it out entirely.
first run
The first time Twin starts, or any time ~/.twin/config.json is missing or setup wasn't finished, it opens a setup window before the chat widget. Four steps and a summary:
- Provider. Pick Anthropic, OpenAI, Google Gemini, xAI, or Ollama. For a cloud provider, paste your API key: Twin checks it with one small test request, then saves it to your macOS Keychain. Wrong key, out of credits, or the wrong provider: it says so and lets you try again. For Ollama, there's no key: see running locally with Ollama for what happens instead.
- Name. What Twin should call you. Filled in with the first name from your Mac account.
- Buddy. Pick one of the five personas. Twin is selected by default.
- Permissions. Buttons for Calendar, the hotkey, and Messages. macOS only prompts when you click one. This page can be skipped.
- Done. A summary of your choices. Twin creates your database at
~/.twin/twin.duckdbhere.
Your API key is not saved in a file. It goes straight into the macOS Keychain. Close the setup window before finishing and Twin quits; it picks up at step 1 next time, with your provider and key filled back in.
running locally with ollama
Ollama is a fifth provider choice, and the local one: pick it in setup and the model runs on your Mac, with no API key and nothing to sign up for.
installing ollama
Twin looks for either the Ollama.app download in /Applications, or the ollama binary from a Homebrew install (brew install ollama), checking /usr/local/bin and /opt/homebrew/bin directly so it's found even when Twin is launched from Finder, which doesn't inherit your shell's PATH. Install either one before picking Ollama in setup.
picking it in setup
Select Ollama on the provider step. If it isn't already running, Twin starts it itself, either by opening the app or by launching ollama serve from the binary, and waits a few seconds for it to answer before moving on. Twin does the same automatic start on every later launch whenever Ollama is your saved provider, not just during setup.
If you have no models pulled yet, Twin recommends one sized to how much RAM your Mac has, shows its approximate size, and downloads it when you click Download. That download needs an internet connection; after it finishes, chat with that model runs fully on your Mac. Replies stream in as the model generates them, the same as typing to a cloud provider.
| your Mac's RAM | recommended model | approx. size |
|---|---|---|
| Under 12 GB, or unknown | qwen2.5:3b | 2.2 GB |
| 12 GB or more | llama3.1:8b | 5.3 GB |
using a model you already have
If you've already pulled models with Ollama, the setup window lists them as chips, click one to use it. You can also type the exact name of any model you've pulled yourself into the field. Twin only auto-downloads the one model it recommends; typing the name of a model that isn't pulled yet tells you to run ollama pull <model> in a terminal first.
cancelling a download, and running out of space
While a model downloads, a Cancel link sits next to the progress bar. Before starting, Twin also checks that your disk has enough free space for the model plus a small margin, and tells you the exact numbers instead of starting a download that can't finish. If space runs out mid-download instead, Ollama reports the problem and Twin shows the same "not enough free disk space" message.
what stays on your mac
There's no key to store for Ollama: nothing gets saved to the Keychain for it. The model itself lives wherever Ollama keeps its own models, outside ~/.twin. Twin's config.json still records that you picked Ollama and which model, same as it would for any other provider.
Conversation summaries work the same way regardless of provider, but matter most here: ask something like "summarize my messages with Alex" and Twin asks once before turning this on at all. Once on, each request reads only the one named, one-to-one thread straight from Messages (group chats aren't included), up to the last 60 messages, and sends it to whichever provider you picked to write the summary. With Ollama, that never leaves your Mac. Either way, nothing from that thread is written to twin.duckdb or anywhere else on disk; it's read fresh every time you ask. This needs the same Full Disk Access as the rest of Messages reading.
what still needs the internet
Chat with Ollama runs fully on your Mac once a model is downloaded. A few other things don't go through your chosen provider at all, and still reach out regardless of whether you're on Ollama or a cloud provider: looking up an SEC filing talks to SEC EDGAR, flight search talks to its own flight data provider, and /ingest's first run downloads the Donut OCR checkpoint from Hugging Face. All three need internet the same way they would with any provider.
permissions
Grant these from the setup window's permissions page, or later in System Settings. macOS applies a permission change on relaunch, not immediately. Quit and reopen Twin after granting one for it to take effect.
| permission | where | without it |
|---|---|---|
| Full Disk Access | System Settings → Privacy & Security → Full Disk Access | Twin reads no Messages and silently ingests nothing, so spending questions come back empty. |
| Screen & System Audio Recording | System Settings → Privacy & Security → Screen & System Audio Recording | screencapture still succeeds but returns only the wallpaper, so Twin answers vaguely instead of erroring. |
| Microphone, Speech Recognition | Allow when prompted, or System Settings → Privacy & Security → Microphone / Speech Recognition | Voice input (hold ⌘ ⇧ V) doesn't work. |
| Accessibility | System Settings → Privacy & Security → Accessibility | The global hotkey (⌘ ⇧ space) does nothing. |
| Calendars | Allow when prompted, or System Settings → Privacy & Security → Calendars | Twin can't mention what's coming up on your calendar. |
Twin keeps working without any of these. It says in the widget which feature is off and where to turn it on.
commands & hotkeys
Everything Twin responds to, typed or held.
| action | how |
|---|---|
| Show or hide Twin | ⌘ ⇧ space from anywhere |
| Ask something | Type in the box and press Return |
| Move the widget | Drag it |
| List personas | /persona, then click one to switch |
| Switch persona | /persona <key>, for example /persona gengar |
| Change provider or key | /setup |
| Catch up on today's Messages | "what did I miss?", "catch me up" |
| Ask about your screen | "what am I looking at?", "what's on my screen?" |
| Look up an SEC filing | "Tesla's last 10-K", "summarize Apple's most recent 8-K" |
| Read a document | /ingest <path>, for example /ingest ~/Downloads/receipt.jpg |
| Stop referencing that document | /forget |
| Check what would be redacted before sending | /redact-test <text> |
| Talk instead of typing | hold ⌘ ⇧ V, or hold the dot by the input field |
| Turn spoken replies on or off | /voice on, /voice off (off by default) |
personas
Pick one during setup, or switch anytime with /persona <key>. Commands take the key, not the display name.
| key | name | character |
|---|---|---|
twin | Twin | Warm, cheerful, easygoing. The default. |
gengar | Shade | Mischievous shadow-ghost, sly and teasing. |
ember | Ember | Bright, energetic spark who hypes you up. |
calm | Luna | Gentle, patient, soothing. |
plain | Assistant | Neutral and professional, no quirks. |
Luna is the only one that resizes: drag the grip in its bottom-right corner, anywhere between 360×350 and 640×720. It opens at 420×460 and remembers the size you leave it at until you quit. Every other persona stays a fixed 360×350 widget with no grip.
providers
Five supported providers, one client each behind a common interface. Switch anytime with /setup.
| provider | get a key from |
|---|---|
| Ollama | local, no key, no account |
| Anthropic | console.anthropic.com/settings/keys |
| OpenAI | platform.openai.com/api-keys |
| Google Gemini | aistudio.google.com/apikey |
| xAI | console.x.ai |
Keys for the four cloud providers are stored in the macOS Keychain as "Twin <provider> API key", never in a file on disk. Ollama has no key to store.
your data
Everything Twin keeps lives in ~/.twin/, outside the repo:
~/.twin/twin.duckdb: parsed transactions, the full raw SMS text, and anything loaded with/ingest.~/.twin/config.json: your name, persona, provider, and setup state.
Your API key sits separately, in the macOS Keychain, for the four cloud providers; Ollama has no key to store. There are no accounts, no sync, no analytics, and no Twin server. Everyone who installs Twin gets their own database, created in their own home folder.
Every insert into twin.duckdb also writes a row to its access_log table, recording what was read, when, and by which part of Twin.
Conversation summaries ("summarize my messages with NAME") are the one exception that never touches this database: they're opt-in, and each one reads a single one-to-one Messages thread fresh at the time you ask, then sends it to your provider to write the summary. Nothing from that thread is written to twin.duckdb, config.json, or anywhere else on disk. See running locally with Ollama for the full behavior.
reset to a clean state
Delete ~/.twin and Twin runs setup again on next launch, as if freshly installed:
rm -rf ~/.twin
uninstall
- Quit Twin, then drag
Twin.appout of Applications. - Delete
~/.twin(your database and config). - Remove the Keychain entry for whichever provider you used. For Anthropic, that's service
"Twin Anthropic API key", account"twin":
security delete-generic-password -s "Twin Anthropic API key" -a twin
faq
why does Twin refuse to tell me my exact spending?
Because the exact numbers never reach it. Before any chat request goes out, your five most recent transactions get turned into vague phrases like "spent money on food earlier this week." Amounts, balances, account numbers, and raw SMS text are stripped before they leave your Mac, not after.
why does an SEC filing come back with exact figures when my own data doesn't?
A filing is a public company disclosure, not personal data, so scrub_system() passes its excerpt through unredacted. Your Messages are yours, so they get vagued into fixed phrases first, and anything you /ingest gets redact.py's full PII pass. Same pipeline, different rules for public disclosure versus personal data.
why doesn't /ingest work in the downloaded app?
Its dependencies (Pillow, pypdfium2, torch, transformers, sentencepiece, and protobuf) aren't bundled into the .dmg. Together they'd take the app from about 150 MB to nearly 1 GB for a feature most people won't use. Run Twin from source (see Install) to get /ingest; everything else works the same either way.
why does an SEC lookup ask for a contact first?
SEC EDGAR requires a contact string in the User-Agent header on every request, as part of its fair-access policy. Set it once with /setup edgar or the SEC_EDGAR_CONTACT environment variable, and lookups stop asking.
does anything leave my Mac?
Depends which provider you picked. With Ollama, chat stays on your Mac, nothing about the conversation leaves it. With a cloud provider, your chat message, your name, today's calendar titles, and a vague transaction summary go to whichever one you picked. Either way, voice, the digest, and nudges are all built and matched on-device and never touch a provider. There's no Twin server to send anything to in the first place.
do I need Ollama running before setup?
No. If it isn't running yet, Twin starts it for you when you pick it, either the app or the ollama serve binary, and waits a few seconds before moving on. You do need Ollama installed first; Twin can't install it for you.
which model should I pick?
If you're not sure, use the one Twin recommends: it's sized to how much RAM your Mac has, from qwen2.5:3b on machines under 12 GB of RAM up to llama3.1:8b on machines with more. If you already use Ollama for other things, any model you've pulled works too, just type its name.
is it offline?
Chat is, once the model is downloaded: that request goes to localhost, not out to the internet. SEC filing lookup, flight search, and /ingest's one-time OCR model download still reach out to their own servers no matter which provider you picked, since none of those go through your AI provider at all.
troubleshooting
| problem | fix |
|---|---|
| Setup says the key didn't work | Check you copied the whole key and picked the matching provider. Out of credits? Check billing with that provider. |
| Twin says your provider didn't accept the key | Type /setup and paste a new key. |
| Setup shows up again on launch | Setup wasn't finished, or ~/.twin/config.json was removed. Finish the steps once. |
| Twin can't read Messages | Give Full Disk Access to Twin (or your terminal), then restart Twin. |
| Hotkey does nothing | Allow Twin (or your terminal) under Accessibility, then restart Twin. |
| "Calendar access isn't allowed yet" | Use the Calendar button on setup, or turn on full access under Privacy & Security → Calendars. |
| "I can only add calendar events right now" | Twin has add-only access. Switch it to Full Access under Privacy & Security → Calendars. |
| Twin can't see your screen | Allow Twin (or your terminal) under Screen & System Audio Recording. |
| Permissions stopped working after rebuilding | Each rebuild gets a new signature. Turn Twin back on in each Privacy & Security list. |
| Twin knows nothing about your spending | Check Full Disk Access, and make sure TWIN_DB_PATH (if set) matches for both the pipeline and Twin. |
| Pipeline inserts 0 transactions | It only reads the last 200 messages, and only bank SMS in the supported formats count. |
| "Ollama isn't installed yet" | Install it from ollama.com/download, or with brew install ollama, then pick Ollama again. |
| "Couldn't reach Ollama" | Open Ollama (or run ollama serve) yourself, then pick Ollama again. Twin tries to start it automatically first and only shows this if that didn't work. |
| "I don't have <model> installed yet" | Run ollama pull <model> in a terminal, or type /setup to pick a different model. |
| "Not enough free disk space for that model" | Free up space, or pick a smaller model. Twin checks before downloading and won't start one that can't finish. |
| "Downloading a model needs an internet connection" | The one-time model download needs internet. Reconnect and try again; chat runs offline once it's done. |
| Download cancelled | The download was cancelled with the Cancel link next to the progress bar. Click Download again to retry. |
limitations
- macOS only.
- The SMS parser targets Indian bank and UPI message formats. Other formats are ignored until someone adds patterns for them.
- The pipeline reads only the 200 most recent messages per run.
- Twin only looks at the five most recent transactions when chatting.
- Each chat message is answered on its own. Twin doesn't remember earlier turns.
- Answers about your screen are vague on purpose, since only a one-sentence summary is sent.
- One ingested document is active at a time. Loading a new one with
/ingestreplaces it; older ones stay stored but out of the conversation until re-ingested. - Filing lookup only searches EDGAR's "recent" filings window, roughly the last year. An older filing of that type comes back as not found even if it exists.
- Donut is fine-tuned on receipts, so extraction quality on other kinds of document photos is weaker.
- Twin only auto-downloads the one model it recommends. A different model has to be pulled yourself with
ollama pull <model>first. - SEC filing lookup, flight search, and
/ingest's one-time OCR model download all need internet, regardless of whether you picked Ollama or a cloud provider. Twin.appships ad-hoc signed, not signed with a paid Developer ID or notarized. That's a deliberate call for a portfolio project, not a gap to fill later.
Still stuck? Open an issue on GitHub.