@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/LICENSE +21 -0
- package/README.md +679 -0
- package/dist/index.js +2440 -0
- package/logos/opencode-logo-dark.png +0 -0
- package/logos/opencode-logo-light.png +0 -0
- package/package.json +62 -0
- package/sounds/complete.wav +0 -0
- package/sounds/error.wav +0 -0
- package/sounds/permission.wav +0 -0
- package/sounds/question.wav +0 -0
- package/sounds/subagent_complete.wav +0 -0
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
|