@mohak34/opencode-notifier 0.1.36 → 0.2.0-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 +20 -4
- package/dist/index.js +141 -19
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -181,6 +181,7 @@ Customize the notification text:
|
|
|
181
181
|
Messages support placeholder tokens that get replaced with actual values:
|
|
182
182
|
|
|
183
183
|
- `{sessionTitle}` - The title/summary of the current session (e.g. "Fix login bug")
|
|
184
|
+
- `{agentName}` - Subagent name extracted from session titles with `(@name subagent)` suffix (e.g. `builder`, `codebase-researcher`), empty for non-subagent sessions
|
|
184
185
|
- `{projectName}` - The project folder name
|
|
185
186
|
- `{timestamp}` - Current time in `HH:MM:SS` format (e.g. "14:30:05")
|
|
186
187
|
- `{turn}` - Global notification counter that persists across restarts (e.g. 1, 2, 3). Stored in `~/.config/opencode/opencode-notifier-state.json`
|
|
@@ -236,7 +237,7 @@ Set per-event volume from `0` to `1`:
|
|
|
236
237
|
|
|
237
238
|
### Custom commands
|
|
238
239
|
|
|
239
|
-
Run your own script when something happens. Use `{event}`, `{message}`, `{sessionTitle}`, `{projectName}`, `{timestamp}`, and `{turn}` as placeholders:
|
|
240
|
+
Run your own script when something happens. Use `{event}`, `{message}`, `{sessionTitle}`, `{agentName}`, `{projectName}`, `{timestamp}`, and `{turn}` as placeholders:
|
|
240
241
|
|
|
241
242
|
```json
|
|
242
243
|
{
|
|
@@ -251,7 +252,7 @@ Run your own script when something happens. Use `{event}`, `{message}`, `{sessio
|
|
|
251
252
|
|
|
252
253
|
- `enabled` - Turn command on/off
|
|
253
254
|
- `path` - Path to your script/executable
|
|
254
|
-
- `args` - Arguments to pass, can use `{event}`, `{message}`, `{sessionTitle}`, `{projectName}`, `{timestamp}`, and `{turn}` tokens
|
|
255
|
+
- `args` - Arguments to pass, can use `{event}`, `{message}`, `{sessionTitle}`, `{agentName}`, `{projectName}`, `{timestamp}`, and `{turn}` tokens
|
|
255
256
|
- `minDuration` - Skip if response was quick, avoids spam (seconds)
|
|
256
257
|
|
|
257
258
|
#### Example: Log events to a file
|
|
@@ -301,6 +302,18 @@ If you're using [Ghostty](https://ghostty.org/) terminal, you can use its native
|
|
|
301
302
|
|
|
302
303
|
This sends notifications directly through the terminal instead of using system notification tools. Works on any platform where Ghostty is running.
|
|
303
304
|
|
|
305
|
+
If you're using Ghostty inside tmux, enable passthrough in your tmux config so OSC 9 notifications can pass through:
|
|
306
|
+
|
|
307
|
+
```tmux
|
|
308
|
+
set -g allow-passthrough on
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Then reload tmux config:
|
|
312
|
+
|
|
313
|
+
```bash
|
|
314
|
+
tmux source-file ~/.tmux.conf
|
|
315
|
+
```
|
|
316
|
+
|
|
304
317
|
## Focus detection
|
|
305
318
|
|
|
306
319
|
When `suppressWhenFocused` is `true` (the default), notifications and sounds are skipped if the terminal running OpenCode is the active/focused window. The idea is simple: if you're already looking at it, you don't need an alert.
|
|
@@ -320,15 +333,18 @@ To disable this and always get notified:
|
|
|
320
333
|
| macOS | AppleScript (`System Events`) | None | Untested |
|
|
321
334
|
| Linux X11 | `xdotool` | `xdotool` installed | Untested |
|
|
322
335
|
| Linux Wayland (Hyprland) | `hyprctl activewindow` | None | Tested |
|
|
336
|
+
| Linux Wayland (Niri) | `niri msg --json focused-window` | None | Tested |
|
|
323
337
|
| Linux Wayland (Sway) | `swaymsg -t get_tree` | None | Untested |
|
|
324
338
|
| Linux Wayland (KDE) | `kdotool` | `kdotool` installed | Untested |
|
|
325
339
|
| Linux Wayland (GNOME) | Not supported | - | Falls back to always notifying |
|
|
326
|
-
| Linux Wayland (
|
|
340
|
+
| Linux Wayland (river, dwl, Cosmic, etc.) | Not supported | - | Falls back to always notifying |
|
|
327
341
|
| Windows | `GetForegroundWindow()` via PowerShell | None | Untested |
|
|
328
342
|
|
|
329
343
|
**Unsupported compositors**: Wayland has no standard protocol for querying the focused window. Each compositor has its own IPC, and GNOME intentionally doesn't expose focus information. Unsupported compositors fall back to always notifying.
|
|
330
344
|
|
|
331
|
-
**tmux/screen**: When running inside tmux,
|
|
345
|
+
**tmux/screen**: When running inside tmux, focus detection uses tmux pane state (`session_attached`, `window_active`, `pane_active`) via `tmux display-message`. This keeps suppression accurate when switching panes/windows/sessions. GNU Screen is not currently handled (falls back to always notifying).
|
|
346
|
+
|
|
347
|
+
**WezTerm panes**: When running in WezTerm with `WEZTERM_PANE` set, focus suppression is pane-aware via `wezterm cli list-clients --format json`. This means notifications are shown when you switch to a different WezTerm pane/tab.
|
|
332
348
|
|
|
333
349
|
**Fail-open design**: If detection fails for any reason (missing tools, unknown compositor, permissions), it falls back to always notifying. It never silently eats your notifications.
|
|
334
350
|
|
package/dist/index.js
CHANGED
|
@@ -220,6 +220,8 @@ function interpolateMessage(message, context) {
|
|
|
220
220
|
let result = message;
|
|
221
221
|
const sessionTitle = context.sessionTitle || "";
|
|
222
222
|
result = result.replaceAll("{sessionTitle}", sessionTitle);
|
|
223
|
+
const agentName = context.agentName || "";
|
|
224
|
+
result = result.replaceAll("{agentName}", agentName);
|
|
223
225
|
const projectName = context.projectName || "";
|
|
224
226
|
result = result.replaceAll("{projectName}", projectName);
|
|
225
227
|
const timestamp = context.timestamp || "";
|
|
@@ -250,6 +252,18 @@ if (platform === "Linux" || platform.match(/BSD$/)) {
|
|
|
250
252
|
var lastNotificationTime = {};
|
|
251
253
|
var lastLinuxNotificationId = null;
|
|
252
254
|
var linuxNotifySendSupportsReplace = null;
|
|
255
|
+
function sanitizeGhosttyField(value) {
|
|
256
|
+
return value.replace(/[;\x07\x1b\n\r]/g, "");
|
|
257
|
+
}
|
|
258
|
+
function formatGhosttyNotificationSequence(title, message, env = process.env) {
|
|
259
|
+
const escapedTitle = sanitizeGhosttyField(title);
|
|
260
|
+
const escapedMessage = sanitizeGhosttyField(message);
|
|
261
|
+
const payload = `\x1B]9;${escapedTitle}: ${escapedMessage}\x07`;
|
|
262
|
+
if (env.TMUX) {
|
|
263
|
+
return `\x1BPtmux;\x1B${payload}\x1B\\`;
|
|
264
|
+
}
|
|
265
|
+
return payload;
|
|
266
|
+
}
|
|
253
267
|
function detectNotifySendCapabilities() {
|
|
254
268
|
return new Promise((resolve) => {
|
|
255
269
|
execFile("notify-send", ["--version"], (error, stdout) => {
|
|
@@ -302,9 +316,8 @@ async function sendNotification(title, message, timeout, iconPath, notificationS
|
|
|
302
316
|
lastNotificationTime[message] = now;
|
|
303
317
|
if (notificationSystem === "ghostty") {
|
|
304
318
|
return new Promise((resolve) => {
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
process.stdout.write(`\x1B]9;${escapedTitle}: ${escapedMessage}\x07`, () => {
|
|
319
|
+
const sequence = formatGhosttyNotificationSequence(title, message);
|
|
320
|
+
process.stdout.write(sequence, () => {
|
|
308
321
|
resolve();
|
|
309
322
|
});
|
|
310
323
|
});
|
|
@@ -481,20 +494,21 @@ async function playSound(event, customPath, volume) {
|
|
|
481
494
|
|
|
482
495
|
// src/command.ts
|
|
483
496
|
import { spawn as spawn2 } from "child_process";
|
|
484
|
-
function substituteTokens(value, event, message, sessionTitle, projectName, timestamp, turn) {
|
|
497
|
+
function substituteTokens(value, event, message, sessionTitle, agentName, projectName, timestamp, turn) {
|
|
485
498
|
let result = value.replaceAll("{event}", event).replaceAll("{message}", message);
|
|
486
499
|
result = result.replaceAll("{sessionTitle}", sessionTitle || "");
|
|
500
|
+
result = result.replaceAll("{agentName}", agentName || "");
|
|
487
501
|
result = result.replaceAll("{projectName}", projectName || "");
|
|
488
502
|
result = result.replaceAll("{timestamp}", timestamp || "");
|
|
489
503
|
result = result.replaceAll("{turn}", turn != null ? String(turn) : "");
|
|
490
504
|
return result;
|
|
491
505
|
}
|
|
492
|
-
function runCommand2(config, event, message, sessionTitle, projectName, timestamp, turn) {
|
|
506
|
+
function runCommand2(config, event, message, sessionTitle, agentName, projectName, timestamp, turn) {
|
|
493
507
|
if (!config.command.enabled || !config.command.path) {
|
|
494
508
|
return;
|
|
495
509
|
}
|
|
496
|
-
const args = (config.command.args ?? []).map((arg) => substituteTokens(arg, event, message, sessionTitle, projectName, timestamp, turn));
|
|
497
|
-
const command = substituteTokens(config.command.path, event, message, sessionTitle, projectName, timestamp, turn);
|
|
510
|
+
const args = (config.command.args ?? []).map((arg) => substituteTokens(arg, event, message, sessionTitle, agentName, projectName, timestamp, turn));
|
|
511
|
+
const command = substituteTokens(config.command.path, event, message, sessionTitle, agentName, projectName, timestamp, turn);
|
|
498
512
|
const proc = spawn2(command, args, {
|
|
499
513
|
stdio: "ignore",
|
|
500
514
|
detached: true
|
|
@@ -578,10 +592,38 @@ function getSwayActiveWindowId() {
|
|
|
578
592
|
return null;
|
|
579
593
|
}
|
|
580
594
|
}
|
|
595
|
+
function getNiriActiveWindowId() {
|
|
596
|
+
const output = execWithTimeout("niri msg --json focused-window", 1000);
|
|
597
|
+
if (!output)
|
|
598
|
+
return null;
|
|
599
|
+
try {
|
|
600
|
+
const data = JSON.parse(output);
|
|
601
|
+
return typeof data?.id === "number" ? String(data.id) : null;
|
|
602
|
+
} catch {
|
|
603
|
+
return null;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
function parseWezTermFocusedPaneId(output) {
|
|
607
|
+
try {
|
|
608
|
+
const data = JSON.parse(output);
|
|
609
|
+
if (!Array.isArray(data))
|
|
610
|
+
return null;
|
|
611
|
+
for (const client of data) {
|
|
612
|
+
if (typeof client?.focused_pane_id === "number") {
|
|
613
|
+
return String(client.focused_pane_id);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
return null;
|
|
617
|
+
} catch {
|
|
618
|
+
return null;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
581
621
|
function getLinuxWaylandActiveWindowId() {
|
|
582
622
|
const env = process.env;
|
|
583
623
|
if (env.HYPRLAND_INSTANCE_SIGNATURE)
|
|
584
624
|
return getHyprlandActiveWindowId();
|
|
625
|
+
if (env.NIRI_SOCKET)
|
|
626
|
+
return getNiriActiveWindowId();
|
|
585
627
|
if (env.SWAYSOCK)
|
|
586
628
|
return getSwayActiveWindowId();
|
|
587
629
|
if (env.KDE_SESSION_VERSION)
|
|
@@ -607,6 +649,9 @@ function normalizeMacAppName(value) {
|
|
|
607
649
|
function getExpectedMacTerminalAppNames(env) {
|
|
608
650
|
const expected = new Set;
|
|
609
651
|
const termProgram = typeof env.TERM_PROGRAM === "string" ? normalizeMacAppName(env.TERM_PROGRAM) : "";
|
|
652
|
+
if (env.TMUX && (termProgram === "tmux" || termProgram === "screen" || termProgram.length === 0)) {
|
|
653
|
+
return new Set(MAC_TERMINAL_APP_NAMES);
|
|
654
|
+
}
|
|
610
655
|
if (termProgram === "apple_terminal") {
|
|
611
656
|
expected.add("terminal");
|
|
612
657
|
} else if (termProgram === "iterm" || termProgram === "iterm2") {
|
|
@@ -652,16 +697,31 @@ function getActiveWindowId() {
|
|
|
652
697
|
return null;
|
|
653
698
|
}
|
|
654
699
|
var cachedWindowId = getActiveWindowId();
|
|
655
|
-
|
|
656
|
-
function isTmuxPaneActive() {
|
|
700
|
+
function isTmuxPaneFocused(tmuxPane, probeResult) {
|
|
657
701
|
if (!tmuxPane)
|
|
658
|
-
return true;
|
|
659
|
-
const result = execWithTimeout(`tmux display-message -t ${tmuxPane} -p '#{session_attached} #{window_active} #{pane_active}'`);
|
|
660
|
-
if (!result)
|
|
661
702
|
return false;
|
|
662
|
-
|
|
703
|
+
if (!probeResult)
|
|
704
|
+
return false;
|
|
705
|
+
const [sessionAttached, windowActive, paneActive] = probeResult.split(" ");
|
|
663
706
|
return sessionAttached === "1" && windowActive === "1" && paneActive === "1";
|
|
664
707
|
}
|
|
708
|
+
function isTmuxPaneActive() {
|
|
709
|
+
const tmuxPane = process.env.TMUX_PANE ?? null;
|
|
710
|
+
const result = execFileWithTimeout("tmux", ["display-message", "-t", tmuxPane ?? "", "-p", "#{session_attached} #{window_active} #{pane_active}"]);
|
|
711
|
+
return isTmuxPaneFocused(tmuxPane, result);
|
|
712
|
+
}
|
|
713
|
+
function isWezTermPaneActive() {
|
|
714
|
+
const weztermPane = process.env.WEZTERM_PANE ?? null;
|
|
715
|
+
if (!weztermPane)
|
|
716
|
+
return true;
|
|
717
|
+
const output = execFileWithTimeout("wezterm", ["cli", "list-clients", "--format", "json"], 1000);
|
|
718
|
+
if (!output)
|
|
719
|
+
return false;
|
|
720
|
+
const focusedPaneId = parseWezTermFocusedPaneId(output);
|
|
721
|
+
if (!focusedPaneId)
|
|
722
|
+
return false;
|
|
723
|
+
return focusedPaneId === weztermPane;
|
|
724
|
+
}
|
|
665
725
|
function isTerminalFocused() {
|
|
666
726
|
try {
|
|
667
727
|
if (process.platform === "darwin") {
|
|
@@ -669,6 +729,9 @@ function isTerminalFocused() {
|
|
|
669
729
|
if (!isMacTerminalAppFocused(frontmostAppName, process.env)) {
|
|
670
730
|
return false;
|
|
671
731
|
}
|
|
732
|
+
if (!isWezTermPaneActive()) {
|
|
733
|
+
return false;
|
|
734
|
+
}
|
|
672
735
|
if (process.env.TMUX) {
|
|
673
736
|
return isTmuxPaneActive();
|
|
674
737
|
}
|
|
@@ -679,6 +742,8 @@ function isTerminalFocused() {
|
|
|
679
742
|
const currentId = getActiveWindowId();
|
|
680
743
|
if (currentId !== cachedWindowId)
|
|
681
744
|
return false;
|
|
745
|
+
if (!isWezTermPaneActive())
|
|
746
|
+
return false;
|
|
682
747
|
if (process.env.TMUX)
|
|
683
748
|
return isTmuxPaneActive();
|
|
684
749
|
return true;
|
|
@@ -687,6 +752,34 @@ function isTerminalFocused() {
|
|
|
687
752
|
}
|
|
688
753
|
}
|
|
689
754
|
|
|
755
|
+
// src/permission-dedupe.ts
|
|
756
|
+
var PERMISSION_DEDUPE_WINDOW_MS = 1000;
|
|
757
|
+
var sessionLastPermissionAt = new Map;
|
|
758
|
+
var globalLastPermissionAt = 0;
|
|
759
|
+
function shouldSuppressPermissionAlert(sessionID, now = Date.now()) {
|
|
760
|
+
const sessionLastAt = sessionID ? sessionLastPermissionAt.get(sessionID) : undefined;
|
|
761
|
+
const latestSeen = Math.max(globalLastPermissionAt, sessionLastAt ?? 0);
|
|
762
|
+
const isDuplicate = latestSeen > 0 && now - latestSeen < PERMISSION_DEDUPE_WINDOW_MS;
|
|
763
|
+
if (isDuplicate) {
|
|
764
|
+
return true;
|
|
765
|
+
}
|
|
766
|
+
globalLastPermissionAt = now;
|
|
767
|
+
if (sessionID) {
|
|
768
|
+
sessionLastPermissionAt.set(sessionID, now);
|
|
769
|
+
}
|
|
770
|
+
return false;
|
|
771
|
+
}
|
|
772
|
+
function prunePermissionAlertState(cutoffMs) {
|
|
773
|
+
for (const [sessionID, timestamp] of sessionLastPermissionAt) {
|
|
774
|
+
if (timestamp < cutoffMs) {
|
|
775
|
+
sessionLastPermissionAt.delete(sessionID);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
if (globalLastPermissionAt < cutoffMs) {
|
|
779
|
+
globalLastPermissionAt = 0;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
690
783
|
// src/index.ts
|
|
691
784
|
var IDLE_COMPLETE_DELAY_MS = 350;
|
|
692
785
|
var pendingIdleTimers = new Map;
|
|
@@ -734,6 +827,7 @@ setInterval(() => {
|
|
|
734
827
|
sessionLastBusyAt.delete(sessionID);
|
|
735
828
|
}
|
|
736
829
|
}
|
|
830
|
+
prunePermissionAlertState(cutoff);
|
|
737
831
|
}, 5 * 60 * 1000);
|
|
738
832
|
function getNotificationTitle(config, projectName) {
|
|
739
833
|
if (config.showProjectName && projectName) {
|
|
@@ -748,7 +842,26 @@ function formatTimestamp() {
|
|
|
748
842
|
const s = String(now.getSeconds()).padStart(2, "0");
|
|
749
843
|
return `${h}:${m}:${s}`;
|
|
750
844
|
}
|
|
751
|
-
|
|
845
|
+
function extractAgentNameFromSessionTitle(sessionTitle) {
|
|
846
|
+
if (!sessionTitle) {
|
|
847
|
+
return "";
|
|
848
|
+
}
|
|
849
|
+
const match = sessionTitle.match(/\s*\(@([^\s)]+)\s+subagent\)\s*$/);
|
|
850
|
+
return match ? match[1] : "";
|
|
851
|
+
}
|
|
852
|
+
function shouldResolveAgentNameForEvent(config, eventType) {
|
|
853
|
+
if (getMessage(config, eventType).includes("{agentName}")) {
|
|
854
|
+
return true;
|
|
855
|
+
}
|
|
856
|
+
if (!config.command.enabled || !isEventCommandEnabled(config, eventType)) {
|
|
857
|
+
return false;
|
|
858
|
+
}
|
|
859
|
+
if (config.command.path.includes("{agentName}")) {
|
|
860
|
+
return true;
|
|
861
|
+
}
|
|
862
|
+
return (config.command.args ?? []).some((arg) => arg.includes("{agentName}"));
|
|
863
|
+
}
|
|
864
|
+
async function handleEvent(config, eventType, projectName, elapsedSeconds, sessionTitle, sessionID, agentName) {
|
|
752
865
|
if (config.suppressWhenFocused && isTerminalFocused()) {
|
|
753
866
|
return;
|
|
754
867
|
}
|
|
@@ -758,6 +871,7 @@ async function handleEvent(config, eventType, projectName, elapsedSeconds, sessi
|
|
|
758
871
|
const rawMessage = getMessage(config, eventType);
|
|
759
872
|
const message = interpolateMessage(rawMessage, {
|
|
760
873
|
sessionTitle: config.showSessionTitle ? sessionTitle : null,
|
|
874
|
+
agentName,
|
|
761
875
|
projectName,
|
|
762
876
|
timestamp,
|
|
763
877
|
turn
|
|
@@ -775,7 +889,7 @@ async function handleEvent(config, eventType, projectName, elapsedSeconds, sessi
|
|
|
775
889
|
const minDuration = config.command?.minDuration;
|
|
776
890
|
const shouldSkipCommand = !isEventCommandEnabled(config, eventType) || typeof minDuration === "number" && Number.isFinite(minDuration) && minDuration > 0 && typeof elapsedSeconds === "number" && Number.isFinite(elapsedSeconds) && elapsedSeconds < minDuration;
|
|
777
891
|
if (!shouldSkipCommand) {
|
|
778
|
-
runCommand2(config, eventType, message, sessionTitle, projectName, timestamp, turn);
|
|
892
|
+
runCommand2(config, eventType, message, sessionTitle, agentName, projectName, timestamp, turn);
|
|
779
893
|
}
|
|
780
894
|
await Promise.allSettled(promises);
|
|
781
895
|
}
|
|
@@ -905,11 +1019,13 @@ async function handleEventWithElapsedTime(client, config, eventType, projectName
|
|
|
905
1019
|
}
|
|
906
1020
|
}
|
|
907
1021
|
let sessionTitle = preloadedSessionTitle ?? null;
|
|
908
|
-
|
|
1022
|
+
const shouldLookupSessionInfo = sessionID && !sessionTitle && (config.showSessionTitle || shouldResolveAgentNameForEvent(config, eventType));
|
|
1023
|
+
if (shouldLookupSessionInfo) {
|
|
909
1024
|
const info = await getSessionInfo(client, sessionID);
|
|
910
1025
|
sessionTitle = info.title;
|
|
911
1026
|
}
|
|
912
|
-
|
|
1027
|
+
const agentName = extractAgentNameFromSessionTitle(sessionTitle);
|
|
1028
|
+
await handleEvent(config, eventType, projectName, elapsedSeconds, sessionTitle, sessionID, agentName);
|
|
913
1029
|
}
|
|
914
1030
|
var NotifierPlugin = async ({ client, directory }) => {
|
|
915
1031
|
const clientEnv = process.env.OPENCODE_CLIENT;
|
|
@@ -924,7 +1040,10 @@ var NotifierPlugin = async ({ client, directory }) => {
|
|
|
924
1040
|
event: async ({ event }) => {
|
|
925
1041
|
const config = getConfig();
|
|
926
1042
|
if (event.type === "permission.asked") {
|
|
927
|
-
|
|
1043
|
+
const sessionID = getSessionIDFromEvent(event);
|
|
1044
|
+
if (!shouldSuppressPermissionAlert(sessionID)) {
|
|
1045
|
+
await handleEventWithElapsedTime(client, config, "permission", projectName, event);
|
|
1046
|
+
}
|
|
928
1047
|
}
|
|
929
1048
|
if (event.type === "session.idle") {
|
|
930
1049
|
const sessionID = getSessionIDFromEvent(event);
|
|
@@ -951,7 +1070,9 @@ var NotifierPlugin = async ({ client, directory }) => {
|
|
|
951
1070
|
},
|
|
952
1071
|
"permission.ask": async () => {
|
|
953
1072
|
const config = getConfig();
|
|
954
|
-
|
|
1073
|
+
if (!shouldSuppressPermissionAlert(null)) {
|
|
1074
|
+
await handleEvent(config, "permission", projectName, null);
|
|
1075
|
+
}
|
|
955
1076
|
},
|
|
956
1077
|
"tool.execute.before": async (input) => {
|
|
957
1078
|
const config = getConfig();
|
|
@@ -963,6 +1084,7 @@ var NotifierPlugin = async ({ client, directory }) => {
|
|
|
963
1084
|
};
|
|
964
1085
|
var src_default = NotifierPlugin;
|
|
965
1086
|
export {
|
|
1087
|
+
extractAgentNameFromSessionTitle,
|
|
966
1088
|
src_default as default,
|
|
967
1089
|
NotifierPlugin
|
|
968
1090
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mohak34/opencode-notifier",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0-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",
|