Link copied ✓
calexo-meet · documentation

Your meetings,
summarized on your Mac.

calexo-meet is a private, local-first AI meeting assistant — a Tauri fork of Meetily. It captures system + microphone audio, transcribes with Whisper or Parakeet, and summarizes with a local or cloud model, all in one desktop binary. No server, no cloud database. Start with the tutorial, or read how it's built.

Tauri 2 · Rust core Next.js 14 · React 18 Whisper.cpp · Parakeet on-device by default
100%
on-device default
5
LLM providers
3
built-in recipes
0
servers to host
2
STT engines
1
desktop binary

Where to go

The one-line mental model

Audio in → transcript and summary out. Nothing crosses the network unless you explicitly point it at a cloud model or ask it to draft an email. Everything else lives on your disk.

how it works · reference

One desktop binary.
Your meetings never leave.

Three layers compiled into one downloadable app. The UI never talks to a network backend of ours — it talks to the Rust core over Tauri's command/event bridge. This page explains what every part does.

01

The shape of the app

One binary, three layers. The Next.js UI calls Rust commands and listens for Rust events; the Rust core owns audio, storage, transcription, and summary orchestration.

┌──────────────────────────────────────────────────────────────────┐
  Next.js UI  (React / TypeScript, port 3118 in dev)             
  pages · components · ConfigContext · SidebarProvider             
└───────────────┬──────────────────────────────────▲───────────────┘
                invoke('cmd')                      emit('event')
┌───────────────▼──────────────────────────────────┴───────────────┐
  Rust core  (Tauri 2 · src-tauri/src)                           
  audio · whisper_engine · parakeet_engine · summary · database    
  mcp · email · ollama/anthropic/groq/openai/openrouter clients    
└───────────────┬──────────────────────────────────────────────────┘
                
       on-device                           off-device (opt-in) ▾
   SQLite · Whisper · Parakeet · Ollama      Claude · Groq · Gmail draft

lib.rs The wiring

core

Everything registers here. src-tauri/src/lib.rs is the Tauri entry point — it builds the app, holds shared state, and registers every #[tauri::command] the frontend can call. state.rs holds shared handles, config.rs reads/writes settings, tray.rs the menu-bar icon, onboarding.rs the first-run flow.

src/lib.rsstate.rsconfig.rstray.rsonboarding.rs
02

Audio pipeline

The app sits on top of your meeting — it never joins as a bot. It captures system audio plus your mic, then splits into two parallel paths: one for a clean recording, one for transcription.

          Mic (cpal)          +          System audio (Core Audio process tap)
                 └──────────────┬──────────────┘
                      AudioPipelineManager (pipeline.rs)
            ┌──────────────────────┴──────────────────────┐
            Recording path                       Transcription path
       RMS-ducked pro mix                      VAD — speech only
                                                  
       recording_saver.rs                      whisper / parakeet
       → .wav on disk                          → transcript-update events

Capture

on-device

macOS captures system audio with a Core Audio process tap — no virtual device like BlackHole needed. It does require the system Audio Capture permission (macOS 14.4+). Windows uses WASAPI loopback; Linux ALSA/PulseAudio.

audio/capture/core_audio.rsdevices/discovery.rspermissions.rs

Mix & VAD

on-device

pipeline.rs aligns the two streams in a ring buffer (50 ms windows), applies RMS-based ducking so system audio doesn't drown your voice, and runs Voice Activity Detection so only speech reaches the transcriber.

audio/pipeline.rsvad.rsrecording_manager.rs

MicOnly mode & recording commands

opt-in

An opt-in Microphone-only mode drops system audio at the source for sensitive calls. The frontend calls start_recording_with_devices_and_meeting with mic_device_name, system_device_name, and meeting_name (all optional — system audio is optional and falls back to mic-only if unavailable).

audio/recording_commands.rsrecording_preferences.rsrecording_state.rs
03

Transcription engines

Two interchangeable local speech-to-text engines. Both run on your machine and GPU-accelerate automatically, falling back to CPU.

Whisper.cpp

on-device

The default. whisper_engine.rs loads a GGUF model once and caches it, auto-detecting Metal / CUDA / Vulkan. The default model is large-v3-turbo; smaller and quantized variants (base, small, medium) are available too.

whisper_engine/whisper_engine.rsacceleration.rscommands.rs

Parakeet

on-device

An alternative engine with its own model manager and download UI. Same contract — speech in, timestamped transcript out — selectable in Transcript Settings.

parakeet_engine/parakeet_engine.rsmodel.rsParakeetModelManager.tsx

Model storage

On macOS, models live in a models/ folder inside the app's data directory (~/Library/Application Support/<app bundle id>/models/). Models are managed in-app; switching one needs a reload.

04

Storage & state

Everything persists to a single local SQLite database via sqlx migrations. No cloud, no external DB.

database/ The local store

on-device

A repository pattern over SQLite: manager.rs owns the pool, repositories/ wrap each entity, models.rs defines the rows, setup.rs runs migrations on launch. Meetings support soft-delete — deleting moves to Trash (reversible); "delete forever" purges with cascade.

database/manager.rsrepositories/models.rssetup.rs

Frontend state mirror

UI

SidebarProvider and ConfigContext hold the React-side mirror of meetings, recording status, and settings. The loop: command mutates Rust state → Rust emits an event → a listener updates React → context propagates to components.

Sidebar/SidebarProvider.tsxcontexts/ConfigContext.tsx
05

Summaries & Recipes

The transcript runs through a pluggable LLM to produce a structured summary. You pick the provider; the default is local Ollama so nothing has to leave the machine.

summary/ The summary path

local default

processor.rs + service.rs orchestrate it; llm_client.rs abstracts the provider; templates/ shape the prompt. Output is markdown, streamed back to the UI's AISummary panel.

Recipes (recipes.rs) are three fixed-prompt actions — Follow-Up Email, Action Items, Coach Mehard-forced onto local Ollama (the recipe provider is a constant, regardless of your summary settings) so transcripts never leave the device. Summary bullets that emit [mm:ss] become clickable seek links to the nearest transcript moment.

summary/processor.rsservice.rsllm_client.rsrecipes.rs

Providers — keys are entered per-user in Model Settings and stored locally; they are never baked into a build.

Provider
What it is
Key
Ollama
Local, on-device, free. The default. Recipes are locked to it.
none
Claude
Anthropic — best-quality summaries, ~pennies/meeting.
claude
Groq
Very fast hosted inference.
groq
OpenRouter
Many models behind one key.
openrouter
OpenAI
GPT models; also a custom OpenAI-compatible endpoint.
openai

Where keys live

Cloud keys are optional and typed into Model Settings, saved on-device — not environment variables in a deployment. Using a cloud provider is the one summary path where transcript text leaves your machine; Ollama keeps everything local.

06

MCP server

A loopback-only server that lets an MCP client — like Claude — read your meetings, transcripts, and summaries and work alongside the app.

mcp/ Loopback, bearer-authed

loopback

server.rs serves HTTP/JSON-RPC on 127.0.0.1; tools.rs exposes read methods over the same SQLite DB; auth.rs enforces a bearer token with constant-time comparison, regenerated every launch. A fallback_server.rs covers the path where the primary stack is unavailable.

get_mcp_connection_info returns a ready-to-paste mcp.json snippet with the URL and bearer header.

mcp/server.rstools.rsauth.rscommands.rsfallback_server.rs
07

Gmail email

The one deliberate, consent-gated step off-device — built defensively. It creates a Gmail draft from a meeting's follow-up. It never auto-sends; no send endpoint exists.

email/ Draft-only by design

off-device · gated

Narrowest scope (gmail.compose), an explicit consent modal, no PII stored, and the OAuth refresh token kept in the OS keychain (service calexo-meet-gmail). oauth.rs runs a desktop loopback OAuth flow, gmail.rs calls create_draft, compose.rs builds the message.

End-to-end use needs one credential you create: a Google Cloud OAuth client (Desktop app) with the Gmail API enabled, set as GMAIL_OAUTH_CLIENT_ID / GMAIL_OAUTH_CLIENT_SECRET (or a gmail-oauth.json store file).

email/oauth.rsgmail.rscompose.rscommands.rs
08

The UI

A Next.js 14 app rendered inside the Tauri window — a thin, reactive shell over the Rust core.

Routes

app/

/ — the recording interface · /meeting-details — transcript + summary + recipes · /settings — providers, models, transcript engine · /notes — note views.

app/page.tsxmeeting-details/settings/

Components

components/

Sidebar (meeting list, Trash, recording state) · MeetingDetails + AISummary · Recipes · model managers · onboarding · Settings.

components/Sidebar/MeetingDetails/Recipes/
09

Run, build & ship

Because it's a desktop app, "deploying" means producing a signed .dmg and putting it on a GitHub Release — there's nothing to host.

develop

Run it locally

From frontend/: ./clean_run.sh (info) or ./clean_run.sh debug. Under the hood that's pnpm run tauri:dev, with GPU variants like tauri:dev:metal. Next.js serves on port 3118 inside the Tauri shell.

build

Produce the artifacts

./release-macos.sh builds a signed + notarized .dmg plus updater artifacts. UNSIGNED=1 ./release-macos.sh makes a working unsigned .dmg for trusted testers today.

distribute

Cut a GitHub Release

Attach the .dmg, the .app.tar.gz + .sig updater payload, and a latest.json. The updater endpoint in tauri.conf.json points at releases/latest/download/latest.json, so installed apps auto-update.

Two one-time setup items before a signed release

① Apple Developer Program ($99/yr) + a "Developer ID Application" cert so Gatekeeper doesn't block the app. ② A Tauri updater signing keypair — generate it and replace the pubkey placeholder in tauri.conf.json. Full steps live in RELEASE.md.

interactive tutorial

From zero to your first
summarized meeting.

Eight short steps to a working setup — install, permissions, devices, recording, transcription, summaries, Recipes, and connecting Claude over MCP. Click through at your own pace; your progress is saved in this browser.

build status · this fork

Five features,
shipped to main.

A private, local-first fork of Meetily — extended with the best of Granola, built in gated phases and verified on a real full-app build. Here's everything new and the short list that still needs you.

5
features
22
new files
36
tests pass
0
failing
4
review rounds
1
cred to add
01

What shipped

Five Granola-inspired features. Four are fully local-first; the fifth is the one deliberate, consent-gated step off-device.

F1 Read-only MCP server

donetests 7/7

A loopback-only HTTP/JSON-RPC surface so Claude can read your meetings, transcripts, and summaries. Bearer auth with a token regenerated every launch. Read-only by construction.

src/mcp/server.rstools.rsauth.rs

F2 No-join · MicOnly capture

donetests 4/4

Confirmed the app never joins a meeting as a bot — it records system audio from on top. Added an opt-in Microphone-only mode that drops system audio at the source.

audio/pipeline.rsrecording_preferences.rs

F3 Soft-delete Trash

donetests 3/3

Deleting a meeting moves it to Trash instead of destroying it. Restore brings it back; "Delete forever" purges with cascade.

migration soft_deleteSidebar/TrashView.tsx

F4 Recipes & timestamp citations

donetests 5/5

Three built-in Recipes — Follow-Up Email, Action Items, Coach Me — hard-forced onto local Ollama. Bullets that emit [mm:ss] become clickable seek links.

summary/recipes.rsRecipes/RecipesPanel.tsx

F5 Gmail email · draft-only

code doneneeds 1 credentialtests 17/17

The only off-device feature. After a meeting, optionally create a draft in Gmail (never auto-sends). Narrowest scope, explicit consent, token in the OS keychain. Needs one OAuth credential you create.

src/email/oauth.rsgmail.rsEmailConsentModal.tsx
02

Roadmap

Where this fork is headed — grounded in the feature plan and the code, not aspiration. Three horizons, no fixed dates. Each item links back to where it's tracked.

Now
Backend done — surfacing it in the UI.
MCP settings panel
The get_mcp_connection_info command already returns a paste-ready mcp.json. What's left is a Settings screen to surface it, so connecting Claude is one click instead of a dev command.
tracked in GRANOLA-FEATURE-PLAN.md · backend in mcp/commands.rs
Next
Designed and scaffolded — scoped for soon.
Persistent diagnostics log
Broken [mm:ss] citations log to the console and memory today. Next is an on-disk diagnostics file plus a Settings view, so transcription-timestamp issues survive restarts.
stub in AISummary/brokenTimestampLog.ts (TODO v2)
Email dispatch status-machine
A persisted email_dispatched_at column so a "draft created" badge survives restarts. v1 shipped a lighter booleans-only store; this finishes the durable version.
scoped in GRANOLA-FEATURE-PLAN.md (Feature 5)
Later
In the plan, deliberately deferred past v1.
Custom & cloud Recipes
v1 Recipes are Ollama-locked by design. v2 adds custom recipes and an opt-in to run them against your chosen cloud provider — with the off-device warning that implies.
GRANOLA-FEATURE-PLAN.md · "custom/cloud recipes deferred to v2"
Structured timestamp citations
Move from parsing [mm:ss] out of markdown to structured {text, timestamp} bullets from recipes — a cleaner, more Granola-faithful citation model.
GRANOLA-FEATURE-PLAN.md · committed v2 path
Source labeling (mic vs system)
Cheap, honest labeling of who's on mic vs system audio — not over-claiming speaker identification, just marking the source.
GRANOLA-FEATURE-PLAN.md · "source-labeling deferred to v2"
Saved email recipient (keychain)
An opt-in to persist your email address in the OS keychain (Option B). v1 ships the privacy-default: never store it. The Settings stub is already present, disabled.
GRANOLA-FEATURE-PLAN.md · "Option B" stub
Now — in progress Next — scoped Later — planned, deferred

Scope note

This is this fork's roadmap. Upstream Meetily PRO features (speaker identification, chat-with-meetings, calendar integration) are a separate commercial track and aren't part of calexo-meet's plan. Larger Granola features (Briefs, team Spaces, semantic search) are noted as intentionally out of scope in the feature plan.

03

What needs you

The code is done and merged. These are the things only you can do — credentials and account settings.

Gmail OAuth credential

action

The email feature is wired but to reach Gmail it needs an OAuth client only you can create: Google Cloud → new project → enable the Gmail API → create a Desktop app OAuth client → set GMAIL_OAUTH_CLIENT_ID and GMAIL_OAUTH_CLIENT_SECRET.

Code-signing for distribution

action

To ship Gatekeeper-clean installs: enroll the Apple Developer Program ($99/yr), generate a Tauri updater signing key, set the signing env vars. Test unsigned today with UNSIGNED=1 ./release-macos.sh.

04

Deferred · not gaps

Conscious cuts. None block the shipped features from working.

MCP settings panel

v2

The get_mcp_connection_info command exists (returns a ready-to-paste snippet), but there's no Settings UI surfacing it yet.

Persistent diagnostics log

v2

Broken [mm:ss] citations log to console + memory. A persistent on-disk diagnostics file is the planned next step.