---
slug: "yusukeshib-pi-notify"
source_type: "readme"
source_url: "https://cdn.jsdelivr.net/gh/yusukeshib/pi-notify@main/README.md"
repo: "https://github.com/yusukeshib/pi-notify"
source_file: "README.md"
branch: "main"
---
# @yusukeshib/pi-notify

A [pi](https://pi.dev) extension that fires a native macOS notification when the
agent finishes a turn and is ready for your input — powered by
[terminal-notifier](https://github.com/julienXX/terminal-notifier).

Stop babysitting a long run. Switch to another window, and pi taps you on the
shoulder when it's done.

## What you get

```text
┌─────────────────────────────────────────────┐
│  Pi · ✅ done            12s                 │
│  Added retry logic to the upload client …    │
└─────────────────────────────────────────────┘
```

- **Status-aware title** — `✅ done` or `❌ error`, with the elapsed turn time as
  the subtitle.
- **Context in the body** — a snippet of the agent's last message, so you know
  what happened without switching tabs.
- **Focus-aware** — if your terminal is already the frontmost app (you're
  watching it), the notification is suppressed. No spam while you read.
- **Click to return** — clicking the notification activates the terminal pi is
  running in (via terminal-notifier's `-activate`) and brings it to the front.
- **Graceful fallback** — on non-macOS hosts, or when terminal-notifier isn't
  installed, it falls back to an OSC 777 desktop notification (Ghostty, iTerm2,
  WezTerm, …).

## Install

For the rich macOS path, install terminal-notifier first:

```bash
brew install terminal-notifier
```

Then add the extension:

```bash
pi install npm:@yusukeshib/pi-notify
```

Via git (no npm publish required):

```bash
pi install git:github.com/yusukeshib/pi-notify
```

Try it without installing:

```bash
pi -e npm:@yusukeshib/pi-notify
```

## Command

### `/notify [subcommand]`

| Command                 | Effect                                                    |
| ----------------------- | --------------------------------------------------------- |
| `/notify`               | Show current settings and the active backend              |
| `/notify on` / `off`    | Enable / disable notifications                            |
| `/notify sound <name>`  | Set the notification sound (e.g. `Glass`, `Ping`) or `off`|
| `/notify min <seconds>` | Only notify for turns at least this long (default `0`)    |
| `/notify focus on`/`off`| Toggle "skip when terminal is frontmost" (default `on`)   |
| `/notify test`          | Fire a test notification right now                        |

## Configuration via environment

Defaults can be set with environment variables; the `/notify` command overrides
them for the current session.

| Variable                | Default | Meaning                                       |
| ----------------------- | ------- | --------------------------------------------- |
| `PI_NOTIFY_ENABLED`     | `1`     | Set to `0` to start disabled                  |
| `PI_NOTIFY_MIN_SECONDS` | `0`     | Minimum turn duration before notifying        |
| `PI_NOTIFY_SOUND`       | `off`   | Sound name, or `off` for silent               |
| `PI_NOTIFY_FOCUS_SKIP`  | `1`     | Set to `0` to always notify, even when focused|

## How it works

The extension tracks each turn via pi's lifecycle events: `agent_start` resets
timing, `tool_execution_end` records whether any tool errored, and `message_end`
captures the agent's last text. On `agent_end` it composes a status line and
hands it to `terminal-notifier`.

Focus detection asks macOS (`System Events`) for the frontmost app's bundle id
and compares it to the terminal pi is running in — resolved from
`__CFBundleIdentifier` (the bundle id macOS injects into GUI-launched apps,
which survives tmux/ssh better than `TERM_PROGRAM`), falling back to a
`TERM_PROGRAM` lookup.

Only interactive (`tui`) sessions notify; print and JSON modes are skipped.

## License

MIT
