WristNote:Apple Watch 一键录音转笔记的开源项目
godagoo/wristnote
开源项目 WristNote 用约 430 行 Swift 代码加一个 Bun 服务器,让 Apple Watch 一键录音后经 Gemini API 生成带标题、转录、摘要和行动项的 Markdown 笔记。
A one-button Apple Watch app that records your voice, sends the audio to your own small server, and turns it into a titled note with a transcript, a summary and action items.
Apple Watch Your server (Bun) Gemini API
┌──────────┐ m4a + secret ┌──────────────────┐ audio ┌────────────┐
│ ● REC │ ────────────> │ POST /voice-note│ ───────> │ transcript │
│ one tap │ <──────────── │ │ <─────── │ title │
└──────────┘ "Saved" └────────┬─────────┘ JSON │ summary │
│ └────────────┘
┌───────────────────┼────────────────────┐
v v v
notes/*.md Telegram message your webhook
(Markdown files) (optional, with cost) (optional)
I built this for my YouTube video where I put it side by side with a commercial AI wristband. The wristband is a nice piece of hardware. But if you already wear an Apple Watch, you can get most of the way there with about 430 lines of Swift on the Watch, a small Bun server, and a Gemini key. This repo is that build, cleaned up so you can start from it.
Watch the video: https://youtube.com/@GodaGo
What it does
- One button. Tap to record, tap to stop. On a Series 9 or later you can Double Tap (pinch twice) instead of touching the screen.
- A complication for your watch face. Tap it and the app opens already recording.
- Raw audio, not dictation. The Watch records a real m4a file, so Gemini hears everything, in any language, with background noise and all.
- One Gemini call returns the transcript, a title, a summary and action items as JSON. Every language stays as spoken, nothing gets translated. When several people talk, turns are labelled Speaker 1, Speaker 2.
- Nothing gets lost. Every recording is named with a UUID and waits on the Watch until the server confirms it. Failed uploads retry the next time you open the app, and the server skips note ids it already has, so a retry never makes a duplicate.
- Long notes. Every 35 minutes the Watch closes the file, uploads it, and keeps recording into a new one. A workout session keeps the app alive with your wrist down.
- Notes are plain Markdown in a folder on your server, with front matter you can query later.
- Cost on every note. Each note is logged to a JSONL ledger with tokens and cost. In my testing a 30 second note costs a fraction of a cent.
bun run billprints the monthly total. - Optional Telegram message with the title, summary, action items, transcript and cost.
- Optional webhook so you can push notes into your own assistant, database or automation.
What you need
- An Apple Developer Program membership ($99/year). That's what I used. The wrist-down recording needs the HealthKit capability, and I haven't tested it with a free account.
- A Mac with Xcode (16 or later) and XcodeGen (
brew install xcodegen). - An Apple Watch on watchOS 11 or later, paired to an iPhone.
- A Gemini API key from https://aistudio.google.com/apikey.
- Bun and somewhere to run the server that your Watch can reach over HTTPS.
Quick start
1. The server
bun install
cp .env.example .env
# Fill in VOICE_NOTE_SECRET (openssl rand -hex 32) and GEMINI_API_KEY
bun startCheck it:
curl http://localhost:3000/health # {"ok":true} curl -X POST http://localhost:3000/voice-note \ -H "x-voice-note-secret: $VOICE_NOTE_SECRET" \ -H "content-type: audio/mp4" \ --data-binary @some-recording.m4a
The Watch needs to reach the server over HTTPS, so for real use put it on a host. See docs/deploy.md for local, Docker and VPS or platform hosting.
2. The Watch app
cd watch cp Secrets.example.xcconfig Secrets.xcconfig # Fill in SERVER_URL, VOICE_NOTE_SECRET, DEVELOPMENT_TEAM and BUNDLE_ID_PREFIX xcodegen generate open WristNote.xcodeproj
Pick your Apple Watch as the run destination and press Run. The first install is the fiddly part (pairing, Developer Mode, signing). I hit every one of those gates, so I wrote them down in docs/watch-setup.md.
Then add the WristNote complication to your watch face, record a short note, and look in notes/.
How it works
The Watch posts the audio as the raw request body with four headers:
| Header | What |
|---|---|
x-voice-note-secret |
Shared secret, must match VOICE_NOTE_SECRET |
x-note-id |
The recording's UUID, used to skip duplicates |
x-recorded-at |
ISO 8601 time the recording started |
x-device |
Free text, the Watch sends apple-watch |
The server also accepts a multipart form with an audio file, or a text field for notes that are already text (no Gemini call, no cost). Fields are note_id, recorded_at and device. That is what the optional iPhone Shortcut uses.
Responses:
| Status | Meaning | What the Watch does |
|---|---|---|
| 200 | Saved, or already saved | Deletes its copy |
| 202 | Over 3 MB, accepted and finishing in the background | Deletes its copy |
| 401 / 503 | Wrong secret, or no secret set on the server | Keeps the file |
| 409 | Same note still processing | Keeps the file |
| 413 | Over 14 MB | Keeps the file |
| 502 | Gemini failed, nothing saved | Keeps the file, retries later |
If a background job fails, the server keeps the audio in notes/failed/ so you can send it again.
A saved note looks like this:
--- id: "6F9619FF-8B86-D011-B42D-00C04FC964FF" title: "Parcel pickup" recorded_at: "2026-05-01T07:30:00.000Z" device: "apple-watch" duration_sec: 30 model: "gemini-3.8-flash-001" cost_usd: 0.001583 --- # Parcel pickup Collect the parcel today. ## Action items - [ ] Pick up the parcel ## Transcript Pick up the parcel before six.
Configuration
Everything lives in .env. See .env.example.
| Variable | Default | What |
|---|---|---|
VOICE_NOTE_SECRET |
none, required | Shared secret for the Watch |
GEMINI_API_KEY |
none, required for audio | Your Gemini key |
GEMINI_MODEL |
gemini-flash-latest |
Any Gemini model that takes audio |
NOTES_DIR |
./notes |
Where the Markdown notes go |
LEDGER_PATH |
./logs/ledger.jsonl |
Cost and token ledger |
KEEP_AUDIO |
false |
true keeps the m4a next to each note |
TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID |
off | Both set means a Telegram message per note |
WEBHOOK_URL |
off | POSTs each note as JSON |
TZ |
system | Time zone for file names |
PORT |
3000 |
Prices for the cost estimate are in src/cost.ts. Google changes them, so check https://ai.google.dev/gemini-api/docs/pricing now and then.
Honest trade-offs
- Your audio goes to Google. The server sends every recording to the Gemini API. On the paid tier Google says it doesn't use your prompts to improve its products. On the free tier it can. Read the Gemini API terms and decide for yourself. If that's a dealbreaker, swap in a local model (see Alternatives).
- No real speaker diarization. Gemini labels turns as Speaker 1 and Speaker 2 when it can tell voices apart, which works fine for two people talking but isn't proper diarization. If you need that, look at pyannote.
- No raw audio kept by default. Once a note is saved, the audio is gone from the Watch and the server never writes it. Set
KEEP_AUDIO=trueif you want it. - The secret is baked into the app. It sits in the app's Info.plist. That's fine for an app only you install, but don't ship this build to anyone else.
- Watch battery. Recording with the wrist down runs a workout session, which keeps the heart-rate sensor on. Long recordings will cost you noticeably more battery than a dedicated recorder would.
- Apple's background limits. Without the workout session, watchOS stops a third-party app soon after you lower your wrist. Details in docs/long-recordings.md.
- You need the developer account. Development builds on the Watch also need reinstalling from Xcode once their provisioning profile expires.
Alternatives for transcription
- Local Whisper: whisper-large-v3-turbo runs on your own machine, so no audio leaves it. You'd add the summary step yourself.
- Speechmatics: strong on accents and mixed languages, with real diarization.
- AssemblyAI: transcription with diarization and summaries through one API.
To swap one in, replace src/gemini.ts and keep returning the same JSON shape.
Repo layout
src/ the Bun server (one file per job: upload, gemini, notes, ledger, telegram, webhook)
test/ bun test
scripts/ bill.ts, the monthly cost report
watch/ the watchOS app and complication (XcodeGen project.yml + Swift)
docs/ Watch setup, long recordings, deploy, the optional iPhone Shortcut
Run the tests with bun test and the type check with bun run typecheck.
License
MIT, see LICENSE.
Made by Goda Go. YouTube: https://youtube.com/@GodaGo · https://autonomee.ai
If you want the always-on assistant this plugs into, see the free Claude Telegram Relay: https://github.com/godagoo/claude-telegram-relay
来源:GitHub · github.com