nuphy-capslock-agent-beacon turns the caps lock light bar on a NuPhy Air75 V3
into a private, local notification beacon on macOS. The light bar pulses when
Codex or Claude Code needs approval, presents a plan/question, or
finishes a response while its app is in the background. The light bar returns to it's normal state when you review the agent's response in their native desktop apps.
It makes no network requests, contains no downloaded runtime dependencies, does not read keystrokes, and never approves agent action on your behalf.
The standard Caps Lock light bar pulses when an agent is waiting for you.
There is no window to keep open and no keyboard profile to configure:
- Codex or Claude Code emits an official local hook event when it needs input or finishes a response.
- The included Python hook writes a tiny routing-only marker on your Mac. It does not save the prompt, response, command, or tool data.
- The background Swift app sees that marker and alternates the Air75 V3's standard Caps Lock LED bit, making the Caps Lock light bar pulse.
- Returning to the desktop app or submitting your next CLI prompt clears the marker and restores the real Caps Lock light state.
The app starts automatically after login by default. Agent hooks can also launch it when needed. After setup, use Codex and Claude normally. The beacon is automatic.
Copy and paste this into Codex or Claude on the Mac you want to configure:
Install NuPhy CapsLock Agent Beacon from
https://github.com/justintylerm/nuphy-capslock-agent-beacon.
Follow docs/AGENT_INSTALL.md.
The longer agent installation guide gives the agent a fixed safety boundary, an inspection checklist, and a private verification flow.
You need:
- macOS 15 or newer;
- a NuPhy Air75 V3 in USB wired mode or Bluetooth channel 1;
- Codex and/or Claude Code already installed;
- Apple's Xcode Command Line Tools.
Check for Apple's Swift compiler:
xcrun --find swiftIf that command fails, run xcode-select --install and complete Apple's prompt.
Nothing is piped from the internet into a shell. Clone the source, preview the exact installation plan, and then run it:
git clone https://github.com/justintylerm/nuphy-capslock-agent-beacon.git
cd nuphy-capslock-agent-beacon
./install.sh --dry-run
./install.shThe installer automatically configures whichever supported agents it finds.
To select them explicitly, use ./install.sh --codex, ./install.sh --claude,
or ./install.sh --codex --claude.
Open System Settings → Privacy & Security → Input Monitoring and enable NuPhy CapsLock Agent Beacon. macOS places keyboard LED access in this broad permission category even though this app never requests keyboard input reports.
If the app exited while waiting for permission, launch it again from
~/Applications/NuPhy CapsLock Agent Beacon.app or rerun ./install.sh.
In Codex, enter /hooks, review the new user hooks, and trust them. Claude Code
loads its settings automatically.
You do not need NuPhy's web configurator, a special RGB effect, or an open beacon window.
- Ask Codex or Claude a normal question that takes a few seconds to answer.
- Switch to another app before the response finishes.
- When the final response is ready, the Caps Lock light bar should pulse.
- Return to the Codex/Claude desktop app, or submit your next CLI prompt. The pulse should stop and the real Caps Lock state should be restored.
Final-response alerts are intentionally suppressed while you are already looking at the agent app. Approval and plan/chooser prompts pulse as soon as they appear. If the test fails, follow the troubleshooting checklist.
| Component | Status |
|---|---|
| NuPhy Air75 V3 ANSI, current tested firmware | Tested |
| USB wired mode | Tested |
Bluetooth channel 1 (Air75 V3-1) |
Tested |
| Codex desktop app hooks | Tested |
| Claude Code hooks | Tested |
| macOS 15 or newer | Required |
| Bluetooth channels 2/3, 2.4 GHz, ISO/JIS layouts | Not yet verified |
| Other NuPhy models | Intentionally rejected until safely verified |
The installer:
- builds the Swift app from the checked-out source with Apple's toolchain;
- installs it only for your account under
~/Applications; - installs one readable Python hook under your Application Support folder;
- merges its handlers into existing Codex and Claude JSON without replacing unrelated settings or hooks;
- saves mode-
0600backups before editing either JSON file; - optionally adds a user LaunchAgent so the app starts after login;
- uses no
sudo, package manager, remote installer, or network call.
To disable automatic startup after login while keeping on-demand hook launching:
./install.sh --codex --claude --no-login-item- Approval or plan question: starts pulsing immediately.
- Tool completes, fails, or is denied: clears the matching approval pulse.
- Final response in the background: starts pulsing after a one-second grace period, preventing a false alert when you are already looking at the app.
- Return to the Codex or Claude Desktop app: clears the message pulse.
- CLI users: the next submitted prompt clears the pulse; merely focusing an arbitrary terminal cannot be detected without broader app/input observation.
- Caps Lock is genuinely on: the beacon pulses around the real state and restores the light to on when the alert ends.
- Disconnect/reconnect: the app retries and restores the real Caps Lock indicator whenever it regains the exact keyboard interface.
- Update/uninstall: the app handles the termination signal and restores the real indicator before the installer continues.
The default pulse alternates every 0.25 seconds. To change it, edit
~/Library/Application Support/NuPhy CapsLock Agent Beacon/pulse-timing.json
and restart the app. Each value must be between 0.15 and 5 seconds.
The Air75 V3's animated RGB modes use a vendor-specific control protocol. During development, RGB effects worked but required repeated vendor lighting-state writes and introduced more failure modes, including a temporarily stuck indicator state. The final project excludes every RGB, profile, keymap, macro, firmware, and reset command.
The Caps Lock light bar is controlled through the standard keyboard LED output that macOS already uses for normal Caps Lock indication. Beacon writes are volatile: they change one output bit, not the keyboard's saved lighting profile or firmware. There is no idle write heartbeat. See Safety for the precise hardware boundary and measured tradeoffs.
Everything stays on the Mac:
- no telemetry, sockets, web requests, analytics, or update checker;
- no Accessibility, Screen Recording, Apple Events, or keystroke capture;
- prompt text, response text, commands, tool inputs, and tool outputs are never written by this project;
- marker files retain only source/session/turn/tool routing identifiers and use private permissions;
- the Codex final-response gate scans only the exact transcript path supplied by
the official Stop hook for a matching
task_completetype and turn ID. It neither parses nor stores conversation content; - diagnostics contain timestamps, event names, sources, counts, and app state, never conversation content.
Codex documents that its transcript JSONL format is not stable, so a Codex update can break final-message detection without expanding this project's data access. Approval and plan hooks do not depend on that scan. Read the full architecture and security policy.
Preview the exact removal, then run it:
./uninstall.sh --dry-run
./uninstall.shThe uninstaller removes only this project's exact hook command, leaves unrelated
settings intact, and moves installed files to a dated folder in ~/.Trash so
they remain recoverable. Add --reset-permission if you also want macOS to
forget this app's Input Monitoring decision. More detail is in
UNINSTALL.md.
sh Scripts/test.sh
sh Scripts/build-beacon-app.shThe automated tests use temporary directories and do not open a HID device or write to a keyboard. Hardware tests are intentionally manual and opt-in.
Start with Troubleshooting. If your Air75 V3 is safely rejected, add a report using the privacy checklist in Compatibility; do not post serial numbers, local paths, hook payloads, or transcripts.
Official references:
This independent project is not affiliated with or endorsed by NuPhy, OpenAI, or Anthropic. NuPhy, Codex, Claude, and macOS are trademarks of their respective owners.
Licensed under the MIT License.