@mohak34/opencode-notifier 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -18
- package/dist/index.js +159 -19
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -50,6 +50,7 @@ Create `~/.config/opencode/opencode-notifier.json` with the defaults:
|
|
|
50
50
|
{
|
|
51
51
|
"sound": true,
|
|
52
52
|
"notification": true,
|
|
53
|
+
"bell": false,
|
|
53
54
|
"timeout": 5,
|
|
54
55
|
"showProjectName": true,
|
|
55
56
|
"showSessionTitle": false,
|
|
@@ -67,12 +68,16 @@ Create `~/.config/opencode/opencode-notifier.json` with the defaults:
|
|
|
67
68
|
"minDuration": 0
|
|
68
69
|
},
|
|
69
70
|
"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 }
|
|
71
|
+
"permission": { "sound": true, "notification": true, "command": true, "bell": false },
|
|
72
|
+
"complete": { "sound": true, "notification": true, "command": true, "bell": false },
|
|
73
|
+
"subagent_complete": { "sound": false, "notification": false, "command": true, "bell": false },
|
|
74
|
+
"error": { "sound": true, "notification": true, "command": true, "bell": false },
|
|
75
|
+
"question": { "sound": true, "notification": true, "command": true, "bell": false },
|
|
76
|
+
"user_cancelled": { "sound": false, "notification": false, "command": true, "bell": false },
|
|
77
|
+
"plan_exit": { "sound": true, "notification": true, "command": true, "bell": false },
|
|
78
|
+
"session_started": { "sound": true, "notification": false, "command": true, "bell": false },
|
|
79
|
+
"user_message": { "sound": true, "notification": false, "command": true, "bell": false },
|
|
80
|
+
"client_connected": { "sound": true, "notification": false, "command": true, "bell": false }
|
|
76
81
|
},
|
|
77
82
|
"messages": {
|
|
78
83
|
"permission": "Session needs permission: {sessionTitle}",
|
|
@@ -80,7 +85,11 @@ Create `~/.config/opencode/opencode-notifier.json` with the defaults:
|
|
|
80
85
|
"subagent_complete": "Subagent task completed: {sessionTitle}",
|
|
81
86
|
"error": "Session encountered an error: {sessionTitle}",
|
|
82
87
|
"question": "Session has a question: {sessionTitle}",
|
|
83
|
-
"user_cancelled": "Session was cancelled by user: {sessionTitle}"
|
|
88
|
+
"user_cancelled": "Session was cancelled by user: {sessionTitle}",
|
|
89
|
+
"plan_exit": "Plan ready for review: {sessionTitle}",
|
|
90
|
+
"session_started": "Session started: {sessionTitle}",
|
|
91
|
+
"user_message": "User sent a message: {sessionTitle}",
|
|
92
|
+
"client_connected": "OpenCode connected"
|
|
84
93
|
},
|
|
85
94
|
"sounds": {
|
|
86
95
|
"permission": null,
|
|
@@ -88,7 +97,11 @@ Create `~/.config/opencode/opencode-notifier.json` with the defaults:
|
|
|
88
97
|
"subagent_complete": null,
|
|
89
98
|
"error": null,
|
|
90
99
|
"question": null,
|
|
91
|
-
"user_cancelled": null
|
|
100
|
+
"user_cancelled": null,
|
|
101
|
+
"plan_exit": null,
|
|
102
|
+
"session_started": null,
|
|
103
|
+
"user_message": null,
|
|
104
|
+
"client_connected": null
|
|
92
105
|
},
|
|
93
106
|
"volumes": {
|
|
94
107
|
"permission": 1,
|
|
@@ -96,7 +109,11 @@ Create `~/.config/opencode/opencode-notifier.json` with the defaults:
|
|
|
96
109
|
"subagent_complete": 1,
|
|
97
110
|
"error": 1,
|
|
98
111
|
"question": 1,
|
|
99
|
-
"user_cancelled": 1
|
|
112
|
+
"user_cancelled": 1,
|
|
113
|
+
"plan_exit": 1,
|
|
114
|
+
"session_started": 1,
|
|
115
|
+
"user_message": 1,
|
|
116
|
+
"client_connected": 1
|
|
100
117
|
}
|
|
101
118
|
}
|
|
102
119
|
```
|
|
@@ -109,6 +126,7 @@ Create `~/.config/opencode/opencode-notifier.json` with the defaults:
|
|
|
109
126
|
{
|
|
110
127
|
"sound": true,
|
|
111
128
|
"notification": true,
|
|
129
|
+
"bell": false,
|
|
112
130
|
"timeout": 5,
|
|
113
131
|
"showProjectName": true,
|
|
114
132
|
"showSessionTitle": false,
|
|
@@ -121,6 +139,7 @@ Create `~/.config/opencode/opencode-notifier.json` with the defaults:
|
|
|
121
139
|
|
|
122
140
|
- `sound` - Turn sounds on/off (default: true)
|
|
123
141
|
- `notification` - Turn notifications on/off (default: true)
|
|
142
|
+
- `bell` - Emit terminal BEL (`\x07`) on events (default: false). Behavior depends on your terminal/WM settings
|
|
124
143
|
- `timeout` - How long notifications show in seconds, Linux only (default: 5)
|
|
125
144
|
- `showProjectName` - Show folder name in notification title (default: true)
|
|
126
145
|
- `showSessionTitle` - Include the session title in notification messages via `{sessionTitle}` placeholder (default: false)
|
|
@@ -137,20 +156,28 @@ Control each event separately:
|
|
|
137
156
|
```json
|
|
138
157
|
{
|
|
139
158
|
"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 }
|
|
159
|
+
"permission": { "sound": true, "notification": true, "command": true, "bell": false },
|
|
160
|
+
"complete": { "sound": true, "notification": true, "command": true, "bell": false },
|
|
161
|
+
"subagent_complete": { "sound": false, "notification": false, "command": true, "bell": false },
|
|
162
|
+
"error": { "sound": true, "notification": true, "command": true, "bell": false },
|
|
163
|
+
"question": { "sound": true, "notification": true, "command": true, "bell": false },
|
|
164
|
+
"user_cancelled": { "sound": false, "notification": false, "command": true, "bell": false },
|
|
165
|
+
"plan_exit": { "sound": true, "notification": true, "command": true, "bell": false },
|
|
166
|
+
"session_started": { "sound": true, "notification": false, "command": true, "bell": false },
|
|
167
|
+
"user_message": { "sound": true, "notification": false, "command": true, "bell": false },
|
|
168
|
+
"client_connected": { "sound": true, "notification": false, "command": true, "bell": false }
|
|
146
169
|
}
|
|
147
170
|
}
|
|
148
171
|
```
|
|
149
172
|
|
|
150
173
|
`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
174
|
|
|
175
|
+
`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).
|
|
176
|
+
|
|
152
177
|
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
178
|
|
|
179
|
+
`bell` is terminal-driven and may be audible, visual, both, or ignored depending on your terminal setup. Quick check: `printf '\a'`.
|
|
180
|
+
|
|
154
181
|
Or use true/false for both:
|
|
155
182
|
|
|
156
183
|
```json
|
|
@@ -173,7 +200,11 @@ Customize the notification text:
|
|
|
173
200
|
"subagent_complete": "Subagent task completed: {sessionTitle}",
|
|
174
201
|
"error": "Session encountered an error: {sessionTitle}",
|
|
175
202
|
"question": "Session has a question: {sessionTitle}",
|
|
176
|
-
"user_cancelled": "Session was cancelled by user: {sessionTitle}"
|
|
203
|
+
"user_cancelled": "Session was cancelled by user: {sessionTitle}",
|
|
204
|
+
"plan_exit": "Plan ready for review: {sessionTitle}",
|
|
205
|
+
"session_started": "Session started: {sessionTitle}",
|
|
206
|
+
"user_message": "User sent a message: {sessionTitle}",
|
|
207
|
+
"client_connected": "OpenCode connected"
|
|
177
208
|
}
|
|
178
209
|
}
|
|
179
210
|
```
|
|
@@ -204,7 +235,11 @@ Use your own sound files:
|
|
|
204
235
|
"subagent_complete": "/path/to/subagent-done.wav",
|
|
205
236
|
"error": "/path/to/error.wav",
|
|
206
237
|
"question": "/path/to/question.wav",
|
|
207
|
-
"user_cancelled": "/path/to/cancelled.wav"
|
|
238
|
+
"user_cancelled": "/path/to/cancelled.wav",
|
|
239
|
+
"plan_exit": "/path/to/plan-ready.wav",
|
|
240
|
+
"session_started": "/path/to/session-started.wav",
|
|
241
|
+
"user_message": "/path/to/user-message.wav",
|
|
242
|
+
"client_connected": "/path/to/client-connected.wav"
|
|
208
243
|
}
|
|
209
244
|
}
|
|
210
245
|
```
|
|
@@ -226,7 +261,11 @@ Set per-event volume from `0` to `1`:
|
|
|
226
261
|
"subagent_complete": 0.15,
|
|
227
262
|
"error": 1,
|
|
228
263
|
"question": 0.7,
|
|
229
|
-
"user_cancelled": 0.5
|
|
264
|
+
"user_cancelled": 0.5,
|
|
265
|
+
"plan_exit": 0.6,
|
|
266
|
+
"session_started": 0.35,
|
|
267
|
+
"user_message": 0.2,
|
|
268
|
+
"client_connected": 0.45
|
|
230
269
|
}
|
|
231
270
|
}
|
|
232
271
|
```
|
package/dist/index.js
CHANGED
|
@@ -10,11 +10,13 @@ import { fileURLToPath } from "url";
|
|
|
10
10
|
var DEFAULT_EVENT_CONFIG = {
|
|
11
11
|
sound: true,
|
|
12
12
|
notification: true,
|
|
13
|
-
command: true
|
|
13
|
+
command: true,
|
|
14
|
+
bell: false
|
|
14
15
|
};
|
|
15
16
|
var DEFAULT_CONFIG = {
|
|
16
17
|
sound: true,
|
|
17
18
|
notification: true,
|
|
19
|
+
bell: false,
|
|
18
20
|
timeout: 5,
|
|
19
21
|
showProjectName: true,
|
|
20
22
|
showSessionTitle: false,
|
|
@@ -37,7 +39,11 @@ var DEFAULT_CONFIG = {
|
|
|
37
39
|
error: { ...DEFAULT_EVENT_CONFIG },
|
|
38
40
|
question: { ...DEFAULT_EVENT_CONFIG },
|
|
39
41
|
interrupted: { ...DEFAULT_EVENT_CONFIG },
|
|
40
|
-
user_cancelled: { ...DEFAULT_EVENT_CONFIG, sound: false, notification: false }
|
|
42
|
+
user_cancelled: { ...DEFAULT_EVENT_CONFIG, sound: false, notification: false },
|
|
43
|
+
plan_exit: { ...DEFAULT_EVENT_CONFIG },
|
|
44
|
+
session_started: { ...DEFAULT_EVENT_CONFIG, notification: false },
|
|
45
|
+
user_message: { ...DEFAULT_EVENT_CONFIG, notification: false },
|
|
46
|
+
client_connected: { ...DEFAULT_EVENT_CONFIG, notification: false }
|
|
41
47
|
},
|
|
42
48
|
messages: {
|
|
43
49
|
permission: "Session needs permission: {sessionTitle}",
|
|
@@ -46,7 +52,11 @@ var DEFAULT_CONFIG = {
|
|
|
46
52
|
error: "Session encountered an error: {sessionTitle}",
|
|
47
53
|
question: "Session has a question: {sessionTitle}",
|
|
48
54
|
interrupted: "Session was interrupted: {sessionTitle}",
|
|
49
|
-
user_cancelled: "Session was cancelled by user: {sessionTitle}"
|
|
55
|
+
user_cancelled: "Session was cancelled by user: {sessionTitle}",
|
|
56
|
+
plan_exit: "Plan ready for review: {sessionTitle}",
|
|
57
|
+
session_started: "Session started: {sessionTitle}",
|
|
58
|
+
user_message: "User sent a message: {sessionTitle}",
|
|
59
|
+
client_connected: "OpenCode connected"
|
|
50
60
|
},
|
|
51
61
|
sounds: {
|
|
52
62
|
permission: null,
|
|
@@ -55,7 +65,11 @@ var DEFAULT_CONFIG = {
|
|
|
55
65
|
error: null,
|
|
56
66
|
question: null,
|
|
57
67
|
interrupted: null,
|
|
58
|
-
user_cancelled: null
|
|
68
|
+
user_cancelled: null,
|
|
69
|
+
plan_exit: null,
|
|
70
|
+
session_started: null,
|
|
71
|
+
user_message: null,
|
|
72
|
+
client_connected: null
|
|
59
73
|
},
|
|
60
74
|
volumes: {
|
|
61
75
|
permission: 1,
|
|
@@ -64,7 +78,11 @@ var DEFAULT_CONFIG = {
|
|
|
64
78
|
error: 1,
|
|
65
79
|
question: 1,
|
|
66
80
|
interrupted: 1,
|
|
67
|
-
user_cancelled: 1
|
|
81
|
+
user_cancelled: 1,
|
|
82
|
+
plan_exit: 1,
|
|
83
|
+
session_started: 1,
|
|
84
|
+
user_message: 1,
|
|
85
|
+
client_connected: 1
|
|
68
86
|
}
|
|
69
87
|
};
|
|
70
88
|
function getConfigPath() {
|
|
@@ -85,13 +103,15 @@ function parseEventConfig(userEvent, defaultConfig) {
|
|
|
85
103
|
return {
|
|
86
104
|
sound: userEvent,
|
|
87
105
|
notification: userEvent,
|
|
88
|
-
command: userEvent
|
|
106
|
+
command: userEvent,
|
|
107
|
+
bell: defaultConfig.bell
|
|
89
108
|
};
|
|
90
109
|
}
|
|
91
110
|
return {
|
|
92
111
|
sound: userEvent.sound ?? defaultConfig.sound,
|
|
93
112
|
notification: userEvent.notification ?? defaultConfig.notification,
|
|
94
|
-
command: userEvent.command ?? defaultConfig.command
|
|
113
|
+
command: userEvent.command ?? defaultConfig.command,
|
|
114
|
+
bell: userEvent.bell ?? defaultConfig.bell
|
|
95
115
|
};
|
|
96
116
|
}
|
|
97
117
|
function parseVolume(value, defaultVolume) {
|
|
@@ -116,10 +136,12 @@ function loadConfig() {
|
|
|
116
136
|
const userConfig = JSON.parse(fileContent);
|
|
117
137
|
const globalSound = userConfig.sound ?? DEFAULT_CONFIG.sound;
|
|
118
138
|
const globalNotification = userConfig.notification ?? DEFAULT_CONFIG.notification;
|
|
139
|
+
const globalBell = userConfig.bell ?? DEFAULT_CONFIG.bell;
|
|
119
140
|
const defaultWithGlobal = {
|
|
120
141
|
sound: globalSound,
|
|
121
142
|
notification: globalNotification,
|
|
122
|
-
command: true
|
|
143
|
+
command: true,
|
|
144
|
+
bell: globalBell
|
|
123
145
|
};
|
|
124
146
|
const userCommand = userConfig.command ?? {};
|
|
125
147
|
const commandArgs = Array.isArray(userCommand.args) ? userCommand.args.filter((arg) => typeof arg === "string") : undefined;
|
|
@@ -127,6 +149,7 @@ function loadConfig() {
|
|
|
127
149
|
return {
|
|
128
150
|
sound: globalSound,
|
|
129
151
|
notification: globalNotification,
|
|
152
|
+
bell: globalBell,
|
|
130
153
|
timeout: typeof userConfig.timeout === "number" && userConfig.timeout > 0 ? userConfig.timeout : DEFAULT_CONFIG.timeout,
|
|
131
154
|
showProjectName: userConfig.showProjectName ?? DEFAULT_CONFIG.showProjectName,
|
|
132
155
|
showSessionTitle: userConfig.showSessionTitle ?? DEFAULT_CONFIG.showSessionTitle,
|
|
@@ -146,11 +169,15 @@ function loadConfig() {
|
|
|
146
169
|
events: {
|
|
147
170
|
permission: parseEventConfig(userConfig.events?.permission ?? userConfig.permission, defaultWithGlobal),
|
|
148
171
|
complete: parseEventConfig(userConfig.events?.complete ?? userConfig.complete, defaultWithGlobal),
|
|
149
|
-
subagent_complete: parseEventConfig(userConfig.events?.subagent_complete ?? userConfig.subagent_complete, { sound: false, notification: false, command: true }),
|
|
172
|
+
subagent_complete: parseEventConfig(userConfig.events?.subagent_complete ?? userConfig.subagent_complete, { sound: false, notification: false, command: true, bell: false }),
|
|
150
173
|
error: parseEventConfig(userConfig.events?.error ?? userConfig.error, defaultWithGlobal),
|
|
151
174
|
question: parseEventConfig(userConfig.events?.question ?? userConfig.question, defaultWithGlobal),
|
|
152
175
|
interrupted: parseEventConfig(userConfig.events?.interrupted ?? userConfig.interrupted, defaultWithGlobal),
|
|
153
|
-
user_cancelled: parseEventConfig(userConfig.events?.user_cancelled ?? userConfig.user_cancelled, { sound: false, notification: false, command: true })
|
|
176
|
+
user_cancelled: parseEventConfig(userConfig.events?.user_cancelled ?? userConfig.user_cancelled, { sound: false, notification: false, command: true, bell: false }),
|
|
177
|
+
plan_exit: parseEventConfig(userConfig.events?.plan_exit ?? userConfig.plan_exit, defaultWithGlobal),
|
|
178
|
+
session_started: parseEventConfig(userConfig.events?.session_started ?? userConfig.session_started, { ...defaultWithGlobal, notification: false }),
|
|
179
|
+
user_message: parseEventConfig(userConfig.events?.user_message ?? userConfig.user_message, { ...defaultWithGlobal, notification: false }),
|
|
180
|
+
client_connected: parseEventConfig(userConfig.events?.client_connected ?? userConfig.client_connected, { ...defaultWithGlobal, notification: false })
|
|
154
181
|
},
|
|
155
182
|
messages: {
|
|
156
183
|
permission: userConfig.messages?.permission ?? DEFAULT_CONFIG.messages.permission,
|
|
@@ -159,7 +186,11 @@ function loadConfig() {
|
|
|
159
186
|
error: userConfig.messages?.error ?? DEFAULT_CONFIG.messages.error,
|
|
160
187
|
question: userConfig.messages?.question ?? DEFAULT_CONFIG.messages.question,
|
|
161
188
|
interrupted: userConfig.messages?.interrupted ?? DEFAULT_CONFIG.messages.interrupted,
|
|
162
|
-
user_cancelled: userConfig.messages?.user_cancelled ?? DEFAULT_CONFIG.messages.user_cancelled
|
|
189
|
+
user_cancelled: userConfig.messages?.user_cancelled ?? DEFAULT_CONFIG.messages.user_cancelled,
|
|
190
|
+
plan_exit: userConfig.messages?.plan_exit ?? DEFAULT_CONFIG.messages.plan_exit,
|
|
191
|
+
session_started: userConfig.messages?.session_started ?? DEFAULT_CONFIG.messages.session_started,
|
|
192
|
+
user_message: userConfig.messages?.user_message ?? DEFAULT_CONFIG.messages.user_message,
|
|
193
|
+
client_connected: userConfig.messages?.client_connected ?? DEFAULT_CONFIG.messages.client_connected
|
|
163
194
|
},
|
|
164
195
|
sounds: {
|
|
165
196
|
permission: userConfig.sounds?.permission ?? DEFAULT_CONFIG.sounds.permission,
|
|
@@ -168,7 +199,11 @@ function loadConfig() {
|
|
|
168
199
|
error: userConfig.sounds?.error ?? DEFAULT_CONFIG.sounds.error,
|
|
169
200
|
question: userConfig.sounds?.question ?? DEFAULT_CONFIG.sounds.question,
|
|
170
201
|
interrupted: userConfig.sounds?.interrupted ?? DEFAULT_CONFIG.sounds.interrupted,
|
|
171
|
-
user_cancelled: userConfig.sounds?.user_cancelled ?? DEFAULT_CONFIG.sounds.user_cancelled
|
|
202
|
+
user_cancelled: userConfig.sounds?.user_cancelled ?? DEFAULT_CONFIG.sounds.user_cancelled,
|
|
203
|
+
plan_exit: userConfig.sounds?.plan_exit ?? DEFAULT_CONFIG.sounds.plan_exit,
|
|
204
|
+
session_started: userConfig.sounds?.session_started ?? DEFAULT_CONFIG.sounds.session_started,
|
|
205
|
+
user_message: userConfig.sounds?.user_message ?? DEFAULT_CONFIG.sounds.user_message,
|
|
206
|
+
client_connected: userConfig.sounds?.client_connected ?? DEFAULT_CONFIG.sounds.client_connected
|
|
172
207
|
},
|
|
173
208
|
volumes: {
|
|
174
209
|
permission: parseVolume(userConfig.volumes?.permission, DEFAULT_CONFIG.volumes.permission),
|
|
@@ -177,7 +212,11 @@ function loadConfig() {
|
|
|
177
212
|
error: parseVolume(userConfig.volumes?.error, DEFAULT_CONFIG.volumes.error),
|
|
178
213
|
question: parseVolume(userConfig.volumes?.question, DEFAULT_CONFIG.volumes.question),
|
|
179
214
|
interrupted: parseVolume(userConfig.volumes?.interrupted, DEFAULT_CONFIG.volumes.interrupted),
|
|
180
|
-
user_cancelled: parseVolume(userConfig.volumes?.user_cancelled, DEFAULT_CONFIG.volumes.user_cancelled)
|
|
215
|
+
user_cancelled: parseVolume(userConfig.volumes?.user_cancelled, DEFAULT_CONFIG.volumes.user_cancelled),
|
|
216
|
+
plan_exit: parseVolume(userConfig.volumes?.plan_exit, DEFAULT_CONFIG.volumes.plan_exit),
|
|
217
|
+
session_started: parseVolume(userConfig.volumes?.session_started, DEFAULT_CONFIG.volumes.session_started),
|
|
218
|
+
user_message: parseVolume(userConfig.volumes?.user_message, DEFAULT_CONFIG.volumes.user_message),
|
|
219
|
+
client_connected: parseVolume(userConfig.volumes?.client_connected, DEFAULT_CONFIG.volumes.client_connected)
|
|
181
220
|
}
|
|
182
221
|
};
|
|
183
222
|
} catch {
|
|
@@ -193,6 +232,9 @@ function isEventNotificationEnabled(config, event) {
|
|
|
193
232
|
function isEventCommandEnabled(config, event) {
|
|
194
233
|
return config.events[event].command;
|
|
195
234
|
}
|
|
235
|
+
function isEventBellEnabled(config, event) {
|
|
236
|
+
return config.events[event].bell;
|
|
237
|
+
}
|
|
196
238
|
function getMessage(config, event) {
|
|
197
239
|
return config.messages[event];
|
|
198
240
|
}
|
|
@@ -492,6 +534,24 @@ async function playSound(event, customPath, volume) {
|
|
|
492
534
|
} catch {}
|
|
493
535
|
}
|
|
494
536
|
|
|
537
|
+
// src/bell.ts
|
|
538
|
+
var lastBellTime = 0;
|
|
539
|
+
var BELL_DEBOUNCE_MS = 500;
|
|
540
|
+
function ringBell(now = Date.now()) {
|
|
541
|
+
if (!process.stdout.isTTY) {
|
|
542
|
+
return Promise.resolve();
|
|
543
|
+
}
|
|
544
|
+
if (now - lastBellTime < BELL_DEBOUNCE_MS) {
|
|
545
|
+
return Promise.resolve();
|
|
546
|
+
}
|
|
547
|
+
lastBellTime = now;
|
|
548
|
+
return new Promise((resolve) => {
|
|
549
|
+
process.stdout.write("\x07", () => {
|
|
550
|
+
resolve();
|
|
551
|
+
});
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
|
|
495
555
|
// src/command.ts
|
|
496
556
|
import { spawn as spawn2 } from "child_process";
|
|
497
557
|
function substituteTokens(value, event, message, sessionTitle, agentName, projectName, timestamp, turn) {
|
|
@@ -523,7 +583,7 @@ var MAC_TERMINAL_APP_NAMES = new Set([
|
|
|
523
583
|
"terminal",
|
|
524
584
|
"iterm2",
|
|
525
585
|
"ghostty",
|
|
526
|
-
"wezterm",
|
|
586
|
+
"wezterm-gui",
|
|
527
587
|
"alacritty",
|
|
528
588
|
"kitty",
|
|
529
589
|
"hyper",
|
|
@@ -662,6 +722,8 @@ function getExpectedMacTerminalAppNames(env) {
|
|
|
662
722
|
expected.add("code insiders");
|
|
663
723
|
} else if (termProgram === "warpterminal") {
|
|
664
724
|
expected.add("warp");
|
|
725
|
+
} else if (termProgram === "wezterm") {
|
|
726
|
+
expected.add("wezterm-gui");
|
|
665
727
|
} else if (termProgram.length > 0) {
|
|
666
728
|
expected.add(termProgram);
|
|
667
729
|
}
|
|
@@ -786,6 +848,28 @@ var pendingIdleTimers = new Map;
|
|
|
786
848
|
var sessionIdleSequence = new Map;
|
|
787
849
|
var sessionErrorSuppressionAt = new Map;
|
|
788
850
|
var sessionLastBusyAt = new Map;
|
|
851
|
+
var subagentSessionIds = new Set;
|
|
852
|
+
function asRecord(value) {
|
|
853
|
+
return value !== null && typeof value === "object" ? value : null;
|
|
854
|
+
}
|
|
855
|
+
function getNestedRecord(root, ...path) {
|
|
856
|
+
let current = root;
|
|
857
|
+
for (const key of path) {
|
|
858
|
+
const record = asRecord(current);
|
|
859
|
+
if (!record || !(key in record)) {
|
|
860
|
+
return null;
|
|
861
|
+
}
|
|
862
|
+
current = record[key];
|
|
863
|
+
}
|
|
864
|
+
return asRecord(current);
|
|
865
|
+
}
|
|
866
|
+
function getStringField(record, key) {
|
|
867
|
+
if (!record) {
|
|
868
|
+
return null;
|
|
869
|
+
}
|
|
870
|
+
const value = record[key];
|
|
871
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
872
|
+
}
|
|
789
873
|
var globalTurnCount = null;
|
|
790
874
|
function loadTurnCount() {
|
|
791
875
|
try {
|
|
@@ -815,6 +899,7 @@ setInterval(() => {
|
|
|
815
899
|
for (const [sessionID] of sessionIdleSequence) {
|
|
816
900
|
if (!pendingIdleTimers.has(sessionID)) {
|
|
817
901
|
sessionIdleSequence.delete(sessionID);
|
|
902
|
+
subagentSessionIds.delete(sessionID);
|
|
818
903
|
}
|
|
819
904
|
}
|
|
820
905
|
for (const [sessionID, timestamp] of sessionErrorSuppressionAt) {
|
|
@@ -886,6 +971,9 @@ async function handleEvent(config, eventType, projectName, elapsedSeconds, sessi
|
|
|
886
971
|
const soundVolume = getSoundVolume(config, eventType);
|
|
887
972
|
promises.push(playSound(eventType, customSoundPath, soundVolume));
|
|
888
973
|
}
|
|
974
|
+
if (isEventBellEnabled(config, eventType)) {
|
|
975
|
+
promises.push(ringBell());
|
|
976
|
+
}
|
|
889
977
|
const minDuration = config.command?.minDuration;
|
|
890
978
|
const shouldSkipCommand = !isEventCommandEnabled(config, eventType) || typeof minDuration === "number" && Number.isFinite(minDuration) && minDuration > 0 && typeof elapsedSeconds === "number" && Number.isFinite(elapsedSeconds) && elapsedSeconds < minDuration;
|
|
891
979
|
if (!shouldSkipCommand) {
|
|
@@ -894,11 +982,23 @@ async function handleEvent(config, eventType, projectName, elapsedSeconds, sessi
|
|
|
894
982
|
await Promise.allSettled(promises);
|
|
895
983
|
}
|
|
896
984
|
function getSessionIDFromEvent(event) {
|
|
897
|
-
const
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
985
|
+
const properties = getNestedRecord(event, "properties");
|
|
986
|
+
return getStringField(properties, "sessionID");
|
|
987
|
+
}
|
|
988
|
+
function getSessionLifecycleInfo(event) {
|
|
989
|
+
const info = getNestedRecord(event, "properties", "info");
|
|
990
|
+
return {
|
|
991
|
+
id: getStringField(info, "id"),
|
|
992
|
+
title: getStringField(info, "title"),
|
|
993
|
+
parentID: getStringField(info, "parentID")
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
function getMessageUpdatedInfo(event) {
|
|
997
|
+
const info = getNestedRecord(event, "properties", "info");
|
|
998
|
+
return {
|
|
999
|
+
role: getStringField(info, "role"),
|
|
1000
|
+
sessionID: getStringField(info, "sessionID")
|
|
1001
|
+
};
|
|
902
1002
|
}
|
|
903
1003
|
function clearPendingIdleTimer(sessionID) {
|
|
904
1004
|
const timer = pendingIdleTimers.get(sessionID);
|
|
@@ -984,6 +1084,10 @@ async function processSessionIdle(client, config, projectName, event, sessionID,
|
|
|
984
1084
|
if (shouldSuppressSessionIdle(sessionID)) {
|
|
985
1085
|
return;
|
|
986
1086
|
}
|
|
1087
|
+
if (subagentSessionIds.has(sessionID)) {
|
|
1088
|
+
await handleEventWithElapsedTime(client, config, "subagent_complete", projectName, event, idleReceivedAtMs, null);
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
987
1091
|
const sessionInfo = await getSessionInfo(client, sessionID);
|
|
988
1092
|
if (!hasCurrentSessionIdleSequence(sessionID, sequence)) {
|
|
989
1093
|
return;
|
|
@@ -995,6 +1099,7 @@ async function processSessionIdle(client, config, projectName, event, sessionID,
|
|
|
995
1099
|
await handleEventWithElapsedTime(client, config, "complete", projectName, event, idleReceivedAtMs, sessionInfo.title);
|
|
996
1100
|
return;
|
|
997
1101
|
}
|
|
1102
|
+
subagentSessionIds.add(sessionID);
|
|
998
1103
|
await handleEventWithElapsedTime(client, config, "subagent_complete", projectName, event, idleReceivedAtMs, sessionInfo.title);
|
|
999
1104
|
}
|
|
1000
1105
|
function scheduleSessionIdle(client, config, projectName, event, sessionID) {
|
|
@@ -1037,9 +1142,32 @@ var NotifierPlugin = async ({ client, directory }) => {
|
|
|
1037
1142
|
}
|
|
1038
1143
|
const getConfig = () => loadConfig();
|
|
1039
1144
|
const projectName = directory ? basename(directory) : null;
|
|
1145
|
+
setTimeout(() => {
|
|
1146
|
+
handleEvent(getConfig(), "client_connected", projectName, null);
|
|
1147
|
+
}, 100);
|
|
1040
1148
|
return {
|
|
1041
1149
|
event: async ({ event }) => {
|
|
1042
1150
|
const config = getConfig();
|
|
1151
|
+
if (event.type === "session.created") {
|
|
1152
|
+
const info = getSessionLifecycleInfo(event);
|
|
1153
|
+
if (info.parentID && info.id) {
|
|
1154
|
+
subagentSessionIds.add(info.id);
|
|
1155
|
+
} else {
|
|
1156
|
+
await handleEvent(config, "session_started", projectName, null, info.title, info.id, null);
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
if (event.type === "session.updated") {
|
|
1160
|
+
const info = getSessionLifecycleInfo(event);
|
|
1161
|
+
if (info.parentID && info.id) {
|
|
1162
|
+
subagentSessionIds.add(info.id);
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
if (event.type === "session.deleted") {
|
|
1166
|
+
const info = getSessionLifecycleInfo(event);
|
|
1167
|
+
if (info.id) {
|
|
1168
|
+
subagentSessionIds.delete(info.id);
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1043
1171
|
if (event.type === "permission.asked") {
|
|
1044
1172
|
const sessionID = getSessionIDFromEvent(event);
|
|
1045
1173
|
if (!shouldSuppressPermissionAlert(sessionID)) {
|
|
@@ -1068,6 +1196,15 @@ var NotifierPlugin = async ({ client, directory }) => {
|
|
|
1068
1196
|
}
|
|
1069
1197
|
await handleEventWithElapsedTime(client, config, eventType, projectName, event, undefined, sessionTitle);
|
|
1070
1198
|
}
|
|
1199
|
+
if (event.type === "message.updated") {
|
|
1200
|
+
const info = getMessageUpdatedInfo(event);
|
|
1201
|
+
if (info.role === "user") {
|
|
1202
|
+
const sessionID = info.sessionID;
|
|
1203
|
+
if (!sessionID || !subagentSessionIds.has(sessionID)) {
|
|
1204
|
+
await handleEvent(config, "user_message", projectName, null, null, sessionID, null);
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1071
1208
|
},
|
|
1072
1209
|
"permission.ask": async () => {
|
|
1073
1210
|
const config = getConfig();
|
|
@@ -1080,6 +1217,9 @@ var NotifierPlugin = async ({ client, directory }) => {
|
|
|
1080
1217
|
if (input.tool === "question") {
|
|
1081
1218
|
await handleEvent(config, "question", projectName, null);
|
|
1082
1219
|
}
|
|
1220
|
+
if (input.tool === "plan_exit") {
|
|
1221
|
+
await handleEvent(config, "plan_exit", projectName, null);
|
|
1222
|
+
}
|
|
1083
1223
|
}
|
|
1084
1224
|
};
|
|
1085
1225
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mohak34/opencode-notifier",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "OpenCode plugin that sends system notifications and plays sounds when permission is needed, generation completes, or errors occur",
|
|
5
5
|
"author": "mohak34",
|
|
6
6
|
"license": "MIT",
|