@pi-unipi/notify

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

Packages

Package details

extensionskill

Install @pi-unipi/notify from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:@pi-unipi/notify
Package
@pi-unipi/notify
Version
3.0.3
Published
Nov 4, 2026
Downloads
5,905/mo · 1,418/wk
Author
neuron-mr-white
License
MIT
Types
extension, skill
Size
173 KB
Dependencies
2 dependencies · 3 peers
Pi manifest JSON
{
  "skills": [
    "./skills"
  ],
  "themes": [],
  "prompts": [],
  "extensions": [
    "./index.ts"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

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