跳到主要内容
    ↑↓ 选择↵ 打开esc 关闭
    中文English
    mohak34

    Notifier

    v0.2.8通知与集成
    @mohak34/opencode-notifier

    OpenCode plugin that sends system notifications and plays sounds when permission is needed, generation completes, or errors occur

    GitHub 星标

    774

    近 30 天 +3

    月装机量

    6,115

    近 7 天 1.3k

    综合评分SCORE

    68.4

    生态多维模型

    最近提交

    1 个月前

    2026-07-19

    快速安装与配置

    opencode.json

    写入当前项目的 opencode.json,只对这个仓库生效。

    opencode.json

    {
      "$schema": "https://opencode.ai/config.json",
      "plugin": ["@mohak34/opencode-notifier@0.2.8"]
    }

    opencode 启动时会通过内嵌运行时自动加载 npm 依赖并缓存至本地目录,无需手动在全局环境执行安装。

    OpenCode plugin that plays sounds and sends system notifications when permission is needed, generation completes, errors occur, or the question tool is invoked. Works on macOS, Linux, and Windows.

    Quick Start

    Add this to your opencode.json:

    {
      "plugin": ["@mohak34/opencode-notifier@latest"]
    }
    

    Restart OpenCode. Done.

    What it does

    You'll get notified when:

    • OpenCode needs permission to run something
    • Your session finishes
    • An error happens
    • The question tool pops up

    There's also subagent_complete for when subagents finish, and user_cancelled for when you press ESC to abort -- both are silent by default so you don't get spammed.

    Setup by platform

    macOS: Nothing to do, works out of the box. Shows the Script Editor icon.

    Linux: Should work if you already have a notification system setup. If not install libnotify:

    sudo apt install libnotify-bin  # Ubuntu/Debian
    sudo dnf install libnotify       # Fedora  
    sudo pacman -S libnotify         # Arch
    

    For sounds, you need one of: paplay, aplay, mpv, or ffplay

    Windows: Works out of the box. But heads up:

    • Only .wav files work (not mp3)
    • Use full paths like C:/Users/You/sounds/alert.wav not ~/

    WSL: It's recommeneded to set customIconPath pointing to a file on Windows filesystem due to issues with path translation (can be copied from logos folder from this repository). This path will be passed down to snoretoast-*.exe

    In opencode-notifier.json config:

      "showIcon": true,
      "customIconPath": "C:\\Users\\jhon\\Documents\\opencode-logo-dark.png",
    

    Config file

    Create ~/.config/opencode/opencode-notifier.json with the defaults:

    {
      "sound": true,
      "notification": true,
      "bell": false,
      "timeout": 5,
      "showProjectName": true,
      "showFullPath": false,
      "showSessionTitle": false,
      "showIcon": true,
      "customIconPath": null,
      "suppressWhenFocused": true,
      "enableOnDesktop": false,
      "notificationSystem": "osascript",
      "suppressGhosttySound": false,
      "linux": {
        "grouping": false
      },
      "minDuration": 0,
      "command": {
        "enabled": false,
        "path": "/path/to/command",
        "args": ["--event", "{event}", "--message", "{message}"],
        "minDuration": 0
      },
      "events": {
        "permission": { "sound": true, "notification": true, "command": true, "bell": false },
        "complete": { "sound": true, "notification": true, "command": true, "bell": false },
        "subagent_complete": { "sound": false, "notification": false, "command": true, "bell": false },
        "error": { "sound": true, "notification": true, "command": true, "bell": false },
        "question": { "sound": true, "notification": true, "command": true, "bell": false },
        "user_cancelled": { "sound": false, "notification": false, "command": true, "bell": false },
        "plan_exit": { "sound": true, "notification": true, "command": true, "bell": false },
        "session_started": { "sound": true, "notification": false, "command": true, "bell": false },
        "user_message": { "sound": true, "notification": false, "command": true, "bell": false },
        "client_connected": { "sound": true, "notification": false, "command": true, "bell": false }
      },
      "messages": {
        "permission": "Session needs permission: {sessionTitle}",
        "complete": "Session has finished: {sessionTitle}",
        "subagent_complete": "Subagent task completed: {sessionTitle}",
        "error": "Session encountered an error: {sessionTitle}",
        "question": "Session has a question: {sessionTitle}",
        "user_cancelled": "Session was cancelled by user: {sessionTitle}",
        "plan_exit": "Plan ready for review: {sessionTitle}",
        "session_started": "Session started: {sessionTitle}",
        "user_message": "User sent a message: {sessionTitle}",
        "client_connected": "OpenCode connected"
      },
      "sounds": {
        "permission": null,
        "complete": null,
        "subagent_complete": null,
        "error": null,
        "question": null,
        "user_cancelled": null,
        "plan_exit": null,
        "session_started": null,
        "user_message": null,
        "client_connected": null
      },
      "volumes": {
        "permission": 1,
        "complete": 1,
        "subagent_complete": 1,
        "error": 1,
        "question": 1,
        "user_cancelled": 1,
        "plan_exit": 1,
        "session_started": 1,
        "user_message": 1,
        "client_connected": 1
      }
    }
    

    All options

    Global options

    {
      "sound": true,
      "notification": true,
      "bell": false,
      "timeout": 5,
      "showProjectName": true,
      "showFullPath": false,
      "showSessionTitle": false,
      "showIcon": true,
      "suppressWhenFocused": true,
      "enableOnDesktop": false,
      "notificationSystem": "osascript",
      "suppressGhosttySound": false
    }
    
    • sound - Turn sounds on/off (default: true)
    • notification - Turn notifications on/off (default: true)
    • bell - Emit terminal BEL (\x07) on events (default: false). Behavior depends on your terminal/WM settings
    • timeout - How long notifications show in seconds, Linux only (default: 5)
    • showProjectName - Show folder name in notification title (default: true)
    • showFullPath - Show full absolute path instead of folder name in notification title and {projectName} token (default: false). When true, shows OpenCode (/home/user/projects/myapp) instead of OpenCode (myapp)
    • showSessionTitle - Include the session title in notification messages via {sessionTitle} placeholder (default: false)
    • showIcon - Show OpenCode icon, Windows/Linux only (default: true)
    • customIconPath - Path to a custom icon for notifications. Useful on WSL where Windows paths are needed (default: null)
    • suppressWhenFocused - Skip notifications and sounds when the terminal is the active window (default: true). See Focus detection for platform details
    • enableOnDesktop - Run the plugin on Desktop and Web clients (default: false). When false, the plugin only runs on CLI. Set to true if you want notifications/sounds/commands on Desktop/Web — useful if you want custom commands (Telegram, webhooks) but don't care about built-in notifications
    • notificationSystem - macOS only: "osascript", "node-notifier", or "ghostty" (default: "osascript"). Use "ghostty" if you're running Ghostty terminal for native OSC 9 notifications
    • suppressGhosttySound - macOS only: when true with notificationSystem: "ghostty", skips the plugin's sound to avoid duplicating macOS Notification Center's default sound (default: false)
    • minDuration - Suppress complete and subagent_complete notifications when session finishes faster than this many seconds (default: 0). See Minimum duration threshold
    • linux.grouping - Linux only: replace notifications in-place instead of stacking (default: false). Requires notify-send 0.8+

    Events

    Control each event separately:

    {
      "events": {
        "permission": { "sound": true, "notification": true, "command": true, "bell": false },
        "complete": { "sound": true, "notification": true, "command": true, "bell": false },
        "subagent_complete": { "sound": false, "notification": false, "command": true, "bell": false },
        "error": { "sound": true, "notification": true, "command": true, "bell": false },
        "question": { "sound": true, "notification": true, "command": true, "bell": false },
        "user_cancelled": { "sound": false, "notification": false, "command": true, "bell": false },
        "plan_exit": { "sound": true, "notification": true, "command": true, "bell": false },
        "session_started": { "sound": true, "notification": false, "command": true, "bell": false },
        "user_message": { "sound": true, "notification": false, "command": true, "bell": false },
        "client_connected": { "sound": true, "notification": false, "command": true, "bell": false }
      }
    }
    

    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.

    session_started fires when a new top-level session is created. user_message fires when a user message is submitted in a top-level session. client_connected fires shortly after the plugin initializes and is best-effort (there is no dedicated SDK connection event from plugin context).

    The command property controls whether the custom command (see 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.

    bell is terminal-driven and may be audible, visual, both, or ignored depending on your terminal setup. Quick check: printf '\a'.

    Or use true/false for both:

    {
      "events": {
        "complete": false
      }
    }
    

    Messages

    Customize the notification text:

    {
      "messages": {
        "permission": "Session needs permission: {sessionTitle}",
        "complete": "Session has finished: {sessionTitle}",
        "subagent_complete": "Subagent task completed: {sessionTitle}",
        "error": "Session encountered an error: {sessionTitle}",
        "question": "Session has a question: {sessionTitle}",
        "user_cancelled": "Session was cancelled by user: {sessionTitle}",
        "plan_exit": "Plan ready for review: {sessionTitle}",
        "session_started": "Session started: {sessionTitle}",
        "user_message": "User sent a message: {sessionTitle}",
        "client_connected": "OpenCode connected"
      }
    }
    

    Messages support placeholder tokens that get replaced with actual values:

    • {sessionTitle} - The title/summary of the current session (e.g. "Fix login bug")
    • {agentName} - Subagent name extracted from session titles with (@name subagent) suffix (e.g. builder, codebase-researcher), empty for non-subagent sessions
    • {projectName} - The project folder name
    • {timestamp} - Current time in HH:MM:SS format (e.g. "14:30:05")
    • {turn} - Global notification counter that persists across restarts (e.g. 1, 2, 3). Stored in ~/.config/opencode/opencode-notifier-state.json

    When showSessionTitle is false, {sessionTitle} is replaced with an empty string. Any trailing separators (: , -, |) are automatically cleaned up when a placeholder resolves to empty.

    To disable session titles in messages without changing showSessionTitle, just remove the {sessionTitle} placeholder from your custom messages.

    The {timestamp} and {turn} placeholders also work in custom command args.

    Sounds

    Use your own sound files:

    {
      "sounds": {
        "permission": "/path/to/alert.wav",
        "complete": "/path/to/done.wav",
        "subagent_complete": "/path/to/subagent-done.wav",
        "error": "/path/to/error.wav",
        "question": "/path/to/question.wav",
        "user_cancelled": "/path/to/cancelled.wav",
        "plan_exit": "/path/to/plan-ready.wav",
        "session_started": "/path/to/session-started.wav",
        "user_message": "/path/to/user-message.wav",
        "client_connected": "/path/to/client-connected.wav"
      }
    }
    

    Platform notes:

    • macOS/Linux: .wav or .mp3 files work
    • Windows: Only .wav files work
    • If file doesn't exist, falls back to bundled sound

    Volumes

    Set per-event volume from 0 to 1:

    {
      "volumes": {
        "permission": 0.6,
        "complete": 0.3,
        "subagent_complete": 0.15,
        "error": 1,
        "question": 0.7,
        "user_cancelled": 0.5,
        "plan_exit": 0.6,
        "session_started": 0.35,
        "user_message": 0.2,
        "client_connected": 0.45
      }
    }
    
    • 0 = mute, 1 = full volume
    • Values outside 0..1 are clamped automatically
    • On Windows, playback still works but custom volume may not be honored by the default player

    Custom commands

    Run your own script when something happens. Use {event}, {message}, {sessionTitle}, {agentName}, {projectName}, {timestamp}, and {turn} as placeholders:

    {
      "command": {
        "enabled": true,
        "path": "/path/to/your/script",
        "args": ["{event}", "{message}"],
        "minDuration": 10
      }
    }
    
    • enabled - Turn command on/off
    • path - Path to your script/executable
    • args - Arguments to pass, can use {event}, {message}, {sessionTitle}, {agentName}, {projectName}, {timestamp}, and {turn} tokens
    • minDuration - Skip if response was quick, avoids spam (seconds)

    Example: Log events to a file

    {
      "command": {
        "enabled": true,
        "path": "/bin/bash",
        "args": [
          "-c",
          "echo '[{event}] {message}' >> /tmp/opencode.log"
        ]
      }
    }
    

    macOS: Pick your notification style

    osascript (default): Reliable but shows Script Editor icon

    { 
      "notificationSystem": "osascript" 
    }
    

    node-notifier: Shows OpenCode icon but might miss notifications sometimes

    { 
      "notificationSystem": "node-notifier" 
    }
    

    NOTE: If you go with node-notifier and start missing notifications, just switch back or remove the option from the config. Users have reported issues with using node-notifier for receiving only sounds and no notification popups.

    Ghostty notifications

    If you're using Ghostty terminal, you can use its native notification system via OSC 9 escape sequences:

    {
      "notificationSystem": "ghostty"
    }
    

    This sends notifications directly through the terminal instead of using system notification tools. Works on any platform where Ghostty is running.

    macOS: Ghostty delivers notifications through macOS Notification Center, which plays its own default sound. This can result in duplicate audio with the plugin's sound effects. Set suppressGhosttySound to true to skip the plugin's sound:

    {
      "notificationSystem": "ghostty",
      "suppressGhosttySound": true
    }
    

    Note: custom sounds configured via the sounds section still play — only default (bundled) sounds are suppressed.

    If you're using Ghostty inside tmux, enable passthrough in your tmux config so OSC 9 notifications can pass through:

    set -g allow-passthrough on
    

    Then reload tmux config:

    tmux source-file ~/.tmux.conf
    

    Focus detection

    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.

    To disable this and always get notified:

    {
      "suppressWhenFocused": false
    }
    

    Minimum duration threshold

    You can suppress complete and subagent_complete notifications for short-lived sessions. Set minDuration to the number of seconds a session must exceed to trigger a done notification:

    {
      "minDuration": 10
    }
    

    With the above, if OpenCode finishes in under 10 seconds, no notification, sound, bell, or command is fired. Default is 0 (no threshold).

    This is independent of command.minDuration, which only controls whether the custom command runs.

    Platform support

    Platform Method Requirements Status
    macOS AppleScript (System Events) None Untested
    Linux X11 xdotool xdotool installed Untested
    Linux Wayland (Hyprland) hyprctl activewindow None Tested
    Linux Wayland (Niri) niri msg --json focused-window None Tested
    Linux Wayland (Sway) swaymsg -t get_tree None Untested
    Linux Wayland (KDE) kdotool kdotool installed Tested
    Linux Wayland (GNOME) Not supported - Falls back to always notifying
    Linux Wayland (river, dwl, Cosmic, etc.) Not supported - Falls back to always notifying
    Windows GetForegroundWindow() via PowerShell None Untested

    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.

    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. On Linux setups where window focus cannot be detected at all, tmux pane state is also used as a best-effort fallback. GNU Screen is not currently handled (falls back to always notifying).

    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.

    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.

    If you test on a platform marked "Untested" and it works (or doesn't), please open an issue and let us know.

    Linux: Notification Grouping

    By default, each notification appears as a separate entry. During active sessions this can create noise when multiple events fire quickly (e.g. permission + complete + question).

    Enable grouping to replace notifications in-place instead of stacking:

    {
      "linux": {
        "grouping": true
      }
    }
    

    With grouping enabled, each new notification replaces the previous one so you only see the latest event. This requires notify-send 0.8+ (standard on Ubuntu 22.04+, Debian 12+, Fedora 36+, Arch). On older systems it falls back to the default stacking behavior automatically.

    Works with all major notification daemons (GNOME, dunst, mako, swaync, etc.) on both X11 and Wayland.

    KDE Plasma: Jump back to terminal from notification

    On KDE Plasma/Wayland, clicking the popup body is not consistently delivered as a notification activation event.This plugin uses an explicit notification action button instead:

    • Jump to terminal (action button on the popup card, or in notification history)

    When clicked, the plugin runs its terminal-focus path. On KDE with kdotool installed, it auto-captures the startup terminal window ID and jumps back to that pinned window. The action button is only enabled on Linux KDE sessions where kdotool is available.

    Updating

    OpenCode caches plugin packages under ~/.cache/opencode. If you switch between latest, beta, or a pinned version and OpenCode still uses the old plugin, close OpenCode and remove the cached package.

    Linux/macOS:

    rm -rf ~/.cache/opencode/packages/@mohak34/opencode-notifier*
    rm -rf ~/.cache/opencode/node_modules/@mohak34/opencode-notifier
    rm -f ~/.cache/opencode/bun.lock
    

    Windows PowerShell:

    Remove-Item -Recurse -Force "$env:USERPROFILE\.cache\opencode\packages\@mohak34\opencode-notifier*" -ErrorAction SilentlyContinue
    Remove-Item -Recurse -Force "$env:USERPROFILE\.cache\opencode\node_modules\@mohak34\opencode-notifier" -ErrorAction SilentlyContinue
    Remove-Item -Force "$env:USERPROFILE\.cache\opencode\bun.lock" -ErrorAction SilentlyContinue
    

    Then reopen OpenCode. It will download the plugin again.

    To avoid cache confusion while testing, pin the exact version in opencode.json instead of using a moving tag:

    {
      "plugin": ["@mohak34/opencode-notifier@x.y.z"]
    }
    

    Check the version published under a tag:

    npm view @mohak34/opencode-notifier@latest version
    npm view @mohak34/opencode-notifier@beta version
    

    Check the version OpenCode cached:

    cat ~/.cache/opencode/packages/@mohak34/opencode-notifier@latest/node_modules/@mohak34/opencode-notifier/package.json | grep version
    

    If you use @beta or a pinned version, replace latest in the path with beta or the exact version, for example 0.2.9-beta.0.

    Troubleshooting

    macOS: Not seeing notifications? Go to System Settings > Notifications > Script Editor, make sure it's set to Banners or Alerts.

    macOS: node-notifier not showing notifications? Switch back to osascript. Some users report node-notifier works for sounds but not visual notifications on certain macOS versions.

    Linux: No notifications? Install libnotify-bin:

    sudo apt install libnotify-bin  # Debian/Ubuntu
    sudo dnf install libnotify       # Fedora
    sudo pacman -S libnotify         # Arch
    

    Test with: notify-send "Test" "Hello"

    Linux: No sounds? Install one of: paplay, aplay, mpv, or ffplay

    KDE Plasma: jumps to wrong terminal window or doesn't jump?

    Jump back feature tested on:

    • KWin with default floating windows
    • KWin + Krohnkite
    • Ghostty and Konsole
    • Warp
    • OpenCode in tmux inside VS Code terminal

    Most terminal emulators should work fine, but there can be exceptions.

    Known limitations:

    • Kitty is currently unsupported for this jump-back path (unstable focus targeting)
    • Yakuake sessions are not supported for activity-specific jump-back behavior

    You can still override manually (if needed) by pinning an explicit window ID:

    export OPENCODE_NOTIFIER_WINDOW_ID="$(kdotool getactivewindow)"
    opencode
    

    Manual pinning bypasses heuristic window matching and should activate that exact window on notification action click.

    X11 deterministic jump-back

    • xdotool support is possible for the same startup pin behavior
    • not implemented yet

    Windows: Custom sounds not working?

    • Must be .wav format (not .mp3)
    • Use full Windows paths: C:/Users/YourName/sounds/alert.wav (not ~/)
    • Make sure the file actually plays in Windows Media Player
    • If using WSL, the path should be accessible from Windows

    Windows WSL notifications not working? WSL doesn't have a native notification daemon. Use PowerShell commands instead:

    {
      "notification": false,
      "sound": true,
      "command": {
        "enabled": true,
        "path": "powershell.exe",
        "args": [
          "-Command",
          "$wshell = New-Object -ComObject Wscript.Shell; $wshell.Popup('{message}', 5, 'OpenCode - {event}', 0+64)"
        ]
      }
    }
    

    Windows: OpenCode crashes when notifications appear? This is a known Bun issue on Windows. Disable native notifications and use PowerShell popups:

    {
      "notification": false,
      "sound": true,
      "command": {
        "enabled": true,
        "path": "powershell.exe",
        "args": [
          "-Command",
          "$wshell = New-Object -ComObject Wscript.Shell; $wshell.Popup('{message}', 5, 'OpenCode - {event}', 0+64)"
        ]
      }
    }
    

    Plugin not loading?

    • Check your opencode.json or config.json syntax
    • Clear the cache (see Updating section)
    • Restart OpenCode

    Plugin installed but no notifications/sounds?

    • Check suppressWhenFocused: when true (default), notifications are skipped while OpenCode terminal is focused. Set to false to always notify.
    • Check enableOnDesktop: defaults to false, so the plugin won't run on Desktop/Web clients. Set to true if you need it there.
    • Verify the package version OpenCode cached:
      cat ~/.cache/opencode/packages/@mohak34/opencode-notifier@latest/node_modules/@mohak34/opencode-notifier/package.json | grep version
      
      If you use @beta or a pinned version, replace latest in the path with beta or the exact version.

    Changelog

    See CHANGELOG.md

    License

    MIT