{"id":50493490,"url":"https://github.com/nowhitestar/yulu","last_synced_at":"2026-06-02T05:00:52.465Z","repository":{"id":356722466,"uuid":"1223740140","full_name":"Nowhitestar/Yulu","owner":"Nowhitestar","description":"Listen quietly. Capture everything. — Native macOS meeting recorder \u0026 note-taker. ScreenCaptureKit + whisper.cpp, no BlackHole.","archived":false,"fork":false,"pushed_at":"2026-05-31T05:08:59.000Z","size":5088,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-31T07:08:11.485Z","etag":null,"topics":["agent","audio-recording","claude-code","local-first","macos","meeting-assistant","meeting-notes","meeting-recorder","productivity","python","screencapturekit","swift","transcription","whisper","yulu"],"latest_commit_sha":null,"homepage":"https://liao.uno/yulu/","language":"Python","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/Nowhitestar.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","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-04-28T15:56:30.000Z","updated_at":"2026-05-29T09:54:40.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/Nowhitestar/Yulu","commit_stats":null,"previous_names":["nowhitestar/yulu"],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/Nowhitestar/Yulu","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Nowhitestar%2FYulu","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Nowhitestar%2FYulu/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Nowhitestar%2FYulu/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Nowhitestar%2FYulu/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Nowhitestar","download_url":"https://codeload.github.com/Nowhitestar/Yulu/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Nowhitestar%2FYulu/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33806987,"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-02T02:00:07.132Z","response_time":109,"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":["agent","audio-recording","claude-code","local-first","macos","meeting-assistant","meeting-notes","meeting-recorder","productivity","python","screencapturekit","swift","transcription","whisper","yulu"],"created_at":"2026-06-02T05:00:51.507Z","updated_at":"2026-06-02T05:00:52.455Z","avatar_url":"https://github.com/Nowhitestar.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"assets/logo.svg\" width=\"120\" alt=\"Yulu logo\" /\u003e\n  \u003ch1\u003eYulu\u003c/h1\u003e\n  \u003cp\u003e\u003cb\u003eListen quietly. Capture everything.\u003c/b\u003e\u003c/p\u003e\n  \u003ca href=\"https://github.com/Nowhitestar/Yulu/stargazers\"\u003e\u003cimg src=\"https://img.shields.io/github/stars/Nowhitestar/Yulu?style=flat-square\" alt=\"Stars\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/Nowhitestar/Yulu/releases\"\u003e\u003cimg src=\"https://img.shields.io/github/v/tag/Nowhitestar/Yulu?label=version\u0026style=flat-square\" alt=\"Version\"\u003e\u003c/a\u003e\n  \u003ca href=\"LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square\" alt=\"License\"\u003e\u003c/a\u003e\n  \u003ca href=\"#\"\u003e\u003cimg src=\"https://img.shields.io/badge/macOS-13%2B-black?style=flat-square\u0026logo=apple\" alt=\"macOS 13+\"\u003e\u003c/a\u003e\n  \u003cp\u003e\u003cb\u003eEnglish\u003c/b\u003e · \u003ca href=\"README.zh-CN.md\"\u003e简体中文\u003c/a\u003e\u003c/p\u003e\n\u003c/div\u003e\n\n## Why\n\nYulu (语录, *yǔ lù*) is the Chinese word for \"recorded sayings\" — the genre that gave us *The Analects of Confucius* 2,500 years ago. It is the oldest answer to a problem we still have today: someone said something important in a room, and nobody wrote it down well enough to remember it.\n\nYulu is a native macOS meeting recorder that listens to your meetings, transcribes them locally with MLX Whisper or `whisper.cpp`, and hands the transcript to any coding agent (Claude Code, Codex, OpenClaw…) to produce a clean meeting note. No virtual audio device. No cloud transcription. No account. The audio never leaves your laptop unless you tell it to.\n\nCompared to Otter / Granola / Fireflies:\n\n- **System audio is captured natively** through `ScreenCaptureKit`, not through BlackHole or a multi-output device.\n- **Transcription is fully local** — MLX Whisper on Apple Silicon, or `whisper-cli` (whisper.cpp) with your own model file. Chinese works as well as English.\n- **The summary step is bring-your-own-agent.** Yulu writes a `summary_request` into a JSON queue; whichever agent you trust reads the transcript and the template, and writes back a polished `summary.md`. Nothing is hard-coded to one vendor.\n- **Half-duplex mixing** keeps remote speakers crisp: system audio leads while others speak, microphone takes over during system silence.\n- **Local web UI at `http://127.0.0.1:7777/`** — voicemails, meetings, search, settings, prompts, glossary, daemon health. See [docs/yulu_ui.md](docs/yulu_ui.md).\n\n## See it\n\n\u003ctable\u003e\n\u003ctr\u003e\n  \u003ctd align=\"center\" width=\"50%\"\u003e\n    \u003cimg src=\"assets/demos/demo-status-window.png\" alt=\"Floating recording status window\" /\u003e\n    \u003cbr\u003e\u003cb\u003eRecording status\u003c/b\u003e\n    \u003cbr\u003e\u003csub\u003eA small floating window with a manual stop button\u003c/sub\u003e\n  \u003c/td\u003e\n  \u003ctd align=\"center\" width=\"50%\"\u003e\n    \u003cimg src=\"assets/demos/demo-summary.png\" alt=\"Generated meeting summary\" /\u003e\n    \u003cbr\u003e\u003cb\u003eFinal summary\u003c/b\u003e\n    \u003cbr\u003e\u003csub\u003eTL;DR · Discussion Points · Action Items · Decisions\u003c/sub\u003e\n  \u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n  \u003ctd align=\"center\" width=\"50%\"\u003e\n    \u003cimg src=\"assets/demos/demo-prompt.png\" alt=\"Prompt before recording\" /\u003e\n    \u003cbr\u003e\u003cb\u003ePrompt before recording\u003c/b\u003e\n    \u003cbr\u003e\u003csub\u003eYulu always asks before it starts listening\u003c/sub\u003e\n  \u003c/td\u003e\n  \u003ctd align=\"center\" width=\"50%\"\u003e\n    \u003cimg src=\"assets/demos/demo-transcript.png\" alt=\"Local transcription with whisper\" /\u003e\n    \u003cbr\u003e\u003cb\u003eLocal transcription\u003c/b\u003e\n    \u003cbr\u003e\u003csub\u003eMLX Whisper or whisper-cli runs offline; Chinese / English / mixed\u003c/sub\u003e\n  \u003c/td\u003e\n\u003c/tr\u003e\n\u003c/table\u003e\n\n## Install\n\nLatest stable release:\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/Nowhitestar/Yulu/main/install.sh | bash\n```\n\nSpecific version:\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/Nowhitestar/Yulu/main/install.sh | bash -s -- --version v0.5.0\n```\n\nDev channel from `main`:\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/Nowhitestar/Yulu/main/install.sh | bash -s -- --dev\n```\n\nBy default, the installer downloads the latest stable GitHub Release assets into `~/.yulu`; it does not clone `main`. The `--version` flag pins only that install operation, and `--dev` opts into the development channel.\n\nThe installer:\n\n1. Checks macOS 13+, Xcode CLI Tools, Homebrew, Python 3.\n2. Installs the selected Yulu runtime into `~/.yulu/` (a stable path — don't move it around).\n3. Installs Homebrew packages: `sox`, `ffmpeg`, `whisper-cpp`, `terminal-notifier`, `gogcli`, `cloudflared`.\n4. Writes per-user config to `~/.config/yulu/config.json` and creates `~/Movies/Yulu/` for recordings.\n5. Compiles the window scanner; walks you through Accessibility permission.\n6. Builds and signs `Yulu.app`; walks you through Microphone + Screen \u0026 System Audio Recording permissions.\n7. Lets you choose the transcription profile: MLX `large-v3`, MLX `large-v3-turbo`, or a `whisper.cpp` GGML model.\n8. Lets you choose the stop-time pipeline: realtime transcript → polish → summary, or full final transcription → summary.\n9. Lets you choose how summaries are finalized: agent queue, Claude CLI, Codex CLI, custom command, or local fallback only.\n10. (Optional) configures Google Calendar via `gog`.\n11. Installs LaunchAgents for background services.\n12. Installs the `yulu` CLI to `~/.local/bin/yulu`.\n13. (Optional) registers Yulu as an **agent skill** so Claude Code / OpenClaw / Codex etc. can drive it from natural language.\n14. Runs a smoke test.\n\nAfter install, **add `~/.local/bin` to your PATH** if it isn't already, so `yulu` is on your shell:\n\n```bash\necho 'export PATH=\"$HOME/.local/bin:$PATH\"' \u003e\u003e ~/.zshrc \u0026\u0026 exec zsh\n```\n\n### Update\n\nLatest stable release:\n\n```bash\nyulu update\n```\n\nSpecific version:\n\n```bash\nyulu update --version v0.5.0\n```\n\nDev channel from `main`:\n\n```bash\nyulu update --dev\n```\n\nStable and specific-version updates download GitHub Release assets into `~/.yulu/`; `--dev` installs or updates from `main`. Either way, setup re-runs in idempotent upgrade mode. Already-granted TCC permissions are not re-prompted; OAuth is not redone; the whisper model isn't re-downloaded.\n\n`--version` affects only that one operation. The next plain `yulu update` returns to the latest stable release.\n\n### Uninstall\n\n```bash\nyulu uninstall\n```\n\nStops services, removes LaunchAgents and the CLI, and asks before deleting recordings / config / agent skills. macOS TCC entries and Homebrew packages are deliberately left alone (other apps may use them) — see the final summary printed by the script for the manual cleanup steps.\n\n### `yulu` CLI\n\n| Command | What |\n|---|---|\n| `yulu setup` | Re-run the installer interactively (fresh install) |\n| `yulu update [--version vX.Y.Z \\| --dev]` | Update from latest stable release assets, a specific version, or the dev channel |\n| `yulu start` / `stop` / `restart` | Control the four LaunchAgents |\n| `yulu version` | Print Yulu version, git commit, tag, and dirty state |\n| `yulu status` | Service health, audio_daemon socket, recent recordings |\n| `yulu doctor` | Config, daemon, model, queue, calendar health check |\n| `yulu logs [name]` | Tail logs (default: `audio_daemon`) |\n| `yulu record start \"\u003ctitle\u003e\"` / `yulu record stop` | Manual recording with the same stop → transcribe → summarize flow as the floating window |\n| `yulu transcription status` | Show transcription engine and post-recording mode |\n| `yulu transcription mode fast\\|full` | Switch between realtime transcript → polish → summary and full final transcription → summary |\n| `yulu transcription engine mlx \u003cmodel\u003e` | Use MLX Whisper, e.g. `mlx-community/whisper-large-v3-mlx` |\n| `yulu transcription engine whisper \u003cpath\u003e` | Use a local whisper.cpp GGML model |\n| `yulu where` | Print all the relevant paths on disk |\n| `yulu uninstall` | See above |\n\n### Use Yulu from your coding agent\n\nYulu ships with a `SKILL.md` under [`skills/yulu/`](skills/yulu/SKILL.md) that teaches Claude Code, OpenClaw, Codex, Cursor, and the [50+ other agents supported by `vercel-labs/skills`](https://github.com/vercel-labs/skills#supported-agents) how to drive it. Once installed, you can say things like:\n\n- \"Start recording, call it Yulu weekly\"\n- \"Stop the recording and summarize it\"\n- \"What did we talk about in last Tuesday's standup?\"\n\n`setup.sh` asks whether to install it and which agents to target. To install or reinstall it later, from anywhere:\n\n```bash\n# Install globally to the agents you choose\nnpx skills add Nowhitestar/Yulu -g -a claude-code -y\nnpx skills add Nowhitestar/Yulu -g -a codex -y\n\n# Or install from your local clone\nnpx skills add . -g -a claude-code -y\n```\n\nThe skill is a thin contract — it tells the agent what verbs Yulu exposes (start, stop, status, summary fulfillment) and how to find past meetings on disk. Yulu's macOS app, launchd services, and local transcription dependencies still come from `setup.sh`. Installing the skill alone does not capture audio.\n\n## How it works\n\n```text\nGoogle Calendar / Window Detector\n          ↓\n schedule.json  ──►  scheduler_daemon.py\n          ↓\n meeting_daemon.py  ──►  notify.py prompt: \"Start recording?\"\n          ↓\n record_audio.py  ──►  Yulu.app  (Unix socket)\n          ↓\n ScreenCaptureKit (system audio) + AVFoundation (microphone)\n          ↓\nWAV  ──►  realtime_transcribe.py / transcribe.py  ──►  MLX Whisper or whisper-cli\n          ↓\n transcript.txt  +  summary_request  ──►  agent-queue.json\n          ↓\n Any agent (Claude Code / Codex / OpenClaw…)  ──►  summary.md\n```\n\nSix numbers worth knowing:\n- WAV is 16-bit stereo 48 kHz.\n- ScreenCaptureKit Float32 planar → interleaved stereo Int16.\n- Half-duplex crossfade kicks in below `silence_threshold` (default 0.01).\n- Default quality profile: MLX `mlx-community/whisper-large-v3-mlx` on Apple Silicon; whisper.cpp `ggml-large-v3.bin` is the non-MLX quality profile.\n- Bundle id: `com.yulu.audiodaemon` (signed; falls back to ad-hoc).\n- Agent queue: `~/.config/yulu/agent-queue.json`.\n\n## macOS Permissions\n\n| Component | Permission | Why |\n|---|---|---|\n| `Yulu.app` | Microphone | Record your local microphone |\n| `Yulu.app` | Screen \u0026 System Audio Recording | Capture system audio with ScreenCaptureKit |\n| `window_scanner` | Accessibility | Read window titles to detect meetings |\n\nIf system audio is missing: System Settings → Privacy \u0026 Security → **Screen \u0026 System Audio Recording** → enable `Yulu.app`, then restart it.\n\n## Configuration\n\nPath: `~/.config/yulu/config.json`\n\n```json\n{\n  \"audio\": {\n    \"backend\": \"daemon\",\n    \"silence_threshold\": 0.01,\n    \"silence_duration_sec\": 300,\n    \"half_duplex\": true\n  },\n  \"transcription\": {\n    \"post_recording_mode\": \"fast_summary\",\n    \"final_engine\": \"mlx\",\n    \"mlx\": {\n      \"python\": \"~/.config/yulu/venv-mlx-whisper/bin/python\",\n      \"model\": \"mlx-community/whisper-large-v3-mlx\"\n    },\n    \"realtime\": {\n      \"engine\": \"mlx\",\n      \"mlx_model\": \"mlx-community/whisper-large-v3-mlx\",\n      \"chunk_sec\": 60\n    },\n    \"whisper_cli\": \"whisper-cli\",\n    \"local_model_path\": \"~/.config/yulu/models/ggml-large-v3.bin\",\n    \"language\": \"zh\"\n  },\n  \"llm\": {\n    \"enabled\": true\n  }\n}\n```\n\n- `audio.backend = \"daemon\"` is the default. `mic_device` / `system_audio_device` only apply to the legacy SoX fallback.\n- `transcription.post_recording_mode = \"fast_summary\"` uses the realtime transcript generated during the meeting, then polishes and summarizes it. Use `yulu transcription mode full` when you want a slower full-audio final transcription before summarization.\n- `transcription.final_engine = \"mlx\"` is best on Apple Silicon. Use `mlx-community/whisper-large-v3-mlx` for highest quality, `mlx-community/whisper-large-v3-turbo` for speed. Use `final_engine = \"whisper\"` with `local_model_path` for the simpler non-MLX route.\n- Leave `llm.command` empty to delegate summarization to your agent through `agent-queue.json`. To call an external LLM directly, set `llm.command` to any CLI that accepts a prompt on stdin and writes Markdown to stdout (e.g. `[\"claude\", \"--print\", \"--model\", \"claude-opus-4-7\"]`). Set `llm.enabled=false` only when the local fallback summary should be final.\n\nFull config reference: [`docs/configuration.md`](docs/configuration.md).\nManual commands and troubleshooting: [`docs/operations.md`](docs/operations.md).\n\n## Design notes\n\nA few decisions are load-bearing and worth understanding before contributing:\n\n- **No virtual audio device.** ScreenCaptureKit was added in macOS 13 specifically so apps could capture system audio without driver hacks. Yulu refuses to fall back to BlackHole even when it would be easier — the install friction is the whole point.\n- **Recording always asks first.** Detection is best-effort, but consent is not. Every recording goes through `notify.py` with a real prompt.\n- **The LLM is a plug-in, not a dependency.** `transcribe.py` runs all the way to a usable Markdown summary even if no agent ever shows up — `fallback_summary()` uses regex bucketing on the transcript so you never see \"TODO: agent will fill this in\".\n- **State lives in JSON files, not RAM.** `agent-queue.json`, `schedule.json`, recordings on disk. Queue writes are locked and atomic; a power outage mid-meeting loses the audio after the last flush, nothing else.\n- **One-binary security boundary.** Only `Yulu.app` holds the TCC permissions. The Python side talks to it through a Unix socket and cannot bypass macOS privacy on its own.\n\n## Background\n\nI take a lot of meetings — internal reviews, customer calls, recordings of talks I want to revisit a month later. Granola does not record system audio. Otter is cloud-only and weak in Chinese. Every \"just install BlackHole\" guide ended with two output devices, no Bluetooth headphones, and a confused friend on the other end.\n\nSo I wrote my own. The first version was 200 lines of `sox` and a prayer. The version you are looking at uses ScreenCaptureKit, mixes half-duplex audio inline, and lets a local Claude Code agent finish the meeting note while I am already in the next one. The name *Yulu* (语录) is the promise: every conversation deserves to land somewhere you can re-read it later.\n\n## Project layout\n\n```text\nYulu/\n├── install.sh                            # one-line installer entry\n├── README.md\n├── LICENSE\n├── CONTRIBUTING.md\n├── CHANGELOG.md\n├── docs/\n│   ├── configuration.md\n│   └── operations.md\n├── assets/\n│   ├── logo.svg\n│   └── demos/\n├── skills/\n│   └── yulu/SKILL.md                     # agent contract installed by `npx skills add`\n└── yulu/\n    ├── SKILL.md                          # internal architecture / developer doc\n    └── scripts/\n        ├── setup.sh                      # interactive installer (--upgrade for re-runs)\n        ├── uninstall.sh                  # invoked by `yulu uninstall`\n        ├── yulu                          # CLI dispatcher (symlinked to ~/.local/bin/yulu)\n        ├── Yulu.app/                     # signed (or ad-hoc) audio daemon bundle\n        ├── audio_daemon.swift            # ScreenCaptureKit + AVFoundation\n        ├── build_audio_daemon.sh         # build \u0026 sign Yulu.app\n        ├── record_audio.py               # recording control\n        ├── meeting_daemon.py             # workflow orchestration\n        ├── scheduler_daemon.py           # calendar-based scheduler\n        ├── meeting_detector.py           # window-based detector\n        ├── window_scanner.swift          # Accessibility window scanner\n        ├── recorder_status.swift         # floating status window\n        ├── transcribe.py                 # MLX / whisper transcription + agent queue writer\n        ├── agent_notify.py               # agent queue helper\n        ├── notify.py                     # macOS notifications \u0026 prompts\n        ├── send_summary.py               # experimental Telegram / Notion / Zulip adapters\n        ├── summary_template.md           # default meeting note template\n        └── com.yulu.*.plist              # LaunchAgent definitions\n```\n\nAfter installing, the on-disk layout looks like:\n\n| Path | Contents |\n|---|---|\n| `~/.yulu/` | Installed runtime from release assets or the dev channel |\n| `~/.config/yulu/config.json` | User configuration |\n| `~/.config/yulu/models/ggml-*.bin` | Downloaded whisper.cpp models |\n| `~/.config/yulu/audio_daemon.sock` | Unix socket exposed by the daemon |\n| `~/.config/yulu/agent-queue.json` | Pending events for your agent |\n| `~/Movies/Yulu/` | Your meeting recordings + transcripts + summaries |\n| `~/Library/LaunchAgents/com.yulu.*.plist` | Background services (4 LaunchAgents) |\n| `~/.local/bin/yulu` | The CLI symlink |\n\n## Support\n\n- If Yulu helped you, star the repo or share it.\n- Ideas, bugs, edge-case meetings: open an issue or PR. See [CONTRIBUTING.md](CONTRIBUTING.md).\n- Security disclosures: please email rather than open a public issue.\n\n## License\n\nMIT. See [LICENSE](LICENSE).\n\n`whisper.cpp`, `ScreenCaptureKit`, `AVFoundation`, `terminal-notifier`, `cloudflared`, and `gog` retain their own licenses.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnowhitestar%2Fyulu","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnowhitestar%2Fyulu","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnowhitestar%2Fyulu/lists"}