@mohak34/opencode-notifier 0.1.32 → 0.1.33
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 +15 -13
- package/dist/index.js +22 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -66,12 +66,12 @@ Create `~/.config/opencode/opencode-notifier.json` with the defaults:
|
|
|
66
66
|
"minDuration": 0
|
|
67
67
|
},
|
|
68
68
|
"events": {
|
|
69
|
-
"permission": { "sound": true, "notification": true },
|
|
70
|
-
"complete": { "sound": true, "notification": true },
|
|
71
|
-
"subagent_complete": { "sound": false, "notification": false },
|
|
72
|
-
"error": { "sound": true, "notification": true },
|
|
73
|
-
"question": { "sound": true, "notification": true },
|
|
74
|
-
"user_cancelled": { "sound": false, "notification": false }
|
|
69
|
+
"permission": { "sound": true, "notification": true, "command": true },
|
|
70
|
+
"complete": { "sound": true, "notification": true, "command": true },
|
|
71
|
+
"subagent_complete": { "sound": false, "notification": false, "command": true },
|
|
72
|
+
"error": { "sound": true, "notification": true, "command": true },
|
|
73
|
+
"question": { "sound": true, "notification": true, "command": true },
|
|
74
|
+
"user_cancelled": { "sound": false, "notification": false, "command": true }
|
|
75
75
|
},
|
|
76
76
|
"messages": {
|
|
77
77
|
"permission": "Session needs permission: {sessionTitle}",
|
|
@@ -123,7 +123,7 @@ Create `~/.config/opencode/opencode-notifier.json` with the defaults:
|
|
|
123
123
|
- `showSessionTitle` - Include the session title in notification messages via `{sessionTitle}` placeholder (default: true)
|
|
124
124
|
- `showIcon` - Show OpenCode icon, Windows/Linux only (default: true)
|
|
125
125
|
- `suppressWhenFocused` - Skip notifications and sounds when the terminal is the active window (default: true). See [Focus detection](#focus-detection) for platform details
|
|
126
|
-
- `notificationSystem` - macOS only: `"osascript"`, `"node-notifier"`, or `"ghostty"` (default: "osascript"). Use `"ghostty"` if you're running Ghostty terminal for native OSC
|
|
126
|
+
- `notificationSystem` - macOS only: `"osascript"`, `"node-notifier"`, or `"ghostty"` (default: "osascript"). Use `"ghostty"` if you're running Ghostty terminal for native OSC 9 notifications
|
|
127
127
|
- `linux.grouping` - Linux only: replace notifications in-place instead of stacking (default: false). Requires `notify-send` 0.8+
|
|
128
128
|
|
|
129
129
|
### Events
|
|
@@ -133,18 +133,20 @@ Control each event separately:
|
|
|
133
133
|
```json
|
|
134
134
|
{
|
|
135
135
|
"events": {
|
|
136
|
-
"permission": { "sound": true, "notification": true },
|
|
137
|
-
"complete": { "sound": true, "notification": true },
|
|
138
|
-
"subagent_complete": { "sound": false, "notification": false },
|
|
139
|
-
"error": { "sound": true, "notification": true },
|
|
140
|
-
"question": { "sound": true, "notification": true },
|
|
141
|
-
"user_cancelled": { "sound": false, "notification": false }
|
|
136
|
+
"permission": { "sound": true, "notification": true, "command": true },
|
|
137
|
+
"complete": { "sound": true, "notification": true, "command": true },
|
|
138
|
+
"subagent_complete": { "sound": false, "notification": false, "command": true },
|
|
139
|
+
"error": { "sound": true, "notification": true, "command": true },
|
|
140
|
+
"question": { "sound": true, "notification": true, "command": true },
|
|
141
|
+
"user_cancelled": { "sound": false, "notification": false, "command": true }
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
144
|
```
|
|
145
145
|
|
|
146
146
|
`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.
|
|
147
147
|
|
|
148
|
+
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.
|
|
149
|
+
|
|
148
150
|
Or use true/false for both:
|
|
149
151
|
|
|
150
152
|
```json
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { basename } from "path";
|
|
3
|
-
import { readFileSync as readFileSync2, writeFileSync
|
|
3
|
+
import { readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
4
4
|
|
|
5
5
|
// src/config.ts
|
|
6
6
|
import { readFileSync, existsSync } from "fs";
|
|
@@ -9,7 +9,8 @@ import { homedir } from "os";
|
|
|
9
9
|
import { fileURLToPath } from "url";
|
|
10
10
|
var DEFAULT_EVENT_CONFIG = {
|
|
11
11
|
sound: true,
|
|
12
|
-
notification: true
|
|
12
|
+
notification: true,
|
|
13
|
+
command: true
|
|
13
14
|
};
|
|
14
15
|
var DEFAULT_CONFIG = {
|
|
15
16
|
sound: true,
|
|
@@ -31,11 +32,11 @@ var DEFAULT_CONFIG = {
|
|
|
31
32
|
events: {
|
|
32
33
|
permission: { ...DEFAULT_EVENT_CONFIG },
|
|
33
34
|
complete: { ...DEFAULT_EVENT_CONFIG },
|
|
34
|
-
subagent_complete: { sound: false, notification: false },
|
|
35
|
+
subagent_complete: { ...DEFAULT_EVENT_CONFIG, sound: false, notification: false },
|
|
35
36
|
error: { ...DEFAULT_EVENT_CONFIG },
|
|
36
37
|
question: { ...DEFAULT_EVENT_CONFIG },
|
|
37
38
|
interrupted: { ...DEFAULT_EVENT_CONFIG },
|
|
38
|
-
user_cancelled: { sound: false, notification: false }
|
|
39
|
+
user_cancelled: { ...DEFAULT_EVENT_CONFIG, sound: false, notification: false }
|
|
39
40
|
},
|
|
40
41
|
messages: {
|
|
41
42
|
permission: "Session needs permission: {sessionTitle}",
|
|
@@ -82,12 +83,14 @@ function parseEventConfig(userEvent, defaultConfig) {
|
|
|
82
83
|
if (typeof userEvent === "boolean") {
|
|
83
84
|
return {
|
|
84
85
|
sound: userEvent,
|
|
85
|
-
notification: userEvent
|
|
86
|
+
notification: userEvent,
|
|
87
|
+
command: userEvent
|
|
86
88
|
};
|
|
87
89
|
}
|
|
88
90
|
return {
|
|
89
91
|
sound: userEvent.sound ?? defaultConfig.sound,
|
|
90
|
-
notification: userEvent.notification ?? defaultConfig.notification
|
|
92
|
+
notification: userEvent.notification ?? defaultConfig.notification,
|
|
93
|
+
command: userEvent.command ?? defaultConfig.command
|
|
91
94
|
};
|
|
92
95
|
}
|
|
93
96
|
function parseVolume(value, defaultVolume) {
|
|
@@ -114,7 +117,8 @@ function loadConfig() {
|
|
|
114
117
|
const globalNotification = userConfig.notification ?? DEFAULT_CONFIG.notification;
|
|
115
118
|
const defaultWithGlobal = {
|
|
116
119
|
sound: globalSound,
|
|
117
|
-
notification: globalNotification
|
|
120
|
+
notification: globalNotification,
|
|
121
|
+
command: true
|
|
118
122
|
};
|
|
119
123
|
const userCommand = userConfig.command ?? {};
|
|
120
124
|
const commandArgs = Array.isArray(userCommand.args) ? userCommand.args.filter((arg) => typeof arg === "string") : undefined;
|
|
@@ -140,11 +144,11 @@ function loadConfig() {
|
|
|
140
144
|
events: {
|
|
141
145
|
permission: parseEventConfig(userConfig.events?.permission ?? userConfig.permission, defaultWithGlobal),
|
|
142
146
|
complete: parseEventConfig(userConfig.events?.complete ?? userConfig.complete, defaultWithGlobal),
|
|
143
|
-
subagent_complete: parseEventConfig(userConfig.events?.subagent_complete ?? userConfig.subagent_complete, { sound: false, notification: false }),
|
|
147
|
+
subagent_complete: parseEventConfig(userConfig.events?.subagent_complete ?? userConfig.subagent_complete, { sound: false, notification: false, command: true }),
|
|
144
148
|
error: parseEventConfig(userConfig.events?.error ?? userConfig.error, defaultWithGlobal),
|
|
145
149
|
question: parseEventConfig(userConfig.events?.question ?? userConfig.question, defaultWithGlobal),
|
|
146
150
|
interrupted: parseEventConfig(userConfig.events?.interrupted ?? userConfig.interrupted, defaultWithGlobal),
|
|
147
|
-
user_cancelled: parseEventConfig(userConfig.events?.user_cancelled ?? userConfig.user_cancelled, { sound: false, notification: false })
|
|
151
|
+
user_cancelled: parseEventConfig(userConfig.events?.user_cancelled ?? userConfig.user_cancelled, { sound: false, notification: false, command: true })
|
|
148
152
|
},
|
|
149
153
|
messages: {
|
|
150
154
|
permission: userConfig.messages?.permission ?? DEFAULT_CONFIG.messages.permission,
|
|
@@ -184,6 +188,9 @@ function isEventSoundEnabled(config, event) {
|
|
|
184
188
|
function isEventNotificationEnabled(config, event) {
|
|
185
189
|
return config.events[event].notification;
|
|
186
190
|
}
|
|
191
|
+
function isEventCommandEnabled(config, event) {
|
|
192
|
+
return config.events[event].command;
|
|
193
|
+
}
|
|
187
194
|
function getMessage(config, event) {
|
|
188
195
|
return config.messages[event];
|
|
189
196
|
}
|
|
@@ -262,6 +269,7 @@ function detectNotifySendCapabilities() {
|
|
|
262
269
|
function sendLinuxNotificationDirect(title, message, timeout, iconPath, grouping = true) {
|
|
263
270
|
return new Promise((resolve) => {
|
|
264
271
|
const args = [];
|
|
272
|
+
args.push("--app-name", "opencode");
|
|
265
273
|
if (iconPath) {
|
|
266
274
|
args.push("--icon", iconPath);
|
|
267
275
|
}
|
|
@@ -336,7 +344,8 @@ async function sendNotification(title, message, timeout, iconPath, notificationS
|
|
|
336
344
|
title,
|
|
337
345
|
message,
|
|
338
346
|
timeout,
|
|
339
|
-
icon: iconPath
|
|
347
|
+
icon: iconPath,
|
|
348
|
+
"app-name": "opencode"
|
|
340
349
|
};
|
|
341
350
|
platformNotifier.notify(notificationOptions, () => {
|
|
342
351
|
resolve();
|
|
@@ -420,7 +429,7 @@ async function playOnLinux(soundPath, volume) {
|
|
|
420
429
|
const players = [
|
|
421
430
|
{ command: "paplay", args: [`--volume=${pulseVolume}`, soundPath] },
|
|
422
431
|
{ command: "aplay", args: [soundPath] },
|
|
423
|
-
{ command: "mpv", args: ["--no-video", "--no-terminal", `--volume=${percentVolume}`, soundPath] },
|
|
432
|
+
{ command: "mpv", args: ["--no-video", "--no-terminal", "--script-opts=autoload-disabled=yes", `--volume=${percentVolume}`, soundPath] },
|
|
424
433
|
{ command: "ffplay", args: ["-nodisp", "-autoexit", "-loglevel", "quiet", "-volume", `${percentVolume}`, soundPath] }
|
|
425
434
|
];
|
|
426
435
|
for (const player of players) {
|
|
@@ -630,7 +639,7 @@ function loadTurnCount() {
|
|
|
630
639
|
}
|
|
631
640
|
function saveTurnCount(count) {
|
|
632
641
|
try {
|
|
633
|
-
|
|
642
|
+
writeFileSync(getStatePath(), JSON.stringify({ turn: count }));
|
|
634
643
|
} catch {}
|
|
635
644
|
}
|
|
636
645
|
function incrementTurnCount() {
|
|
@@ -697,7 +706,7 @@ async function handleEvent(config, eventType, projectName, elapsedSeconds, sessi
|
|
|
697
706
|
promises.push(playSound(eventType, customSoundPath, soundVolume));
|
|
698
707
|
}
|
|
699
708
|
const minDuration = config.command?.minDuration;
|
|
700
|
-
const shouldSkipCommand = typeof minDuration === "number" && Number.isFinite(minDuration) && minDuration > 0 && typeof elapsedSeconds === "number" && Number.isFinite(elapsedSeconds) && elapsedSeconds < minDuration;
|
|
709
|
+
const shouldSkipCommand = !isEventCommandEnabled(config, eventType) || typeof minDuration === "number" && Number.isFinite(minDuration) && minDuration > 0 && typeof elapsedSeconds === "number" && Number.isFinite(elapsedSeconds) && elapsedSeconds < minDuration;
|
|
701
710
|
if (!shouldSkipCommand) {
|
|
702
711
|
runCommand2(config, eventType, message, sessionTitle, projectName, timestamp, turn);
|
|
703
712
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mohak34/opencode-notifier",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.33",
|
|
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",
|