pi-notify-phone
Send phone/desktop notifications from Pi through Bark or ntfy: task done/failed, questions, goal events, reminders and schedules
Package details
Install pi-notify-phone from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:pi-notify-phone- Package
pi-notify-phone- Version
0.4.0- Published
- Oct 18, 2026
- Downloads
- 198/mo · 198/wk
- Author
- voorjaar
- License
- MIT
- Types
- extension, skill
- Size
- 143.3 KB
- Dependencies
- 0 dependencies · 3 peers
Pi manifest JSON
{
"skills": [
"./skills"
],
"extensions": [
"./src/index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-notify-phone
English | 简体中文
Send notifications from the Pi coding agent to your phone - task finished, task failed, a question waiting for you, goal events, and reminders you ask the agent to set. Two transports are supported: Bark and ntfy.
Pi finishes a long task -> pi-notify-phone -> https://api.day.app/... -> your phone buzzes
Features
- Two providers: Bark (iOS) and ntfy (iOS, Android, desktop, web). Choose one in config or
with
/notify provider bark|ntfy. - Optional encryption: Bark AES-128-CBC or AES-256-GCM request bodies (the official
crypto.tsscheme), and self-hosted ntfy AES-256-GCM envelopes that the agent can decrypt back to plain text. - Automatic or manual: one master switch, one automatic switch, and a switch per scenario.
The agent can always send on request as long as the
manualscope is on. - Scenarios: task start / complete / failed / aborted,
ask_user_questionwaiting for an answer, goal start / complete / blocked / paused, background task start / complete / failed, blocking UI prompts, tool errors, reminders. Every scenario has its own switch, and the table below lists the defaults. - Reminders and scheduled notifications: relative (
in 20m), absolute (at 08:30,tomorrow 09:00), and recurring (every 1d), persisted so a restart still delivers them, and claimed per due time so several Pi sessions open at once deliver each reminder once. - Noise control: minimum run duration, per-scope throttling, duplicate suppression, body length caps, TTL so stale notifications are dropped, and a switch that silences task notifications while a goal is running.
Install
pi install npm:pi-notify-phone
Or straight from the repository:
pi install git:github.com/voorjaar/pi-notify-phone
Or point Pi at a local checkout:
pi -e /path/to/pi-notify-phone/src/index.ts
Install it once. The npm package and the git source are the same extension; loading both makes Pi reject the duplicate tool names.
Setup
Create ~/.pi/agent/pi-notify.json with at least the provider you want to use:
{
"provider": "bark",
"bark": { "key": "your-bark-device-key" }
}
Copy config.example.json for the full set of options. Then check it:
/notify status
/notify test
You can also verify delivery without a Pi session:
node scripts/send.ts "pi-notify-phone" "hello" info
Configuration
Config is merged layer by layer, later layers win:
- built-in defaults
~/.pi/agent/pi-notify.json<project>/.pi/pi-notify.json<project>/.pi/pi-notify.local.json(personal overrides;.pi/is gitignored)PI_NOTIFY_*environment variables
Invalid values are ignored and reported by /notify status instead of breaking the session.
| Key | Default | Meaning |
|---|---|---|
provider |
bark |
bark or ntfy |
enabled |
true |
Master switch. Off sends nothing, including manual sends. |
titlePrefix |
Pi |
Prefix for notification titles; null disables it. |
includeSession |
true |
Append session: <project name> to every body. |
statusColor |
accent |
Color of the footer status (a clock and the pending reminder count, shown only when something is scheduled). Any Pi theme token: accent, success, warning, error, muted, mdCode, mdLink. |
timeoutMs |
8000 |
Per-attempt HTTP timeout. |
retries |
2 |
Retries for 5xx and network errors (not for 4xx). |
bark.key / bark.keys |
- | Device key(s) from the Bark app. Multiple keys send to multiple devices. |
bark.baseUrl |
https://api.day.app |
Self-hosted Bark server. |
bark.group |
pi |
Bark group. |
bark.sound |
glass |
Bark sound. |
bark.icon |
unset | Notification icon URL. Unset keeps Bark's own icon. assets/pi-icon.png is a square pi mark you can host anywhere and point this key at. |
bark.level |
active / timeSensitive / critical |
Maps our levels to Bark interruption levels. |
bark.callOnCritical |
true |
call=1 on critical so it can break Do Not Disturb. |
bark.ttlSeconds |
600 |
ttl: drop the notification if the device is offline this long. |
bark.encryption.enabled |
false |
Encrypt the request body. |
bark.encryption.algorithm |
aes-128-cbc |
aes-128-cbc or aes-256-gcm. |
bark.encryption.key |
- | 16 chars for AES-128, 32 for AES-256. |
bark.encryption.iv |
- | Required for AES-128-CBC (16 chars). AES-256-GCM uses a random IV. |
ntfy.server |
https://ntfy.sh |
Server URL; public topics do not need a token. |
ntfy.topic |
- | Publish topic. For public ntfy.sh pick something hard to guess. |
ntfy.token |
- | k... access token for private topics. |
ntfy.user / ntfy.password |
- | HTTP basic auth, for self-hosted servers. |
ntfy.priority |
3 / 4 / 5 |
Maps our levels to ntfy priorities (1-5, 5 = max priority). |
ntfy.encryption.enabled |
false |
Send AES-256-GCM envelopes (self-hosted ntfy with cache-enc-key). |
ntfy.encryption.key |
- | Any passphrase; derived with PBKDF2-SHA256, or 32 raw key bytes as hex. |
auto.enabled |
true |
Automatic scenario notifications. |
auto.scopes |
see below | Per-scenario switches. |
auto.minRunSeconds |
0 |
Minimum length of a run before a task-complete notification is sent. 0 sends for every finished run; 180 sends only for runs of three minutes or more. |
auto.goalMinSeconds |
0 |
Same gate for goal-complete notifications, measured from the moment the goal started. |
auto.suppressTaskNotify |
false |
While a goal is running, skip run-end notifications (task complete, task failed, aborted). Goal events still fire. |
auto.throttleSeconds |
15 |
Minimum gap between notifications per scope. |
auto.maxBodyChars |
900 |
Bodies longer than this are truncated. |
auto.notifyAborted |
false |
Notify when a run is aborted. |
scheduleFile |
~/.pi/agent/pi-notify-schedule.json |
Where reminders are stored. Several sessions share it; writes are serialized through a <scheduleFile>.lock file. |
maxLateMs |
21600000 |
A reminder whose due time is more than this late is not delivered. A one-shot is dropped; a recurring one rolls forward to its next occurrence. |
logFile |
~/.pi/agent/pi-notify-log.jsonl |
One JSON line per send attempt, including skips. Read it with /notify log. |
logEnabled |
true |
Turn the send history off. |
schedules |
[] |
Predefined scheduled notifications, e.g. a daily standup ping. |
Scenario scopes (auto.scopes): one switch each. A scenario that only echoes something that just
began, or something you can already see on screen, starts off; everything that reports a result
starts on.
| Scope | Default | Fires when |
|---|---|---|
task_start |
off | A run starts (its first message) |
task_complete |
on | A run finishes, after auto.minRunSeconds |
task_failed |
on | A run ends with an API/model error |
question |
on | ask_user_question is waiting for your answer |
goal_start |
off | A goal starts (a create_goal call, or a goal continuation turn) |
goal_complete |
on | A goal completes, after auto.goalMinSeconds |
goal_blocked |
on | A goal is set blocked |
goal_paused |
on | A goal is set paused |
bg_start |
off | A background task is launched (bg_run) |
bg_complete |
on | A background task completes |
bg_failed |
on | A background task fails, is killed, or times out |
tool_error |
off | A tool call returns an error |
ui_prompt |
off | An extension opens a blocking input dialog |
reminder |
on | A scheduled reminder is due |
manual |
on | You or the agent asked for a send explicitly |
goal and bg are group names that cover a whole family at once: /notify scope goal off or
PI_NOTIFY_AUTO_SCOPES=goal:off switches all four goal scenarios. A config file written before the split
that sets "goal": false (or "bg": true) in auto.scopes is still honoured for the whole group, and a
scenario named in the same layer wins over the group. In PI_NOTIFY_AUTO_SCOPES, both noquestion and
question:off are accepted, and an unknown name drops the whole override rather than applying half of it.
One blocking ask_user_question arrives as both question and ui_prompt_start. The question
notification covers the dialog, so no separate ui_prompt push follows it.
Environment overrides (they win over the files): PI_NOTIFY_PROVIDER, PI_NOTIFY_ENABLED,
PI_NOTIFY_TITLE, PI_NOTIFY_STATUS_COLOR, PI_NOTIFY_AUTO_ENABLED, PI_NOTIFY_AUTO_SCOPES=question:off,goal:off,
PI_NOTIFY_MIN_RUN_SECONDS, PI_NOTIFY_GOAL_MIN_SECONDS, PI_NOTIFY_SUPPRESS_TASK_NOTIFY, PI_NOTIFY_THROTTLE_SECONDS, PI_NOTIFY_BARK_KEY
(comma-separated for several devices), PI_NOTIFY_BARK_BASE_URL, PI_NOTIFY_BARK_GROUP,
PI_NOTIFY_BARK_SOUND, PI_NOTIFY_BARK_ICON, PI_NOTIFY_BARK_ENCRYPT,
PI_NOTIFY_BARK_ENCRYPT_KEY, PI_NOTIFY_BARK_ENCRYPT_IV, PI_NOTIFY_BARK_ENCRYPT_ALGORITHM,
PI_NOTIFY_NTFY_SERVER, PI_NOTIFY_NTFY_TOPIC, PI_NOTIFY_NTFY_TOKEN, PI_NOTIFY_NTFY_USER,
PI_NOTIFY_NTFY_PASSWORD, PI_NOTIFY_NTFY_ENCRYPT, PI_NOTIFY_NTFY_ENCRYPT_KEY.
Use it from the agent
Ask in plain language:
- "remind me to stretch in 20 minutes"
- "notify me when the build finishes"
- "ping me every day at 09:30 that the backup ran"
The agent uses the notify tool (in, at, every, level, url, group, sound) and
notify_reminders to list, cancel, or clear scheduled notifications. notify_status tells the
agent how notifications are configured - provider, switches, pending reminders, and the last sends -
so it can tell you whether a notification is actually on its way.
Commands
/notify status summary
/notify on | off master switch (this session)
/notify auto on | off automatic scenario notifications
/notify scope <name> on|off one scenario, or a group (`goal`, `bg`)
/notify tasknotify on|off suppress task notifications while a goal is running
/notify provider bark|ntfy transport (this session)
/notify test send one notification to check delivery
/notify send <text> send now
/notify schedule <when> <text> [every <dur>] e.g. /notify schedule 09:30 standup every 1d
/notify list scheduled notifications
/notify cancel <id> cancel one
/notify clear cancel all
/notify mute [minutes] pause automatic notifications (default 30)
/notify unmute resume them
/notify log [n] last n send attempts (sent, skipped, failed)
/notify decrypt <text> decrypt a pi-notify ntfy envelope
Overrides from /notify apply to the current session; edit the config file to make them permanent.
Encryption
Bark. The extension sends the same envelope the official Bark app produces:
?c=1&zone=... plus a base64 body, where the payload is key:body (or
title:body:sound:link:group:icon:call:level) encrypted with AES-128-CBC or AES-256-GCM.
The ciphertext is padded to a multiple of 16 bytes, as Bark requires. Set the same key (and IV
for AES-128-CBC) in the Bark app under Encryption.
ntfy. Public ntfy.sh topics are plain HTTPS - anything published there is readable by
whoever knows the topic. For real confidentiality use a self-hosted ntfy with
cache-enc-key and set ntfy.encryption.enabled = true: the extension publishes a
pi-notify:v1:<iv>:<tag>:<ciphertext> envelope (AES-256-GCM, key derived from your passphrase
with PBKDF2-SHA256) that only a client holding the passphrase can read. /notify decrypt and
the agent can turn such an envelope back into readable text.
Development
node --test tests/selftest.ts
The self-test covers time parsing, config layering, both transports, both encryption paths, reminder scheduling, and the notification switches. It uses a fake transport, so it never sends real notifications.
Two live checks drive real Pi sessions and read the delivered notifications back from the send log:
node tests/livecheck.mjs --local # goal and run-end lifecycle
node tests/live-schedule-dedupe.mjs --local # reminders across several sessions at once
live-schedule-dedupe.mjs keeps its schedule store and log inside a scratch project and runs with
the transport switched off, so it is quiet; its final round re-enables it and delivers one real
reminder.
Limitations
- Notifications only fire while a Pi session is running; there is no daemon. A reminder due while
Pi is closed is delivered the next time a session starts (or, if it is more than
maxLateMslate, skipped: a one-shot is dropped and a recurring one moves to its next occurrence). - Reminders are delivered by whichever session fires first. Several sessions sharing one schedule file arm the same reminder, so the due time is claimed before the send and only one session delivers it.
- ntfy server-side scheduling (
Send At) is used for one-shot reminders when encryption is off; recurring reminders are scheduled by the extension itself. - The extension does not read your phone, so it cannot tell you whether a notification was actually delivered.
Changelog
Release notes for every version are in CHANGELOG.md.
License
MIT. See LICENSE.
Source, issues, and releases: github.com/voorjaar/pi-notify-phone.