Winix.Notify
0.3.0
Prefix Reserved
dotnet tool install --global Winix.Notify --version 0.3.0
dotnet new tool-manifest
dotnet tool install --local Winix.Notify --version 0.3.0
#tool dotnet:?package=Winix.Notify&version=0.3.0
nuke :add-package Winix.Notify --version 0.3.0
notify
Cross-platform desktop notifications + ntfy.sh push, in one consistent CLI. Single native binary, no runtime, same flag surface across Windows, macOS, and Linux. Pairs naturally with long-running commands (make test; notify "tests done") and other Winix tools.
Install
Scoop (Windows)
scoop bucket add winix https://github.com/Yortw/winix
scoop install winix/notify
Winget (Windows, stable releases)
winget install Winix.Notify
.NET Tool (cross-platform)
dotnet tool install -g Winix.Notify
Direct Download
Download native binaries from GitHub Releases.
Usage
notify TITLE [BODY] [options]
Examples
# Basic desktop notification
notify "build done"
# Title and body
notify "tests done" "5 of 200 failed — see build.log"
# Critical urgency — sound + attention on every backend
notify "deploy failed" --urgency critical
# Silent low-priority
notify "scrape complete" --urgency low
# Send to ntfy.sh — desktop AND push to phone
notify "alert" --ntfy myalerts
# Set ntfy globally for the shell session
export NOTIFY_NTFY_TOPIC=alerts
notify "see you"
notify "back online"
# Push only — no desktop attempt (useful in CI / SSH)
notify "server warn" --no-desktop --ntfy phone
# Self-hosted ntfy with auth
notify "deploy ok" --ntfy deploys --ntfy-server https://ntfy.example.com --ntfy-token tk_xyz
# Strict mode — exit non-zero if any backend fails
notify "important" --ntfy alerts --strict
# JSON output for scripts
notify "build done" --json
# {"title":"build done","urgency":"normal","backends":[{"name":"windows-toast","ok":true}]}
# Compose with other tools
make test && notify "tests done"
timeit slow-script.sh && notify "done"
notify "release published" --ntfy phone | tee log.txt
Options
| Flag | Default | Description |
|---|---|---|
--urgency LEVEL |
normal |
low, normal, or critical. |
--icon PATH |
none | Icon path. Best-effort per backend (see below). |
--ntfy TOPIC |
env NOTIFY_NTFY_TOPIC |
Send to ntfy.sh on TOPIC. |
--ntfy-server URL |
env NOTIFY_NTFY_SERVER or https://ntfy.sh |
Override server (self-hosted). |
--ntfy-token TOKEN |
env NOTIFY_NTFY_TOKEN |
Bearer auth for self-hosted ntfy. |
--no-desktop |
off | Suppress the desktop backend. |
--no-ntfy |
off | Suppress ntfy even if env var is set. |
--strict |
off | Exit non-zero if any configured backend fails (default: best-effort). |
--json |
off | Emit JSON output to stdout. |
--describe |
Emit structured JSON metadata for AI agents. | |
--help -h |
Show help and exit. | |
--version -v |
Show version and exit. | |
--color WHEN |
auto |
auto, always, never. Respects NO_COLOR. |
--no-color |
Equivalent to --color never. |
Backend Behaviour
| Behaviour | Windows | macOS | Linux | ntfy.sh |
|---|---|---|---|---|
| Implementation | Inline PowerShell + WinRT toast XML | osascript -e 'display notification ...' |
notify-send shellout |
HTTP POST |
| Title + body | yes | yes | yes | yes (Title header) |
--urgency low |
silent toast | silent | -u low |
priority 2 |
--urgency normal |
default toast | silent | -u normal |
priority 3 |
--urgency critical |
urgent scenario |
sound Submarine |
-u critical |
priority 5 |
--icon PATH |
yes (file path) | ignored — bundle required | yes (path or named) | not applicable |
| Cold-start latency | ~400ms (PowerShell startup) | ~50ms | ~5ms | ~100-300ms (network) |
Windows specifics
The Windows backend creates a per-user Start Menu shortcut at %APPDATA%\Microsoft\Windows\Start Menu\Programs\Winix Notify.lnk on first invocation. This is an Action Center requirement — toasts won't display unless the calling process has an AppUserModelID (AUMID) that resolves to a Start Menu shortcut. The shortcut is created idempotently (skipped if it already exists) and is harmless to delete; it'll be recreated on the next run.
If the shortcut can't be created (locked-down profile, AV blocking lnk write, COM init failure), notify returns a typed backend failure with the captured diagnostic — the toast does not silently no-op. Recovery hint: try running notify once interactively, or check Programs folder permissions.
Toasts appear in the standard Action Center / notification flyout. Click behaviour: launches notify.exe (the AUMID target) which is a no-op since notify is fire-and-forget.
The PowerShell-shellout latency (~400ms) is the trade-off for not requiring a Windows-only TFM split — modern .NET cannot directly marshal IInspectable (the WinRT base interface). A future v2 may migrate to the official WinRT projection if sub-100ms latency becomes important.
macOS specifics
osascript is the only viable path on macOS — UNUserNotificationCenter requires a signed app bundle which a loose CLI binary can't provide. As a result, --icon is ignored on macOS.
Linux specifics
Requires notify-send (libnotify CLI) on PATH. Install:
- Debian/Ubuntu:
sudo apt install libnotify-bin - Fedora:
sudo dnf install libnotify - Arch:
sudo pacman -S libnotify
If notify-send is missing, notify exits with a clear install hint.
Headless / SSH usage: pair with --no-desktop --ntfy TOPIC since libnotify needs a D-Bus session.
ntfy.sh Integration
ntfy.sh is a free, self-hostable pub-sub push notification service. Topics are URL paths — anyone who knows the topic name can publish or subscribe. Treat the topic name as a password.
# Subscribe in browser, app (Android/iOS), or curl:
curl -s https://ntfy.sh/myalerts/json
# Publish:
notify "build done" --ntfy myalerts
Self-hosted ntfy
Self-hosted ntfy supports Bearer-token auth for access control:
notify "deploy ok" \
--ntfy deploys \
--ntfy-server https://ntfy.example.com \
--ntfy-token tk_xyz
Or set globally via env:
export NOTIFY_NTFY_SERVER=https://ntfy.example.com
export NOTIFY_NTFY_TOKEN=tk_xyz
export NOTIFY_NTFY_TOPIC=deploys
notify "deploy ok" # uses all three from env
ntfy error diagnostics
When ntfy returns a non-2xx status (401 unauthorized, 403 forbidden, 429 rate-limited, etc.), notify reads up to 2 KB of the response body and includes a 512-character snippet in the stderr error message. So a 401 from a self-hosted server with auth misconfigured will surface the server's "topic requires auth" / "rate limited" detail rather than just the bare status code. The body read is bounded to defend against hostile or misconfigured servers returning multi-GB responses.
Why both desktop and ntfy fire
When you set NOTIFY_NTFY_TOPIC and run notify, both the desktop notification AND the ntfy push fire in parallel. Rationale: at desk → see desktop; stepped out → phone catches it. The env var is the global "I want remote alerts on" switch; per-call --no-desktop / --no-ntfy are the suppression escape hatches.
Headless / SSH / CI
Inside an SSH session or CI runner there's no display, so the Linux desktop backend will fail with a D-Bus error. Recommended:
notify --no-desktop --ntfy alerts "build $BUILD_NUMBER ok"
The stderr warning from the failed desktop backend is honest about what happened, and best-effort mode keeps exit 0 as long as ntfy succeeded. Add --strict if you want CI to fail when ntfy fails too.
Exit Codes
| Code | Meaning |
|---|---|
| 0 | Success — at least one configured backend succeeded. |
| 1 | --strict mode and at least one backend failed. |
| 125 | Usage error — bad flags, missing TITLE, no backends configured. |
| 126 | All backends failed, or runtime error (generic catch-all). |
Exit 1 is reserved for --strict-mode failures and never used for runtime errors. Scripts of the shape notify ... --strict || alert-loud can rely on 1 meaning "at least one delivery channel failed" — runtime crashes return 126 instead.
Concurrent backend isolation
When multiple backends run in parallel (desktop + ntfy), one backend throwing an exception can no longer corrupt the batch — the dispatcher converts any backend's exception to a per-backend BackendResult with a clear "violated never-throw contract" diagnostic. So a typo'd --ntfy-server URL won't mask a successful desktop notification with a process-crash error. Cooperative cancellation (the internal 15-second timeout) is also converted to per-backend "cancelled before completion" results so partial successes are preserved.
Differences from notify-send
- Cross-platform — same flag surface on Windows, macOS, Linux.
- ntfy.sh integration — push to phone in the same call.
--jsonfor machine-parseable output.--describefor AI-agent discovery.
Related Tools
timeit—timeit slow-cmd && notify "done"peep— watch + notify on completionretry— retry with notification on final failureclip—notify --json | jq -r .title | clip- Windows-only AI-agent alternative: Toasty
See Also
man notify(afterwinix install man)notify --describefor JSON metadata- ntfy.sh — the push notification service
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
This package has no dependencies.
## [0.3.0] - 2026-05-10
- Initial release.
See full changelog at https://github.com/Yortw/winix/blob/main/src/notify/CHANGELOG.md