capsude
v0.1.0
Published
Blinks the Caps Lock LED when Claude Code finishes a turn or needs user input.
Downloads
130
Maintainers
Readme
capsude
Blinks your Mac's Caps Lock LED when Claude Code finishes a turn or needs your input. Ambient, peripheral, nothing on screen.
capsude speaks two Morse letters:
K(—·—) — ham-radio prosign for "over to you, transmit". Fires after Claude finishes and stays idle for 8 seconds. Suppressed if a terminal is focused (you're already watching). ~1.35s total.?(··——··) — Morse question mark, "I have a question." Fires on any Notification event (permission prompt, clarifying question). Ignores the focus guard — if Claude is actively waiting, you get the signal even if you're in a terminal. ~2.3s total.
Both are rate-limited to once per 2 seconds. After a day or two the two codes become distinguishable at a glance — K feels like ta-di-ta, ? feels like di-di-da-da-di-di.
macOS only. Node 18+.
Install
npm install -g capsude
capsudeOn first run, capsude will:
- Try to toggle Caps Lock. macOS will raise a permission prompt.
- Detect that permission is missing, open System Settings → Privacy & Security → Accessibility, and print instructions.
- Exit. Grant permission, then re-run
capsude.
Once permission is granted, capsude installs hooks into ~/.claude/settings.json (with a timestamped backup) and starts watching.
First-run: granting Accessibility permission
When the Accessibility pane opens:
- Look for an entry whose path ends in
/bin/capstoggle. The full absolute path is shown — this is normal and expected. - Toggle it ON.
- Re-run
capsude.
If you don't see capstoggle in the list, scroll to the bottom. New Accessibility entries often appear unsorted. The entry only appears after the first permission-triggering call, which capsude just made.
Running in the background
Foreground mode (default) runs in a terminal; close the terminal and it stops. To start automatically at login and run in the background:
capsude --install-agent # installs ~/Library/LaunchAgents/com.capsude.daemon.plist
capsude --uninstall-agent # removes itThe LaunchAgent runs capsude --daemon at login. Logs go to ~/Library/Logs/capsude.log.
Note: KeepAlive is intentionally false — if the daemon crashes with Caps Lock stuck on, you won't be caught in a restart loop that keeps flipping the LED. Restart manually with capsude --install-agent (re-runs launchctl load) or by running capsude in a terminal.
Commands
capsude Start watching in foreground (installs hook on first run)
capsude --daemon Start in daemon mode (used by LaunchAgent; no setup)
capsude send <event> Send event to running daemon (Stop | Notification | Activity)
capsude --uninstall Remove hook from ~/.claude/settings.json
capsude --install-agent Install LaunchAgent
capsude --uninstall-agent Remove LaunchAgent
capsude --status Show install state, daemon state, paths
capsude --test Trigger a test blink via the running daemon
capsude --help Show usageConfiguration
One environment variable:
CAPSUDE_IDLE_MS— milliseconds of silence after a Stop event before blinking. Default8000. Lower it (e.g.5000) for more responsive "Claude is done" signals; raise it (e.g.12000) if you find it triggers during short inter-turn pauses.
Uninstall
capsude --uninstall-agent # if you installed the LaunchAgent
capsude --uninstall # restores settings.json from the backup capsude made on install
npm uninstall -g capsude--uninstall performs a byte-identical restore of ~/.claude/settings.json from the backup taken at install time. If the backup is missing (e.g. you deleted it), capsude falls back to surgically removing its own hook entries.
Troubleshooting
Permission prompt doesn't appear / capsude exits immediately with "Accessibility permission".
The prompt only appears when capstoggle actually attempts to set Caps Lock state — which happens on first run. If you dismissed the prompt, run capsude again to retrigger. If it still fails, open the Accessibility pane manually:
open "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility"Look for the capstoggle entry (path ending in /bin/capstoggle) and toggle it on.
Daemon won't start: "another daemon is already listening".
Another capsude process is using the socket. Check with capsude --status. If it's your LaunchAgent, stop it with launchctl unload ~/Library/LaunchAgents/com.capsude.daemon.plist before running in foreground.
Hooks don't seem to fire.
capsude --status— confirms hooks are installed and daemon is running.capsude send Notification— manually triggers a blink through the full pipeline. If this works, the daemon is healthy and the issue is with Claude Code invoking the hooks. Check~/.claude/settings.jsonfor the expected entries.~/Library/Logs/capsude.log(if running via LaunchAgent) shows event timestamps.
Caps Lock gets stuck on.
Press Caps Lock once manually. The daemon's cleanup trap tries to restore parity on exit, but kill -9 bypasses it.
postinstall fails with "swiftc not found".
Install Xcode Command Line Tools:
xcode-select --install
npm install -g capsudeLaunchAgent doesn't start at login.
Check ~/Library/Logs/capsude.log for errors. Verify the plist has the correct absolute path to the capsude binary (it should match which capsude). Reinstall with capsude --install-agent to regenerate with the current path.
FAQ
Why Caps Lock specifically?
It's the only modifier LED that can be toggled programmatically on modern Macs without installing a kernel extension. Num Lock doesn't exist on Apple keyboards; the other LEDs (power, battery) are OS-controlled. Caps Lock is also peripheral — you'll see it out of the corner of your eye without needing to look at the screen.
Why not a sound, menu bar icon, or notification?
- Sound is disruptive in shared spaces.
- Menu bar icons require GUI focus to notice.
- macOS notifications are easy to miss when you're focused on another window, and they clutter Notification Center.
A physical LED on your keyboard works regardless of what's on screen and demands zero focus to parse.
Why macOS only?
Caps Lock LED control differs significantly across platforms. Linux requires setleds or X11 APIs (and Wayland breaks most of them). Windows needs keybd_event hacks. Both deserve separate implementations rather than a cross-platform abstraction that works poorly everywhere.
Does capsude read my keyboard input?
No. The Accessibility permission is required because macOS groups "modify modifier lock state" under the same permission class as input monitoring. capsude only writes the Caps Lock state via IOHIDSetModifierLockState — it never reads keystrokes. The capstoggle binary is ~50 lines of Swift; the source is in src/capstoggle.swift.
What happens if Claude Code changes its hook event names?
The capsude send subcommand validates event names (Stop, Notification, Activity). If Claude Code introduces a new hook event that capsude should care about, update src/installer.js (add the hook kind) and src/send.js (add to VALID_EVENTS).
License
MIT. See LICENSE.
