Build Granola

YESreplaces $14/mosaves $168/yrback to the verdict

0%0 of 29 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

A local meeting-notes app for your Mac: a menu bar button records system audio and your microphone, whisper.cpp transcribes on your machine, an LLM turns the transcript plus your rough notes into a summary with decisions and action items, and everything lands as Markdown files in a folder you own. Nothing leaves the machine except the optional LLM call.

estimated effort weekendthe files for this build are in the project pack

AppSwift 6, SwiftUI, MenuBarExtra, macOS 14+AudioScreenCaptureKit for system audio, AVAudioEngine for the micTranscriptionwhisper.cpp via the whisper-cli binarySummariesAn LLM API behind a small provider interfaceStorageMarkdown files in ~/MeetingNotes

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. have readythe Mac you have

    Why ScreenCaptureKit audio capture needs macOS 13+, and local transcription is only pleasant on Apple Silicon.

    Get it Apple menu > About This Mac. Update through System Settings > General > Software Update if below 14.

    Verify sw_vers prints ProductVersion 14 or higher

  2. installfree

    Why Swift, SwiftUI and the macOS SDK come with it. The Command Line Tools alone are not enough for a signed menu bar app.

    Get it Install from the Mac App Store (it is large; start the download first). Open it once to accept the licence and install components. open ↗

    Verify xcodebuild -version prints Xcode 16 or higher

  3. installfree

    Why cmake builds whisper.cpp; ffmpeg is the easiest way to inspect and convert audio while you debug Phase 1.

    Get it Install Homebrew from brew.sh, then: brew install cmake ffmpeg open ↗

    Verify cmake --version and ffmpeg -version both print

  4. installfree

    Why The whisper-cli binary is what Phase 2 shells out to.

    Get it git clone https://github.com/ggml-org/whisper.cpp && cd whisper.cpp && cmake -B build && cmake --build build --config Release. The binary is build/bin/whisper-cli. open ↗

    Verify ./build/bin/whisper-cli -h prints usage

  5. have readyfree, 1.5 GB of disk

    Why Without a model file there is no transcription. medium.en is the quality-speed balance for English meetings.

    Get it Inside the whisper.cpp folder: ./models/download-ggml-model.sh medium.en. It saves to models/ggml-medium.en.bin. Use small.en (about 500 MB) if disk or speed is tight.

    Verify ls -lh models/ggml-medium.en.bin shows about 1.5G

  6. API keypay per use; a one-hour meeting summary costs a few cents

    Why Phase 3 sends the transcript to a model for the summary. Without a key the app still records and transcribes; it just skips the summary.

    Get it Anthropic: console.anthropic.com > API Keys > Create Key. OpenAI: platform.openai.com/api-keys > Create new secret key. Copy it once; it is not shown again. Put it in .env, never in the source. open ↗

  7. API keypay per use; Groq has a free tier

    Why An optional fast path when local transcription is too slow on your machine. Local stays the default.

    Get it Groq: console.groq.com > API Keys. OpenAI: the same key as above works for the audio endpoint. open ↗

  8. decidefree

    Why System audio arrives under the Screen Recording permission (no separate audio permission exists); the mic needs its own. Both are granted once per app build.

    Get it macOS prompts on first use. If you dismiss the prompt, the app must be relaunched before it can ask again; otherwise System Settings > Privacy & Security > Screen Recording and > Microphone.

Data model

Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.

One meeting is one folder: `~/MeetingNotes/YYYY-MM-DD-HHMM-<slug>/`
containing `audio.wav`, `transcript.txt`, and `notes.md`. `notes.md` carries YAML
frontmatter (title, date, duration, participants, model used) so the folder stays
greppable and portable with no app-specific index to corrupt.

Environment variables

These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.

VariableNeededExampleWhere the value comes from
NOTES_DIRrequired~/MeetingNotesWhere meeting folders are written. Any folder you sync or back up.
WHISPER_BINrequired/Users/you/whisper.cpp/build/bin/whisper-cliPath to the binary you built.
WHISPER_MODELrequired/Users/you/whisper.cpp/models/ggml-medium.en.binPath to the model you downloaded.
ANTHROPIC_API_KEYsecretoptionalsk-ant-...Anthropic console. Leave empty to skip summaries.
OPENAI_API_KEYsecretoptionalsk-...OpenAI dashboard. Used for summaries if no Anthropic key, and for hosted Whisper if enabled.
LLM_MODELrequiredclaude-sonnet-5Model name for summaries. Anything the provider offers.
GROQ_API_KEYsecretoptionalgsk_...console.groq.com. Enables the hosted transcription fast path.
TRANSCRIBE_MODErequiredlocallocal or hosted. Local is the default and works offline.

The build, in order

  1. Audio capture, proven as a CLI first

    Before any UI exists, prove you can record system audio and the mic into a WAV that whisper-cli accepts. This is the phase that kills the project if skipped.

    1. The CLI target is for this phase; the app target waits until Phase 5. Both share a Swift package with the capture code.

      Files MeetingNotes.xcodeprojSources/Capture/

    2. Build an SCStreamConfiguration with capturesAudio true. SCStream still requires a screen output to be added even for audio only: add one and set minimumFrameInterval to a very large value so no real frames are processed. On macOS 14.2+ you may instead use Core Audio process taps (AudioHardwareCreateProcessTap) for genuine audio-only capture; pick one and say which in a comment.

    3. Do not use SCStreamConfiguration's microphone capture; it has known output-corruption reports. Tap the input node and collect buffers.

    4. Resample explicitly with AVAudioConverter. Not 44.1 kHz, not float, not stereo. whisper-cli accepts 16-bit WAV only; every hour lost to this project is lost here.

    5. terminal
      swift run capture-cli --seconds 30 --out /tmp/test.wav
      afinfo /tmp/test.wav
      /path/to/whisper-cli -m /path/to/ggml-medium.en.bin -f /tmp/test.wav
    done when · tick each as it passes
    watch out
    • Do not tell the user to install BlackHole or Loopback. ScreenCaptureKit delivers system audio under the screen-recording grant with no driver.
    • The permission prompt appears once per launch. If it is dismissed the app must be relaunched; detect the denied state and say so.
  2. Transcription

    A transcript from a WAV, local by default, with progress you can see and a hosted fast path when a key exists.

    1. Process with WHISPER_BIN, WHISPER_MODEL and the audio path. Read the .txt it writes. Capture stderr for progress lines.

      terminal
      whisper-cli -m $WHISPER_MODEL -f audio.wav -otxt -of transcript
    2. A 45-minute meeting on the medium model is minutes of CPU. Parse whisper's progress output into a percentage; a UI that looks frozen will be assumed broken.

    3. When hosted and a Groq or OpenAI key exists, post the WAV to the Whisper endpoint. Local remains the default: the recording should not leave the machine unless asked.

    4. Write the transcript to a temp name and rename on completion. Killing the app mid-transcription must leave the audio intact and no half-written transcript.

    done when · tick each as it passes
  3. Summarization

    A fixed prompt turns the transcript and your jotted notes into a 5-bullet summary, decisions and action items with owners, and long meetings are chunked rather than truncated.

    1. summarize(transcript, notes) returning structured text. Anthropic first; OpenAI as a second implementation selected by which key exists.

    2. Split on paragraph boundaries into chunks under the model's limit, summarize each, then summarize the summaries. Never silently truncate: that produces confident notes about the first third of a meeting.

    3. This is the actual Granola idea: your jottings steer what the summary emphasises.

    done when · tick each as it passes
  4. Notes on disk

    One folder per meeting, plain Markdown with frontmatter, never overwriting.

    1. notes.md: YAML frontmatter (title, date, duration, participants, model), the summary, a divider, then the full transcript.

    2. If the folder exists, suffix a counter. Two meetings in the same minute must produce two folders.

    done when · tick each as it passes
  5. Menu bar app

    Start and stop from the menu bar, jot notes while recording, see recent meetings, and get told exactly what to do when a permission is missing.

    1. A recording indicator that is unmistakable. Wire the capture code from Phase 1.

    2. Text typed during the meeting is passed to the summarizer in Phase 3.

    3. Read NOTES_DIR; newest first.

    4. If Screen Recording or Microphone is denied, show which System Settings pane to open and the relaunch caveat, instead of failing silently.

    done when · tick each as it passes
  6. Offline, packaging, README

    Works with the network off when the model is present, signed for your own machine, documented.

    1. Transcript saved; summary skipped with an honest note in notes.md.

    2. Sign with your development certificate for your own use. Right-click > Open the first time.

    3. Permissions and where to grant them, model download size, disk cost per hour of audio, and a plain warning that recording a meeting can require consent from everyone in it depending on where they are.

      Files README.md

    done when · tick each as it passes
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more meeting notes.