@landienzla/claude-code-notify 1.0.4 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -11
- package/bin/cli.js +40 -41
- package/hooks/scripts/notify.sh +40 -14
- package/package.json +7 -1
package/README.md
CHANGED
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
# claude-code-notify
|
|
2
2
|
|
|
3
|
+
[](https://github.com/Landienzla/claude-code-notify/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/@landienzla/claude-code-notify)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
|
|
3
7
|
Cross-platform desktop notifications for [Claude Code](https://docs.anthropic.com/en/docs/claude-code). Get notified when Claude Code needs your attention — permission approval, task completion, idle prompts, and more.
|
|
4
8
|
|
|
5
9
|
Works as a **Claude Code plugin** (zero config) or as a **CLI setup tool**.
|
|
6
10
|
|
|
7
11
|
## Platforms
|
|
8
12
|
|
|
9
|
-
| Platform | Method |
|
|
10
|
-
|
|
11
|
-
| Windows | Toast notification (slides in from right) |
|
|
12
|
-
| macOS | Native notification center |
|
|
13
|
-
| Linux | `notify-send` (libnotify) |
|
|
14
|
-
| WSL | Windows toast via PowerShell |
|
|
13
|
+
| Platform | Method |
|
|
14
|
+
|----------|--------|
|
|
15
|
+
| Windows | Toast notification (slides in from right) |
|
|
16
|
+
| macOS | Native notification center |
|
|
17
|
+
| Linux | `notify-send` (libnotify) |
|
|
18
|
+
| WSL | Windows toast via PowerShell |
|
|
19
|
+
|
|
20
|
+
Notifications are **silent by default**. Use `--sound` to enable sound. Use `--name` to give your Claude a name.
|
|
15
21
|
|
|
16
22
|
## Quick Start
|
|
17
23
|
|
|
@@ -53,12 +59,16 @@ Plugin mode requires no changes to your `settings.json` — the hook is loaded a
|
|
|
53
59
|
|
|
54
60
|
| Command | Description |
|
|
55
61
|
|---------|-------------|
|
|
56
|
-
| `claude-code-notify setup` | Install notification hook
|
|
62
|
+
| `claude-code-notify setup` | Install notification hook (silent) |
|
|
63
|
+
| `claude-code-notify setup --sound` | Install with sound enabled |
|
|
64
|
+
| `claude-code-notify setup --name "Jarvis"` | Install with a custom name |
|
|
57
65
|
| `claude-code-notify dry-run` | Preview what setup will change (no writes) |
|
|
58
66
|
| `claude-code-notify test` | Send a test notification |
|
|
59
67
|
| `claude-code-notify uninstall` | Remove notification hook and script |
|
|
60
68
|
| `claude-code-notify help` | Show usage info |
|
|
61
69
|
|
|
70
|
+
Options can be combined: `claude-code-notify setup --sound --name "Jarvis"`
|
|
71
|
+
|
|
62
72
|
Run `dry-run` first to see exactly what files will be created/modified before committing.
|
|
63
73
|
|
|
64
74
|
## How It Works
|
|
@@ -81,7 +91,7 @@ The hook fires on all notification types:
|
|
|
81
91
|
|
|
82
92
|
## Requirements
|
|
83
93
|
|
|
84
|
-
- **
|
|
94
|
+
- **Node.js** — already installed if you're using npm
|
|
85
95
|
- **Linux:** `libnotify-bin` for `notify-send`
|
|
86
96
|
```bash
|
|
87
97
|
# Debian/Ubuntu
|
|
@@ -119,9 +129,6 @@ npx @landienzla/claude-code-notify uninstall
|
|
|
119
129
|
- Install `libnotify-bin` (see Requirements)
|
|
120
130
|
- Ensure a notification daemon is running (e.g. `dunst`, `mako`, GNOME/KDE built-in)
|
|
121
131
|
|
|
122
|
-
**Generic "Needs your attention" message instead of context:**
|
|
123
|
-
- Ensure Python 3 is installed and on your PATH
|
|
124
|
-
|
|
125
132
|
## License
|
|
126
133
|
|
|
127
134
|
[MIT](LICENSE)
|
package/bin/cli.js
CHANGED
|
@@ -13,20 +13,30 @@ const NOTIFY_DEST = path.join(SCRIPTS_DIR, 'notify.sh');
|
|
|
13
13
|
const PACKAGE_ROOT = path.resolve(__dirname, '..');
|
|
14
14
|
const NOTIFY_SRC = path.join(PACKAGE_ROOT, 'hooks', 'scripts', 'notify.sh');
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
16
|
+
// Parse flags
|
|
17
|
+
const args = process.argv.slice(3);
|
|
18
|
+
const sound = args.includes('--sound');
|
|
19
|
+
const nameIdx = args.indexOf('--name');
|
|
20
|
+
const name = nameIdx !== -1 && args[nameIdx + 1] ? args[nameIdx + 1] : null;
|
|
21
|
+
const soundFlag = sound ? ' --sound' : '';
|
|
22
|
+
const nameFlag = name ? ` --name "${name}"` : '';
|
|
23
|
+
|
|
24
|
+
function hookConfig() {
|
|
25
|
+
return {
|
|
26
|
+
Notification: [
|
|
27
|
+
{
|
|
28
|
+
matcher: '',
|
|
29
|
+
hooks: [
|
|
30
|
+
{
|
|
31
|
+
type: 'command',
|
|
32
|
+
command: `bash "${NOTIFY_DEST.replace(/\\/g, '/')}"${soundFlag}${nameFlag}`,
|
|
33
|
+
timeout: 10
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
};
|
|
39
|
+
}
|
|
30
40
|
|
|
31
41
|
function dryRun() {
|
|
32
42
|
console.log('Dry run — no changes will be made.\n');
|
|
@@ -36,7 +46,7 @@ function dryRun() {
|
|
|
36
46
|
console.log(` 2. Add Notification hook to:`);
|
|
37
47
|
console.log(` ${SETTINGS_PATH}\n`);
|
|
38
48
|
console.log(' Hook config:');
|
|
39
|
-
console.log(JSON.stringify(
|
|
49
|
+
console.log(JSON.stringify(hookConfig(), null, 2).split('\n').map(l => ' ' + l).join('\n'));
|
|
40
50
|
|
|
41
51
|
if (fs.existsSync(SETTINGS_PATH)) {
|
|
42
52
|
try {
|
|
@@ -49,16 +59,6 @@ function dryRun() {
|
|
|
49
59
|
}
|
|
50
60
|
}
|
|
51
61
|
|
|
52
|
-
// Check python3
|
|
53
|
-
const { spawnSync } = require('child_process');
|
|
54
|
-
const py = spawnSync('python3', ['--version'], { stdio: 'pipe' });
|
|
55
|
-
if (py.status !== 0) {
|
|
56
|
-
console.log('\n Warning: Python 3 not found. Notifications will work but show');
|
|
57
|
-
console.log(' generic messages instead of contextual ones.');
|
|
58
|
-
} else {
|
|
59
|
-
console.log(`\n Python 3: ${py.stdout.toString().trim()}`);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
62
|
console.log('\nRun "claude-code-notify setup" to apply these changes.');
|
|
63
63
|
}
|
|
64
64
|
|
|
@@ -68,15 +68,6 @@ function setup() {
|
|
|
68
68
|
process.exit(1);
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
// Check python3 availability
|
|
72
|
-
const { spawnSync } = require('child_process');
|
|
73
|
-
const py = spawnSync('python3', ['--version'], { stdio: 'pipe' });
|
|
74
|
-
if (py.status !== 0) {
|
|
75
|
-
console.log(' Warning: Python 3 not found. Notifications will work but show');
|
|
76
|
-
console.log(' generic messages instead of contextual ones.');
|
|
77
|
-
console.log(' Install Python 3 for full functionality.\n');
|
|
78
|
-
}
|
|
79
|
-
|
|
80
71
|
// Copy notify.sh
|
|
81
72
|
fs.mkdirSync(SCRIPTS_DIR, { recursive: true });
|
|
82
73
|
fs.copyFileSync(NOTIFY_SRC, NOTIFY_DEST);
|
|
@@ -101,11 +92,12 @@ function setup() {
|
|
|
101
92
|
}
|
|
102
93
|
|
|
103
94
|
if (!settings.hooks) settings.hooks = {};
|
|
104
|
-
settings.hooks.Notification =
|
|
95
|
+
settings.hooks.Notification = hookConfig().Notification;
|
|
105
96
|
|
|
106
97
|
fs.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2) + '\n');
|
|
107
98
|
console.log(` Updated ${SETTINGS_PATH}`);
|
|
108
99
|
|
|
100
|
+
if (name) console.log(` Notifications will appear as "${name}".`);
|
|
109
101
|
console.log('\nDone! Restart Claude Code for notifications to take effect.');
|
|
110
102
|
}
|
|
111
103
|
|
|
@@ -138,7 +130,10 @@ function test() {
|
|
|
138
130
|
const input = JSON.stringify({ notification_message: 'Test notification from claude-code-notify' });
|
|
139
131
|
try {
|
|
140
132
|
const { spawnSync } = require('child_process');
|
|
141
|
-
const
|
|
133
|
+
const scriptArgs = [NOTIFY_SRC];
|
|
134
|
+
if (sound) scriptArgs.push('--sound');
|
|
135
|
+
if (name) scriptArgs.push('--name', name);
|
|
136
|
+
const result = spawnSync('bash', scriptArgs, {
|
|
142
137
|
input,
|
|
143
138
|
stdio: ['pipe', 'inherit', 'inherit'],
|
|
144
139
|
timeout: 15000
|
|
@@ -156,11 +151,15 @@ function help() {
|
|
|
156
151
|
claude-code-notify - Desktop notifications for Claude Code
|
|
157
152
|
|
|
158
153
|
Usage:
|
|
159
|
-
claude-code-notify setup
|
|
160
|
-
claude-code-notify dry-run
|
|
161
|
-
claude-code-notify uninstall
|
|
162
|
-
claude-code-notify test
|
|
163
|
-
claude-code-notify help
|
|
154
|
+
claude-code-notify setup [options] Install notification hook into ~/.claude/
|
|
155
|
+
claude-code-notify dry-run [options] Preview what setup will change (no writes)
|
|
156
|
+
claude-code-notify uninstall Remove notification hook from ~/.claude/
|
|
157
|
+
claude-code-notify test [options] Send a test notification
|
|
158
|
+
claude-code-notify help Show this help
|
|
159
|
+
|
|
160
|
+
Options:
|
|
161
|
+
--sound Enable notification sound (default: silent)
|
|
162
|
+
--name <name> Give your Claude a name (e.g. --name "Jarvis")
|
|
164
163
|
|
|
165
164
|
Plugin mode (no setup needed):
|
|
166
165
|
claude --plugin-dir /path/to/node_modules/claude-code-notify
|
package/hooks/scripts/notify.sh
CHANGED
|
@@ -2,23 +2,41 @@
|
|
|
2
2
|
# Cross-platform desktop notification for Claude Code
|
|
3
3
|
# Reads hook JSON from stdin, extracts notification message, sends native OS notification
|
|
4
4
|
|
|
5
|
+
# Parse flags
|
|
6
|
+
SILENT=true
|
|
7
|
+
NAME="Claude Code"
|
|
8
|
+
while [ $# -gt 0 ]; do
|
|
9
|
+
case "$1" in
|
|
10
|
+
--sound) SILENT=false ;;
|
|
11
|
+
--silent) SILENT=true ;;
|
|
12
|
+
--name) shift; NAME="$1" ;;
|
|
13
|
+
esac
|
|
14
|
+
shift
|
|
15
|
+
done
|
|
16
|
+
|
|
5
17
|
INPUT=$(cat)
|
|
6
18
|
|
|
7
19
|
# Parse JSON and sanitize for safe shell usage
|
|
8
20
|
# - Strip characters that could break shell/PowerShell/XML/AppleScript interpolation
|
|
9
21
|
# - Truncate to 200 chars (OS notification display limit)
|
|
10
|
-
MESSAGE=$(printf '%s' "$INPUT" |
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
22
|
+
MESSAGE=$(printf '%s' "$INPUT" | node -e "
|
|
23
|
+
let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{
|
|
24
|
+
try{const o=JSON.parse(d);
|
|
25
|
+
let m=o.notification_message||o.message||'Needs your attention';
|
|
26
|
+
m=m.replace(/[^\w\s.,!?:;()\[\]{}\/\\@#%+=\-]/g,'').slice(0,200);
|
|
27
|
+
process.stdout.write(m);
|
|
28
|
+
}catch(e){process.stdout.write('Needs your attention')}
|
|
29
|
+
});
|
|
17
30
|
" 2>/dev/null)
|
|
18
31
|
|
|
19
|
-
# Fallback if python3 parsing fails
|
|
20
32
|
MESSAGE="${MESSAGE:-Needs your attention}"
|
|
21
33
|
|
|
34
|
+
# Build audio XML for Windows toast
|
|
35
|
+
AUDIO_XML=""
|
|
36
|
+
if [ "$SILENT" = true ]; then
|
|
37
|
+
AUDIO_XML='<audio silent="true"/>'
|
|
38
|
+
fi
|
|
39
|
+
|
|
22
40
|
# Detect OS and send notification
|
|
23
41
|
case "$(uname -s)" in
|
|
24
42
|
Linux*)
|
|
@@ -28,18 +46,26 @@ case "$(uname -s)" in
|
|
|
28
46
|
[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] > \$null
|
|
29
47
|
[Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] > \$null
|
|
30
48
|
\$xml = New-Object Windows.Data.Xml.Dom.XmlDocument
|
|
31
|
-
\$xml.LoadXml('<toast><visual><binding template=\"ToastText01\"><text id=\"1\">$MESSAGE</text></binding></visual
|
|
49
|
+
\$xml.LoadXml('<toast><visual><binding template=\"ToastText01\"><text id=\"1\">$MESSAGE</text></binding></visual>$AUDIO_XML</toast>')
|
|
32
50
|
\$toast = New-Object Windows.UI.Notifications.ToastNotification \$xml
|
|
33
|
-
[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('
|
|
51
|
+
[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('$NAME').Show(\$toast)
|
|
34
52
|
" 2>/dev/null
|
|
35
53
|
else
|
|
36
54
|
# Native Linux
|
|
37
|
-
|
|
55
|
+
if [ "$SILENT" = true ]; then
|
|
56
|
+
notify-send "$NAME" "$MESSAGE" --app-name="$NAME" -h string:suppress-sound:true 2>/dev/null || true
|
|
57
|
+
else
|
|
58
|
+
notify-send "$NAME" "$MESSAGE" --app-name="$NAME" 2>/dev/null || true
|
|
59
|
+
fi
|
|
38
60
|
fi
|
|
39
61
|
;;
|
|
40
62
|
Darwin*)
|
|
41
63
|
# macOS
|
|
42
|
-
|
|
64
|
+
if [ "$SILENT" = true ]; then
|
|
65
|
+
osascript -e "display notification \"$MESSAGE\" with title \"$NAME\"" 2>/dev/null || true
|
|
66
|
+
else
|
|
67
|
+
osascript -e "display notification \"$MESSAGE\" with title \"$NAME\" sound name \"default\"" 2>/dev/null || true
|
|
68
|
+
fi
|
|
43
69
|
;;
|
|
44
70
|
CYGWIN*|MINGW*|MSYS*)
|
|
45
71
|
# Native Windows (Git Bash / MSYS2)
|
|
@@ -47,9 +73,9 @@ case "$(uname -s)" in
|
|
|
47
73
|
[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] > \$null
|
|
48
74
|
[Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] > \$null
|
|
49
75
|
\$xml = New-Object Windows.Data.Xml.Dom.XmlDocument
|
|
50
|
-
\$xml.LoadXml('<toast><visual><binding template=\"ToastText01\"><text id=\"1\">$MESSAGE</text></binding></visual
|
|
76
|
+
\$xml.LoadXml('<toast><visual><binding template=\"ToastText01\"><text id=\"1\">$MESSAGE</text></binding></visual>$AUDIO_XML</toast>')
|
|
51
77
|
\$toast = New-Object Windows.UI.Notifications.ToastNotification \$xml
|
|
52
|
-
[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('
|
|
78
|
+
[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('$NAME').Show(\$toast)
|
|
53
79
|
" 2>/dev/null
|
|
54
80
|
;;
|
|
55
81
|
esac
|
package/package.json
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@landienzla/claude-code-notify",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "Cross-platform desktop notifications for Claude Code — plugin and CLI setup tool",
|
|
5
5
|
"author": "Talha <landienzla@gmail.com>",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "node --test test/cli.test.js test/notify.test.js"
|
|
8
|
+
},
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": ">=18"
|
|
11
|
+
},
|
|
6
12
|
"license": "MIT",
|
|
7
13
|
"repository": {
|
|
8
14
|
"type": "git",
|