@gregjohnso/pi-monitor
Background shell command runner for pi. Each stdout line becomes a live TUI event that wakes the agent.
Package details
Install @gregjohnso/pi-monitor from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@gregjohnso/pi-monitor- Package
@gregjohnso/pi-monitor- Version
0.1.1- Published
- Aug 19, 2026
- Downloads
- 225/mo · 34/wk
- Author
- gregjohnso
- License
- MIT
- Types
- extension
- Size
- 49.4 KB
- Dependencies
- 0 dependencies · 3 peers
Pi manifest JSON
{
"extensions": [
"./extensions/monitor"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
Monitor Extension
Make pi event-driven. Catch errors in real time, watch logs and deploys, poll PRs — and save tokens by reacting instead of polling.
Reverse-engineered port of Claude Code's Monitor tool (announced by Anthropic on 2026-04-09). Same model, pi-native plumbing.
Problem
Background work in pi was blind. You ran something with bash, the turn
ended, and you saw a single "done" message later — no visibility into
what happened along the way. The alternative was polling: waking the
agent every N seconds with a fresh prompt to ask "did anything happen
yet?". Five-minute test run, 30-second poll = ten full LLM calls,
nine of them wasted.
Quick Win
Tell pi to monitor your dev server. One sentence:
"Monitor
npm run devfor errors and tell me the moment one shows up."
pi launches the server, attaches a background filter, and only wakes when something breaks. Zero tokens spent waiting.
The Shift
Time-driven → event-driven. Before monitor, pi checked things at intervals. With monitor, pi watches things and reacts when they happen. Same architectural difference as polling a database every five seconds vs. subscribing to a change stream. One wastes cycles. The other responds instantly.
Monitor works by launching a shell command whose stdout becomes an event stream. Each line of output is a notification that wakes the session. If the command is silent, the agent spends nothing. The moment something matches your filter it pushes into the conversation and pi starts reacting — while the underlying process keeps running.
Install
# from npm
pi install npm:@gregjohnso/pi-monitor
# or directly from git
pi install git:github.com/gregjohnso/pi-monitor
Then /reload inside pi, or start a new session.
To develop locally, symlink this repo into your extensions directory:
ln -s /absolute/path/to/pi-monitor/extensions/monitor ~/.pi/agent/extensions/monitor
Tools exposed to the LLM
| Tool | Purpose |
|---|---|
monitor |
Start a background command; stdout lines become events |
monitor_stop |
Kill a running monitor by id |
monitor_list |
Enumerate running monitors with status + activity |
monitor_read_stderr |
Pull the stderr tail for a monitor on demand (no wake) |
monitor parameters
| Param | What it controls |
|---|---|
description |
Short label shown in every notification ("errors in deploy.log"). |
command |
Shell script whose stdout is the event stream. |
timeout_ms |
Auto-kill after N ms. Default 300 000 (5 min), max 3 600 000 (1 hr). |
persistent |
If true, lives for the whole session. Stop manually with monitor_stop. |
The command is where the real work happens. Each line it prints to
stdout becomes one notification. Lines arriving within 200 ms of each
other batch into a single notification, so multi-line output from one
event groups naturally. Stderr goes to a file you can read later
but does not trigger events.
Set persistent: true for things that should live as long as your
session — dev server watchers, log tailers, PR monitors. For bounded
tasks (a test run, a deploy window), let timeout_ms auto-kill the
monitor when the window closes.
Two filter shapes
Stream filter — watch continuous output, surface matching lines
# tail logs, surface only errors
tail -f /var/log/app.log | grep --line-buffered -E "ERROR|FATAL"
# dev server: catch build errors as they appear
npm run dev 2>&1 | grep --line-buffered -E "error|Failed"
Poll-and-if — check a source on an interval, emit when something changes
# watch a PR for new comments, robust to transient failures
while true; do
gh api "repos/owner/repo/pulls/123/comments" --jq '.[-1].body' || true
sleep 30
done
# print a line only when test results change
last=""
while true; do
cur="$(make -s test 2>&1 | tail -1)" || true
if [ "$cur" != "$last" ]; then echo "$cur"; last="$cur"; fi
sleep 5
done
Both shapes follow the same rule: stdout lines are events; silence means nothing to report. pi keeps working on other things while the monitor runs quietly in the background.
Three rules
Always make pipes line-buffered.
grep --line-buffered,awk -W interactive, or wrap withstdbuf -oL. Without it, pipe buffering can delay events by minutes. This is the single most common mistake.Handle transient failures in poll loops. Append
|| trueafter each API call so one network timeout does not kill the monitor. Pair it with asleep Nbetween iterations so the loop can't trip the firehose auto-stop.Be selective with stdout. Every line becomes a conversation message and counts against context. Monitors emitting >50 lines/sec over 10 s are auto-stopped. Filter raw logs through a specific grep pattern. Never stream unfiltered output.
Poll-interval tip: use ≥30 s for remote APIs (rate limits apply), 0.5–1 s for local checks.
Token economics
A /loop checking your test suite every 2 minutes over a 10-minute run
costs 5 full LLM calls. Each loads context, processes the prompt,
and returns a response. Five calls, five charges, four with no useful
work to do.
Monitor inverts this. pi watches the test runner's output through a filter. When test #23 fails at minute 4, that failure line pushes directly into the session. pi starts diagnosing the error while tests 24–47 are still running. No wasted calls. No delayed discovery. The savings compound in long workflows — deploy pipelines, overnight builds, multi-hour CI runs.
Use cases
- Dev server error catching. Monitor your Next.js / Vite / FastAPI dev server and get notified the moment a build error or crash loop appears.
- Test suite triage. Surface failing tests the instant they fail. pi starts writing fixes while the rest of the suite finishes.
- Deploy pipeline watching. Follow CI/CD output and get pinged on failures, warnings, or specific deployment stages completing.
- PR review polling. Watch for new comments, review requests, or status checks on GitHub pull requests.
- Log monitoring. Tail production or staging logs with a filter for specific patterns. Each matching line becomes an event pi can act on immediately.
monitor vs. fire-and-forget bash
This distinction trips people up. Both run things in the background. The difference is the feedback model.
bash (in tmux, say) |
monitor |
|
|---|---|---|
| Notifications | One — when it's done | One per matching stdout line |
| Visibility during run | None | Live stream, filtered |
| Best for | "run X and ping me" | "tell me the moment X breaks" |
| Cost when nothing happens | Constant (still alive) | Zero (no events = no tokens) |
bash is fire-and-forget. Good for "run this build and tell me when
it's done." Monitor is a live stream. Good for "run this build and tell
me the moment something goes wrong." Monitor keeps pi reactive during
the process, not just after it.
Three automation layers
| Trigger | Best for | |
|---|---|---|
| Hooks | Tool events | Validation before/after tool calls |
| Schedulers | Time | Recurring work on a fixed cadence |
| Monitor | Events | Reacting to real-time output |
Hooks fire on pi's own actions (before a file edit, after a commit). Schedulers fire on a clock. Monitors fire on external events. The strongest setups combine all three: hooks enforce guardrails, schedulers handle periodic maintenance, monitors provide real-time observability on everything else.
Event semantics
- Each stdout line is one event.
- 200 ms coalesce window: lines arriving close together group into a single event so multi-line output from one source stays together.
- Stderr is never streamed. It is piped to
~/.pi/agent/monitor/<id>.stderrand exposed viamonitor_read_stderr. - Per-event truncation at 400 chars with a
…(truncated)suffix. Keep your filter patterns specific enough that 400 chars is enough signal. - Firehose auto-stop: a monitor emitting >50 lines/sec over 10 s is automatically stopped with an explanatory final event so the model can retry with a tighter filter.
- Session-scoped lifetime: children die when pi exits. By design.
Event delivery model
Events use deliverAs: "followUp", triggerTurn: true. Each event wakes
pi. If a turn is active, followUp queues the event until the current
work finishes. Otherwise, pi starts a new turn immediately. The event
appears in the transcript and enters the LLM's context.
A transient ctx.ui.notify toast displays the event during active tool
execution. A status widget shows "N monitor" / "N monitors" while any
are running.
User commands
| Command | Effect |
|---|---|
/monitors |
List running monitors with status + age |
/monitor-stop <id|all> |
SIGTERM a monitor (SIGKILL fallback after 2 s), or stop all |
/monitor-tail <id> [n] |
Dump last N stderr lines to the TUI (default 200) |
/monitor-clean |
Alias for /monitor-stop all |
Flags
| Flag | Effect |
|---|---|
--no-monitor |
Disable the extension (tools/commands not registered). |
Plan-mode interop
While plan-mode is active, a tool_call hook blocks the monitor
start tool. monitor_list / monitor_stop / monitor_read_stderr
remain available — you can still triage what's already running.
Permissions
There is no per-spawn confirm. The LLM is trusted the same way as for
bash. Safety rails are structural:
- session-scoped processes (children die with pi)
MAX_CONCURRENT = 8- firehose auto-stop at 50 lines/s over 10 s
--no-monitorto disable the entire extension for a session
Settings (module constants in index.ts)
BATCH_WINDOW_MS = 200 // coalesce window
PER_EVENT_MAX_CHARS = 400 // per-event truncation cap
DEFAULT_TIMEOUT_MS = 300_000 // 5 min
MAX_TIMEOUT_MS = 3_600_000 // 1 hr
MAX_CONCURRENT = 8
RATE_LIMIT_LINES_PER_SEC = 50
RATE_LIMIT_WINDOW_SEC = 10
STDERR_DIR = ~/.pi/agent/monitor
Running the unit tests
cd /path/to/pi-monitor
npm test
Known limitations
- Children do not survive pi restart — by design.
- Does not inherit pi's bash allow/deny settings. Disable the whole
extension for a session with
--no-monitorif needed. - Windows untested; uses
process.env.SHELL || "/bin/sh".
Inspired by — and behavior-compatible with — the Monitor tool in Claude Code.
Share your own pi extension by publishing it to npm with the
pi-package keyword — pi's
package gallery auto-indexes it.