@mohak34/opencode-notifier 0.1.21-beta.0 → 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.
Files changed (3) hide show
  1. package/README.md +29 -2
  2. package/dist/index.js +64 -11
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -52,7 +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": true,
55
+ "showSessionTitle": false,
56
56
  "showIcon": true,
57
57
  "notificationSystem": "osascript",
58
58
  "command": {
@@ -81,6 +81,13 @@ Create `~/.config/opencode/opencode-notifier.json` with the defaults:
81
81
  "subagent_complete": null,
82
82
  "error": null,
83
83
  "question": null
84
+ },
85
+ "volumes": {
86
+ "permission": 1,
87
+ "complete": 1,
88
+ "subagent_complete": 1,
89
+ "error": 1,
90
+ "question": 1
84
91
  }
85
92
  }
86
93
  ```
@@ -95,7 +102,7 @@ Create `~/.config/opencode/opencode-notifier.json` with the defaults:
95
102
  "notification": true,
96
103
  "timeout": 5,
97
104
  "showProjectName": true,
98
- "showSessionTitle": true,
105
+ "showSessionTitle": false,
99
106
  "showIcon": true,
100
107
  "notificationSystem": "osascript"
101
108
  }
@@ -181,6 +188,26 @@ Platform notes:
181
188
  - Windows: Only .wav files work
182
189
  - If file doesn't exist, falls back to bundled sound
183
190
 
191
+ ### Volumes
192
+
193
+ Set per-event volume from `0` to `1`:
194
+
195
+ ```json
196
+ {
197
+ "volumes": {
198
+ "permission": 0.6,
199
+ "complete": 0.3,
200
+ "subagent_complete": 0.15,
201
+ "error": 1,
202
+ "question": 0.7
203
+ }
204
+ }
205
+ ```
206
+
207
+ - `0` = mute, `1` = full volume
208
+ - Values outside `0..1` are clamped automatically
209
+ - On Windows, playback still works but custom volume may not be honored by the default player
210
+
184
211
  ### Custom commands
185
212
 
186
213
  Run your own script when something happens. Use `{event}`, `{message}`, and `{sessionTitle}` as placeholders:
package/dist/index.js CHANGED
@@ -3743,7 +3743,7 @@ var DEFAULT_CONFIG = {
3743
3743
  notification: true,
3744
3744
  timeout: 5,
3745
3745
  showProjectName: true,
3746
- showSessionTitle: true,
3746
+ showSessionTitle: false,
3747
3747
  showIcon: true,
3748
3748
  notificationSystem: "osascript",
3749
3749
  command: {
@@ -3771,6 +3771,13 @@ var DEFAULT_CONFIG = {
3771
3771
  subagent_complete: null,
3772
3772
  error: null,
3773
3773
  question: null
3774
+ },
3775
+ volumes: {
3776
+ permission: 1,
3777
+ complete: 1,
3778
+ subagent_complete: 1,
3779
+ error: 1,
3780
+ question: 1
3774
3781
  }
3775
3782
  };
3776
3783
  function getConfigPath() {
@@ -3791,6 +3798,18 @@ function parseEventConfig(userEvent, defaultConfig) {
3791
3798
  notification: userEvent.notification ?? defaultConfig.notification
3792
3799
  };
3793
3800
  }
3801
+ function parseVolume(value, defaultVolume) {
3802
+ if (typeof value !== "number" || !Number.isFinite(value)) {
3803
+ return defaultVolume;
3804
+ }
3805
+ if (value < 0) {
3806
+ return 0;
3807
+ }
3808
+ if (value > 1) {
3809
+ return 1;
3810
+ }
3811
+ return value;
3812
+ }
3794
3813
  function loadConfig() {
3795
3814
  const configPath = getConfigPath();
3796
3815
  if (!existsSync(configPath)) {
@@ -3842,6 +3861,13 @@ function loadConfig() {
3842
3861
  subagent_complete: userConfig.sounds?.subagent_complete ?? DEFAULT_CONFIG.sounds.subagent_complete,
3843
3862
  error: userConfig.sounds?.error ?? DEFAULT_CONFIG.sounds.error,
3844
3863
  question: userConfig.sounds?.question ?? DEFAULT_CONFIG.sounds.question
3864
+ },
3865
+ volumes: {
3866
+ permission: parseVolume(userConfig.volumes?.permission, DEFAULT_CONFIG.volumes.permission),
3867
+ complete: parseVolume(userConfig.volumes?.complete, DEFAULT_CONFIG.volumes.complete),
3868
+ subagent_complete: parseVolume(userConfig.volumes?.subagent_complete, DEFAULT_CONFIG.volumes.subagent_complete),
3869
+ error: parseVolume(userConfig.volumes?.error, DEFAULT_CONFIG.volumes.error),
3870
+ question: parseVolume(userConfig.volumes?.question, DEFAULT_CONFIG.volumes.question)
3845
3871
  }
3846
3872
  };
3847
3873
  } catch {
@@ -3860,6 +3886,9 @@ function getMessage(config, event) {
3860
3886
  function getSoundPath(config, event) {
3861
3887
  return config.sounds[event];
3862
3888
  }
3889
+ function getSoundVolume(config, event) {
3890
+ return config.volumes[event];
3891
+ }
3863
3892
  function getIconPath(config) {
3864
3893
  if (!config.showIcon) {
3865
3894
  return;
@@ -3951,6 +3980,8 @@ import { existsSync as existsSync2 } from "fs";
3951
3980
  import { spawn } from "child_process";
3952
3981
  var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
3953
3982
  var DEBOUNCE_MS2 = 1000;
3983
+ var FULL_VOLUME_PERCENT = 100;
3984
+ var FULL_VOLUME_PULSE = 65536;
3954
3985
  var lastSoundTime = {};
3955
3986
  function getBundledSoundPath(event) {
3956
3987
  const soundFilename = `${event}.wav`;
@@ -3993,12 +4024,32 @@ async function runCommand(command, args) {
3993
4024
  });
3994
4025
  });
3995
4026
  }
3996
- async function playOnLinux(soundPath) {
4027
+ function normalizeVolume(volume) {
4028
+ if (!Number.isFinite(volume)) {
4029
+ return 1;
4030
+ }
4031
+ if (volume < 0) {
4032
+ return 0;
4033
+ }
4034
+ if (volume > 1) {
4035
+ return 1;
4036
+ }
4037
+ return volume;
4038
+ }
4039
+ function toPercentVolume(volume) {
4040
+ return Math.round(volume * FULL_VOLUME_PERCENT);
4041
+ }
4042
+ function toPulseVolume(volume) {
4043
+ return Math.round(volume * FULL_VOLUME_PULSE);
4044
+ }
4045
+ async function playOnLinux(soundPath, volume) {
4046
+ const percentVolume = toPercentVolume(volume);
4047
+ const pulseVolume = toPulseVolume(volume);
3997
4048
  const players = [
3998
- { command: "paplay", args: [soundPath] },
4049
+ { command: "paplay", args: [`--volume=${pulseVolume}`, soundPath] },
3999
4050
  { command: "aplay", args: [soundPath] },
4000
- { command: "mpv", args: ["--no-video", "--no-terminal", soundPath] },
4001
- { command: "ffplay", args: ["-nodisp", "-autoexit", "-loglevel", "quiet", soundPath] }
4051
+ { command: "mpv", args: ["--no-video", "--no-terminal", `--volume=${percentVolume}`, soundPath] },
4052
+ { command: "ffplay", args: ["-nodisp", "-autoexit", "-loglevel", "quiet", "-volume", `${percentVolume}`, soundPath] }
4002
4053
  ];
4003
4054
  for (const player of players) {
4004
4055
  try {
@@ -4009,20 +4060,21 @@ async function playOnLinux(soundPath) {
4009
4060
  }
4010
4061
  }
4011
4062
  }
4012
- async function playOnMac(soundPath) {
4013
- await runCommand("afplay", [soundPath]);
4063
+ async function playOnMac(soundPath, volume) {
4064
+ await runCommand("afplay", ["-v", `${volume}`, soundPath]);
4014
4065
  }
4015
4066
  async function playOnWindows(soundPath) {
4016
4067
  const script = `& { (New-Object Media.SoundPlayer $args[0]).PlaySync() }`;
4017
4068
  await runCommand("powershell", ["-c", script, soundPath]);
4018
4069
  }
4019
- async function playSound(event, customPath) {
4070
+ async function playSound(event, customPath, volume) {
4020
4071
  const now = Date.now();
4021
4072
  if (lastSoundTime[event] && now - lastSoundTime[event] < DEBOUNCE_MS2) {
4022
4073
  return;
4023
4074
  }
4024
4075
  lastSoundTime[event] = now;
4025
4076
  const soundPath = getSoundFilePath(event, customPath);
4077
+ const normalizedVolume = normalizeVolume(volume);
4026
4078
  if (!soundPath) {
4027
4079
  return;
4028
4080
  }
@@ -4030,10 +4082,10 @@ async function playSound(event, customPath) {
4030
4082
  try {
4031
4083
  switch (os2) {
4032
4084
  case "darwin":
4033
- await playOnMac(soundPath);
4085
+ await playOnMac(soundPath, normalizedVolume);
4034
4086
  break;
4035
4087
  case "linux":
4036
- await playOnLinux(soundPath);
4088
+ await playOnLinux(soundPath, normalizedVolume);
4037
4089
  break;
4038
4090
  case "win32":
4039
4091
  await playOnWindows(soundPath);
@@ -4087,7 +4139,8 @@ async function handleEvent(config, eventType, projectName, elapsedSeconds, sessi
4087
4139
  }
4088
4140
  if (isEventSoundEnabled(config, eventType)) {
4089
4141
  const customSoundPath = getSoundPath(config, eventType);
4090
- promises.push(playSound(eventType, customSoundPath));
4142
+ const soundVolume = getSoundVolume(config, eventType);
4143
+ promises.push(playSound(eventType, customSoundPath, soundVolume));
4091
4144
  }
4092
4145
  const minDuration = config.command?.minDuration;
4093
4146
  const shouldSkipCommand = typeof minDuration === "number" && Number.isFinite(minDuration) && minDuration > 0 && typeof elapsedSeconds === "number" && Number.isFinite(elapsedSeconds) && elapsedSeconds < minDuration;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mohak34/opencode-notifier",
3
- "version": "0.1.21-beta.0",
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",