# Codex Agent Indicator
Use the five G-keys on a Logitech G915 keyboard as a live Codex Desktop task
monitor.


[](https://crates.io/crates/codex-agent-indicator)
[](#license)
## What the lights mean
| 🔵 Blue | Codex is working |
| 🟠Amber | Codex needs approval |
| 🟣 Purple | Codex needs your input |
| 🟢 Green | Codex finished successfully |
| 🔴 Red | Codex stopped with an error |
G1 through G5 each represent one top-level task from the Codex desktop app.
From 09:00 to 17:00 by default, active keys flash brightly while the rest of
the keyboard stays on with a dim, steady background. At night the rest of the
keyboard is dark and occupied G1-G5 indicators stay steadily lit at only 20%
brightness, with success green raised to 40% so it remains visible. Codex CLI,
`codex exec`, Claude Code integrations, other
apps, and ephemeral tasks do not occupy G-keys.
The indicator intentionally uses only G1 through G5. It leaves F1 through F12,
M1 through M3, MR, media controls, macros, and onboard profiles untouched.
Green, red, purple, and amber stay visible until the task is handled. Pressing
the G-key brings Codex to the foreground and selects the matching task in its
sidebar. Green and red clear after that successful navigation. Amber stays
amber until Codex resumes the approved tool; purple stays purple until you
submit the requested input. A blue key stays blue while its task works.
## Requirements
- macOS
- Logitech G915 connected through its wired USB HID interface
- [Rust](https://rustup.rs/) 1.91 or newer
- [`jq`](https://jqlang.github.io/jq/) for safely merging Codex hooks
If `jq` is missing and you use Homebrew:
```sh
brew install jq
```
## Install
### Install the CLI from crates.io
Install the published command-line binary:
```sh
cargo install codex-agent-indicator --version 0.4.15 --locked
```
Cargo installs the command in `~/.cargo/bin`. This gives you the CLI, but it
does not add Codex hooks or create the macOS LaunchAgent.
After installation, update to the latest published release with:
```sh
codex-agent-indicator update
```
The updater uses Cargo to replace the binary in its current install root. This
means a complete `~/.local/bin` setup stays in `~/.local/bin` instead of gaining
a second copy in `~/.cargo/bin`. It preserves your configuration and hooks, and
restarts the keyboard indicator service when it was installed by the complete
setup below.
To reinstall a specific version instead:
```sh
cargo install codex-agent-indicator --version 0.4.15 --locked --force
```
### Complete keyboard-monitor setup
Clone or download this repository, open Terminal in its folder, and run:
```sh
./scripts/install.sh
```
The installer:
1. builds the optimized Rust binary;
2. installs it in `~/.local/bin`;
3. creates a private user configuration;
4. safely adds the required hooks to `~/.codex/hooks.json`;
5. starts a lightweight macOS LaunchAgent.
It preserves unrelated hooks already present in your Codex configuration.
After the first install, open `/hooks` in Codex and trust this command:
```text
~/.local/bin/codex-agent-indicator hook
```
You do not need to restart Codex.
The complete installer builds the same version from the checked-out source and
is recommended for first-time setup because it also configures the Codex hooks
and macOS LaunchAgent.
## Uninstall
From the repository folder, run:
```sh
./scripts/uninstall.sh
```
This removes the daemon, binary, and only this project's hook entries. Your
custom configuration is kept. To remove that too:
```sh
./scripts/uninstall.sh --purge
```
## Use it
Press an illuminated G-key to bring Codex forward and select its matching task
in the sidebar.
Useful commands:
```sh
codex-agent-indicator status
codex-agent-indicator doctor
codex-agent-indicator reload
codex-agent-indicator update
```
You can also preview or clear states manually:
```sh
codex-agent-indicator set done demo-task
codex-agent-indicator set approval demo-task
codex-agent-indicator clear demo-task
codex-agent-indicator clear
```
If your shell cannot find the command, add this to your shell profile:
```sh
export PATH="$HOME/.local/bin:$HOME/.cargo/bin:$PATH"
```
## Customize the lights
Edit:
```text
~/.config/codex-agent-indicator/config.toml
```
The daemon automatically reloads valid changes. The main settings are:
```toml
[lighting]
background = "#101820"
flash_enabled = true
flash_interval_ms = 500
flash_dim_percent = 20
day_start = "09:00"
day_end = "17:00"
night_background = "#000000"
night_indicator_brightness_percent = 20
night_done_brightness_percent = 40
reassert_interval_ms = 1000
[colors]
working = "#007aff"
approval = "#ff9500"
requested = "#af52de"
done = "#34c759"
error = "#ff3b30"
```
- Change `background` to control the steady light on every non-indicator key
and every unoccupied G-key. It does not change active G1-G5 status colours.
Useful six-digit RGB values are:
- `#101820` — very dim;
- `#202c38` — readable in a dark room;
- `#304050` — brighter.
- Increase `flash_interval_ms` for slower flashing.
- Keep `flash_dim_percent` above zero so a delayed background process cannot
leave active indicators looking completely dark.
- Set `flash_enabled = false` for steady status colours.
- `day_start` is inclusive and `day_end` is exclusive. The existing
`background`, `flash_enabled`, `flash_interval_ms`, and `flash_dim_percent`
settings form the day profile. Times use the Mac's current local timezone.
- Night mode automatically covers the rest of the day, from `day_end` back to
`day_start`. It uses `night_background` for every ordinary and unoccupied
key, disables flashing, and scales occupied G1-G5 status colours to
`night_indicator_brightness_percent`. Success green uses
`night_done_brightness_percent` because the generic dim level is effectively
invisible on the G915.
- The default schedule is day mode from `09:00` through `16:59`, then night
mode from `17:00` through `08:59`. A day window may cross midnight, such as
`day_start = "21:00"` and `day_end = "06:00"`.
- Use strict 24-hour `HH:MM` times. `day_start` and `day_end` cannot be equal.
Both night brightness settings must be between 20 and 100 percent because
lower RGB values are effectively invisible on the G915.
- Keep `reassert_interval_ms = 1000` for the fastest supported recovery when
G HUB or another lighting app takes control. Increase it for less frequent
direct-lighting watchdog refreshes.
- Set `navigation.enabled = false` if G-keys should show status without opening
tasks.
- With `detect_questions = true`, direct questions and explicit input requests
use purple. A completed report section labelled as questions for a client,
customer, or stakeholders remains green. Set it to `false` to treat every
stopped turn as completed.
## Behaviour
- The first five top-level Codex Desktop tasks use G1 through G5.
- A task is admitted only when its matching persisted journal identifies
`Codex Desktop` as its origin. Codex CLI, `codex exec`, Claude Code
integrations, other apps, ephemeral sessions, and standalone subagent
sessions are ignored.
- Child activity within an admitted desktop task can update its parent light,
but never receives a separate G-key.
- Fast hooks are reconciled against the known task's local `task_started` and
`task_complete` journal records and correlated local tool-call completions.
A missed terminal or post-approval hook therefore corrects itself within
about 250 ms instead of leaving a stale blue or amber key. An unresolved
tool call remains amber, including when another concurrent call completes.
- Permission and user-input states take priority over unrelated subagent
activity.
- A failed individual tool remains blue while Codex handles it; red is reserved
for a terminal turn failure.
- Finished and attention states are never removed by a timer.
- Current Codex Desktop task lights are restored after the daemon or Mac
restarts. Missing journals and old non-app slots are pruned before the
keyboard is painted, preventing ghost keys.
- Archiving or removing a Codex Desktop task clears its G-key within about
250 ms, including when the task is archived mid-turn without a completion
record.
- A second daemon exits without removing the active daemon's socket, preventing
duplicate processes from competing for keyboard lighting and G-key presses.
- A low-rate watchdog reasserts direct lighting mode after keyboard sleep or
another lighting app takes control.
- Status-cache write failures are recorded without stopping live lighting or
G-key navigation. The daemon reports the last status-write and G915 failures
after recovery so intermittent outages remain diagnosable.
- `codex-agent-indicator status` also reports successful lighting reassertion
count/timing, the current `lighting_mode`, and event-loop delays of at least
250 ms. Routine successful
reassertions stay quiet and do not force additional status-file writes;
repeated loop-delay logs and writes are limited to once every 30 seconds.
- The `status` command asks the daemon for a fresh, state-neutral snapshot
before reading the local cache, so diagnostics are current without adding
background polling.
- Once assigned, a task keeps the same G-key until it is archived, explicitly
cleared, or its terminal green/red state is opened and acknowledged. Opening
amber or purple changes only the selected Codex task; the attention state
remains mapped until Codex resumes the approved tool or receives the
requested input. New tasks never displace an existing blue, green, red,
purple, or amber key.
- Additional Codex Desktop tasks wait in FIFO order when all five keys are
occupied. The oldest waiting task is promoted onto the exact key that becomes
free, without remapping the other four keys.
- Waiting tasks keep receiving lifecycle updates and remain queued across
daemon or Mac restarts. `codex-agent-indicator status` reports them under
`queued_sessions`.
- G-key navigation explicitly targets the Codex app, brings it to the
foreground, and selects the task through its local deep link.
- Terminal green/red is acknowledged only after its Codex deep link opens
successfully. Navigation never acknowledges amber or purple. Amber changes
back to blue only when Codex emits the subsequent tool-resumption event.
- Merely ending a Codex process does not clear an unacknowledged result.
## Troubleshooting
Run:
```sh
codex-agent-indicator doctor
codex-agent-indicator status
```
Common causes:
- The G915 must expose the expected wired USB HID interface.
- Logitech G HUB may overwrite the indicator colours if it is running an active
lighting effect.
- Codex may ask you to trust the hook command after installation.
- G1 through G5 intentionally ignore tasks started from Terminal, `codex exec`,
Claude Code, or another app. Use Codex Desktop when a task should appear on
the keyboard and be reopenable by pressing its G-key.
- A terminal `task_complete` record repairs a missed `Stop` hook. An app-level
failure that writes neither record can still leave the last blue state; use
`codex-agent-indicator set error TASK_ID` to correct that rare case manually.
- A completed, turn-correlated local tool output repairs a missed post-approval
hook. The indicator does not infer approval merely from navigation or from an
unrelated tool output, so a genuinely pending request remains amber.
- The daemon log is stored at
`~/Library/Logs/codex-agent-indicator.log`. Hardware and persistence recovery
events include Unix timestamps. `event-loop-delay` identifies CPU scheduling
or slow local work; a recent successful lighting reassertion with no loop,
HID, or persistence failure points to another lighting app taking control.
- The daemon checks the log once per minute. At 5 MiB it preserves the previous
contents as `.log.1`, shifts the older archive to `.log.2`, and truncates the
active file without interrupting launchd logging. This bounds normal log use
to about 15 MiB and requires no root service or external rotation tool.
## Privacy and performance
- Everything runs locally on your Mac.
- There is no telemetry, analytics, cloud service, or network server.
- Hook messages travel through a private user-only Unix socket.
- Every automatic hook is checked against the matching journal's bounded
metadata record. Only a top-level Codex Desktop task is admitted; unknown or
missing metadata fails closed.
- The daemon follows only the local journals for tasks currently assigned to
G1 through G5 or waiting for a free G-key. It reads at most 256 KiB to
classify a journal, scans at most the latest 8 MiB once after a restart, then
checks only for appended bytes every 250 ms.
- The active day/night schedule is checked at most once per second and only
repaints when the selected mode changes.
- It ignores unrelated journal records and never stores journal content in its
status file or logs. Tool reconciliation retains only in-memory call IDs for
the currently followed tasks.
- The daemon does not poll Codex databases or processes and does not start a
second app-server.
- The LaunchAgent uses macOS's interactive scheduling class because it handles
physical G-key presses. It remains event-driven, keeps filesystem I/O at low
priority, and normally uses only a small fraction of one CPU core.
- Only the tail of a final assistant message is inspected to distinguish a
direct question from a completed response. Clearly labelled external client,
customer, and stakeholder question lists are treated as report content.
- No Accessibility permission, screen recording, browser control, MCP server,
Codex plugin, or extra background app is required.
- Repository templates contain placeholders rather than usernames or personal
computer paths.
## How it works
The project is one small native Rust binary. It serves as:
- the background daemon;
- the fast Codex hook forwarder;
- the G-key task switcher;
- the configuration and diagnostic command.
Hooks send a small Unix datagram and exit. The daemon verifies the task's
persisted Codex Desktop origin, tracks task, turn, and child-agent identity,
batches lighting changes into one HID++ frame, debounces unchanged status-file
writes, restores only valid app tasks after restarts, and sleeps between events.
A bounded local-journal adapter reconciles native turn start, turn completion,
and correlated local tool completion records when a hook is missed. A low-rate
watchdog reclaims Logitech direct-lighting mode. G-key presses use Logitech's
HID++ `0x8010` feature. Task switching uses Codex's
`codex://threads/<thread-id>` deep link.
Only live RGB output and G-key notification diversion are controlled while the
daemon runs. The program does not edit G HUB macros, profiles, key assignments,
or onboard memory.
## Development
```sh
cargo check --all-targets
cargo test --all-targets
cargo clippy --all-targets -- -D warnings
```
Lifecycle replay tests use privacy-scrubbed, version-labelled Codex hook JSON
and the matching official input schemas. A separate pinned Codex app fixture
exercises origin admission and native `task_started`/`task_complete`
reconciliation. A second pinned fixture covers approval-resume tool output,
including concurrent and newer pending calls. The fixtures also cover rejected
CLI/other-app sessions, missing journals, a missed `Stop`, and a newer active
turn. Together they exercise the complete adapter → lifecycle tracker → G-key
slot path:
```sh
cargo test wire::replay_tests
cargo test journal::tests
```
When the Codex hook schema changes, add a new fixture snapshot instead of
rewriting the historical one.
The project intentionally does not require a formatter pass for validation.
## License
Licensed under either of:
- [Apache License, Version 2.0](LICENSE-APACHE)
- [MIT License](LICENSE-MIT)
at your option.
## References
- [OpenAI Codex lifecycle hooks](https://learn.chatgpt.com/docs/hooks)
- [OpenAI Codex desktop commands and deep links](https://learn.chatgpt.com/docs/reference/commands.md)
- [Logitech G915](https://www.logitechg.com/en-us/products/gaming-keyboards/g915-low-profile-wireless-mechanical-gaming-keyboard.html)
- [OpenLogi](https://github.com/AprilNEA/OpenLogi)
- [Workmux Codex status tracking](https://github.com/raine/workmux/blob/main/src/state/codex_status.rs)
- [LED Cube Agent Monitor](https://github.com/pirate/led-cube-agent-monitor)
- [OpenRGB G915 controller](https://github.com/CalcProgrammer1/OpenRGB/tree/master/Controllers/LogitechController/LogitechG915Controller)
- [hidapi 2.6.6](https://docs.rs/hidapi/2.6.6/hidapi/)