English | 简体中文
intro https://neoemacs.com/debuff
A native macOS utility: on launch it shows a settings window where you can adjust the sedentary threshold and the debuff icon. The timer starts at launch or after you clear the debuff; once the threshold is exceeded, a Warcraft-style debuff popup appears: the frame uses the border.png asset, and the skill icon defaults to sentinel-juggernautstance-128.png (overridable with a user-chosen image). Below the icon is the timer (minutes, one decimal place, with an m suffix). Double-click the debuff popup to clear the state and restart the timer.
It also includes voice input: press a global hotkey to open the system default microphone, segment locally via silence detection, transcribe with a local OpenAI-compatible STT service, and paste the text at the current cursor position (with "paste as you speak" support).
The image below shows the tool in action: the settings window and the debuff popup on the desktop (frame, icon, and elapsed time).
Settings:
- Debuff after sitting for: numeric option
- Custom debuff icon: choose a local image; "Restore default" returns to the bundled
sentinel-juggernautstance-128.png.
The frame and default icon live in Sources/SedentaryDebuff/Resources/:
| File | Purpose |
|---|---|
border.png |
debuff popup frame (overlays the icon and timer text) |
sentinel-juggernautstance-128.png |
default debuff icon |
You can replace the files with the same names and rebuild; keep the file names unchanged.
- macOS 13 or later
- Swift 5.9+ (installed with Xcode or the Command Line Tools)
From the project root (the directory containing Package.swift):
cd /path/to/debuff
swift build -c releaseRun the Release build:
./.build/release/SedentaryDebuffFor a debug build:
swift run SedentaryDebuffOn first launch, if Gatekeeper warns about an unsigned local tool, allow it under "System Settings → Privacy & Security" as needed, or open it from the right-click context menu.
Run from the project root containing Package.swift (Xcode required; xcodebuild ships with Xcode's command line tools).
Release build:
cd /path/to/debuff
xcodebuild \
-scheme SedentaryDebuff \
-destination 'platform=macOS' \
-configuration Release \
buildDebug build: change -configuration Release above to -configuration Debug.
Output location: by default this writes to Xcode's DerivedData, at a path like
~/Library/Developer/Xcode/DerivedData/debuff-<random-suffix>/Build/Products/Release/.
The main program is SedentaryDebuff (the executable); the same directory also contains the resource bundle SedentaryDebuff_SedentaryDebuff.bundle. When running or distributing, keep the executable and that .bundle in the same directory (or distribute the whole Release directory).
Pin the build output inside the repo (easier to find, no random DerivedData directory):
cd /path/to/debuff
xcodebuild \
-scheme SedentaryDebuff \
-destination 'platform=macOS' \
-configuration Release \
-derivedDataPath "$(pwd)/.xcodebuild/DerivedData" \
buildAfter a successful build, launch it from the terminal:
open .xcodebuild/DerivedData/Build/Products/Release/SedentaryDebuffPackage as a standard .app (draggable into Applications): from the project root:
./scripts/package-macos-app.shOn success you get dist/debuff.app (dist/ is in .gitignore). Example install:
cp -R dist/debuff.app /Applications/The script runs swift build -c release, places the executable and the SPM resource bundle into Contents/MacOS/, and writes App/Info.plist; it also fills in App/ResourceBundle-Info.plist for the resource bundle so ad-hoc code signing (codesign -s -) works. To distribute publicly and pass Gatekeeper, you still need to Archive in Xcode or notarize with an Apple Developer account.
- Open Xcode, choose File → Open…, and select
Package.swiftin this repo (don't select just the folder). - Wait for dependency resolution, then pick
SedentaryDebuffin the scheme selector at the top and set the run destination to My Mac. - Press ⌘R (Product → Run) to launch the app and adjust settings in the settings window.
- Timer start: when the app launches, or after you double-click the debuff popup to clear it.
- Popup timer: counted from the moment sitting time first reaches the threshold, shown as
XX.Xm(one decimal place). - Config persistence: the threshold and custom icon path are stored in local
UserDefaults.
The status bar menu adds a "Voice input" section: default hotkey ⌥⇧F2 (changeable under the menu "Voice input → Settings → Hotkey"; it's global and doesn't depend on window focus).
How to use
- Press the hotkey to start recording (a rounded waveform indicator bar appears next to the mouse: a row of vertical bars that lengthen when you speak and shorten when silent, jumping in real time with the volume; the bar stays pinned beside the mouse and follows it).
- Speak; a pause reaching the configured length (1 second by default) or a single segment reaching the maximum length (10 seconds by default) auto-segments.
- Each segment is transcribed by the local STT service and pasted immediately at the current cursor position ("live / paste as you speak" mode, on by default).
- Press the hotkey again to stop; the final un-pasted segment is transcribed and pasted, and the waveform indicator disappears.
Screen off / lock while recording
If a recording session is active and the display sleeps, the screen locks, or you close the lid, the app does not stop and does not close the microphone: as long as the microphone stays connected it keeps recording and transcribing in the background, so when the screen wakes everything continues as if it was never interrupted. Only when the selected microphone actually disconnects (unplugged, phone out of range, Bluetooth/Continuity link lost) does the app end the session gracefully: it finishes transcribing what was captured, runs one cleanup pass (DeepSeek), then pastes the whole text and writes it to the voice journal. When voice input is not recording (idle/standby), screen off/lock behaves as before: pending text is committed and the microphone is stopped, then restored on wake.
Waveform sensitivity (menu "Voice input → Settings → Waveform sensitivity", range 0.1–1, default 0.5)
- 0.5 = standard: identical to the previous look (gain factor of 1).
- 0.5 → 1 = boost range: up to about 10× gain; the closer to 1, the more even a quiet voice fills the bars — useful in a quiet room or with a low-output microphone.
- 0.5 → 0.1 = reduce range: you have to speak louder before the bars rise — useful in noisy rooms or when the microphone gain is too high.
- It applies immediately while dragging and is saved automatically.
Don't lock the screen while recording (menu "Voice input → Settings → Don't auto-lock while recording", on by default)
While recording is active, the app holds a PreventUserIdleDisplaySleep power assertion, so the screen stays lit and the system does not silently lock itself just because the mouse and keyboard have been idle for a while — you can keep talking without being interrupted. This only affects the idle path: a manual lock (⌃⌘Q, menu → Lock Screen), closing the lid, and sleep from the menu are deliberate actions and still take effect. Turn the option off to let the system follow your normal display-off/lock timers.
Never lose what you already said
- Every time a segment is transcribed, the whole uncommitted buffer is written to a draft file (
.pending-voice.txtinside the journal folder). If the app crashes, is force-quit, or the machine loses power, the draft is appended to the voice journal on the next launch, so at most the final un-transcribed segment is lost.
STT service URL
- Configurable under the menu "Voice input → Settings → STT service URL"; a custom URL is supported (OpenAI-compatible
POST /v1/audio/transcriptions, multipartfilefield, returning{"text": "..."}). Default:http://127.0.0.1:8001/v1/audio/transcriptions. - The menu item "Test service connection" checks whether the service is reachable.
- A companion local service for this repo (funasr-llamacpp + FSMN-VAD, with
--persistent): see~/Desktop/foucs/funasr-llamacpp/and itsREADME/start-funasr-server.sh.
Permissions (when running as a packaged .app)
- Microphone: the system prompts for authorization on first use (via
NSMicrophoneUsageDescription). - Accessibility: pasting simulates ⌘V; if unauthorized, the app guides you to System Settings (via
NSAccessibilityUsageDescription).
Note: run the
dist/debuff.appproduced by./scripts/package-macos-app.sh; when running the bare executable directly withswift run, the microphone permission prompt may not appear correctly.
