{"id":51110272,"url":"https://github.com/bhanafee/alfredmeetings","last_synced_at":"2026-06-24T17:00:55.456Z","repository":{"id":366999715,"uuid":"1274823947","full_name":"bhanafee/AlfredMeetings","owner":"bhanafee","description":"CLI + Alfred workflow to record, transcribe, and summarize voice calls/meetings. This is completely vibe-coded using Claude 4.8, YMMV.","archived":false,"fork":false,"pushed_at":"2026-06-24T05:52:22.000Z","size":137,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-24T07:28:21.110Z","etag":null,"topics":["alfred-workflows","vibe-coded","voice-ai"],"latest_commit_sha":null,"homepage":"","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bhanafee.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-19T23:47:01.000Z","updated_at":"2026-06-24T05:52:25.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/bhanafee/AlfredMeetings","commit_stats":null,"previous_names":["bhanafee/alfredmeetings"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/bhanafee/AlfredMeetings","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bhanafee%2FAlfredMeetings","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bhanafee%2FAlfredMeetings/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bhanafee%2FAlfredMeetings/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bhanafee%2FAlfredMeetings/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bhanafee","download_url":"https://codeload.github.com/bhanafee/AlfredMeetings/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bhanafee%2FAlfredMeetings/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34741320,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-24T02:00:07.484Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["alfred-workflows","vibe-coded","voice-ai"],"created_at":"2026-06-24T17:00:54.443Z","updated_at":"2026-06-24T17:00:55.447Z","avatar_url":"https://github.com/bhanafee.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Meeting Notes — Alfred workflow\n\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n\nRecord a meeting, transcribe it locally with speaker separation, and turn the\ntranscript into clean notes / a summary / minutes — all on-device, no cloud, no\nper-meeting cost. Built for Apple Silicon.\n\nThree independent components, each available **both as an Alfred keyword and as a\n`meetings` subcommand in your shell**. Each also accepts a file, so they chain but can\nrun standalone:\n\n| Keyword | `meetings` command | Input | Output |\n|---|---|---|---|\n| `rec` | `meetings rec` | — (toggle; run again to stop) | stereo `.m4a` (left = your mic → **Me**, right = system audio → **Them**) |\n| `transcribe [file]` | `meetings transcribe [file]` | newest recording, or a file you pass | timestamped, speaker-labeled `.md` transcript |\n| `notes` | `meetings notes \u003cmode\u003e [file]` | newest transcript, or a file you pass | processed `.md`: **Minutes**, **Summary**, **Clean up**, or a **Custom** instruction |\n\nEverything is written to `~/Desktop/Meeting Notes/` by default (configurable).\n\nWhile a recording is active, a red ● appears in the **menu bar** — click it to stop\n(handy when the `rec` toggle leaves you unsure whether you started or stopped).\n\n## How it works\n\n- **Recording** captures a stereo file with your mic on the left and the far side on\n  the right, using a **Core Audio process tap** clocked by the microphone — no BlackHole\n  and no Audio MIDI Setup. Your output device and volume are left untouched (nothing is\n  rerouted), and the tap can optionally be scoped to a single app. See\n  [ADR 0001](docs/adr/0001-system-audio-capture-via-core-audio-process-tap.md).\n- **Transcription** splits the stereo file into two mono channels, transcribes each\n  independently with [`mlx-whisper`](https://github.com/ml-explore/mlx-examples)\n  (`whisper-large-v3-turbo`), and merges the segments chronologically with `Me` /\n  `Them` labels. The deterministic two-way split reliably separates **you** from the\n  remote side. To tell *individual remote speakers* apart — who all share the one\n  system-audio channel — it additionally runs\n  [`pyannote.audio`](https://github.com/pyannote/pyannote-audio) diarization on the\n  Them channel and labels each speaker `Them 1`, `Them 2`, … (see\n  [Speaker attribution](#speaker-attribution-them-side) below).\n- **Notes** sends the transcript to a local Ollama model (`qwen3:4b-instruct` by\n  default) via its OpenAI-compatible endpoint, using one of the bundled prompts.\n\n## Setup\n\n### 1. Homebrew packages\n\n```sh\nbrew install ffmpeg switchaudio-osx\nbrew install --cask ollama\n```\n\n(No BlackHole needed — the far side is captured with a Core Audio process tap. If you\ninstalled `blackhole-2ch` for an earlier version, it's now unused and safe to remove.)\n\nThen start Ollama and pull the model:\n\n```sh\nopen -a Ollama\nollama pull qwen3:4b-instruct\n```\n\n### 2. Python environment\n\n```sh\n./setup/install.sh\n```\n\nCreates a venv at `~/Library/Application Support/AlfredMeetings/venv` and installs\n`mlx-whisper` + `openai` + `pyannote.audio`, builds the signed **capture helper**\n(`MeetingCapture.app`) and menu-bar recording indicator, and symlinks the `meetings`\nCLI into `~/.local/bin`. (Needs Xcode Command Line Tools for `swiftc`; no Homebrew.)\n\n### 3. Install the workflow\n\n```sh\n./build.sh                      # produces dist/AlfredMeetings.alfredworkflow\nopen dist/AlfredMeetings.alfredworkflow\n```\n\nThere are **no audio devices to configure** — recording uses a process tap. The first\n`rec` prompts once for **Microphone** access (the tap is gated by the Microphone service);\nclick Allow. The first `transcribe` run downloads the Whisper model (~1.5 GB, one time).\n\n## Configuration\n\nDefaults live in [`src/config.sh`](src/config.sh) and every value can be overridden\nby an environment variable. The most common ones are also exposed in Alfred's\n**Configure Workflow** panel:\n\n| Variable | Default |\n|---|---|\n| `MEETINGS_OUTPUT_DIR` | `~/Desktop/Meeting Notes` |\n| `MEETINGS_LLM_MODEL` | `qwen3:4b-instruct` |\n| `MEETINGS_LLM_BASE_URL` | `http://localhost:11434/v1` |\n| `MEETINGS_WHISPER_MODEL` | `mlx-community/whisper-large-v3-turbo` |\n| `MEETINGS_MIC_DEVICE` | _(from Microphone choice)_ — CoreAudio UID or name |\n| `MEETINGS_THEM_APP` | _(empty)_ — app name to scope the tap; blank = all system audio |\n| `MEETINGS_DIARIZE` | `auto` (`off` to disable Them-side speaker labels) |\n| `MEETINGS_HF_TOKEN` | _(empty)_ — Hugging Face token for diarization |\n\nOn a 24 GB Mac you can point `MEETINGS_LLM_MODEL` at a larger model (e.g.\n`qwen2.5:7b`) for higher-quality minutes.\n\n## Command line\n\nThe same three stages run from your shell via the `meetings` command (symlinked into\n`~/.local/bin` by `setup/install.sh`):\n\n```sh\nmeetings rec                              # start; run again (or click the menu-bar ●) to stop\nmeetings transcribe [audio]              # newest recording, or a file you pass\nmeetings notes minutes [transcript]      # also: summary | clean\nmeetings notes custom \"List risks and open questions\"\n```\n\n`rec` auto-transcribes on stop just as it does under Alfred. The CLI and the Alfred\nkeywords run the **same** scripts and honour the same `MEETINGS_*` configuration.\n\n## Speaker attribution (Them side)\n\nThe mic channel is always **you** (\"Me\"). Everyone you hear shares the single\nsystem-audio channel, so to label them individually the transcriber runs\n`pyannote.audio` diarization on that channel and tags each voice `Them 1`, `Them 2`, …\n(the labels are plain text — search-and-replace them with real names afterward).\n\nThis needs a free Hugging Face token and one-time access to the gated model\n(`pyannote/speaker-diarization-community-1`, the pyannote.audio 4.x flagship pipeline):\n\n1. Create a token at \u003chttps://huggingface.co/settings/tokens\u003e (a **Read** token).\n2. Accept the model terms at\n   \u003chttps://huggingface.co/pyannote/speaker-diarization-community-1\u003e.\n3. Set the token: Alfred → **Configure Workflow → Hugging Face token**, or export\n   `MEETINGS_HF_TOKEN` in your shell.\n\nOverride the pipeline with `MEETINGS_DIARIZE_MODEL` if you pin a different pyannote\nversion (e.g. `pyannote/speaker-diarization-3.1` under pyannote.audio 3.x).\n\nIf the token, model access, or `pyannote.audio` is missing, transcription **falls back\nto a single `Them` label** — nothing breaks, you just lose the per-speaker split. Set\n`MEETINGS_DIARIZE=off` (or the **Speaker labels** popup) to skip it entirely.\nDiarization adds a CPU pass over the Them channel, so a long meeting takes a few extra\nminutes; set `MEETINGS_DIARIZE_DEVICE=mps` to try the GPU.\n\n## Repository layout\n\n```\nbuild.sh                 Package src/ into dist/AlfredMeetings.alfredworkflow\nsetup/install.sh         Build the venv, indicator app, and `meetings` CLI symlink\nsetup/audio-setup.md     One-time audio device guide\nsrc/info.plist           Alfred workflow graph\nsrc/config.sh            Shared, env-overridable configuration\nsrc/bin/                 Component entrypoints (record / transcribe / notes)\nsrc/bin/meetings         Single-entrypoint CLI dispatcher for the shell\nsrc/engine/              Python engines + prompt templates\nsrc/indicator/           Native menu-bar \"recording now\" app (Swift)\n```\n\n## License\n\nThis project's source is released under the [MIT License](LICENSE).\n\nThe runtime dependencies are **not** bundled or redistributed here — `setup/install.sh`\nfetches them on your machine (the Python venv via `pip`; `ffmpeg`, `ollama`, and\n`switchaudio-osx` via Homebrew). They keep their own licenses (mostly MIT / BSD /\nApache-2.0). `ffmpeg` is only invoked as a separate process, so its GPL terms do not\napply to this code. See [THIRD-PARTY-NOTICES.md](THIRD-PARTY-NOTICES.md) for the full\ndependency list and per-package licenses.\n\nThat file is kept honest by CI: `setup/check-notices.sh` fails the build if a dependency\ndeclared in `setup/install.sh` isn't documented. After changing dependencies, run\n`./setup/check-notices.sh --refresh` on your Mac to regenerate the version table from the\ninstalled venv, then commit the result.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbhanafee%2Falfredmeetings","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbhanafee%2Falfredmeetings","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbhanafee%2Falfredmeetings/lists"}