macOS notifications with sound and voice alerts for agent CLIs.
The repository still ships the original Claude Code plugin, and now also includes a dedicated Codex adapter. They live in one repo, but each environment keeps its own entrypoint and installation flow.
| Adapter | Entry point | Installation model |
|---|---|---|
| Claude Code | hooks/scripts/notify.sh |
Claude plugin |
| Codex | adapters/codex/notify.sh |
~/.codex/config.toml notify command |
When the adapter fires, you get:
- Desktop notification via
terminal-notifierwhen available - Audio alert with
Basso - Voice announcement with the session label
- Closing sound with
Submarine
The spoken label is environment-specific. For Codex, the voice uses Codex {label}. For Claude, the existing Claude Code {label} behavior is preserved.
- macOS
afplaysayosascriptterminal-notifier
brew install terminal-notifierThe Claude adapter stays backward-compatible with the existing plugin layout.
From a local directory:
claude plugin install /path/to/claude-code-notifyFrom GitHub:
claude plugin install github:robertoecf/claude-code-iterm-notifyRestart Claude Code after installing or updating. Claude loads hooks at session start.
The plugin registers two Notification hooks:
permission_promptidle_prompt
Both still call hooks/scripts/notify.sh. That path is now a shim that delegates to adapters/claude/notify.sh, so older setups do not break.
echo '{"message":"test","title":"Claude Code"}' | bash hooks/scripts/notify.shCodex does not use the Claude plugin system. The supported integration point is the notify command in ~/.codex/config.toml.
Copy the adapter to your Codex bin directory:
bash adapters/codex/install.sh --install-script ~/.codex/bin/codex-notify.shbash adapters/codex/install.sh --print-config ~/.codex/bin/codex-notify.shExpected output:
notify = ["/Users/you/.codex/bin/codex-notify.sh"]Then add that line to ~/.codex/config.toml.
Run the adapter in dry-run mode:
bash adapters/codex/install.sh --self-test ~/.codex/bin/codex-notify.shOr invoke the repository script directly:
NOTIFY_TEST_MODE=1 bash adapters/codex/notify.sh \
'{"type":"agent-turn-complete","cwd":"/tmp/example","last-assistant-message":"READY_FOR_REVIEW: notifier pronto","title":"Codex"}'Codex notify is best-effort and follows Codex's own signals, not Claude's hook model.
The adapter maps the final agent message like this:
READY_FOR_REVIEW:→Pronto para revisaoINPUT_NEEDED:→Input necessarioAUTH_NEEDED:→Autorizacao necessaria- anything else on
agent-turn-complete→Turno concluido
The Codex adapter resolves the label in this order:
- iTerm2 profile name when
ITERM_SESSION_IDis available - Codex App process ancestry when the script is launched from the macOS app
- normalized terminal name from
TERM_PROGRAM - basename of the
cwdin the Codex payload Codex
This keeps multiple tabs distinguishable without hardcoding agent names into the voice message.
The adapter now resolves these environments explicitly:
Ghostty→GhosttyiTerm2→ profile name when available, otherwiseiTerm2Apple_Terminal→Terminal.appvscode→VS Code- Codex macOS App process tree →
Codex App
If you run multiple Claude or Codex sessions in iTerm2, create named profiles such as 1, 2, api, or review.
When a session needs attention, the voice will use the profile name when it can be resolved. That is the cleanest way to identify parallel sessions.
.
├── adapters
│ ├── claude
│ │ └── notify.sh
│ └── codex
│ ├── install.sh
│ └── notify.sh
├── hooks
│ ├── hooks.json
│ └── scripts
│ └── notify.sh
└── tests
└── codex_notify_test.sh
Run the Codex adapter regression test:
./tests/codex_notify_test.sh- Restart Claude Code
- Check
~/.claude/settings.jsonfor invalid configuration - Test
hooks/scripts/notify.shmanually
- Confirm
notifyin~/.codex/config.tomlpoints to the right script - Confirm the script receives the payload as an argument
- Use
NOTIFY_TEST_MODE=1to inspect parsed output without sending desktop notifications
That is expected. Codex notify is configured through config.toml, and the current documented external notification event is agent-turn-complete. This repository keeps adapters separate so each environment can use the signals it actually exposes.
MIT — see LICENSE.