npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

opencode-telegram-notifications

v0.1.4

Published

OpenCode plugin that sends system, sound, and outbound Telegram notifications with TUI controls

Downloads

0

Readme

opencode-telegram-notifications

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

Quick Start

Install the package with OpenCode:

opencode plugin opencode-telegram-notifications

This updates both server and TUI plugin config because the package exposes ./server and ./tui entrypoints.

If you prefer manual config, add this to your opencode.json:

{
  "plugin": ["opencode-telegram-notifications"]
}

And add this to your tui.json:

{
  "plugin": ["opencode-telegram-notifications/tui"]
}

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",
  "linux": {
    "grouping": false
  },
  "minDuration": 0,
  "command": {
    "enabled": false,
    "path": "/path/to/command",
    "args": ["--event", "{event}", "--message", "{message}"],
    "minDuration": 0
  },
  "telegram": {
    "enabled": false
  },
  "events": {
    "permission": { "sound": true, "notification": true, "command": true, "bell": false, "telegram": true },
    "complete": { "sound": true, "notification": true, "command": true, "bell": false, "telegram": true },
    "subagent_complete": { "sound": false, "notification": false, "command": true, "bell": false, "telegram": false },
    "error": { "sound": true, "notification": true, "command": true, "bell": false, "telegram": true },
    "question": { "sound": true, "notification": true, "command": true, "bell": false, "telegram": true },
    "user_cancelled": { "sound": false, "notification": false, "command": true, "bell": false, "telegram": false },
    "plan_exit": { "sound": true, "notification": true, "command": true, "bell": false, "telegram": true },
    "session_started": { "sound": true, "notification": false, "command": true, "bell": false, "telegram": false },
    "user_message": { "sound": true, "notification": false, "command": true, "bell": false, "telegram": false },
    "client_connected": { "sound": true, "notification": false, "command": true, "bell": false, "telegram": 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"
}
  • 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
  • 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, "telegram": true },
    "complete": { "sound": true, "notification": true, "command": true, "bell": false, "telegram": true },
    "subagent_complete": { "sound": false, "notification": false, "command": true, "bell": false, "telegram": false },
    "error": { "sound": true, "notification": true, "command": true, "bell": false, "telegram": true },
    "question": { "sound": true, "notification": true, "command": true, "bell": false, "telegram": true },
    "user_cancelled": { "sound": false, "notification": false, "command": true, "bell": false, "telegram": false },
    "plan_exit": { "sound": true, "notification": true, "command": true, "bell": false, "telegram": true },
    "session_started": { "sound": true, "notification": false, "command": true, "bell": false, "telegram": false },
    "user_message": { "sound": true, "notification": false, "command": true, "bell": false, "telegram": false },
    "client_connected": { "sound": true, "notification": false, "command": true, "bell": false, "telegram": 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'.

telegram controls whether Telegram sends for that event when telegram.enabled is true and env allowlists are configured.

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)

Telegram notifications

Telegram support is outbound-only in this first pass. The plugin sends messages to allowlisted Telegram users/chats, but it does not read Telegram commands or start long-polling control loops yet.

Enable Telegram in ~/.config/opencode/opencode-notifier.json:

{
  "telegram": {
    "enabled": true
  },
  "events": {
    "permission": { "telegram": true },
    "complete": { "telegram": true },
    "error": { "telegram": true }
  }
}

Put secrets and allowlists in ~/.config/opencode/opencode-notifier.env:

TELEGRAM_BOT_TOKEN=123456789:your-bot-token
TELEGRAM_LONG_POLLING=true
TELEGRAM_AUTHORIZED_USER_IDS=<your-user-id>,<another-user-id>
TELEGRAM_AUTHORIZED_CHAT_IDS=<your-chat-id>,<another-chat-id>
  • TELEGRAM_BOT_TOKEN is required to send messages, must use Telegram's 123456789:token format, and is never stored in JSON config.
  • TELEGRAM_LONG_POLLING defaults to true, but is informational only until inbound commands are implemented.
  • TELEGRAM_AUTHORIZED_USER_IDS and TELEGRAM_AUTHORIZED_CHAT_IDS scope delivery. Empty allowlists send nothing. User IDs must be positive integers; chat IDs must be non-zero integers.
  • Negative chat IDs are valid. Telegram supergroups/channels commonly use IDs like -1001234567890.
  • OPENCODE_NOTIFIER_ENV_PATH can point at a different env file.

Telegram TUI controls

The TUI entrypoint is exported as opencode-telegram-notifications/tui. In OpenCode TUI, press ctrl+p and select Telegram: Enabled or Telegram: Disabled to open the Telegram settings screen.

The TUI can toggle telegram.enabled and per-event events.<event>.telegram values with selection or Enter/Return on the focused row. Esc exits the settings screen. It shows whether the bot token is present, long-polling value, allowlist counts, and the env/config paths. It does not display or edit secrets.

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.

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

If Opencode does not update the plugin or there is an issue with the cache version:

# Linux/macOS
rm -rf ~/.cache/opencode/packages/opencode-telegram-notifications
rm -rf ~/.cache/opencode/node_modules/opencode-telegram-notifications

# Windows
Remove-Item -Recurse -Force "$env:USERPROFILE\.cache\opencode\packages\opencode-telegram-notifications"
Remove-Item -Recurse -Force "$env:USERPROFILE\.cache\opencode\node_modules\opencode-telegram-notifications"

Then restart OpenCode.

Verify installation:

cat ~/.cache/opencode/node_modules/opencode-telegram-notifications/package.json | grep version

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 is actually cached:
    cat ~/.cache/opencode/node_modules/opencode-telegram-notifications/package.json | grep version

Changelog

See CHANGELOG.md

License

MIT