🚦 CLI for a USB-HID traffic-light device that turns your desk light into a live session indicator for Claude Code, Codex, and Opencode.
- Control red, yellow, and green LEDs individually or all at once
- Three modes per LED:
off,on, andblinkwith configurable speed solocommand lights one LED and clears the other twostatusquery reads current device statebootplays a startup animation- Claude Code, Codex, and Opencode hook integration — the light follows session events automatically where supported
- Node.js 18+
- The semaphore device (VID
0x1209, PID0x0001) connected via USB
npm install -g semaphore-cliOr from source:
git clone https://github.com/artemy/semaphore
cd semaphore/software
npm install
npm install -g .semaphore <led> off
semaphore <led> on
semaphore <led> blink [half_period_ms]
semaphore solo <color> on
semaphore solo <color> blink [half_period_ms]
semaphore status
semaphore boot
<led>:red|yellow|green|all<color>:red|yellow|green(solo turns off the other two)- blink half-period: 20–5000 ms (default 500)
--soft/-s: silently exit 0 on device-connectivity errors (for hooks)
semaphore red on # turn red LED on
semaphore green blink 250 # fast-blink green (250 ms half-period)
semaphore solo yellow on # yellow on, red and green off
semaphore all off # turn everything off
semaphore status # read and print current device state
⚠️ Every hook command uses--soft. This keeps the LED best-effort when the device is unplugged, and suppresses stdout so hook output is not injected into the model's context.
Claude Code hooks lifecycle
Claude Code support uses the documented lifecycle hooks in hooks/hooks.json:
| LED state | Meaning | Hook events |
|---|---|---|
| green solid | Idle / waiting for input | SessionStart, Stop |
| red solid | Claude is busy | UserPromptSubmit, PostToolUse |
| yellow blink | Needs human attention | PermissionRequest, Notification |
| all off | No active session | SessionEnd |
Make sure the package is installed globally first, then run from inside Claude Code:
/plugin marketplace add artemy/semaphore
/plugin install semaphore-hooks@semaphore
Merge the hooks key from hooks/hooks.json into your .claude/settings.json.
Codex hooks lifecycle
Codex support uses the documented Codex lifecycle hooks in hooks/codex-hooks.json:
| LED state | Meaning | Hook events |
|---|---|---|
| green solid | Idle / waiting for input | Stop |
| red solid | Codex is busy | UserPromptSubmit, PostToolUse |
| yellow blink | Needs human attention | PreToolUse (on request_user_input) |
| yellow blink | Needs human approval | PermissionRequest |
Make sure the package is installed globally first, then run from your terminal:
codex plugin marketplace add artemy/semaphore
codex plugin add semaphore-hooks@semaphore
⚠️ Codex will prompt you to review the newly installed hooks after you run it next time.
Copy the contents of hooks/codex-hooks.json into your .codex/hooks.json.
Opencode hooks lifecycle
Opencode support uses the native plugin system — the plugin at
.opencode/plugins/semaphore-hooks.js is auto-discovered and maps Opencode lifecycle events to the semaphore device:
| LED state | Meaning | Opencode events |
|---|---|---|
| green solid | Idle / waiting for input | session.created, session.idle |
| red solid | Opencode is busy | message.updated (role=user), tool.execute.after |
| yellow blink | Needs human attention | permission.asked, tool.execute.before (on question) |
| all off | No active session | session.deleted, dispose |
TODO: Installation
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
This project is licensed under the MIT License — see the LICENSE.md file for details