Winix.Notify 0.3.0

Prefix Reserved
dotnet tool install --global Winix.Notify --version 0.3.0
                    
This package contains a .NET tool you can call from the shell/command line.
dotnet new tool-manifest
                    
if you are setting up this repo
dotnet tool install --local Winix.Notify --version 0.3.0
                    
This package contains a .NET tool you can call from the shell/command line.
#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.
  • --json for machine-parseable output.
  • --describe for AI-agent discovery.
  • timeittimeit slow-cmd && notify "done"
  • peep — watch + notify on completion
  • retry — retry with notification on final failure
  • clipnotify --json | jq -r .title | clip
  • Windows-only AI-agent alternative: Toasty

See Also

  • man notify (after winix install man)
  • notify --describe for JSON metadata
  • ntfy.sh — the push notification service
Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

Version Downloads Last Updated
0.3.0 100 5/26/2026
0.3.0-rc2 108 5/10/2026

## [0.3.0] - 2026-05-10

- Initial release.

See full changelog at https://github.com/Yortw/winix/blob/main/src/notify/CHANGELOG.md