A fast, offline-first Rust CLI for syncing and searching Granola meeting transcripts
Muesli syncs your Granola meeting transcripts to local markdown files and provides powerful search capabilities including full-text search (BM25) and semantic search (embeddings).
- 🔄 Sync transcripts - Download and convert to clean markdown with frontmatter
- 🔍 Full-text search - Fast BM25 search with Tantivy
- 🧠 Semantic search - Meaning-based search using e5-small-v2 embeddings
- 📝 AI summaries - Generate structured summaries with OpenAI
- 🚀 Fast & offline - All search happens locally, no API calls
- 💾 XDG compliant - Follows XDG Base Directory specification
- 🔒 Secure - API tokens in keychain (macOS) or environment variables
Download the latest release for your platform:
# macOS (Apple Silicon)
curl -L https://github.com/harperreed/muesli/releases/latest/download/muesli-macos-aarch64 -o muesli
chmod +x muesli
sudo mv muesli /usr/local/bin/
# macOS (Intel)
curl -L https://github.com/harperreed/muesli/releases/latest/download/muesli-macos-x86_64 -o muesli
chmod +x muesli
sudo mv muesli /usr/local/bin/
# Linux
curl -L https://github.com/harperreed/muesli/releases/latest/download/muesli-linux-x86_64 -o muesli
chmod +x muesli
sudo mv muesli /usr/local/bin/
# Windows
# Download muesli-windows-x86_64.exe from releases page# Install Rust (if needed)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Install directly from GitHub (all features)
cargo install --git https://github.com/harperreed/muesli.git --all-features
# Or install with specific features
cargo install --git https://github.com/harperreed/muesli.git --features index,summariesgit clone https://github.com/harperreed/muesli.git
cd muesli
# Install to PATH with all features
cargo install --path . --all-features
# Or build without installing
cargo build --release --all-features
# Binary is at target/release/muesli# 1. Set your Granola API token
export GRANOLA_TOKEN="your-token-here"
# Or let muesli read from ~/.granola/supabase.json
# 2. Sync your transcripts
muesli sync
# 3. Search (full-text)
muesli search "quarterly planning"
# 4. Search (semantic - meaning-based)
muesli search --semantic "improving team collaboration"# Sync all transcripts (updates only changed documents)
muesli sync
# Force rebuild text search index without re-downloading
muesli sync --reindexSynced files are stored in:
- Transcripts:
~/.local/share/muesli/transcripts/(markdown) - Raw data:
~/.local/share/muesli/raw/(JSON) - Indexes:
~/.local/share/muesli/index/(search indexes)
Full-text search (keyword matching with BM25 ranking):
# Basic search
muesli search "machine learning"
# Limit results
muesli search "product roadmap" -n 5
# Multi-word queries
muesli search "Q1 planning meeting"Semantic search (meaning-based with embeddings):
# Find conceptually similar documents
muesli search --semantic "team productivity improvements"
# Works with questions
muesli search --semantic "how do we handle customer feedback"
# Finds related concepts (not just exact keywords)
muesli search --semantic "innovation strategy" -n 10# List all synced documents
muesli listOutput format: <doc-id> <date> <title>
# Download a specific document by ID
muesli fetch <doc-id># Set OpenAI API key (macOS - stores in Keychain)
muesli set-api-key sk-...
# Or use environment variable
export OPENAI_API_KEY="sk-..."
# Generate summary for a document
muesli summarize <doc-id>Summaries include:
- Key topics discussed
- Action items
- Decisions made
- Follow-up items
# Show current configuration
muesli set-config --show
# Change the OpenAI model
muesli set-config --model gpt-4o
# Set context window size (in characters)
muesli set-config --context-window 8000
# Use a custom prompt file
muesli set-config --prompt-file /path/to/prompt.txtMuesli can run as a Model Context Protocol server, allowing AI assistants like Claude to search and access your meeting transcripts.
# Start the MCP server
muesli mcpConfigure in your AI assistant's MCP settings to enable transcript search and retrieval.
All features are enabled by default. If you need a smaller binary, you can disable features:
| Feature | Description |
|---|---|
index |
Full-text search (Tantivy) |
embeddings |
Semantic search (ONNX, e5-small-v2) |
summaries |
AI summaries (OpenAI) |
mcp |
MCP server for AI assistant integration |
# Default build (all features, ~21MB)
cargo build --release
# Core only (sync, list, fetch - ~5MB)
cargo build --release --no-default-features
# With only text search (~9MB)
cargo build --release --no-default-features --features index
# With semantic search (includes text search, ~17MB)
cargo build --release --no-default-features --features embeddings
# With summaries (~11MB)
cargo build --release --no-default-features --features summariesMuesli looks for your Granola API token in this order:
--tokenflagGRANOLA_TOKENenvironment variable~/.granola/supabase.json(Granola desktop app location)
Override the default data directory:
muesli sync --data-dir /custom/pathBy default, muesli throttles API requests (500-1000ms between calls) to be respectful to the Granola API.
# Disable throttling (not recommended)
muesli sync --no-throttle
# Custom throttle range (min:max in milliseconds)
muesli sync --throttle-ms 200:400- Fetches document list from Granola API
- Checks local cache to determine which documents need updating
- Downloads updated documents (metadata + transcript)
- Converts to clean markdown with YAML frontmatter
- Writes atomically to disk (crash-safe)
- Updates search indexes (if features enabled)
- Documents are indexed with Tantivy during sync
- Search uses BM25 ranking algorithm (like Elasticsearch)
- Searches both title and body fields
- Results ranked by relevance
- Downloads e5-small-v2 model from HuggingFace (~133MB, cached locally)
- Generates 384-dimensional embeddings for each document during sync
- Stores vectors in binary format (~1.5KB per document)
- Search uses cosine similarity for meaning-based matching
- Finds related concepts even without keyword matches
- Rust 1.70+ (install via rustup)
- Granola API access
# Clone repository
git clone https://github.com/harperreed/muesli.git
cd muesli
# Run tests
cargo test
# Run tests with all features
cargo test --all-features
# Run integration tests
cargo test --test workflow_integration --features index,embeddings
# Build debug binary
cargo build
# Run with logging
RUST_LOG=debug cargo run -- syncmuesli/
├── src/
│ ├── api.rs # Granola API client
│ ├── auth.rs # Token resolution
│ ├── cli.rs # Command-line interface
│ ├── convert.rs # Transcript → Markdown
│ ├── error.rs # Error types
│ ├── lib.rs # Library exports
│ ├── main.rs # Binary entry point
│ ├── model.rs # Data structures
│ ├── storage.rs # File I/O and paths
│ ├── sync.rs # Sync orchestration
│ ├── util.rs # Helpers
│ ├── index/
│ │ └── text.rs # Tantivy full-text search
│ ├── embeddings/
│ │ ├── downloader.rs # Model download
│ │ ├── engine.rs # ONNX embedding generation
│ │ └── vector.rs # Vector store and search
│ └── summary.rs # OpenAI integration
├── tests/
│ ├── api_integration.rs # API mocking tests
│ └── workflow_integration.rs # End-to-end tests
└── docs/
├── IMPLEMENTATION_STATUS.md
└── plans/
# Format check
cargo fmt --all -- --check
# Clippy
cargo clippy --all-features -- -D warnings
# All tests
cargo test --all-features| Operation | Speed | Notes |
|---|---|---|
| Sync (initial) | ~536 docs in 60s | API rate limited |
| Sync (incremental) | ~1s | Only changed docs |
| Reindex | ~538 docs in 1s | From local files |
| Text search | <50ms | BM25 index scan |
| Semantic search | ~200ms | First query (model load) |
| Semantic search | <50ms | Subsequent queries |
| Embedding generation | ~100ms/doc | During sync |
| Build Type | Size | Configuration |
|---|---|---|
| Debug | 120MB | Default dev build |
| Release (default) | 34MB | cargo build --release |
| Release (optimized) | 21MB | LTO + strip (current) |
Optimizations applied:
- Link Time Optimization (LTO)
- Size-optimized (
opt-level = "z") - Debug symbols stripped
- Panic abort (no unwinding)
The text search index needs to be built:
muesli sync --reindexRun sync with embeddings feature to generate vectors:
muesli syncThis is normal on first sync. The e5-small-v2 model (~133MB) is downloaded once and cached. Subsequent syncs only generate embeddings for new documents.
Grant Terminal/iTerm2 keychain access in System Preferences → Privacy & Security.
Contributions welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Write tests for your changes
- Ensure all tests pass (
cargo test --all-features) - Run clippy (
cargo clippy --all-features -- -D warnings) - Format code (
cargo fmt --all) - Commit with conventional commits
- Open a Pull Request
MIT License - see LICENSE file for details
- Built with Tantivy for full-text search
- Embeddings powered by e5-small-v2
- ONNX Runtime via ort
- CLI powered by clap
Built with ❤️ in Rust