alfredo
by bchong12
README.md
<p align="center">
<sub>Made by <a href="https://purist.design"><b>Purist</b></a>, the design tool your AI agent can open, read and edit. Alfredo is how we run it.</sub>
</p>
<p align="center">
<img src="docs/wordmark.svg" alt="Alfredo" width="620">
</p>
<p align="center">
<b>Your company's brain, in a database you own.</b>
</p>
<p align="center">
<a href="https://github.com/bchong12/alfredo/actions/workflows/build.yml"><img alt="Builds and runs on macOS, Windows and Linux" src="https://img.shields.io/github/actions/workflow/status/bchong12/alfredo/build.yml?branch=main&style=flat-square&label=macOS%20%C2%B7%20Windows%20%C2%B7%20Linux&labelColor=0a0a0a&color=ededed"></a>
<a href="LICENSE"><img alt="MIT licensed" src="https://img.shields.io/badge/licence-MIT-ededed?style=flat-square&labelColor=0a0a0a"></a>
<img alt="No subscription" src="https://img.shields.io/badge/price-%240%20forever-ededed?style=flat-square&labelColor=0a0a0a">
<img alt="Runs on macOS, Linux and Windows" src="https://img.shields.io/badge/runs-macOS%20%C2%B7%20Linux%20%C2%B7%20Windows-ededed?style=flat-square&labelColor=0a0a0a">
<img alt="Works with any MCP client" src="https://img.shields.io/badge/MCP-any%20client-ededed?style=flat-square&labelColor=0a0a0a">
</p>
<p align="center">
<img src="docs/hero.png" alt="Asking the workspace a question and getting an answer with citations to the doc and the meeting it came from" width="960">
</p>
Alfredo is a Mac app with four tabs, **Board, Docs, Canvas and Meetings**, that
answers questions about everything written in them. It records and transcribes
your meetings on your own machine, and it hands the whole workspace to whatever
AI agent you already use through a local MCP server, so you can say "what did
we decide about pricing" or "put last week's unfinished cards in this cycle" and
mean it. Every agent that speaks MCP connects the same way: Cursor, Codex,
Claude Code, Gemini CLI, VS Code, Zed, your own.
There is no account, no server of ours, and no subscription. Each workspace
lives in a database you choose: a folder on this Mac, your Supabase project, or
your Cloudflare account.
## What it replaces

| Instead of | Alfredo's tab | Their list price, per person, per month |
| --- | --- | --- |
| [Linear](https://linear.app/pricing) Business | Board: cards, cycles, backlog | $16 |
| [Notion](https://www.notion.com/pricing) Business | Docs, and the AI that reads them | $20 |
| [Miro](https://miro.com/pricing/) Business | Canvas: frames, stickies, arrows | $20 |
| [Granola](https://www.granola.ai/) Business | Meetings: transcripts and notes | $14 |
| [Glean](https://www.glean.com/)-class search over all of it | Ask it anything, with citations | $45 to $75, with a seat minimum |
Prices as published in September 2026. A team of ten pays about **$8,400 a
year** for the first four. Alfredo is MIT licensed and costs nothing; the only
bill is the database, which is free on this Mac, free on Supabase and Cloudflare
at the sizes a workspace runs at, and yours either way.
That is not a claim that Alfredo is as good as any of them at their own game.
It is one app, built for a team that would rather own the data and the bill.
## Get it
[**Download the latest release**](https://github.com/bchong12/alfredo/releases/latest).
On a Mac, open the `.dmg` and drag Alfredo into Applications; it is signed and
notarized, so it opens like any other app. On Windows and Linux, unzip it and
run it. The Windows build is not code-signed yet, so SmartScreen asks once:
*More info*, then *Run anyway*.
If someone sent you an invite, open Alfredo, choose **Add workspace**, then
**I have a link**, and paste their link.
## Run it
Needs Node 22 or newer.
```sh
npm install
npm run dev # http://localhost:5210
```
Or build the desktop app for the machine you are on:
```sh
npm run desktop:install # macOS, straight into /Applications
npm run desktop:build:win # Windows, into dist-app/win-unpacked
npm run desktop:build:linux # Linux, into dist-app/linux-unpacked
```
Build it on the platform you want it for: npm only installs the native pieces
(the embedding runtime, the terminal) for the machine doing the installing.
The first screen asks where your first workspace should live. Nothing else is
required to start.
### Where it runs
Most of Alfredo is Node, WebAssembly and a browser, so it runs wherever those
do. Two things are genuinely Apple's, and the app says so rather than pretending:
| | macOS (Apple silicon) | Linux and Windows |
| --- | --- | --- |
| Board, Docs, Canvas, projects, people | Yes | Yes |
| Your database: this machine, Supabase, Cloudflare | Yes | Yes. The local one is PGlite, which is WebAssembly |
| The brain: chunking, embeddings, hybrid search | Yes | Yes. The embedding model has Linux and Windows builds |
| MCP server for your own AI | Yes | Yes |
| Meetings you write yourself | Yes | Yes |
| A packaged desktop app | Yes | Yes. Built by CI on each one |
| **Recording a meeting** | The room and the call together, through a small ScreenCaptureKit helper | The microphone, always. The call as well on Windows if a loopback device is installed (see below) |
| **Transcribing it here** | Parakeet through FluidAudio (CoreML) | Parakeet through ONNX Runtime, the same weights |
#### Hearing both sides of a call
Both operating systems will hand a program the audio they are playing, and
Alfredo asks each one in its own way, records it alongside the microphone, and
mixes the two into a single track.
| | how | build it with |
| --- | --- | --- |
| macOS | ScreenCaptureKit, in `native/alfredo-audio` | `sh native/alfredo-audio/build.sh` (needs Xcode's command line tools) |
| Windows | [WASAPI loopback](https://learn.microsoft.com/en-us/windows/win32/coreaudio/loopback-recording), in `native/alfredo-audio-win` | `native\alfredo-audio-win\build.ps1` from a Developer PowerShell (needs the MSVC build tools) |
Both helpers take the same arguments and answer with the same words, so the
server does not know which one it is talking to. Neither needs a virtual cable
or a driver. CI compiles both on every push, and runs the Windows one.
Without a helper, ffmpeg records the microphone alone, because DirectShow
cannot see the render side by itself. Alfredo still picks up a loopback device
if you happen to have one (VB-Cable, `virtual-audio-capturer`, Stereo Mix), and
Meetings says on the page which of the two it is going to record, rather than
letting you find out after an hour. On Linux, route the call into a PulseAudio
monitor source and point `CRM_AUDIO_DEVICE` at it.
#### Transcription, on whatever machine you have
[Parakeet](https://huggingface.co/nvidia/parakeet-tdt-0.6b-v3) is NVIDIA's
model, not Apple's, and Alfredo reaches it two ways:
| | through | download |
| --- | --- | --- |
| macOS, Apple silicon | [FluidAudio](https://github.com/FluidInference/FluidAudio), CoreML | built once by `scripts/build-parakeet.sh` |
| Windows, Linux, Intel Macs | ONNX Runtime, which Alfredo already carries for its embeddings | the [ONNX export](https://huggingface.co/istupakov/parakeet-tdt-0.6b-v3-onnx), about 640 MB, fetched by the Download button in Meetings |
Same weights, same `parakeet-tdt-0.6b-v3`, same machine doing the work. The
ONNX side is three graphs, run in `server/parakeet-onnx.ts`: log-mel features,
the conformer encoder, then greedy TDT decoding, which is RNN-T with the joint
network also saying how many frames to skip. Long recordings go through in
thirty-second pieces that overlap by two seconds, and a piece starts at the
first whole word after the overlap, so nothing is heard twice and no word is
cut in half at a seam.
Set `ALFREDO_TRANSCRIBE=onnx` on a Mac to use that path instead of CoreML, or
`hosted` to skip local transcription entirely.
Secrets live in the macOS keychain where there is one, and in a `0600` file
beside the workspace registry where there is not.
Checked on every push, on all three: `.github/workflows/build.yml` installs
Alfredo on macOS, Windows and Linux, type-checks it, builds it, packages the
desktop app, compiles both system-audio helpers and runs the Windows one, and
then runs `scripts/smoke.mjs`, which starts the server, makes a workspace,
writes a doc, a card and a meeting into the local database and reads them back,
reads the workspace into the brain and asks a question of it (which is the
embedding model running on that machine), and asks the MCP server for its
tools. Run it yourself with
`npm run build:server && node scripts/smoke.mjs`.
## Everything goes through the database

There is no Alfredo backend. The app talks to one database per workspace, and
every last thing it knows lives in there: cards, docs, canvases, meetings,
people, roles, invitations, which tabs the workspace has, and the passages the
brain searches. Switching workspace switches database.
| Where | Good for | Setup | Enforced by |
| --- | --- | --- | --- |
| **This Mac** | Yourself, offline | None. A folder under `~/.alfredo/workspaces` | Nothing to enforce: one person |
| **Supabase** | A team | Paste a personal access token, Alfredo creates the tables | Postgres row-level security |
| **Cloudflare D1** | A team, on your own account | One click with your `wrangler` login | A Worker that owns the only door to D1 |
- Supabase: `db/schema.sql` and `db/policies.sql`, both safe to run again.
- Cloudflare: `cloudflare/schema.sql` and `cloudflare/worker.mjs`.
- This Mac: the same schema, in PGlite (Postgres compiled to WebAssembly).
Because the rules live in the database, the app is not what stands between a
teammate and a project they are not in. An invited teammate's copy of Alfredo
holds no secret key at all: it connects with the publishable key and their own
sign-in, and the list of projects they see is Postgres's answer, not the app's.
Moving a workspace between databases is a matter of pointing Alfredo at the
other one; nothing of yours is left behind on a machine of ours, because there
isn't one.
## The brain: asking your own writing

Everything written in a workspace is cut into passages, embedded, and kept in a
`chunks` table beside the work itself. A question searches those passages two
ways at once and hands the best of them to the agent on your machine, which
answers from them and says where each claim came from.
**Reading it in.** A doc, a meeting's notes and transcript, a card or the words
on a canvas get cut on the writing's own headings, about 380 tokens a piece with
60 tokens of overlap, and every piece carries its title and heading path in
front of it, so a paragraph that says "we went with the second option" still
knows what it is an option about. Retrieval lives or dies on that, far more than
on the model. New work is read in as it is saved; everything from before is read
in once, from Settings, Models (or `read_workspace_in` over MCP).
**The model.** `bge-small-en-v1.5`, quantised to int8: 384 numbers a passage,
about 34 MB, downloaded on first use and then never online again. It is small on
purpose. What makes retrieval good is the chunking, a lexical pass beside the
vectors, and fusing the two.
**Searching.** Two lists, fused by reciprocal rank. The vectors find the
passage that means what you asked even when it uses different words; full-text
finds the exact part number, name or phrase that vectors always miss. Fusing
them means neither has to be right on its own.
**Answering.** The coding agent on this Mac, from those passages only, with the
bracketed number of each passage it used. When the workspace does not say, it
says so rather than filling the gap. There is no hosted fallback: a company's
own writing is the last thing to hand to a service nobody chose.
**What it may find.** The search runs as whoever is asking, through the same
row-level security as the work, so a project you are not in cannot be quoted at
you. Ask inside a project and the answer stays inside it.
⌘K takes a question as well as a name. The citations are chips: click one and
the doc or meeting opens.
## Meetings

Press **Transcribe**. Alfredo records the room and what the call is playing,
turns it into text on this Mac with [Parakeet](https://github.com/FluidInference/FluidAudio),
and deletes the audio the moment a transcript exists. The agent on this Mac then
writes the note: summary, decisions, open questions, and action items with owners and
dates resolved (nobody lets the model do date arithmetic; it says "by Friday"
and the code works out which Friday). The meeting is saved to your database and
read into the brain, so the next question can quote it.
Meetings offers to download Parakeet the first time, about 600 MB, needing
Xcode's command line tools. No Parakeet and no agent still leaves you a working
tab: **New meeting** makes one you type the notes into yourself.

The transcript is one click away, and it is the only copy of the meeting that
survives:

Connect Google Calendar in Settings, Connections, and what is coming up appears
above the list with a Transcribe button on each.
## Every AI agent, the same way
Alfredo runs a plain [MCP](https://modelcontextprotocol.io) server over
streamable HTTP at `http://127.0.0.1:29981/mcp`. It is a local MCP: there is
nothing in it that belongs to one vendor, so any agent that speaks MCP can list
the tools and call them.
```json
// Cursor (~/.cursor/mcp.json), VS Code, Zed, Windsurf, Goose and the rest
// take the same thing in their own config file:
{ "mcpServers": { "alfredo": { "url": "http://127.0.0.1:29981/mcp" } } }
```
```toml
# Codex (~/.codex/config.toml)
[mcp_servers.alfredo]
url = "http://127.0.0.1:29981/mcp"
```
```sh
# Claude Code
claude mcp add --transport http alfredo http://127.0.0.1:29981/mcp
```
A client that only speaks stdio can bridge with
`npx mcp-remote http://127.0.0.1:29981/mcp`. Add the header
`x-workspace: <id>` to pin one workspace; otherwise calls go to the active one.
### Handing it a piece of the workspace
Every copy button in Alfredo (on a card, a column, a whole cycle, a doc, a
canvas, a meeting's notes or its transcript) copies the thing **and** the way
back to it:
```
Alfredo · Acme · meeting “Pricing review (notes)”
Read it again: ws_read kind:"meeting" id:"2344c5c0-…" (MCP header x-workspace: acme)
Change it: ws_write kind:"meeting" id:"2344c5c0-…"
## Summary
The team plan price will rise to $24 per seat starting in March…
```
Paste that anywhere. An assistant with Alfredo connected can follow the handle
to read the rest or write back; one without it still has the text, which is
what pasting usually means anyway.
Two of the tools matter most, and they split along exactly this line:
- **`search_workspace`** hands back the passages and where each came from, and
nothing else. Whatever model your client runs writes the answer. This is the
one to use from Cursor, Zed, VS Code or your own SDK client.
- **`ask_workspace`** writes the answer as well, using the coding agent on this
Mac. Nothing is sent to a service of ours: a company's own writing only ever
goes to an agent you installed.
Then talk to it:
> "Connect my Supabase project Acme to a new workspace called Acme."
> "What did we decide about the team plan price, and which meeting was it?"
> "Make a project called Website, put Neil in it as an editor, and move the
> three pricing docs into it."
> "Read the whole workspace in, then tell me what is unfinished this cycle."
| Tool | Does |
| --- | --- |
| `list_workspaces` | Every workspace and where it lives |
| `ws_list`, `ws_read`, `ws_write` | Cards, docs, canvases, meetings, members |
| `get_workspace`, `set_workspace_tabs` | The workspace's name and its tabs JSON |
| `ask_workspace`, `search_workspace` | Answer from the workspace's own writing, with citations |
| `read_workspace_in`, `brain_status` | Read everything in, and see how far it got |
| `list_projects`, `create_project`, `set_project_people`, `move_to_project`, `set_projects_enabled` | Projects, and who is in them |
| `invite_person` | An invite link to send someone |
| `list_pack_data`, `get_pack_data`, `set_pack_data` | Data for tab types you add yourself |
| `list_supabase_projects`, `connect_supabase` | Add a Supabase workspace |
| `get_setup_sql`, `setup_supabase_tables` | Create or upgrade its tables |
| `connect_cloudflare`, `create_cloudflare_workspace`, `get_cloudflare_deploy` | Add a Cloudflare workspace |
The server binds to 127.0.0.1, refuses browser origins and foreign hosts, and
holds database keys, so it never listens on the network.
### Do you need an AI at all?
No. The board, docs, canvas, meetings, projects and search all work with no AI
of any kind. Transcription is Parakeet, on your Mac. Search is the embedding
model, on your Mac.
Two things use a model, both through the agent on your machine: writing up a
meeting, and writing the prose answer to a question. Without it you still get the
transcript and the passages, and any MCP client can turn those into an answer
with its own model.
## People, projects and who may see what
Whoever connects the database and makes the first account runs the workspace.
They invite the rest with a link, put each person in the projects they need, and
give each a role there: runs it, can edit, or read only. Admin can be handed to
someone else. All of it is rows in the workspace's database, so it travels with
the workspace rather than with a Mac.
**Projects** are off until a workspace turns them on. They split one workspace
into separate boards, docs, canvases and meetings inside the same database, and
you switch between the ones you are in, under the workspace name. There is no
"all projects" view: you see the projects you were let into.
**Inviting someone.** Settings, Members, type their email, choose what they
may do and which projects they are in, then **Copy the invitation**. That puts
a whole message on your clipboard, not a bare token: where to download Alfredo,
what to do with the link, and which address to sign in with. Send it however
you like; Alfredo has no mail server and never will.
The link itself carries where the workspace is and nothing secret: the
publishable key and the invitation, both safe to read. What they can see once
they are in is the database's decision, not the link's. It works once, for that
address, and stops working in two weeks.
**Sign in with Google** works on Supabase workspaces whose project has the
Google provider on. Google will not sign anyone in inside an app window, so
Alfredo opens the system browser and catches the answer on
`http://127.0.0.1:29981/auth/callback`; add that to the project's redirect URLs.
## Making it yours
**Tabs are data, not code.** Every workspace keeps a tabs JSON in its own
database. Rename them, hide them, reorder them, or add your own:
```json
[
{ "id": "board", "type": "board", "name": "Board", "columns": ["Todo", "In progress", "Review", "Done"] },
{ "id": "docs", "type": "docs", "name": "Docs" },
{ "id": "canvas", "type": "canvas", "name": "Canvas" },
{ "id": "meetings", "type": "meetings", "name": "Meetings", "hidden": true }
]
```
Rename the board's columns per workspace with `columns`. Any other `type` is a
**pack**: a folder in `src/v2/packs/<name>/` whose `index.ts` exports `tabs`,
keyed by type. A pack's data goes in the workspace's database through the pack
data tools, never in the code, so a tab you invent travels with the workspace
like everything else.
**Cycles** run 1, 2 or 4 weeks. When one ends you decide what happens to
unfinished work: ask every time, roll it into the next cycle on its own, or send
it back to the backlog. Whatever the setting, **Carry over** on the board moves
what is unfinished whenever you say so, into the next cycle, the one running
now, or the backlog.
**The canvas** is React Flow JSON, so a board made elsewhere in that format
opens unchanged. Frames carry what is inside them, the hand tool pans from
anywhere, scroll pans and ⌘-scroll zooms.
**Docs** list as a grid or a table, whichever you leave it on.
## What leaves your machine
Nothing, unless you ask for it.
| | Where it happens |
| --- | --- |
| Transcription | Parakeet, on this Mac. Only if you have no Parakeet *and* you set `OPENROUTER_API_KEY` does audio go to a hosted model instead |
| Meeting write-ups | The coding agent on this Mac, or not at all |
| Answers from the brain | The coding agent on this Mac, or not at all |
| Embeddings | On this Mac, by a model downloaded once |
| Your work | The database you chose, and nowhere else |
Keys and tokens live in the macOS keychain. `.env.local` is optional; see
`.env.local.example`.
## Building on it
```sh
npm run dev # app on 5210, server on 29982
npm run build # the web build
npm run desktop:install # build the Mac app and put it in /Applications
npm run db:push # apply db/schema.sql and db/policies.sql to a Supabase project
```
The app is Svelte 5 with runes. The server is Hono, one route file per
area under `server/`, with a `Store` interface each database kind implements
(`SqlStore` for Supabase and PGlite, `CloudflareStore` over the Worker), so a
feature is written once and works on all three.
## License
MIT. See [LICENSE](LICENSE).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues