@delorenj/claude-notifications 1.0.0
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 +208 -0
- package/bin/claude-notifications.js +265 -0
- package/bin/claude-notify.js +81 -0
- package/claude-notification.png +0 -0
- package/examples/claude-code-settings.json +15 -0
- package/image.png +0 -0
- package/index.js +55 -0
- package/install.sh +161 -0
- package/package.json +47 -0
- package/postinstall.js +35 -0
- package/preuninstall.js +15 -0
- package/test.sh +20 -0
- package/uninstall.sh +39 -0
package/README.md
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# Claude Notifications ๐
|
|
2
|
+
|
|
3
|
+
Delightful audible notifications for Claude Code. Never alt+tab back to disappointment, thinking it was cranking away for 30-minutes on your task only to see a "Just double checking - you cool with this plan? Y/n"
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
## โก Super Quick Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g @delorenj/claude-notifications
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
That's it! ๐ The package will automatically:
|
|
14
|
+
|
|
15
|
+
- โ
Install the notification system
|
|
16
|
+
- ๐ผ Generate a delightful notification scale
|
|
17
|
+
- ๐ช Configure Claude Code stop hooks
|
|
18
|
+
- ๐งช Test the installation
|
|
19
|
+
|
|
20
|
+
## Features
|
|
21
|
+
|
|
22
|
+
- ๐ต **Final Fantasy Dream Harp** - Classic C-D-E-G ascending/(optional)descending pattern
|
|
23
|
+
- ๐ **Cross-Platform Audio** - Works on Linux and macOS
|
|
24
|
+
- ๐ฅ๏ธ **Desktop Notifications** - Visual notifications with Claude Code branding
|
|
25
|
+
- ๐ช **Auto-Integration** - Automatically configures Claude Code hooks
|
|
26
|
+
- โก **Zero Configuration** - Works out of the box
|
|
27
|
+
- ๐จ **Customizable** - Easy to modify sounds and settings
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
After installation, Claude Code will begin notifying you when it finishes or is waiting on your response.
|
|
32
|
+
|
|
33
|
+
### Manual Commands
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# Trigger notification manually
|
|
37
|
+
claude-notify
|
|
38
|
+
|
|
39
|
+
# Test the system
|
|
40
|
+
claude-notifications test
|
|
41
|
+
|
|
42
|
+
# Reinstall/repair
|
|
43
|
+
claude-notifications install
|
|
44
|
+
|
|
45
|
+
# Get help
|
|
46
|
+
claude-notifications help
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## The Sound
|
|
50
|
+
|
|
51
|
+
- **Pattern**: C1-D1-E1-G1-C2-D2-E2-G2-C3-G2-E2-D2-C2-G1-E1-D1-C1
|
|
52
|
+
- **Duration**: ~2 seconds of dreamy notes
|
|
53
|
+
|
|
54
|
+
## How It Works
|
|
55
|
+
|
|
56
|
+
The package automatically:
|
|
57
|
+
|
|
58
|
+
1. **Detects Claude Code** - Finds your Claude Code configuration
|
|
59
|
+
2. **Adds Stop Hook** - Configures the notification trigger
|
|
60
|
+
3. **Creates Sound** - Generates the sound using `sox`
|
|
61
|
+
4. **Sets Up Commands** - Installs `claude-notify` globally
|
|
62
|
+
|
|
63
|
+
### Claude Code Integration
|
|
64
|
+
|
|
65
|
+
The installer automatically adds this to your Claude Code settings:
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"hooks": {
|
|
70
|
+
"Stop": [
|
|
71
|
+
{
|
|
72
|
+
"matcher": "",
|
|
73
|
+
"hooks": [
|
|
74
|
+
{
|
|
75
|
+
"type": "command",
|
|
76
|
+
"command": "claude-notify"
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
]
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Requirements
|
|
86
|
+
|
|
87
|
+
- **Node.js** 14+ (you probably have this if you use Claude Code)
|
|
88
|
+
- **Linux or macOS** (Windows support coming soon)
|
|
89
|
+
- **Audio system** (PulseAudio, ALSA, or CoreAudio)
|
|
90
|
+
|
|
91
|
+
### Auto-Installed Dependencies
|
|
92
|
+
|
|
93
|
+
The package will automatically install:
|
|
94
|
+
|
|
95
|
+
- `sox` for sound generation (Linux: apt/dnf, macOS: brew)
|
|
96
|
+
- `node-notifier` for desktop notifications
|
|
97
|
+
|
|
98
|
+
## Customization
|
|
99
|
+
|
|
100
|
+
### Change the Sound
|
|
101
|
+
|
|
102
|
+
Replace the generated sound file:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
# Find the sound file
|
|
106
|
+
ls ~/.local/share/sounds/claude-notification.wav
|
|
107
|
+
|
|
108
|
+
# Replace with your own
|
|
109
|
+
cp your-custom-sound.wav ~/.local/share/sounds/claude-notification.wav
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Create Custom Patterns
|
|
113
|
+
|
|
114
|
+
Use `sox` to create new victory fanfares:
|
|
115
|
+
|
|
116
|
+
> Note: Previously, a tedious chore I wouldn't recommend to a sane person. But now that we live in a fictional Blade Runner, Cyberpunk-inspired alternate timeline, just ask your robot buddy to do it!
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
# Simple ascending scale
|
|
120
|
+
sox -n custom.wav synth 0.1 sine 261.63 : synth 0.1 sine 293.66 : synth 0.1 sine 329.63
|
|
121
|
+
|
|
122
|
+
# Your imagination is the limit!
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Troubleshooting
|
|
126
|
+
|
|
127
|
+
### No Sound?
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
# Test your audio system
|
|
131
|
+
paplay /usr/share/sounds/alsa/Front_Left.wav # Linux
|
|
132
|
+
afplay /System/Library/Sounds/Glass.aiff # macOS
|
|
133
|
+
|
|
134
|
+
# Reinstall
|
|
135
|
+
claude-notifications install
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### No Notification?
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
# Test manually
|
|
142
|
+
claude-notify
|
|
143
|
+
|
|
144
|
+
# Check Claude Code config
|
|
145
|
+
claude-notifications install # Will show config location
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Command Not Found?
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
# Reinstall globally
|
|
152
|
+
npm install -g @delorenj/claude-notifications
|
|
153
|
+
|
|
154
|
+
# Or use npx
|
|
155
|
+
npx @delorenj/claude-notifications test
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Uninstall
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
npm uninstall -g @delorenj/claude-notifications
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
The uninstaller will clean up sound files and notify you about manual config cleanup.
|
|
165
|
+
|
|
166
|
+
## Development
|
|
167
|
+
|
|
168
|
+
### Local Development
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
git clone https://github.com/delorenj/claude-notifications.git
|
|
172
|
+
cd claude-notifications
|
|
173
|
+
npm link
|
|
174
|
+
claude-notifications install
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Publishing
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
npm version patch
|
|
181
|
+
npm publish
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Contributing
|
|
185
|
+
|
|
186
|
+
PRs welcome! Especially for:
|
|
187
|
+
|
|
188
|
+
- Windows support
|
|
189
|
+
- More sound patterns
|
|
190
|
+
- Better Claude Code detection
|
|
191
|
+
- macOS improvements
|
|
192
|
+
|
|
193
|
+
## License
|
|
194
|
+
|
|
195
|
+
MIT License - Make it your own! ๐ต
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
**Ready to level up your Claude Code experience?**
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
npm install -g @delorenj/claude-notifications
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
_Made with โค๏ธ for fellow developers of the world over.
|
|
206
|
+
but mostly for me, who alt-tabs away into the night leaving techno-breadcrumb trails of unanswered and unfinished Claude Code tasks._
|
|
207
|
+
|
|
208
|
+
๐ฎโจ **Enjoy!** โจ๐ฎ
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { execSync, spawn } = require("child_process");
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const os = require("os");
|
|
7
|
+
|
|
8
|
+
const colors = {
|
|
9
|
+
red: "\x1b[31m",
|
|
10
|
+
green: "\x1b[32m",
|
|
11
|
+
yellow: "\x1b[33m",
|
|
12
|
+
blue: "\x1b[34m",
|
|
13
|
+
reset: "\x1b[0m",
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function log(color, message) {
|
|
17
|
+
console.log(`${colors[color]}${message}${colors.reset}`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function findClaudeCodeConfig() {
|
|
21
|
+
// Common Claude Code config locations
|
|
22
|
+
const possiblePaths = [
|
|
23
|
+
path.join(os.homedir(), ".claude", "config.json"),
|
|
24
|
+
path.join(os.homedir(), ".config", "claude", "config.json"),
|
|
25
|
+
path.join(os.homedir(), ".claude-code", "config.json"),
|
|
26
|
+
path.join(os.homedir(), ".config", "claude-code", "config.json"),
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
for (const configPath of possiblePaths) {
|
|
30
|
+
if (fs.existsSync(configPath)) {
|
|
31
|
+
return configPath;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function updateClaudeCodeConfig() {
|
|
38
|
+
const configPath = findClaudeCodeConfig();
|
|
39
|
+
|
|
40
|
+
if (!configPath) {
|
|
41
|
+
log("yellow", "โ ๏ธ Could not find Claude Code config file.");
|
|
42
|
+
log(
|
|
43
|
+
"blue",
|
|
44
|
+
"๐ก Please manually add the stop hook to your Claude Code settings:",
|
|
45
|
+
);
|
|
46
|
+
console.log(
|
|
47
|
+
JSON.stringify(
|
|
48
|
+
{
|
|
49
|
+
hooks: {
|
|
50
|
+
Stop: [
|
|
51
|
+
{
|
|
52
|
+
matcher: "",
|
|
53
|
+
hooks: [
|
|
54
|
+
{
|
|
55
|
+
type: "command",
|
|
56
|
+
command: "claude-notify",
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
null,
|
|
64
|
+
2,
|
|
65
|
+
),
|
|
66
|
+
);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
let config = {};
|
|
72
|
+
if (fs.existsSync(configPath)) {
|
|
73
|
+
const configContent = fs.readFileSync(configPath, "utf8");
|
|
74
|
+
config = JSON.parse(configContent);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Add or update hooks
|
|
78
|
+
if (!config.hooks) config.hooks = {};
|
|
79
|
+
if (!config.hooks.Stop) config.hooks.Stop = [];
|
|
80
|
+
|
|
81
|
+
// Check if our hook already exists
|
|
82
|
+
const existingHook = config.hooks.Stop.find(
|
|
83
|
+
(hook) =>
|
|
84
|
+
hook.hooks && hook.hooks.some((h) => h.command === "claude-notify"),
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
if (!existingHook) {
|
|
88
|
+
config.hooks.Stop.push({
|
|
89
|
+
matcher: "",
|
|
90
|
+
hooks: [
|
|
91
|
+
{
|
|
92
|
+
type: "command",
|
|
93
|
+
command: "claude-notify",
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
99
|
+
log("green", "โ
Added stop hook to Claude Code config!");
|
|
100
|
+
} else {
|
|
101
|
+
log("blue", "๐ Stop hook already exists in Claude Code config");
|
|
102
|
+
}
|
|
103
|
+
} catch (error) {
|
|
104
|
+
log("red", `โ Error updating Claude Code config: ${error.message}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function createSoundFile() {
|
|
109
|
+
const soundDir = path.join(os.homedir(), ".local", "share", "sounds");
|
|
110
|
+
const soundFile = path.join(soundDir, "claude-notification.wav");
|
|
111
|
+
|
|
112
|
+
// Create directory if it doesn't exist
|
|
113
|
+
if (!fs.existsSync(soundDir)) {
|
|
114
|
+
fs.mkdirSync(soundDir, { recursive: true });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Check if sox is available
|
|
118
|
+
try {
|
|
119
|
+
execSync("which sox", { stdio: "ignore" });
|
|
120
|
+
} catch (error) {
|
|
121
|
+
log("yellow", "โ ๏ธ sox not found. Installing...");
|
|
122
|
+
try {
|
|
123
|
+
if (process.platform === "linux") {
|
|
124
|
+
execSync("sudo apt update && sudo apt install -y sox", {
|
|
125
|
+
stdio: "inherit",
|
|
126
|
+
});
|
|
127
|
+
} else if (process.platform === "darwin") {
|
|
128
|
+
execSync("brew install sox", { stdio: "inherit" });
|
|
129
|
+
}
|
|
130
|
+
} catch (installError) {
|
|
131
|
+
log("red", "โ Could not install sox. Please install it manually.");
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Generate pleasant notification scale
|
|
137
|
+
log("blue", "๐ผ Generating a pleasant notification scale...");
|
|
138
|
+
|
|
139
|
+
const soxCommand =
|
|
140
|
+
`sox -n "${soundFile}" ` +
|
|
141
|
+
"synth 0.12 sine 523.25 fade 0.01 0.12 0.01 : " +
|
|
142
|
+
"synth 0.12 sine 587.33 fade 0.01 0.12 0.01 : " +
|
|
143
|
+
"synth 0.12 sine 659.25 fade 0.01 0.12 0.01 : " +
|
|
144
|
+
"synth 0.12 sine 783.99 fade 0.01 0.12 0.01 : " +
|
|
145
|
+
"synth 0.12 sine 1046.50 fade 0.01 0.12 0.01 : " +
|
|
146
|
+
"synth 0.12 sine 1174.66 fade 0.01 0.12 0.01 : " +
|
|
147
|
+
"synth 0.12 sine 1318.51 fade 0.01 0.12 0.01 : " +
|
|
148
|
+
"synth 0.12 sine 1567.98 fade 0.01 0.12 0.01 : " +
|
|
149
|
+
"synth 0.12 sine 2093.00 fade 0.01 0.12 0.01 : " +
|
|
150
|
+
"synth 0.12 sine 1567.98 fade 0.01 0.12 0.01 : " +
|
|
151
|
+
"synth 0.12 sine 1318.51 fade 0.01 0.12 0.01 : " +
|
|
152
|
+
"synth 0.12 sine 1174.66 fade 0.01 0.12 0.01 : " +
|
|
153
|
+
"synth 0.12 sine 1046.50 fade 0.01 0.12 0.01 : " +
|
|
154
|
+
"synth 0.12 sine 783.99 fade 0.01 0.12 0.01 : " +
|
|
155
|
+
"synth 0.12 sine 659.25 fade 0.01 0.12 0.01 : " +
|
|
156
|
+
"synth 0.12 sine 587.33 fade 0.01 0.12 0.01 : " +
|
|
157
|
+
"synth 0.12 sine 523.25 fade 0.01 0.12 0.01 " +
|
|
158
|
+
"vol 0.7";
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
execSync(soxCommand, { stdio: "ignore" });
|
|
162
|
+
log("green", "โ
Sound file created successfully!");
|
|
163
|
+
return true;
|
|
164
|
+
} catch (error) {
|
|
165
|
+
log("red", `โ Error creating sound file: ${error.message}`);
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function main() {
|
|
171
|
+
const command = process.argv[2];
|
|
172
|
+
|
|
173
|
+
switch (command) {
|
|
174
|
+
case "install":
|
|
175
|
+
case undefined:
|
|
176
|
+
log("blue", "๐ต Installing Claude Notifications...");
|
|
177
|
+
|
|
178
|
+
if (createSoundFile()) {
|
|
179
|
+
updateClaudeCodeConfig();
|
|
180
|
+
log("green", "๐ Installation complete!");
|
|
181
|
+
log("blue", "๐งช Testing notification...");
|
|
182
|
+
|
|
183
|
+
// Test the notification
|
|
184
|
+
const testProcess = spawn(
|
|
185
|
+
"node",
|
|
186
|
+
[path.join(__dirname, "claude-notify.js")],
|
|
187
|
+
{
|
|
188
|
+
stdio: "inherit",
|
|
189
|
+
},
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
testProcess.on("close", () => {
|
|
193
|
+
log(
|
|
194
|
+
"green",
|
|
195
|
+
"โ
Test complete! You should have heard a dreamy notification!",
|
|
196
|
+
);
|
|
197
|
+
console.log("");
|
|
198
|
+
log("blue", "Usage:");
|
|
199
|
+
console.log(
|
|
200
|
+
" claude-notify # Trigger notification manually",
|
|
201
|
+
);
|
|
202
|
+
console.log(" claude-notifications # This installer");
|
|
203
|
+
console.log("");
|
|
204
|
+
log(
|
|
205
|
+
"blue",
|
|
206
|
+
"Claude Code will now beckon you back with a pleasant notification when it finishes responses or is waiting for your input! ๐ฎ",
|
|
207
|
+
);
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
break;
|
|
211
|
+
|
|
212
|
+
case "test":
|
|
213
|
+
log("blue", "๐งช Testing notification...");
|
|
214
|
+
spawn("node", [path.join(__dirname, "claude-notify.js")], {
|
|
215
|
+
stdio: "inherit",
|
|
216
|
+
});
|
|
217
|
+
break;
|
|
218
|
+
|
|
219
|
+
case "uninstall":
|
|
220
|
+
log("blue", "๐๏ธ Uninstalling Claude Notifications...");
|
|
221
|
+
|
|
222
|
+
const soundFile = path.join(
|
|
223
|
+
os.homedir(),
|
|
224
|
+
".local",
|
|
225
|
+
"share",
|
|
226
|
+
"sounds",
|
|
227
|
+
"claude-notification.wav",
|
|
228
|
+
);
|
|
229
|
+
if (fs.existsSync(soundFile)) {
|
|
230
|
+
fs.unlinkSync(soundFile);
|
|
231
|
+
log("green", "โ
Removed sound file");
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
log(
|
|
235
|
+
"yellow",
|
|
236
|
+
"โ ๏ธ Please manually remove the stop hook from your Claude Code config",
|
|
237
|
+
);
|
|
238
|
+
log("green", "๐ Uninstallation complete!");
|
|
239
|
+
break;
|
|
240
|
+
|
|
241
|
+
case "help":
|
|
242
|
+
case "--help":
|
|
243
|
+
case "-h":
|
|
244
|
+
console.log("Notifications for Claude Code");
|
|
245
|
+
console.log("");
|
|
246
|
+
console.log("Usage:");
|
|
247
|
+
console.log(" claude-notifications [command]");
|
|
248
|
+
console.log("");
|
|
249
|
+
console.log("Commands:");
|
|
250
|
+
console.log(" install Install notifications (default)");
|
|
251
|
+
console.log(" test Test the notification");
|
|
252
|
+
console.log(" uninstall Remove notifications");
|
|
253
|
+
console.log(" help Show this help");
|
|
254
|
+
break;
|
|
255
|
+
|
|
256
|
+
default:
|
|
257
|
+
log("red", `โ Unknown command: ${command}`);
|
|
258
|
+
log("blue", 'Run "claude-notifications help" for usage information');
|
|
259
|
+
process.exit(1);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (require.main === module) {
|
|
264
|
+
main();
|
|
265
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { execSync, spawn } = require('child_process');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const os = require('os');
|
|
7
|
+
|
|
8
|
+
function playSound() {
|
|
9
|
+
const soundFile = path.join(os.homedir(), '.local', 'share', 'sounds', 'claude-notification.wav');
|
|
10
|
+
|
|
11
|
+
if (!fs.existsSync(soundFile)) {
|
|
12
|
+
// Fallback to system bell
|
|
13
|
+
process.stdout.write('\x07');
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
// Try different audio players based on platform
|
|
19
|
+
if (process.platform === 'linux') {
|
|
20
|
+
// Try paplay first (PulseAudio)
|
|
21
|
+
try {
|
|
22
|
+
execSync(`paplay "${soundFile}"`, { stdio: 'ignore' });
|
|
23
|
+
return;
|
|
24
|
+
} catch (e) {
|
|
25
|
+
// Try aplay (ALSA)
|
|
26
|
+
try {
|
|
27
|
+
execSync(`aplay "${soundFile}"`, { stdio: 'ignore' });
|
|
28
|
+
return;
|
|
29
|
+
} catch (e2) {
|
|
30
|
+
// Try play (sox)
|
|
31
|
+
try {
|
|
32
|
+
execSync(`play "${soundFile}"`, { stdio: 'ignore' });
|
|
33
|
+
return;
|
|
34
|
+
} catch (e3) {
|
|
35
|
+
// Fallback to system bell
|
|
36
|
+
process.stdout.write('\x07');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
} else if (process.platform === 'darwin') {
|
|
41
|
+
// macOS
|
|
42
|
+
try {
|
|
43
|
+
execSync(`afplay "${soundFile}"`, { stdio: 'ignore' });
|
|
44
|
+
return;
|
|
45
|
+
} catch (e) {
|
|
46
|
+
// Fallback to system bell
|
|
47
|
+
process.stdout.write('\x07');
|
|
48
|
+
}
|
|
49
|
+
} else {
|
|
50
|
+
// Other platforms - just system bell
|
|
51
|
+
process.stdout.write('\x07');
|
|
52
|
+
}
|
|
53
|
+
} catch (error) {
|
|
54
|
+
// Final fallback
|
|
55
|
+
process.stdout.write('\x07');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function showNotification() {
|
|
60
|
+
const notifier = require('node-notifier');
|
|
61
|
+
|
|
62
|
+
notifier.notify({
|
|
63
|
+
title: 'Claude Code',
|
|
64
|
+
message: 'Waiting for you...',
|
|
65
|
+
icon: path.join(os.homedir(), 'Pictures', 'claude.png'), // Will fallback gracefully if not found
|
|
66
|
+
sound: false, // We handle sound separately
|
|
67
|
+
urgency: 'critical'
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function main() {
|
|
72
|
+
// Play sound in background
|
|
73
|
+
playSound();
|
|
74
|
+
|
|
75
|
+
// Show desktop notification
|
|
76
|
+
showNotification();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (require.main === module) {
|
|
80
|
+
main();
|
|
81
|
+
}
|
|
Binary file
|
package/image.png
ADDED
|
Binary file
|
package/index.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Main entry point for @delorenj/claude-notifications
|
|
4
|
+
// This allows the package to be used programmatically as well
|
|
5
|
+
|
|
6
|
+
const { spawn } = require('child_process');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
function notify() {
|
|
10
|
+
return new Promise((resolve, reject) => {
|
|
11
|
+
const notifyProcess = spawn('node', [path.join(__dirname, 'bin', 'claude-notify.js')], {
|
|
12
|
+
stdio: 'inherit'
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
notifyProcess.on('close', (code) => {
|
|
16
|
+
if (code === 0) {
|
|
17
|
+
resolve();
|
|
18
|
+
} else {
|
|
19
|
+
reject(new Error(`Notification failed with code ${code}`));
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function install() {
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
const installProcess = spawn('node', [path.join(__dirname, 'bin', 'claude-notifications.js'), 'install'], {
|
|
28
|
+
stdio: 'inherit'
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
installProcess.on('close', (code) => {
|
|
32
|
+
if (code === 0) {
|
|
33
|
+
resolve();
|
|
34
|
+
} else {
|
|
35
|
+
reject(new Error(`Installation failed with code ${code}`));
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module.exports = {
|
|
42
|
+
notify,
|
|
43
|
+
install
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// If called directly, run the CLI
|
|
47
|
+
if (require.main === module) {
|
|
48
|
+
const command = process.argv[2] || 'install';
|
|
49
|
+
|
|
50
|
+
if (command === 'notify') {
|
|
51
|
+
notify().catch(console.error);
|
|
52
|
+
} else {
|
|
53
|
+
install().catch(console.error);
|
|
54
|
+
}
|
|
55
|
+
}
|
package/install.sh
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Claude Code Notifications Installer
|
|
4
|
+
# Installs a much needed notification system for Claude Code
|
|
5
|
+
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
echo "๐ต Installing Claude Code Notifications..."
|
|
9
|
+
|
|
10
|
+
# Colors for output
|
|
11
|
+
RED='\033[0;31m'
|
|
12
|
+
GREEN='\033[0;32m'
|
|
13
|
+
YELLOW='\033[1;33m'
|
|
14
|
+
BLUE='\033[0;34m'
|
|
15
|
+
NC='\033[0m' # No Color
|
|
16
|
+
|
|
17
|
+
# Check if we're on Linux
|
|
18
|
+
if [[ "$OSTYPE" != "linux-gnu"* ]]; then
|
|
19
|
+
echo -e "${RED}โ This installer is currently Linux-only. macOS support coming soon!${NC}"
|
|
20
|
+
exit 1
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
# Create necessary directories
|
|
24
|
+
echo -e "${BLUE}๐ Creating directories...${NC}"
|
|
25
|
+
mkdir -p ~/.local/bin
|
|
26
|
+
mkdir -p ~/.local/share/sounds
|
|
27
|
+
|
|
28
|
+
# Check for required tools and install if needed
|
|
29
|
+
echo -e "${BLUE}๐ง Checking dependencies...${NC}"
|
|
30
|
+
|
|
31
|
+
# Check for sox
|
|
32
|
+
if ! command -v sox &>/dev/null; then
|
|
33
|
+
echo -e "${YELLOW}โ ๏ธ sox not found. Installing...${NC}"
|
|
34
|
+
if command -v apt &>/dev/null; then
|
|
35
|
+
sudo apt update && sudo apt install -y sox
|
|
36
|
+
elif command -v dnf &>/dev/null; then
|
|
37
|
+
sudo dnf install -y sox
|
|
38
|
+
elif command -v pacman &>/dev/null; then
|
|
39
|
+
sudo pacman -S sox
|
|
40
|
+
else
|
|
41
|
+
echo -e "${RED}โ Could not install sox. Please install it manually.${NC}"
|
|
42
|
+
exit 1
|
|
43
|
+
fi
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
# Check for notify-send
|
|
47
|
+
if ! command -v notify-send &>/dev/null; then
|
|
48
|
+
echo -e "${YELLOW}โ ๏ธ notify-send not found. Installing...${NC}"
|
|
49
|
+
if command -v apt &>/dev/null; then
|
|
50
|
+
sudo apt install -y libnotify-bin
|
|
51
|
+
elif command -v dnf &>/dev/null; then
|
|
52
|
+
sudo dnf install -y libnotify
|
|
53
|
+
elif command -v pacman &>/dev/null; then
|
|
54
|
+
sudo pacman -S libnotify
|
|
55
|
+
fi
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
# Generate that sweet Final Fantasy scale
|
|
59
|
+
echo -e "${BLUE}๐ผ Generating dreamy notification sound...${NC}"
|
|
60
|
+
sox -n ~/.local/share/sounds/claude-notification.wav \
|
|
61
|
+
synth 0.12 sine 523.25 fade 0.01 0.12 0.01 : \
|
|
62
|
+
synth 0.12 sine 587.33 fade 0.01 0.12 0.01 : \
|
|
63
|
+
synth 0.12 sine 659.25 fade 0.01 0.12 0.01 : \
|
|
64
|
+
synth 0.12 sine 783.99 fade 0.01 0.12 0.01 : \
|
|
65
|
+
synth 0.12 sine 1046.50 fade 0.01 0.12 0.01 : \
|
|
66
|
+
synth 0.12 sine 1174.66 fade 0.01 0.12 0.01 : \
|
|
67
|
+
synth 0.12 sine 1318.51 fade 0.01 0.12 0.01 : \
|
|
68
|
+
synth 0.12 sine 1567.98 fade 0.01 0.12 0.01 : \
|
|
69
|
+
synth 0.12 sine 2093.00 fade 0.01 0.12 0.01 :
|
|
70
|
+
# Uncomment for the longer, more nostalgic version
|
|
71
|
+
# synth 0.12 sine 1567.98 fade 0.01 0.12 0.01 : \
|
|
72
|
+
# synth 0.12 sine 1318.51 fade 0.01 0.12 0.01 : \
|
|
73
|
+
# synth 0.12 sine 1174.66 fade 0.01 0.12 0.01 : \
|
|
74
|
+
# synth 0.12 sine 1046.50 fade 0.01 0.12 0.01 : \
|
|
75
|
+
# synth 0.12 sine 783.99 fade 0.01 0.12 0.01 : \
|
|
76
|
+
# synth 0.12 sine 659.25 fade 0.01 0.12 0.01 : \
|
|
77
|
+
# synth 0.12 sine 587.33 fade 0.01 0.12 0.01 : \
|
|
78
|
+
# synth 0.12 sine 523.25 fade 0.01 0.12 0.01 \
|
|
79
|
+
vol 0.7
|
|
80
|
+
|
|
81
|
+
# Create the claude-notify script
|
|
82
|
+
echo -e "${BLUE}๐ Creating claude-notify script...${NC}"
|
|
83
|
+
cat >~/.local/bin/claude-notify <<'EOF'
|
|
84
|
+
#!/usr/bin/env bash
|
|
85
|
+
|
|
86
|
+
# Claude Code notification script
|
|
87
|
+
# Sends a desktop notification for Claude Code along with a dreamy notification sound
|
|
88
|
+
|
|
89
|
+
# Play notification sound (try multiple methods for compatibility)
|
|
90
|
+
play_sound() {
|
|
91
|
+
local sound_file="$HOME/.local/share/sounds/claude-notification.wav"
|
|
92
|
+
|
|
93
|
+
# Try paplay first (PulseAudio - most common on Ubuntu)
|
|
94
|
+
if command -v paplay >/dev/null 2>&1 && [[ -f "$sound_file" ]]; then
|
|
95
|
+
paplay "$sound_file" 2>/dev/null &
|
|
96
|
+
# Fallback to aplay (ALSA)
|
|
97
|
+
elif command -v aplay >/dev/null 2>&1 && [[ -f "$sound_file" ]]; then
|
|
98
|
+
aplay "$sound_file" 2>/dev/null &
|
|
99
|
+
# Fallback to system sounds
|
|
100
|
+
elif command -v paplay >/dev/null 2>&1; then
|
|
101
|
+
paplay /usr/share/sounds/alsa/Front_Left.wav 2>/dev/null &
|
|
102
|
+
# Final fallback to system bell
|
|
103
|
+
else
|
|
104
|
+
printf '\a'
|
|
105
|
+
fi
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
# Play sound in background
|
|
109
|
+
play_sound
|
|
110
|
+
|
|
111
|
+
# Send desktop notification
|
|
112
|
+
notify-send \
|
|
113
|
+
--app-name="Claude Code" \
|
|
114
|
+
--icon /home/clouedoc/Pictures/claude.png \
|
|
115
|
+
--urgency critical \
|
|
116
|
+
"Claude Code" \
|
|
117
|
+
"Waiting for you..."
|
|
118
|
+
EOF
|
|
119
|
+
|
|
120
|
+
# Make the script executable
|
|
121
|
+
chmod +x ~/.local/bin/claude-notify
|
|
122
|
+
|
|
123
|
+
# Add ~/.local/bin to PATH if not already there
|
|
124
|
+
if [[ ":$PATH:" != *":$HOME/.local/bin:"* ]]; then
|
|
125
|
+
echo -e "${BLUE}๐ค๏ธ Adding ~/.local/bin to PATH...${NC}"
|
|
126
|
+
|
|
127
|
+
# Detect shell and add to appropriate config file
|
|
128
|
+
if [[ -n "$ZSH_VERSION" ]] || [[ "$SHELL" == *"zsh"* ]]; then
|
|
129
|
+
echo 'export PATH="$HOME/.local/bin:$PATH"' >>~/.zshrc
|
|
130
|
+
echo -e "${YELLOW}โ ๏ธ Added to ~/.zshrc - restart your terminal or run: source ~/.zshrc${NC}"
|
|
131
|
+
elif [[ -n "$BASH_VERSION" ]] || [[ "$SHELL" == *"bash"* ]]; then
|
|
132
|
+
echo 'export PATH="$HOME/.local/bin:$PATH"' >>~/.bashrc
|
|
133
|
+
echo -e "${YELLOW}โ ๏ธ Added to ~/.bashrc - restart your terminal or run: source ~/.bashrc${NC}"
|
|
134
|
+
else
|
|
135
|
+
echo -e "${YELLOW}โ ๏ธ Please add ~/.local/bin to your PATH manually${NC}"
|
|
136
|
+
fi
|
|
137
|
+
fi
|
|
138
|
+
|
|
139
|
+
# Test the installation
|
|
140
|
+
echo -e "${BLUE}๐งช Testing installation...${NC}"
|
|
141
|
+
if command -v claude-notify &>/dev/null; then
|
|
142
|
+
echo -e "${GREEN}โ
claude-notify command is available!${NC}"
|
|
143
|
+
echo -e "${BLUE}๐ Playing test notification...${NC}"
|
|
144
|
+
claude-notify
|
|
145
|
+
else
|
|
146
|
+
echo -e "${YELLOW}โ ๏ธ claude-notify not in PATH yet. Restart your terminal or source your shell config.${NC}"
|
|
147
|
+
fi
|
|
148
|
+
|
|
149
|
+
echo ""
|
|
150
|
+
echo -e "${GREEN}๐ Installation complete!${NC}"
|
|
151
|
+
echo ""
|
|
152
|
+
echo -e "${BLUE}Next steps:${NC}"
|
|
153
|
+
echo "1. Test the notification: ${YELLOW}claude-notify${NC}"
|
|
154
|
+
echo "2. Add to Claude Code settings (see README.md for JSON config)"
|
|
155
|
+
echo "3. Never alt-tab back to a unexpected Y/n again! ๐ต"
|
|
156
|
+
echo ""
|
|
157
|
+
echo -e "${BLUE}Configuration location:${NC}"
|
|
158
|
+
echo "โข Script: ~/.local/bin/claude-notify"
|
|
159
|
+
echo "โข Sound: ~/.local/share/sounds/claude-notification.wav"
|
|
160
|
+
echo ""
|
|
161
|
+
echo -e "${BLUE}Need help?${NC} Check the README.md for customization options!"
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@delorenj/claude-notifications",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Delightful Notification for Claude Code",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"claude-notifications": "./bin/claude-notifications.js",
|
|
8
|
+
"claude-notify": "./bin/claude-notify.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"postinstall": "node postinstall.js",
|
|
12
|
+
"preuninstall": "node preuninstall.js",
|
|
13
|
+
"test": "node test.js"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/delorenj/claude-notifications.git"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"claude-code",
|
|
21
|
+
"notifications",
|
|
22
|
+
"adhd",
|
|
23
|
+
"audio",
|
|
24
|
+
"desktop-notifications",
|
|
25
|
+
"linux",
|
|
26
|
+
"productivity",
|
|
27
|
+
"cli"
|
|
28
|
+
],
|
|
29
|
+
"author": "Jarad DeLorenzo",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/delorenj/claude-notifications/issues"
|
|
33
|
+
},
|
|
34
|
+
"homepage": "https://github.com/delorenj/claude-notifications#readme",
|
|
35
|
+
"os": [
|
|
36
|
+
"linux",
|
|
37
|
+
"darwin"
|
|
38
|
+
],
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=14.0.0"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"node-notifier": "^10.0.1",
|
|
44
|
+
"which": "^4.0.0"
|
|
45
|
+
},
|
|
46
|
+
"preferGlobal": true
|
|
47
|
+
}
|
package/postinstall.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { spawn } = require("child_process");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
|
|
6
|
+
console.log("๐ต Setting up Claude Notifications...");
|
|
7
|
+
|
|
8
|
+
// Run the main installer
|
|
9
|
+
const installer = spawn(
|
|
10
|
+
"node",
|
|
11
|
+
[path.join(__dirname, "bin", "claude-notifications.js"), "install"],
|
|
12
|
+
{
|
|
13
|
+
stdio: "inherit",
|
|
14
|
+
},
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
installer.on("close", (code) => {
|
|
18
|
+
if (code === 0) {
|
|
19
|
+
console.log("");
|
|
20
|
+
console.log("๐ Claude Notifications installed successfully!");
|
|
21
|
+
console.log("");
|
|
22
|
+
console.log("Usage:");
|
|
23
|
+
console.log(
|
|
24
|
+
" claude-notify # Trigger notification manually",
|
|
25
|
+
);
|
|
26
|
+
console.log(" claude-notifications test # Test the notification");
|
|
27
|
+
console.log("");
|
|
28
|
+
console.log(
|
|
29
|
+
"Claude Code will now beckon you back with a soothing scale! ๐ฎโจ",
|
|
30
|
+
);
|
|
31
|
+
} else {
|
|
32
|
+
console.error("โ Installation failed");
|
|
33
|
+
process.exit(code);
|
|
34
|
+
}
|
|
35
|
+
});
|
package/preuninstall.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { spawn } = require('child_process');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
console.log('๐๏ธ Cleaning up Claude Notifications...');
|
|
7
|
+
|
|
8
|
+
// Run the uninstaller
|
|
9
|
+
const uninstaller = spawn('node', [path.join(__dirname, 'bin', 'claude-notifications.js'), 'uninstall'], {
|
|
10
|
+
stdio: 'inherit'
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
uninstaller.on('close', (code) => {
|
|
14
|
+
console.log('๐ Thanks for using Claude Notifications!');
|
|
15
|
+
});
|
package/test.sh
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Test script for Claude Code Notifications
|
|
4
|
+
|
|
5
|
+
echo "๐งช Testing Claude Code Notifications..."
|
|
6
|
+
|
|
7
|
+
# Check if claude-notify exists
|
|
8
|
+
if command -v claude-notify &> /dev/null; then
|
|
9
|
+
echo "โ
claude-notify command found"
|
|
10
|
+
|
|
11
|
+
# Test the notification
|
|
12
|
+
echo "๐ Playing test notification..."
|
|
13
|
+
claude-notify
|
|
14
|
+
|
|
15
|
+
echo "โ
Test complete! Did you hear the Final Fantasy fanfare?"
|
|
16
|
+
else
|
|
17
|
+
echo "โ claude-notify command not found"
|
|
18
|
+
echo "๐ก Try running ./install.sh first"
|
|
19
|
+
exit 1
|
|
20
|
+
fi
|
package/uninstall.sh
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Claude Code Notifications Uninstaller
|
|
4
|
+
|
|
5
|
+
set -e
|
|
6
|
+
|
|
7
|
+
echo "๐๏ธ Uninstalling Claude Code Notifications..."
|
|
8
|
+
|
|
9
|
+
# Colors for output
|
|
10
|
+
RED='\033[0;31m'
|
|
11
|
+
GREEN='\033[0;32m'
|
|
12
|
+
YELLOW='\033[1;33m'
|
|
13
|
+
BLUE='\033[0;34m'
|
|
14
|
+
NC='\033[0m' # No Color
|
|
15
|
+
|
|
16
|
+
# Remove the script
|
|
17
|
+
if [[ -f ~/.local/bin/claude-notify ]]; then
|
|
18
|
+
rm ~/.local/bin/claude-notify
|
|
19
|
+
echo -e "${GREEN}โ
Removed claude-notify script${NC}"
|
|
20
|
+
else
|
|
21
|
+
echo -e "${YELLOW}โ ๏ธ claude-notify script not found${NC}"
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
# Remove the sound file
|
|
25
|
+
if [[ -f ~/.local/share/sounds/claude-notification.wav ]]; then
|
|
26
|
+
rm ~/.local/share/sounds/claude-notification.wav
|
|
27
|
+
echo -e "${GREEN}โ
Removed notification sound${NC}"
|
|
28
|
+
else
|
|
29
|
+
echo -e "${YELLOW}โ ๏ธ Notification sound not found${NC}"
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
echo ""
|
|
33
|
+
echo -e "${GREEN}๐ Uninstallation complete!${NC}"
|
|
34
|
+
echo ""
|
|
35
|
+
echo -e "${BLUE}Note:${NC} You may want to:"
|
|
36
|
+
echo "โข Remove the Claude Code stop hook from your settings"
|
|
37
|
+
echo "โข Remove PATH modification from your shell config (if added)"
|
|
38
|
+
echo ""
|
|
39
|
+
echo -e "${BLUE}Thanks for using Claude Code Notifications! ๐ต${NC}"
|