- HTML 49.7%
- TypeScript 39.1%
- Rust 10.4%
- JavaScript 0.8%
| crates/monitor-core | ||
| docs/research | ||
| graphify-out | ||
| gui | ||
| reporter | ||
| src | ||
| src-tauri | ||
| test | ||
| .gitignore | ||
| AGENTS.md | ||
| Cargo.lock | ||
| Cargo.toml | ||
| LICENSE | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
pi-monitor
A local, live desktop GUI, CLI and TUI monitor for the Pi coding agent. It listens to the global Pi activity-reporter extension and renders what Pi is doing without replacing Pi's normal interactive interface.
The interactive dashboard monitors multiple Pi sessions, shows running tools, keeps a per-session timeline, tracks cumulative input/output tokens and the active model's context-window size, and aggregates tool activity:
pi-monitor ● 2 connected 3 sessions 2 working
──────────────────────────────────────────────────────────────────────────────
SESSIONS RECENT EVENTS · FOLLOW CURRENT ACTIVITY
> ● pi-monitor 12.4s 03:50:09 ↳ EDIT Editing… Tokens: ↑123.5k ↓8.2k · ctx 200k
○ Bagsy IDLE 03:50:08 ✓ READ Completed… WORKING · 12.4s
● website 4.1s 03:50:04 ↳ READ Reading… Tools: 8/9
──────────────────────────────────────────────────────────────────────────────
↑↓/jk move tab panel space pause f follow ? help q quit
The line-oriented watcher remains available for scripts and log-oriented use:
03:49:48 ○ SESSION [my-project] Pi session started
03:50:02 ? PROMPT [my-project] Asked Pi: Refactor the authentication service
03:50:02 ● WORK [my-project] Pi started working
03:50:04 ↳ READ [my-project] Reading src/AuthService.ts
03:50:05 ✓ READ [my-project] Completed read · 84ms
03:50:08 ↳ EDIT [my-project] Editing src/AuthService.ts
03:50:09 ✓ EDIT [my-project] Completed edit · 112ms
03:50:12 ✓ DONE [my-project] Pi finished working · 10.2s
Desktop GUI (Tauri + Rust)
The desktop app uses a native Rust collector, not a Node sidecar. The existing TypeScript reporter and CLI/TUI remain compatible during migration.
npm install
npm run desktop:dev # native window with Vite development frontend
npm run desktop:build # production application bundle
Desktop development requires Rust/Cargo and the Tauri 2 platform prerequisites
(Xcode Command Line Tools on macOS; WebKitGTK/build dependencies on Linux).
This version supports macOS and Linux, using Unix sockets. Windows named
pipes are not implemented. Close any CLI/TUI collector using the same socket
before opening the desktop app; only one collector may own a socket.
PI_ACTIVITY_SOCKET overrides the socket path; PI_MONITOR_PROJECT restricts
collection to a project directory and its descendants.
Desktop boundaries
crates/monitor-core: UI-independent protocol validation, reducer, Unix-socket actor, disconnect-grace maintenance and shutdown. Bounded JSONL records, owner-scoped tool IDs and reporter reconnect snapshots retain existing semantics.src-tauri: thin lifecycle/IPC adapter.monitor_snapshotreturns a detached JSON snapshot;monitor-updatepublishes complete replacement snapshots;dismiss_sessionis the only shared-state mutation. Monotonicrevisionprevents delayed IPC responses from overwriting newer frontend state.gui: React dashboard using framework-neutralsrc/dashboard-policy.ts. Filters, selection, sorting, timeline/follow and completion acknowledgement are local to each view. Acknowledge carries the observed completion ID and does not mutate collector state. Pausing freezes the display, not ingestion.
Snapshots omit arbitrary event data (including exact tool I/O), but retain summaries and prompt previews. Socket permissions are owner-only. The Rust collector uses a persistent advisory lock file alongside its socket and only removes the socket inode it owns on shutdown.
Validation
npm run check
npm test
npm run gui:build
cargo test -p monitor-core
cargo check -p pi-monitor-desktop
Rust reducer parity fixtures compare four event traces step-by-step against the
TypeScript reducer. Regenerate them with npx tsx test/rust-parity.ts when
intentionally changing reducer semantics. Real socket tests cover framing,
UTF-8 fragmentation, project filtering, malformed/oversized records, stale
socket recovery, exclusive ownership and shutdown cleanup.
Requirements
- Node.js 22.19 or newer
- The global Pi activity-reporter extension at
~/.pi/agent/extensions/activity-reporter/
The extension sends versioned JSONL activity over a user-specific local socket.
It reports cumulative input, output, cache-read, and cache-write token counts
along with the active model's configured context-window size, without sending
message contents. Exact tool inputs and results are hidden by default; set
PI_ACTIVITY_INCLUDE_TOOL_IO=1 in Pi's environment to publish them. This can
expose file contents, commands, credentials, and other sensitive data.
Shell-command previews are hidden unless explicitly enabled in the extension
environment.
Install from this checkout
npm install
npm run build
npm link
This installs the pi-monitor command using a symlink to the checkout.
Usage
Open the interactive dashboard before or after Pi:
pi-monitor tui
pi-monitor dashboard is an alias. The extension reconnects automatically, so
already-running Pi sessions will begin reporting once the monitor is available.
The dashboard uses these keys:
↑/↓ or j/k Select a session or scroll its timeline
Tab Switch between sessions and timeline
d Dismiss the selected stopped or unavailable session
s Toggle attention-first / status sorting
v Cycle all / working / errors / done / quiet filters
a Acknowledge the selected session's completion
Space Pause/resume visual updates (events continue buffering)
f Follow the newest event
? Show help
q Quit
Sessions use two lines: name and attention badges, followed by running-tool count, longest-running tool duration, and time since the last event (or lifecycle status when no tool is running). Attention sorting is the default: recent tool errors first, then unacknowledged completions, then quiet working sessions; remaining ties retain status/recency order. Selection stays on the same session when ordering changes, unless it no longer matches the active filter.
ERROR means a failed tool event in the last five minutes, not necessarily an
unresolved failure. DONE means an observed agent.settled event that has not
been explicitly acknowledged with a; simply selecting a session does not clear
it. Starting another run clears the old completion. quiet means a working
session has emitted no event for at least two minutes, not that it is stuck or
disconnected. Ages use reporter timestamps. Badges and acknowledgement are
in-memory and do not survive monitor restart. Filtered-out sessions continue
collecting events, and the header counts all tracked sessions.
Reporter health is separate from its last reported lifecycle state. Socket
closure immediately shows reconnecting. After a five-second grace period,
without an event on a replacement connection, the reporter becomes unavailable.
Unavailable reporters no longer contribute running tools or live-working counts
and are excluded from working/quiet filters. Their last reported lifecycle state
and history remain visible; disconnect never creates a completion or DONE.
Health maintenance continues while visual updates are paused and uses local
monotonic time, independent of reporter timestamps.
Ownership is scoped to reporter instance ID and PID within each logical session; tool calls and lifecycle state are isolated per reporter. A newer connection for the same reporter supersedes the old connection. An accepted event, including a duplicate replay, restores ownership without replaying metrics. The reporter sends an authoritative snapshot after queued replay on every connection, including silent reconnects. Snapshots repair lifecycle state and running tools without inventing completion notifications or tool metrics. Monotonic per-instance sequence numbers prevent old replay from reversing the snapshot. Snapshots contain model/token totals and running-tool metadata, never exact inputs or outputs, and do not reset activity age or enter event history. Queue overflow triggers a fresh snapshot after surviving replay drains. An open but silent socket is not treated as unavailable. This is connection health, not a process-liveness or heartbeat check.
d dismisses a stopped or unavailable session from the current dashboard; it
does not delete Pi's session file. The header still includes retained history in
its total session count. The reporter source is maintained in reporter/. Install both files and reload
or restart Pi to activate reconnect snapshots:
mkdir -p ~/.pi/agent/extensions/activity-reporter
cp reporter/index.ts reporter/transport.ts ~/.pi/agent/extensions/activity-reporter/
Legacy reporters still work, but cannot provide snapshot recovery. Reporter shutdown drains an established connection for up to 250 ms; offline shutdown still relies on disconnect health rather than guaranteed delivery.
For the existing line-oriented stream, run:
pi-monitor
# or: pi-monitor watch
Filter to one project
The selected directory and nested working directories are included:
pi-monitor --project .
Machine-readable JSONL
pi-monitor --json
JSON mode validates each event against activity protocol version 1 before
writing it to stdout. Monitor diagnostics remain on stderr. When exact tool I/O
is enabled in the reporter, tool.started.data.input contains the call arguments
and tool.completed.data contains both input and output. JSON mode preserves
them exactly. The human-readable watch view shows compact representations
fitted to the terminal width; the TUI shows only the tool event summary and
never renders exact call arguments or results.
Options
Commands:
watch Stream line-oriented activity (default)
tui Open the interactive activity dashboard
Options:
--socket <path> Override the activity socket
--project <path> Show only activity under this project directory
--json Emit validated JSONL (watch only)
--no-color Disable ANSI colors
-h, --help Show help
-v, --version Show version
The default socket is /tmp/pi-activity-<uid>.sock. Both the monitor and Pi
extension honor PI_ACTIVITY_SOCKET.
To monitor exact tool calls and their output, start Pi with:
PI_ACTIVITY_INCLUDE_TOOL_IO=1 pi
Use pi-monitor --json when the complete, untruncated values are required.
UI-independent architecture
src/monitor-service.ts owns the local activity socket, project filtering,
activity store, connection-health maintenance, and asynchronous start/stop.
It has no terminal or GUI dependency. The TUI uses this service; the raw watch
stream remains unchanged.
snapshot()returns detached, JSON-native monitor state (schema version 1). Tool entries preserve their owner-scoped keys; metric categories are arrays. Event history excludes arbitrarydata, including opt-in exact tool I/O. Prompt and tool summaries remain visible, so snapshots are not fully redacted.subscribe(listener)delivers complete snapshots on activity, health, client count, warning, and dismissal changes. It returns an unsubscribe function; obtain initial state explicitly withsnapshot(). Subscriber exceptions are isolated and reentrant updates are queued. Elapsed-time displays still need their own UI clock: silence does not publish periodic snapshots.dispatch({ type: "dismiss", key })dismisses only stopped/unavailable sessions.start()andstop()serialize lifecycle transitions and are idempotent. Stopping disconnects retained owners; restarting reuses retained history.
src/dashboard-policy.ts provides a framework-neutral DashboardController
with typed filter, sort, select, move, scroll, follow, acknowledge, and dismiss
commands. Acknowledgement includes the observed completion ID, so stale clients
cannot acknowledge a newer completion. Filters, selection, and acknowledgements
are per-view; dismissal changes shared collector state. A GUI can adapt snapshot
sessions to its DashboardSource interface without importing terminal code.
The controller owns status/recency ordering (including attention tie-breaking),
so adapters need not pre-sort sessions. Synchronous adapters supply
removeStoppedSession; remote adapters call prepareDismissal(key) before
mutation and invoke its returned completion only after success. The controller
owns acknowledgement cleanup and successor selection, waits for removal to reach
the displayed snapshot, and does not overwrite navigation made during the request.
src/dashboard.ts retains keyboard decoding, ANSI styling, responsive terminal
layout, help, and visual pause. Its interaction policies delegate to the shared
controller. src/tui-app.ts only connects the service to terminal lifecycle.
The service's store-shaped accessors are an in-process compatibility adapter
for the TUI; external consumers should use snapshots and commands.
These boundaries do not add network access or change the reporter protocol. Only one collector can own a given socket; multiple future views must share it.
Safety and lifecycle
- The socket is created with mode
0600. - A second monitor refuses to replace an active monitor's socket.
- A stale socket is detected and removed.
- A non-socket filesystem path is never removed.
SIGINTandSIGTERMclose clients and remove the owned socket.- Malformed, oversized, and unsupported-version events are ignored with a warning.
Development
npm run check
npm test
npm run build
The dashboard uses @earendil-works/pi-tui for differential terminal
rendering. Tests use Node's test runner via tsx.