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-agent-push

v1.0.4

Published

Pi extension: push agent idle / interrupted / need-input events to Bark, Feishu, WeCom, DingTalk, ntfy, or a generic webhook.

Readme

pi-agent-push

Push a message to your phone or group chat when the pi main agent settles: Bark, Feishu, WeCom, DingTalk, ntfy, or a generic Webhook. All payloads are plain text.

Install

pi install npm:pi-agent-push
# or from GitHub:
pi install git:github.com/kiim-wong/pi-agent-push

Restart pi or run /reload after install.

Config

Pick one:

  1. CLI (recommended)
    /push set ntfy topic=your-topic enabled=true
    /push test ntfy
  2. Config file
    Create config.json in the extension directory (see package config.example.json).
    Or point env PI_AGENT_PUSH_CONFIG at a custom path.

The repo / npm package does not ship real keys; keep your local config.json private.


Quick start

  1. Open config.json, fill in keys for the channels you want, set that channel's "enabled" to true
  2. Restart pi, or run /reload in the session
  3. /push test — prints HTTP results per channel
/push                         status
/push list                    list channels
/push get <channel>           view config (secrets redacted)
/push enable|disable <channel> toggle channel (writes config.json)
/push set <channel> k=v [k=v…] set topic/token/deviceKey/url…
/push test [channel]          test (optionally one channel)
/push events [k=on|off…]      global event toggles
/push on|off                  session master switch (not persisted)
/push help

Channel selector: ntfy / name / ntfy#1 / index.

/push set ntfy topic=my-topic token=tk_xxx enabled=true
/push enable ntfy
/push test ntfy

When it pushes

| Event | Trigger | Default | Message | |---|---|---|---| | idle | Model finished, auto-retry and auto-compaction done, pi is truly waiting for you | on | pi ready · output finished, waiting for input | | interrupted | Turn cancelled with Esc | on | pi interrupted · turn cancelled | | interrupted | Turn errored (after retries exhausted) | on | pi interrupted · runtime error: 401 authentication_error: invalid x-api-key | | needInput | A question tool (ask_user_question, plan_mode_question) was called | on | pi needs input · pi is waiting for your answer | | exit | Session exit (Ctrl+C / Ctrl+D / /quit) | off | pi exited · session ended |

Both idle and interrupted are decided inside pi's agent_settled event, so auto-retries do not spam — one user-visible "pi settled" maps to one message.

Channel config

config.json channels is an array; you can configure many (including multiple of the same type). Every channel supports name (log display name), enabled, timeoutMs, and events (overrides global toggles).

Bark (iOS)

{ "type": "bark", "enabled": true, "deviceKey": "$BARK_KEY",
  "server": "https://api.day.app", "sound": "bell", "group": "pi", "level": "active" }

deviceKey is the key shown in the app. For a self-hosted bark-server, change server (uses POST /:device_key, compatible with V1/V2 servers).

Feishu group bot

{ "type": "feishu", "enabled": true,
  "url": "https://open.feishu.cn/open-apis/bot/v2/hook/xxxx", "secret": "" }

secret is only needed when signature verification is enabled. With custom keywords, put the keyword in template.

WeCom group bot

{ "type": "wecom", "enabled": true,
  "url": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxxx" }

DingTalk group bot

{ "type": "dingtalk", "enabled": true,
  "url": "https://oapi.dingtalk.com/robot/send?access_token=xxxx", "secret": "SECxxxx" }

For sign mode, set secret; for keyword mode, put the keyword in template, otherwise DingTalk returns errcode=310000 (the plugin logs this as a failure).

ntfy

{ "type": "ntfy", "enabled": true, "topic": "$NTFY_TOPIC",
  "server": "https://ntfy.sh", "token": "$NTFY_TOKEN",
  "priority": "default", "tags": "pi,computer" }

On the public server, topic is effectively a password — use a hard-to-guess name. For self-hosted ntfy, change server. Set token when you need an access token (adds Authorization: Bearer automatically). priority accepts 1-5 or min|low|default|high|max.

Generic Webhook

{ "type": "webhook", "name": "my-hook", "enabled": true,
  "url": "https://example.com/push",
  "method": "POST",
  "headers": { "Authorization": "Bearer ${MY_TOKEN}" },
  "body": { "text": "{{text}}", "meta": { "event": "{{event}}", "project": "{{project}}" } },
  "events": { "idle": false, "interrupted": true } }

Prefer writing body as a JSON object: placeholders are substituted into the structure, then the whole thing is serialized, so quotes and newlines in error text never break JSON. String templates are also supported and auto-escaped by contentType. Placeholders in url are URL-encoded.

All options

| Key | Default | Description | |---|---|---| | enabled | true | Master switch | | modes | ["tui"] | pi run modes allowed to push; see below | | timeoutMs | 5000 | Per-request timeout | | shutdownTimeoutMs | 2000 | Max block time to send on exit | | dedupeMs | 3000 | Same content only sent once within this window | | minDurationSec | 0 | Skip idle when turn is shorter than this (interrupts still fire) | | maxTextChars | 500 | Body truncation length | | titleTemplate | pi {{status}} | Title template (Bark) | | template | pi {{status}} · {{reason}} | Body template | | events | see table above | Global event toggles | | needInputTools | ["ask_user_question", "plan_mode_question"] | Which tools count as "asking you" | | debug | false | Log successes too |

Placeholders: {{status}} {{reason}} {{text}} {{title}} {{event}} {{cwd}} {{project}} {{duration}} {{session}} {{model}} {{host}} {{time}} {{date}}

Values written as "$VAR" are replaced entirely from the environment; "Bearer ${VAR}" does in-string substitution — keys need not live on disk.

The config file is hot-read by mtime; edits do not need /reload (only plugin code changes do).

Why modes defaults to only tui

pi subagents run as pi --mode json -p child processes, and global extensions load there too. If you allow json/print, every finished subagent would push — pure noise. Add print only when you really want batch-job notifications.

Troubleshooting

  • /push test shows each channel's HTTP status or error reason directly
  • Failures are written to push.log (same directory as config.json); with "debug": true, successes are logged too
  • Group bots that return HTTP 200 but business failure (bad signature, missing keyword, rate limit) are treated as failures and logged with the error code — never pretended as success

Known limits

  • SIGKILL / power loss / hard-killed terminal get no notification: pi does not fire session_shutdown, so nothing in-process can send
  • pi itself crashing (uncaughtException) also gets none: pi's crash handler registers first and calls process.exit(1) synchronously, so async requests cannot leave
  • Only tool-shaped questions (needInputTools) are detected; confirm dialogs from other extensions are not

Self-test

cd ~/.pi/agent/extensions/pi-agent-push && node test/run.ts   # needs Node >= 23

Spins up a local HTTP server that pretends to be each channel and runs assertions: signature algorithms (fixed vectors matching official algorithms), per-channel payload shapes, dedupe, timeout, mode isolation, JSON escaping, per-channel event overrides, command behavior, etc.


中文

pi 主 agent 停下来时,把消息推到手机 / 群:Bark、飞书、企业微信、钉钉、ntfy、通用 Webhook。全部纯文本

安装

pi install npm:pi-agent-push
# 或从 GitHub:
pi install git:github.com/kiim-wong/pi-agent-push

安装后重启 pi 或执行 /reload

配置

任选其一:

  1. 命令行(推荐)
    /push set ntfy topic=your-topic enabled=true
    /push test ntfy
  2. 配置文件
    在扩展目录创建 config.json(可参考包内 config.example.json)。
    也可用环境变量 PI_AGENT_PUSH_CONFIG 指向自定义路径。

仓库 / npm 包不包含真实 key;本地 config.json 请自行保管。


快速开始

  1. 打开 config.json,把要用的渠道填上 key,并把该渠道的 "enabled" 改成 true
  2. 重启 pi,或在会话里执行 /reload
  3. /push test —— 会逐个渠道打出 HTTP 结果
/push                         状态
/push list                    列出渠道
/push get <渠道>              查看配置(密钥脱敏)
/push enable|disable <渠道>   开关渠道(写 config.json)
/push set <渠道> k=v [k=v…]   设置 topic/token/deviceKey/url…
/push test [渠道]             测试(可只测一个)
/push events [k=on|off…]      全局事件开关
/push on|off                  当前会话总开关(不写盘)
/push help

渠道选择器:ntfy / 名称 / ntfy#1 / 序号。

/push set ntfy topic=my-topic token=tk_xxx enabled=true
/push enable ntfy
/push test ntfy

什么时候会推送

| 事件 | 触发时机 | 默认 | 文案 | |---|---|---|---| | idle | 模型回答完、自动重试和自动压缩都结束,pi 真正在等你 | 开 | pi 已就绪 · 输出结束,等待输入 | | interrupted | 本轮被 Esc 取消 | 开 | pi 已中断 · 本轮被取消 | | interrupted | 本轮报错(重试用尽后) | 开 | pi 已中断 · 运行出错:401 authentication_error: invalid x-api-key | | needInput | 调用了提问工具(ask_user_questionplan_mode_question) | 开 | pi 需要确认 · pi 正在等你回答问题 | | exit | 会话退出(Ctrl+C / Ctrl+D / /quit) | | pi 已退出 · 会话结束 |

idleinterrupted 都在 pi 的 agent_settled 事件里判定,所以自动重试不会重复推送——一次用户可感知的"pi 停下来了"只对应一条消息。

渠道配置

config.jsonchannels 是数组,可以配多个(含同类型多个)。每个渠道都支持 name(日志显示名)、enabledtimeoutMsevents(覆盖全局开关)。

Bark(iOS)

{ "type": "bark", "enabled": true, "deviceKey": "$BARK_KEY",
  "server": "https://api.day.app", "sound": "bell", "group": "pi", "level": "active" }

deviceKey 是 App 里那串 key。自建 bark-server 改 server 即可(用的是 POST /:device_key,V1/V2 服务端都兼容)。

飞书群机器人

{ "type": "feishu", "enabled": true,
  "url": "https://open.feishu.cn/open-apis/bot/v2/hook/xxxx", "secret": "" }

只有开启了「签名校验」才需要 secret。用「自定义关键词」的话,把关键词写进 template 里。

企业微信群机器人

{ "type": "wecom", "enabled": true,
  "url": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxxx" }

钉钉群机器人

{ "type": "dingtalk", "enabled": true,
  "url": "https://oapi.dingtalk.com/robot/send?access_token=xxxx", "secret": "SECxxxx" }

加签模式填 secret;关键词模式请把关键词写进 template,否则钉钉会返回 errcode=310000(插件会把它当作失败记进日志)。

ntfy

{ "type": "ntfy", "enabled": true, "topic": "$NTFY_TOPIC",
  "server": "https://ntfy.sh", "token": "$NTFY_TOKEN",
  "priority": "default", "tags": "pi,computer" }

topic 在公共服务器上相当于密码,请用难猜的名字。自建 ntfy 改 server。 需要 access token 时填 token(自动加 Authorization: Bearer)。 priority 支持 1-5min|low|default|high|max

通用 Webhook

{ "type": "webhook", "name": "my-hook", "enabled": true,
  "url": "https://example.com/push",
  "method": "POST",
  "headers": { "Authorization": "Bearer ${MY_TOKEN}" },
  "body": { "text": "{{text}}", "meta": { "event": "{{event}}", "project": "{{project}}" } },
  "events": { "idle": false, "interrupted": true } }

body 建议写成 JSON 对象:占位符先替换进结构、再整体序列化,错误信息里的引号和 换行永远不会破坏 JSON。写成字符串模板也支持,会按 contentType 自动转义。 url 里的占位符会做 URL 编码。

全部配置项

| 键 | 默认 | 说明 | |---|---|---| | enabled | true | 总开关 | | modes | ["tui"] | 允许推送的 pi 运行模式,见下方说明 | | timeoutMs | 5000 | 单次请求超时 | | shutdownTimeoutMs | 2000 | 退出时最多阻塞多久发消息 | | dedupeMs | 3000 | 相同内容在此窗口内只发一次 | | minDurationSec | 0 | 本轮短于该秒数时不发 idle(中断不受影响) | | maxTextChars | 500 | 正文截断长度 | | titleTemplate | pi {{status}} | 标题模板(Bark 用) | | template | pi {{status}} · {{reason}} | 正文模板 | | events | 见上表 | 全局事件开关 | | needInputTools | ["ask_user_question", "plan_mode_question"] | 哪些工具算"在问你" | | debug | false | 成功也记日志 |

占位符:{{status}} {{reason}} {{text}} {{title}} {{event}} {{cwd}} {{project}} {{duration}} {{session}} {{model}} {{host}} {{time}} {{date}}

值写成 "$VAR" 会整体取环境变量,写成 "Bearer ${VAR}" 可嵌入替换——key 不必落盘。

配置文件按 mtime 热读,改完不需要 /reload(改插件代码才需要)。

为什么 modes 默认只有 tui

pi 的 subagent 是用 pi --mode json -p 拉子进程跑的,全局扩展在子进程里同样会加载。 如果放开 json/print,每个 subagent 跑完都会推一条,纯噪音。确实想给批处理任务 推送时,再把 print 加进去。

排错

  • /push test 会直接显示每个渠道的 HTTP 状态或错误原因
  • 失败会记到 push.log(和 config.json 同目录);"debug": true 时成功也记
  • 群机器人 HTTP 200 但业务失败(签名错、缺关键词、限流)会被判定为失败并记录 错误码,不会假装成功

已知限制

  • SIGKILL / 断电 / 终端被强杀收不到通知:这类情况 pi 不会触发 session_shutdown, 进程内无从发出请求
  • pi 自身崩溃(uncaughtException)同样收不到:pi 的崩溃处理器先注册、且同步 process.exit(1),异步请求来不及发出
  • 只能感知工具形式的提问(needInputTools);其它扩展直接弹的确认框拦不到

自测

cd ~/.pi/agent/extensions/pi-agent-push && node test/run.ts   # 需要 Node >= 23

会起一个本地 HTTP 服务假扮各渠道,跑自测断言:签名算法(对拍官方算法的固定 向量)、各渠道报文结构、去重、超时、模式隔离、JSON 转义、渠道级开关覆盖、命令行为等。