@mohak34/opencode-notifier 0.1.16 → 0.1.18-beta.0

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 CHANGED
@@ -92,7 +92,6 @@ To customize the plugin, create `~/.config/opencode/opencode-notifier.json`:
92
92
  "notification": true,
93
93
  "timeout": 5,
94
94
  "showProjectName": true,
95
- "suppressWhenFocused": false,
96
95
  "command": {
97
96
  "enabled": false,
98
97
  "path": "/path/to/command",
@@ -131,7 +130,6 @@ To customize the plugin, create `~/.config/opencode/opencode-notifier.json`:
131
130
  | `notification` | boolean | `true` | Global toggle for all notifications |
132
131
  | `timeout` | number | `5` | Notification duration in seconds (Linux only) |
133
132
  | `showProjectName` | boolean | `true` | Show project folder name in notification title |
134
- | `suppressWhenFocused` | boolean | `false` | Suppress notifications when terminal is focused |
135
133
  | `command` | object | — | Command execution settings (enabled/path/args/minDuration) |
136
134
 
137
135
  ### Events
@@ -166,14 +164,6 @@ Or use a boolean to toggle both:
166
164
 
167
165
  Note: `complete` fires for primary (main) session completion, while `subagent_complete` fires for subagent completion. `subagent_complete` defaults to disabled (both sound and notification are false).
168
166
 
169
- ### Suppress when focused
170
-
171
- When `suppressWhenFocused` is enabled, notifications are skipped if your terminal is the frontmost window.
172
-
173
- - macOS: works out of the box
174
- - Windows: uses PowerShell to detect the foreground app
175
- - Linux: requires one of `xdotool`, `wmctrl`, or `xprop` (install via your package manager). If none are installed, suppression is skipped.
176
-
177
167
  ### Messages
178
168
 
179
169
  Customize notification text:
package/dist/index.js CHANGED
@@ -18,231 +18,6 @@ var __toESM = (mod, isNodeMode, target) => {
18
18
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
19
19
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
20
20
 
21
- // node_modules/detect-terminal/dist/index.js
22
- var require_dist = __commonJS((exports, module) => {
23
- var __create2 = Object.create;
24
- var __defProp2 = Object.defineProperty;
25
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
26
- var __getOwnPropNames2 = Object.getOwnPropertyNames;
27
- var __getProtoOf2 = Object.getPrototypeOf;
28
- var __hasOwnProp2 = Object.prototype.hasOwnProperty;
29
- var __name = (target, value) => __defProp2(target, "name", { value, configurable: true });
30
- var __export = (target, all) => {
31
- for (var name in all)
32
- __defProp2(target, name, { get: all[name], enumerable: true });
33
- };
34
- var __copyProps = (to, from, except, desc) => {
35
- if (from && typeof from === "object" || typeof from === "function") {
36
- for (let key of __getOwnPropNames2(from))
37
- if (!__hasOwnProp2.call(to, key) && key !== except)
38
- __defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
39
- }
40
- return to;
41
- };
42
- var __toESM2 = (mod, isNodeMode, target) => (target = mod != null ? __create2(__getProtoOf2(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp2(target, "default", { value: mod, enumerable: true }) : target, mod));
43
- var __toCommonJS = (mod) => __copyProps(__defProp2({}, "__esModule", { value: true }), mod);
44
- var detect_terminal_exports = {};
45
- __export(detect_terminal_exports, {
46
- default: () => detect_terminal_default,
47
- detectFromEnv: () => detectFromEnv,
48
- detectFromProcessTitle: () => detectFromProcessTitle,
49
- detectFromShell: () => detectFromShell,
50
- detectTerminal: () => detectTerminal
51
- });
52
- module.exports = __toCommonJS(detect_terminal_exports);
53
- var import_node_child_process = __require("child_process");
54
- var import_node_path = __toESM2(__require("path"));
55
- var detectFromEnv = /* @__PURE__ */ __name(() => {
56
- const platform = process.platform;
57
- if (platform === "android" && process.env.TERMUX_VERSION) {
58
- return "termux";
59
- }
60
- let termProgram = process.env.TERM_PROGRAM?.trim()?.toLowerCase();
61
- if (platform === "darwin" && termProgram) {
62
- termProgram = import_node_path.default.parse(termProgram).name;
63
- }
64
- if (termProgram) {
65
- switch (termProgram) {
66
- case "apple_terminal":
67
- return "terminal";
68
- case "eterm":
69
- return "eterm";
70
- case "gnome-terminal":
71
- return "gnome_terminal";
72
- case "gnome-terminal-server":
73
- return "gnome_terminal";
74
- case "hyper":
75
- return "hyper";
76
- case "iterm.app":
77
- return "iterm";
78
- case "iterm":
79
- return "iterm";
80
- case "iterm2":
81
- return "iterm";
82
- case "kitty":
83
- return "kitty";
84
- case "konsole":
85
- return "konsole";
86
- case "mate-terminal":
87
- return "mate_terminal";
88
- case "powershell":
89
- return "powershell";
90
- case "putty":
91
- return "putty";
92
- case "qterminal":
93
- return "qterminal";
94
- case "rxvt":
95
- return "rxvt";
96
- case "terminal.app":
97
- return "terminal_app";
98
- case "terminal":
99
- return "terminal";
100
- case "terminator":
101
- return "terminator";
102
- case "termux":
103
- return "termux";
104
- case "vscode":
105
- return "vscode";
106
- case "warp":
107
- return "warp";
108
- case "wezterm":
109
- return "wezterm";
110
- case "xfce4-terminal":
111
- return "xfce4_terminal";
112
- case "alacritty":
113
- return "alacritty";
114
- default:
115
- break;
116
- }
117
- return termProgram.replace(/[^a-z0-9]+/g, "_");
118
- }
119
- if (typeof process.env.VSCODE_PID !== "undefined" || typeof process.env.TERM_PROGRAM_VERSION !== "undefined" && /vscode/i.test(process.env.TERM_PROGRAM_VERSION)) {
120
- return "vscode";
121
- }
122
- const term = process.env.TERM?.trim()?.toLowerCase();
123
- if (term && term !== "unknown") {
124
- if (term === "xterm" || term === "xterm-256color") {
125
- if (process.env.VTE_VERSION) {
126
- const vteVersion = parseInt(process.env.VTE_VERSION, 10);
127
- if (vteVersion >= 3803) {
128
- return "gnome_terminal";
129
- }
130
- }
131
- for (const [key] of Object.entries(process.env)) {
132
- if (/konsole/i.test(key)) {
133
- return "konsole";
134
- }
135
- }
136
- if (platform === "darwin") {
137
- return "terminal";
138
- }
139
- return "xterm";
140
- }
141
- if (/screen/.test(term))
142
- return "screen";
143
- if (/tmux/.test(term))
144
- return "tmux";
145
- if (/rxvt/.test(term))
146
- return "rxvt";
147
- if (/vt100/.test(term))
148
- return "vt100";
149
- if (/linux/.test(term))
150
- return "linux_console";
151
- if (/alacritty/.test(term))
152
- return "alacritty";
153
- if (/dopamine/.test(term))
154
- return "dopamine";
155
- if (/kitty/.test(term))
156
- return "kitty";
157
- if (/ghostty/.test(term))
158
- return "ghostty";
159
- return term.replace(/[^a-z0-9]+/g, "_");
160
- }
161
- const colorTerm = process.env.COLORTERM?.trim()?.toLowerCase();
162
- if (colorTerm === "truecolor" || colorTerm === "24bit") {
163
- return "truecolor_terminal";
164
- }
165
- if (colorTerm) {
166
- return colorTerm.replace(/[^a-z0-9]+/g, "_");
167
- }
168
- return null;
169
- }, "detectFromEnv");
170
- var detectFromShell = /* @__PURE__ */ __name(() => {
171
- try {
172
- const isWindows = process.platform === "win32";
173
- if (isWindows) {
174
- if (process.env.WT_SESSION)
175
- return "windows_terminal";
176
- const shell = process.env.COMSPEC?.toLowerCase() || "";
177
- if (/powershell/i.test(shell))
178
- return "powershell";
179
- if (/pwsh/i.test(shell))
180
- return "powershell";
181
- if (/cmd\.exe/i.test(shell))
182
- return "cmd";
183
- if (/wt\.exe/i.test(shell))
184
- return "windows_terminal";
185
- if (/conhost\.exe/i.test(shell))
186
- return "conhost";
187
- return "windows_cmd";
188
- }
189
- const terminal = (0, import_node_child_process.execSync)("echo $TERM", {
190
- encoding: "utf8",
191
- timeout: 1000,
192
- stdio: ["ignore", "pipe", "ignore"]
193
- }).trim().toLowerCase();
194
- return terminal ? terminal.replace(/[^a-z0-9]+/g, "_") : null;
195
- } catch {
196
- return null;
197
- }
198
- }, "detectFromShell");
199
- var detectFromProcessTitle = /* @__PURE__ */ __name(() => {
200
- const processTitle = process.title?.toLowerCase() ?? "";
201
- if (/^alacritty/.test(processTitle))
202
- return "alacritty";
203
- if (/^kitty/.test(processTitle))
204
- return "kitty";
205
- if (/^wezterm/.test(processTitle))
206
- return "wezterm";
207
- if (/^hyper/.test(processTitle))
208
- return "hyper";
209
- if (/bash/.test(processTitle))
210
- return "bash";
211
- if (/zsh/.test(processTitle))
212
- return "zsh";
213
- if (/ksh/.test(processTitle))
214
- return "ksh";
215
- if (/fish/.test(processTitle))
216
- return "fish";
217
- if (/csh/.test(processTitle))
218
- return "csh";
219
- if (/tcsh/.test(processTitle))
220
- return "tcsh";
221
- if (/pwsh/.test(processTitle))
222
- return "powershell";
223
- if (/powershell/.test(processTitle))
224
- return "powershell";
225
- if (/cmd/.test(processTitle))
226
- return "cmd";
227
- if (/sh$/.test(processTitle))
228
- return "sh";
229
- if (/^node/.test(processTitle))
230
- return "node";
231
- return null;
232
- }, "detectFromProcessTitle");
233
- var detectTerminal = /* @__PURE__ */ __name(() => {
234
- let terminal = detectFromEnv();
235
- if (!terminal) {
236
- terminal = detectFromShell();
237
- }
238
- if (!terminal) {
239
- terminal = detectFromProcessTitle();
240
- }
241
- return terminal || "unknown";
242
- }, "detectTerminal");
243
- var detect_terminal_default = detectTerminal;
244
- });
245
-
246
21
  // node_modules/shellwords/lib/shellwords.js
247
22
  var require_shellwords = __commonJS((exports) => {
248
23
  (function() {
@@ -3727,7 +3502,7 @@ var require_version = __commonJS((exports) => {
3727
3502
  });
3728
3503
 
3729
3504
  // node_modules/uuid/dist/index.js
3730
- var require_dist2 = __commonJS((exports) => {
3505
+ var require_dist = __commonJS((exports) => {
3731
3506
  Object.defineProperty(exports, "__esModule", {
3732
3507
  value: true
3733
3508
  });
@@ -3807,7 +3582,7 @@ var require_toaster = __commonJS((exports, module) => {
3807
3582
  var utils = require_utils();
3808
3583
  var Balloon = require_balloon();
3809
3584
  var os = __require("os");
3810
- var { v4: uuid } = require_dist2();
3585
+ var { v4: uuid } = require_dist();
3811
3586
  var EventEmitter = __require("events").EventEmitter;
3812
3587
  var util = __require("util");
3813
3588
  var fallback;
@@ -3956,8 +3731,9 @@ import { basename } from "path";
3956
3731
 
3957
3732
  // src/config.ts
3958
3733
  import { readFileSync, existsSync } from "fs";
3959
- import { join } from "path";
3734
+ import { join, dirname } from "path";
3960
3735
  import { homedir } from "os";
3736
+ import { fileURLToPath } from "url";
3961
3737
  var DEFAULT_EVENT_CONFIG = {
3962
3738
  sound: true,
3963
3739
  notification: true
@@ -3967,7 +3743,6 @@ var DEFAULT_CONFIG = {
3967
3743
  notification: true,
3968
3744
  timeout: 5,
3969
3745
  showProjectName: true,
3970
- suppressWhenFocused: false,
3971
3746
  command: {
3972
3747
  enabled: false,
3973
3748
  path: "",
@@ -4035,7 +3810,6 @@ function loadConfig() {
4035
3810
  notification: globalNotification,
4036
3811
  timeout: typeof userConfig.timeout === "number" && userConfig.timeout > 0 ? userConfig.timeout : DEFAULT_CONFIG.timeout,
4037
3812
  showProjectName: userConfig.showProjectName ?? DEFAULT_CONFIG.showProjectName,
4038
- suppressWhenFocused: typeof userConfig.suppressWhenFocused === "boolean" ? userConfig.suppressWhenFocused : DEFAULT_CONFIG.suppressWhenFocused,
4039
3813
  command: {
4040
3814
  enabled: typeof userCommand.enabled === "boolean" ? userCommand.enabled : DEFAULT_CONFIG.command.enabled,
4041
3815
  path: typeof userCommand.path === "string" ? userCommand.path : DEFAULT_CONFIG.command.path,
@@ -4080,10 +3854,17 @@ function getMessage(config, event) {
4080
3854
  function getSoundPath(config, event) {
4081
3855
  return config.sounds[event];
4082
3856
  }
4083
-
4084
- // src/index.ts
4085
- var import_detect_terminal = __toESM(require_dist(), 1);
4086
- import { execFile } from "child_process";
3857
+ function getIconPath() {
3858
+ try {
3859
+ const __filename2 = fileURLToPath(import.meta.url);
3860
+ const __dirname2 = dirname(__filename2);
3861
+ const iconPath = join(__dirname2, "..", "..", "logos", "opencode-logo-dark.png");
3862
+ if (existsSync(iconPath)) {
3863
+ return iconPath;
3864
+ }
3865
+ } catch {}
3866
+ return;
3867
+ }
4087
3868
 
4088
3869
  // src/notify.ts
4089
3870
  var import_node_notifier = __toESM(require_node_notifier(), 1);
@@ -4102,7 +3883,7 @@ if (platform === "Linux" || platform.match(/BSD$/)) {
4102
3883
  platformNotifier = import_node_notifier.default;
4103
3884
  }
4104
3885
  var lastNotificationTime = {};
4105
- async function sendNotification(title, message, timeout) {
3886
+ async function sendNotification(title, message, timeout, iconPath) {
4106
3887
  const now = Date.now();
4107
3888
  if (lastNotificationTime[message] && now - lastNotificationTime[message] < DEBOUNCE_MS) {
4108
3889
  return;
@@ -4122,7 +3903,7 @@ async function sendNotification(title, message, timeout) {
4122
3903
  title,
4123
3904
  message,
4124
3905
  timeout,
4125
- icon: undefined
3906
+ icon: iconPath
4126
3907
  };
4127
3908
  platformNotifier.notify(notificationOptions, () => {
4128
3909
  resolve();
@@ -4132,11 +3913,11 @@ async function sendNotification(title, message, timeout) {
4132
3913
 
4133
3914
  // src/sound.ts
4134
3915
  import { platform as platform2 } from "os";
4135
- import { join as join2, dirname } from "path";
4136
- import { fileURLToPath } from "url";
3916
+ import { join as join2, dirname as dirname2 } from "path";
3917
+ import { fileURLToPath as fileURLToPath2 } from "url";
4137
3918
  import { existsSync as existsSync2 } from "fs";
4138
3919
  import { spawn } from "child_process";
4139
- var __dirname2 = dirname(fileURLToPath(import.meta.url));
3920
+ var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
4140
3921
  var DEBOUNCE_MS2 = 1000;
4141
3922
  var lastSoundTime = {};
4142
3923
  function getBundledSoundPath(event) {
@@ -4261,14 +4042,9 @@ async function handleEvent(config, eventType, projectName, elapsedSeconds) {
4261
4042
  const promises = [];
4262
4043
  const message = getMessage(config, eventType);
4263
4044
  if (isEventNotificationEnabled(config, eventType)) {
4264
- if (config.suppressWhenFocused) {
4265
- const shouldSuppress = await isTerminalFocused();
4266
- if (shouldSuppress) {
4267
- return;
4268
- }
4269
- }
4270
4045
  const title = getNotificationTitle(config, projectName);
4271
- promises.push(sendNotification(title, message, config.timeout));
4046
+ const iconPath = getIconPath();
4047
+ promises.push(sendNotification(title, message, config.timeout, iconPath));
4272
4048
  }
4273
4049
  if (isEventSoundEnabled(config, eventType)) {
4274
4050
  const customSoundPath = getSoundPath(config, eventType);
@@ -4281,99 +4057,6 @@ async function handleEvent(config, eventType, projectName, elapsedSeconds) {
4281
4057
  }
4282
4058
  await Promise.allSettled(promises);
4283
4059
  }
4284
- async function runExecCommand(command, args) {
4285
- return new Promise((resolve) => {
4286
- execFile(command, args, (error, stdout) => {
4287
- if (error) {
4288
- resolve(null);
4289
- return;
4290
- }
4291
- resolve(stdout.trim());
4292
- });
4293
- });
4294
- }
4295
- async function runOsascript(script) {
4296
- if (process.platform !== "darwin")
4297
- return null;
4298
- return runExecCommand("osascript", ["-e", script]);
4299
- }
4300
- async function getFrontmostAppMac() {
4301
- return runOsascript('tell application "System Events" to get name of first application process whose frontmost is true');
4302
- }
4303
- async function getFrontmostProcessWindows() {
4304
- if (process.platform !== "win32")
4305
- return null;
4306
- const script = [
4307
- 'Add-Type @"',
4308
- "using System;",
4309
- "using System.Runtime.InteropServices;",
4310
- "public class Win32 {",
4311
- ' [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();',
4312
- ' [DllImport("user32.dll")] public static extern int GetWindowThreadProcessId(IntPtr hWnd, out int processId);',
4313
- "}",
4314
- '"@',
4315
- "$hwnd = [Win32]::GetForegroundWindow()",
4316
- "$pid = 0",
4317
- "[Win32]::GetWindowThreadProcessId($hwnd, [ref]$pid) | Out-Null",
4318
- "(Get-Process -Id $pid).ProcessName"
4319
- ].join(`
4320
- `);
4321
- return runExecCommand("powershell", ["-NoProfile", "-Command", script]);
4322
- }
4323
- function extractQuotedStrings(input) {
4324
- const matches = input.match(/"([^"]+)"/g);
4325
- if (!matches)
4326
- return [];
4327
- return matches.map((value) => value.replace(/"/g, ""));
4328
- }
4329
- async function getActiveWindowClassLinux() {
4330
- if (process.platform !== "linux")
4331
- return null;
4332
- const xdotoolClass = await runExecCommand("xdotool", ["getactivewindow", "getwindowclassname"]);
4333
- if (xdotoolClass)
4334
- return xdotoolClass;
4335
- const activeWindow = await runExecCommand("xprop", ["-root", "_NET_ACTIVE_WINDOW"]);
4336
- if (!activeWindow)
4337
- return null;
4338
- const idMatch = activeWindow.match(/0x[0-9a-fA-F]+/);
4339
- if (!idMatch)
4340
- return null;
4341
- const wmClass = await runExecCommand("xprop", ["-id", idMatch[0], "WM_CLASS"]);
4342
- if (!wmClass)
4343
- return null;
4344
- const names = extractQuotedStrings(wmClass);
4345
- if (names.length === 0)
4346
- return null;
4347
- return names[names.length - 1];
4348
- }
4349
- function normalizeAppName(value) {
4350
- return value.toLowerCase().replace(/[^a-z0-9]/g, "");
4351
- }
4352
- function matchesTerminal(frontmost, terminal) {
4353
- const frontNormalized = normalizeAppName(frontmost);
4354
- const terminalNormalized = normalizeAppName(terminal);
4355
- if (!frontNormalized || !terminalNormalized)
4356
- return false;
4357
- return frontNormalized.includes(terminalNormalized) || terminalNormalized.includes(frontNormalized);
4358
- }
4359
- async function isTerminalFocused() {
4360
- const terminalName = import_detect_terminal.default({ preferOuter: true });
4361
- if (!terminalName)
4362
- return false;
4363
- if (process.platform === "darwin") {
4364
- const frontmost = await getFrontmostAppMac();
4365
- return frontmost ? matchesTerminal(frontmost, terminalName) : false;
4366
- }
4367
- if (process.platform === "win32") {
4368
- const frontmostProcess = await getFrontmostProcessWindows();
4369
- return frontmostProcess ? matchesTerminal(frontmostProcess, terminalName) : false;
4370
- }
4371
- if (process.platform === "linux") {
4372
- const frontmostClass = await getActiveWindowClassLinux();
4373
- return frontmostClass ? matchesTerminal(frontmostClass, terminalName) : false;
4374
- }
4375
- return false;
4376
- }
4377
4060
  function getSessionIDFromEvent(event) {
4378
4061
  const sessionID = event?.properties?.sessionID;
4379
4062
  if (typeof sessionID === "string" && sessionID.length > 0) {
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mohak34/opencode-notifier",
3
- "version": "0.1.16",
3
+ "version": "0.1.18-beta.0",
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",
@@ -17,7 +17,8 @@
17
17
  },
18
18
  "files": [
19
19
  "dist",
20
- "sounds"
20
+ "sounds",
21
+ "logos"
21
22
  ],
22
23
  "scripts": {
23
24
  "build": "bun build src/index.ts --outdir dist --target node",
@@ -32,7 +33,6 @@
32
33
  "alerts"
33
34
  ],
34
35
  "dependencies": {
35
- "detect-terminal": "^2.0.0",
36
36
  "node-notifier": "^10.0.1"
37
37
  },
38
38
  "devDependencies": {