@nm156/opencode-notifier 0.3.1

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 ADDED
@@ -0,0 +1,679 @@
1
+ # opencode-notifier
2
+
3
+ > [!NOTE]
4
+ > This is a fork of [`@mohak34/opencode-notifier`](https://github.com/mohak34/opencode-notifier) that adds
5
+ > OpenCode 2 support from [mohak34/opencode-notifier#110](https://github.com/mohak34/opencode-notifier/pull/110),
6
+ > rebased onto 0.3.0. Use the upstream package once it supports OpenCode 2.
7
+
8
+ 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.
9
+
10
+ ## Quick Start
11
+
12
+ Install the plugin via the CLI: `opencode plug -g @nm156/opencode-notifier`.
13
+
14
+ Or add manually to your `opencode.json`:
15
+
16
+ ```json
17
+ {
18
+ "plugin": ["@nm156/opencode-notifier@latest"]
19
+ }
20
+ ```
21
+
22
+ Restart OpenCode. Done.
23
+
24
+ ## OpenCode version support
25
+
26
+ This plugin supports both OpenCode 1 and OpenCode 2 from the same package:
27
+
28
+ - **OpenCode 1** loads the legacy `server()` entrypoint.
29
+ - **OpenCode 2** loads the `setup()` entrypoint introduced with the V2 plugin API.
30
+
31
+ Install it the same way for both. On OpenCode 2, `opencode notifier` appears in `opencode plugin list` without any
32
+ extra configuration.
33
+
34
+ ## What it does
35
+
36
+ You'll get notified when:
37
+
38
+ - OpenCode needs permission to run something
39
+ - Your session finishes
40
+ - An error happens
41
+ - The question tool pops up
42
+
43
+ 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.
44
+
45
+ ## Setup by platform
46
+
47
+ **macOS**: Nothing to do, works out of the box. Shows the Script Editor icon.
48
+
49
+ **Linux**: Should work if you already have a notification system setup. If not install libnotify:
50
+
51
+ ```bash
52
+ sudo apt install libnotify-bin # Ubuntu/Debian
53
+ sudo dnf install libnotify # Fedora
54
+ sudo pacman -S libnotify # Arch
55
+ ```
56
+
57
+ For sounds, you need one of: `paplay`, `aplay`, `mpv`, or `ffplay`
58
+
59
+ **Windows**: Works out of the box. But heads up:
60
+
61
+ - Only `.wav` files work (not mp3)
62
+ - Use full paths like `C:/Users/You/sounds/alert.wav` not `~/`
63
+
64
+ **WSL**: It's recommeneded to set `customIconPath` pointing to a file on Windows filesystem
65
+ due to issues with path translation (can be copied from `logos` folder from this repository).
66
+ This path will be passed down to `snoretoast-*.exe`
67
+
68
+ In `opencode-notifier.json` config:
69
+ ```json
70
+ "showIcon": true,
71
+ "customIconPath": "C:\\Users\\jhon\\Documents\\opencode-logo-dark.png",
72
+ ```
73
+
74
+ - If notifications are not showing up, check out: [missing WSL notification](https://github.com/mikaelbr/node-notifier?tab=readme-ov-file#windows-and-wsl2)
75
+
76
+ ## Config file
77
+
78
+ Create `~/.config/opencode/opencode-notifier.json` with the defaults:
79
+
80
+ ```json
81
+ {
82
+ "sound": true,
83
+ "notification": true,
84
+ "bell": false,
85
+ "timeout": 5,
86
+ "showProjectName": true,
87
+ "showFullPath": false,
88
+ "showSessionTitle": false,
89
+ "showIcon": true,
90
+ "customIconPath": null,
91
+ "suppressWhenFocused": true,
92
+ "enableOnDesktop": false,
93
+ "notificationSystem": "osascript",
94
+ "suppressGhosttySound": false,
95
+ "linux": {
96
+ "grouping": false
97
+ },
98
+ "minDuration": 0,
99
+ "command": {
100
+ "enabled": false,
101
+ "path": "/path/to/command",
102
+ "args": ["--event", "{event}", "--message", "{message}"],
103
+ "minDuration": 0
104
+ },
105
+ "events": {
106
+ "permission": { "sound": true, "notification": true, "command": true, "bell": false },
107
+ "complete": { "sound": true, "notification": true, "command": true, "bell": false },
108
+ "subagent_complete": { "sound": false, "notification": false, "command": true, "bell": false },
109
+ "error": { "sound": true, "notification": true, "command": true, "bell": false },
110
+ "question": { "sound": true, "notification": true, "command": true, "bell": false },
111
+ "user_cancelled": { "sound": false, "notification": false, "command": true, "bell": false },
112
+ "plan_exit": { "sound": true, "notification": true, "command": true, "bell": false },
113
+ "session_started": { "sound": true, "notification": false, "command": true, "bell": false },
114
+ "user_message": { "sound": true, "notification": false, "command": true, "bell": false },
115
+ "client_connected": { "sound": true, "notification": false, "command": true, "bell": false }
116
+ },
117
+ "messages": {
118
+ "permission": "Session needs permission: {sessionTitle}",
119
+ "complete": "Session has finished: {sessionTitle}",
120
+ "subagent_complete": "Subagent task completed: {sessionTitle}",
121
+ "error": "Session encountered an error: {sessionTitle}",
122
+ "question": "Session has a question: {sessionTitle}",
123
+ "user_cancelled": "Session was cancelled by user: {sessionTitle}",
124
+ "plan_exit": "Plan ready for review: {sessionTitle}",
125
+ "session_started": "Session started: {sessionTitle}",
126
+ "user_message": "User sent a message: {sessionTitle}",
127
+ "client_connected": "OpenCode connected"
128
+ },
129
+ "sounds": {
130
+ "permission": null,
131
+ "complete": null,
132
+ "subagent_complete": null,
133
+ "error": null,
134
+ "question": null,
135
+ "user_cancelled": null,
136
+ "plan_exit": null,
137
+ "session_started": null,
138
+ "user_message": null,
139
+ "client_connected": null
140
+ },
141
+ "volumes": {
142
+ "permission": 1,
143
+ "complete": 1,
144
+ "subagent_complete": 1,
145
+ "error": 1,
146
+ "question": 1,
147
+ "user_cancelled": 1,
148
+ "plan_exit": 1,
149
+ "session_started": 1,
150
+ "user_message": 1,
151
+ "client_connected": 1
152
+ }
153
+ }
154
+ ```
155
+
156
+ ## All options
157
+
158
+ ### Global options
159
+
160
+ ```json
161
+ {
162
+ "sound": true,
163
+ "notification": true,
164
+ "bell": false,
165
+ "timeout": 5,
166
+ "showProjectName": true,
167
+ "showFullPath": false,
168
+ "showSessionTitle": false,
169
+ "showIcon": true,
170
+ "suppressWhenFocused": true,
171
+ "enableOnDesktop": false,
172
+ "notificationSystem": "osascript",
173
+ "suppressGhosttySound": false
174
+ }
175
+ ```
176
+
177
+ - `sound` - Turn sounds on/off (default: true)
178
+ - `notification` - Turn notifications on/off (default: true)
179
+ - `bell` - Emit terminal BEL (`\x07`) on events (default: false). Behavior depends on your terminal/WM settings
180
+ - `timeout` - How long notifications show in seconds, Linux only (default: 5)
181
+ - `showProjectName` - Show folder name in notification title (default: true)
182
+ - `showFullPath` - Show full absolute path instead of folder name in notification title and `{projectName}` token (default: false). When true, shows `OpenCode (/home/user/projects/myapp)` instead of `OpenCode (myapp)`
183
+ - `showSessionTitle` - Include the session title in notification messages via `{sessionTitle}` placeholder (default: false)
184
+ - `showIcon` - Show OpenCode icon, Windows/Linux only (default: true)
185
+ - `customIconPath` - Path to a custom icon for notifications. Useful on WSL where Windows paths are needed (default: null)
186
+ - `suppressWhenFocused` - Skip notifications and sounds when the terminal is the active window (default: true). See [Focus detection](#focus-detection) for platform details
187
+ - `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
188
+ - `notificationSystem` - macOS only: `"osascript"`, `"node-notifier"`, or `"ghostty"` (default: "osascript"). Use `"ghostty"` if you're running Ghostty terminal for native OSC 9 notifications
189
+ - `suppressGhosttySound` - macOS only: when `true` with `notificationSystem: "ghostty"`, skips the plugin's sound to avoid duplicating macOS Notification Center's default sound (default: false)
190
+ - `minDuration` - Suppress `complete` and `subagent_complete` notifications when session finishes faster than this many seconds (default: 0). See [Minimum duration threshold](#minimum-duration-threshold)
191
+ - `linux.grouping` - Linux only: replace notifications in-place instead of stacking (default: false). Requires `notify-send` 0.8+
192
+
193
+ ### Events
194
+
195
+ Control each event separately:
196
+
197
+ ```json
198
+ {
199
+ "events": {
200
+ "permission": { "sound": true, "notification": true, "command": true, "bell": false },
201
+ "complete": { "sound": true, "notification": true, "command": true, "bell": false },
202
+ "subagent_complete": { "sound": false, "notification": false, "command": true, "bell": false },
203
+ "error": { "sound": true, "notification": true, "command": true, "bell": false },
204
+ "question": { "sound": true, "notification": true, "command": true, "bell": false },
205
+ "user_cancelled": { "sound": false, "notification": false, "command": true, "bell": false },
206
+ "plan_exit": { "sound": true, "notification": true, "command": true, "bell": false },
207
+ "session_started": { "sound": true, "notification": false, "command": true, "bell": false },
208
+ "user_message": { "sound": true, "notification": false, "command": true, "bell": false },
209
+ "client_connected": { "sound": true, "notification": false, "command": true, "bell": false }
210
+ }
211
+ }
212
+ ```
213
+
214
+ `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.
215
+
216
+ `session_started` fires when a new top-level session is created. `user_message` fires when a user message is submitted in a top-level session. `client_connected` fires shortly after the plugin initializes and is best-effort (there is no dedicated SDK connection event from plugin context).
217
+
218
+ 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.
219
+
220
+ `bell` is terminal-driven and may be audible, visual, both, or ignored depending on your terminal setup. Quick check: `printf '\a'`.
221
+
222
+ Or use true/false for both:
223
+
224
+ ```json
225
+ {
226
+ "events": {
227
+ "complete": false
228
+ }
229
+ }
230
+ ```
231
+
232
+ ### Messages
233
+
234
+ Customize the notification text:
235
+
236
+ ```json
237
+ {
238
+ "messages": {
239
+ "permission": "Session needs permission: {sessionTitle}",
240
+ "complete": "Session has finished: {sessionTitle}",
241
+ "subagent_complete": "Subagent task completed: {sessionTitle}",
242
+ "error": "Session encountered an error: {sessionTitle}",
243
+ "question": "Session has a question: {sessionTitle}",
244
+ "user_cancelled": "Session was cancelled by user: {sessionTitle}",
245
+ "plan_exit": "Plan ready for review: {sessionTitle}",
246
+ "session_started": "Session started: {sessionTitle}",
247
+ "user_message": "User sent a message: {sessionTitle}",
248
+ "client_connected": "OpenCode connected"
249
+ }
250
+ }
251
+ ```
252
+
253
+ Messages support placeholder tokens that get replaced with actual values:
254
+
255
+ - `{sessionTitle}` - The title/summary of the current session (e.g. "Fix login bug")
256
+ - `{agentName}` - Subagent name extracted from session titles with `(@name subagent)` suffix (e.g. `builder`, `codebase-researcher`), empty for non-subagent sessions
257
+ - `{projectName}` - The project folder name
258
+ - `{timestamp}` - Current time in `HH:MM:SS` format (e.g. "14:30:05")
259
+ - `{turn}` - Global notification counter that persists across restarts (e.g. 1, 2, 3). Stored in `~/.config/opencode/opencode-notifier-state.json`
260
+
261
+ When `showSessionTitle` is `false`, `{sessionTitle}` is replaced with an empty string. Any trailing separators (`: `, `-`, `|`) are automatically cleaned up when a placeholder resolves to empty.
262
+
263
+ To disable session titles in messages without changing `showSessionTitle`, just remove the `{sessionTitle}` placeholder from your custom messages.
264
+
265
+ The `{timestamp}` and `{turn}` placeholders also work in custom command args.
266
+
267
+ ### Sounds
268
+
269
+ Use your own sound files:
270
+
271
+ ```json
272
+ {
273
+ "sounds": {
274
+ "permission": "/path/to/alert.wav",
275
+ "complete": "/path/to/done.wav",
276
+ "subagent_complete": "/path/to/subagent-done.wav",
277
+ "error": "/path/to/error.wav",
278
+ "question": "/path/to/question.wav",
279
+ "user_cancelled": "/path/to/cancelled.wav",
280
+ "plan_exit": "/path/to/plan-ready.wav",
281
+ "session_started": "/path/to/session-started.wav",
282
+ "user_message": "/path/to/user-message.wav",
283
+ "client_connected": "/path/to/client-connected.wav"
284
+ }
285
+ }
286
+ ```
287
+
288
+ Platform notes:
289
+
290
+ - macOS/Linux: .wav or .mp3 files work
291
+ - Windows: Only .wav files work
292
+ - If file doesn't exist, falls back to bundled sound
293
+
294
+ ### Volumes
295
+
296
+ Set per-event volume from `0` to `1`:
297
+
298
+ ```json
299
+ {
300
+ "volumes": {
301
+ "permission": 0.6,
302
+ "complete": 0.3,
303
+ "subagent_complete": 0.15,
304
+ "error": 1,
305
+ "question": 0.7,
306
+ "user_cancelled": 0.5,
307
+ "plan_exit": 0.6,
308
+ "session_started": 0.35,
309
+ "user_message": 0.2,
310
+ "client_connected": 0.45
311
+ }
312
+ }
313
+ ```
314
+
315
+ - `0` = mute, `1` = full volume
316
+ - Values outside `0..1` are clamped automatically
317
+ - On Windows, playback still works but custom volume may not be honored by the default player
318
+
319
+ ### Custom commands
320
+
321
+ Run your own script when something happens. Use `{event}`, `{message}`, `{sessionTitle}`, `{agentName}`, `{projectName}`, `{timestamp}`, and `{turn}` as placeholders:
322
+
323
+ ```json
324
+ {
325
+ "command": {
326
+ "enabled": true,
327
+ "path": "/path/to/your/script",
328
+ "args": ["{event}", "{message}"],
329
+ "minDuration": 10
330
+ }
331
+ }
332
+ ```
333
+
334
+ - `enabled` - Turn command on/off
335
+ - `path` - Path to your script/executable
336
+ - `args` - Arguments to pass, can use `{event}`, `{message}`, `{sessionTitle}`, `{agentName}`, `{projectName}`, `{timestamp}`, and `{turn}` tokens
337
+ - `minDuration` - Skip if response was quick, avoids spam (seconds)
338
+
339
+ Token values are passed as argv values and are not shell-escaped for use inside
340
+ script source. Do not put `{message}`, `{sessionTitle}`, or other dynamic tokens
341
+ inside a `sh -c`, `bash -c`, `powershell -Command`, or similar script string.
342
+ Use a wrapper script and pass the tokens as separate arguments instead.
343
+ Custom commands run with the same user permissions as OpenCode, so only enable
344
+ scripts you trust.
345
+
346
+ #### Example: Log events to a file
347
+
348
+ ```json
349
+ {
350
+ "command": {
351
+ "enabled": true,
352
+ "path": "/bin/bash",
353
+ "args": [
354
+ "-c",
355
+ "printf '[%s] %s\\n' \"$1\" \"$2\" >> /tmp/opencode.log",
356
+ "opencode-notifier",
357
+ "{event}",
358
+ "{message}"
359
+ ]
360
+ }
361
+ }
362
+ ```
363
+
364
+ ## macOS: Pick your notification style
365
+
366
+ **osascript** (default): Reliable but shows Script Editor icon
367
+
368
+ ```json
369
+ {
370
+ "notificationSystem": "osascript"
371
+ }
372
+ ```
373
+
374
+ **node-notifier**: Shows OpenCode icon but might miss notifications sometimes
375
+
376
+ ```json
377
+ {
378
+ "notificationSystem": "node-notifier"
379
+ }
380
+ ```
381
+
382
+ **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.
383
+
384
+ ## Ghostty notifications
385
+
386
+ 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:
387
+
388
+ ```json
389
+ {
390
+ "notificationSystem": "ghostty"
391
+ }
392
+ ```
393
+
394
+ This sends notifications directly through the terminal instead of using system notification tools. Works on any platform where Ghostty is running.
395
+
396
+ **macOS:** Ghostty delivers notifications through macOS Notification Center, which plays its own default sound. This can result in duplicate audio with the plugin's sound effects. Set `suppressGhosttySound` to `true` to skip the plugin's sound:
397
+
398
+ ```json
399
+ {
400
+ "notificationSystem": "ghostty",
401
+ "suppressGhosttySound": true
402
+ }
403
+ ```
404
+
405
+ Note: custom sounds configured via the `sounds` section still play — only default (bundled) sounds are suppressed.
406
+
407
+ If you're using Ghostty inside tmux, enable passthrough in your tmux config so OSC 9 notifications can pass through:
408
+
409
+ ```tmux
410
+ set -g allow-passthrough on
411
+ ```
412
+
413
+ Then reload tmux config:
414
+
415
+ ```bash
416
+ tmux source-file ~/.tmux.conf
417
+ ```
418
+
419
+ ## Focus detection
420
+
421
+ 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.
422
+
423
+ To disable this and always get notified:
424
+
425
+ ```json
426
+ {
427
+ "suppressWhenFocused": false
428
+ }
429
+ ```
430
+
431
+ ## Minimum duration threshold
432
+
433
+ You can suppress `complete` and `subagent_complete` notifications for short-lived sessions. Set `minDuration` to the number of seconds a session must exceed to trigger a done notification:
434
+
435
+ ```json
436
+ {
437
+ "minDuration": 10
438
+ }
439
+ ```
440
+
441
+ With the above, if OpenCode finishes in under 10 seconds, no notification, sound, bell, or command is fired. Default is `0` (no threshold).
442
+
443
+ This is independent of `command.minDuration`, which only controls whether the custom command runs.
444
+
445
+ ### Platform support
446
+
447
+ | Platform | Method | Requirements | Status |
448
+ | ---------------------------------------- | ---------------------------------------- | --------------------- | ------------------------------ |
449
+ | macOS | AppleScript (`System Events`) | None | Untested |
450
+ | Linux X11 | `xdotool` | `xdotool` installed | Untested |
451
+ | Linux Wayland (Hyprland) | `hyprctl activewindow` | None | Tested |
452
+ | Linux Wayland (Niri) | `niri msg --json focused-window` | None | Tested |
453
+ | Linux Wayland (Sway) | `swaymsg -t get_tree` | None | Untested |
454
+ | Linux Wayland (KDE) | `kdotool` | `kdotool` installed | Tested |
455
+ | Linux Wayland (GNOME) | AT-SPI (`gdbus` on the `org.a11y.Bus`) | `gdbus` installed | Tested (Ubuntu 26.04.1 LTS + GNOME Shell 50.1 + Ghostty 1.3.0) |
456
+ | Linux Wayland (river, dwl, Cosmic, etc.) | Not supported | - | Falls back to always notifying |
457
+ | Windows | `GetForegroundWindow()` via PowerShell | None | Untested |
458
+
459
+ **GNOME Wayland**: GNOME exposes no compositor API for the focused window (`Introspect.GetWindows` and `Eval` are access-denied) and XWayland tools like `xdotool` cannot see native Wayland windows, so focus is read from the accessibility bus instead: the active terminal window is the one whose AT-SPI `ACTIVE` state bit is set. Ghostty is matched by its `/com/mitchellh/ghostty` AT-SPI path, other terminals by app name (including the `gnome-terminal-server` AT-SPI alias). Window identity is `bus@path` since AT-SPI paths repeat across processes. Implemented and verified on Ubuntu 26.04.1 LTS + GNOME Shell 50.1 + Ghostty 1.3.0. With several terminal windows open, suppression compares against the window that was active at startup. Set `OPENCODE_NOTIFIER_DEBUG=1` to log the focus backend decision.
460
+
461
+ **Unsupported compositors**: Wayland has no standard protocol for querying the focused window. Each compositor has its own IPC. Compositors without a backend (river, dwl, Cosmic, etc.) fall back to always notifying.
462
+
463
+ **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. On Linux setups where window focus cannot be detected at all, tmux pane state is also used as a best-effort fallback. GNU Screen is not currently handled (falls back to always notifying).
464
+
465
+ **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.
466
+
467
+ **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.
468
+
469
+ If you test on a platform marked "Untested" and it works (or doesn't), please open an issue and let us know.
470
+
471
+ ## Linux: Notification Grouping
472
+
473
+ 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).
474
+
475
+ Enable grouping to replace notifications in-place instead of stacking:
476
+
477
+ ```json
478
+ {
479
+ "linux": {
480
+ "grouping": true
481
+ }
482
+ }
483
+ ```
484
+
485
+ 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.
486
+
487
+ Works with all major notification daemons (GNOME, dunst, mako, swaync, etc.) on both X11 and Wayland.
488
+
489
+ ## KDE Plasma: Jump back to terminal from notification
490
+
491
+ On KDE Plasma/Wayland, clicking the popup body is not consistently delivered as a notification activation event.This plugin uses an explicit notification action button instead:
492
+
493
+ - **Jump to terminal** (action button on the popup card, or in notification history)
494
+
495
+ When clicked, the plugin runs its terminal-focus path. On KDE with `kdotool` installed, it auto-captures the startup terminal window ID and jumps back to that pinned window.
496
+ The action button is only enabled on Linux KDE sessions where `kdotool` is available.
497
+
498
+ ## Updating
499
+
500
+ OpenCode caches plugin packages under `~/.cache/opencode`. If you switch between `latest`, `beta`, or a pinned version and OpenCode still uses the old plugin, close OpenCode and remove the cached package.
501
+
502
+ Linux/macOS:
503
+
504
+ ```bash
505
+ rm -rf ~/.cache/opencode/packages/@nm156/opencode-notifier*
506
+ rm -rf ~/.cache/opencode/node_modules/@nm156/opencode-notifier
507
+ rm -f ~/.cache/opencode/bun.lock
508
+ ```
509
+
510
+ Windows PowerShell:
511
+
512
+ ```powershell
513
+ Remove-Item -Recurse -Force "$env:USERPROFILE\.cache\opencode\packages\@nm156\opencode-notifier*" -ErrorAction SilentlyContinue
514
+ Remove-Item -Recurse -Force "$env:USERPROFILE\.cache\opencode\node_modules\@nm156\opencode-notifier" -ErrorAction SilentlyContinue
515
+ Remove-Item -Force "$env:USERPROFILE\.cache\opencode\bun.lock" -ErrorAction SilentlyContinue
516
+ ```
517
+
518
+ Then reopen OpenCode. It will download the plugin again.
519
+
520
+ To avoid cache confusion while testing, pin the exact version in `opencode.json` instead of using a moving tag:
521
+
522
+ ```json
523
+ {
524
+ "plugin": ["@nm156/opencode-notifier@x.y.z"]
525
+ }
526
+ ```
527
+
528
+ Check the version published under a tag:
529
+
530
+ ```bash
531
+ npm view @nm156/opencode-notifier@latest version
532
+ npm view @nm156/opencode-notifier@beta version
533
+ ```
534
+
535
+ Check the version OpenCode cached:
536
+
537
+ ```bash
538
+ cat ~/.cache/opencode/packages/@nm156/opencode-notifier@latest/node_modules/@nm156/opencode-notifier/package.json | grep version
539
+ ```
540
+
541
+ If you use `@beta` or a pinned version, replace `latest` in the path with `beta` or the exact version, for example `0.2.9-beta.0`.
542
+
543
+ ## Troubleshooting
544
+
545
+ **macOS: Not seeing notifications?**
546
+ Go to System Settings > Notifications > Script Editor, make sure it's set to Banners or Alerts.
547
+
548
+ **macOS: node-notifier not showing notifications?**
549
+ Switch back to osascript. Some users report node-notifier works for sounds but not visual notifications on certain macOS versions.
550
+
551
+ **Linux: No notifications?**
552
+ Install libnotify-bin:
553
+
554
+ ```bash
555
+ sudo apt install libnotify-bin # Debian/Ubuntu
556
+ sudo dnf install libnotify # Fedora
557
+ sudo pacman -S libnotify # Arch
558
+ ```
559
+
560
+ Test with: `notify-send "Test" "Hello"`
561
+
562
+ **Linux: No sounds?**
563
+ Install one of: `paplay`, `aplay`, `mpv`, or `ffplay`
564
+
565
+ **KDE Plasma: jumps to wrong terminal window or doesn't jump?**
566
+
567
+ Jump back feature tested on:
568
+
569
+ - KWin with default floating windows
570
+ - KWin + Krohnkite
571
+ - Ghostty and Konsole
572
+ - Warp
573
+ - OpenCode in tmux inside VS Code terminal
574
+
575
+ Most terminal emulators should work fine, but there can be exceptions.
576
+
577
+ Known limitations:
578
+
579
+ - Kitty is currently unsupported for this jump-back path (unstable focus targeting)
580
+ - Yakuake sessions are not supported for activity-specific jump-back behavior
581
+
582
+ You can still override manually (if needed) by pinning an explicit window ID:
583
+
584
+ ```bash
585
+ export OPENCODE_NOTIFIER_WINDOW_ID="$(kdotool getactivewindow)"
586
+ opencode
587
+ ```
588
+
589
+ Manual pinning bypasses heuristic window matching and should activate that exact window on notification action click.
590
+
591
+ **X11 deterministic jump-back**
592
+
593
+ - `xdotool` support is possible for the same startup pin behavior
594
+ - not implemented yet
595
+
596
+ **Windows: Custom sounds not working?**
597
+
598
+ - Must be .wav format (not .mp3)
599
+ - Use full Windows paths: `C:/Users/YourName/sounds/alert.wav` (not `~/`)
600
+ - Make sure the file actually plays in Windows Media Player
601
+ - If using WSL, the path should be accessible from Windows
602
+
603
+ **Windows WSL notifications not working?**
604
+ WSL doesn't have a native notification daemon. Use PowerShell commands instead:
605
+
606
+ Save this wrapper as `C:\Users\YourName\bin\opencode-notifier-popup.ps1`:
607
+
608
+ ```powershell
609
+ param(
610
+ [string]$Message,
611
+ [string]$Event
612
+ )
613
+
614
+ $wshell = New-Object -ComObject Wscript.Shell
615
+ $wshell.Popup($Message, 5, ("OpenCode - {0}" -f $Event), 0+64)
616
+ ```
617
+
618
+ ```json
619
+ {
620
+ "notification": false,
621
+ "sound": true,
622
+ "command": {
623
+ "enabled": true,
624
+ "path": "powershell.exe",
625
+ "args": [
626
+ "-NoProfile",
627
+ "-File",
628
+ "C:\\Users\\YourName\\bin\\opencode-notifier-popup.ps1",
629
+ "{message}",
630
+ "{event}"
631
+ ]
632
+ }
633
+ }
634
+ ```
635
+
636
+ **Windows: OpenCode crashes when notifications appear?**
637
+ This is a known Bun issue on Windows. Disable native notifications and use PowerShell popups:
638
+
639
+ ```json
640
+ {
641
+ "notification": false,
642
+ "sound": true,
643
+ "command": {
644
+ "enabled": true,
645
+ "path": "powershell.exe",
646
+ "args": [
647
+ "-NoProfile",
648
+ "-File",
649
+ "C:\\Users\\YourName\\bin\\opencode-notifier-popup.ps1",
650
+ "{message}",
651
+ "{event}"
652
+ ]
653
+ }
654
+ }
655
+ ```
656
+
657
+ **Plugin not loading?**
658
+
659
+ - Check your `opencode.json` or `config.json` syntax
660
+ - Clear the cache (see Updating section)
661
+ - Restart OpenCode
662
+
663
+ **Plugin installed but no notifications/sounds?**
664
+
665
+ - Check `suppressWhenFocused`: when `true` (default), notifications are skipped while OpenCode terminal is focused. Set to `false` to always notify.
666
+ - Check `enableOnDesktop`: defaults to `false`, so the plugin won't run on Desktop/Web clients. Set to `true` if you need it there.
667
+ - Verify the package version OpenCode cached:
668
+ ```bash
669
+ cat ~/.cache/opencode/packages/@nm156/opencode-notifier@latest/node_modules/@nm156/opencode-notifier/package.json | grep version
670
+ ```
671
+ If you use `@beta` or a pinned version, replace `latest` in the path with `beta` or the exact version.
672
+
673
+ ## Changelog
674
+
675
+ See [CHANGELOG.md](CHANGELOG.md)
676
+
677
+ ## License
678
+
679
+ MIT