Skip to content

Commit 256f503

Browse files
committed
feat: add channels and usage reset alerts
1 parent 25103aa commit 256f503

16 files changed

Lines changed: 2115 additions & 19 deletions

README.md

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,19 @@ Desktop notifications for AI coding tools - get alerts when tasks complete or in
1313
<img src="assets/multi-tools-support-02.png" width="48%" alt="All tools enabled"/>
1414
</p>
1515

16-
[![Version](https://img.shields.io/badge/version-1.8.0-blue.svg)](https://github.com/mylee04/code-notify/releases)
16+
[![Version](https://img.shields.io/badge/version-1.9.0-blue.svg)](https://github.com/mylee04/code-notify/releases)
1717
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
1818
[![macOS](https://img.shields.io/badge/macOS-supported-green.svg)](https://www.apple.com/macos)
1919
[![Linux](https://img.shields.io/badge/Linux-supported-green.svg)](https://www.linux.org/)
2020
[![Windows](https://img.shields.io/badge/Windows-supported-green.svg)](https://www.microsoft.com/windows)
2121

2222
---
2323

24-
## What's New in v1.8.0
24+
## What's New in v1.9.0
2525

26-
- **Immediate AskUserQuestion alerts**: `cn alerts add ask_user` now enables a Claude `PreToolUse` hook for `AskUserQuestion` prompts
27-
- **Preserves custom PreToolUse hooks**: enabling or removing `ask_user` only manages code-notify's own hook and keeps existing user hooks intact
28-
- **Windows UTF-8 stdin fix**: Windows notifications now read redirected Claude hook payloads as UTF-8 before parsing question text
26+
- **Slack and Discord delivery**: `cn channels` can mirror Code-Notify alerts to incoming webhooks
27+
- **Codex and Claude usage alerts**: `cn usage` can warn at 20%/10% and announce daily (5h) or weekly (7d) token resets
28+
- **Distinct reset announcements**: token reset alerts use separate voice/sound controls and include README voice samples
2929

3030
---
3131

@@ -38,6 +38,8 @@ Desktop notifications for AI coding tools - get alerts when tasks complete or in
3838
- **macOS click-through control** - Choose which app notification clicks activate
3939
- **Sound notifications** - Play custom sounds on task completion
4040
- **Voice announcements** - Hear when tasks complete (macOS, Windows)
41+
- **Slack/Discord delivery** - Mirror notifications to incoming webhooks
42+
- **Usage alerts** - Opt-in Codex/Claude 20%, 10%, and reset notifications
4143
- **Tool-specific messages** - "Claude completed the task", "Codex completed the task"
4244
- **Project-specific settings** - Different configs per project
4345
- **Quick aliases** - `cn` and `cnp` for fast access
@@ -117,6 +119,8 @@ curl -s https://raw.githubusercontent.com/mylee04/code-notify/main/docs/installa
117119
| `cn click-through` | Show current macOS click-through mappings |
118120
| `cn click-through add <app>` | Add a macOS click-through mapping |
119121
| `cn alerts` | Configure which events trigger notifications |
122+
| `cn channels` | Configure Slack/Discord delivery channels |
123+
| `cn usage` | Configure Codex/Claude usage alerts |
120124
| `cn sound on` | Enable sound notifications |
121125
| `cn sound set <path>`| Use custom sound file |
122126
| `cn voice on` | Enable voice (macOS, Windows) |
@@ -201,6 +205,51 @@ Alert-type matching applies to Claude Code notification hooks and Gemini CLI not
201205

202206
Agent-team and subagent workflows can be noisy if `permission_prompt` is enabled. If you only want idle pings, run `cn alerts remove permission_prompt && cn on`. Codex currently uses completion events from `notify`, so `permission_prompt` and `idle_prompt` settings do not change Codex behavior.
203207

208+
### Slack And Discord
209+
210+
Code-Notify can also send the same notification to Slack or Discord through incoming webhooks. Desktop notifications still work normally; remote delivery is an extra channel.
211+
212+
```bash
213+
cn channels add slack https://hooks.slack.com/services/...
214+
cn channels add discord https://discord.com/api/webhooks/...
215+
cn channels status
216+
cn channels test all
217+
```
218+
219+
Webhook URLs are stored locally in `~/.config/code-notify/channels.json` and are redacted in `cn status`.
220+
221+
### Usage Alerts
222+
223+
Usage alerts are opt-in for Codex and Claude:
224+
225+
```bash
226+
cn usage on
227+
cn usage check
228+
cn usage watch --interval 300
229+
cn usage thresholds set 20,10
230+
cn usage reset-alerts voice on
231+
cn usage reset-alerts sound default
232+
```
233+
234+
Code-Notify checks the daily (5h) and weekly (7d) usage windows. It sends a warning when remaining usage crosses 20% or 10%, and sends a reset notification when a window returns to 100%.
235+
236+
Reset alerts are intentionally separate from normal task-complete alerts. By default they use a different title, voice message, and reset sound so it is clear that tokens have refilled. The voice message identifies the window, for example `Codex token daily limit reset` or `Codex token weekly limit reset`. You can disable or customize that behavior:
237+
238+
Voice samples:
239+
240+
| Alert | Sample |
241+
| --- | --- |
242+
| Codex daily limit reset | [Listen](assets/audio/codex-token-daily-limit-reset.m4a) |
243+
| Codex weekly limit reset | [Listen](assets/audio/codex-token-weekly-limit-reset.m4a) |
244+
245+
```bash
246+
cn usage reset-alerts off
247+
cn usage reset-alerts voice off
248+
cn usage reset-alerts sound set ~/sounds/tokens-reset.wav
249+
```
250+
251+
Codex usage checks read `~/.codex/auth.json`. Claude usage checks read `~/.claude/.credentials.json`. Code-Notify does not launch provider CLIs, start login flows, or install a background daemon.
252+
204253
## Troubleshooting
205254

206255
**Command not found?**
13.1 KB
Binary file not shown.
13.4 KB
Binary file not shown.

bin/code-notify

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
set -e
77

88
# Version
9-
VERSION="1.8.0"
9+
VERSION="1.9.0"
1010

1111
# Determine the directory where the script is located (resolve symlinks)
1212
SCRIPT_PATH="${BASH_SOURCE[0]}"
@@ -48,7 +48,7 @@ case "$COMMAND_NAME" in
4848
"version"|"-v"|"--version")
4949
show_version
5050
;;
51-
"on"|"off"|"status"|"test"|"setup"|"voice"|"sound"|"alerts"|"update")
51+
"on"|"off"|"status"|"test"|"setup"|"voice"|"sound"|"alerts"|"channels"|"usage"|"update")
5252
source "$LIB_DIR/commands/global.sh"
5353
handle_global_command "$@"
5454
;;
@@ -78,7 +78,7 @@ case "$COMMAND_NAME" in
7878
"code-notify"|*)
7979
# Called as 'code-notify' - full command parsing
8080
case "${1:-help}" in
81-
"on"|"off"|"status"|"test"|"setup"|"voice"|"sound"|"alerts"|"update")
81+
"on"|"off"|"status"|"test"|"setup"|"voice"|"sound"|"alerts"|"channels"|"usage"|"update")
8282
source "$LIB_DIR/commands/global.sh"
8383
handle_global_command "$@"
8484
;;

docs/installation.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ Code-Notify adds desktop notifications to Claude Code, Codex, and Gemini CLI. Yo
99
- Tasks complete
1010
- Claude or Gemini needs your input
1111
- Voice announcements (macOS)
12+
- Slack or Discord webhook messages when channels are configured
13+
- Codex or Claude usage crosses configured thresholds or resets
1214

1315
Codex currently exposes completion notifications through its `notify` hook. Approval and `request_permissions` prompts do not currently trigger Code-Notify through Codex.
1416

@@ -49,6 +51,30 @@ cn status # Should show: Global notifications: ENABLED
4951
cn test # Should trigger a desktop notification
5052
```
5153

54+
### Optional Slack/Discord Delivery
55+
56+
```bash
57+
cn channels add slack https://hooks.slack.com/services/...
58+
cn channels add discord https://discord.com/api/webhooks/...
59+
cn channels test all
60+
```
61+
62+
Webhook URLs are stored locally and are redacted in status output.
63+
64+
### Optional Usage Alerts
65+
66+
```bash
67+
cn usage on
68+
cn usage check
69+
cn usage watch --interval 300
70+
cn usage reset-alerts voice on
71+
cn usage reset-alerts sound default
72+
```
73+
74+
Usage alerts currently support Codex and Claude daily (5h) and weekly (7d) windows. Low-usage warnings use normal Code-Notify delivery. Token reset alerts are separate and can use their own voice/sound controls with `cn usage reset-alerts ...`. Reset voice messages identify the window, such as `Codex token daily limit reset` or `Codex token weekly limit reset`.
75+
76+
They use existing local login state from `~/.codex/auth.json` and `~/.claude/.credentials.json`. Code-Notify does not start provider login flows or install a background scheduler.
77+
5278
### Enable Voice (macOS only)
5379

5480
```bash
@@ -97,6 +123,9 @@ After installation, these files are created:
97123
- `~/.claude/settings.json` - Hook configuration on the default Claude Code path
98124
- `~/.config/.claude/settings.json` - Hook configuration on some Windows Claude Code setups
99125
- `~/.claude/notifications/voice-enabled` - Voice setting (if enabled)
126+
- `~/.config/code-notify/channels.json` - Slack/Discord channel config (if configured)
127+
- `~/.config/code-notify/usage.json` - Usage alert config (if enabled)
128+
- `~/.config/code-notify/usage-state.json` - Usage alert dedupe state (if alerts have fired)
100129

101130
### Uninstallation
102131

@@ -164,6 +193,8 @@ cd code-notify
164193
| `cn status` | Check current status |
165194
| `cn update` | Update code-notify |
166195
| `cn update check` | Check the latest release and show the update command |
196+
| `cn channels` | Configure Slack/Discord delivery |
197+
| `cn usage` | Configure Codex/Claude usage alerts |
167198
| `cn voice on` | Enable voice (macOS) |
168199
| `cnp on` | Enable for current project only |
169200

lib/code-notify/commands/global.sh

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ source "$GLOBAL_CMD_DIR/../utils/voice.sh"
88
source "$GLOBAL_CMD_DIR/../utils/sound.sh"
99
source "$GLOBAL_CMD_DIR/../utils/help.sh"
1010
source "$GLOBAL_CMD_DIR/../utils/click-through.sh"
11+
source "$GLOBAL_CMD_DIR/../utils/channels.sh"
12+
source "$GLOBAL_CMD_DIR/../utils/usage.sh"
1113

1214
CODE_NOTIFY_RELEASES_API="https://api.github.com/repos/mylee04/code-notify/releases/latest"
1315

@@ -44,6 +46,12 @@ handle_global_command() {
4446
"alerts")
4547
handle_alerts_command "$@"
4648
;;
49+
"channels")
50+
handle_channels_command "$@"
51+
;;
52+
"usage")
53+
handle_usage_command "$@"
54+
;;
4755
"click-through")
4856
handle_click_through_command "$@"
4957
;;
@@ -600,6 +608,43 @@ show_status() {
600608
local alert_types=$(get_notify_types)
601609
echo " ${BELL} Alert types: ${CYAN}$alert_types${RESET}"
602610

611+
echo ""
612+
if channels_has_python3; then
613+
local channel_summary
614+
channel_summary="$(channels_list_redacted 2>/dev/null | awk -F '\t' '
615+
$1 == "enabled" { enabled = $2 }
616+
$1 == "channel" { count++; providers[$3] = 1 }
617+
END {
618+
if (enabled == "") enabled = "true"
619+
provider_list = ""
620+
for (provider in providers) {
621+
provider_list = provider_list (provider_list ? "," : "") provider
622+
}
623+
if (count == "") count = 0
624+
print enabled "\t" count "\t" provider_list
625+
}
626+
')"
627+
local channels_enabled channels_count channels_providers
628+
IFS=$'\t' read -r channels_enabled channels_count channels_providers <<< "$channel_summary"
629+
if [[ "$channels_enabled" == "true" && "$channels_count" -gt 0 ]]; then
630+
echo " ${CHECK_MARK} Channels: ${GREEN}ENABLED${RESET} ($channels_count configured: $channels_providers)"
631+
elif [[ "$channels_count" -gt 0 ]]; then
632+
echo " ${MUTE} Channels: ${DIM}DISABLED${RESET} ($channels_count configured)"
633+
else
634+
echo " ${MUTE} Channels: ${DIM}not configured${RESET}"
635+
fi
636+
fi
637+
638+
if usage_has_python3; then
639+
local usage_enabled
640+
usage_enabled="$(usage_read_config_json | python3 -c 'import json,sys; print("true" if json.load(sys.stdin).get("enabled", False) else "false")' 2>/dev/null || echo false)"
641+
if [[ "$usage_enabled" == "true" ]]; then
642+
echo " ${CHECK_MARK} Usage alerts: ${GREEN}ENABLED${RESET}"
643+
else
644+
echo " ${MUTE} Usage alerts: ${DIM}DISABLED${RESET}"
645+
fi
646+
fi
647+
603648
# Notification tool status (platform-specific)
604649
local current_os
605650
current_os="$(detect_os)"

lib/code-notify/core/notifier.sh

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ NOTIFIER_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
1515
source "$NOTIFIER_DIR/../utils/detect.sh"
1616
source "$NOTIFIER_DIR/../utils/voice.sh"
1717
source "$NOTIFIER_DIR/../utils/sound.sh"
18+
source "$NOTIFIER_DIR/../utils/channels.sh"
19+
source "$NOTIFIER_DIR/../utils/usage.sh"
1820
source "$NOTIFIER_DIR/../utils/click-through-store.sh"
1921
source "$NOTIFIER_DIR/../utils/click-through-runtime.sh"
2022
source "$NOTIFIER_DIR/../utils/click-through-resolver.sh"
@@ -475,6 +477,20 @@ case "$HOOK_TYPE" in
475477
VOICE_MESSAGE="Notifications are working"
476478
SOUND="Glass"
477479
;;
480+
"usage")
481+
TITLE="${CODE_NOTIFY_USAGE_TITLE:-$TOOL_DISPLAY usage alert}"
482+
SUBTITLE="Usage Alert"
483+
MESSAGE="${CODE_NOTIFY_USAGE_MESSAGE:-$TOOL_DISPLAY usage changed}"
484+
VOICE_MESSAGE="${CODE_NOTIFY_USAGE_VOICE_MESSAGE:-$TOOL_DISPLAY usage alert}"
485+
SOUND="Ping"
486+
;;
487+
"usage_reset")
488+
TITLE="${CODE_NOTIFY_USAGE_TITLE:-$TOOL_DISPLAY tokens reset}"
489+
SUBTITLE="Tokens Reset"
490+
MESSAGE="${CODE_NOTIFY_USAGE_MESSAGE:-$TOOL_DISPLAY tokens have reset. Usage is back to 100%.}"
491+
VOICE_MESSAGE="${CODE_NOTIFY_USAGE_VOICE_MESSAGE:-$TOOL_DISPLAY tokens have reset}"
492+
SOUND="Hero"
493+
;;
478494
"PreToolUse")
479495
# AskUserQuestion: extract question text and show notification
480496
ASK_QUESTION_TEXT=""
@@ -593,6 +609,10 @@ send_windows_notification() {
593609

594610
# Check if voice is enabled for this tool
595611
should_speak() {
612+
if [[ "$HOOK_TYPE" == "usage_reset" && "${CODE_NOTIFY_USAGE_RESET_VOICE:-true}" == "true" ]]; then
613+
return 0
614+
fi
615+
596616
# Check tool-specific voice setting first
597617
if [[ -n "$TOOL_NAME" ]]; then
598618
local tool_voice_file="$HOME/.claude/notifications/voice-$TOOL_NAME"
@@ -612,6 +632,17 @@ should_speak() {
612632

613633
# Get voice setting (tool-specific or global)
614634
get_voice_setting() {
635+
if [[ "$HOOK_TYPE" == "usage_reset" ]]; then
636+
local usage_reset_voice
637+
usage_reset_voice=$(get_voice "tool" "$TOOL_NAME" 2>/dev/null || get_voice "global" 2>/dev/null || true)
638+
if [[ -n "$usage_reset_voice" ]]; then
639+
printf '%s\n' "$usage_reset_voice"
640+
return
641+
fi
642+
printf '%s\n' "Samantha"
643+
return
644+
fi
645+
615646
# Check tool-specific voice first
616647
if [[ -n "$TOOL_NAME" ]]; then
617648
local tool_voice_file="$HOME/.claude/notifications/voice-$TOOL_NAME"
@@ -627,9 +658,30 @@ get_voice_setting() {
627658

628659
# Check if sound should play
629660
should_play_sound() {
661+
if [[ "$HOOK_TYPE" == "usage_reset" ]]; then
662+
[[ "${CODE_NOTIFY_USAGE_RESET_SOUND:-true}" == "true" ]]
663+
return $?
664+
fi
630665
is_sound_enabled
631666
}
632667

668+
get_notification_sound_file() {
669+
if [[ "$HOOK_TYPE" == "usage_reset" ]]; then
670+
if [[ -n "${CODE_NOTIFY_USAGE_RESET_SOUND_FILE:-}" ]]; then
671+
printf '%s\n' "$CODE_NOTIFY_USAGE_RESET_SOUND_FILE"
672+
return
673+
fi
674+
case "$(detect_os 2>/dev/null || uname -s | tr '[:upper:]' '[:lower:]')" in
675+
"macos"|"Darwin"|"darwin")
676+
printf '%s\n' "/System/Library/Sounds/Hero.aiff"
677+
return
678+
;;
679+
esac
680+
fi
681+
682+
get_sound
683+
}
684+
633685
# Send notification based on OS
634686
OS=$(detect_os)
635687
case "$OS" in
@@ -644,14 +696,14 @@ case "$OS" in
644696
fi
645697
# Sound notification if enabled (separate from voice)
646698
if should_play_sound; then
647-
play_sound
699+
play_sound "$(get_notification_sound_file)"
648700
fi
649701
;;
650702
linux)
651703
send_linux_notification
652704
# Sound notification if enabled
653705
if should_play_sound; then
654-
play_sound
706+
play_sound "$(get_notification_sound_file)"
655707
fi
656708
;;
657709
wsl)
@@ -685,7 +737,7 @@ case "$OS" in
685737
fi
686738
# Sound notification if enabled
687739
if should_play_sound; then
688-
play_sound
740+
play_sound "$(get_notification_sound_file)"
689741
fi
690742
;;
691743
windows)
@@ -698,6 +750,16 @@ case "$OS" in
698750
esac
699751

700752
# Log the notification
753+
channels_deliver "$TITLE" "$MESSAGE" "$TOOL_NAME" "$PROJECT_NAME" "${CODE_NOTIFY_USAGE_CONTEXT:-}" || true
754+
755+
if [[ "${CODE_NOTIFY_SKIP_USAGE_CHECK:-}" != "1" ]]; then
756+
case "$TOOL_NAME" in
757+
"codex"|"claude")
758+
usage_check_with_lock "$TOOL_NAME" >/dev/null 2>&1 || true
759+
;;
760+
esac
761+
fi
762+
701763
LOG_DIR="$HOME/.claude/logs"
702764
if [[ -d "$LOG_DIR" ]]; then
703765
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$TOOL_NAME] [$PROJECT_NAME] $MESSAGE" >> "$LOG_DIR/notifications.log"

0 commit comments

Comments
 (0)