Skip to content

Repository files navigation

AgentPeeper

A native macOS app for real-time monitoring of Claude Code and Pi coding agent sessions.

License: MIT

Quick Start

git clone https://github.com/onorbumbum/agent-peeper.git
cd agent-peeper
npm install
npm start

On first launch, click "Setup Monitoring" to install the Claude Code hooks. Pi sessions are discovered automatically — no setup required.

For detailed installation instructions (including Windows/Linux), see INSTALL.md.

macOS Gatekeeper Notice

If you build and install the app to /Applications, macOS may block it with "app is damaged" or "unidentified developer" warnings. This is because the app isn't notarized with Apple.

Fix it with one command:

xattr -cr "/Applications/AgentPeeper.app"

Or: Right-click the app → Open → Click "Open" in the dialog.

Screenshots

Setup Dashboard
Setup screen Dashboard

Left: First-run setup automatically installs monitoring hooks. Right: Dashboard ready to monitor sessions.

Why This Exists

Problem: When running Claude Code or Pi in headless mode (via automation, CLI, or -p flag), there's no visibility into what the agent is doing — no way to see its thinking, tool calls, or responses in real-time.

Solution: This Electron app reads session transcript files directly from disk and displays them in a live-updating dashboard. No server required. Works with both Claude Code and Pi coding agent.

Architecture

┌──────────────────────────┐     ┌──────────────────────────┐
│      Claude Code         │     │    Pi Coding Agent       │
│  (interactive or -p)     │     │  (interactive or -p)     │
└───────────┬──────────────┘     └───────────┬──────────────┘
            │ writes                         │ writes
            ▼                                ▼
┌──────────────────────────┐     ┌──────────────────────────┐
│      ~/.claude/          │     │  ~/.pi/agent/sessions/   │
│  projects/[proj]/        │     │  [encoded-cwd]/          │
│    [session-id].jsonl    │     │    [ts]_[uuid].jsonl     │
└───────────┬──────────────┘     └───────────┬──────────────┘
            │ reads (fs polling)             │ reads (fs polling)
            └───────────┬────────────────────┘
                        ▼
┌─────────────────────────────────────────────────────────────┐
│                AgentPeeper.app                                │
│  ├── main.js      ← Electron main, IPC, session discovery   │
│  ├── preload.js   ← Secure context bridge                   │
│  └── index.html   ← Dashboard UI, unified rendering         │
└─────────────────────────────────────────────────────────────┘

How It Works

Session Discovery

The watcher uses three discovery mechanisms that run in parallel:

  1. Claude Code hooks (interactive mode) — monitor.js hook script maintains ~/.claude/active-sessions.json on every tool call
  2. Claude Code filesystem polling (headless -p mode) — scans ~/.claude/projects/ for .jsonl files modified in the last 5 minutes. This catches sessions where hooks don't fire (e.g., claude -p "prompt")
  3. Pi filesystem polling — scans ~/.pi/agent/sessions/ for recently-modified .jsonl files. No hooks or setup needed

All discovered sessions are merged and deduplicated. Each session tab shows a source badge: CC (blue) for Claude Code or π (violet) for Pi.

Transcript Formats

Claude Code transcripts (.jsonl):

{"type": "assistant", "message": {"content": [{"type": "text", "text": "..."}]}, "timestamp": "..."}
{"type": "assistant", "message": {"content": [{"type": "tool_use", "name": "Bash", "input": {...}}]}}
{"type": "user", "message": {"content": [{"type": "tool_result", "content": "..."}]}}

Pi transcripts (.jsonl with tree structure):

{"type": "session", "version": 3, "id": "uuid", "cwd": "/path/to/project"}
{"type": "message", "id": "abc", "parentId": null, "message": {"role": "user", "content": [...]}}
{"type": "message", "id": "def", "parentId": "abc", "message": {"role": "assistant", "content": [...]}}
{"type": "message", "id": "ghi", "parentId": "def", "message": {"role": "toolResult", "toolName": "read", "content": [...]}}
{"type": "model_change", "id": "jkl", "parentId": "ghi", "modelId": "claude-opus-4"}

Pi entries are normalized to Claude Code format before rendering — toolCalltool_use, toolResulttool_result, etc. — so all rendering code is shared.

Pi's tree structure (branching) is resolved by following the latest leaf path.

Dashboard Polling

The Electron app:

  1. Polls all three discovery sources every 5 seconds
  2. Polls each session's transcript every 2 seconds
  3. Renders messages with newest first
  4. Shows unified tabs with source badges for multiple concurrent sessions

Features

  • Multi-Agent Support: Monitor both Claude Code and Pi coding agent sessions
  • Headless Mode Support: Filesystem polling catches sessions from -p mode where hooks don't fire
  • Setup Screen: First-run experience that automatically installs Claude Code monitoring hooks
  • Settings Menu: Install/uninstall hooks, switch themes
  • Dark/Light Theme: Toggle between themes via settings dropdown
  • Multi-Session Tabs: Monitor multiple concurrent sessions with source badges (CC / π)
  • Pi Inline Indicators: Subtle indicators for model changes and thinking level changes
  • Live Updates: Real-time polling with pause/resume controls
  • Zero Pi Setup: Pi sessions are discovered automatically via filesystem — no hooks or extensions needed

File Structure

agent-peeper/
├── package.json        # App config, build scripts
├── main.js             # Electron main process
│                       # - Creates BrowserWindow with secure config
│                       # - IPC handlers for CC and Pi sessions
│                       # - Filesystem scanners for both agents
│                       # - Embedded monitor.js script content
├── preload.js          # Secure context bridge for IPC
├── index.html          # Dashboard UI (single file)
│                       # - Styles (CSS variables, dark/light themes)
│                       # - Setup screen for first-run
│                       # - Settings dropdown (hooks, theme)
│                       # - Pi transcript normalization + tree resolution
│                       # - Unified rendering (messages, tools, tabs)
│                       # - Polling logic (3 discovery sources)
├── logo.png            # App icon (PNG)
├── icon.icns           # macOS app icon
├── screenshot-1.png    # Setup screen screenshot
├── screenshot-2.png    # Dashboard screenshot
├── LICENSE             # MIT license
├── CONTRIBUTING.md     # Contribution guidelines
├── CODE_OF_CONDUCT.md  # Community guidelines
├── CHANGELOG.md        # Version history
├── dist/               # Built app output (git-ignored)
│   └── mac/
│       └── AgentPeeper.app
└── README.md           # This file

~/.claude/                          # Claude Code data
├── settings.json                   # Hooks config
├── monitor.js                      # Hook script (installed by app)
├── active-sessions.json            # Hook-based session registry
└── projects/[project-path]/
    └── [session-id].jsonl          # CC transcripts

~/.pi/agent/sessions/               # Pi data (read-only)
└── [encoded-cwd]/
    └── [timestamp]_[uuid].jsonl    # Pi transcripts

Development

Run in Development

npm start

Build macOS App

npm run build:dir
# Output: dist/mac-arm64/AgentPeeper.app

Install to Applications

rm -rf "/Applications/AgentPeeper.app"
cp -R dist/mac-arm64/AgentPeeper.app /Applications/

Key Implementation Details

IPC Communication

The renderer (index.html) cannot access the filesystem directly. It uses a secure preload script:

// preload.js - exposes safe API via contextBridge
contextBridge.exposeInMainWorld('api', {
  readSessions: () => ipcRenderer.invoke('read-sessions'),
  readCcSessionsFs: () => ipcRenderer.invoke('read-cc-sessions-fs'),
  readPiSessions: () => ipcRenderer.invoke('read-pi-sessions'),
  // ...
});

Available IPC handlers:

  • check-setup - Returns setup status (hooks configured, script exists)
  • run-setup - Installs monitor.js and configures hooks
  • run-uninstall - Removes hooks and monitor script
  • read-sessions - Returns hook-based active sessions JSON
  • read-cc-sessions-fs - Returns filesystem-discovered CC sessions
  • read-transcript - Returns CC transcript file content
  • read-pi-sessions - Returns filesystem-discovered Pi sessions
  • read-pi-transcript - Returns Pi transcript file content

Pi Transcript Normalization

Pi uses a different JSONL format than Claude Code. The dashboard normalizes Pi entries to the CC format:

Pi format Normalized to CC format
type: "toolCall" with arguments type: "tool_use" with input
Separate role: "toolResult" message Inline type: "tool_result" block
role: "bashExecution" Tool result with command + output
type: "model_change" Inline indicator (↻ Switched to ...)
type: "thinking_level_change" Inline indicator (↻ Thinking: ...)
type: "compaction" Inline indicator (↻ Context compacted)

Pi Tree Resolution

Pi sessions use a tree structure with id/parentId for branching. The watcher resolves this by:

  1. Building a parent → children map
  2. Finding all leaf nodes (entries with no children)
  3. Selecting the leaf with the latest timestamp
  4. Walking from leaf to root to get the active branch

Session Expiry

Sessions disappear from the dashboard after 5 minutes of inactivity:

  • CC hooks: Cleaned from active-sessions.json when new hook events fire
  • CC filesystem: File mtime checked against 5-minute cutoff
  • Pi filesystem: Same mtime-based cutoff

Content Format Handling

Transcript messages have message.content as either:

  • A string (legacy format)
  • An array of blocks (modern format)

The dashboard handles both:

let content = msg.message?.content;
if (typeof content === 'string') content = [{ type: 'text', text: content }];
else if (!Array.isArray(content)) content = [];

Troubleshooting

No sessions appearing

  1. For CC (interactive): Check hooks are installed via Settings menu
  2. For CC (headless): Verify .jsonl files exist in ~/.claude/projects/ and are being written to
  3. For Pi: Verify .jsonl files exist in ~/.pi/agent/sessions/
  4. Sessions expire after 5 minutes — the agent must be actively running

App shows empty messages

  1. Check the transcript file exists at the path shown
  2. Verify transcript has valid JSONL entries
  3. Check browser console (Cmd+Option+I) for errors

Hooks not firing (CC interactive mode)

  1. Verify ~/.claude/monitor.js exists
  2. Test hook manually: echo '{"session_id":"test"}' | node ~/.claude/monitor.js pre_tool
  3. Check ~/.claude/events.jsonl for logged events
  4. Re-install hooks via Settings menu
  5. Note: Hooks don't fire in -p mode — the filesystem scanner handles that

Dependencies

  • Electron 28.x - Cross-platform desktop app framework
  • electron-builder - Packaging for macOS .app
  • Node.js fs - Reading transcript files

No external dependencies like jq required — the monitor script is pure Node.js.

Context for AI Agents

When working on this codebase:

  1. The app reads files, never writes them — it's a passive monitor (except for hook setup)
  2. Three discovery mechanisms — CC hooks, CC filesystem scan, Pi filesystem scan
  3. Hooks don't fire in -p mode — filesystem polling is the fallback for headless sessions
  4. Pi entries are normalized to CC format — all rendering code is shared (DRY)
  5. Pi tree structure is resolved to follow the latest leaf branch
  6. Transcript format varies — always handle both string and array content
  7. No server needed — Electron's Node.js integration reads files directly
  8. Session registry is ephemeral — rebuilt from hooks/filesystem on each poll
  9. UI is single-file — all styles, scripts, and markup in index.html for simplicity
  10. Setup is automatic for CC — hooks installed on first run; Pi needs no setup

The design prioritizes KISS:

  • Single HTML file for the entire UI
  • No build step for the frontend (no React, no bundler)
  • No database (just JSON files on disk)
  • No server (Electron reads filesystem directly)
  • No external dependencies for the hook script (pure Node.js)
  • Pi support requires zero configuration

Contributing

Contributions are welcome! Please read CONTRIBUTING.md for guidelines and CODE_OF_CONDUCT.md for community standards.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Changelog

See CHANGELOG.md for version history.

About

A simple native app for real-time monitoring of Claude Code sessions

Topics

Resources

Code of conduct

Contributing

Stars

8 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages