Skip to main content

termnotify

Desktop notifications through your terminal, the way OpenCode does it. Zero dependencies, pure Python, no permission prompts, no code signing.

from termnotify import notify

notify("Build finished", title="CI")
$ termnotify "Build finished" -t CI
$ termnotify --detect
osc777

Why this exists

OpenCode does not use osascript, terminal-notifier or pyobjc for its notifications. Its TUI writes an OSC escape sequence to the terminal and the terminal (Ghostty, iTerm2, kitty, WezTerm, ...) turns it into a native notification. Compared to the system-API route that the existing Python libraries take, that means:

  • no code signing or app bundle required (see the macOS notes in desktop-notifier)
  • no notification/Automation permission prompts
  • works over SSH: the notification appears on the machine running the terminal, not on the server
  • nothing to install beyond Python itself

As far as we could tell there is no Python library that does this, so this is one. It mirrors the detection heuristics and payload formats of the OpenTUI terminal backend used by OpenCode: packages/native/src/terminal.zig (called from packages/tui/src/feature-plugins/system/notifications.ts in OpenCode).

Protocols

Protocol Sequence Terminals
OSC 99 ESC ] 99 ; ... with base64 title/body kitty, foot
OSC 777 ESC ] 777 ; notify ; title ; body Ghostty, WezTerm, Warp, hterm/Blink, Contour, VTE (GNOME Terminal, Tilix, Terminator, ...), rxvt, Windows Terminal
OSC 9 ESC ] 9 ; title: body iTerm2, Apple Terminal, ConEmu

The protocol is detected from the environment (TERM, TERM_PROGRAM, TERM_FEATURES, WT_SESSION, plus VTE_VERSION as a VTE-family hint); when several match, the highest protocol priority (osc99 > osc777 > osc9) wins. No interactive capability query is performed, so detection is instant and never blocks or touches the input stream.

Force a protocol when detection fails:

$ TERMNOTIFY_PROTOCOL=osc9 termnotify "hello"

or per call: notify("hello", protocol="osc99"). Setting the variable to 0, false, off or none disables notifications entirely.

Multiplexers

  • tmux: sequences are wrapped in DCS passthrough. tmux needs set -g allow-passthrough on.
  • GNU Screen (STY set): sequences are wrapped in a Screen DCS.
  • Zellij: detection is disabled (Zellij only forwards OSC 99 and needs a query handshake), so pass protocol="osc99" explicitly if you know the terminal forwards notifications.

API

notify(message, title=None, *, protocol=None, file=None, env=None) -> bool

Writes the notification sequence. Returns True if the bytes were written to a terminal. That means the terminal was asked to notify, not that a banner appeared: the window may be focused, notifications may be muted in the terminal or the OS, etc. Returns False when no protocol was detected, the message is empty, or no terminal is reachable.

Output goes to the controlling terminal (/dev/tty) rather than stdout, so redirections and pipes don't swallow notifications and program output is never polluted. Pass a binary file to override.

detect_protocol(env=None) -> str | None

Returns the protocol notify() would use, or None.

Installation

$ pip install -e .
# or simply copy src/termnotify into your project

Requires Python 3.9+.

Limitations

  • Focus gating is up to the terminal. On macOS, Ghostty only presents a banner when its window is not focused; while focused, the notification is delivered silently to Notification Center. (OpenCode appears to work around this by only sending notifications when its TUI is blurred -- the terminal applies the same rule regardless.) To test, switch to another app right after triggering a notification.
  • No sound. OpenCode plays sounds through its own audio engine; that is not part of the terminal notification protocols.
  • Environment-based detection only. If your terminal is not recognized, set TERMNOTIFY_PROTOCOL.
  • Requires a terminal. For GUI apps without one, use a system API library such as desktop-notifier.

Development

$ python -m unittest discover -s tests -v
$ PYTHONPATH=src python -m termnotify --detect

Metadata

Release files for termnotify 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for termnotify 0.1.0
File Size Uploaded
termnotify-0.1.0.tar.gz 10.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for termnotify 0.1.0
File Interpreter ABI Platform
termnotify-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 19.7 kB

Release files / termnotify-0.1.0.tar.gz

Download URL termnotify-0.1.0.tar.gz
Size 10.4 kB
Tags Source
SHA-256 checksum
How to use checksums
cbee93d13c6c6aadbe69403c8990edd41dd89b8956551bc39ba2213548761aa2
BLAKE2b-256 checksum
How to use checksums
d3c34e6f898ccfe43008ccf64a4977c18682a3133c10d388c1b34b4688b31f6c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / termnotify-0.1.0-py3-none-any.whl

Download URL termnotify-0.1.0-py3-none-any.whl
Size 9.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
289e3b6c052fd892b1fdbd4b27241bcc2afa3a997063900e3f5d823892a349b1
BLAKE2b-256 checksum
How to use checksums
02f09510e18f403eb9270989cfbaa7b06556e5d97ff3111e72899ebabe4d11a8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page