@pi-unipi/notify
Cross-platform notification extension for Pi — native OS, Gotify, and Telegram notifications for agent lifecycle events
Package details
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_usertool. - 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:
- Open
/unipi:settings. - Select the Notify group.
- Open the page for
gotify,telegramorntfy. - Set Enabled to on.
- Enter the server URL, token, topic or chat ID.
- 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-hostgenerates 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.tsencrypts the ntfymessagewith AES-256-GCM and pinstitleto a fixed neutral"UniPi";clickstill carries the deep link (host/pid/dialogquery params only, never question text). No key yet → unchanged plaintext/appDetailbehaviour. - 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 inunipi-app'sdocs/m6/PROTOCOL.md. - Fan-out: the UniPi app can also register a
UnifiedPush endpoint (
notify_registeron the host) so notifications reach it even when it has no ntfy topic/token of its own —app-endpoints.tspersists the list at~/.unipi/config/ notify/app-endpoints.json, and every send fans out to each registered endpoint in addition to the configured topic.