@mohak34/opencode-notifier 0.1.20 → 0.1.21
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 +24 -12
- package/dist/index.js +61 -29
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -52,6 +52,7 @@ Create `~/.config/opencode/opencode-notifier.json` with the defaults:
|
|
|
52
52
|
"notification": true,
|
|
53
53
|
"timeout": 5,
|
|
54
54
|
"showProjectName": true,
|
|
55
|
+
"showSessionTitle": false,
|
|
55
56
|
"showIcon": true,
|
|
56
57
|
"notificationSystem": "osascript",
|
|
57
58
|
"command": {
|
|
@@ -68,11 +69,11 @@ Create `~/.config/opencode/opencode-notifier.json` with the defaults:
|
|
|
68
69
|
"question": { "sound": true, "notification": true }
|
|
69
70
|
},
|
|
70
71
|
"messages": {
|
|
71
|
-
"permission": "Session needs permission",
|
|
72
|
-
"complete": "Session has finished",
|
|
73
|
-
"subagent_complete": "Subagent task completed",
|
|
74
|
-
"error": "Session encountered an error",
|
|
75
|
-
"question": "Session has a question"
|
|
72
|
+
"permission": "Session needs permission: {sessionTitle}",
|
|
73
|
+
"complete": "Session has finished: {sessionTitle}",
|
|
74
|
+
"subagent_complete": "Subagent task completed: {sessionTitle}",
|
|
75
|
+
"error": "Session encountered an error: {sessionTitle}",
|
|
76
|
+
"question": "Session has a question: {sessionTitle}"
|
|
76
77
|
},
|
|
77
78
|
"sounds": {
|
|
78
79
|
"permission": null,
|
|
@@ -101,6 +102,7 @@ Create `~/.config/opencode/opencode-notifier.json` with the defaults:
|
|
|
101
102
|
"notification": true,
|
|
102
103
|
"timeout": 5,
|
|
103
104
|
"showProjectName": true,
|
|
105
|
+
"showSessionTitle": false,
|
|
104
106
|
"showIcon": true,
|
|
105
107
|
"notificationSystem": "osascript"
|
|
106
108
|
}
|
|
@@ -110,6 +112,7 @@ Create `~/.config/opencode/opencode-notifier.json` with the defaults:
|
|
|
110
112
|
- `notification` - Turn notifications on/off (default: true)
|
|
111
113
|
- `timeout` - How long notifications show in seconds, Linux only (default: 5)
|
|
112
114
|
- `showProjectName` - Show folder name in notification title (default: true)
|
|
115
|
+
- `showSessionTitle` - Include the session title in notification messages via `{sessionTitle}` placeholder (default: true)
|
|
113
116
|
- `showIcon` - Show OpenCode icon, Windows/Linux only (default: true)
|
|
114
117
|
- `notificationSystem` - macOS only: `"osascript"` or `"node-notifier"` (default: "osascript")
|
|
115
118
|
|
|
@@ -146,15 +149,24 @@ Customize the notification text:
|
|
|
146
149
|
```json
|
|
147
150
|
{
|
|
148
151
|
"messages": {
|
|
149
|
-
"permission": "Session needs permission",
|
|
150
|
-
"complete": "Session has finished",
|
|
151
|
-
"subagent_complete": "Subagent task completed",
|
|
152
|
-
"error": "Session encountered an error",
|
|
153
|
-
"question": "Session has a question"
|
|
152
|
+
"permission": "Session needs permission: {sessionTitle}",
|
|
153
|
+
"complete": "Session has finished: {sessionTitle}",
|
|
154
|
+
"subagent_complete": "Subagent task completed: {sessionTitle}",
|
|
155
|
+
"error": "Session encountered an error: {sessionTitle}",
|
|
156
|
+
"question": "Session has a question: {sessionTitle}"
|
|
154
157
|
}
|
|
155
158
|
}
|
|
156
159
|
```
|
|
157
160
|
|
|
161
|
+
Messages support placeholder tokens that get replaced with actual values:
|
|
162
|
+
|
|
163
|
+
- `{sessionTitle}` - The title/summary of the current session (e.g. "Fix login bug")
|
|
164
|
+
- `{projectName}` - The project folder name
|
|
165
|
+
|
|
166
|
+
When `showSessionTitle` is `false`, `{sessionTitle}` is replaced with an empty string. Any trailing separators (`: `, ` - `, ` | `) are automatically cleaned up when a placeholder resolves to empty.
|
|
167
|
+
|
|
168
|
+
To disable session titles in messages without changing `showSessionTitle`, just remove the `{sessionTitle}` placeholder from your custom messages.
|
|
169
|
+
|
|
158
170
|
### Sounds
|
|
159
171
|
|
|
160
172
|
Use your own sound files:
|
|
@@ -198,7 +210,7 @@ Set per-event volume from `0` to `1`:
|
|
|
198
210
|
|
|
199
211
|
### Custom commands
|
|
200
212
|
|
|
201
|
-
Run your own script when something happens. Use `{event}` and `{
|
|
213
|
+
Run your own script when something happens. Use `{event}`, `{message}`, and `{sessionTitle}` as placeholders:
|
|
202
214
|
|
|
203
215
|
```json
|
|
204
216
|
{
|
|
@@ -213,7 +225,7 @@ Run your own script when something happens. Use `{event}` and `{message}` as pla
|
|
|
213
225
|
|
|
214
226
|
- `enabled` - Turn command on/off
|
|
215
227
|
- `path` - Path to your script/executable
|
|
216
|
-
- `args` - Arguments to pass, can use `{event}` and `{
|
|
228
|
+
- `args` - Arguments to pass, can use `{event}`, `{message}`, and `{sessionTitle}` tokens
|
|
217
229
|
- `minDuration` - Skip if response was quick, avoids spam (seconds)
|
|
218
230
|
|
|
219
231
|
#### Example: Log events to a file
|
package/dist/index.js
CHANGED
|
@@ -3743,6 +3743,7 @@ var DEFAULT_CONFIG = {
|
|
|
3743
3743
|
notification: true,
|
|
3744
3744
|
timeout: 5,
|
|
3745
3745
|
showProjectName: true,
|
|
3746
|
+
showSessionTitle: false,
|
|
3746
3747
|
showIcon: true,
|
|
3747
3748
|
notificationSystem: "osascript",
|
|
3748
3749
|
command: {
|
|
@@ -3758,11 +3759,11 @@ var DEFAULT_CONFIG = {
|
|
|
3758
3759
|
question: { ...DEFAULT_EVENT_CONFIG }
|
|
3759
3760
|
},
|
|
3760
3761
|
messages: {
|
|
3761
|
-
permission: "Session needs permission",
|
|
3762
|
-
complete: "Session has finished",
|
|
3763
|
-
subagent_complete: "Subagent task completed",
|
|
3764
|
-
error: "Session encountered an error",
|
|
3765
|
-
question: "Session has a question"
|
|
3762
|
+
permission: "Session needs permission: {sessionTitle}",
|
|
3763
|
+
complete: "Session has finished: {sessionTitle}",
|
|
3764
|
+
subagent_complete: "Subagent task completed: {sessionTitle}",
|
|
3765
|
+
error: "Session encountered an error: {sessionTitle}",
|
|
3766
|
+
question: "Session has a question: {sessionTitle}"
|
|
3766
3767
|
},
|
|
3767
3768
|
sounds: {
|
|
3768
3769
|
permission: null,
|
|
@@ -3831,6 +3832,7 @@ function loadConfig() {
|
|
|
3831
3832
|
notification: globalNotification,
|
|
3832
3833
|
timeout: typeof userConfig.timeout === "number" && userConfig.timeout > 0 ? userConfig.timeout : DEFAULT_CONFIG.timeout,
|
|
3833
3834
|
showProjectName: userConfig.showProjectName ?? DEFAULT_CONFIG.showProjectName,
|
|
3835
|
+
showSessionTitle: userConfig.showSessionTitle ?? DEFAULT_CONFIG.showSessionTitle,
|
|
3834
3836
|
showIcon: userConfig.showIcon ?? DEFAULT_CONFIG.showIcon,
|
|
3835
3837
|
notificationSystem: userConfig.notificationSystem === "node-notifier" ? "node-notifier" : "osascript",
|
|
3836
3838
|
command: {
|
|
@@ -3901,6 +3903,16 @@ function getIconPath(config) {
|
|
|
3901
3903
|
} catch {}
|
|
3902
3904
|
return;
|
|
3903
3905
|
}
|
|
3906
|
+
function interpolateMessage(message, context) {
|
|
3907
|
+
let result = message;
|
|
3908
|
+
const sessionTitle = context.sessionTitle || "";
|
|
3909
|
+
result = result.replaceAll("{sessionTitle}", sessionTitle);
|
|
3910
|
+
const projectName = context.projectName || "";
|
|
3911
|
+
result = result.replaceAll("{projectName}", projectName);
|
|
3912
|
+
result = result.replace(/\s*[:\-|]\s*$/, "").trim();
|
|
3913
|
+
result = result.replace(/\s{2,}/g, " ");
|
|
3914
|
+
return result;
|
|
3915
|
+
}
|
|
3904
3916
|
|
|
3905
3917
|
// src/notify.ts
|
|
3906
3918
|
var import_node_notifier = __toESM(require_node_notifier(), 1);
|
|
@@ -4086,15 +4098,18 @@ async function playSound(event, customPath, volume) {
|
|
|
4086
4098
|
|
|
4087
4099
|
// src/command.ts
|
|
4088
4100
|
import { spawn as spawn2 } from "child_process";
|
|
4089
|
-
function substituteTokens(value, event, message) {
|
|
4090
|
-
|
|
4101
|
+
function substituteTokens(value, event, message, sessionTitle, projectName) {
|
|
4102
|
+
let result = value.replaceAll("{event}", event).replaceAll("{message}", message);
|
|
4103
|
+
result = result.replaceAll("{sessionTitle}", sessionTitle || "");
|
|
4104
|
+
result = result.replaceAll("{projectName}", projectName || "");
|
|
4105
|
+
return result;
|
|
4091
4106
|
}
|
|
4092
|
-
function runCommand2(config, event, message) {
|
|
4107
|
+
function runCommand2(config, event, message, sessionTitle, projectName) {
|
|
4093
4108
|
if (!config.command.enabled || !config.command.path) {
|
|
4094
4109
|
return;
|
|
4095
4110
|
}
|
|
4096
|
-
const args = (config.command.args ?? []).map((arg) => substituteTokens(arg, event, message));
|
|
4097
|
-
const command = substituteTokens(config.command.path, event, message);
|
|
4111
|
+
const args = (config.command.args ?? []).map((arg) => substituteTokens(arg, event, message, sessionTitle, projectName));
|
|
4112
|
+
const command = substituteTokens(config.command.path, event, message, sessionTitle, projectName);
|
|
4098
4113
|
const proc = spawn2(command, args, {
|
|
4099
4114
|
stdio: "ignore",
|
|
4100
4115
|
detached: true
|
|
@@ -4110,9 +4125,13 @@ function getNotificationTitle(config, projectName) {
|
|
|
4110
4125
|
}
|
|
4111
4126
|
return "OpenCode";
|
|
4112
4127
|
}
|
|
4113
|
-
async function handleEvent(config, eventType, projectName, elapsedSeconds) {
|
|
4128
|
+
async function handleEvent(config, eventType, projectName, elapsedSeconds, sessionTitle) {
|
|
4114
4129
|
const promises = [];
|
|
4115
|
-
const
|
|
4130
|
+
const rawMessage = getMessage(config, eventType);
|
|
4131
|
+
const message = interpolateMessage(rawMessage, {
|
|
4132
|
+
sessionTitle: config.showSessionTitle ? sessionTitle : null,
|
|
4133
|
+
projectName
|
|
4134
|
+
});
|
|
4116
4135
|
if (isEventNotificationEnabled(config, eventType)) {
|
|
4117
4136
|
const title = getNotificationTitle(config, projectName);
|
|
4118
4137
|
const iconPath = getIconPath(config);
|
|
@@ -4126,7 +4145,7 @@ async function handleEvent(config, eventType, projectName, elapsedSeconds) {
|
|
|
4126
4145
|
const minDuration = config.command?.minDuration;
|
|
4127
4146
|
const shouldSkipCommand = typeof minDuration === "number" && Number.isFinite(minDuration) && minDuration > 0 && typeof elapsedSeconds === "number" && Number.isFinite(elapsedSeconds) && elapsedSeconds < minDuration;
|
|
4128
4147
|
if (!shouldSkipCommand) {
|
|
4129
|
-
runCommand2(config, eventType, message);
|
|
4148
|
+
runCommand2(config, eventType, message, sessionTitle, projectName);
|
|
4130
4149
|
}
|
|
4131
4150
|
await Promise.allSettled(promises);
|
|
4132
4151
|
}
|
|
@@ -4156,26 +4175,33 @@ async function getElapsedSinceLastPrompt(client, sessionID) {
|
|
|
4156
4175
|
} catch {}
|
|
4157
4176
|
return null;
|
|
4158
4177
|
}
|
|
4159
|
-
async function
|
|
4178
|
+
async function getSessionInfo(client, sessionID) {
|
|
4160
4179
|
try {
|
|
4161
4180
|
const response = await client.session.get({ path: { id: sessionID } });
|
|
4162
|
-
|
|
4163
|
-
|
|
4181
|
+
return {
|
|
4182
|
+
isChild: !!response.data?.parentID,
|
|
4183
|
+
title: response.data?.title ?? null
|
|
4184
|
+
};
|
|
4164
4185
|
} catch {
|
|
4165
|
-
return false;
|
|
4186
|
+
return { isChild: false, title: null };
|
|
4166
4187
|
}
|
|
4167
4188
|
}
|
|
4168
|
-
async function handleEventWithElapsedTime(client, config, eventType, projectName, event) {
|
|
4169
|
-
const
|
|
4170
|
-
const shouldLookupElapsed = !!config.command?.enabled && typeof config.command?.path === "string" && config.command.path.length > 0 && typeof minDuration === "number" && Number.isFinite(minDuration) && minDuration > 0;
|
|
4189
|
+
async function handleEventWithElapsedTime(client, config, eventType, projectName, event, preloadedSessionTitle) {
|
|
4190
|
+
const sessionID = getSessionIDFromEvent(event);
|
|
4171
4191
|
let elapsedSeconds = null;
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4192
|
+
let sessionTitle = preloadedSessionTitle ?? null;
|
|
4193
|
+
if (sessionID) {
|
|
4194
|
+
const minDuration = config.command?.minDuration;
|
|
4195
|
+
const shouldLookupElapsed = !!config.command?.enabled && typeof config.command?.path === "string" && config.command.path.length > 0 && typeof minDuration === "number" && Number.isFinite(minDuration) && minDuration > 0;
|
|
4196
|
+
if (shouldLookupElapsed) {
|
|
4175
4197
|
elapsedSeconds = await getElapsedSinceLastPrompt(client, sessionID);
|
|
4176
4198
|
}
|
|
4199
|
+
if (!sessionTitle && config.showSessionTitle) {
|
|
4200
|
+
const info = await getSessionInfo(client, sessionID);
|
|
4201
|
+
sessionTitle = info.title;
|
|
4202
|
+
}
|
|
4177
4203
|
}
|
|
4178
|
-
await handleEvent(config, eventType, projectName, elapsedSeconds);
|
|
4204
|
+
await handleEvent(config, eventType, projectName, elapsedSeconds, sessionTitle);
|
|
4179
4205
|
}
|
|
4180
4206
|
var NotifierPlugin = async ({ client, directory }) => {
|
|
4181
4207
|
const config = loadConfig();
|
|
@@ -4191,18 +4217,24 @@ var NotifierPlugin = async ({ client, directory }) => {
|
|
|
4191
4217
|
if (event.type === "session.idle") {
|
|
4192
4218
|
const sessionID = getSessionIDFromEvent(event);
|
|
4193
4219
|
if (sessionID) {
|
|
4194
|
-
const
|
|
4195
|
-
if (!isChild) {
|
|
4196
|
-
await handleEventWithElapsedTime(client, config, "complete", projectName, event);
|
|
4220
|
+
const sessionInfo = await getSessionInfo(client, sessionID);
|
|
4221
|
+
if (!sessionInfo.isChild) {
|
|
4222
|
+
await handleEventWithElapsedTime(client, config, "complete", projectName, event, sessionInfo.title);
|
|
4197
4223
|
} else {
|
|
4198
|
-
await handleEventWithElapsedTime(client, config, "subagent_complete", projectName, event);
|
|
4224
|
+
await handleEventWithElapsedTime(client, config, "subagent_complete", projectName, event, sessionInfo.title);
|
|
4199
4225
|
}
|
|
4200
4226
|
} else {
|
|
4201
4227
|
await handleEventWithElapsedTime(client, config, "complete", projectName, event);
|
|
4202
4228
|
}
|
|
4203
4229
|
}
|
|
4204
4230
|
if (event.type === "session.error") {
|
|
4205
|
-
|
|
4231
|
+
const sessionID = getSessionIDFromEvent(event);
|
|
4232
|
+
let sessionTitle = null;
|
|
4233
|
+
if (sessionID && config.showSessionTitle) {
|
|
4234
|
+
const info = await getSessionInfo(client, sessionID);
|
|
4235
|
+
sessionTitle = info.title;
|
|
4236
|
+
}
|
|
4237
|
+
await handleEventWithElapsedTime(client, config, "error", projectName, event, sessionTitle);
|
|
4206
4238
|
}
|
|
4207
4239
|
},
|
|
4208
4240
|
"permission.ask": async () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mohak34/opencode-notifier",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.21",
|
|
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",
|