@agileguy/nitfy
v1.0.0
Published
ntfy CLI client — messages, send, unread, read, watch, health, topics, completions. Zero dependencies, runs with bun.
Maintainers
Readme
nitfy
A fast, zero-dependency CLI client for ntfy - the simple HTTP push notification service.
Works with self-hosted ntfy servers and ntfy.sh. Supports named profiles, topic groups, watch mode with audio alerts, and shell completions.
Installation
Run with Bun (no build step)
git clone https://github.com/agileguy/nitfy
cd nitfy
bun install
bun run ntfy.ts messagesAdd a shell alias for convenience:
alias ntfy='bun run /path/to/nitfy/ntfy.ts'Compile to a standalone binary
Requires Bun 1.0+. The compiled binary runs without Bun installed.
bun run build
# produces: ./ntfy
cp ntfy /usr/local/bin/The ntfy binary is excluded from version control via .gitignore.
Quick Start
Option A: Environment variables (simplest)
Set these in your shell profile (~/.bashrc, ~/.zshrc, or ~/.profile):
export NTFY_URL=https://ntfy.sh
export NTFY_TOPIC=my-alerts
export NTFY_USER=alice # optional for private topics
export NTFY_PASSWORD=secret # optional for private topicsThen use the CLI immediately:
ntfy messages
ntfy send "Hello from ntfy"
ntfy healthOption B: Named profiles (recommended)
Create a named profile for persistent, multi-server configuration:
ntfy config add home \
--url https://ntfy.example.com \
--user alice \
--password secret \
--topic alertsThe first profile added becomes the active profile automatically.
Configuration
Configuration is stored at ~/.config/ntfy-cli/config.json (mode 0600). State (last-read timestamps) is stored at ~/.config/ntfy-cli/state.json.
Profile commands
# Add a profile
ntfy config add <name> --url <url> [--user <user>] [--password <pass>] --topic <topic>
# List all profiles
ntfy config list
# Switch active profile
ntfy config use <name>
# Show active profile (password masked)
ntfy config show
# Remove a profile
ntfy config remove <name>Example: multiple servers
ntfy config add home \
--url https://ntfy.home.example.com \
--user dan \
--password s3cr3t \
--topic alerts
ntfy config add work \
--url https://ntfy.sh \
--user work-user \
--password work-pass \
--topic work-alerts
ntfy config add public --url https://ntfy.sh --topic public-announcements
# Use work by default
ntfy config use work
# Override per-command
ntfy messages --server home
ntfy send "Deploy done" --server workCommands Reference
All commands support --server <name> to override the active profile, --json for machine-readable output, --no-color to strip ANSI codes, and --quiet / -q to suppress decorative output.
messages / msg
Fetch and display messages for a topic.
ntfy messages
ntfy messages --topic alerts
ntfy messages --since 6h
ntfy messages --since 24h --priority high
ntfy messages --limit 10
ntfy messages --json
ntfy msg --topic alerts --since 1h --limit 5Flags:
--topic / -t <topic>- Topic to fetch (defaults to profile defaultTopic)--since / -s <duration>- How far back to fetch:1h,6h,24h,2d,7d,all(default:12h)--priority <level>- Filter to messages at or above priority level (1-5 or min/low/default/high/urgent)--limit <n>- Show only the most recent N messages
all
Alias that fetches the FAST-all topic if present in the profile, otherwise the first configured topic.
ntfy all
ntfy all --since 2hunread
Show messages received since the last time ntfy read was run, across all watched topics.
ntfy unread
ntfy unread --topic alerts
ntfy unread --since 6h
ntfy unread --json
ntfy unread --count # print integer count for single topic
ntfy unread --total # print total count across all topicsFlags:
--topic <topic>- Check only this topic--since <duration>- Override since period (ignores last-read timestamp)--count- Print count as plain integer (per topic or total)--total- Print total count across all topics as integer
send
Send a notification message to a topic.
ntfy send "Backup completed"
ntfy send "Deploy failed" --title "CI Alert" --priority urgent
ntfy send "Weekly report" --topic reports --tags "report,weekly"
ntfy send "Reminder" --delay 30m
ntfy send "Click me" --click https://example.com
ntfy send "**Bold** text" --markdownFlags:
--topic / -t <topic>- Destination topic (defaults to profile defaultTopic)--title <title>- Notification title--priority / -p <level>- Priority:1-5ormin,low,default,high,urgent--tags <tags>- Comma-separated tags (displayed as emoji on mobile)--delay <duration>- Schedule delivery:30s,1m,5m,30m,1h,3h,12h--click <url>- URL to open when notification is tapped--attach <url>- URL of an attachment--markdown / --md- Render message body as Markdown
read
Mark topic(s) as read by updating the last-read timestamp. Affects what ntfy unread shows next time.
ntfy read # mark all profile topics as read
ntfy read --topic alerts # mark only one topic
ntfy read --all # mark all topics on all profilesunread workflow example
ntfy unread # see what's new
ntfy read # mark all as read
ntfy unread # now shows 0watch
Poll topics for new messages in real time and play an audio alert on arrival. Press Ctrl+C to stop and see a session summary.
ntfy watch
ntfy watch --topic alerts
ntfy watch --group critical # watch a named topic group
ntfy watch --interval 30 # poll every 30 seconds (default: 60)
ntfy watch --no-sound # disable audio
ntfy watch --sound /path/to/ping.wav
ntfy watch --device "Built-in Output" # macOS audio device
ntfy watch --priority high # only play sound for high+ priorityFlags:
--topic / -t <topic>- Watch a specific topic--group <name>- Watch a named topic group--interval <seconds>- Polling interval in seconds (default: 60)--no-sound- Disable audio notifications--sound <path>- Path to a custom sound file--device <name>- Audio output device (macOSafplay -d)--priority <level>- Minimum priority to trigger audio (default: all)
Audio playback uses afplay on macOS and tries play (sox) then paplay on Linux. Audio failures are non-fatal.
health
Check server health and version.
ntfy health
ntfy health --json
ntfy health --all # check all configured profiles in parallelExit code 1 if the server is unhealthy.
delete
Delete a message by its globally-unique message ID.
ntfy delete abc12345
ntfy delete abc12345 --topic alertsMessage IDs are shown in --json output. The --topic flag is accepted for compatibility but not required (IDs are globally unique).
version
ntfy versiontopics
Manage the list of watched topics for the active profile.
ntfy topics list # show topics and groups
ntfy topics list --json
ntfy topics add work-alerts # add to watch list
ntfy topics remove old-alerts # remove from watch list
ntfy topics group add critical alerts ops # create group "critical" with two topics
ntfy topics group remove criticalcompletions
Generate shell completion scripts.
Shell Completions
Shell completions are dynamically populated with your current profile names and topic names.
Bash
ntfy completions bash >> ~/.bash_completion
# or system-wide:
ntfy completions bash > /etc/bash_completion.d/ntfyZsh
ntfy completions zsh > "${fpath[1]}/_ntfy"
# then reload:
autoload -U compinit && compinitFish
ntfy completions fish > ~/.config/fish/completions/ntfy.fishWatch Mode
Watch mode is designed for ambient awareness. It polls configured topics on a set interval and plays a sound when new messages arrive.
# Watch all topics in the active profile, poll every minute
ntfy watch
# Watch a topic group with faster polling
ntfy watch --group critical --interval 10
# Silence audio but still display messages
ntfy watch --no-sound
# Play a custom sound file
ntfy watch --sound ~/sounds/chime.aiffWatch mode updates the last-read state just like ntfy read does, so ntfy unread will correctly exclude messages you saw in a watch session.
Migrating from Environment Variables
The original prototype used only environment variables:
# Old approach (env-var only)
export NTFY_URL=https://ntfy.sh
export NTFY_USER=alice
export NTFY_PASSWORD=s3cr3t
export NTFY_TOPIC=my-alerts
ntfy messagesThis still works as a fallback. But named profiles offer several advantages:
- Multiple servers with one command switch (
--server work) - Multiple topics per profile with unread tracking
- Named topic groups for batch monitoring
- Config stored securely at mode 0600
Migration steps
- Create a named profile with your existing values:
ntfy config add home \
--url "$NTFY_URL" \
--user "$NTFY_USER" \
--password "$NTFY_PASSWORD" \
--topic "$NTFY_TOPIC"- Verify the profile is active:
ntfy config list
ntfy healthRemove the env vars from your shell profile. The named profile takes precedence.
Add more topics and profiles as needed:
ntfy topics add secondary-topic
ntfy config add work --url https://ntfy.work.example.com --user bob --password ... --topic work-alertsPrecedence order
When resolving which profile to use, nitfy follows this order:
--server <name>flag (explicit override per command)- Active profile from
~/.config/ntfy-cli/config.json NTFY_URL/NTFY_USER/NTFY_PASSWORD/NTFY_TOPICenvironment variables
Environment Variables
| Variable | Purpose |
|----------|---------|
| NTFY_URL | Server base URL (fallback when no config) |
| NTFY_USER | Username (fallback) |
| NTFY_PASSWORD | Password (fallback) |
| NTFY_TOPIC | Default topic (fallback) |
| NTFY_CONFIG_DIR | Override config directory (useful for testing) |
| NO_COLOR | Disable ANSI colors when set |
nitfy also reads NTFY_* variables from a .env file if present in the working directory.
Exit Codes
| Code | Meaning | |------|---------| | 0 | Success | | 1 | Runtime error (network, auth, server error) | | 2 | Usage error (bad arguments, missing required argument) |
Development
# Run tests
bun test
# Run in dev mode (no build)
bun run dev -- messages
# Build binary
bun run buildTests use Bun's built-in test runner with mocked fetch. Integration tests are opt-in:
bun test tests/integration/ \
--env NTFY_URL=... NTFY_USER=... NTFY_PASSWORD=... NTFY_TOPIC=...License
MIT
