oc-plugin-binetz-notifier 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Mohak S
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,459 @@
1
+ # opencode-notifier
2
+
3
+ OpenCode plugin that plays sounds and sends system notifications when permission is needed, generation completes, errors occur, or the question tool is invoked. Works on macOS, Linux, and Windows.
4
+
5
+ ## Quick Start
6
+
7
+ Add this to your `opencode.json`:
8
+
9
+ ```json
10
+ {
11
+ "plugin": ["@mohak34/opencode-notifier@latest"]
12
+ }
13
+ ```
14
+
15
+ Restart OpenCode. Done.
16
+
17
+ ## What it does
18
+
19
+ You'll get notified when:
20
+ - OpenCode needs permission to run something
21
+ - Your session finishes
22
+ - An error happens
23
+ - The question tool pops up
24
+
25
+ There's also `subagent_complete` for when subagents finish, and `user_cancelled` for when you press ESC to abort -- both are silent by default so you don't get spammed.
26
+
27
+ ## Setup by platform
28
+
29
+ **macOS**: Nothing to do, works out of the box. Shows the Script Editor icon.
30
+
31
+ **Linux**: Should work if you already have a notification system setup. If not install libnotify:
32
+
33
+ ```bash
34
+ sudo apt install libnotify-bin # Ubuntu/Debian
35
+ sudo dnf install libnotify # Fedora
36
+ sudo pacman -S libnotify # Arch
37
+ ```
38
+
39
+ For sounds, you need one of: `paplay`, `aplay`, `mpv`, or `ffplay`
40
+
41
+ **Windows**: Works out of the box. But heads up:
42
+ - Only `.wav` files work (not mp3)
43
+ - Use full paths like `C:/Users/You/sounds/alert.wav` not `~/`
44
+
45
+ ## Config file
46
+
47
+ Create `~/.config/opencode/opencode-notifier.json` with the defaults:
48
+
49
+ ```json
50
+ {
51
+ "sound": true,
52
+ "notification": true,
53
+ "timeout": 5,
54
+ "showProjectName": true,
55
+ "showSessionTitle": false,
56
+ "showIcon": true,
57
+ "suppressWhenFocused": true,
58
+ "enableOnDesktop": false,
59
+ "notificationSystem": "osascript",
60
+ "linux": {
61
+ "grouping": false
62
+ },
63
+ "command": {
64
+ "enabled": false,
65
+ "path": "/path/to/command",
66
+ "args": ["--event", "{event}", "--message", "{message}"],
67
+ "minDuration": 0
68
+ },
69
+ "events": {
70
+ "permission": { "sound": true, "notification": true, "command": true },
71
+ "complete": { "sound": true, "notification": true, "command": true },
72
+ "subagent_complete": { "sound": false, "notification": false, "command": true },
73
+ "error": { "sound": true, "notification": true, "command": true },
74
+ "question": { "sound": true, "notification": true, "command": true },
75
+ "user_cancelled": { "sound": false, "notification": false, "command": true }
76
+ },
77
+ "messages": {
78
+ "permission": "Session needs permission: {sessionTitle}",
79
+ "complete": "Session has finished: {sessionTitle}",
80
+ "subagent_complete": "Subagent task completed: {sessionTitle}",
81
+ "error": "Session encountered an error: {sessionTitle}",
82
+ "question": "Session has a question: {sessionTitle}",
83
+ "user_cancelled": "Session was cancelled by user: {sessionTitle}"
84
+ },
85
+ "sounds": {
86
+ "permission": null,
87
+ "complete": null,
88
+ "subagent_complete": null,
89
+ "error": null,
90
+ "question": null,
91
+ "user_cancelled": null
92
+ },
93
+ "volumes": {
94
+ "permission": 1,
95
+ "complete": 1,
96
+ "subagent_complete": 1,
97
+ "error": 1,
98
+ "question": 1,
99
+ "user_cancelled": 1
100
+ }
101
+ }
102
+ ```
103
+
104
+ ## All options
105
+
106
+ ### Global options
107
+
108
+ ```json
109
+ {
110
+ "sound": true,
111
+ "notification": true,
112
+ "timeout": 5,
113
+ "showProjectName": true,
114
+ "showSessionTitle": false,
115
+ "showIcon": true,
116
+ "suppressWhenFocused": true,
117
+ "enableOnDesktop": false,
118
+ "notificationSystem": "osascript"
119
+ }
120
+ ```
121
+
122
+ - `sound` - Turn sounds on/off (default: true)
123
+ - `notification` - Turn notifications on/off (default: true)
124
+ - `timeout` - How long notifications show in seconds, Linux only (default: 5)
125
+ - `showProjectName` - Show folder name in notification title (default: true)
126
+ - `showSessionTitle` - Include the session title in notification messages via `{sessionTitle}` placeholder (default: false)
127
+ - `showIcon` - Show OpenCode icon, Windows/Linux only (default: true)
128
+ - `suppressWhenFocused` - Skip notifications and sounds when the terminal is the active window (default: true). See [Focus detection](#focus-detection) for platform details
129
+ - `enableOnDesktop` - Run the plugin on Desktop and Web clients (default: false). When false, the plugin only runs on CLI. Set to true if you want notifications/sounds/commands on Desktop/Web — useful if you want custom commands (Telegram, webhooks) but don't care about built-in notifications
130
+ - `notificationSystem` - macOS only: `"osascript"`, `"node-notifier"`, or `"ghostty"` (default: "osascript"). Use `"ghostty"` if you're running Ghostty terminal for native OSC 9 notifications
131
+ - `linux.grouping` - Linux only: replace notifications in-place instead of stacking (default: false). Requires `notify-send` 0.8+
132
+
133
+ ### Events
134
+
135
+ Control each event separately:
136
+
137
+ ```json
138
+ {
139
+ "events": {
140
+ "permission": { "sound": true, "notification": true, "command": true },
141
+ "complete": { "sound": true, "notification": true, "command": true },
142
+ "subagent_complete": { "sound": false, "notification": false, "command": true },
143
+ "error": { "sound": true, "notification": true, "command": true },
144
+ "question": { "sound": true, "notification": true, "command": true },
145
+ "user_cancelled": { "sound": false, "notification": false, "command": true }
146
+ }
147
+ }
148
+ ```
149
+
150
+ `user_cancelled` fires when you press ESC to abort a session. It's silent by default so intentional cancellations don't trigger error alerts. Set `sound` or `notification` to `true` if you want confirmation when cancelling.
151
+
152
+ The `command` property controls whether the custom command (see [Custom commands](#custom-commands)) runs for that event. Defaults to `true` for all events. Set it to `false` to suppress the command for specific events without disabling it globally.
153
+
154
+ Or use true/false for both:
155
+
156
+ ```json
157
+ {
158
+ "events": {
159
+ "complete": false
160
+ }
161
+ }
162
+ ```
163
+
164
+ ### Messages
165
+
166
+ Customize the notification text:
167
+
168
+ ```json
169
+ {
170
+ "messages": {
171
+ "permission": "Session needs permission: {sessionTitle}",
172
+ "complete": "Session has finished: {sessionTitle}",
173
+ "subagent_complete": "Subagent task completed: {sessionTitle}",
174
+ "error": "Session encountered an error: {sessionTitle}",
175
+ "question": "Session has a question: {sessionTitle}",
176
+ "user_cancelled": "Session was cancelled by user: {sessionTitle}"
177
+ }
178
+ }
179
+ ```
180
+
181
+ Messages support placeholder tokens that get replaced with actual values:
182
+
183
+ - `{sessionTitle}` - The title/summary of the current session (e.g. "Fix login bug")
184
+ - `{agentName}` - Subagent name extracted from session titles with `(@name subagent)` suffix (e.g. `builder`, `codebase-researcher`), empty for non-subagent sessions
185
+ - `{projectName}` - The project folder name
186
+ - `{timestamp}` - Current time in `HH:MM:SS` format (e.g. "14:30:05")
187
+ - `{turn}` - Global notification counter that persists across restarts (e.g. 1, 2, 3). Stored in `~/.config/opencode/opencode-notifier-state.json`
188
+
189
+ When `showSessionTitle` is `false`, `{sessionTitle}` is replaced with an empty string. Any trailing separators (`: `, ` - `, ` | `) are automatically cleaned up when a placeholder resolves to empty.
190
+
191
+ To disable session titles in messages without changing `showSessionTitle`, just remove the `{sessionTitle}` placeholder from your custom messages.
192
+
193
+ The `{timestamp}` and `{turn}` placeholders also work in custom command args.
194
+
195
+ ### Sounds
196
+
197
+ Use your own sound files:
198
+
199
+ ```json
200
+ {
201
+ "sounds": {
202
+ "permission": "/path/to/alert.wav",
203
+ "complete": "/path/to/done.wav",
204
+ "subagent_complete": "/path/to/subagent-done.wav",
205
+ "error": "/path/to/error.wav",
206
+ "question": "/path/to/question.wav",
207
+ "user_cancelled": "/path/to/cancelled.wav"
208
+ }
209
+ }
210
+ ```
211
+
212
+ Platform notes:
213
+ - macOS/Linux: .wav or .mp3 files work
214
+ - Windows: Only .wav files work
215
+ - If file doesn't exist, falls back to bundled sound
216
+
217
+ ### Volumes
218
+
219
+ Set per-event volume from `0` to `1`:
220
+
221
+ ```json
222
+ {
223
+ "volumes": {
224
+ "permission": 0.6,
225
+ "complete": 0.3,
226
+ "subagent_complete": 0.15,
227
+ "error": 1,
228
+ "question": 0.7,
229
+ "user_cancelled": 0.5
230
+ }
231
+ }
232
+ ```
233
+
234
+ - `0` = mute, `1` = full volume
235
+ - Values outside `0..1` are clamped automatically
236
+ - On Windows, playback still works but custom volume may not be honored by the default player
237
+
238
+ ### Custom commands
239
+
240
+ Run your own script when something happens. Use `{event}`, `{message}`, `{sessionTitle}`, `{agentName}`, `{projectName}`, `{timestamp}`, and `{turn}` as placeholders:
241
+
242
+ ```json
243
+ {
244
+ "command": {
245
+ "enabled": true,
246
+ "path": "/path/to/your/script",
247
+ "args": ["{event}", "{message}"],
248
+ "minDuration": 10
249
+ }
250
+ }
251
+ ```
252
+
253
+ - `enabled` - Turn command on/off
254
+ - `path` - Path to your script/executable
255
+ - `args` - Arguments to pass, can use `{event}`, `{message}`, `{sessionTitle}`, `{agentName}`, `{projectName}`, `{timestamp}`, and `{turn}` tokens
256
+ - `minDuration` - Skip if response was quick, avoids spam (seconds)
257
+
258
+ #### Example: Log events to a file
259
+
260
+ ```json
261
+ {
262
+ "command": {
263
+ "enabled": true,
264
+ "path": "/bin/bash",
265
+ "args": [
266
+ "-c",
267
+ "echo '[{event}] {message}' >> /tmp/opencode.log"
268
+ ]
269
+ }
270
+ }
271
+ ```
272
+
273
+ ## macOS: Pick your notification style
274
+
275
+ **osascript** (default): Reliable but shows Script Editor icon
276
+
277
+ ```json
278
+ {
279
+ "notificationSystem": "osascript"
280
+ }
281
+ ```
282
+
283
+ **node-notifier**: Shows OpenCode icon but might miss notifications sometimes
284
+
285
+ ```json
286
+ {
287
+ "notificationSystem": "node-notifier"
288
+ }
289
+ ```
290
+
291
+ **NOTE:** If you go with node-notifier and start missing notifications, just switch back or remove the option from the config. Users have reported issues with using node-notifier for receiving only sounds and no notification popups.
292
+
293
+ ## Ghostty notifications
294
+
295
+ If you're using [Ghostty](https://ghostty.org/) terminal, you can use its native notification system via [OSC 9](https://ghostty.org/docs/vt/osc/9) escape sequences:
296
+
297
+ ```json
298
+ {
299
+ "notificationSystem": "ghostty"
300
+ }
301
+ ```
302
+
303
+ This sends notifications directly through the terminal instead of using system notification tools. Works on any platform where Ghostty is running.
304
+
305
+ If you're using Ghostty inside tmux, enable passthrough in your tmux config so OSC 9 notifications can pass through:
306
+
307
+ ```tmux
308
+ set -g allow-passthrough on
309
+ ```
310
+
311
+ Then reload tmux config:
312
+
313
+ ```bash
314
+ tmux source-file ~/.tmux.conf
315
+ ```
316
+
317
+ ## Focus detection
318
+
319
+ When `suppressWhenFocused` is `true` (the default), notifications and sounds are skipped if the terminal running OpenCode is the active/focused window. The idea is simple: if you're already looking at it, you don't need an alert.
320
+
321
+ To disable this and always get notified:
322
+
323
+ ```json
324
+ {
325
+ "suppressWhenFocused": false
326
+ }
327
+ ```
328
+
329
+ ### Platform support
330
+
331
+ | Platform | Method | Requirements | Status |
332
+ |----------|--------|--------------|--------|
333
+ | macOS | AppleScript (`System Events`) | None | Untested |
334
+ | Linux X11 | `xdotool` | `xdotool` installed | Untested |
335
+ | Linux Wayland (Hyprland) | `hyprctl activewindow` | None | Tested |
336
+ | Linux Wayland (Niri) | `niri msg --json focused-window` | None | Tested |
337
+ | Linux Wayland (Sway) | `swaymsg -t get_tree` | None | Untested |
338
+ | Linux Wayland (KDE) | `kdotool` | `kdotool` installed | Untested |
339
+ | Linux Wayland (GNOME) | Not supported | - | Falls back to always notifying |
340
+ | Linux Wayland (river, dwl, Cosmic, etc.) | Not supported | - | Falls back to always notifying |
341
+ | Windows | `GetForegroundWindow()` via PowerShell | None | Untested |
342
+
343
+ **Unsupported compositors**: Wayland has no standard protocol for querying the focused window. Each compositor has its own IPC, and GNOME intentionally doesn't expose focus information. Unsupported compositors fall back to always notifying.
344
+
345
+ **tmux/screen**: When running inside tmux, focus detection uses tmux pane state (`session_attached`, `window_active`, `pane_active`) via `tmux display-message`. This keeps suppression accurate when switching panes/windows/sessions. GNU Screen is not currently handled (falls back to always notifying).
346
+
347
+ **WezTerm panes**: When running in WezTerm with `WEZTERM_PANE` set, focus suppression is pane-aware via `wezterm cli list-clients --format json`. This means notifications are shown when you switch to a different WezTerm pane/tab.
348
+
349
+ **Fail-open design**: If detection fails for any reason (missing tools, unknown compositor, permissions), it falls back to always notifying. It never silently eats your notifications.
350
+
351
+ If you test on a platform marked "Untested" and it works (or doesn't), please open an issue and let us know.
352
+
353
+ ## Linux: Notification Grouping
354
+
355
+ By default, each notification appears as a separate entry. During active sessions this can create noise when multiple events fire quickly (e.g. permission + complete + question).
356
+
357
+ Enable grouping to replace notifications in-place instead of stacking:
358
+
359
+ ```json
360
+ {
361
+ "linux": {
362
+ "grouping": true
363
+ }
364
+ }
365
+ ```
366
+
367
+ With grouping enabled, each new notification replaces the previous one so you only see the latest event. This requires `notify-send` 0.8+ (standard on Ubuntu 22.04+, Debian 12+, Fedora 36+, Arch). On older systems it falls back to the default stacking behavior automatically.
368
+
369
+ Works with all major notification daemons (GNOME, dunst, mako, swaync, etc.) on both X11 and Wayland.
370
+
371
+ ## Updating
372
+
373
+ If Opencode does not update the plugin or there is an issue with the cache version:
374
+
375
+ ```bash
376
+ # Linux/macOS
377
+ rm -rf ~/.cache/opencode/node_modules/@mohak34/opencode-notifier
378
+
379
+ # Windows
380
+ Remove-Item -Recurse -Force "$env:USERPROFILE\.cache\opencode\node_modules\@mohak34\opencode-notifier"
381
+ ```
382
+
383
+ Then restart OpenCode.
384
+
385
+ ## Troubleshooting
386
+
387
+ **macOS: Not seeing notifications?**
388
+ Go to System Settings > Notifications > Script Editor, make sure it's set to Banners or Alerts.
389
+
390
+ **macOS: node-notifier not showing notifications?**
391
+ Switch back to osascript. Some users report node-notifier works for sounds but not visual notifications on certain macOS versions.
392
+
393
+ **Linux: No notifications?**
394
+ Install libnotify-bin:
395
+ ```bash
396
+ sudo apt install libnotify-bin # Debian/Ubuntu
397
+ sudo dnf install libnotify # Fedora
398
+ sudo pacman -S libnotify # Arch
399
+ ```
400
+
401
+ Test with: `notify-send "Test" "Hello"`
402
+
403
+ **Linux: No sounds?**
404
+ Install one of: `paplay`, `aplay`, `mpv`, or `ffplay`
405
+
406
+ **Windows: Custom sounds not working?**
407
+ - Must be .wav format (not .mp3)
408
+ - Use full Windows paths: `C:/Users/YourName/sounds/alert.wav` (not `~/`)
409
+ - Make sure the file actually plays in Windows Media Player
410
+ - If using WSL, the path should be accessible from Windows
411
+
412
+ **Windows WSL notifications not working?**
413
+ WSL doesn't have a native notification daemon. Use PowerShell commands instead:
414
+
415
+ ```json
416
+ {
417
+ "notification": false,
418
+ "sound": true,
419
+ "command": {
420
+ "enabled": true,
421
+ "path": "powershell.exe",
422
+ "args": [
423
+ "-Command",
424
+ "$wshell = New-Object -ComObject Wscript.Shell; $wshell.Popup('{message}', 5, 'OpenCode - {event}', 0+64)"
425
+ ]
426
+ }
427
+ }
428
+ ```
429
+
430
+ **Windows: OpenCode crashes when notifications appear?**
431
+ This is a known Bun issue on Windows. Disable native notifications and use PowerShell popups:
432
+
433
+ ```json
434
+ {
435
+ "notification": false,
436
+ "sound": true,
437
+ "command": {
438
+ "enabled": true,
439
+ "path": "powershell.exe",
440
+ "args": [
441
+ "-Command",
442
+ "$wshell = New-Object -ComObject Wscript.Shell; $wshell.Popup('{message}', 5, 'OpenCode - {event}', 0+64)"
443
+ ]
444
+ }
445
+ }
446
+ ```
447
+
448
+ **Plugin not loading?**
449
+ - Check your opencode.json syntax
450
+ - Clear the cache (see Updating section)
451
+ - Restart OpenCode
452
+
453
+ ## Changelog
454
+
455
+ See [CHANGELOG.md](CHANGELOG.md)
456
+
457
+ ## License
458
+
459
+ MIT