@leo000001/opencode-notify-native
v1.4.6
Published
OpenCode plugin for direct native notification center alerts
Maintainers
Readme
opencode-notify-native
Direct native notification plugin for OpenCode.
When OpenCode is running outside the TUI, this plugin stays quiet by default.
What this repository ships
- npm package:
@leo000001/opencode-notify-native
Repository layout
src/: plugin source codedist/: build output used by npm package entrypoints- repository root
package.json: publishable package@leo000001/opencode-notify-native - repository root also contains docs and CI workflows
Features
- Native notifications on Windows, macOS, Linux
- Auto-silence in non-TUI runtimes to avoid duplicate frontend alerts
- Automatic event hooks:
completeerrorattention
- Defensive event payload parsing (raw + wrapped hook envelopes)
- Per-event sound profile
- Notification anti-spam controls (collapse + cooldown)
- Basic text sanitization and truncation
Notification signal policy
- Notify only terminal, user-actionable events.
- Do not notify non-terminal progress states; specifically,
session.statuswithbusy/retryis always ignored. - Treat user-initiated interrupt/cancel/abort flows as no-notify outcomes.
session.status idleis considered complete only after a recent active status for the same session.- Legacy
session.idleduplicates are suppressed whensession.status idlehas already been seen. - Complete notifications are held briefly and canceled if a
session.errorarrives right after idle, preventing false "Completed" after failures/aborts. - Idle transitions right after non-abort errors are suppressed to avoid "Error + Completed" double noise.
- Attention notifications are emitted only for unresolved prompts (
permission.asked, unresolved legacypermission.updated, andquestion.asked). - Permission and question prompts are briefly delayed and canceled if the same request is resolved immediately, so auto-approved or instantly answered flows stay quiet by default.
- Acknowledgement events (
permission.replied,question.replied,question.rejected) and legacyquestion.updatedare ignored. - When event semantics are unclear, the plugin prefers no-notify and relies on debug logs for observation.
Runtime compatibility
- This package is ESM-only (
"type": "module").
Notification content
- Title format:
OpenCode · <Session Title or Project> - Body first line:
<Completed|Error|Attention> · <summary> - Optional body lines:
Project Dir: <shortened worktree path>(controlled byshowDirectory)Session ID: <first 8 chars>(controlled byshowSessionId, default off)
Install
From npm
Add to opencode.json:
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["@leo000001/opencode-notify-native"]
}opencode.json vs tui.json
- Put plugin registration in
opencode.json(plugin: [...]). - Use
tui.jsononly for terminal UI preferences/keymaps. - This plugin is loaded from OpenCode runtime config, not from TUI-only config.
Local development install
{
"$schema": "https://opencode.ai/config.json",
"plugin": [
"file:///ABSOLUTE/PATH/TO/plugins/opencode-notify-native/dist/index.js"
]
}Use your own absolute path, but always point to this entry file:
.../opencode-notify-native/dist/index.js
Notes:
dist/is generated. Runnpm ciandnpm run buildbefore using the file URL (and re-run after making changes).
Optional config
Recommended global config:
~/.config/opencode/notify-native.config.json- Windows fallback:
%APPDATA%\\opencode\\notify-native.config.json
Optional project overrides:
<worktree>/notify-native.config.json<directory>/notify-native.config.json(when different fromworktree)<worktree>/.opencode/notify-native.config.json<directory>/.opencode/notify-native.config.json(when different fromworktree)
Note: OpenCode provides both worktree (project root) and directory (current working directory). In monorepos they can differ; this plugin checks both locations.
Compatibility names still supported:
opencode-native-notify.config.jsonopencode-notify.config.json
Resolution order (low -> high):
- Global config (
~/.config/opencode/...) <worktree>/...<directory>/...<worktree>/.opencode/...<directory>/.opencode/...OPENCODE_NOTIFY_NATIVE_CONFIG(if set)
Values are layered; later sources override earlier ones.
{
"enabled": true,
"autoSilence": {
"nonTui": true
},
"events": {
"complete": true,
"error": true,
"attention": true
},
"soundByEvent": {
"complete": true,
"error": "error",
"attention": "attention"
},
"collapseWindowMs": 3000,
"cooldownMs": 30000,
"sanitize": true,
"maxBodyLength": 200,
"showDirectory": false,
"showSessionId": false
}Notes:
autoSilence.nonTuisilences the plugin for non-TUI OpenCode runtimes, including Desktop, ACP, and server commands such asopencode webandopencode serve.- Backward compatibility: older configs can still use
autoSilence.desktop, which is treated as an alias forautoSilence.nonTui. sanitize: trueenables best-effort redaction of token-like substrings (for exampleBearer ...).- Regardless of
sanitize, the plugin normalizes whitespace, strips control characters, and clamps lengths to keep notification backends stable. - If you set
sanitize: false, notifications may include secrets from tool output or error messages. showDirectorydefaults tofalseto reduce lock-screen/path leakage. Enable it only if directory context is worth the privacy tradeoff.
Data files
- This plugin does not persist state files.
- No queue/status bridge is used.
Platform notes
- Windows: notifications depend on system notification settings and Focus Assist.
- Windows sender label: defaults to Explorer to keep click behavior stable. Set
OPENCODE_NOTIFY_NATIVE_WINDOWS_SENDER=terminalto prefer Windows Terminal app IDs. - Windows command launch is hardened for
.cmd/shim-heavy environments (including CI) with retry and shell fallbacks. - macOS: uses
osascript(display notification) for notification UI and best-effort explicit sound playback. This backend cannot replace/group notifications at the OS level. - macOS sound names accept both legacy file names (
Glass,Basso,Funk, ...) and newer UI labels (Crystal,Mezzo,Boop, ...). Custom sounds resolve from~/Library/Sounds,/Library/Sounds, then/System/Library/Sounds. - macOS Sequoia/Tahoe note:
osascriptnotifications are delivered as Script Editor. On first use, users may need to open Script Editor once, rundisplay notification "test" with title "test", and allow notifications for Script Editor in System Settings. - Linux: requires
notify-send(for examplelibnotify-binon Debian/Ubuntu). Backend delivery falls back throughlong -> short -> plain -> minimalargument modes for compatibility. - Linux sound:
notify-sendhas no standard sound support; this plugin can only best-effort play sounds whencanberra-gtk-playis available. - Sender identity is platform-defined: macOS
osascriptdoes not support setting the sender to the current terminal, Windows sender is tied to the selected AUMID, and Linux can only provide a best-effort app name (opencode).
Debugging
If a config file exists but is ignored (for example due to invalid JSON), this plugin falls back to the last successfully loaded config (built-in defaults if none).
- Non-ENOENT config load failures emit a one-time warning to stderr.
- Unknown config keys emit a one-time warning and are ignored.
- Backend command spawn failures also emit one-time warnings to stderr.
- If macOS notifications never appear, run this one-time permission check in Script Editor:
display notification "test" with title "test". - Set
OPENCODE_NOTIFY_NATIVE_DEBUG=1for detailed debug logs (including full error messages and ignored event traces). - Config is loaded once at plugin initialization (no hot-reload during a running session).
If you are testing and expect a banner for every completion, note the defaults:
collapseWindowMscollapses bursts into one notification.cooldownMssuppresses repeats for the same session/event.- Auto-resolved permission/question requests are suppressed by default, so
attentionnotifications usually surface only for prompts that still need user action.
Implementation note:
collapseWindowMsis a fixed window starting at the first event for a given key (it does not extend on each subsequent event).- Collapse timers are
unref()'d, so a last pending collapsed notification can be dropped if OpenCode exits before the window fires.
Click behavior
- The plugin keeps click behavior as best-effort no-op.
- The plugin does not register any custom click action.
- On macOS
osascript, custom click actions are not supported; click behavior is controlled by Notification Center and varies by system settings. - On Linux and Windows, click handling still depends on the OS notification service and cannot be guaranteed as strict no-op in every environment.
- It is intentionally not designed to jump/focus the originating terminal/editor window.
- If you opt into
OPENCODE_NOTIFY_NATIVE_WINDOWS_SENDER=terminal, click behavior is still best-effort no-op but depends on Windows Terminal activation handling.
Build and test
npm install
npm run build
npm run typecheck
npm testOptional local integration check on macOS (sends a real notification):
OC_NOTIFY_NATIVE_INTEGRATION=1 npm testRelease
- CI and release workflows publish only the npm plugin.
- Tag push (
v*) runs build/typecheck/test/pack and optionally publishes to npm whenNPM_TOKENis configured. - Before release, ensure your local worktree is clean (
git status) so unpublished local edits do not skew manual verification.
Design and maintenance docs
- Runtime design:
DESIGN.md - Maintainer guardrails:
AGENTS.md
