feat(pi-notifications): add PI_NOTIFICATION_DEBUG mode with visible steer signal

- Add PI_NOTIFICATION_DEBUG=true env var
- When enabled, calls ctx.ui.steer() instead of desktop notification
- Lets you verify trigger logic in the agent loop without actual notifications
- Synced to both monorepo and auto-discovery extension paths
This commit is contained in:
2026-04-28 12:09:06 +01:00
parent ce4d6c5971
commit 45a13fd08c
6 changed files with 265 additions and 0 deletions
+38
View File
@@ -0,0 +1,38 @@
# pi-notifications
Desktop notifications for pi agent events via macOS Notification Center.
## What it does
Shows a system notification when the agent finishes a turn, so you can step away and get alerted when input is needed.
## Configuration
| Env var | Default | Description |
|---------|---------|-------------|
| `PI_NOTIFICATIONS_ENABLED` | `true` | Set to `false` to disable all notifications |
| `PI_NOTIFICATION_AGENT_END` | `true` | Notification when agent finishes |
| `PI_NOTIFICATION_TITLE` | `pi` | Notification title |
| `PI_NOTIFICATION_SOUND` | `default` | macOS sound (Bottle, Ping, Pop, etc.) or `""` for silent |
## Usage
Add to `~/.pi/agent/settings.json`:
```json
{
"packages": [
"/path/to/packages/pi-notifications"
]
}
```
Then reload pi:
```bash
/reload
```
## License
MIT
+17
View File
@@ -0,0 +1,17 @@
{
"name": "pi-notifications",
"version": "0.1.0",
"description": "Desktop notifications for pi agent events",
"type": "module",
"exports": {
".": "./src/index.ts"
},
"keywords": ["pi-package"],
"pi": {
"extensions": ["src/index.ts"]
},
"peerDependencies": {
"@mariozechner/pi-coding-agent": "*"
},
"license": "MIT"
}
+52
View File
@@ -0,0 +1,52 @@
// Desktop notifications for pi agent events
// Uses osascript (macOS) to trigger Notification Center alerts
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
import { execSync } from "node:child_process";
// Configuration via environment variables
const enabled = process.env.PI_NOTIFICATIONS_ENABLED !== "false";
const agentEndEnabled = process.env.PI_NOTIFICATION_AGENT_END !== "false";
const debug = process.env.PI_NOTIFICATION_DEBUG === "true";
const title = process.env.PI_NOTIFICATION_TITLE || "pi";
const sound = process.env.PI_NOTIFICATION_SOUND || "default";
function notify(body: string, subtitle?: string): void {
if (!enabled) return;
try {
const sub = subtitle ? `subtitle "${subtitle}"` : "";
const snd = sound ? `sound "${sound}"` : "";
execSync(
`osascript -e 'display notification "${body}" with title "${title}" ${sub} ${snd}'`.trim(),
{ stdio: "ignore" }
);
} catch {
// osascript not available (non-macOS) — silently fail
}
}
export default function (pi: ExtensionAPI) {
console.log("[pi-notifications] loaded (enabled=" + enabled + ", agentEnd=" + agentEndEnabled + ")");
pi.on("session_start", async (_event, ctx) => {
if (debug) {
ctx.ui.steer("[pi-notifications] session_start — debug mode, skipping actual notification");
return;
}
if (enabled) {
notify("pi-notifications active", "Listening for agent_end");
}
});
pi.on("agent_end", async (event, ctx) => {
if (!agentEndEnabled) return;
if (debug) {
ctx.ui.steer(`[pi-notifications] agent_end — debug mode, skipping actual notification (${event.messages?.length ?? 0} messages)`);
return;
}
console.log(`[pi-notifications] agent_end: messages=${JSON.stringify(event.messages?.map((m: any) => m.type))}`);
notify("Agent finished", `${event.messages?.length ?? 0} turns`);
});
}