npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@pi-unipi/notify

v3.0.3

Published

Cross-platform notification extension for Pi — native OS, Gotify, and Telegram notifications for agent lifecycle events

Readme

Notify

Send a notification to your desktop or phone when the agent finishes work or needs an answer.

@pi-unipi/notify · part of UniPi

What it does

  • Sends notifications to four platforms: native OS, Gotify, Telegram and ntfy.
  • Sends a notification for each enabled event, for example when a workflow ends or the agent asks a question.
  • Lets you route each event to its own platforms.
  • Gives the agent a notify_user tool.
  • Sends a question again every 2 minutes, 3 times at most, until you answer it.
  • Can write a one-line summary of the last agent message with a model of your choice ("recap").

Quick start

UniPi installs this package:

pi install npm:@pi-unipi/unipi

To install this package alone:

pi install npm:@pi-unipi/notify

Native OS notifications work with no setup. To add a phone platform:

  1. Open /unipi:settings.
  2. Select the Notify group.
  3. Open the page for gotify, telegram or ntfy.
  4. Set Enabled to on.
  5. Enter the server URL, token, topic or chat ID.
  6. Select Send test notification to make sure that it works.

For Telegram, get a bot token from @BotFather. Then enter the token and your chat ID.

Commands

| Command | What it does | |---|---| | /unipi:notify-event <event> <on\|off> | Turns one event on or off without the TUI. Run /reload after it to apply the change. |

All other controls are in /unipi:settings → Notify.

Agent tools

| Tool | What it does | |---|---| | notify_user | Sends a notification. Takes message, and optional title, priority (low, normal, high) and platforms. |

priority sets Gotify to 2, 5 or 8 and ntfy to 2, 3 or 5. Native and Telegram ignore it.

notify_user({ title: "Build failed", message: "tsc found 12 errors.", priority: "high" })

Events

| Event | Default | Sent when | |---|---|---| | workflow_end | on | A workflow command ends. | | ralph_loop_end | on | A ralph loop ends. | | mcp_server_error | on | An MCP server reports an error. | | agent_end | off | The run finished: one notification per run, sent once pi settles (after every follow-up, steer and continuation) and nothing chained a new run within 1.5 s. Not per message. While a background task, subagent or sidekick is still pending it waits and sends one "All done" instead. | | agent_settled | off | Same single "finished" notification as agent_end. Turning both on still sends one. | | memory_consolidated | off | Memory saves facts. | | session_shutdown | off | The session ends. | | ask_user_prompt | off | The agent asks you a question and waits. | | permission_request | off | A permission prompt opens. This needs @gotgenes/pi-permission-system. | | input_needed | off | The agent waits on any prompt while it runs. Covers prompts that send no event of their own — third-party ask_user tools, the permission prompt, the plan review. |

ask_user_prompt and permission_request are blocking events. The agent stops until you answer.

input_needed is the catch-all for the same situation: Pi fires it around every blocking prompt, including ones that emit no event of their own. It only fires while the agent is running — prompts you open yourself while the agent is idle (for example /unipi:settings) stay quiet — and a prompt already announced as ask_user_prompt or permission_request is not announced twice. Closing any prompt stops its reminders.

Settings

Open /unipi:settings → Notify. The file is ~/.unipi/config/notify/config.json. A project file at .unipi/config/notify/config.json overrides it.

| Key | Default | What it does | |---|---|---| | events.<event>.enabled | refer to Events | Turns the event on or off. | | events.<event>.platforms | [] | Platforms for the event. An empty list means all enabled platforms. | | native.enabled | true | Turns native OS notifications on or off. | | native.suppressWhenFocused | false | Stops native notifications when the Pi window has focus. Works on Windows only. | | gotify.serverUrl, gotify.appToken, gotify.priority | priority 5 | Gotify server, token and priority (0–10). | | telegram.botToken, telegram.chatId | unset | Telegram bot and chat. | | ntfy.serverUrl, ntfy.topic, ntfy.token, ntfy.priority | https://ntfy.sh, priority 3 | ntfy server, topic, token and priority (1–5). | | ntfy.appDetail | "minimal" | "minimal" sends a generic "Tap to open in UniPi" body (just the deep link); "full" sends the real question text. Controls what goes inside the encrypted payload too (see below) — never changes whether encryption happens. | | silenceAfterInput.enabled | false | Stops notifications for a time after you press a key. | | silenceAfterInput.windowMs | 10000 | Length of that quiet time, in milliseconds. | | silenceAfterInput.platforms | ["native"] | Platforms to keep quiet. | | renotify.enabled | true | Sends a blocking event again until you answer. | | renotify.intervalMs | 120000 | Time between two reminders, in milliseconds. | | renotify.maxRepeats | 3 | Number of reminders after the first notification. 0 sends no reminders. | | recap.enabled | false | Summarizes the last agent message for agent_end and agent_settled. | | recap.model | openrouter/openai/gpt-oss-20b | Model for the summary. | | recap.disableThinking | false | Asks a llama.cpp or vLLM server to skip reasoning tokens. |

How it works

A reminder has the text (still waiting) in its title and high priority. Reminders stop when one of these occurs:

  • You press a key.
  • herdr reports that the agent is not blocked.
  • The agent starts a new turn.
  • The prompt closes.
  • The session ends.

Only one reminder runs at a time. Silence after input does not apply to blocking events.

Recap sends the last message (2,000 characters at most) to the recap model with a limit of 100 tokens. If the model gives no summary, notify uses the first 100 characters of the message. A thinking model can use all 100 tokens to reason. If your server supports chat template options, set recap.disableThinking to true. Keep it false for a strict OpenAI-compatible server, because that server rejects unknown fields.

Native notifications use node-notifier: SnoreToast on Windows, terminal-notifier on macOS and notify-send on Linux.

Each sent notification emits a NOTIFICATION_SENT event on the event bus.

UniPi app push (end-to-end encrypted)

When the UniPi phone app is paired with this machine, ntfy notifications also carry a deep link so a tap opens the right chat/dialog, and — once a key exists — the whole payload is encrypted so the ntfy server (even a public one) only ever sees ciphertext:

  • Key: unipi-host generates a random 256-bit key at $UNIPI_HOST_DIR/notify-key (default ~/.unipi/app-host/notify-key) the first time the app asks for it (notify_config). This package reads the same file (notify-key.ts) — if present, platforms/ntfy.ts encrypts the ntfy message with AES-256-GCM and pins title to a fixed neutral "UniPi"; click still carries the deep link (host/pid/dialog query params only, never question text). No key yet → unchanged plaintext/appDetail behaviour.
  • Wire format: unipi1: + base64(12-byte IV + AES-256-GCM ciphertext + 16-byte tag) of a small JSON object ({title, body, kind, host, pid, session, dialogId, options?}). Full format and the host/app sides are documented in unipi-app's docs/m6/PROTOCOL.md.
  • Fan-out: the UniPi app can also register a UnifiedPush endpoint (notify_register on the host) so notifications reach it even when it has no ntfy topic/token of its own — app-endpoints.ts persists the list at ~/.unipi/config/ notify/app-endpoints.json, and every send fans out to each registered endpoint in addition to the configured topic.

See also